File indexing completed on 2024-05-05 17:19:07

0001 /***************************************************************************
0002  * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr
0003  * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  ***************************************************************************/
0006 /** @file
0007  * A table with graph with more features.
0008  *
0009  * @author Stephane MANKOWSKI / Guillaume DE BURE
0010  */
0011 #include "skgtablewithgraph.h"
0012 
0013 #include <kcolorscheme.h>
0014 #include <kstringhandler.h>
0015 
0016 #include <qcollator.h>
0017 #include <qdesktopservices.h>
0018 #include <qdom.h>
0019 #include <qfileinfo.h>
0020 #include <qgraphicseffect.h>
0021 #include <qgraphicsitem.h>
0022 #include <qheaderview.h>
0023 #include <qmath.h>
0024 #include <qmenu.h>
0025 #include <qpainter.h>
0026 #include <qprinter.h>
0027 #include <qsavefile.h>
0028 #include <qscriptengine.h>
0029 #include <qscrollbar.h>
0030 #include <qtablewidget.h>
0031 #include <qtextcodec.h>
0032 #include <qtimer.h>
0033 #include <qwidgetaction.h>
0034 
0035 #include <algorithm>
0036 
0037 #include "skgcolorbutton.h"
0038 #include "skgcombobox.h"
0039 #include "skggraphicsscene.h"
0040 #include "skgmainpanel.h"
0041 #include "skgservices.h"
0042 #include "skgtraces.h"
0043 #include "skgtreemap.h"
0044 
0045 /**
0046   * Data identifier for value
0047   */
0048 static const int DATA_VALUE = 12;
0049 /**
0050   * Data identifier for color
0051   */
0052 static const int DATA_COLOR_H = 11;
0053 /**
0054   * Data identifier for color
0055   */
0056 static const int DATA_COLOR_S = 12;
0057 /**
0058   * Data identifier for color
0059   */
0060 static const int DATA_COLOR_V = 13;
0061 /**
0062   * Data identifier for Z value
0063   */
0064 static const int DATA_Z_VALUE = 14;
0065 /**
0066   * Data identifier for mode
0067   */
0068 static const int DATA_MODE = 15;
0069 /**
0070   * Alpha value
0071   */
0072 static const int ALPHA = 200;
0073 /**
0074   * Box size
0075   */
0076 static const double BOX_SIZE = 120.0;
0077 /**
0078   * Box margin
0079   */
0080 static const double BOX_MARGIN = 10.0;
0081 
0082 #define cloneAction(MENU, ACTION, SLOTTOCALL) \
0083     auto act = (MENU)->addAction((ACTION)->icon(), (ACTION)->text()); \
0084     act->setCheckable(true); \
0085     act->setChecked((ACTION)->isChecked()); \
0086     connect(act, &QAction::triggered, this, SLOTTOCALL); \
0087     connect(act, &QAction::toggled, ACTION, &QAction::setChecked); \
0088     connect(ACTION, &QAction::toggled, act, &QAction::setChecked);
0089 
0090 SKGTableWithGraph::SKGTableWithGraph() : SKGTableWithGraph(nullptr)
0091 {}
0092 
0093 SKGTableWithGraph::SKGTableWithGraph(QWidget* iParent)
0094     : QWidget(iParent), m_scene(nullptr), m_additionalInformation(NONE), m_nbVirtualColumns(0),
0095       m_selectable(true), m_toolBarVisible(true), m_graphTypeVisible(true), m_limitVisible(true), m_averageVisible(true),
0096       m_linearRegressionVisible(true), m_paretoVisible(false), m_legendVisible(false), m_graphVisible(true), m_tableVisible(true), m_textVisible(false), m_zeroVisible(true), m_decimalsVisible(true), m_shadow(true),
0097       m_mainMenu(nullptr),
0098       m_indexSum(-1), m_indexAverage(-1), m_indexMin(-1), m_indexLinearRegression(-1), m_sortOrder(Qt::AscendingOrder), m_sortColumn(0)
0099 {
0100     m_axisColor = Qt::gray;
0101     m_gridColor = Qt::lightGray;
0102     m_minColor = Qt::red;
0103     m_maxColor = Qt::green;
0104     m_paretoColor = Qt::darkRed;
0105     m_averageColor = Qt::blue;
0106     m_tendencyColor = Qt::darkYellow;
0107     m_backgroundColor = Qt::white;
0108     m_textColor = Qt::black;
0109     m_NegativeColor = KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText);
0110     m_WhiteColor = QBrush(Qt::white);
0111 
0112     ui.setupUi(this);
0113     ui.kTextEdit->hide();
0114 
0115     ui.kFilterEdit->setPlaceholderText(i18n("Search"));
0116 
0117     m_displayMode = new SKGComboBox(this);
0118     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-bar-stacked")), i18nc("Noun, a type of graph, with bars stacked upon each other", "Stack of lines"), static_cast<int>(STACK));
0119     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-bar-stacked")), i18nc("Noun, a type of graph, with bars stacked upon each other", "Stack of columns"), static_cast<int>(STACKCOLUMNS));
0120     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-bar")), i18nc("Noun, a type of graph, with bars placed besides each other", "Histogram"), static_cast<int>(HISTOGRAM));
0121     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-scatter")), i18nc("Noun, a type of graph with only points", "Point"), static_cast<int>(POINT));
0122     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-line")), i18nc("Noun, a type of graph with only lines", "Line"), static_cast<int>(LINE));
0123     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-area-stacked")), i18nc("Noun, a type of graph, with lines stacked upon each other", "Stacked area"), static_cast<int>(STACKAREA));
0124     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("skg-chart-bubble")), i18nc("Noun, a type of graph, with bubbles", "Bubble"), static_cast<int>(BUBBLE));
0125     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-pie")), i18nc("Noun, a type of graph that looks like a sliced pie", "Pie"), static_cast<int>(PIE));
0126     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-ring")), i18nc("Noun, a type of graph that looks like concentric slices of a pie (a la filelight)", "Concentric pie"), static_cast<int>(CONCENTRICPIE));
0127     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("map-flat")), i18nc("Noun, a type of graph that looks treemap", "Treemap"), static_cast<int>(TREEMAP));
0128 
0129     ui.graphicView->addToolbarWidget(m_displayMode);
0130 
0131     ui.kShow->addItem(QStringLiteral("table"), i18n("Table"),               QStringLiteral("view-list-details"), QString(), QString(),            QStringLiteral("text"),        QStringLiteral("graph"), QString(), Qt::META + Qt::Key_T);
0132     ui.kShow->addItem(QStringLiteral("graph"), i18n("Graph"),               QStringLiteral("office-chart-pie"), QString(), QString(),                QStringLiteral("text"),        QStringLiteral("table"), QString(), Qt::META + Qt::Key_G);
0133     ui.kShow->addItem(QStringLiteral("text"), i18n("Text"),                 QStringLiteral("view-list-text"), QString(), QString(),               QStringLiteral("table;graph"), QStringLiteral("table;graph"), QString(), Qt::META + Qt::Key_R);
0134 
0135     ui.kShow->setDefaultState(QStringLiteral("table;graph"));
0136 
0137     connect(ui.kShow, &SKGShow::stateChanged, this, &SKGTableWithGraph::onDisplayModeChanged, Qt::QueuedConnection);
0138 
0139     m_timer.setSingleShot(true);
0140     connect(&m_timer, &QTimer::timeout, this, &SKGTableWithGraph::refresh, Qt::QueuedConnection);
0141 
0142     m_timerRedraw.setSingleShot(true);
0143     connect(&m_timerRedraw, &QTimer::timeout, this, &SKGTableWithGraph::redrawGraph, Qt::QueuedConnection);
0144 
0145     // Build contextual menu
0146     ui.kTable->setContextMenuPolicy(Qt::CustomContextMenu);
0147     connect(ui.kTable, &SKGTableWidget::customContextMenuRequested, this, &SKGTableWithGraph::showMenu);
0148 
0149     m_mainMenu = new QMenu(ui.kTable);
0150 
0151     QAction* actExport = m_mainMenu->addAction(SKGServices::fromTheme(QStringLiteral("document-export")), i18nc("Noun, user action", "Export…"));
0152     connect(actExport, &QAction::triggered, this, &SKGTableWithGraph::onExport);
0153 
0154     // Add graph mode in menu
0155     getGraphContextualMenu()->addSeparator();
0156     auto displayModeMenu = new SKGComboBox(this);
0157     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-bar-stacked")), i18nc("Noun, a type of graph, with bars stacked upon each other", "Stack of lines"), static_cast<int>(STACK));
0158     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-bar-stacked")), i18nc("Noun, a type of graph, with bars stacked upon each other", "Stack of columns"), static_cast<int>(STACKCOLUMNS));
0159     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-bar")), i18nc("Noun, a type of graph, with bars placed besides each other", "Histogram"), static_cast<int>(HISTOGRAM));
0160     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-scatter")), i18nc("Noun, a type of graph with only points", "Point"), static_cast<int>(POINT));
0161     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-line")), i18nc("Noun, a type of graph with only lines", "Line"), static_cast<int>(LINE));
0162     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-area-stacked")), i18nc("Noun, a type of graph, with lines stacked upon each other", "Stacked area"), static_cast<int>(STACKAREA));
0163     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("skg-chart-bubble")), i18nc("Noun, a type of graph, with bubbles", "Bubble"), static_cast<int>(BUBBLE));
0164     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-pie")), i18nc("Noun, a type of graph that looks like a sliced pie", "Pie"), static_cast<int>(PIE));
0165     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-ring")), i18nc("Noun, a type of graph that looks like concentric slices of a pie (a la filelight)", "Concentric pie"), static_cast<int>(CONCENTRICPIE));
0166     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("map-flat")), i18nc("Noun, a type of graph that looks treemap", "Treemap"), static_cast<int>(TREEMAP));
0167 
0168     m_displayModeWidget = new QWidgetAction(this);
0169     m_displayModeWidget->setDefaultWidget(displayModeMenu);
0170     getGraphContextualMenu()->addAction(m_displayModeWidget);
0171 
0172     connect(displayModeMenu, static_cast<void (SKGComboBox::*)(int)>(&SKGComboBox::currentIndexChanged), m_displayMode, &SKGComboBox::setCurrentIndex);
0173     connect(m_displayMode, static_cast<void (SKGComboBox::*)(int)>(&SKGComboBox::currentIndexChanged), displayModeMenu, &SKGComboBox::setCurrentIndex);
0174 
0175     // Reset Colors
0176     QAction* resetColorsAction = m_mainMenu->addAction(SKGServices::fromTheme(QStringLiteral("format-fill-color")), i18nc("Noun, user action", "Reset default colors"));
0177     connect(resetColorsAction, &QAction::triggered, this, &SKGTableWithGraph::resetColors);
0178     getGraphContextualMenu()->addAction(resetColorsAction);
0179 
0180     // Add item in menu of the graph
0181     m_allPositiveMenu = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("go-up-search")), i18nc("Noun, user action", "All values in positive"));
0182     if (m_allPositiveMenu != nullptr) {
0183         m_allPositiveMenu->setCheckable(true);
0184     }
0185     connect(m_allPositiveMenu, &QAction::toggled, this, &SKGTableWithGraph::redrawGraphDelayed, Qt::QueuedConnection);
0186 
0187     // Add item in menu of the graph
0188     m_actShowLimits = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show limits"));
0189     if (m_actShowLimits != nullptr) {
0190         m_actShowLimits->setCheckable(true);
0191         m_actShowLimits->setChecked(m_limitVisible);
0192         connect(m_actShowLimits, &QAction::triggered, this, &SKGTableWithGraph::switchLimitsVisibility);
0193 
0194         // Clone action on table contextual menu
0195         cloneAction(m_mainMenu, m_actShowLimits, &SKGTableWithGraph::switchLimitsVisibility)
0196     }
0197 
0198     // Add item in menu of the graph
0199     m_actShowAverage = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show average"));
0200     if (m_actShowAverage != nullptr) {
0201         m_actShowAverage->setCheckable(true);
0202         m_actShowAverage->setChecked(m_averageVisible);
0203         connect(m_actShowAverage, &QAction::triggered, this, &SKGTableWithGraph::switchAverageVisibility);
0204 
0205         // Clone action on table contextual menu
0206         cloneAction(m_mainMenu, m_actShowAverage, &SKGTableWithGraph::switchAverageVisibility)
0207     }
0208 
0209     // Add item in menu of the graph
0210     m_actShowLinearRegression = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show tendency line"));
0211     if (m_actShowLinearRegression != nullptr) {
0212         m_actShowLinearRegression->setCheckable(true);
0213         m_actShowLinearRegression->setChecked(m_linearRegressionVisible);
0214         connect(m_actShowLinearRegression, &QAction::triggered, this, &SKGTableWithGraph::switchLinearRegressionVisibility);
0215 
0216         // Clone action on table contextual menu
0217         cloneAction(m_mainMenu, m_actShowLinearRegression, &SKGTableWithGraph::switchLinearRegressionVisibility)
0218     }
0219 
0220     // Add item in menu of the graph
0221     m_actShowPareto = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show Pareto curve"));
0222     if (m_actShowPareto != nullptr) {
0223         m_actShowPareto->setCheckable(true);
0224         m_actShowPareto->setChecked(m_paretoVisible);
0225         connect(m_actShowPareto, &QAction::triggered, this, &SKGTableWithGraph::switchParetoVisibility);
0226     }
0227 
0228     // Add item in menu of the graph
0229     m_actShowLegend = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("help-contents")), i18nc("Noun, user action", "Show legend"));
0230     if (m_actShowLegend != nullptr) {
0231         m_actShowLegend->setCheckable(true);
0232         m_actShowLegend->setChecked(m_legendVisible);
0233         connect(m_actShowLegend, &QAction::triggered, this, &SKGTableWithGraph::switchLegendVisibility);
0234     }
0235 
0236     // Add item in menu of the graph
0237     m_actShowZero = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("labplot-xy-plot-two-axes-centered-origin")), i18nc("Noun, user action", "Show origin"));
0238     if (m_actShowZero != nullptr) {
0239         m_actShowZero->setCheckable(true);
0240         m_actShowZero->setChecked(m_zeroVisible);
0241         connect(m_actShowZero, &QAction::triggered, this, &SKGTableWithGraph::swithOriginVisibility);
0242     }
0243 
0244     // Add item in menu of the graph
0245     m_actShowDecimal = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("format-precision-less")), i18nc("Noun, user action", "Show decimals"));
0246     if (m_actShowDecimal != nullptr) {
0247         m_actShowDecimal->setCheckable(true);
0248         m_actShowDecimal->setChecked(m_decimalsVisible);
0249         connect(m_actShowDecimal, &QAction::triggered, this, &SKGTableWithGraph::swithDecimalsVisibility);
0250 
0251         // Clone action on table contextual menu
0252         cloneAction(m_mainMenu, m_actShowDecimal, &SKGTableWithGraph::swithDecimalsVisibility)
0253     }
0254 
0255     // Set headers parameters
0256     QHeaderView* verticalHeader = ui.kTable->verticalHeader();
0257     if (verticalHeader != nullptr) {
0258         verticalHeader->hide();
0259         verticalHeader->setDefaultSectionSize(verticalHeader->minimumSectionSize());
0260         // verticalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
0261     }
0262 
0263     ui.kTable->setSortingEnabled(false);    // sort must be enable for refresh method
0264     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
0265     if (horizontalHeader != nullptr) {
0266         horizontalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
0267         horizontalHeader->show();
0268         horizontalHeader->setSortIndicatorShown(true);
0269         horizontalHeader->setSortIndicator(m_sortColumn, m_sortOrder);
0270         connect(horizontalHeader, &QHeaderView::sortIndicatorChanged, this, &SKGTableWithGraph::refresh);
0271     }
0272 
0273     connect(ui.kTable->horizontalScrollBar(), &QScrollBar::valueChanged, this, &SKGTableWithGraph::onHorizontalScrollBarChanged);
0274     connect(m_displayMode, static_cast<void (SKGComboBox::*)(const QString&)>(&SKGComboBox::currentTextChanged), this, &SKGTableWithGraph::redrawGraphDelayed, Qt::QueuedConnection);
0275     connect(ui.graphicView, &SKGGraphicsView::resized, this, &SKGTableWithGraph::redrawGraphDelayed, Qt::QueuedConnection);
0276 
0277     connect(ui.kTable, &SKGTableWidget::cellDoubleClicked, this, &SKGTableWithGraph::onDoubleClick);
0278     connect(ui.kTable, &SKGTableWidget::itemSelectionChanged, this, &SKGTableWithGraph::onSelectionChanged);
0279     connect(ui.kFilterEdit, &QLineEdit::textChanged, this, &SKGTableWithGraph::onFilterModified);
0280 
0281 #ifdef SKG_WEBENGINE
0282     connect(ui.kTextEdit, &SKGWebView::linkClicked, this, &SKGTableWithGraph::onLinkClicked);
0283 #endif
0284 #ifdef SKG_WEBKIT
0285     ui.kTextEdit->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
0286     connect(ui.kTextEdit, &SKGWebView::linkClicked, this, &SKGTableWithGraph::onLinkClicked);
0287 #endif
0288 }
0289 
0290 
0291 SKGTableWithGraph::~SKGTableWithGraph()
0292 {
0293     delete m_scene;
0294     m_scene = nullptr;
0295 
0296     m_mainMenu = nullptr;
0297     m_actShowLimits = nullptr;
0298     m_actShowLinearRegression = nullptr;
0299     m_actShowPareto = nullptr;
0300     m_displayModeWidget = nullptr;
0301     m_displayMode = nullptr;
0302 }
0303 
0304 void SKGTableWithGraph::onHorizontalScrollBarChanged(int iValue)
0305 {
0306     QHeaderView* verticalHeader = ui.kTable->verticalHeader();
0307     if (verticalHeader != nullptr) {
0308         verticalHeader->setVisible(iValue > 0);
0309     }
0310 }
0311 
0312 bool SKGTableWithGraph::isGraphVisible() const
0313 {
0314     return m_graphVisible;
0315 }
0316 
0317 bool SKGTableWithGraph::isTableVisible() const
0318 {
0319     return m_tableVisible;
0320 }
0321 
0322 bool SKGTableWithGraph::isTextReportVisible() const
0323 {
0324     return m_textVisible;
0325 }
0326 
0327 SKGShow* SKGTableWithGraph::getShowWidget() const
0328 {
0329     return ui.kShow;
0330 }
0331 void SKGTableWithGraph::setAverageColor(const QColor& iColor)
0332 {
0333     m_averageColor = iColor;
0334 }
0335 
0336 void SKGTableWithGraph::setAxisColor(const QColor& iColor)
0337 {
0338     m_axisColor = iColor;
0339 }
0340 
0341 void SKGTableWithGraph::setGridColor(const QColor& iColor)
0342 {
0343     m_gridColor = iColor;
0344 }
0345 
0346 void SKGTableWithGraph::setMaxColor(const QColor& iColor)
0347 {
0348     m_maxColor = iColor;
0349 }
0350 
0351 void SKGTableWithGraph::setParetoColor(const QColor& iColor)
0352 {
0353     m_paretoColor = iColor;
0354 }
0355 
0356 void SKGTableWithGraph::setMinColor(const QColor& iColor)
0357 {
0358     m_minColor = iColor;
0359 }
0360 
0361 void SKGTableWithGraph::setTendencyColor(const QColor& iColor)
0362 {
0363     m_tendencyColor = iColor;
0364 }
0365 
0366 void SKGTableWithGraph::setOutlineColor(const QColor& iColor)
0367 {
0368     m_outlineColor = iColor;
0369 }
0370 
0371 void SKGTableWithGraph::setBackgroundColor(const QColor& iColor)
0372 {
0373     m_backgroundColor = iColor;
0374 }
0375 
0376 void SKGTableWithGraph::setTextColor(const QColor& iColor)
0377 {
0378     m_textColor = iColor;
0379 }
0380 
0381 void SKGTableWithGraph::setAntialiasing(bool iAntialiasing)
0382 {
0383     ui.graphicView->setAntialiasing(iAntialiasing);
0384 }
0385 
0386 void SKGTableWithGraph::setGraphTypeSelectorVisible(bool iVisible)
0387 {
0388     if (m_graphTypeVisible != iVisible) {
0389         m_graphTypeVisible = iVisible;
0390         if (m_displayMode != nullptr) {
0391             m_displayMode->setVisible(m_graphTypeVisible);
0392         }
0393         if (m_displayModeWidget != nullptr) {
0394             m_displayModeWidget->setVisible(m_graphTypeVisible);
0395         }
0396         Q_EMIT modified();
0397     }
0398 }
0399 
0400 bool SKGTableWithGraph::isGraphTypeSelectorVisible() const
0401 {
0402     return m_graphTypeVisible;
0403 }
0404 
0405 bool SKGTableWithGraph::switchLimitsVisibility()
0406 {
0407     m_limitVisible = !m_limitVisible;
0408     refresh();
0409     return m_limitVisible;
0410 }
0411 
0412 bool SKGTableWithGraph::switchAverageVisibility()
0413 {
0414     m_averageVisible = !m_averageVisible;
0415     refresh();
0416     return m_averageVisible;
0417 }
0418 
0419 bool SKGTableWithGraph::switchLegendVisibility()
0420 {
0421     m_legendVisible = !m_legendVisible;
0422     redrawGraphDelayed();
0423     return m_legendVisible;
0424 }
0425 
0426 bool SKGTableWithGraph::swithOriginVisibility()
0427 {
0428     m_zeroVisible = !m_zeroVisible;
0429     redrawGraphDelayed();
0430     return m_zeroVisible;
0431 }
0432 
0433 bool SKGTableWithGraph::swithDecimalsVisibility()
0434 {
0435     m_decimalsVisible = !m_decimalsVisible;
0436     refresh();
0437     return m_decimalsVisible;
0438 }
0439 
0440 bool SKGTableWithGraph::switchLinearRegressionVisibility()
0441 {
0442     m_linearRegressionVisible = !m_linearRegressionVisible;
0443     refresh();
0444     return m_linearRegressionVisible;
0445 }
0446 
0447 bool SKGTableWithGraph::switchParetoVisibility()
0448 {
0449     m_paretoVisible = !m_paretoVisible;
0450     redrawGraphDelayed();
0451     return m_paretoVisible;
0452 }
0453 
0454 QMenu* SKGTableWithGraph::getTableContextualMenu() const
0455 {
0456     return m_mainMenu;
0457 }
0458 
0459 QMenu* SKGTableWithGraph::getGraphContextualMenu() const
0460 {
0461     return ui.graphicView->getContextualMenu();
0462 }
0463 
0464 void SKGTableWithGraph::showMenu(const QPoint iPos)
0465 {
0466     if (m_mainMenu != nullptr) {
0467         m_mainMenu->popup(ui.kTable->mapToGlobal(iPos));
0468     }
0469 }
0470 
0471 QTableWidget* SKGTableWithGraph::table() const
0472 {
0473     return ui.kTable;
0474 }
0475 
0476 SKGGraphicsView* SKGTableWithGraph::graph() const
0477 {
0478     return ui.graphicView;
0479 }
0480 
0481 SKGWebView* SKGTableWithGraph::textReport() const
0482 {
0483     return ui.kTextEdit;
0484 }
0485 
0486 SKGStringListList SKGTableWithGraph::getTable()
0487 {
0488     // Build table
0489     SKGStringListList output;
0490 
0491     // Get header names
0492     int nb2 = ui.kTable->rowCount();
0493     int nb = ui.kTable->columnCount();
0494     output.reserve(nb2 + 1);
0495     QStringList cols;
0496     cols.reserve(2 * nb);
0497     for (int i = 0; i < nb; ++i) {
0498         cols.append(ui.kTable->horizontalHeaderItem(i)->text());
0499         cols.append(i18n("%1 (raw)", ui.kTable->horizontalHeaderItem(i)->text()));
0500     }
0501     output.append(cols);
0502 
0503     // Get content
0504     for (int i = 0; i < nb2; ++i) {
0505         QStringList row;
0506         row.reserve(nb);
0507         for (int j = 0; j < nb; j++) {
0508             auto* button = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, j));
0509             if (button != nullptr) {
0510                 row.append(button->text());
0511                 row.append(button->color().toRgb().name());
0512             } else {
0513                 row.append(ui.kTable->item(i, j)->text());
0514                 QString raw = ui.kTable->item(i, j)->data(DATA_VALUE).toString();
0515                 if (raw.isEmpty()) {
0516                     raw = ui.kTable->item(i, j)->text();
0517                 }
0518                 row.append(raw);
0519             }
0520         }
0521         output.append(row);
0522     }
0523     return output;
0524 }
0525 QString SKGTableWithGraph::getState()
0526 {
0527     SKGTRACEINFUNC(10)
0528     QDomDocument doc(QStringLiteral("SKGML"));
0529     QDomElement root = doc.createElement(QStringLiteral("parameters"));
0530     doc.appendChild(root);
0531 
0532     if (ui.graphicView->isVisible() && ui.kTable->isVisible()) {
0533         root.setAttribute(QStringLiteral("splitterState"), QString(ui.splitter->saveState().toHex()));
0534     }
0535     root.setAttribute(QStringLiteral("graphMode"), SKGServices::intToString(static_cast<int>(getGraphType())));
0536     root.setAttribute(QStringLiteral("allPositive"), m_allPositiveMenu->isChecked() ? QStringLiteral("Y") : QStringLiteral("N"));
0537     root.setAttribute(QStringLiteral("filter"), ui.kFilterEdit->text());
0538     root.setAttribute(QStringLiteral("limitVisible"), m_limitVisible ? QStringLiteral("Y") : QStringLiteral("N"));
0539     root.setAttribute(QStringLiteral("averageVisible"), m_averageVisible ? QStringLiteral("Y") : QStringLiteral("N"));
0540     root.setAttribute(QStringLiteral("linearRegressionVisible"), m_linearRegressionVisible ? QStringLiteral("Y") : QStringLiteral("N"));
0541     root.setAttribute(QStringLiteral("paretoVisible"), m_paretoVisible ? QStringLiteral("Y") : QStringLiteral("N"));
0542     root.setAttribute(QStringLiteral("legendVisible"), m_legendVisible ? QStringLiteral("Y") : QStringLiteral("N"));
0543     root.setAttribute(QStringLiteral("zeroVisible"), m_zeroVisible ? QStringLiteral("Y") : QStringLiteral("N"));
0544     root.setAttribute(QStringLiteral("decimalsVisible"), m_decimalsVisible ? QStringLiteral("Y") : QStringLiteral("N"));
0545     QMapIterator<QString, QColor> i(m_mapTitleColor);
0546     while (i.hasNext()) {
0547         i.next();
0548         QDomElement color = doc.createElement(QStringLiteral("color"));
0549         root.appendChild(color);
0550         color.setAttribute(QStringLiteral("key"), i.key());
0551         color.setAttribute(QStringLiteral("value"), i.value().name());
0552     }
0553 
0554     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
0555     root.setAttribute(QStringLiteral("sortOrder"), SKGServices::intToString(horizontalHeader->sortIndicatorOrder()));
0556     root.setAttribute(QStringLiteral("sortColumn"), SKGServices::intToString(horizontalHeader->sortIndicatorSection()));
0557     root.setAttribute(QStringLiteral("graphicViewState"), ui.graphicView->getState());
0558     root.setAttribute(QStringLiteral("web"), ui.kTextEdit->getState());
0559     root.setAttribute(QStringLiteral("show"), ui.kShow->getState());
0560 
0561     if (ui.kTable->stickHorizontal()) {
0562         root.setAttribute(QStringLiteral("stickH"), QStringLiteral("Y"));
0563     }
0564     if (ui.kTable->stickVertical()) {
0565         root.setAttribute(QStringLiteral("stickV"), QStringLiteral("Y"));
0566     }
0567 
0568     return doc.toString();
0569 }
0570 
0571 void SKGTableWithGraph::setState(const QString& iState)
0572 {
0573     SKGTRACEINFUNC(10)
0574     m_timer.stop();
0575     m_timerRedraw.stop();
0576 
0577     QDomDocument doc(QStringLiteral("SKGML"));
0578     doc.setContent(iState);
0579     QDomElement root = doc.documentElement();
0580 
0581     m_mapTitleColor.clear();
0582 
0583     QDomNodeList colors = root.elementsByTagName(QStringLiteral("color"));
0584     int nb = colors.count();
0585     for (int i = 0; i < nb; ++i) {
0586         QDomElement c = colors.at(i).toElement();
0587         m_mapTitleColor[c.attribute(QStringLiteral("key"))] = QColor(c.attribute(QStringLiteral("value")));
0588     }
0589 
0590     QString splitterStateString = root.attribute(QStringLiteral("splitterState"));
0591     if (!splitterStateString.isEmpty()) {
0592         ui.splitter->restoreState(QByteArray::fromHex(splitterStateString.toLatin1()));
0593     }
0594     QString graphModeString = root.attribute(QStringLiteral("graphMode"));
0595     QString allPositiveString = root.attribute(QStringLiteral("allPositive"));
0596     QString limitVisibleString = root.attribute(QStringLiteral("limitVisible"));
0597     QString averageVisibleString = root.attribute(QStringLiteral("averageVisible"));
0598     QString legendVisibleString = root.attribute(QStringLiteral("legendVisible"));
0599     QString zeroVisibleString = root.attribute(QStringLiteral("zeroVisible"));
0600     QString decimalsVisibleString = root.attribute(QStringLiteral("decimalsVisible"));
0601     QString linearRegressionVisibleString = root.attribute(QStringLiteral("linearRegressionVisible"));
0602     QString paretorVisibleString = root.attribute(QStringLiteral("paretoVisible"));
0603     QString sortOrderString = root.attribute(QStringLiteral("sortOrder"));
0604     QString sortColumnString = root.attribute(QStringLiteral("sortColumn"));
0605     QString graphicViewStateString = root.attribute(QStringLiteral("graphicViewState"));
0606     QString webString = root.attribute(QStringLiteral("web"));
0607     QString showString = root.attribute(QStringLiteral("show"));
0608     ui.kTable->setStickHorizontal(root.attribute(QStringLiteral("stickH")) == QStringLiteral("Y"));
0609     ui.kTable->setStickVertical(root.attribute(QStringLiteral("stickV")) == QStringLiteral("Y"));
0610 
0611     // Default value in case of reset
0612     if (graphModeString.isEmpty()) {
0613         graphModeString = SKGServices::intToString(LINE);
0614     }
0615     if (allPositiveString.isEmpty()) {
0616         allPositiveString = 'N';
0617     }
0618     if (limitVisibleString.isEmpty()) {
0619         limitVisibleString = 'Y';
0620     }
0621     if (averageVisibleString.isEmpty()) {
0622         averageVisibleString = 'Y';
0623     }
0624     if (legendVisibleString.isEmpty()) {
0625         legendVisibleString = 'N';
0626     }
0627     if (linearRegressionVisibleString.isEmpty()) {
0628         linearRegressionVisibleString = 'Y';
0629     }
0630     if (paretorVisibleString.isEmpty()) {
0631         paretorVisibleString = 'N';
0632     }
0633     if (sortOrderString.isEmpty()) {
0634         sortOrderString = '0';
0635     }
0636     if (sortColumnString.isEmpty()) {
0637         sortColumnString = '0';
0638     }
0639 
0640     // Set
0641     setGraphType(SKGTableWithGraph::HISTOGRAM);
0642     if (m_displayMode != nullptr) {
0643         m_displayMode->setCurrentIndex(1);
0644     }
0645     setGraphType(static_cast<SKGTableWithGraph::GraphType>(SKGServices::stringToInt(graphModeString)));
0646     m_allPositiveMenu->setChecked(allPositiveString == QStringLiteral("Y"));
0647     ui.kFilterEdit->setText(root.attribute(QStringLiteral("filter")));
0648     m_limitVisible = (limitVisibleString == QStringLiteral("Y"));
0649     if (m_actShowLimits != nullptr) {
0650         m_actShowLimits->setChecked(m_limitVisible);
0651     }
0652     m_averageVisible = (averageVisibleString == QStringLiteral("Y"));
0653     if (m_actShowAverage != nullptr) {
0654         m_actShowAverage->setChecked(m_averageVisible);
0655     }
0656     m_legendVisible = (legendVisibleString == QStringLiteral("Y"));
0657     if (m_actShowLegend != nullptr) {
0658         m_actShowLegend->setChecked(m_legendVisible);
0659     }
0660     m_zeroVisible = (zeroVisibleString != QStringLiteral("N"));
0661     if (m_actShowZero != nullptr) {
0662         m_actShowZero->setChecked(m_zeroVisible);
0663     }
0664     m_decimalsVisible = (decimalsVisibleString != QStringLiteral("N"));
0665     if (m_actShowDecimal != nullptr) {
0666         m_actShowDecimal->setChecked(m_decimalsVisible);
0667     }
0668     m_linearRegressionVisible = (linearRegressionVisibleString == QStringLiteral("Y"));
0669     if (m_actShowLinearRegression != nullptr) {
0670         m_actShowLinearRegression->setChecked(m_linearRegressionVisible);
0671     }
0672     m_paretoVisible = (paretorVisibleString == QStringLiteral("Y"));
0673     if (m_actShowPareto != nullptr) {
0674         m_actShowPareto->setChecked(m_paretoVisible);
0675     }
0676 
0677     ui.kTable->setColumnCount(SKGServices::stringToInt(sortColumnString) + 1);
0678     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
0679     if (horizontalHeader != nullptr) {
0680         bool previous = horizontalHeader->blockSignals(true);
0681         horizontalHeader->setSortIndicator(SKGServices::stringToInt(sortColumnString), static_cast<Qt::SortOrder>(SKGServices::stringToInt(sortOrderString)));
0682         horizontalHeader->blockSignals(previous);
0683     }
0684     ui.graphicView->setState(graphicViewStateString);
0685     ui.kTextEdit->setState(webString);
0686     if (!showString.isEmpty()) {
0687         ui.kShow->setState(showString);
0688     }
0689 }
0690 
0691 void SKGTableWithGraph::setFilterVisibility(bool iVisibility) const
0692 {
0693     ui.kToolbar->setVisible(iVisibility);
0694 }
0695 
0696 void SKGTableWithGraph::onFilterModified()
0697 {
0698     m_timerRedraw.stop();
0699     m_timer.start(300);
0700 }
0701 
0702 void SKGTableWithGraph::onDisplayModeChanged()
0703 {
0704     QStringList mode = SKGServices::splitCSVLine(ui.kShow->getState());
0705 
0706     // Hide all
0707     if (m_scene != nullptr) {
0708         m_scene->clear();
0709         delete m_scene;
0710     }
0711     m_scene = new SKGGraphicsScene();
0712     ui.graphicView->setScene(m_scene);
0713     ui.graphicView->hide();
0714     ui.kTextEdit->hide();
0715     bool p = ui.kTable->blockSignals(true);
0716     ui.kTable->hide();
0717     ui.kTable->blockSignals(p);
0718     m_graphVisible = false;
0719     m_tableVisible = false;
0720     m_textVisible = false;
0721     m_mapItemGraphic.clear();
0722 
0723     // Show needed widget
0724     if (mode.contains(QStringLiteral("table"))) {
0725         ui.kTable->show();
0726         m_tableVisible = true;
0727     }
0728     if (mode.contains(QStringLiteral("graph"))) {
0729         ui.graphicView->show();
0730         m_graphVisible = true;
0731         redrawGraphDelayed();
0732     }
0733     if (mode.contains(QStringLiteral("text"))) {
0734         QTimer::singleShot(100, Qt::CoarseTimer, ui.kTextEdit, &SKGWebView::show);
0735         m_textVisible = true;
0736         redrawText();
0737     }
0738 }
0739 
0740 void SKGTableWithGraph::setData(const SKGStringListList& iData,
0741                                 const SKGServices::SKGUnitInfo& iPrimaryUnit,
0742                                 const SKGServices::SKGUnitInfo& iSecondaryUnit,
0743                                 SKGTableWithGraph::DisplayAdditionalFlag iAdditionalInformation,
0744                                 int iNbVirtualColumn)
0745 {
0746     SKGTRACEINFUNC(10)
0747     m_data = iData;
0748     m_primaryUnit = iPrimaryUnit;
0749     m_secondaryUnit = iSecondaryUnit;
0750     m_additionalInformation = iAdditionalInformation;
0751     m_nbVirtualColumns = iNbVirtualColumn;
0752 
0753     onFilterModified();
0754 }
0755 
0756 SKGTableWithGraph::DisplayAdditionalFlag SKGTableWithGraph::getAdditionalDisplayMode() const
0757 {
0758     return m_additionalInformation;
0759 }
0760 
0761 QStringList SKGTableWithGraph::getSumItems(const QString& iString) const
0762 {
0763     QStringList output;
0764     QString current = iString;
0765     int index = -1;
0766     do {
0767         output.insert(0, current);
0768         index = current.lastIndexOf(OBJECTSEPARATOR);
0769         if (index != -1) {
0770             current = current.left(index);
0771         }
0772     } while (index != -1);
0773     return output;
0774 }
0775 
0776 void SKGTableWithGraph::addSums(SKGStringListList& ioTable, int& iNblines)
0777 {
0778     SKGTRACEINFUNC(10)
0779     int nbCols = -1;
0780     if (!ioTable.isEmpty()) {
0781         nbCols = ioTable.at(0).count();
0782     }
0783 
0784     // Create a list of sums lines associated to the index where to add them
0785     QMap<double, QStringList> sums;
0786     for (int i = 1; i < nbCols; ++i) {
0787         QStringList previousHeaderSum;
0788         QList<double> sum;
0789         QList<int> nbElem;
0790 
0791         for (int j = 1; j < iNblines; ++j) {
0792             double indextoadd = j + 1 - 0.01;
0793             QStringList currentHeaderSum = getSumItems(ioTable.at(j).at(0));
0794             if (previousHeaderSum.isEmpty()) {
0795                 previousHeaderSum = currentHeaderSum;
0796             }
0797 
0798             int nb = qMax(currentHeaderSum.count(), previousHeaderSum.count());
0799             for (int k = 0; k < nb; ++k) {
0800                 QString chString = currentHeaderSum.value(k);
0801                 QString phString = previousHeaderSum.value(k);
0802                 if (chString != phString) {
0803                     // Add this sum
0804                     if (nbElem.value(k) > 1) {
0805                         QStringList thisSum;
0806                         if (i == 1) {
0807                             thisSum.push_back(phString);
0808                         } else {
0809                             thisSum = sums[indextoadd];
0810                         }
0811                         thisSum.push_back(SKGServices::doubleToString(sum.value(k)));
0812 
0813                         sums[indextoadd] = thisSum;
0814 
0815                         indextoadd -= 0.01;
0816                     }
0817 
0818                     // Reset sum
0819                     if (k < sum.count()) {
0820                         sum[k] = 0;
0821                     } else {
0822                         sum.push_back(0);
0823                     }
0824                     if (k < nbElem.count()) {
0825                         nbElem[k] = 0;
0826                     } else {
0827                         nbElem.push_back(0);
0828                     }
0829                 }
0830                 if (j < iNblines - 1) {
0831                     sum = sum.mid(0, previousHeaderSum.count());
0832                     nbElem = nbElem.mid(0, previousHeaderSum.count());
0833 
0834                     QString valstring = ioTable.at(j).at(i);
0835                     double v = 0.0;
0836                     if (!valstring.isEmpty()) {
0837                         v = SKGServices::stringToDouble(valstring);
0838                     }
0839                     if (k < sum.count()) {
0840                         sum[k] += v;
0841                     } else {
0842                         sum.push_back(v);
0843                     }
0844                     if (k < nbElem.count()) {
0845                         nbElem[k] = nbElem[k] + 1;
0846                     } else {
0847                         nbElem.push_back(1);
0848                     }
0849                 }
0850             }
0851 
0852             previousHeaderSum = currentHeaderSum;
0853         }
0854     }
0855 
0856     // Add all sums in table
0857     QList<double> keys = sums.keys();
0858     std::sort(keys.begin(), keys.end());
0859     int nbkey = keys.count();
0860     for (int i = nbkey - 1; i >= 0; --i) {
0861         double key = keys.at(i);
0862         ioTable.insert(key, sums[key]);
0863         m_sumRows.insert(key, true);
0864         ++iNblines;
0865     }
0866 }
0867 
0868 void SKGTableWithGraph::refresh()
0869 {
0870     SKGTRACEINFUNC(10)
0871     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0872 
0873     // Set parameters for static method
0874     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
0875     m_sortOrder = horizontalHeader->sortIndicatorOrder();
0876     m_sortColumn = horizontalHeader->sortIndicatorSection();
0877 
0878     SKGStringListList groupedTable = m_data;
0879     int nbCols = -1;
0880     if (!groupedTable.isEmpty()) {
0881         nbCols = groupedTable.at(0).count();
0882     }
0883     int nbRealCols = nbCols;
0884 
0885     // Create filtered table
0886     {
0887         // Build list of criterias
0888         SKGServices::SKGSearchCriteriaList criterias = SKGServices::stringToSearchCriterias(ui.kFilterEdit->text());
0889 
0890         // Check all lines
0891         int nblists = criterias.count();
0892         if ((nblists != 0) && (nbCols != 0)) {
0893             for (int i = groupedTable.count() - 1; i >= 1; --i) {  // The first line is not filtered because it is the title
0894                 QStringList line = groupedTable.at(i);
0895 
0896                 // Get title of the line
0897                 const QString& val = line.at(0);
0898 
0899                 // Filtered
0900                 bool ok = false;
0901                 for (int l = 0; l < nblists; ++l) {
0902                     QStringList words = criterias[l].words;
0903                     QChar mode = criterias[l].mode;
0904                     int nbwords = words.count();
0905 
0906                     bool validateAllWords = true;
0907                     for (int w = 0; validateAllWords && w < nbwords; ++w) {
0908                         validateAllWords = val.contains(words.at(w), Qt::CaseInsensitive);
0909                     }
0910                     if (mode == '+') {
0911                         ok |= validateAllWords;
0912                     } else if (mode == '-' && validateAllWords) {
0913                         ok = false;
0914                     }
0915                 }
0916 
0917                 if (!ok) {
0918                     groupedTable.removeAt(i);
0919                 }
0920             }
0921         }
0922     }
0923 
0924     // Initialise sumRows
0925     int nblines = groupedTable.count();
0926     m_sumRows.clear();
0927     for (int j = 0; j < nblines; ++j) {
0928         m_sumRows.push_back(false);
0929     }
0930 
0931     // Compute sums line
0932     if ((m_additionalInformation & SUM) != 0u) {
0933         QStringList sums;
0934         sums.reserve(nbCols + 1);
0935         sums.push_back(QString());
0936         for (int i = 1; i < nbCols; ++i) {
0937             double sum = 0;
0938             for (int j = 1; j < nblines; ++j) {
0939                 QString valstring = groupedTable.at(j).at(i);
0940                 if (!valstring.isEmpty()) {
0941                     sum += SKGServices::stringToDouble(valstring);
0942                 }
0943             }
0944             sums.push_back(SKGServices::doubleToString(sum));
0945         }
0946         groupedTable.push_back(sums);
0947         m_sumRows.push_back(true);
0948         ++nblines;
0949     }
0950 
0951     // Compute sub sums lines
0952     if ((m_additionalInformation & SUM) != 0u && m_sortColumn == 0 && m_sortOrder == Qt::AscendingOrder) {
0953         addSums(groupedTable, nblines);
0954     }
0955 
0956     // Compute sum and average column
0957     m_indexSum = -1;
0958     m_indexAverage = -1;
0959     m_indexMin = -1;
0960     m_indexLinearRegression = -1;
0961     if (nbCols > 2 && ((m_additionalInformation & SUM) != 0u || (m_averageVisible && (m_additionalInformation & AVERAGE) != 0u) || (m_limitVisible && (m_additionalInformation & LIMITS) != 0u))) {
0962         SKGTRACEINFUNC(10)
0963         // Add title
0964         QStringList newLine = groupedTable.at(0);
0965         if ((m_additionalInformation & SUM) != 0u) {
0966             m_indexSum = newLine.count();
0967             newLine.push_back(i18nc("Noun, the numerical sum of a list of values", "Sum"));
0968         }
0969         if (m_averageVisible && (m_additionalInformation & AVERAGE) != 0u) {
0970             m_indexAverage = newLine.count();
0971             newLine.push_back(i18nc("Noun, the numerical average of a list of values", "Average"));
0972         }
0973         if (m_limitVisible && (m_additionalInformation & LIMITS) != 0u) {
0974             m_indexMin = newLine.count();
0975             newLine.push_back(i18nc("Noun, the minimum value of a list of values", "Min"));
0976             newLine.push_back(i18nc("Noun, the maximum value of a list of values", "Max"));
0977         }
0978 
0979         if (m_linearRegressionVisible) {
0980             m_indexLinearRegression = newLine.count();
0981             newLine.push_back(i18nc("Noun", "Tendency line"));
0982         }
0983 
0984         groupedTable.replace(0, newLine);
0985 
0986         for (int i = 1; i < nblines; ++i) {
0987             QStringList newLine2 = groupedTable.at(i);
0988             double sumy = 0;
0989             double sumx = 0;
0990             double sumx2 = 0;
0991             double sumxy = 0;
0992             double min = 10e20;
0993             double max = -10e20;
0994             int nbVals = 0;
0995             for (int j = 1; j < nbCols - m_nbVirtualColumns; ++j) {
0996                 const QString& valstring = newLine2.at(j);
0997                 if (!valstring.isEmpty()) {
0998                     double v = SKGServices::stringToDouble(valstring);
0999                     sumx += j;
1000                     sumx2 += j * j;
1001                     sumy += v;
1002                     sumxy += j * v;
1003                     min = qMin(min, v);
1004                     max = qMax(max, v);
1005                     ++nbVals;
1006                 }
1007             }
1008             if ((m_additionalInformation & SUM) != 0u) {
1009                 newLine2.push_back(SKGServices::doubleToString(sumy));
1010             }
1011             if (m_averageVisible && (m_additionalInformation & AVERAGE) != 0u) {
1012                 if (nbVals != 0) {
1013                     newLine2.push_back(SKGServices::doubleToString(sumy / nbVals));
1014                 } else {
1015                     newLine2.push_back(QStringLiteral("0"));
1016                 }
1017             }
1018             if (m_limitVisible && (m_additionalInformation & LIMITS) != 0u) {
1019                 if (nbVals != 0) {
1020                     newLine2.push_back(SKGServices::doubleToString(min));
1021                     newLine2.push_back(SKGServices::doubleToString(max));
1022                 } else {
1023                     newLine2.push_back(QStringLiteral("0"));
1024                     newLine2.push_back(QStringLiteral("0"));
1025                 }
1026             }
1027 
1028             if (nbVals != 0) {
1029                 double s2x = sumx2 / nbVals - sumx * sumx / (nbVals * nbVals);
1030                 double sxy = sumxy / nbVals - (sumx / nbVals) * (sumy / nbVals);
1031 
1032                 double a = (s2x != 0.0 ? sxy / s2x : 0.0);
1033                 double b = sumy / nbVals - a * sumx / nbVals;
1034 
1035                 newLine2.push_back("y=" % SKGServices::doubleToString(a) % "*x+" % SKGServices::doubleToString(b));
1036             } else {
1037                 newLine2.push_back(QStringLiteral("y=0"));
1038             }
1039 
1040             groupedTable.replace(i, newLine2);
1041         }
1042         if (m_linearRegressionVisible) {
1043             ++nbCols;
1044         }
1045         if ((m_additionalInformation & SUM) != 0u) {
1046             ++nbCols;
1047         }
1048         if (m_averageVisible && (m_additionalInformation & AVERAGE) != 0u) {
1049             ++nbCols;
1050         }
1051         if (m_limitVisible && (m_additionalInformation & LIMITS) != 0u) {
1052             nbCols += 2;
1053         }
1054     }
1055 
1056     // Sort lines
1057     if (m_sortColumn != 0  || m_sortOrder != Qt::AscendingOrder) {
1058         SKGTRACEINFUNC(10)
1059         // Extract title and sums
1060         if (!groupedTable.isEmpty()) {
1061             QStringList fist = groupedTable.takeFirst();
1062             QStringList last;
1063             if ((m_additionalInformation & SUM) != 0u && !groupedTable.isEmpty()) {
1064                 last = groupedTable.takeLast();
1065             }
1066 
1067             // Sort
1068             QCollator comp;
1069             comp.setCaseSensitivity(Qt::CaseInsensitive);
1070             std::sort(groupedTable.begin(), groupedTable.end(), [&](const QStringList & s1, const QStringList & s2) {
1071                 if (m_sortColumn >= s1.count()) {
1072                     m_sortColumn = s1.count() - 1;
1073                 }
1074                 if (m_sortColumn >= 0) {
1075                     const QString& v1 = s1.at(m_sortColumn);
1076                     const QString& v2 = s2.at(m_sortColumn);
1077                     if (m_sortColumn == 0) {
1078                         int v = comp.compare(v1, v2);
1079                         return (m_sortOrder != 0u ? v > 0 : v < 0);
1080                     }
1081 
1082                     double vd1 = SKGServices::stringToDouble(v1);
1083                     double vd2 = SKGServices::stringToDouble(v2);
1084                     return (m_sortOrder != 0u ? vd1 > vd2 : vd1 < vd2);
1085                 }
1086                 return false;
1087             });
1088 
1089             // Add title and sums
1090             groupedTable.insert(0, fist);
1091             if ((m_additionalInformation & SUM) != 0u) {
1092                 groupedTable.push_back(last);
1093             }
1094         }
1095     }
1096 
1097     IFSKGTRACEL(10) {
1098         QStringList dump = SKGServices::tableToDump(groupedTable, SKGServices::DUMP_TEXT);
1099         int nbl = dump.count();
1100         for (int i = 0; i < nbl; ++i) {
1101             SKGTRACE << dump.at(i) << SKGENDL;
1102         }
1103     }
1104 
1105     // Fill table
1106     {
1107         SKGTRACEINFUNC(10)
1108 
1109         int nbRealColumns = nbRealCols - m_nbVirtualColumns;
1110         ui.kTable->hide();
1111         QHeaderView* hHeader = ui.kTable->horizontalHeader();
1112         if (hHeader != nullptr) {
1113             hHeader->setSectionResizeMode(QHeaderView::Fixed);    // Needed to improve performances of setHorizontalHeaderLabels
1114         }
1115         ui.kTable->clear();
1116         ui.kTable->setRowCount(nblines - 1);
1117         ui.kTable->setColumnCount(nbCols);
1118         for (int i = 0; i < nblines; ++i) {
1119             const QStringList& line = groupedTable.at(i);
1120             if (i == 0) {
1121                 SKGTRACEINFUNC(10)
1122                 // Set header
1123                 ui.kTable->setHorizontalHeaderLabels(line);
1124             } else {
1125                 for (int j = 0; j < nbCols; ++j) {
1126                     const QString& val = line.at(j);
1127 
1128                     QTableWidgetItem* item;
1129                     if (j == 0) {
1130                         // Create the line header
1131                         if (m_sumRows.at(i)) {
1132                             item = new QTableWidgetItem(val.isEmpty() ?
1133                                                         i18nc("Noun, the numerical sum of a list of values", "Sum")
1134                                                         : i18nc("Noun, the numerical sum of a list of values", "Sum of '%1'", val));
1135                             item->setData(1, val);
1136                             QFont f = item->font();
1137                             f.setBold(true);
1138                             item->setFont(f);
1139                             if (m_indexLinearRegression != -1) {
1140                                 item->setData(DATA_VALUE, line.at(m_indexLinearRegression));
1141                             }
1142                             ui.kTable->setItem(i - 1, j, item);
1143 
1144                             // Set header
1145                             ui.kTable->setVerticalHeaderItem(i - 1, new QTableWidgetItem(*item));
1146                         } else {
1147                             // New color selector
1148                             QColor defaultColor;
1149                             int color_h = (240 + 360 * (i - 1) / nblines) % 360;  // First color is blue to be compliant with min (red) and max (green)
1150                             defaultColor = QColor::fromHsv(color_h, 255, 255);
1151 
1152                             QColor color;
1153                             if (m_mapTitleColor.contains(val)) {
1154                                 color = m_mapTitleColor[val];
1155                             } else {
1156                                 color = defaultColor;
1157                                 m_mapTitleColor[val] = color;
1158                             }
1159 
1160                             auto colorSelector = new SKGColorButton(this);
1161                             colorSelector->setText(val);
1162                             colorSelector->setToolTip(val);
1163                             colorSelector->setColor(color);
1164                             colorSelector->setDefaultColor(defaultColor);
1165                             connect(colorSelector, &SKGColorButton::changed, this, &SKGTableWithGraph::onChangeColor);
1166 
1167                             ui.kTable->setCellWidget(i - 1, 0, colorSelector);
1168 
1169                             // Set header
1170                             auto itemTmp = new QTableWidgetItem(val);
1171                             itemTmp->setBackground(color);
1172                             itemTmp->setToolTip(val);
1173                             ui.kTable->setVerticalHeaderItem(i - 1, itemTmp);
1174                         }
1175                     } else {
1176                         // Add a value
1177                         QString tooltip = line.at(0) % '\n' % groupedTable.at(0).at(j);
1178                         if (!val.isEmpty()) {
1179                             if (j == m_indexLinearRegression) {
1180                                 // A linear regression value
1181                                 item = new QTableWidgetItem(val);
1182                             } else {
1183                                 // A single value
1184                                 double vald = SKGServices::stringToDouble(val);
1185                                 QString vals = SKGServices::toCurrencyString(vald, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0);
1186                                 tooltip += '\n' % vals;
1187 
1188                                 item = new QTableWidgetItem(vals);
1189                                 item->setToolTip(tooltip);
1190                                 if (!m_secondaryUnit.Symbol.isEmpty() && (m_secondaryUnit.Value != 0.0)) {
1191                                     item->setToolTip(tooltip % '\n' % SKGServices::toCurrencyString(vald / m_secondaryUnit.Value, m_secondaryUnit.Symbol, m_decimalsVisible ? m_secondaryUnit.NbDecimal : 0));
1192                                 }
1193 
1194                                 item->setData(DATA_VALUE, vald);
1195                                 item->setTextAlignment(Qt::AlignRight);
1196                                 if (vald < 0) {
1197                                     item->setForeground(m_NegativeColor);
1198                                 }
1199                                 if (m_sumRows.at(i)) {
1200                                     QFont f = item->font();
1201                                     f.setBold(true);
1202                                     item->setFont(f);
1203                                 }
1204                             }
1205                         } else {
1206                             // An empty value
1207                             item = new QTableWidgetItem(QString());
1208                             item->setToolTip(tooltip);
1209                             item->setData(DATA_VALUE, "");
1210                         }
1211                         item->setFlags((j >= nbRealColumns && j != m_indexSum) || ui.kTable->horizontalHeaderItem(j)->text() == QStringLiteral("0000") ? Qt::NoItemFlags : Qt::ItemIsEnabled | Qt::ItemIsSelectable);
1212                         ui.kTable->setItem(i - 1, j, item);
1213                     }
1214                 }
1215             }
1216         }
1217 
1218         // Refresh graphic view
1219         redrawGraph();
1220 
1221         // Refresh text area
1222         redrawText();
1223 
1224         if (hHeader != nullptr) {
1225             hHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
1226         }
1227 
1228         if (m_tableVisible) {
1229             ui.kTable->show();
1230         }
1231 
1232         if ((hHeader != nullptr) && (hHeader->count() != 0)) {
1233             hHeader->resizeSection(0, 100);
1234             hHeader->setSectionResizeMode(0, QHeaderView::Interactive);
1235         }
1236     }
1237 
1238     QApplication::restoreOverrideCursor();
1239 }
1240 
1241 void SKGTableWithGraph::onChangeColor()
1242 {
1243     auto* colorButton = qobject_cast<SKGColorButton*>(sender());
1244     if (colorButton != nullptr) {
1245         m_mapTitleColor[colorButton->text()] = colorButton->color();
1246         refresh();
1247     }
1248 }
1249 
1250 void SKGTableWithGraph::resetColors()
1251 {
1252     m_mapTitleColor.clear();
1253     refresh();
1254 }
1255 
1256 void SKGTableWithGraph::onSelectionChanged()
1257 {
1258     _SKGTRACEINFUNC(10)
1259     if (m_graphVisible) {
1260         // Unset color on previous selection
1261         int nbRow = ui.kTable->rowCount();
1262         int nbCol = ui.kTable->columnCount();
1263         for (int r = 0; r < nbRow; ++r) {
1264             for (int c = 0; c < nbCol; ++c) {
1265                 QTableWidgetItem* previous = ui.kTable->item(r, c);
1266                 if (previous != nullptr) {
1267                     QGraphicsItem* val = m_mapItemGraphic.value(previous);
1268                     if (val != nullptr) {
1269                         auto* graphicItem = qgraphicsitem_cast<QAbstractGraphicsShapeItem*>(val);
1270                         if (graphicItem != nullptr) {
1271                             QColor color = QColor::fromHsv(graphicItem->data(DATA_COLOR_H).toInt(),
1272                                                            graphicItem->data(DATA_COLOR_S).toInt(),
1273                                                            graphicItem->data(DATA_COLOR_V).toInt());
1274                             color.setAlpha(ALPHA);
1275 
1276                             if (graphicItem->data(DATA_MODE).toInt() == 1) {
1277                                 QPen pen = graphicItem->pen();
1278                                 pen.setColor(color);
1279                                 graphicItem->setPen(pen);
1280                             } else {
1281                                 graphicItem->setBrush(QBrush(color));
1282                             }
1283                             graphicItem->setZValue(graphicItem->data(DATA_Z_VALUE).toReal());
1284                             if (graphicItem->isSelected()) {
1285                                 graphicItem->setSelected(false);
1286                             }
1287                         }
1288                     }
1289                 }
1290             }
1291         }
1292 
1293         // Set highlight color on current selection
1294         QList<QTableWidgetItem*> selected = ui.kTable->selectedItems();
1295         int nb = selected.count();
1296         for (int i = 0; i < nb; ++i) {
1297             QTableWidgetItem* current = selected.at(i);
1298             if (current != nullptr) {
1299                 QGraphicsItem* val = m_mapItemGraphic.value(current);
1300                 auto* graphicItem = qgraphicsitem_cast<QAbstractGraphicsShapeItem*>(val);
1301                 if (graphicItem != nullptr) {
1302                     if (graphicItem->data(DATA_MODE).toInt() == 1) {
1303                         QPen pen = graphicItem->pen();
1304                         pen.setColor(QApplication::palette().color(QPalette::Highlight));
1305                         graphicItem->setPen(pen);
1306                     } else {
1307                         graphicItem->setBrush(QBrush(QApplication::palette().color(QPalette::Highlight)));
1308                     }
1309                     graphicItem->setZValue(15);
1310                     graphicItem->setSelected(true);
1311                     graphicItem->ensureVisible();
1312                 }
1313             }
1314         }
1315     }
1316 
1317     emit selectionChanged();
1318 }
1319 
1320 void SKGTableWithGraph::onDoubleClickGraph()
1321 {
1322     if (m_scene != nullptr) {
1323         // Get selection
1324         QList<QGraphicsItem*> selectedGraphItems = m_scene->selectedItems();
1325         if (!selectedGraphItems.isEmpty()) {
1326             Q_EMIT cellDoubleClicked(selectedGraphItems[0]->data(1).toInt(), selectedGraphItems[0]->data(2).toInt());
1327         }
1328     }
1329 }
1330 
1331 void SKGTableWithGraph::onDoubleClick(int row, int column)
1332 {
1333     Q_EMIT cellDoubleClicked(row, column);
1334 }
1335 
1336 void SKGTableWithGraph::onLinkClicked(const QUrl& url)
1337 {
1338     QString path = url.toString().remove(QStringLiteral("https://linkclicked/"));
1339     QStringList items = SKGServices::splitCSVLine(path, ',');
1340     if (items.count() == 2) {
1341         Q_EMIT cellDoubleClicked(SKGServices::stringToInt(items[0]), SKGServices::stringToInt(items[1]));
1342     }
1343 }
1344 
1345 void SKGTableWithGraph::onSelectionChangedInGraph()
1346 {
1347     _SKGTRACEINFUNC(10)
1348     if (m_scene != nullptr) {
1349         bool previous = ui.kTable->blockSignals(true);
1350         ui.kTable->clearSelection();
1351 
1352         // Get selection
1353         QList<QGraphicsItem*> selectedGraphItems = m_scene->selectedItems();
1354         int nb = selectedGraphItems.count();
1355         for (int i = 0; i < nb; ++i) {
1356             ui.kTable->setCurrentCell(selectedGraphItems.at(i)->data(1).toInt(), selectedGraphItems.at(i)->data(2).toInt(), QItemSelectionModel::Select);
1357         }
1358         ui.kTable->blockSignals(previous);
1359 
1360         previous = m_scene->blockSignals(true);
1361         onSelectionChanged();
1362         m_scene->blockSignals(previous);
1363     }
1364 }
1365 
1366 void SKGTableWithGraph::redrawGraphDelayed()
1367 {
1368     m_timerRedraw.start(300);
1369 }
1370 
1371 void SKGTableWithGraph::redrawText()
1372 {
1373     if (!m_textVisible) {
1374         return;
1375     }
1376     SKGTRACEINFUNC(10)
1377     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1378 
1379     QString html = QStringLiteral("<? xml version = \"1.0\" encoding=\"utf-8\"?>"
1380                                   "<!DOCTYPE html PUBLIC \"-// W3C// DTD XHTML 1.0 Strict// EN\" \"https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
1381                                   "<html xmlns=\"https://www.w3.org/1999/xhtml\">"
1382                                   "<head>"
1383                                   "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" />"
1384                                   "<meta http-equiv=\"Content-Style-Type\" content=\"text/css\" />"
1385                                   "<style type=\"text/css\">"
1386                                   "body{background-color: #FFFFFF; font-size : small; display: inline;} a{color: inherit; text-decoration: inherit;} h1{text-decoration: underline; color: #FF3333;} h2{text-decoration: underline; color: #FF9933;} .table{border: thin solid #000000; border-collapse: collapse; background-color: #000000;} .tabletitle{background-color: #6495ed; color : #FFFF33; font-weight : bold; font-size : normal} .tabletotal{background-color: #D0E3FA;font-weight : bold;} tr{background-color: #FFFFFF;padding: 2px;} td{padding: 2px; white-space: nowrap;}"
1387                                   "</style>"
1388                                   "</head>"
1389                                   "<body>"
1390                                   "<table class=\"table\"><tr class=\"tabletitle\">");
1391     // Dump header
1392     int nbCols = ui.kTable->columnCount();
1393     for (int i = 0; i < nbCols; ++i) {
1394         QTableWidgetItem* item = ui.kTable->horizontalHeaderItem(i);
1395         if (item != nullptr) {
1396             html += R"(<td align="center" width="1000"><b>)" % item->text() % "</b></td>";
1397         }
1398     }
1399     html += QStringLiteral("</tr>");
1400 
1401     // Dump values
1402     int nbLines = ui.kTable->rowCount();
1403     for (int j = 0; j < nbLines; ++j) {
1404         html += QStringLiteral("<tr") % (m_sumRows.at(j + 1) ? " class=\"tabletotal\"" : "") % '>';
1405         for (int i = 0; i < nbCols; ++i) {
1406             QTableWidgetItem* item = ui.kTable->item(j, i);
1407             if (item != nullptr) {
1408                 bool red = (item->data(DATA_VALUE).toDouble() < 0);
1409                 html += QStringLiteral("<td align=\"right\">") % (red ? "<font color=\"red\">" : "");
1410                 if ((item->flags()&Qt::ItemIsSelectable) != 0u) {
1411                     html += "<a href=\"https://linkclicked/" % SKGServices::intToString(j) % "," % SKGServices::intToString(i) % "\">";
1412                 }
1413                 html += item->text();
1414                 if ((item->flags()&Qt::ItemIsSelectable) != 0u) {
1415                     html += QStringLiteral("</a>");
1416                 }
1417                 html += QString(red ? QStringLiteral("</font>") : QString()) % "</td>";
1418             } else {
1419                 auto* colorButton = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(j, i));
1420                 if (colorButton != nullptr) {
1421                     html += "<td><b>" % colorButton->text() % "</b></td>";
1422                 }
1423             }
1424         }
1425         html += QStringLiteral("</tr>");
1426     }
1427     html += QStringLiteral("</table>");
1428     html += QStringLiteral("</body></html>");
1429     ui.kTextEdit->setHtml(html);
1430     QApplication::restoreOverrideCursor();
1431 }
1432 
1433 void SKGTableWithGraph::redrawGraph()
1434 {
1435     SKGTRACEINFUNC(10)
1436     m_mapItemGraphic.clear();
1437     if (!m_graphVisible) {
1438         return;
1439     }
1440     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1441 
1442     ui.graphicView->hide();
1443     ui.kTable->hide();
1444 
1445     // Recreate scene
1446     if (m_scene != nullptr) {
1447         SKGTRACEINFUNC(10)
1448         m_scene->clear();
1449         delete m_scene;
1450     }
1451 
1452     m_scene = new SKGGraphicsScene();
1453     {
1454         SKGTRACEINFUNC(10)
1455         m_scene->setBackgroundBrush(m_backgroundColor);
1456 
1457         // Get current selection
1458         int crow = ui.kTable->currentRow();
1459         int ccolumn = ui.kTable->currentColumn();
1460 
1461         // Get nb columns and rows
1462         int nbRows = ui.kTable->rowCount();
1463         int nbRealRows = nbRows;
1464         for (int posy = 0; posy < nbRows; ++posy) {
1465             if (m_sumRows.at(posy + 1)) {
1466                 --nbRealRows;
1467             }
1468         }
1469 
1470         int nbColumns = getNbColumns(false);
1471         int nbRealColumns = nbColumns - m_nbVirtualColumns;
1472 
1473         // Get graphic mode
1474         GraphType mode =   getGraphType();
1475 
1476         // Get in positive
1477         bool inPositive = false;
1478         if (mode == STACK || mode == STACKCOLUMNS || mode == HISTOGRAM || mode == POINT || mode == LINE || mode == STACKAREA) {
1479             m_allPositiveMenu->setEnabled(true);
1480             inPositive = (m_allPositiveMenu->isChecked());
1481         } else {
1482             m_allPositiveMenu->setEnabled(false);
1483             if (mode == CONCENTRICPIE || mode == PIE || mode == TREEMAP) {
1484                 inPositive = true;
1485             }
1486         }
1487 
1488         // Get show origin
1489         bool showOrigin = true;
1490         if (mode == HISTOGRAM || mode == POINT || mode == LINE) {
1491             m_actShowZero->setEnabled(true);
1492             showOrigin = (m_actShowZero->isChecked());
1493         } else {
1494             m_actShowZero->setEnabled(false);
1495         }
1496 
1497         // Compute y limits
1498         double minLimit = (showOrigin ? 0 : 9999999);
1499         double maxLimit = (showOrigin ? 0 : -9999999);
1500         int nbLevel = 0;
1501         SKGTRACEL(3) << "mode=" << static_cast<unsigned int>(mode) << SKGENDL;
1502         SKGTRACEL(3) << "nb rows        =" << nbRows << SKGENDL;
1503         SKGTRACEL(3) << "nb real rows   =" << nbRealRows << SKGENDL;
1504         SKGTRACEL(3) << "nb columns     =" << nbColumns << SKGENDL;
1505         SKGTRACEL(3) << "nb real columns=" << nbRealColumns << SKGENDL;
1506         SKGTRACEL(3) << "selected row   =" << crow << SKGENDL;
1507         SKGTRACEL(3) << "selected column=" << ccolumn << SKGENDL;
1508         if (mode == STACK) {
1509             // STACK
1510             for (int posx = 0; posx < nbRows; ++posx) {
1511                 if (!m_sumRows[posx + 1]) {
1512                     double sumPositive = 0;
1513                     double sumNegative = 0;
1514                     for (int posy = 0; posy < nbColumns; ++posy) {
1515                         QTableWidgetItem* tableItem = ui.kTable->item(posx, posy);
1516                         if (tableItem != nullptr) {
1517                             QVariant valQ = tableItem->data(DATA_VALUE);
1518                             if (valQ.type() == QVariant::Double) {
1519                                 double val = valQ.toDouble();
1520                                 if (inPositive || val >= 0) {
1521                                     sumPositive += qAbs(val);
1522                                 } else {
1523                                     sumNegative += val;
1524                                 }
1525                             }
1526                         }
1527                     }
1528 
1529                     minLimit = qMin(minLimit, sumNegative);
1530                     maxLimit = qMax(maxLimit, sumPositive);
1531                 }
1532             }
1533         } else if (mode == STACKAREA || mode == STACKCOLUMNS || mode == PIE || mode == CONCENTRICPIE || mode == TREEMAP) {
1534             // STACKAREA or STACKCOLUMNS or PIE or CONCENTRICPIE
1535             for (int posy = 0; posy < nbColumns; ++posy) {
1536                 double sumPositive = 0;
1537                 double sumNegative = 0;
1538                 for (int posx = 0; posx < nbRows; ++posx) {
1539                     if (!m_sumRows[posx + 1]) {
1540                         QTableWidgetItem* tableItem = ui.kTable->item(posx, posy);
1541                         if (tableItem != nullptr) {
1542                             QVariant valQ = tableItem->data(DATA_VALUE);
1543                             if (valQ.type() == QVariant::Double) {
1544                                 double val = valQ.toDouble();
1545                                 if (inPositive || val >= 0) {
1546                                     sumPositive += qAbs(val);
1547                                 } else {
1548                                     sumNegative += val;
1549                                 }
1550                             }
1551                         }
1552                     }
1553                 }
1554                 if (mode == TREEMAP) {
1555                     // TREEMAP
1556                     minLimit = 0;
1557                     maxLimit = qAbs(sumNegative) + sumPositive;
1558                 } else {
1559                     minLimit = qMin(minLimit, sumNegative);
1560                     maxLimit = qMax(maxLimit, sumPositive);
1561                 }
1562             }
1563         } else if (mode == HISTOGRAM || mode == POINT || mode == LINE) {
1564             // HISTOGRAM or POINTS or LINES
1565             for (int posx = 0; posx < nbRows; ++posx) {
1566                 if (!m_sumRows[posx + 1]) {
1567                     for (int posy = 0; posy < nbColumns; ++posy) {
1568                         QTableWidgetItem* tableItem = ui.kTable->item(posx, posy);
1569                         if (tableItem != nullptr) {
1570                             QVariant valQ = tableItem->data(DATA_VALUE);
1571                             if (valQ.type() == QVariant::Double) {
1572                                 double val = valQ.toDouble();
1573                                 if (inPositive) {
1574                                     maxLimit = qMax(maxLimit, qAbs(val));
1575                                     minLimit = qMin(minLimit, qAbs(val));
1576                                 } else {
1577                                     minLimit = qMin(minLimit, val);
1578                                     maxLimit = qMax(maxLimit, val);
1579                                 }
1580                             }
1581                         }
1582                     }
1583                 }
1584             }
1585         } else if (mode == BUBBLE) {
1586             for (int posx = 0; posx < nbRows; ++posx) {
1587                 if (!m_sumRows[posx + 1]) {
1588                     for (int posy = 0; posy < nbColumns; ++posy) {
1589                         QTableWidgetItem* tableItem = ui.kTable->item(posx, posy);
1590                         if (tableItem != nullptr) {
1591                             QVariant valQ = tableItem->data(DATA_VALUE);
1592                             if (valQ.type() == QVariant::Double) {
1593                                 double val = valQ.toDouble();
1594                                 maxLimit = qMax(maxLimit, qAbs(val));
1595                             }
1596                         }
1597                     }
1598                 }
1599             }
1600         }
1601 
1602         if (mode == CONCENTRICPIE) {
1603             for (int posx = 0; posx < nbRows; ++posx) {
1604                 auto* btn = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(posx, 0));
1605                 if (btn != nullptr) {
1606                     QString xname = btn->text();
1607                     QStringList vals = xname.split(OBJECTSEPARATOR);
1608                     int nbvals = vals.count();
1609                     nbLevel = qMax(nbLevel, nbvals - 1);
1610                 }
1611             }
1612         }
1613 
1614         // Compute
1615         double yorigin = 0.0;
1616         double widthItem = 10;
1617         double maxX = 0;
1618         double margin = 0;
1619         double marginLeft = 0;
1620         double xstep = 0.0;
1621         double radius = 0.0;
1622         int jstep = qMax(computeStepSize(nbColumns, 10), static_cast<double>(1.0));
1623         double ystep = computeStepSize(maxLimit - minLimit, 10);
1624         if (mode != BUBBLE && mode != PIE && mode != CONCENTRICPIE && mode != TREEMAP && ystep != 0) {
1625             double newMinLimit = ystep * qRound(minLimit / ystep);
1626             minLimit = (minLimit - newMinLimit < -EPSILON ? newMinLimit - ystep : newMinLimit);
1627             double newMaxLimit = ystep * qRound(maxLimit / ystep);
1628             maxLimit = (maxLimit - newMaxLimit > EPSILON  ? newMaxLimit + ystep : newMaxLimit);
1629         }
1630 
1631         if ((nbRealRows != 0) && ystep != 0) {
1632             QRect vSize = ui.graphicView->rect();
1633             if (mode == STACK) {
1634                 margin = (maxLimit - minLimit) * 0.3;
1635 
1636                 if (!showOrigin) {
1637                     if (minLimit > 0) {
1638                         yorigin = ystep * (qRound(minLimit / ystep));
1639                     } else if (maxLimit < 0) {
1640                         yorigin = ystep * (qRound(maxLimit / ystep));
1641                     }
1642                 }
1643 
1644                 widthItem = (maxLimit - minLimit) / (nbRealRows + 1);
1645                 widthItem = widthItem * (static_cast<double>(vSize.width())) / (static_cast<double>(vSize.height()));
1646 
1647                 xstep = widthItem * (nbRealRows + 1);
1648                 marginLeft = widthItem;
1649 
1650                 maxX = widthItem * nbRealRows + margin;
1651             } else if (mode == HISTOGRAM || mode == POINT || mode == LINE || mode == STACKAREA || mode == STACKCOLUMNS) {
1652                 margin = (maxLimit - minLimit) * 0.3;
1653 
1654                 if (!showOrigin) {
1655                     if (minLimit > 0) {
1656                         yorigin = ystep * (qRound(minLimit / ystep));
1657                     } else if (maxLimit < 0) {
1658                         yorigin = ystep * (qRound(maxLimit / ystep));
1659                     }
1660                 }
1661 
1662                 widthItem = (maxLimit - minLimit) / ((nbRealRows + 1) * (nbColumns - 1));
1663                 widthItem = widthItem * (static_cast<double>(vSize.width())) / (static_cast<double>(vSize.height()));
1664 
1665                 xstep = widthItem * (nbRealRows + 1);
1666                 marginLeft = qMin(jstep * xstep, widthItem * (nbColumns - 1));
1667 
1668                 maxX = xstep * (nbColumns - 1);
1669                 if (mode == POINT || mode == LINE || mode == STACKAREA) {
1670                     maxX -= xstep;
1671                 }
1672 //                 radius = qMax(margin / 100.0, qMin(width, qMin(ystep / 4, jstep * xstep / 4)));
1673                 radius = qMax(margin / 200, qMin(widthItem, qMin(ystep / 8, jstep * xstep / 8)));
1674             } else if (mode == BUBBLE) {
1675                 margin = ystep * nbRealRows * 0.3;
1676 
1677                 widthItem = ystep * nbRealRows / (nbRealRows * (nbColumns - 1));
1678                 widthItem = widthItem * (static_cast<double>(vSize.width())) / (static_cast<double>(vSize.height()));
1679 
1680                 xstep = widthItem * (nbRealRows + 1);
1681                 marginLeft = jstep * xstep;
1682 
1683                 maxX = widthItem * (nbColumns - 1) * (nbRealRows + 1);
1684             }
1685         }
1686 
1687         SKGTRACEL(3) << "minLimit=" << minLimit << SKGENDL;
1688         SKGTRACEL(3) << "maxLimit=" << maxLimit << SKGENDL;
1689         SKGTRACEL(3) << "ystep=" << ystep << SKGENDL;
1690         SKGTRACEL(3) << "yorigin=" << yorigin << SKGENDL;
1691         SKGTRACEL(3) << "width=" << widthItem << SKGENDL;
1692         SKGTRACEL(3) << "maxX=" << maxX << SKGENDL;
1693         SKGTRACEL(3) << "margin=" << margin << SKGENDL;
1694         SKGTRACEL(3) << "xstep=" << xstep << SKGENDL;
1695         SKGTRACEL(3) << "marginLeft=" << marginLeft << SKGENDL;
1696         SKGTRACEL(3) << "radius=" << radius << SKGENDL;
1697 
1698         // Initialise pens
1699         QPen axisPen = QPen(Qt::SolidLine);
1700         axisPen.setColor(m_axisColor);
1701         axisPen.setWidthF(0);
1702         axisPen.setJoinStyle(Qt::RoundJoin);
1703 
1704         QPen gridPen = QPen(Qt::SolidLine);
1705         gridPen.setColor(m_gridColor);
1706         gridPen.setWidthF(0);
1707 
1708         QPen dotPen = QPen(Qt::SolidLine);  // WARNING: Qt::DotLine is very bad for performances
1709         dotPen.setWidthF(0);
1710 
1711         QPen outlinePen(m_outlineColor);
1712         outlinePen.setWidthF(0);
1713 
1714         // Set rect
1715         int nbColInMode2 = (nbColumns > 2 ? sqrt(nbColumns - 1) + .5 : 1);
1716         m_scene->setItemIndexMethod(QGraphicsScene::NoIndex);
1717         SKGTRACEL(10) << "Items:" << nbRealRows << "x" << (nbColumns - 1) << "=" << nbRealRows*(nbColumns - 1) << SKGENDL;
1718         SKGTRACEL(10) << "nbColInMode2=" << nbColInMode2 << SKGENDL;
1719 
1720         // Compute treemap
1721         QMap<QString, SKGTreeMap> treemap;
1722         if (mode == TREEMAP) {
1723             SKGTreeMap treemapobj(QString(), 0, 0, 0, BOX_SIZE, BOX_SIZE);
1724             for (int j = 1; j < nbColumns; ++j) {
1725                 SKGTreeMap treemapobj2;
1726                 for (int xx = 0; xx < nbRows; ++xx) {
1727                     auto* colorButton = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(xx, 0));
1728                     if (colorButton != nullptr) {
1729                         // Get cell value
1730                         QTableWidgetItem* tableItem = ui.kTable->item(xx, j);
1731                         if (tableItem != nullptr) {
1732                             QVariant valQ = tableItem->data(DATA_VALUE);
1733                             if (valQ.type() == QVariant::Double) {
1734                                 double val = qAbs(valQ.toDouble());
1735                                 QString id = SKGServices::intToString(xx) % QStringLiteral("-") % SKGServices::intToString(j);
1736                                 treemapobj2.addChild(SKGTreeMap(id, val));
1737                             }
1738                         }
1739                     }
1740                 }
1741                 treemapobj.addChild(treemapobj2);
1742             }
1743 
1744             treemapobj.compute();
1745             treemap = treemapobj.getAllTilesById();
1746         }
1747 
1748         // Redraw scene
1749         double x0 = 0;
1750         double x1 = 0;
1751         double ymin = (showOrigin || mode == STACK || mode == STACKCOLUMNS ? 0 : 9999999);
1752         double ymax = (showOrigin || mode == STACK || mode == STACKCOLUMNS ? 0 : -9999999);
1753         int posx = 0;
1754         int nbRowsDrawed = 0;
1755         QMap<int, double> previousStackedPlus;
1756         QMap<int, double> previousStackedMoins;
1757         QMap<double, double> paretoPoints;
1758         for (int xx = 0; xx < nbRows; ++xx) {
1759             auto* colorButton = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(xx, 0));
1760             if (colorButton != nullptr) {
1761                 QString xname = colorButton->text();
1762                 QColor initialColor = colorButton->color();
1763                 double yPlus = 0;
1764                 double yMoins = 0;
1765                 int jprevious = -1;
1766 
1767                 ++nbRowsDrawed;
1768                 for (int j = 1; j < nbColumns; ++j) {
1769                     // Get cell value
1770                     QTableWidgetItem* tableItem = ui.kTable->item(xx, j);
1771                     if (tableItem != nullptr) {
1772                         QVariant valQ = tableItem->data(DATA_VALUE);
1773                         if (valQ.type() == QVariant::Double) {
1774                             double val = valQ.toDouble();
1775                             QString valstring = tableItem->text();
1776 
1777                             if (inPositive) {
1778                                 val = qAbs(val);
1779                             }
1780 
1781                             int color_h;
1782                             int color_s;
1783                             int color_v;
1784                             initialColor.getHsv(&color_h, &color_s, &color_v);
1785                             color_s = color_s - color_s * 155 / 255 * (nbColumns - 1 - j) / nbColumns;
1786                             color_v = color_v - color_v * 155 / 255 * (nbColumns - 1 - j) / nbColumns;
1787                             if (j >= nbRealColumns) {
1788                                 // Yellow
1789                                 color_h = 60;
1790                                 color_s = 255;
1791                                 color_v = 255;
1792                             }
1793 
1794                             QColor color;
1795                             if (posx == crow && j == ccolumn) {
1796                                 color = QApplication::palette().color(QPalette::Highlight);
1797                             } else {
1798                                 color = QColor::fromHsv(color_h, color_s, color_v);
1799                             }
1800 
1801                             color.setAlpha(ALPHA);
1802                             QBrush brush(color);
1803 
1804                             QGraphicsItem* graphItem = nullptr;
1805                             if (mode == STACK) {
1806                                 // STACK
1807                                 if (val >= 0) {
1808                                     graphItem = m_scene->addRect(widthItem * posx, -yPlus - qAbs(val), widthItem, qAbs(val), outlinePen, brush);
1809                                     yPlus += qAbs(val);
1810                                     if (yPlus > ymax) {
1811                                         ymax = yPlus;
1812                                     }
1813                                 } else {
1814                                     graphItem = m_scene->addRect(widthItem * posx, -yMoins, widthItem, -val, outlinePen, brush);
1815                                     yMoins += val;
1816                                     if (yMoins < ymin) {
1817                                         ymin = yMoins;
1818                                     }
1819                                 }
1820                                 if (graphItem != nullptr) {
1821                                     graphItem->setZValue(5 + nbColumns - j);
1822                                 }
1823                             }
1824                             if (mode == STACKAREA) {
1825                                 // STACKAREA
1826                                 // Empty cells are ignored
1827                                 if (j == 1) {
1828                                     x0 = widthItem * (j - 1) * (nbRealRows + 1);
1829                                 }
1830                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1831                                     x1 = widthItem * (j - 1) * (nbRealRows + 1);
1832                                 }
1833 
1834                                 if (!valstring.isEmpty()) {
1835                                     if (jprevious == -1) {
1836                                         jprevious = j;
1837                                     }
1838                                     QTableWidgetItem* tableItem2 = ui.kTable->item(xx, jprevious);
1839                                     if (tableItem2 != nullptr) {
1840                                         QString val2string = tableItem->text();
1841                                         if (!val2string.isEmpty()) {
1842                                             double val2 = tableItem2->data(DATA_VALUE).toDouble();
1843                                             if (j == jprevious) {
1844                                                 val2 = 0;
1845                                             }
1846                                             if (inPositive) {
1847                                                 val2 = qAbs(val2);
1848                                             }
1849 
1850                                             if (val > EPSILON || (qAbs(val) <= EPSILON && val2 >= EPSILON)) {
1851                                                 double xp = widthItem * (jprevious - 1) * (nbRealRows + 1) - (jprevious == j ? widthItem / 20.0 : 0);
1852                                                 double xn = widthItem * (j - 1) * (nbRealRows + 1);
1853                                                 double yp = (previousStackedPlus.contains(jprevious) ? previousStackedPlus[jprevious] : -val2);
1854                                                 double yn = previousStackedPlus[j] - val;
1855 
1856                                                 QPolygonF polygon;
1857                                                 polygon << QPointF(xp, yp + val2) << QPointF(xp, yp)
1858                                                         << QPointF(xn, yn) << QPointF(xn, previousStackedPlus[j]);
1859                                                 graphItem = m_scene->addPolygon(polygon, outlinePen, brush);
1860                                                 previousStackedPlus[j] = yn;
1861                                                 if (-yn > ymax) {
1862                                                     ymax = -yn;
1863                                                 }
1864                                             } else {
1865                                                 double xp = widthItem * (jprevious - 1) * (nbRealRows + 1) - (jprevious == j ? widthItem / 20.0 : 0);
1866                                                 double xn = widthItem * (j - 1) * (nbRealRows + 1);
1867                                                 double yp = (previousStackedMoins.contains(jprevious) ? previousStackedMoins[jprevious] : -val2);
1868                                                 double yn = previousStackedMoins[j] - val;
1869 
1870                                                 QPolygonF polygon;
1871                                                 polygon << QPointF(xp, yp + val2) << QPointF(xp, yp)
1872                                                         << QPointF(xn, yn) << QPointF(xn, previousStackedMoins[j]);
1873                                                 graphItem = m_scene->addPolygon(polygon, outlinePen, brush);
1874                                                 previousStackedMoins[j] = yn;
1875                                                 if (-yn < ymin) {
1876                                                     ymin = -yn;
1877                                                 }
1878                                             }
1879                                             jprevious = j;
1880                                         }
1881                                     }
1882                                 }
1883                             }
1884                             if (mode == STACKCOLUMNS) {
1885                                 // STACKCOLUMNS
1886                                 if (val >= 0) {
1887                                     graphItem = m_scene->addRect(widthItem * (j - 1) * (nbRealRows + 1), -previousStackedPlus[j] - qAbs(val), widthItem * (nbRealRows + 1), qAbs(val), outlinePen, brush);
1888                                     previousStackedPlus[j] += qAbs(val);
1889                                     if (previousStackedPlus[j] > ymax) {
1890                                         ymax = previousStackedPlus[j];
1891                                     }
1892                                 } else {
1893                                     graphItem = m_scene->addRect(widthItem * (j - 1) * (nbRealRows + 1), -previousStackedMoins[j], widthItem * (nbRealRows + 1), -val, outlinePen, brush);
1894                                     previousStackedMoins[j] += val;
1895                                     if (previousStackedMoins[j] < ymin) {
1896                                         ymin = previousStackedMoins[j];
1897                                     }
1898                                 }
1899                                 if (graphItem != nullptr) {
1900                                     graphItem->setZValue(5 + nbRows - posx);
1901                                 }
1902                             } else if (mode == HISTOGRAM) {
1903                                 // HISTOGRAM
1904                                 if (j == 1) {
1905                                     x0 = widthItem * ((j - 1) * (nbRealRows + 1) + posx) + widthItem;
1906                                 }
1907                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1908                                     x1 = widthItem * ((j - 1) * (nbRealRows + 1) + posx) + widthItem;
1909                                 }
1910                                 graphItem = m_scene->addRect(widthItem * ((j - 1) * (nbRealRows + 1) + posx) + widthItem / 2.0, (val < 0 ? -yorigin : -val), widthItem, (val < 0 ? yorigin - val : -yorigin + val), outlinePen, brush);
1911                                 if (val > ymax) {
1912                                     ymax = val;
1913                                 }
1914                                 if (val < ymin) {
1915                                     ymin = val;
1916                                 }
1917                             } else if (mode == POINT) {
1918                                 // POINTS
1919                                 double xmin = widthItem * (j - 1) * (nbRealRows + 1) - radius;
1920                                 if (j == 1) {
1921                                     x0 = xmin + radius;
1922                                 }
1923                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1924                                     x1 = xmin + radius;
1925                                 }
1926                                 graphItem = drawPoint(xmin, -val, radius, posx, brush);
1927                                 if (val > ymax) {
1928                                     ymax = val;
1929                                 }
1930                                 if (val < ymin) {
1931                                     ymin = val;
1932                                 }
1933                             } else if (mode == LINE) {
1934                                 // LINES
1935                                 // Empty cells are ignored
1936                                 if (j == 1) {
1937                                     x0 = widthItem * (j - 1) * (nbRealRows + 1);
1938                                 }
1939                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1940                                     x1 = widthItem * (j - 1) * (nbRealRows + 1);
1941                                 }
1942 
1943                                 if (!valstring.isEmpty()) {
1944                                     if (jprevious == -1) {
1945                                         jprevious = j;
1946                                     }
1947 
1948                                     // Create pen
1949                                     color.setAlpha(255);
1950                                     QPen pen = QPen(color);
1951                                     pen.setWidthF(radius >= (1 << 15) ? 0 : radius);
1952                                     pen.setCapStyle(Qt::RoundCap);
1953                                     pen.setJoinStyle(Qt::RoundJoin);
1954 
1955                                     QTableWidgetItem* tableItem2 = ui.kTable->item(xx, jprevious);
1956                                     if (tableItem2 != nullptr) {
1957                                         QString val2string = tableItem->text();
1958                                         if (!val2string.isEmpty()) {
1959                                             double val2 = tableItem2->data(DATA_VALUE).toDouble();
1960                                             if (inPositive) {
1961                                                 val2 = qAbs(val2);
1962                                             }
1963 
1964                                             QGraphicsLineItem* line = m_scene->addLine(widthItem * (jprevious - 1) * (nbRealRows + 1) - (jprevious == j ? widthItem / 20.0 : 0), -val2, widthItem * (j - 1) * (nbRealRows + 1), -val, pen);
1965                                             line->setZValue(10);
1966                                             if (isShadowVisible()) {
1967                                                 auto line_shadow = new QGraphicsDropShadowEffect();
1968                                                 line_shadow->setOffset(3);
1969                                                 line->setGraphicsEffect(line_shadow);
1970                                             }
1971                                             graphItem = drawPoint(widthItem * (j - 1) * (nbRealRows + 1) - radius * 0.7, -val, radius * 0.7, posx % 5, brush);
1972                                             if (isShadowVisible()) {
1973                                                 auto line_shadow = new QGraphicsDropShadowEffect();
1974                                                 line_shadow->setOffset(3);
1975                                                 line->setGraphicsEffect(line_shadow);
1976                                             }
1977                                             graphItem->setZValue(20);
1978                                             if (val > ymax) {
1979                                                 ymax = val;
1980                                             }
1981                                             if (val < ymin) {
1982                                                 ymin = val;
1983                                             }
1984                                             jprevious = j;
1985                                         }
1986                                     }
1987                                 }
1988                             } else if (mode == BUBBLE) {
1989                                 // BUBBLE
1990                                 ymin = 0;
1991                                 ymax = ystep * nbRealRows;
1992 
1993                                 radius =  sqrt(qAbs(val) / maxLimit) * qMin(ystep, jstep * xstep);
1994                                 double xmin = widthItem * (j - 1) * (nbRealRows + 1) - radius;
1995 
1996                                 graphItem = m_scene->addEllipse(xmin, -ystep * posx - radius, 2 * radius, 2 * radius, outlinePen, brush);
1997                                 if (graphItem != nullptr) {
1998                                     graphItem->setZValue(5 + 5 * (maxLimit - qAbs(val)) / maxLimit);
1999                                 }
2000                             }  else if (mode == PIE || mode == CONCENTRICPIE) {
2001                                 // PIE
2002                                 val = qAbs(val);
2003 
2004                                 // Compute absolute sum of the column
2005                                 double previousSum = 0;
2006                                 for (int x2 = 0; x2 < nbRows; ++x2) {
2007                                     if (!m_sumRows[x2 + 1]) {
2008                                         QTableWidgetItem* tabItem = ui.kTable->item(x2, j);
2009                                         if (tabItem != nullptr) {
2010                                             double absVal = qAbs(tabItem->data(DATA_VALUE).toDouble());
2011                                             if (x2 < xx) {
2012                                                 previousSum += absVal;
2013                                             }
2014                                         }
2015                                     }
2016                                 }
2017 
2018                                 if (maxLimit != 0.0) {
2019                                     int nbvals = xname.split(OBJECTSEPARATOR).count();
2020                                     double step = 0;
2021                                     double p = 0;
2022                                     if (mode == CONCENTRICPIE) {
2023                                         step = 100.0 / static_cast<double>(nbLevel + 1);
2024                                         p = step * (nbLevel + 1 - nbvals);
2025                                     }
2026 
2027                                     QPainterPath path;
2028                                     path.moveTo(BOX_SIZE * ((j - 1) % nbColInMode2) + BOX_SIZE / 2.0, BOX_SIZE * floor((j - 1) / nbColInMode2) + BOX_SIZE / 2.0);
2029                                     path.arcTo(BOX_SIZE * ((j - 1) % nbColInMode2) + BOX_MARGIN + p / 2.0, BOX_SIZE * floor((j - 1) / nbColInMode2) + BOX_MARGIN + p / 2.0, BOX_SIZE - 2 * BOX_MARGIN - p, BOX_SIZE - 2 * BOX_MARGIN - p, 90.0 - 360.0 * previousSum / maxLimit, -360.0 * val / maxLimit);
2030                                     path.closeSubpath();
2031                                     if (mode == CONCENTRICPIE && nbvals <= nbLevel + 1 && nbvals > 1) {
2032                                         p = step * (nbLevel + 1 - nbvals + 1);
2033                                         QPainterPath path2;
2034                                         path2.addEllipse(BOX_SIZE * ((j - 1) % nbColInMode2) + BOX_MARGIN + p / 2.0,
2035                                                          BOX_SIZE * floor((j - 1) / nbColInMode2) + BOX_MARGIN + p / 2.0,
2036                                                          BOX_SIZE - 2 * BOX_MARGIN - p,
2037                                                          BOX_SIZE - 2 * BOX_MARGIN - p);
2038                                         path -= path2;
2039                                     }
2040                                     graphItem = m_scene->addPath(path, outlinePen, brush);
2041                                 }
2042                             }  else if (mode == TREEMAP) {
2043                                 // TREEMAP
2044                                 QString id = SKGServices::intToString(xx) % QStringLiteral("-") % SKGServices::intToString(j);
2045                                 SKGTreeMap tm = treemap[id];
2046                                 graphItem = m_scene->addRect(tm.getX(), tm.getY(), tm.getW(), tm.getH(), outlinePen, brush);
2047                             }
2048 
2049                             // Compute pareto points
2050                             if (mode == POINT || mode == LINE) {
2051                                 paretoPoints[widthItem * (j - 1) * (nbRealRows + 1)] = val;
2052                             } else if (mode == HISTOGRAM) {
2053                                 paretoPoints[widthItem * ((j - 1) * (nbRealRows + 1) + posx) + widthItem] = val;
2054                             }
2055 
2056                             if (graphItem != nullptr) {
2057                                 if (graphItem->zValue() == 0) {
2058                                     graphItem->setZValue(5);
2059                                 }
2060                                 graphItem->setToolTip(tableItem->toolTip());
2061                                 bool isSelect = (isSelectable() && ((tableItem->flags() & Qt::ItemIsEnabled) != 0u));
2062                                 graphItem->setFlag(QGraphicsItem::ItemIsSelectable, isSelect);
2063                                 if (isSelect) {
2064                                     graphItem->setCursor(Qt::PointingHandCursor);
2065                                 }
2066                                 graphItem->setData(1, xx);
2067                                 graphItem->setData(2, j);
2068                                 graphItem->setData(DATA_COLOR_H, color_h);
2069                                 graphItem->setData(DATA_COLOR_S, color_s);
2070                                 graphItem->setData(DATA_COLOR_V, color_v);
2071                                 graphItem->setData(DATA_Z_VALUE, graphItem->zValue());
2072 
2073                                 m_mapItemGraphic[tableItem] = graphItem;
2074                             }
2075                         }
2076                     } else {
2077                         SKGTRACE << "WARNING: cell " << posx << "," << j << " null" << SKGENDL;
2078                     }
2079                 }
2080 
2081                 ++posx;
2082             }
2083         }
2084 
2085         // Draw axis
2086         double scaleText = 0.0;
2087         auto nbRowInMode2 = static_cast<int>(((nbColumns - 1) / nbColInMode2));
2088         if (nbRealRows != 0) {
2089             if (mode == TREEMAP) {
2090                 // TREEMAP
2091             } else if (mode == PIE || mode == CONCENTRICPIE) {
2092                 // PIE
2093                 if (nbRowInMode2 * nbColInMode2 < nbColumns - 1) {
2094                     ++nbRowInMode2;
2095                 }
2096                 for (int i = 0; i <= nbColInMode2; ++i) {
2097                     QGraphicsLineItem* item = m_scene->addLine(BOX_SIZE * i, 0, BOX_SIZE * i, BOX_SIZE * nbRowInMode2);
2098                     item->setPen(axisPen);
2099                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2100                 }
2101                 for (int i = 0; i <= nbRowInMode2; ++i) {
2102                     QGraphicsLineItem* item = m_scene->addLine(0, BOX_SIZE * i, BOX_SIZE * nbColInMode2, BOX_SIZE * i);
2103                     item->setPen(axisPen);
2104                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2105                 }
2106 
2107                 for (int j = 1; j < nbColumns; ++j) {
2108                     // Get column name
2109                     QString yname = ui.kTable->horizontalHeaderItem(j)->text();
2110 
2111                     QGraphicsTextItem* textItem = m_scene->addText(yname);
2112                     textItem->setDefaultTextColor(m_textColor);
2113                     textItem->setPos(BOX_SIZE * ((j - 1) % nbColInMode2) + 2, BOX_SIZE * floor((j - 1) / nbColInMode2) + 2);
2114                     textItem->setScale(.5);
2115                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2116                     textItem->setZValue(20);
2117                 }
2118             } else {
2119                 // STACK & HISTOGRAMM
2120                 QGraphicsLineItem* item;
2121 
2122                 // Compute scale text
2123                 if (mode != BUBBLE) {
2124                     QGraphicsTextItem* t = m_scene->addText(SKGServices::toCurrencyString(-qMax(qAbs(ymax), qAbs(ymin)), m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2125                     QRectF bRect = t->boundingRect();
2126                     scaleText = 0.8 * qMin(marginLeft / bRect.width(), qMin(marginLeft / bRect.height(), ystep / bRect.height()));
2127                     m_scene->removeItem(t);
2128                 } else {
2129                     maxLimit = ystep * nbRealRows;
2130                 }
2131 
2132                 if (ystep > 0) {
2133                     // Draw
2134                     int i = 1;
2135                     for (double posy = yorigin + ystep ; posy <= maxLimit ; posy = posy + ystep) {
2136                         item = m_scene->addLine(0, -posy, maxX, -posy);
2137                         item->setPen(gridPen);
2138                         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2139                         item->setZValue(1);
2140                         QGraphicsTextItem* textItem;
2141                         if (mode == BUBBLE) {
2142                             auto* cel = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2143                             ++i;
2144                             if (cel == nullptr) {
2145                                 cel = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2146                                 ++i;
2147                             }
2148                             textItem = m_scene->addText(cel != nullptr ? cel->text() : QString());
2149                         } else {
2150                             textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2151                         }
2152 
2153                         QRectF textRect = textItem->boundingRect();
2154                         if (scaleText == 0) {
2155                             scaleText = 0.8 * qMin(marginLeft / textRect.width(), qMin(marginLeft / textRect.height(), ystep / textRect.height()));
2156                         }
2157                         textItem->setDefaultTextColor(m_textColor);
2158                         textItem->setScale(scaleText);
2159                         textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2160                         textItem->setZValue(20);
2161                         double delta = scaleText * textRect.height() / 2.0;
2162 
2163                         textItem->setPos(-marginLeft, -posy - delta);
2164                     }
2165 
2166                     i = 0;
2167                     for (double posy = yorigin ; (posy == yorigin) || posy >= minLimit; posy = posy - ystep) {
2168                         item = m_scene->addLine(0, -posy, maxX, -posy);
2169                         item->setPen(gridPen);
2170                         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2171                         item->setZValue(1);
2172 
2173                         QGraphicsTextItem* textItem;
2174                         if (mode == BUBBLE) {
2175                             auto* cel = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2176                             textItem = m_scene->addText(cel != nullptr ? cel->text() : QString());
2177                         } else {
2178                             textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2179                         }
2180 
2181                         QRectF textRect = textItem->boundingRect();
2182                         if (scaleText == 0) {
2183                             scaleText = 0.8 * qMin(marginLeft / textRect.width(), qMin(marginLeft / textRect.height(), ystep / textRect.height()));
2184                         }
2185                         textItem->setDefaultTextColor(m_textColor);
2186                         textItem->setScale(scaleText);
2187                         textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2188                         textItem->setZValue(20);
2189                         double delta = scaleText * textRect.height() / 2.0;
2190 
2191                         textItem->setPos(-marginLeft, -posy - delta);
2192                     }
2193                 }
2194 
2195                 if (mode == HISTOGRAM || mode == POINT || mode == LINE || mode == BUBBLE || mode == STACKAREA || mode == STACKCOLUMNS) {
2196                     // Line y
2197                     for (int j = 1; j < nbColumns; j += jstep) {
2198                         QString yname = ui.kTable->horizontalHeaderItem(j)->text();
2199                         double posx2 = xstep + (j - 2) * xstep;
2200 
2201                         QGraphicsTextItem* textItem = m_scene->addText(yname);
2202 
2203                         QRectF textRect = textItem->boundingRect();
2204                         if (scaleText == 0) {
2205                             scaleText = 0.8 * qMin(marginLeft / textRect.width(), qMin(marginLeft / textRect.height(), ystep / textRect.height()));
2206                         }
2207                         textItem->setDefaultTextColor(m_textColor);
2208                         textItem->setScale(scaleText);
2209                         textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2210                         textItem->setZValue(20);
2211                         double delta = scaleText * textRect.height() / 2.0;
2212                         textItem->setRotation(90);
2213                         textItem->moveBy(textRect.height() *scaleText, 0);
2214 
2215                         textItem->setPos(posx2 + delta, -yorigin);
2216 
2217                         QGraphicsLineItem* graphicItem = m_scene->addLine(posx2, qMax(-yorigin, -minLimit), posx2, qMin(-yorigin, -maxLimit));
2218                         graphicItem->setPen(gridPen);
2219                         graphicItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2220                     }
2221                 }
2222 
2223                 // Axis x
2224                 if (yorigin == 0.0) {
2225                     item = m_scene->addLine(0, -yorigin, maxX, -yorigin, axisPen);
2226                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2227                     item->setZValue(2);
2228                 }
2229 
2230                 // Rect
2231                 {
2232                     QGraphicsRectItem* graphicItem = m_scene->addRect(0, qMax(-yorigin, -minLimit), maxX, qMin(-yorigin, -maxLimit) - (qMax(-yorigin, -minLimit)), axisPen);
2233                     graphicItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2234                     graphicItem->setZValue(2);
2235                 }
2236             }
2237         }
2238 
2239         // Draw Average
2240         bool lineCondition = (mode == HISTOGRAM || mode == POINT || mode == LINE) && (scaleText != 0.0) && nbRowsDrawed == 1;
2241         int averageCol = getAverageColumnIndex();
2242         if (m_averageVisible && lineCondition && averageCol != -1) {
2243             QTableWidgetItem* tableItem = ui.kTable->item(0, averageCol);
2244             if (tableItem != nullptr) {
2245                 double posy = tableItem->data(DATA_VALUE).toDouble();
2246                 if (inPositive) {
2247                     posy = qAbs(posy);
2248                 }
2249 
2250                 QGraphicsLineItem* item = m_scene->addLine(0, -posy, maxX, -posy);
2251                 dotPen.setColor(m_averageColor);
2252                 item->setPen(dotPen);
2253                 item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2254                 item->setZValue(1);
2255                 item->setToolTip(tableItem->toolTip());
2256 
2257                 QGraphicsTextItem* textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2258                 QRectF textRect = textItem->boundingRect();
2259                 double delta = scaleText * textRect.height() / 2.0;
2260                 textItem->setPos(maxX, -posy - delta);
2261                 textItem->setDefaultTextColor(m_averageColor);
2262                 textItem->setScale(scaleText);
2263                 textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2264                 textItem->setZValue(20);
2265                 textItem->setToolTip(tableItem->toolTip());
2266             }
2267         }
2268 
2269         // Draw Min & Max limits
2270         int minCol = getMinColumnIndex();
2271         if (lineCondition && minCol != -1) {
2272             // Min
2273             {
2274                 QTableWidgetItem* tableItem = ui.kTable->item(0, minCol);
2275                 if (tableItem != nullptr) {
2276                     double posy = tableItem->data(DATA_VALUE).toDouble();
2277                     if (inPositive) {
2278                         posy = qAbs(posy);
2279                     }
2280 
2281                     QGraphicsLineItem* item = m_scene->addLine(0, -posy, maxX, -posy);
2282                     dotPen.setColor(m_minColor);
2283                     item->setPen(dotPen);
2284                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2285                     item->setZValue(1);
2286                     item->setToolTip(tableItem->toolTip());
2287 
2288                     QGraphicsTextItem* textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2289                     QRectF textRect = textItem->boundingRect();
2290                     double delta = scaleText * textRect.height() / 2.0;
2291                     textItem->setPos(maxX, -posy - delta);
2292                     textItem->setDefaultTextColor(m_minColor);
2293                     textItem->setScale(scaleText);
2294                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2295                     textItem->setZValue(20);
2296                     textItem->setToolTip(tableItem->toolTip());
2297                 }
2298             }
2299 
2300             // Max
2301             {
2302                 QTableWidgetItem* tableItem = ui.kTable->item(0, minCol + 1);
2303                 if (tableItem != nullptr) {
2304                     double posy = tableItem->data(DATA_VALUE).toDouble();
2305                     if (inPositive) {
2306                         posy = qAbs(posy);
2307                     }
2308 
2309                     QGraphicsLineItem* item = m_scene->addLine(0, -posy, maxX, -posy);
2310                     dotPen.setColor(m_maxColor);
2311                     item->setPen(dotPen);
2312                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2313                     item->setZValue(1);
2314                     item->setToolTip(tableItem->toolTip());
2315 
2316                     QGraphicsTextItem* textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2317                     QRectF textRect = textItem->boundingRect();
2318                     double delta = scaleText * textRect.height() / 2.0;
2319                     textItem->setPos(maxX, -posy - delta);
2320                     textItem->setDefaultTextColor(m_maxColor);
2321                     textItem->setScale(scaleText);
2322                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2323                     textItem->setZValue(20);
2324                     textItem->setToolTip(tableItem->toolTip());
2325                 }
2326             }
2327         }
2328 
2329         // Draw Linear Regression
2330         if (m_linearRegressionVisible && lineCondition && m_indexLinearRegression != -1) {
2331             QTableWidgetItem* tableItem = ui.kTable->item(0, m_indexLinearRegression);
2332             if (tableItem != nullptr) {
2333                 QString f = tableItem->text();
2334 
2335                 QScriptEngine myEngine;
2336                 QString f0 = f;
2337                 f0 = f0.remove(QStringLiteral("y="));
2338                 f0 = f0.replace('x', '1');
2339                 f0 = f0.replace(',', '.');  // Replace comma by a point in case of typo
2340                 if (inPositive) {
2341                     f0 = "Math.abs(" % f0 % ')';
2342                 }
2343                 double y0 = myEngine.evaluate(f0).toNumber();
2344 
2345                 QString f1 = f;
2346                 f1 = f1.remove(QStringLiteral("y="));
2347                 f1 = f1.replace('x', SKGServices::intToString(nbRealColumns - 1));
2348                 f1 = f1.replace(',', '.');  // Replace comma by a point in case of typo
2349                 if (inPositive) {
2350                     f1 = "Math.abs(" % f1 % ')';
2351                 }
2352                 double y1 = myEngine.evaluate(f1).toNumber();
2353 
2354                 QGraphicsLineItem* item = m_scene->addLine(x0, -y0, x1, -y1);
2355                 dotPen.setColor(m_tendencyColor);
2356                 item->setPen(dotPen);
2357                 item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2358                 item->setZValue(1);
2359                 item->setToolTip(f);
2360             }
2361         }
2362 
2363         // Draw pareto
2364         if (lineCondition && m_paretoVisible && !paretoPoints.isEmpty()) {
2365             // Compute the sum
2366             double sum = 0.0;
2367             QList<double> list = paretoPoints.keys();
2368             for (auto xp1 : qAsConst(list)) {
2369                 sum += paretoPoints[xp1];
2370             }
2371 
2372             // Draw the second axis
2373             for (int i = 0 ; i <= 100 ; i = i + 10) {
2374                 double posy = (maxLimit - minLimit) * static_cast<double>(i) / 100.0 + minLimit;
2375 
2376                 QGraphicsTextItem* textItem = m_scene->addText(SKGServices::intToString(i) % "%");
2377                 QRectF textRect = textItem->boundingRect();
2378                 textItem->setDefaultTextColor(m_paretoColor);
2379                 textItem->setScale(scaleText);
2380                 textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2381                 textItem->setZValue(19);
2382 
2383                 double delta = scaleText * textRect.height() / 2.0;
2384                 textItem->setPos(maxX,  -posy - delta);
2385             }
2386 
2387             // Draw the curve
2388             double x00 = -1;
2389             double y00 = -1;
2390             double csum = 0.0;
2391             for (auto xp2 : qAsConst(list)) {
2392                 csum += paretoPoints[xp2];
2393                 if (x00 != -1) {
2394                     QGraphicsLineItem* item = m_scene->addLine(x00, -((maxLimit - minLimit) * y00 / sum + minLimit), xp2, -((maxLimit - minLimit) * csum / sum + minLimit));
2395                     dotPen.setColor(m_paretoColor);
2396                     item->setPen(dotPen);
2397                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2398                     item->setZValue(1);
2399                 }
2400                 x00 = xp2;
2401                 y00 = csum;
2402             }
2403         }
2404         // Draw legend
2405         if (m_legendVisible) {
2406             QPointF legendPosition;
2407             double maxY;
2408             if (mode == TREEMAP) {
2409                 scaleText = 0.2;
2410                 margin = BOX_SIZE / 10;
2411                 legendPosition = QPointF(BOX_SIZE + margin, 0);
2412                 maxY = BOX_SIZE * nbRowInMode2;
2413             } else if (mode == PIE || mode == CONCENTRICPIE) {
2414                 if (nbRowInMode2 * nbColInMode2 < nbColumns - 1) {
2415                     ++nbRowInMode2;
2416                 }
2417 
2418                 scaleText = 0.2;
2419                 margin = BOX_SIZE / 10;
2420                 legendPosition = QPointF(BOX_SIZE * nbColInMode2 + margin, 0);
2421                 maxY = BOX_SIZE * nbRowInMode2;
2422             } else {
2423                 legendPosition = QPointF(maxX + margin, qMin(-yorigin, -maxLimit) - margin / 6);
2424                 maxY = qMax(-yorigin, -minLimit);
2425             }
2426             addLegend(legendPosition, margin / 4, scaleText, maxY);
2427         }
2428     }
2429     {
2430         SKGTRACEINFUNC(10)
2431         m_scene->setSceneRect(QRectF());
2432         ui.graphicView->setScene(m_scene);
2433         ui.graphicView->show();
2434         if (m_tableVisible) {
2435             ui.kTable->show();
2436         }
2437         ui.graphicView->initializeZoom();
2438 
2439         // Add selection event on scene
2440         connect(m_scene, &SKGGraphicsScene::selectionChanged, this, &SKGTableWithGraph::onSelectionChangedInGraph, Qt::QueuedConnection);
2441         connect(m_scene, &SKGGraphicsScene::doubleClicked, this, &SKGTableWithGraph::onDoubleClickGraph, Qt::QueuedConnection);
2442     }
2443     QApplication::restoreOverrideCursor();
2444 }
2445 
2446 int SKGTableWithGraph::getNbColumns(bool iWithComputed) const
2447 {
2448     int nbColumns = ui.kTable->columnCount();
2449     if (!iWithComputed) {
2450         if (m_indexMin != -1) {
2451             nbColumns -= 2;
2452         }
2453         if (m_indexAverage != -1) {
2454             --nbColumns;
2455         }
2456         if (m_indexSum != -1) {
2457             --nbColumns;
2458         }
2459         if (m_indexLinearRegression != -1) {
2460             --nbColumns;
2461         }
2462     }
2463     return nbColumns;
2464 }
2465 
2466 int SKGTableWithGraph::getAverageColumnIndex() const
2467 {
2468     return m_indexAverage;
2469 }
2470 
2471 int SKGTableWithGraph::getMinColumnIndex() const
2472 {
2473     return m_indexMin;
2474 }
2475 
2476 double SKGTableWithGraph::computeStepSize(double iRange, double iTargetSteps)
2477 {
2478     // Calculate an initial guess at step size
2479     double tempStep = iRange / iTargetSteps;
2480     // Get the magnitude of the step size
2481     double mag = floor(log10(tempStep));
2482     double magPow = pow(static_cast<double>(10.0), mag);
2483     // Calculate most significant digit of the new step size
2484     double magMsd = static_cast<int>(tempStep / magPow + .5);
2485     // promote the MSD to either 1, 2, or 5
2486     if (magMsd > 5.0) {
2487         magMsd = 10.0;
2488     } else if (magMsd > 2.0) {
2489         magMsd = 5.0;
2490     } else if (magMsd > 1.0) {
2491         magMsd = 2.0;
2492     }
2493     return magMsd * magPow;
2494 }
2495 
2496 void SKGTableWithGraph::addLegend(const QPointF iPosition, double iSize, double iScaleText, double iMaxY)
2497 {
2498     SKGTRACEINFUNC(10)
2499 
2500     QPen outlinePen(m_outlineColor);
2501     outlinePen.setWidthF((iScaleText * 4.0 / 500.0) >= (1 << 15) ? 0 : (iScaleText * 4.0 / 500.0));
2502 
2503     if (m_scene != nullptr) {
2504         GraphType mode =  getGraphType();
2505         int nbRows = ui.kTable->rowCount();
2506         int nbRealRows = nbRows;
2507         for (int posy = 0; posy < nbRows; ++posy) {
2508             if (m_sumRows.at(posy + 1)) {
2509                 --nbRealRows;
2510             }
2511         }
2512         int nbColumns = getNbColumns(false);
2513         if (nbColumns > 1) {
2514             double margin = 1.2;
2515             double currentYPos = iPosition.y();
2516             double currentXPos = iPosition.x();
2517             double currentXMaxSize = 0;
2518             for (int i = 0; i < nbRows; ++i) {
2519                 auto* btn = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2520                 if (btn != nullptr) {
2521                     // Get title
2522                     QString title = btn->text();
2523 
2524                     // Build brush
2525                     QColor color1;
2526                     QTableWidgetItem* it = ui.kTable->item(i, 1);
2527                     if (it != nullptr) {
2528                         QGraphicsItem* graphicItem = m_mapItemGraphic.value(it);
2529                         if (graphicItem != nullptr) {
2530                             // Draw box
2531                             color1 = QColor::fromHsv(graphicItem->data(DATA_COLOR_H).toInt(),
2532                                                      graphicItem->data(DATA_COLOR_S).toInt(),
2533                                                      graphicItem->data(DATA_COLOR_V).toInt());
2534                             color1.setAlpha(ALPHA);
2535                         }
2536                     }
2537                     QColor color2;
2538                     it = ui.kTable->item(i, nbColumns - 1 - m_nbVirtualColumns);
2539                     if (it != nullptr) {
2540                         QGraphicsItem* graphicItem = m_mapItemGraphic.value(it);
2541                         if (graphicItem != nullptr) {
2542                             // Draw box
2543                             color2 = QColor::fromHsv(graphicItem->data(DATA_COLOR_H).toInt(),
2544                                                      graphicItem->data(DATA_COLOR_S).toInt(),
2545                                                      graphicItem->data(DATA_COLOR_V).toInt());
2546                             color2.setAlpha(ALPHA);
2547                         }
2548                     }
2549 
2550                     QLinearGradient grandient(currentXPos, currentYPos, currentXPos + 2.0 * iSize, currentYPos);
2551                     grandient.setColorAt(0, color1);
2552                     grandient.setColorAt(1, color2);
2553 
2554                     // Draw legend item
2555                     QGraphicsItem* item = nullptr;
2556                     if (mode == POINT || mode == LINE) {
2557                         item = drawPoint(currentXPos, currentYPos + iSize / 2.0, iSize / 2.0, mode == POINT ? i : (i % 5), QBrush(grandient));
2558                     } else if (mode == BUBBLE) {
2559                         item = m_scene->addEllipse(currentXPos, currentYPos, iSize, iSize, outlinePen, QBrush(grandient));
2560                     } else if (mode == PIE || mode == CONCENTRICPIE) {
2561                         QPainterPath path;
2562                         path.moveTo(currentXPos + iSize / 2.0, currentYPos + iSize / 2.0);
2563                         path.arcTo(currentXPos, currentYPos, iSize, iSize, 45, 270);
2564                         path.closeSubpath();
2565                         if (mode == CONCENTRICPIE) {
2566                             QPainterPath path2;
2567                             double p = iSize / 3.0;
2568                             path2.addEllipse(currentXPos + p, currentYPos + p, iSize -  2.0 * p, iSize -  2.0 * p);
2569                             path -= path2;
2570                         }
2571                         item = m_scene->addPath(path, outlinePen, QBrush(grandient));
2572                     } else {
2573                         item = m_scene->addRect(currentXPos, currentYPos, iSize, iSize, outlinePen, QBrush(grandient));
2574                     }
2575                     if (item != nullptr) {
2576                         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2577                         item->setToolTip(title);
2578 
2579                         // Set shadow
2580                         if (isShadowVisible()) {
2581                             auto ellipse_shadow = new QGraphicsDropShadowEffect();
2582                             ellipse_shadow->setOffset(3);
2583                             item->setGraphicsEffect(ellipse_shadow);
2584                         }
2585                     }
2586 
2587                     // Draw text
2588                     QGraphicsTextItem* textItem = m_scene->addText(title);
2589                     textItem->setDefaultTextColor(m_textColor);
2590                     textItem->setScale(iScaleText);
2591                     textItem->setPos(currentXPos + margin * iSize, currentYPos + iSize / 2.0 - textItem->boundingRect().height()*iScaleText / 2.0);
2592                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2593 
2594                     // Compute next position
2595                     currentYPos += margin * iSize;
2596                     QRectF textRect = textItem->boundingRect();
2597                     currentXMaxSize = qMax(currentXMaxSize, static_cast<double>(iScaleText * textRect.width()));
2598                     if (currentYPos > iMaxY) {
2599                         currentYPos = iPosition.y();
2600                         currentXPos += currentXMaxSize + 2 * margin * iSize;
2601                         currentXMaxSize = 0;
2602                     }
2603                 }
2604             }
2605         }
2606     }
2607 }
2608 
2609 void SKGTableWithGraph::addArrow(const QPointF iPeak, double iSize, double iArrowAngle, double iDegree)
2610 {
2611     if (m_scene != nullptr) {
2612         QPolygonF pol;
2613         double radian = 3.14 * iArrowAngle / 360.0;
2614         pol << QPointF(0, 0) << QPointF(iSize * cos(radian), iSize * sin(radian)) << QPointF(iSize * cos(radian), -iSize * sin(radian)) << QPointF(0, 0);
2615         QGraphicsPolygonItem* item = m_scene->addPolygon(pol, QPen(m_axisColor, iSize / 20.0), QBrush(m_axisColor));
2616         item->setRotation(iDegree);
2617         item->moveBy(iPeak.x(), iPeak.y());
2618         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2619         item->setZValue(2);
2620     }
2621 }
2622 
2623 QGraphicsItem* SKGTableWithGraph::drawPoint(qreal iX, qreal iY, qreal iRadius, int iMode, const QBrush& iBrush)
2624 {
2625     QGraphicsItem* graphItem = nullptr;
2626     int nbMode = 13;
2627     if (m_scene != nullptr) {
2628         QPen outlinePen(m_outlineColor);
2629         outlinePen.setWidthF((iRadius / 10) >= (1 << 15) ? 0.0 : iRadius / 10);
2630 
2631         QPen pen;
2632         if ((iMode % nbMode) <= 4) {
2633             pen = QPen(iBrush.color());
2634             const QGradient* grad = iBrush.gradient();
2635             if (grad != nullptr) {
2636                 auto stops = grad->stops();
2637                 auto stop = stops.last();
2638                 pen = QPen(stop.second);
2639             }
2640             pen.setWidthF((iRadius / 10) >= (1 << 15) ? 00.0 : iRadius / 10);
2641         }
2642 
2643         switch (iMode % nbMode) {
2644         case 0: {
2645             graphItem = m_scene->addEllipse(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, pen, m_WhiteColor);
2646             break;
2647         }
2648         case 1: {
2649             graphItem = m_scene->addRect(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, pen, m_WhiteColor);
2650             break;
2651         }
2652         case 2: {
2653             QPolygonF polygon;
2654             polygon << QPointF(iX + iRadius, iY + iRadius) << QPointF(iX + 2 * iRadius, iY - iRadius)
2655                     << QPointF(iX, iY - iRadius) << QPointF(iX + iRadius, iY + iRadius);
2656             graphItem = m_scene->addPolygon(polygon, pen, m_WhiteColor);
2657             break;
2658         }
2659         case 3: {
2660             QPolygonF polygon;
2661             polygon << QPointF(iX, iY) << QPointF(iX + iRadius, iY + iRadius)
2662                     << QPointF(iX + 2 * iRadius, iY) << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX, iY);
2663             graphItem = m_scene->addPolygon(polygon, pen, m_WhiteColor);
2664             break;
2665         }
2666         case 4: {
2667             QPolygonF polygon;
2668             polygon << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius)
2669                     << QPointF(iX, iY + iRadius) << QPointF(iX + iRadius, iY - iRadius);
2670             graphItem = m_scene->addPolygon(polygon, pen, m_WhiteColor);
2671             break;
2672         }
2673 
2674 
2675 
2676         case 5: {
2677             graphItem = m_scene->addEllipse(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, outlinePen, iBrush);
2678             break;
2679         }
2680         case 6: {
2681             graphItem = m_scene->addRect(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, outlinePen, iBrush);
2682             break;
2683         }
2684         case 7: {
2685             QPolygonF polygon;
2686             polygon << QPointF(iX, iY - iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius)
2687                     << QPointF(iX + 2 * iRadius, iY - iRadius) << QPointF(iX, iY + iRadius);
2688             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2689             break;
2690         }
2691         case 8: {
2692             QPolygonF polygon;
2693             polygon << QPointF(iX + iRadius, iY + iRadius) << QPointF(iX + 2 * iRadius, iY - iRadius)
2694                     << QPointF(iX, iY - iRadius) << QPointF(iX + iRadius, iY + iRadius);
2695             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2696             break;
2697         }
2698         case 9: {
2699             QPolygonF polygon;
2700             polygon << QPointF(iX, iY) << QPointF(iX + iRadius, iY + iRadius)
2701                     << QPointF(iX + 2 * iRadius, iY) << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX, iY);
2702             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2703             break;
2704         }
2705         case 10: {
2706             QPolygonF polygon;
2707             polygon << QPointF(iX, iY - iRadius) << QPointF(iX + 2 * iRadius, iY - iRadius)
2708                     << QPointF(iX, iY + iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius);
2709             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2710             break;
2711         }
2712         case 11: {
2713             QPolygonF polygon;
2714             polygon << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius)
2715                     << QPointF(iX, iY + iRadius) << QPointF(iX + iRadius, iY - iRadius);
2716             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2717             break;
2718         }
2719         case 12:
2720         default: {
2721             QPainterPath path;
2722             path.addEllipse(iX, iY - iRadius, 2 * iRadius, 2 * iRadius);
2723             path.closeSubpath();
2724 
2725             QPainterPath path2;
2726             path2.addEllipse(iX + iRadius / 2.0, iY - iRadius / 2.0, iRadius, iRadius);
2727             path -= path2;
2728             graphItem = m_scene->addPath(path, outlinePen, iBrush);
2729             break;
2730         }
2731         }
2732     }
2733     if ((graphItem != nullptr) && (iMode % nbMode) <= 4) {
2734         graphItem->setData(DATA_MODE, 1);
2735     }
2736     return graphItem;
2737 }
2738 
2739 SKGError SKGTableWithGraph::exportInFile(const QString& iFileName)
2740 {
2741     SKGError err;
2742     _SKGTRACEINFUNCRC(10, err)
2743     QString lastCodecUsed = QTextCodec::codecForLocale()->name();
2744 
2745     QString extension = QFileInfo(iFileName).suffix().toUpper();
2746     if (extension == QStringLiteral("CSV")) {
2747         // Write file
2748         QSaveFile file(iFileName);
2749         if (!file.open(QIODevice::WriteOnly)) {
2750             err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", iFileName));
2751         } else {
2752             QTextStream out(&file);
2753             out.setCodec(lastCodecUsed.toLatin1().constData());
2754             QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_CSV);
2755             int nbl = dump.count();
2756             for (int i = 0; i < nbl; ++i) {
2757                 out << dump.at(i) << SKGENDL;
2758             }
2759 
2760             // Close file
2761             file.commit();
2762         }
2763     } else {
2764         // Write file
2765         QSaveFile file(iFileName);
2766         if (!file.open(QIODevice::WriteOnly)) {
2767             err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", iFileName));
2768         } else {
2769             QTextStream out(&file);
2770             out.setCodec(lastCodecUsed.toLatin1().constData());
2771             QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_TEXT);
2772             int nbl = dump.count();
2773             for (int i = 0; i < nbl; ++i) {
2774                 out << dump.at(i) << SKGENDL;
2775             }
2776 
2777             // Close file
2778             file.commit();
2779         }
2780     }
2781     return err;
2782 }
2783 
2784 void SKGTableWithGraph::onExport()
2785 {
2786     _SKGTRACEINFUNC(10)
2787     SKGError err;
2788     QString fileName = SKGMainPanel::getSaveFileName(QStringLiteral("kfiledialog:///IMPEXP"), QStringLiteral("text/csv text/plain"), this);
2789     if (!fileName.isEmpty()) {
2790         err = exportInFile(fileName);
2791 
2792         SKGMainPanel::displayErrorMessage(err);
2793         QDesktopServices::openUrl(QUrl(fileName));
2794     }
2795 }
2796 
2797 void SKGTableWithGraph::setGraphType(SKGTableWithGraph::GraphType iType)
2798 {
2799     if (m_displayMode != nullptr) {
2800         auto newIndex = m_displayMode->findData(static_cast<int>(iType));
2801         if (m_displayMode->currentIndex() != newIndex) {
2802             m_displayMode->setCurrentIndex(newIndex);
2803             Q_EMIT modified();
2804         }
2805     }
2806 }
2807 
2808 SKGTableWithGraph::GraphType SKGTableWithGraph::getGraphType() const
2809 {
2810     GraphType mode = static_cast<GraphType>(m_displayMode->itemData(m_displayMode->currentIndex()).toInt());
2811     return mode;
2812 }
2813 
2814 void SKGTableWithGraph::setSelectable(bool iSelectable)
2815 {
2816     if (m_selectable != iSelectable) {
2817         m_selectable = iSelectable;
2818         Q_EMIT modified();
2819     }
2820 }
2821 
2822 bool SKGTableWithGraph::isSelectable() const
2823 {
2824     return m_selectable;
2825 }
2826 
2827 void SKGTableWithGraph::setShadowVisible(bool iShadow)
2828 {
2829     if (m_shadow != iShadow) {
2830         m_shadow = iShadow;
2831         Q_EMIT modified();
2832     }
2833 }
2834 
2835 bool SKGTableWithGraph::isShadowVisible() const
2836 {
2837     return m_shadow;
2838 }
2839 
2840