1 /***************************************************************************
2  * SPDX-FileCopyrightText: 2021 S. MANKOWSKI stephane@mankowski.fr
3  * SPDX-FileCopyrightText: 2021 G. DE BURE support@mankowski.fr
4  * SPDX-License-Identifier: GPL-3.0-or-later
5  ***************************************************************************/
6 /** @file
7  * A table with graph with more features.
8  *
9  * @author Stephane MANKOWSKI / Guillaume DE BURE
10  */
11 #include "skgtablewithgraph.h"
12 
13 #include <kcolorscheme.h>
14 #include <kstringhandler.h>
15 
16 #include <qcollator.h>
17 #include <qdesktopservices.h>
18 #include <qdom.h>
19 #include <qfileinfo.h>
20 #include <qgraphicseffect.h>
21 #include <qgraphicsitem.h>
22 #include <qheaderview.h>
23 #include <qmath.h>
24 #include <qmenu.h>
25 #include <qpainter.h>
26 #include <qprinter.h>
27 #include <qsavefile.h>
28 #include <qscriptengine.h>
29 #include <qscrollbar.h>
30 #include <qtablewidget.h>
31 #include <qtextcodec.h>
32 #include <qtimer.h>
33 #include <qwidgetaction.h>
34 
35 #include <algorithm>
36 
37 #include "skgcolorbutton.h"
38 #include "skgcombobox.h"
39 #include "skggraphicsscene.h"
40 #include "skgmainpanel.h"
41 #include "skgservices.h"
42 #include "skgtraces.h"
43 #include "skgtreemap.h"
44 
45 /**
46   * Data identifier for value
47   */
48 static const int DATA_VALUE = 12;
49 /**
50   * Data identifier for color
51   */
52 static const int DATA_COLOR_H = 11;
53 /**
54   * Data identifier for color
55   */
56 static const int DATA_COLOR_S = 12;
57 /**
58   * Data identifier for color
59   */
60 static const int DATA_COLOR_V = 13;
61 /**
62   * Data identifier for Z value
63   */
64 static const int DATA_Z_VALUE = 14;
65 /**
66   * Data identifier for mode
67   */
68 static const int DATA_MODE = 15;
69 /**
70   * Alpha value
71   */
72 static const int ALPHA = 200;
73 /**
74   * Box size
75   */
76 static const double BOX_SIZE = 120.0;
77 /**
78   * Box margin
79   */
80 static const double BOX_MARGIN = 10.0;
81 
82 #define cloneAction(MENU, ACTION, SLOTTOCALL) \
83     auto act = (MENU)->addAction((ACTION)->icon(), (ACTION)->text()); \
84     act->setCheckable(true); \
85     act->setChecked((ACTION)->isChecked()); \
86     connect(act, &QAction::triggered, this, SLOTTOCALL); \
87     connect(act, &QAction::toggled, ACTION, &QAction::setChecked); \
88     connect(ACTION, &QAction::toggled, act, &QAction::setChecked);
89 
SKGTableWithGraph()90 SKGTableWithGraph::SKGTableWithGraph() : SKGTableWithGraph(nullptr)
91 {}
92 
SKGTableWithGraph(QWidget * iParent)93 SKGTableWithGraph::SKGTableWithGraph(QWidget* iParent)
94     : QWidget(iParent), m_scene(nullptr), m_additionalInformation(NONE), m_nbVirtualColumns(0),
95       m_selectable(true), m_toolBarVisible(true), m_graphTypeVisible(true), m_limitVisible(true), m_averageVisible(true),
96       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),
97       m_mainMenu(nullptr),
98       m_indexSum(-1), m_indexAverage(-1), m_indexMin(-1), m_indexLinearRegression(-1), m_sortOrder(Qt::AscendingOrder), m_sortColumn(0)
99 {
100     m_axisColor = Qt::gray;
101     m_gridColor = Qt::lightGray;
102     m_minColor = Qt::red;
103     m_maxColor = Qt::green;
104     m_paretoColor = Qt::darkRed;
105     m_averageColor = Qt::blue;
106     m_tendencyColor = Qt::darkYellow;
107     m_backgroundColor = Qt::white;
108     m_textColor = Qt::black;
109     m_NegativeColor = KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText);
110     m_WhiteColor = QBrush(Qt::white);
111 
112     ui.setupUi(this);
113     ui.kTextEdit->hide();
114 
115     ui.kFilterEdit->setPlaceholderText(i18n("Search"));
116 
117     m_displayMode = new SKGComboBox(this);
118     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));
119     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));
120     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));
121     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-scatter")), i18nc("Noun, a type of graph with only points", "Point"), static_cast<int>(POINT));
122     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-line")), i18nc("Noun, a type of graph with only lines", "Line"), static_cast<int>(LINE));
123     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));
124     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("skg-chart-bubble")), i18nc("Noun, a type of graph, with bubbles", "Bubble"), static_cast<int>(BUBBLE));
125     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));
126     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));
127     m_displayMode->addItem(SKGServices::fromTheme(QStringLiteral("map-flat")), i18nc("Noun, a type of graph that looks treemap", "Treemap"), static_cast<int>(TREEMAP));
128 
129     ui.graphicView->addToolbarWidget(m_displayMode);
130 
131     ui.kShow->addItem(QStringLiteral("table"), i18n("Table"),               QStringLiteral("view-list-details"), QString(), QString(),            QStringLiteral("text"),        QStringLiteral("graph"), QString(), Qt::META + Qt::Key_T);
132     ui.kShow->addItem(QStringLiteral("graph"), i18n("Graph"),               QStringLiteral("office-chart-pie"), QString(), QString(),                QStringLiteral("text"),        QStringLiteral("table"), QString(), Qt::META + Qt::Key_G);
133     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);
134 
135     ui.kShow->setDefaultState(QStringLiteral("table;graph"));
136 
137     connect(ui.kShow, &SKGShow::stateChanged, this, &SKGTableWithGraph::onDisplayModeChanged, Qt::QueuedConnection);
138 
139     m_timer.setSingleShot(true);
140     connect(&m_timer, &QTimer::timeout, this, &SKGTableWithGraph::refresh, Qt::QueuedConnection);
141 
142     m_timerRedraw.setSingleShot(true);
143     connect(&m_timerRedraw, &QTimer::timeout, this, &SKGTableWithGraph::redrawGraph, Qt::QueuedConnection);
144 
145     // Build contextual menu
146     ui.kTable->setContextMenuPolicy(Qt::CustomContextMenu);
147     connect(ui.kTable, &SKGTableWidget::customContextMenuRequested, this, &SKGTableWithGraph::showMenu);
148 
149     m_mainMenu = new QMenu(ui.kTable);
150 
151     QAction* actExport = m_mainMenu->addAction(SKGServices::fromTheme(QStringLiteral("document-export")), i18nc("Noun, user action", "Export..."));
152     connect(actExport, &QAction::triggered, this, &SKGTableWithGraph::onExport);
153 
154     // Add graph mode in menu
155     getGraphContextualMenu()->addSeparator();
156     auto displayModeMenu = new SKGComboBox(this);
157     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));
158     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));
159     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));
160     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-scatter")), i18nc("Noun, a type of graph with only points", "Point"), static_cast<int>(POINT));
161     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("office-chart-line")), i18nc("Noun, a type of graph with only lines", "Line"), static_cast<int>(LINE));
162     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));
163     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("skg-chart-bubble")), i18nc("Noun, a type of graph, with bubbles", "Bubble"), static_cast<int>(BUBBLE));
164     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));
165     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));
166     displayModeMenu->addItem(SKGServices::fromTheme(QStringLiteral("map-flat")), i18nc("Noun, a type of graph that looks treemap", "Treemap"), static_cast<int>(TREEMAP));
167 
168     m_displayModeWidget = new QWidgetAction(this);
169     m_displayModeWidget->setDefaultWidget(displayModeMenu);
170     getGraphContextualMenu()->addAction(m_displayModeWidget);
171 
172     connect(displayModeMenu, static_cast<void (SKGComboBox::*)(int)>(&SKGComboBox::currentIndexChanged), m_displayMode, &SKGComboBox::setCurrentIndex);
173     connect(m_displayMode, static_cast<void (SKGComboBox::*)(int)>(&SKGComboBox::currentIndexChanged), displayModeMenu, &SKGComboBox::setCurrentIndex);
174 
175     // Reset Colors
176     QAction* resetColorsAction = m_mainMenu->addAction(SKGServices::fromTheme(QStringLiteral("format-fill-color")), i18nc("Noun, user action", "Reset default colors"));
177     connect(resetColorsAction, &QAction::triggered, this, &SKGTableWithGraph::resetColors);
178     getGraphContextualMenu()->addAction(resetColorsAction);
179 
180     // Add item in menu of the graph
181     m_allPositiveMenu = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("go-up-search")), i18nc("Noun, user action", "All values in positive"));
182     if (m_allPositiveMenu != nullptr) {
183         m_allPositiveMenu->setCheckable(true);
184     }
185     connect(m_allPositiveMenu, &QAction::toggled, this, &SKGTableWithGraph::redrawGraphDelayed, Qt::QueuedConnection);
186 
187     // Add item in menu of the graph
188     m_actShowLimits = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show limits"));
189     if (m_actShowLimits != nullptr) {
190         m_actShowLimits->setCheckable(true);
191         m_actShowLimits->setChecked(m_limitVisible);
192         connect(m_actShowLimits, &QAction::triggered, this, &SKGTableWithGraph::switchLimitsVisibility);
193 
194         // Clone action on table contextual menu
195         cloneAction(m_mainMenu, m_actShowLimits, &SKGTableWithGraph::switchLimitsVisibility)
196     }
197 
198     // Add item in menu of the graph
199     m_actShowAverage = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show average"));
200     if (m_actShowAverage != nullptr) {
201         m_actShowAverage->setCheckable(true);
202         m_actShowAverage->setChecked(m_averageVisible);
203         connect(m_actShowAverage, &QAction::triggered, this, &SKGTableWithGraph::switchAverageVisibility);
204 
205         // Clone action on table contextual menu
206         cloneAction(m_mainMenu, m_actShowAverage, &SKGTableWithGraph::switchAverageVisibility)
207     }
208 
209     // Add item in menu of the graph
210     m_actShowLinearRegression = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show tendency line"));
211     if (m_actShowLinearRegression != nullptr) {
212         m_actShowLinearRegression->setCheckable(true);
213         m_actShowLinearRegression->setChecked(m_linearRegressionVisible);
214         connect(m_actShowLinearRegression, &QAction::triggered, this, &SKGTableWithGraph::switchLinearRegressionVisibility);
215 
216         // Clone action on table contextual menu
217         cloneAction(m_mainMenu, m_actShowLinearRegression, &SKGTableWithGraph::switchLinearRegressionVisibility)
218     }
219 
220     // Add item in menu of the graph
221     m_actShowPareto = getGraphContextualMenu()->addAction(i18nc("Noun, user action", "Show Pareto curve"));
222     if (m_actShowPareto != nullptr) {
223         m_actShowPareto->setCheckable(true);
224         m_actShowPareto->setChecked(m_paretoVisible);
225         connect(m_actShowPareto, &QAction::triggered, this, &SKGTableWithGraph::switchParetoVisibility);
226     }
227 
228     // Add item in menu of the graph
229     m_actShowLegend = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("help-contents")), i18nc("Noun, user action", "Show legend"));
230     if (m_actShowLegend != nullptr) {
231         m_actShowLegend->setCheckable(true);
232         m_actShowLegend->setChecked(m_legendVisible);
233         connect(m_actShowLegend, &QAction::triggered, this, &SKGTableWithGraph::switchLegendVisibility);
234     }
235 
236     // Add item in menu of the graph
237     m_actShowZero = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("labplot-xy-plot-two-axes-centered-origin")), i18nc("Noun, user action", "Show origin"));
238     if (m_actShowZero != nullptr) {
239         m_actShowZero->setCheckable(true);
240         m_actShowZero->setChecked(m_zeroVisible);
241         connect(m_actShowZero, &QAction::triggered, this, &SKGTableWithGraph::swithOriginVisibility);
242     }
243 
244     // Add item in menu of the graph
245     m_actShowDecimal = getGraphContextualMenu()->addAction(SKGServices::fromTheme(QStringLiteral("format-precision-less")), i18nc("Noun, user action", "Show decimals"));
246     if (m_actShowDecimal != nullptr) {
247         m_actShowDecimal->setCheckable(true);
248         m_actShowDecimal->setChecked(m_decimalsVisible);
249         connect(m_actShowDecimal, &QAction::triggered, this, &SKGTableWithGraph::swithDecimalsVisibility);
250 
251         // Clone action on table contextual menu
252         cloneAction(m_mainMenu, m_actShowDecimal, &SKGTableWithGraph::swithDecimalsVisibility)
253     }
254 
255     // Set headers parameters
256     QHeaderView* verticalHeader = ui.kTable->verticalHeader();
257     if (verticalHeader != nullptr) {
258         verticalHeader->hide();
259         verticalHeader->setDefaultSectionSize(verticalHeader->minimumSectionSize());
260         // verticalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
261     }
262 
263     ui.kTable->setSortingEnabled(false);    // sort must be enable for refresh method
264     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
265     if (horizontalHeader != nullptr) {
266         horizontalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
267         horizontalHeader->show();
268         horizontalHeader->setSortIndicatorShown(true);
269         horizontalHeader->setSortIndicator(m_sortColumn, m_sortOrder);
270         connect(horizontalHeader, &QHeaderView::sortIndicatorChanged, this, &SKGTableWithGraph::refresh);
271     }
272 
273     connect(ui.kTable->horizontalScrollBar(), &QScrollBar::valueChanged, this, &SKGTableWithGraph::onHorizontalScrollBarChanged);
274     connect(m_displayMode, static_cast<void (SKGComboBox::*)(const QString&)>(&SKGComboBox::currentTextChanged), this, &SKGTableWithGraph::redrawGraphDelayed, Qt::QueuedConnection);
275     connect(ui.graphicView, &SKGGraphicsView::resized, this, &SKGTableWithGraph::redrawGraphDelayed, Qt::QueuedConnection);
276 
277     connect(ui.kTable, &SKGTableWidget::cellDoubleClicked, this, &SKGTableWithGraph::onDoubleClick);
278     connect(ui.kTable, &SKGTableWidget::itemSelectionChanged, this, &SKGTableWithGraph::onSelectionChanged);
279     connect(ui.kFilterEdit, &QLineEdit::textChanged, this, &SKGTableWithGraph::onFilterModified);
280 
281 #ifdef SKG_WEBENGINE
282     connect(ui.kTextEdit, &SKGWebView::linkClicked, this, &SKGTableWithGraph::onLinkClicked);
283 #endif
284 #ifdef SKG_WEBKIT
285     ui.kTextEdit->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
286     connect(ui.kTextEdit, &SKGWebView::linkClicked, this, &SKGTableWithGraph::onLinkClicked);
287 #endif
288 }
289 
290 
~SKGTableWithGraph()291 SKGTableWithGraph::~SKGTableWithGraph()
292 {
293     delete m_scene;
294     m_scene = nullptr;
295 
296     m_mainMenu = nullptr;
297     m_actShowLimits = nullptr;
298     m_actShowLinearRegression = nullptr;
299     m_actShowPareto = nullptr;
300     m_displayModeWidget = nullptr;
301     m_displayMode = nullptr;
302 }
303 
onHorizontalScrollBarChanged(int iValue)304 void SKGTableWithGraph::onHorizontalScrollBarChanged(int iValue)
305 {
306     QHeaderView* verticalHeader = ui.kTable->verticalHeader();
307     if (verticalHeader != nullptr) {
308         verticalHeader->setVisible(iValue > 0);
309     }
310 }
311 
isGraphVisible() const312 bool SKGTableWithGraph::isGraphVisible() const
313 {
314     return m_graphVisible;
315 }
316 
isTableVisible() const317 bool SKGTableWithGraph::isTableVisible() const
318 {
319     return m_tableVisible;
320 }
321 
isTextReportVisible() const322 bool SKGTableWithGraph::isTextReportVisible() const
323 {
324     return m_textVisible;
325 }
326 
getShowWidget() const327 SKGShow* SKGTableWithGraph::getShowWidget() const
328 {
329     return ui.kShow;
330 }
setAverageColor(const QColor & iColor)331 void SKGTableWithGraph::setAverageColor(const QColor& iColor)
332 {
333     m_averageColor = iColor;
334 }
335 
setAxisColor(const QColor & iColor)336 void SKGTableWithGraph::setAxisColor(const QColor& iColor)
337 {
338     m_axisColor = iColor;
339 }
340 
setGridColor(const QColor & iColor)341 void SKGTableWithGraph::setGridColor(const QColor& iColor)
342 {
343     m_gridColor = iColor;
344 }
345 
setMaxColor(const QColor & iColor)346 void SKGTableWithGraph::setMaxColor(const QColor& iColor)
347 {
348     m_maxColor = iColor;
349 }
350 
setParetoColor(const QColor & iColor)351 void SKGTableWithGraph::setParetoColor(const QColor& iColor)
352 {
353     m_paretoColor = iColor;
354 }
355 
setMinColor(const QColor & iColor)356 void SKGTableWithGraph::setMinColor(const QColor& iColor)
357 {
358     m_minColor = iColor;
359 }
360 
setTendencyColor(const QColor & iColor)361 void SKGTableWithGraph::setTendencyColor(const QColor& iColor)
362 {
363     m_tendencyColor = iColor;
364 }
365 
setOutlineColor(const QColor & iColor)366 void SKGTableWithGraph::setOutlineColor(const QColor& iColor)
367 {
368     m_outlineColor = iColor;
369 }
370 
setBackgroundColor(const QColor & iColor)371 void SKGTableWithGraph::setBackgroundColor(const QColor& iColor)
372 {
373     m_backgroundColor = iColor;
374 }
375 
setTextColor(const QColor & iColor)376 void SKGTableWithGraph::setTextColor(const QColor& iColor)
377 {
378     m_textColor = iColor;
379 }
380 
setAntialiasing(bool iAntialiasing)381 void SKGTableWithGraph::setAntialiasing(bool iAntialiasing)
382 {
383     ui.graphicView->setAntialiasing(iAntialiasing);
384 }
385 
setGraphTypeSelectorVisible(bool iVisible)386 void SKGTableWithGraph::setGraphTypeSelectorVisible(bool iVisible)
387 {
388     if (m_graphTypeVisible != iVisible) {
389         m_graphTypeVisible = iVisible;
390         if (m_displayMode != nullptr) {
391             m_displayMode->setVisible(m_graphTypeVisible);
392         }
393         if (m_displayModeWidget != nullptr) {
394             m_displayModeWidget->setVisible(m_graphTypeVisible);
395         }
396         Q_EMIT modified();
397     }
398 }
399 
isGraphTypeSelectorVisible() const400 bool SKGTableWithGraph::isGraphTypeSelectorVisible() const
401 {
402     return m_graphTypeVisible;
403 }
404 
switchLimitsVisibility()405 bool SKGTableWithGraph::switchLimitsVisibility()
406 {
407     m_limitVisible = !m_limitVisible;
408     refresh();
409     return m_limitVisible;
410 }
411 
switchAverageVisibility()412 bool SKGTableWithGraph::switchAverageVisibility()
413 {
414     m_averageVisible = !m_averageVisible;
415     refresh();
416     return m_averageVisible;
417 }
418 
switchLegendVisibility()419 bool SKGTableWithGraph::switchLegendVisibility()
420 {
421     m_legendVisible = !m_legendVisible;
422     redrawGraphDelayed();
423     return m_legendVisible;
424 }
425 
swithOriginVisibility()426 bool SKGTableWithGraph::swithOriginVisibility()
427 {
428     m_zeroVisible = !m_zeroVisible;
429     redrawGraphDelayed();
430     return m_zeroVisible;
431 }
432 
swithDecimalsVisibility()433 bool SKGTableWithGraph::swithDecimalsVisibility()
434 {
435     m_decimalsVisible = !m_decimalsVisible;
436     refresh();
437     return m_decimalsVisible;
438 }
439 
switchLinearRegressionVisibility()440 bool SKGTableWithGraph::switchLinearRegressionVisibility()
441 {
442     m_linearRegressionVisible = !m_linearRegressionVisible;
443     refresh();
444     return m_linearRegressionVisible;
445 }
446 
switchParetoVisibility()447 bool SKGTableWithGraph::switchParetoVisibility()
448 {
449     m_paretoVisible = !m_paretoVisible;
450     redrawGraphDelayed();
451     return m_paretoVisible;
452 }
453 
getTableContextualMenu() const454 QMenu* SKGTableWithGraph::getTableContextualMenu() const
455 {
456     return m_mainMenu;
457 }
458 
getGraphContextualMenu() const459 QMenu* SKGTableWithGraph::getGraphContextualMenu() const
460 {
461     return ui.graphicView->getContextualMenu();
462 }
463 
showMenu(const QPoint iPos)464 void SKGTableWithGraph::showMenu(const QPoint iPos)
465 {
466     if (m_mainMenu != nullptr) {
467         m_mainMenu->popup(ui.kTable->mapToGlobal(iPos));
468     }
469 }
470 
table() const471 QTableWidget* SKGTableWithGraph::table() const
472 {
473     return ui.kTable;
474 }
475 
graph() const476 SKGGraphicsView* SKGTableWithGraph::graph() const
477 {
478     return ui.graphicView;
479 }
480 
textReport() const481 SKGWebView* SKGTableWithGraph::textReport() const
482 {
483     return ui.kTextEdit;
484 }
485 
getTable()486 SKGStringListList SKGTableWithGraph::getTable()
487 {
488     // Build table
489     SKGStringListList output;
490 
491     // Get header names
492     int nb2 = ui.kTable->rowCount();
493     int nb = ui.kTable->columnCount();
494     output.reserve(nb2 + 1);
495     QStringList cols;
496     cols.reserve(2 * nb);
497     for (int i = 0; i < nb; ++i) {
498         cols.append(ui.kTable->horizontalHeaderItem(i)->text());
499         cols.append(i18n("%1 (raw)", ui.kTable->horizontalHeaderItem(i)->text()));
500     }
501     output.append(cols);
502 
503     // Get content
504     for (int i = 0; i < nb2; ++i) {
505         QStringList row;
506         row.reserve(nb);
507         for (int j = 0; j < nb; j++) {
508             auto* button = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, j));
509             if (button != nullptr) {
510                 row.append(button->text());
511                 row.append(button->color().toRgb().name());
512             } else {
513                 row.append(ui.kTable->item(i, j)->text());
514                 QString raw = ui.kTable->item(i, j)->data(DATA_VALUE).toString();
515                 if (raw.isEmpty()) {
516                     raw = ui.kTable->item(i, j)->text();
517                 }
518                 row.append(raw);
519             }
520         }
521         output.append(row);
522     }
523     return output;
524 }
getState()525 QString SKGTableWithGraph::getState()
526 {
527     SKGTRACEINFUNC(10)
528     QDomDocument doc(QStringLiteral("SKGML"));
529     QDomElement root = doc.createElement(QStringLiteral("parameters"));
530     doc.appendChild(root);
531 
532     if (ui.graphicView->isVisible() && ui.kTable->isVisible()) {
533         root.setAttribute(QStringLiteral("splitterState"), QString(ui.splitter->saveState().toHex()));
534     }
535     root.setAttribute(QStringLiteral("graphMode"), SKGServices::intToString(static_cast<int>(getGraphType())));
536     root.setAttribute(QStringLiteral("allPositive"), m_allPositiveMenu->isChecked() ? QStringLiteral("Y") : QStringLiteral("N"));
537     root.setAttribute(QStringLiteral("filter"), ui.kFilterEdit->text());
538     root.setAttribute(QStringLiteral("limitVisible"), m_limitVisible ? QStringLiteral("Y") : QStringLiteral("N"));
539     root.setAttribute(QStringLiteral("averageVisible"), m_averageVisible ? QStringLiteral("Y") : QStringLiteral("N"));
540     root.setAttribute(QStringLiteral("linearRegressionVisible"), m_linearRegressionVisible ? QStringLiteral("Y") : QStringLiteral("N"));
541     root.setAttribute(QStringLiteral("paretoVisible"), m_paretoVisible ? QStringLiteral("Y") : QStringLiteral("N"));
542     root.setAttribute(QStringLiteral("legendVisible"), m_legendVisible ? QStringLiteral("Y") : QStringLiteral("N"));
543     root.setAttribute(QStringLiteral("zeroVisible"), m_zeroVisible ? QStringLiteral("Y") : QStringLiteral("N"));
544     root.setAttribute(QStringLiteral("decimalsVisible"), m_decimalsVisible ? QStringLiteral("Y") : QStringLiteral("N"));
545     QMapIterator<QString, QColor> i(m_mapTitleColor);
546     while (i.hasNext()) {
547         i.next();
548         QDomElement color = doc.createElement(QStringLiteral("color"));
549         root.appendChild(color);
550         color.setAttribute(QStringLiteral("key"), i.key());
551         color.setAttribute(QStringLiteral("value"), i.value().name());
552     }
553 
554     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
555     root.setAttribute(QStringLiteral("sortOrder"), SKGServices::intToString(horizontalHeader->sortIndicatorOrder()));
556     root.setAttribute(QStringLiteral("sortColumn"), SKGServices::intToString(horizontalHeader->sortIndicatorSection()));
557     root.setAttribute(QStringLiteral("graphicViewState"), ui.graphicView->getState());
558     root.setAttribute(QStringLiteral("web"), ui.kTextEdit->getState());
559     root.setAttribute(QStringLiteral("show"), ui.kShow->getState());
560 
561     if (ui.kTable->stickHorizontal()) {
562         root.setAttribute(QStringLiteral("stickH"), QStringLiteral("Y"));
563     }
564     if (ui.kTable->stickVertical()) {
565         root.setAttribute(QStringLiteral("stickV"), QStringLiteral("Y"));
566     }
567 
568     return doc.toString();
569 }
570 
setState(const QString & iState)571 void SKGTableWithGraph::setState(const QString& iState)
572 {
573     SKGTRACEINFUNC(10)
574     m_timer.stop();
575     m_timerRedraw.stop();
576 
577     QDomDocument doc(QStringLiteral("SKGML"));
578     doc.setContent(iState);
579     QDomElement root = doc.documentElement();
580 
581     m_mapTitleColor.clear();
582 
583     QDomNodeList colors = root.elementsByTagName(QStringLiteral("color"));
584     int nb = colors.count();
585     for (int i = 0; i < nb; ++i) {
586         QDomElement c = colors.at(i).toElement();
587         m_mapTitleColor[c.attribute(QStringLiteral("key"))] = QColor(c.attribute(QStringLiteral("value")));
588     }
589 
590     QString splitterStateString = root.attribute(QStringLiteral("splitterState"));
591     if (!splitterStateString.isEmpty()) {
592         ui.splitter->restoreState(QByteArray::fromHex(splitterStateString.toLatin1()));
593     }
594     QString graphModeString = root.attribute(QStringLiteral("graphMode"));
595     QString allPositiveString = root.attribute(QStringLiteral("allPositive"));
596     QString limitVisibleString = root.attribute(QStringLiteral("limitVisible"));
597     QString averageVisibleString = root.attribute(QStringLiteral("averageVisible"));
598     QString legendVisibleString = root.attribute(QStringLiteral("legendVisible"));
599     QString zeroVisibleString = root.attribute(QStringLiteral("zeroVisible"));
600     QString decimalsVisibleString = root.attribute(QStringLiteral("decimalsVisible"));
601     QString linearRegressionVisibleString = root.attribute(QStringLiteral("linearRegressionVisible"));
602     QString paretorVisibleString = root.attribute(QStringLiteral("paretoVisible"));
603     QString sortOrderString = root.attribute(QStringLiteral("sortOrder"));
604     QString sortColumnString = root.attribute(QStringLiteral("sortColumn"));
605     QString graphicViewStateString = root.attribute(QStringLiteral("graphicViewState"));
606     QString webString = root.attribute(QStringLiteral("web"));
607     QString showString = root.attribute(QStringLiteral("show"));
608     ui.kTable->setStickHorizontal(root.attribute(QStringLiteral("stickH")) == QStringLiteral("Y"));
609     ui.kTable->setStickVertical(root.attribute(QStringLiteral("stickV")) == QStringLiteral("Y"));
610 
611     // Default value in case of reset
612     if (graphModeString.isEmpty()) {
613         graphModeString = SKGServices::intToString(LINE);
614     }
615     if (allPositiveString.isEmpty()) {
616         allPositiveString = 'N';
617     }
618     if (limitVisibleString.isEmpty()) {
619         limitVisibleString = 'Y';
620     }
621     if (averageVisibleString.isEmpty()) {
622         averageVisibleString = 'Y';
623     }
624     if (legendVisibleString.isEmpty()) {
625         legendVisibleString = 'N';
626     }
627     if (linearRegressionVisibleString.isEmpty()) {
628         linearRegressionVisibleString = 'Y';
629     }
630     if (paretorVisibleString.isEmpty()) {
631         paretorVisibleString = 'N';
632     }
633     if (sortOrderString.isEmpty()) {
634         sortOrderString = '0';
635     }
636     if (sortColumnString.isEmpty()) {
637         sortColumnString = '0';
638     }
639 
640     // Set
641     setGraphType(SKGTableWithGraph::HISTOGRAM);
642     if (m_displayMode != nullptr) {
643         m_displayMode->setCurrentIndex(1);
644     }
645     setGraphType(static_cast<SKGTableWithGraph::GraphType>(SKGServices::stringToInt(graphModeString)));
646     m_allPositiveMenu->setChecked(allPositiveString == QStringLiteral("Y"));
647     ui.kFilterEdit->setText(root.attribute(QStringLiteral("filter")));
648     m_limitVisible = (limitVisibleString == QStringLiteral("Y"));
649     if (m_actShowLimits != nullptr) {
650         m_actShowLimits->setChecked(m_limitVisible);
651     }
652     m_averageVisible = (averageVisibleString == QStringLiteral("Y"));
653     if (m_actShowAverage != nullptr) {
654         m_actShowAverage->setChecked(m_averageVisible);
655     }
656     m_legendVisible = (legendVisibleString == QStringLiteral("Y"));
657     if (m_actShowLegend != nullptr) {
658         m_actShowLegend->setChecked(m_legendVisible);
659     }
660     m_zeroVisible = (zeroVisibleString != QStringLiteral("N"));
661     if (m_actShowZero != nullptr) {
662         m_actShowZero->setChecked(m_zeroVisible);
663     }
664     m_decimalsVisible = (decimalsVisibleString != QStringLiteral("N"));
665     if (m_actShowDecimal != nullptr) {
666         m_actShowDecimal->setChecked(m_decimalsVisible);
667     }
668     m_linearRegressionVisible = (linearRegressionVisibleString == QStringLiteral("Y"));
669     if (m_actShowLinearRegression != nullptr) {
670         m_actShowLinearRegression->setChecked(m_linearRegressionVisible);
671     }
672     m_paretoVisible = (paretorVisibleString == QStringLiteral("Y"));
673     if (m_actShowPareto != nullptr) {
674         m_actShowPareto->setChecked(m_paretoVisible);
675     }
676 
677     ui.kTable->setColumnCount(SKGServices::stringToInt(sortColumnString) + 1);
678     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
679     if (horizontalHeader != nullptr) {
680         bool previous = horizontalHeader->blockSignals(true);
681         horizontalHeader->setSortIndicator(SKGServices::stringToInt(sortColumnString), static_cast<Qt::SortOrder>(SKGServices::stringToInt(sortOrderString)));
682         horizontalHeader->blockSignals(previous);
683     }
684     ui.graphicView->setState(graphicViewStateString);
685     ui.kTextEdit->setState(webString);
686     if (!showString.isEmpty()) {
687         ui.kShow->setState(showString);
688     }
689 }
690 
setFilterVisibility(bool iVisibility) const691 void SKGTableWithGraph::setFilterVisibility(bool iVisibility) const
692 {
693     ui.kToolbar->setVisible(iVisibility);
694 }
695 
onFilterModified()696 void SKGTableWithGraph::onFilterModified()
697 {
698     m_timerRedraw.stop();
699     m_timer.start(300);
700 }
701 
onDisplayModeChanged()702 void SKGTableWithGraph::onDisplayModeChanged()
703 {
704     QStringList mode = SKGServices::splitCSVLine(ui.kShow->getState());
705 
706     // Hide all
707     if (m_scene != nullptr) {
708         m_scene->clear();
709         delete m_scene;
710     }
711     m_scene = new SKGGraphicsScene();
712     ui.graphicView->setScene(m_scene);
713     ui.graphicView->hide();
714     ui.kTextEdit->hide();
715     bool p = ui.kTable->blockSignals(true);
716     ui.kTable->hide();
717     ui.kTable->blockSignals(p);
718     m_graphVisible = false;
719     m_tableVisible = false;
720     m_textVisible = false;
721     m_mapItemGraphic.clear();
722 
723     // Show needed widget
724     if (mode.contains(QStringLiteral("table"))) {
725         ui.kTable->show();
726         m_tableVisible = true;
727     }
728     if (mode.contains(QStringLiteral("graph"))) {
729         ui.graphicView->show();
730         m_graphVisible = true;
731         redrawGraphDelayed();
732     }
733     if (mode.contains(QStringLiteral("text"))) {
734         QTimer::singleShot(100, Qt::CoarseTimer, ui.kTextEdit, &SKGWebView::show);
735         m_textVisible = true;
736         redrawText();
737     }
738 }
739 
setData(const SKGStringListList & iData,const SKGServices::SKGUnitInfo & iPrimaryUnit,const SKGServices::SKGUnitInfo & iSecondaryUnit,SKGTableWithGraph::DisplayAdditionalFlag iAdditionalInformation,int iNbVirtualColumn)740 void SKGTableWithGraph::setData(const SKGStringListList& iData,
741                                 const SKGServices::SKGUnitInfo& iPrimaryUnit,
742                                 const SKGServices::SKGUnitInfo& iSecondaryUnit,
743                                 SKGTableWithGraph::DisplayAdditionalFlag iAdditionalInformation,
744                                 int iNbVirtualColumn)
745 {
746     SKGTRACEINFUNC(10)
747     m_data = iData;
748     m_primaryUnit = iPrimaryUnit;
749     m_secondaryUnit = iSecondaryUnit;
750     m_additionalInformation = iAdditionalInformation;
751     m_nbVirtualColumns = iNbVirtualColumn;
752 
753     onFilterModified();
754 }
755 
getAdditionalDisplayMode() const756 SKGTableWithGraph::DisplayAdditionalFlag SKGTableWithGraph::getAdditionalDisplayMode() const
757 {
758     return m_additionalInformation;
759 }
760 
getSumItems(const QString & iString) const761 QStringList SKGTableWithGraph::getSumItems(const QString& iString) const
762 {
763     QStringList output;
764     QString current = iString;
765     int index = -1;
766     do {
767         output.insert(0, current);
768         index = current.lastIndexOf(OBJECTSEPARATOR);
769         if (index != -1) {
770             current = current.left(index);
771         }
772     } while (index != -1);
773     return output;
774 }
775 
addSums(SKGStringListList & ioTable,int & iNblines)776 void SKGTableWithGraph::addSums(SKGStringListList& ioTable, int& iNblines)
777 {
778     SKGTRACEINFUNC(10)
779     int nbCols = -1;
780     if (!ioTable.isEmpty()) {
781         nbCols = ioTable.at(0).count();
782     }
783 
784     // Create a list of sums lines associated to the index where to add them
785     QMap<double, QStringList> sums;
786     for (int i = 1; i < nbCols; ++i) {
787         QStringList previousHeaderSum;
788         QList<double> sum;
789         QList<int> nbElem;
790 
791         for (int j = 1; j < iNblines; ++j) {
792             double indextoadd = j + 1 - 0.01;
793             QStringList currentHeaderSum = getSumItems(ioTable.at(j).at(0));
794             if (previousHeaderSum.isEmpty()) {
795                 previousHeaderSum = currentHeaderSum;
796             }
797 
798             int nb = qMax(currentHeaderSum.count(), previousHeaderSum.count());
799             for (int k = 0; k < nb; ++k) {
800                 QString chString = currentHeaderSum.value(k);
801                 QString phString = previousHeaderSum.value(k);
802                 if (chString != phString) {
803                     // Add this sum
804                     if (nbElem.value(k) > 1) {
805                         QStringList thisSum;
806                         if (i == 1) {
807                             thisSum.push_back(phString);
808                         } else {
809                             thisSum = sums[indextoadd];
810                         }
811                         thisSum.push_back(SKGServices::doubleToString(sum.value(k)));
812 
813                         sums[indextoadd] = thisSum;
814 
815                         indextoadd -= 0.01;
816                     }
817 
818                     // Reset sum
819                     if (k < sum.count()) {
820                         sum[k] = 0;
821                     } else {
822                         sum.push_back(0);
823                     }
824                     if (k < nbElem.count()) {
825                         nbElem[k] = 0;
826                     } else {
827                         nbElem.push_back(0);
828                     }
829                 }
830                 if (j < iNblines - 1) {
831                     sum = sum.mid(0, previousHeaderSum.count());
832                     nbElem = nbElem.mid(0, previousHeaderSum.count());
833 
834                     QString valstring = ioTable.at(j).at(i);
835                     double v = 0.0;
836                     if (!valstring.isEmpty()) {
837                         v = SKGServices::stringToDouble(valstring);
838                     }
839                     if (k < sum.count()) {
840                         sum[k] += v;
841                     } else {
842                         sum.push_back(v);
843                     }
844                     if (k < nbElem.count()) {
845                         nbElem[k] = nbElem[k] + 1;
846                     } else {
847                         nbElem.push_back(1);
848                     }
849                 }
850             }
851 
852             previousHeaderSum = currentHeaderSum;
853         }
854     }
855 
856     // Add all sums in table
857     QList<double> keys = sums.keys();
858     std::sort(keys.begin(), keys.end());
859     int nbkey = keys.count();
860     for (int i = nbkey - 1; i >= 0; --i) {
861         double key = keys.at(i);
862         ioTable.insert(key, sums[key]);
863         m_sumRows.insert(key, true);
864         ++iNblines;
865     }
866 }
867 
refresh()868 void SKGTableWithGraph::refresh()
869 {
870     SKGTRACEINFUNC(10)
871     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
872 
873     // Set parameters for static method
874     QHeaderView* horizontalHeader = ui.kTable->horizontalHeader();
875     m_sortOrder = horizontalHeader->sortIndicatorOrder();
876     m_sortColumn = horizontalHeader->sortIndicatorSection();
877 
878     SKGStringListList groupedTable = m_data;
879     int nbCols = -1;
880     if (!groupedTable.isEmpty()) {
881         nbCols = groupedTable.at(0).count();
882     }
883     int nbRealCols = nbCols;
884 
885     // Create filtered table
886     {
887         // Build list of criterias
888         SKGServices::SKGSearchCriteriaList criterias = SKGServices::stringToSearchCriterias(ui.kFilterEdit->text());
889 
890         // Check all lines
891         int nblists = criterias.count();
892         if ((nblists != 0) && (nbCols != 0)) {
893             for (int i = groupedTable.count() - 1; i >= 1; --i) {  // The first line is not filtered because it is the title
894                 QStringList line = groupedTable.at(i);
895 
896                 // Get title of the line
897                 const QString& val = line.at(0);
898 
899                 // Filtered
900                 bool ok = false;
901                 for (int l = 0; l < nblists; ++l) {
902                     QStringList words = criterias[l].words;
903                     QChar mode = criterias[l].mode;
904                     int nbwords = words.count();
905 
906                     bool validateAllWords = true;
907                     for (int w = 0; validateAllWords && w < nbwords; ++w) {
908                         validateAllWords = val.contains(words.at(w), Qt::CaseInsensitive);
909                     }
910                     if (mode == '+') {
911                         ok |= validateAllWords;
912                     } else if (mode == '-' && validateAllWords) {
913                         ok = false;
914                     }
915                 }
916 
917                 if (!ok) {
918                     groupedTable.removeAt(i);
919                 }
920             }
921         }
922     }
923 
924     // Initialise sumRows
925     int nblines = groupedTable.count();
926     m_sumRows.clear();
927     for (int j = 0; j < nblines; ++j) {
928         m_sumRows.push_back(false);
929     }
930 
931     // Compute sums line
932     if ((m_additionalInformation & SUM) != 0u) {
933         QStringList sums;
934         sums.reserve(nbCols + 1);
935         sums.push_back(QString());
936         for (int i = 1; i < nbCols; ++i) {
937             double sum = 0;
938             for (int j = 1; j < nblines; ++j) {
939                 QString valstring = groupedTable.at(j).at(i);
940                 if (!valstring.isEmpty()) {
941                     sum += SKGServices::stringToDouble(valstring);
942                 }
943             }
944             sums.push_back(SKGServices::doubleToString(sum));
945         }
946         groupedTable.push_back(sums);
947         m_sumRows.push_back(true);
948         ++nblines;
949     }
950 
951     // Compute sub sums lines
952     if ((m_additionalInformation & SUM) != 0u && m_sortColumn == 0 && m_sortOrder == Qt::AscendingOrder) {
953         addSums(groupedTable, nblines);
954     }
955 
956     // Compute sum and average column
957     m_indexSum = -1;
958     m_indexAverage = -1;
959     m_indexMin = -1;
960     m_indexLinearRegression = -1;
961     if (nbCols > 2 && ((m_additionalInformation & SUM) != 0u || (m_averageVisible && (m_additionalInformation & AVERAGE) != 0u) || (m_limitVisible && (m_additionalInformation & LIMITS) != 0u))) {
962         SKGTRACEINFUNC(10)
963         // Add title
964         QStringList newLine = groupedTable.at(0);
965         if ((m_additionalInformation & SUM) != 0u) {
966             m_indexSum = newLine.count();
967             newLine.push_back(i18nc("Noun, the numerical sum of a list of values", "Sum"));
968         }
969         if (m_averageVisible && (m_additionalInformation & AVERAGE) != 0u) {
970             m_indexAverage = newLine.count();
971             newLine.push_back(i18nc("Noun, the numerical average of a list of values", "Average"));
972         }
973         if (m_limitVisible && (m_additionalInformation & LIMITS) != 0u) {
974             m_indexMin = newLine.count();
975             newLine.push_back(i18nc("Noun, the minimum value of a list of values", "Min"));
976             newLine.push_back(i18nc("Noun, the maximum value of a list of values", "Max"));
977         }
978 
979         if (m_linearRegressionVisible) {
980             m_indexLinearRegression = newLine.count();
981             newLine.push_back(i18nc("Noun", "Tendency line"));
982         }
983 
984         groupedTable.replace(0, newLine);
985 
986         for (int i = 1; i < nblines; ++i) {
987             QStringList newLine2 = groupedTable.at(i);
988             double sumy = 0;
989             double sumx = 0;
990             double sumx2 = 0;
991             double sumxy = 0;
992             double min = 10e20;
993             double max = -10e20;
994             int nbVals = 0;
995             for (int j = 1; j < nbCols - m_nbVirtualColumns; ++j) {
996                 const QString& valstring = newLine2.at(j);
997                 if (!valstring.isEmpty()) {
998                     double v = SKGServices::stringToDouble(valstring);
999                     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 
onChangeColor()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 
resetColors()1250 void SKGTableWithGraph::resetColors()
1251 {
1252     m_mapTitleColor.clear();
1253     refresh();
1254 }
1255 
onSelectionChanged()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 
onDoubleClickGraph()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 
onDoubleClick(int row,int column)1331 void SKGTableWithGraph::onDoubleClick(int row, int column)
1332 {
1333     Q_EMIT cellDoubleClicked(row, column);
1334 }
1335 
onLinkClicked(const QUrl & url)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 
onSelectionChangedInGraph()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 
redrawGraphDelayed()1366 void SKGTableWithGraph::redrawGraphDelayed()
1367 {
1368     m_timerRedraw.start(300);
1369 }
1370 
redrawText()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 
redrawGraph()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 
1697         // Initialise pens
1698         QPen axisPen = QPen(Qt::SolidLine);
1699         axisPen.setColor(m_axisColor);
1700         axisPen.setWidthF(margin / 30.0);
1701         axisPen.setJoinStyle(Qt::RoundJoin);
1702 
1703         QPen gridPen = QPen(Qt::SolidLine);
1704         gridPen.setColor(m_gridColor);
1705         gridPen.setWidthF(margin / 100.0);
1706 
1707         QPen dotPen = QPen(Qt::SolidLine);  // WARNING: Qt::DotLine is very bad for performances
1708         dotPen.setWidthF(margin / 50.0);
1709 
1710         QPen outlinePen(m_outlineColor);
1711         outlinePen.setWidthF(margin / 500.0);
1712 
1713         // Set rect
1714         int nbColInMode2 = (nbColumns > 2 ? sqrt(nbColumns - 1) + .5 : 1);
1715         m_scene->setItemIndexMethod(QGraphicsScene::NoIndex);
1716         SKGTRACEL(10) << "Items:" << nbRealRows << "x" << (nbColumns - 1) << "=" << nbRealRows*(nbColumns - 1) << SKGENDL;
1717         SKGTRACEL(10) << "nbColInMode2=" << nbColInMode2 << SKGENDL;
1718 
1719         // Compute treemap
1720         QMap<QString, SKGTreeMap> treemap;
1721         if (mode == TREEMAP) {
1722             SKGTreeMap treemapobj(QString(), 0, 0, 0, BOX_SIZE, BOX_SIZE);
1723             for (int j = 1; j < nbColumns; ++j) {
1724                 SKGTreeMap treemapobj2;
1725                 for (int xx = 0; xx < nbRows; ++xx) {
1726                     auto* colorButton = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(xx, 0));
1727                     if (colorButton != nullptr) {
1728                         // Get cell value
1729                         QTableWidgetItem* tableItem = ui.kTable->item(xx, j);
1730                         if (tableItem != nullptr) {
1731                             QVariant valQ = tableItem->data(DATA_VALUE);
1732                             if (valQ.type() == QVariant::Double) {
1733                                 double val = qAbs(valQ.toDouble());
1734                                 QString id = SKGServices::intToString(xx) % QStringLiteral("-") % SKGServices::intToString(j);
1735                                 treemapobj2.addChild(SKGTreeMap(id, val));
1736                             }
1737                         }
1738                     }
1739                 }
1740                 treemapobj.addChild(treemapobj2);
1741             }
1742 
1743             treemapobj.compute();
1744             treemap = treemapobj.getAllTilesById();
1745         }
1746 
1747         // Redraw scene
1748         double x0 = 0;
1749         double x1 = 0;
1750         double ymin = (showOrigin || mode == STACK || mode == STACKCOLUMNS ? 0 : 9999999);
1751         double ymax = (showOrigin || mode == STACK || mode == STACKCOLUMNS ? 0 : -9999999);
1752         int posx = 0;
1753         int nbRowsDrawed = 0;
1754         QMap<int, double> previousStackedPlus;
1755         QMap<int, double> previousStackedMoins;
1756         QMap<double, double> paretoPoints;
1757         for (int xx = 0; xx < nbRows; ++xx) {
1758             auto* colorButton = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(xx, 0));
1759             if (colorButton != nullptr) {
1760                 QString xname = colorButton->text();
1761                 QColor initialColor = colorButton->color();
1762                 double yPlus = 0;
1763                 double yMoins = 0;
1764                 int jprevious = -1;
1765 
1766                 ++nbRowsDrawed;
1767                 for (int j = 1; j < nbColumns; ++j) {
1768                     // Get cell value
1769                     QTableWidgetItem* tableItem = ui.kTable->item(xx, j);
1770                     if (tableItem != nullptr) {
1771                         QVariant valQ = tableItem->data(DATA_VALUE);
1772                         if (valQ.type() == QVariant::Double) {
1773                             double val = valQ.toDouble();
1774                             QString valstring = tableItem->text();
1775 
1776                             if (inPositive) {
1777                                 val = qAbs(val);
1778                             }
1779 
1780                             int color_h;
1781                             int color_s;
1782                             int color_v;
1783                             initialColor.getHsv(&color_h, &color_s, &color_v);
1784                             color_s = color_s - color_s * 155 / 255 * (nbColumns - 1 - j) / nbColumns;
1785                             color_v = color_v - color_v * 155 / 255 * (nbColumns - 1 - j) / nbColumns;
1786                             if (j >= nbRealColumns) {
1787                                 // Yellow
1788                                 color_h = 60;
1789                                 color_s = 255;
1790                                 color_v = 255;
1791                             }
1792 
1793                             QColor color;
1794                             if (posx == crow && j == ccolumn) {
1795                                 color = QApplication::palette().color(QPalette::Highlight);
1796                             } else {
1797                                 color = QColor::fromHsv(color_h, color_s, color_v);
1798                             }
1799 
1800                             color.setAlpha(ALPHA);
1801                             QBrush brush(color);
1802 
1803                             QGraphicsItem* graphItem = nullptr;
1804                             if (mode == STACK) {
1805                                 // STACK
1806                                 if (val >= 0) {
1807                                     graphItem = m_scene->addRect(widthItem * posx, -yPlus - qAbs(val), widthItem, qAbs(val), outlinePen, brush);
1808                                     yPlus += qAbs(val);
1809                                     if (yPlus > ymax) {
1810                                         ymax = yPlus;
1811                                     }
1812                                 } else {
1813                                     graphItem = m_scene->addRect(widthItem * posx, -yMoins, widthItem, -val, outlinePen, brush);
1814                                     yMoins += val;
1815                                     if (yMoins < ymin) {
1816                                         ymin = yMoins;
1817                                     }
1818                                 }
1819                                 if (graphItem != nullptr) {
1820                                     graphItem->setZValue(5 + nbColumns - j);
1821                                 }
1822                             }
1823                             if (mode == STACKAREA) {
1824                                 // STACKAREA
1825                                 // Empty cells are ignored
1826                                 if (j == 1) {
1827                                     x0 = widthItem * (j - 1) * (nbRealRows + 1);
1828                                 }
1829                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1830                                     x1 = widthItem * (j - 1) * (nbRealRows + 1);
1831                                 }
1832 
1833                                 if (!valstring.isEmpty()) {
1834                                     if (jprevious == -1) {
1835                                         jprevious = j;
1836                                     }
1837                                     QTableWidgetItem* tableItem2 = ui.kTable->item(xx, jprevious);
1838                                     if (tableItem2 != nullptr) {
1839                                         QString val2string = tableItem->text();
1840                                         if (!val2string.isEmpty()) {
1841                                             double val2 = tableItem2->data(DATA_VALUE).toDouble();
1842                                             if (j == jprevious) {
1843                                                 val2 = 0;
1844                                             }
1845                                             if (inPositive) {
1846                                                 val2 = qAbs(val2);
1847                                             }
1848 
1849                                             if (val > EPSILON || (qAbs(val) <= EPSILON && val2 >= EPSILON)) {
1850                                                 double xp = widthItem * (jprevious - 1) * (nbRealRows + 1) - (jprevious == j ? widthItem / 20.0 : 0);
1851                                                 double xn = widthItem * (j - 1) * (nbRealRows + 1);
1852                                                 double yp = (previousStackedPlus.contains(jprevious) ? previousStackedPlus[jprevious] : -val2);
1853                                                 double yn = previousStackedPlus[j] - val;
1854 
1855                                                 QPolygonF polygon;
1856                                                 polygon << QPointF(xp, yp + val2) << QPointF(xp, yp)
1857                                                         << QPointF(xn, yn) << QPointF(xn, previousStackedPlus[j]);
1858                                                 graphItem = m_scene->addPolygon(polygon, outlinePen, brush);
1859                                                 previousStackedPlus[j] = yn;
1860                                                 if (-yn > ymax) {
1861                                                     ymax = -yn;
1862                                                 }
1863                                             } else {
1864                                                 double xp = widthItem * (jprevious - 1) * (nbRealRows + 1) - (jprevious == j ? widthItem / 20.0 : 0);
1865                                                 double xn = widthItem * (j - 1) * (nbRealRows + 1);
1866                                                 double yp = (previousStackedMoins.contains(jprevious) ? previousStackedMoins[jprevious] : -val2);
1867                                                 double yn = previousStackedMoins[j] - val;
1868 
1869                                                 QPolygonF polygon;
1870                                                 polygon << QPointF(xp, yp + val2) << QPointF(xp, yp)
1871                                                         << QPointF(xn, yn) << QPointF(xn, previousStackedMoins[j]);
1872                                                 graphItem = m_scene->addPolygon(polygon, outlinePen, brush);
1873                                                 previousStackedMoins[j] = yn;
1874                                                 if (-yn < ymin) {
1875                                                     ymin = -yn;
1876                                                 }
1877                                             }
1878                                             jprevious = j;
1879                                         }
1880                                     }
1881                                 }
1882                             }
1883                             if (mode == STACKCOLUMNS) {
1884                                 // STACKCOLUMNS
1885                                 if (val >= 0) {
1886                                     graphItem = m_scene->addRect(widthItem * (j - 1) * (nbRealRows + 1), -previousStackedPlus[j] - qAbs(val), widthItem * (nbRealRows + 1), qAbs(val), outlinePen, brush);
1887                                     previousStackedPlus[j] += qAbs(val);
1888                                     if (previousStackedPlus[j] > ymax) {
1889                                         ymax = previousStackedPlus[j];
1890                                     }
1891                                 } else {
1892                                     graphItem = m_scene->addRect(widthItem * (j - 1) * (nbRealRows + 1), -previousStackedMoins[j], widthItem * (nbRealRows + 1), -val, outlinePen, brush);
1893                                     previousStackedMoins[j] += val;
1894                                     if (previousStackedMoins[j] < ymin) {
1895                                         ymin = previousStackedMoins[j];
1896                                     }
1897                                 }
1898                                 if (graphItem != nullptr) {
1899                                     graphItem->setZValue(5 + nbRows - posx);
1900                                 }
1901                             } else if (mode == HISTOGRAM) {
1902                                 // HISTOGRAM
1903                                 if (j == 1) {
1904                                     x0 = widthItem * ((j - 1) * (nbRealRows + 1) + posx) + widthItem;
1905                                 }
1906                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1907                                     x1 = widthItem * ((j - 1) * (nbRealRows + 1) + posx) + widthItem;
1908                                 }
1909                                 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);
1910                                 if (val > ymax) {
1911                                     ymax = val;
1912                                 }
1913                                 if (val < ymin) {
1914                                     ymin = val;
1915                                 }
1916                             } else if (mode == POINT) {
1917                                 // POINTS
1918                                 double xmin = widthItem * (j - 1) * (nbRealRows + 1) - radius;
1919                                 if (j == 1) {
1920                                     x0 = xmin + radius;
1921                                 }
1922                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1923                                     x1 = xmin + radius;
1924                                 }
1925                                 graphItem = drawPoint(xmin, -val, radius, posx, brush);
1926                                 if (val > ymax) {
1927                                     ymax = val;
1928                                 }
1929                                 if (val < ymin) {
1930                                     ymin = val;
1931                                 }
1932                             } else if (mode == LINE) {
1933                                 // LINES
1934                                 // Empty cells are ignored
1935                                 if (j == 1) {
1936                                     x0 = widthItem * (j - 1) * (nbRealRows + 1);
1937                                 }
1938                                 if (j == nbColumns - m_nbVirtualColumns - 1) {
1939                                     x1 = widthItem * (j - 1) * (nbRealRows + 1);
1940                                 }
1941 
1942                                 if (!valstring.isEmpty()) {
1943                                     if (jprevious == -1) {
1944                                         jprevious = j;
1945                                     }
1946 
1947                                     // Create pen
1948                                     color.setAlpha(255);
1949                                     QPen pen = QPen(color);
1950                                     pen.setWidthF(radius);
1951                                     pen.setCapStyle(Qt::RoundCap);
1952                                     pen.setJoinStyle(Qt::RoundJoin);
1953 
1954                                     QTableWidgetItem* tableItem2 = ui.kTable->item(xx, jprevious);
1955                                     if (tableItem2 != nullptr) {
1956                                         QString val2string = tableItem->text();
1957                                         if (!val2string.isEmpty()) {
1958                                             double val2 = tableItem2->data(DATA_VALUE).toDouble();
1959                                             if (inPositive) {
1960                                                 val2 = qAbs(val2);
1961                                             }
1962 
1963                                             QGraphicsLineItem* line = m_scene->addLine(widthItem * (jprevious - 1) * (nbRealRows + 1) - (jprevious == j ? widthItem / 20.0 : 0), -val2, widthItem * (j - 1) * (nbRealRows + 1), -val, pen);
1964                                             line->setZValue(10);
1965                                             if (isShadowVisible()) {
1966                                                 auto line_shadow = new QGraphicsDropShadowEffect();
1967                                                 line_shadow->setOffset(3);
1968                                                 line->setGraphicsEffect(line_shadow);
1969                                             }
1970                                             graphItem = drawPoint(widthItem * (j - 1) * (nbRealRows + 1) - radius * 0.7, -val, radius * 0.7, posx % 5, brush);
1971                                             if (isShadowVisible()) {
1972                                                 auto line_shadow = new QGraphicsDropShadowEffect();
1973                                                 line_shadow->setOffset(3);
1974                                                 line->setGraphicsEffect(line_shadow);
1975                                             }
1976                                             graphItem->setZValue(20);
1977                                             if (val > ymax) {
1978                                                 ymax = val;
1979                                             }
1980                                             if (val < ymin) {
1981                                                 ymin = val;
1982                                             }
1983                                             jprevious = j;
1984                                         }
1985                                     }
1986                                 }
1987                             } else if (mode == BUBBLE) {
1988                                 // BUBBLE
1989                                 ymin = 0;
1990                                 ymax = ystep * nbRealRows;
1991 
1992                                 radius =  sqrt(qAbs(val) / maxLimit) * qMin(ystep, jstep * xstep);
1993                                 double xmin = widthItem * (j - 1) * (nbRealRows + 1) - radius;
1994 
1995                                 graphItem = m_scene->addEllipse(xmin, -ystep * posx - radius, 2 * radius, 2 * radius, outlinePen, brush);
1996                                 if (graphItem != nullptr) {
1997                                     graphItem->setZValue(5 + 5 * (maxLimit - qAbs(val)) / maxLimit);
1998                                 }
1999                             }  else if (mode == PIE || mode == CONCENTRICPIE) {
2000                                 // PIE
2001                                 val = qAbs(val);
2002 
2003                                 // Compute absolute sum of the column
2004                                 double previousSum = 0;
2005                                 for (int x2 = 0; x2 < nbRows; ++x2) {
2006                                     if (!m_sumRows[x2 + 1]) {
2007                                         QTableWidgetItem* tabItem = ui.kTable->item(x2, j);
2008                                         if (tabItem != nullptr) {
2009                                             double absVal = qAbs(tabItem->data(DATA_VALUE).toDouble());
2010                                             if (x2 < xx) {
2011                                                 previousSum += absVal;
2012                                             }
2013                                         }
2014                                     }
2015                                 }
2016 
2017                                 if (maxLimit != 0.0) {
2018                                     int nbvals = xname.split(OBJECTSEPARATOR).count();
2019                                     double step = 0;
2020                                     double p = 0;
2021                                     if (mode == CONCENTRICPIE) {
2022                                         step = 100.0 / static_cast<double>(nbLevel + 1);
2023                                         p = step * (nbLevel + 1 - nbvals);
2024                                     }
2025 
2026                                     QPainterPath path;
2027                                     path.moveTo(BOX_SIZE * ((j - 1) % nbColInMode2) + BOX_SIZE / 2.0, BOX_SIZE * floor((j - 1) / nbColInMode2) + BOX_SIZE / 2.0);
2028                                     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);
2029                                     path.closeSubpath();
2030                                     if (mode == CONCENTRICPIE && nbvals <= nbLevel + 1 && nbvals > 1) {
2031                                         p = step * (nbLevel + 1 - nbvals + 1);
2032                                         QPainterPath path2;
2033                                         path2.addEllipse(BOX_SIZE * ((j - 1) % nbColInMode2) + BOX_MARGIN + p / 2.0,
2034                                                          BOX_SIZE * floor((j - 1) / nbColInMode2) + BOX_MARGIN + p / 2.0,
2035                                                          BOX_SIZE - 2 * BOX_MARGIN - p,
2036                                                          BOX_SIZE - 2 * BOX_MARGIN - p);
2037                                         path -= path2;
2038                                     }
2039                                     graphItem = m_scene->addPath(path, outlinePen, brush);
2040                                 }
2041                             }  else if (mode == TREEMAP) {
2042                                 // TREEMAP
2043                                 QString id = SKGServices::intToString(xx) % QStringLiteral("-") % SKGServices::intToString(j);
2044                                 SKGTreeMap tm = treemap[id];
2045                                 graphItem = m_scene->addRect(tm.getX(), tm.getY(), tm.getW(), tm.getH(), outlinePen, brush);
2046                             }
2047 
2048                             // Compute pareto points
2049                             if (mode == POINT || mode == LINE) {
2050                                 paretoPoints[widthItem * (j - 1) * (nbRealRows + 1)] = val;
2051                             } else if (mode == HISTOGRAM) {
2052                                 paretoPoints[widthItem * ((j - 1) * (nbRealRows + 1) + posx) + widthItem] = val;
2053                             }
2054 
2055                             if (graphItem != nullptr) {
2056                                 if (graphItem->zValue() == 0) {
2057                                     graphItem->setZValue(5);
2058                                 }
2059                                 graphItem->setToolTip(tableItem->toolTip());
2060                                 bool isSelect = (isSelectable() && ((tableItem->flags() & Qt::ItemIsEnabled) != 0u));
2061                                 graphItem->setFlag(QGraphicsItem::ItemIsSelectable, isSelect);
2062                                 if (isSelect) {
2063                                     graphItem->setCursor(Qt::PointingHandCursor);
2064                                 }
2065                                 graphItem->setData(1, xx);
2066                                 graphItem->setData(2, j);
2067                                 graphItem->setData(DATA_COLOR_H, color_h);
2068                                 graphItem->setData(DATA_COLOR_S, color_s);
2069                                 graphItem->setData(DATA_COLOR_V, color_v);
2070                                 graphItem->setData(DATA_Z_VALUE, graphItem->zValue());
2071 
2072                                 m_mapItemGraphic[tableItem] = graphItem;
2073                             }
2074                         }
2075                     } else {
2076                         SKGTRACE << "WARNING: cell " << posx << "," << j << " null" << SKGENDL;
2077                     }
2078                 }
2079 
2080                 ++posx;
2081             }
2082         }
2083 
2084         // Draw axis
2085         double scaleText = 0.0;
2086         auto nbRowInMode2 = static_cast<int>(((nbColumns - 1) / nbColInMode2));
2087         if (nbRealRows != 0) {
2088             if (mode == TREEMAP) {
2089                 // TREEMAP
2090             } else if (mode == PIE || mode == CONCENTRICPIE) {
2091                 // PIE
2092                 if (nbRowInMode2 * nbColInMode2 < nbColumns - 1) {
2093                     ++nbRowInMode2;
2094                 }
2095                 for (int i = 0; i <= nbColInMode2; ++i) {
2096                     QGraphicsLineItem* item = m_scene->addLine(BOX_SIZE * i, 0, BOX_SIZE * i, BOX_SIZE * nbRowInMode2);
2097                     item->setPen(axisPen);
2098                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2099                 }
2100                 for (int i = 0; i <= nbRowInMode2; ++i) {
2101                     QGraphicsLineItem* item = m_scene->addLine(0, BOX_SIZE * i, BOX_SIZE * nbColInMode2, BOX_SIZE * i);
2102                     item->setPen(axisPen);
2103                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2104                 }
2105 
2106                 for (int j = 1; j < nbColumns; ++j) {
2107                     // Get column name
2108                     QString yname = ui.kTable->horizontalHeaderItem(j)->text();
2109 
2110                     QGraphicsTextItem* textItem = m_scene->addText(yname);
2111                     textItem->setDefaultTextColor(m_textColor);
2112                     textItem->setPos(BOX_SIZE * ((j - 1) % nbColInMode2) + 2, BOX_SIZE * floor((j - 1) / nbColInMode2) + 2);
2113                     textItem->setScale(.5);
2114                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2115                     textItem->setZValue(20);
2116                 }
2117             } else {
2118                 // STACK & HISTOGRAMM
2119                 QGraphicsLineItem* item;
2120 
2121                 // Compute scale text
2122                 if (mode != BUBBLE) {
2123                     QGraphicsTextItem* t = m_scene->addText(SKGServices::toCurrencyString(-qMax(qAbs(ymax), qAbs(ymin)), m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2124                     QRectF bRect = t->boundingRect();
2125                     scaleText = 0.8 * qMin(marginLeft / bRect.width(), qMin(marginLeft / bRect.height(), ystep / bRect.height()));
2126                     m_scene->removeItem(t);
2127                 } else {
2128                     maxLimit = ystep * nbRealRows;
2129                 }
2130 
2131                 if (ystep > 0) {
2132                     // Draw
2133                     int i = 1;
2134                     for (double posy = yorigin + ystep ; posy <= maxLimit ; posy = posy + ystep) {
2135                         item = m_scene->addLine(0, -posy, maxX, -posy);
2136                         item->setPen(gridPen);
2137                         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2138                         item->setZValue(1);
2139                         QGraphicsTextItem* textItem;
2140                         if (mode == BUBBLE) {
2141                             auto* cel = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2142                             ++i;
2143                             if (cel == nullptr) {
2144                                 cel = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2145                                 ++i;
2146                             }
2147                             textItem = m_scene->addText(cel != nullptr ? cel->text() : QString());
2148                         } else {
2149                             textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2150                         }
2151 
2152                         QRectF textRect = textItem->boundingRect();
2153                         if (scaleText == 0) {
2154                             scaleText = 0.8 * qMin(marginLeft / textRect.width(), qMin(marginLeft / textRect.height(), ystep / textRect.height()));
2155                         }
2156                         textItem->setDefaultTextColor(m_textColor);
2157                         textItem->setScale(scaleText);
2158                         textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2159                         textItem->setZValue(20);
2160                         double delta = scaleText * textRect.height() / 2.0;
2161 
2162                         textItem->setPos(-marginLeft, -posy - delta);
2163                     }
2164 
2165                     i = 0;
2166                     for (double posy = yorigin ; (posy == yorigin) || posy >= minLimit; posy = posy - ystep) {
2167                         item = m_scene->addLine(0, -posy, maxX, -posy);
2168                         item->setPen(gridPen);
2169                         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2170                         item->setZValue(1);
2171 
2172                         QGraphicsTextItem* textItem;
2173                         if (mode == BUBBLE) {
2174                             auto* cel = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2175                             textItem = m_scene->addText(cel != nullptr ? cel->text() : QString());
2176                         } else {
2177                             textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2178                         }
2179 
2180                         QRectF textRect = textItem->boundingRect();
2181                         if (scaleText == 0) {
2182                             scaleText = 0.8 * qMin(marginLeft / textRect.width(), qMin(marginLeft / textRect.height(), ystep / textRect.height()));
2183                         }
2184                         textItem->setDefaultTextColor(m_textColor);
2185                         textItem->setScale(scaleText);
2186                         textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2187                         textItem->setZValue(20);
2188                         double delta = scaleText * textRect.height() / 2.0;
2189 
2190                         textItem->setPos(-marginLeft, -posy - delta);
2191                     }
2192                 }
2193 
2194                 if (mode == HISTOGRAM || mode == POINT || mode == LINE || mode == BUBBLE || mode == STACKAREA || mode == STACKCOLUMNS) {
2195                     // Line y
2196                     for (int j = 1; j < nbColumns; j += jstep) {
2197                         QString yname = ui.kTable->horizontalHeaderItem(j)->text();
2198                         double posx2 = xstep + (j - 2) * xstep;
2199 
2200                         QGraphicsTextItem* textItem = m_scene->addText(yname);
2201 
2202                         QRectF textRect = textItem->boundingRect();
2203                         if (scaleText == 0) {
2204                             scaleText = 0.8 * qMin(marginLeft / textRect.width(), qMin(marginLeft / textRect.height(), ystep / textRect.height()));
2205                         }
2206                         textItem->setDefaultTextColor(m_textColor);
2207                         textItem->setScale(scaleText);
2208                         textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2209                         textItem->setZValue(20);
2210                         double delta = scaleText * textRect.height() / 2.0;
2211                         textItem->setRotation(90);
2212                         textItem->moveBy(textRect.height() *scaleText, 0);
2213 
2214                         textItem->setPos(posx2 + delta, -yorigin);
2215 
2216                         QGraphicsLineItem* graphicItem = m_scene->addLine(posx2, qMax(-yorigin, -minLimit), posx2, qMin(-yorigin, -maxLimit));
2217                         graphicItem->setPen(gridPen);
2218                         graphicItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2219                     }
2220                 }
2221 
2222                 // Axis x
2223                 if (yorigin == 0.0) {
2224                     item = m_scene->addLine(0, -yorigin, maxX, -yorigin, axisPen);
2225                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2226                     item->setZValue(2);
2227                 }
2228 
2229                 // Rect
2230                 {
2231                     QGraphicsRectItem* graphicItem = m_scene->addRect(0, qMax(-yorigin, -minLimit), maxX, qMin(-yorigin, -maxLimit) - (qMax(-yorigin, -minLimit)), axisPen);
2232                     graphicItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2233                     graphicItem->setZValue(2);
2234                 }
2235             }
2236         }
2237 
2238         // Draw Average
2239         bool lineCondition = (mode == HISTOGRAM || mode == POINT || mode == LINE) && (scaleText != 0.0) && nbRowsDrawed == 1;
2240         int averageCol = getAverageColumnIndex();
2241         if (m_averageVisible && lineCondition && averageCol != -1) {
2242             QTableWidgetItem* tableItem = ui.kTable->item(0, averageCol);
2243             if (tableItem != nullptr) {
2244                 double posy = tableItem->data(DATA_VALUE).toDouble();
2245                 if (inPositive) {
2246                     posy = qAbs(posy);
2247                 }
2248 
2249                 QGraphicsLineItem* item = m_scene->addLine(0, -posy, maxX, -posy);
2250                 dotPen.setColor(m_averageColor);
2251                 item->setPen(dotPen);
2252                 item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2253                 item->setZValue(1);
2254                 item->setToolTip(tableItem->toolTip());
2255 
2256                 QGraphicsTextItem* textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2257                 QRectF textRect = textItem->boundingRect();
2258                 double delta = scaleText * textRect.height() / 2.0;
2259                 textItem->setPos(maxX, -posy - delta);
2260                 textItem->setDefaultTextColor(m_averageColor);
2261                 textItem->setScale(scaleText);
2262                 textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2263                 textItem->setZValue(20);
2264                 textItem->setToolTip(tableItem->toolTip());
2265             }
2266         }
2267 
2268         // Draw Min & Max limits
2269         int minCol = getMinColumnIndex();
2270         if (lineCondition && minCol != -1) {
2271             // Min
2272             {
2273                 QTableWidgetItem* tableItem = ui.kTable->item(0, minCol);
2274                 if (tableItem != nullptr) {
2275                     double posy = tableItem->data(DATA_VALUE).toDouble();
2276                     if (inPositive) {
2277                         posy = qAbs(posy);
2278                     }
2279 
2280                     QGraphicsLineItem* item = m_scene->addLine(0, -posy, maxX, -posy);
2281                     dotPen.setColor(m_minColor);
2282                     item->setPen(dotPen);
2283                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2284                     item->setZValue(1);
2285                     item->setToolTip(tableItem->toolTip());
2286 
2287                     QGraphicsTextItem* textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2288                     QRectF textRect = textItem->boundingRect();
2289                     double delta = scaleText * textRect.height() / 2.0;
2290                     textItem->setPos(maxX, -posy - delta);
2291                     textItem->setDefaultTextColor(m_minColor);
2292                     textItem->setScale(scaleText);
2293                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2294                     textItem->setZValue(20);
2295                     textItem->setToolTip(tableItem->toolTip());
2296                 }
2297             }
2298 
2299             // Max
2300             {
2301                 QTableWidgetItem* tableItem = ui.kTable->item(0, minCol + 1);
2302                 if (tableItem != nullptr) {
2303                     double posy = tableItem->data(DATA_VALUE).toDouble();
2304                     if (inPositive) {
2305                         posy = qAbs(posy);
2306                     }
2307 
2308                     QGraphicsLineItem* item = m_scene->addLine(0, -posy, maxX, -posy);
2309                     dotPen.setColor(m_maxColor);
2310                     item->setPen(dotPen);
2311                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2312                     item->setZValue(1);
2313                     item->setToolTip(tableItem->toolTip());
2314 
2315                     QGraphicsTextItem* textItem = m_scene->addText(SKGServices::toCurrencyString(posy, m_primaryUnit.Symbol, m_decimalsVisible ? m_primaryUnit.NbDecimal : 0));
2316                     QRectF textRect = textItem->boundingRect();
2317                     double delta = scaleText * textRect.height() / 2.0;
2318                     textItem->setPos(maxX, -posy - delta);
2319                     textItem->setDefaultTextColor(m_maxColor);
2320                     textItem->setScale(scaleText);
2321                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2322                     textItem->setZValue(20);
2323                     textItem->setToolTip(tableItem->toolTip());
2324                 }
2325             }
2326         }
2327 
2328         // Draw Linear Regression
2329         if (m_linearRegressionVisible && lineCondition && m_indexLinearRegression != -1) {
2330             QTableWidgetItem* tableItem = ui.kTable->item(0, m_indexLinearRegression);
2331             if (tableItem != nullptr) {
2332                 QString f = tableItem->text();
2333 
2334                 QScriptEngine myEngine;
2335                 QString f0 = f;
2336                 f0 = f0.remove(QStringLiteral("y="));
2337                 f0 = f0.replace('x', '1');
2338                 f0 = f0.replace(',', '.');  // Replace comma by a point in case of typo
2339                 if (inPositive) {
2340                     f0 = "Math.abs(" % f0 % ')';
2341                 }
2342                 double y0 = myEngine.evaluate(f0).toNumber();
2343 
2344                 QString f1 = f;
2345                 f1 = f1.remove(QStringLiteral("y="));
2346                 f1 = f1.replace('x', SKGServices::intToString(nbRealColumns - 1));
2347                 f1 = f1.replace(',', '.');  // Replace comma by a point in case of typo
2348                 if (inPositive) {
2349                     f1 = "Math.abs(" % f1 % ')';
2350                 }
2351                 double y1 = myEngine.evaluate(f1).toNumber();
2352 
2353                 QGraphicsLineItem* item = m_scene->addLine(x0, -y0, x1, -y1);
2354                 dotPen.setColor(m_tendencyColor);
2355                 item->setPen(dotPen);
2356                 item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2357                 item->setZValue(1);
2358                 item->setToolTip(f);
2359             }
2360         }
2361 
2362         // Draw pareto
2363         if (lineCondition && m_paretoVisible && !paretoPoints.isEmpty()) {
2364             // Compute the sum
2365             double sum = 0.0;
2366             QList<double> list = paretoPoints.keys();
2367             for (auto xp1 : qAsConst(list)) {
2368                 sum += paretoPoints[xp1];
2369             }
2370 
2371             // Draw the second axis
2372             for (int i = 0 ; i <= 100 ; i = i + 10) {
2373                 double posy = (maxLimit - minLimit) * static_cast<double>(i) / 100.0 + minLimit;
2374 
2375                 QGraphicsTextItem* textItem = m_scene->addText(SKGServices::intToString(i) % "%");
2376                 QRectF textRect = textItem->boundingRect();
2377                 textItem->setDefaultTextColor(m_paretoColor);
2378                 textItem->setScale(scaleText);
2379                 textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2380                 textItem->setZValue(19);
2381 
2382                 double delta = scaleText * textRect.height() / 2.0;
2383                 textItem->setPos(maxX,  -posy - delta);
2384             }
2385 
2386             // Draw the curve
2387             double x00 = -1;
2388             double y00 = -1;
2389             double csum = 0.0;
2390             for (auto xp2 : qAsConst(list)) {
2391                 csum += paretoPoints[xp2];
2392                 if (x00 != -1) {
2393                     QGraphicsLineItem* item = m_scene->addLine(x00, -((maxLimit - minLimit) * y00 / sum + minLimit), xp2, -((maxLimit - minLimit) * csum / sum + minLimit));
2394                     dotPen.setColor(m_paretoColor);
2395                     item->setPen(dotPen);
2396                     item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2397                     item->setZValue(1);
2398                 }
2399                 x00 = xp2;
2400                 y00 = csum;
2401             }
2402         }
2403         // Draw legend
2404         if (m_legendVisible) {
2405             QPointF legendPosition;
2406             double maxY;
2407             if (mode == TREEMAP) {
2408                 scaleText = 0.2;
2409                 margin = BOX_SIZE / 10;
2410                 legendPosition = QPointF(BOX_SIZE + margin, 0);
2411                 maxY = BOX_SIZE * nbRowInMode2;
2412             } else if (mode == PIE || mode == CONCENTRICPIE) {
2413                 if (nbRowInMode2 * nbColInMode2 < nbColumns - 1) {
2414                     ++nbRowInMode2;
2415                 }
2416 
2417                 scaleText = 0.2;
2418                 margin = BOX_SIZE / 10;
2419                 legendPosition = QPointF(BOX_SIZE * nbColInMode2 + margin, 0);
2420                 maxY = BOX_SIZE * nbRowInMode2;
2421             } else {
2422                 legendPosition = QPointF(maxX + margin, qMin(-yorigin, -maxLimit) - margin / 6);
2423                 maxY = qMax(-yorigin, -minLimit);
2424             }
2425             addLegend(legendPosition, margin / 4, scaleText, maxY);
2426         }
2427     }
2428     {
2429         SKGTRACEINFUNC(10)
2430         m_scene->setSceneRect(QRectF());
2431         ui.graphicView->setScene(m_scene);
2432         ui.graphicView->show();
2433         if (m_tableVisible) {
2434             ui.kTable->show();
2435         }
2436         ui.graphicView->initializeZoom();
2437 
2438         // Add selection event on scene
2439         connect(m_scene, &SKGGraphicsScene::selectionChanged, this, &SKGTableWithGraph::onSelectionChangedInGraph, Qt::QueuedConnection);
2440         connect(m_scene, &SKGGraphicsScene::doubleClicked, this, &SKGTableWithGraph::onDoubleClickGraph, Qt::QueuedConnection);
2441     }
2442     QApplication::restoreOverrideCursor();
2443 }
2444 
getNbColumns(bool iWithComputed) const2445 int SKGTableWithGraph::getNbColumns(bool iWithComputed) const
2446 {
2447     int nbColumns = ui.kTable->columnCount();
2448     if (!iWithComputed) {
2449         if (m_indexMin != -1) {
2450             nbColumns -= 2;
2451         }
2452         if (m_indexAverage != -1) {
2453             --nbColumns;
2454         }
2455         if (m_indexSum != -1) {
2456             --nbColumns;
2457         }
2458         if (m_indexLinearRegression != -1) {
2459             --nbColumns;
2460         }
2461     }
2462     return nbColumns;
2463 }
2464 
getAverageColumnIndex() const2465 int SKGTableWithGraph::getAverageColumnIndex() const
2466 {
2467     return m_indexAverage;
2468 }
2469 
getMinColumnIndex() const2470 int SKGTableWithGraph::getMinColumnIndex() const
2471 {
2472     return m_indexMin;
2473 }
2474 
computeStepSize(double iRange,double iTargetSteps)2475 double SKGTableWithGraph::computeStepSize(double iRange, double iTargetSteps)
2476 {
2477     // Calculate an initial guess at step size
2478     double tempStep = iRange / iTargetSteps;
2479     // Get the magnitude of the step size
2480     double mag = floor(log10(tempStep));
2481     double magPow = pow(static_cast<double>(10.0), mag);
2482     // Calculate most significant digit of the new step size
2483     double magMsd = static_cast<int>(tempStep / magPow + .5);
2484     // promote the MSD to either 1, 2, or 5
2485     if (magMsd > 5.0) {
2486         magMsd = 10.0;
2487     } else if (magMsd > 2.0) {
2488         magMsd = 5.0;
2489     } else if (magMsd > 1.0) {
2490         magMsd = 2.0;
2491     }
2492     return magMsd * magPow;
2493 }
2494 
addLegend(const QPointF iPosition,double iSize,double iScaleText,double iMaxY)2495 void SKGTableWithGraph::addLegend(const QPointF iPosition, double iSize, double iScaleText, double iMaxY)
2496 {
2497     SKGTRACEINFUNC(10)
2498 
2499     QPen outlinePen(m_outlineColor);
2500     outlinePen.setWidthF(iScaleText * 4.0 / 500.0);
2501 
2502     if (m_scene != nullptr) {
2503         GraphType mode =  getGraphType();
2504         int nbRows = ui.kTable->rowCount();
2505         int nbRealRows = nbRows;
2506         for (int posy = 0; posy < nbRows; ++posy) {
2507             if (m_sumRows.at(posy + 1)) {
2508                 --nbRealRows;
2509             }
2510         }
2511         int nbColumns = getNbColumns(false);
2512         if (nbColumns > 1) {
2513             double margin = 1.2;
2514             double currentYPos = iPosition.y();
2515             double currentXPos = iPosition.x();
2516             double currentXMaxSize = 0;
2517             for (int i = 0; i < nbRows; ++i) {
2518                 auto* btn = qobject_cast<SKGColorButton*>(ui.kTable->cellWidget(i, 0));
2519                 if (btn != nullptr) {
2520                     // Get title
2521                     QString title = btn->text();
2522 
2523                     // Build brush
2524                     QColor color1;
2525                     QTableWidgetItem* it = ui.kTable->item(i, 1);
2526                     if (it != nullptr) {
2527                         QGraphicsItem* graphicItem = m_mapItemGraphic.value(it);
2528                         if (graphicItem != nullptr) {
2529                             // Draw box
2530                             color1 = QColor::fromHsv(graphicItem->data(DATA_COLOR_H).toInt(),
2531                                                      graphicItem->data(DATA_COLOR_S).toInt(),
2532                                                      graphicItem->data(DATA_COLOR_V).toInt());
2533                             color1.setAlpha(ALPHA);
2534                         }
2535                     }
2536                     QColor color2;
2537                     it = ui.kTable->item(i, nbColumns - 1 - m_nbVirtualColumns);
2538                     if (it != nullptr) {
2539                         QGraphicsItem* graphicItem = m_mapItemGraphic.value(it);
2540                         if (graphicItem != nullptr) {
2541                             // Draw box
2542                             color2 = QColor::fromHsv(graphicItem->data(DATA_COLOR_H).toInt(),
2543                                                      graphicItem->data(DATA_COLOR_S).toInt(),
2544                                                      graphicItem->data(DATA_COLOR_V).toInt());
2545                             color2.setAlpha(ALPHA);
2546                         }
2547                     }
2548 
2549                     QLinearGradient grandient(currentXPos, currentYPos, currentXPos + 2.0 * iSize, currentYPos);
2550                     grandient.setColorAt(0, color1);
2551                     grandient.setColorAt(1, color2);
2552 
2553                     // Draw legend item
2554                     QGraphicsItem* item = nullptr;
2555                     if (mode == POINT || mode == LINE) {
2556                         item = drawPoint(currentXPos, currentYPos + iSize / 2.0, iSize / 2.0, mode == POINT ? i : (i % 5), QBrush(grandient));
2557                     } else if (mode == BUBBLE) {
2558                         item = m_scene->addEllipse(currentXPos, currentYPos, iSize, iSize, outlinePen, QBrush(grandient));
2559                     } else if (mode == PIE || mode == CONCENTRICPIE) {
2560                         QPainterPath path;
2561                         path.moveTo(currentXPos + iSize / 2.0, currentYPos + iSize / 2.0);
2562                         path.arcTo(currentXPos, currentYPos, iSize, iSize, 45, 270);
2563                         path.closeSubpath();
2564                         if (mode == CONCENTRICPIE) {
2565                             QPainterPath path2;
2566                             double p = iSize / 3.0;
2567                             path2.addEllipse(currentXPos + p, currentYPos + p, iSize -  2.0 * p, iSize -  2.0 * p);
2568                             path -= path2;
2569                         }
2570                         item = m_scene->addPath(path, outlinePen, QBrush(grandient));
2571                     } else {
2572                         item = m_scene->addRect(currentXPos, currentYPos, iSize, iSize, outlinePen, QBrush(grandient));
2573                     }
2574                     if (item != nullptr) {
2575                         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2576                         item->setToolTip(title);
2577 
2578                         // Set shadow
2579                         if (isShadowVisible()) {
2580                             auto ellipse_shadow = new QGraphicsDropShadowEffect();
2581                             ellipse_shadow->setOffset(3);
2582                             item->setGraphicsEffect(ellipse_shadow);
2583                         }
2584                     }
2585 
2586                     // Draw text
2587                     QGraphicsTextItem* textItem = m_scene->addText(title);
2588                     textItem->setDefaultTextColor(m_textColor);
2589                     textItem->setScale(iScaleText);
2590                     textItem->setPos(currentXPos + margin * iSize, currentYPos + iSize / 2.0 - textItem->boundingRect().height()*iScaleText / 2.0);
2591                     textItem->setFlag(QGraphicsItem::ItemIsSelectable, false);
2592 
2593                     // Compute next position
2594                     currentYPos += margin * iSize;
2595                     QRectF textRect = textItem->boundingRect();
2596                     currentXMaxSize = qMax(currentXMaxSize, static_cast<double>(iScaleText * textRect.width()));
2597                     if (currentYPos > iMaxY) {
2598                         currentYPos = iPosition.y();
2599                         currentXPos += currentXMaxSize + 2 * margin * iSize;
2600                         currentXMaxSize = 0;
2601                     }
2602                 }
2603             }
2604         }
2605     }
2606 }
2607 
addArrow(const QPointF iPeak,double iSize,double iArrowAngle,double iDegree)2608 void SKGTableWithGraph::addArrow(const QPointF iPeak, double iSize, double iArrowAngle, double iDegree)
2609 {
2610     if (m_scene != nullptr) {
2611         QPolygonF pol;
2612         double radian = 3.14 * iArrowAngle / 360.0;
2613         pol << QPointF(0, 0) << QPointF(iSize * cos(radian), iSize * sin(radian)) << QPointF(iSize * cos(radian), -iSize * sin(radian)) << QPointF(0, 0);
2614         QGraphicsPolygonItem* item = m_scene->addPolygon(pol, QPen(m_axisColor, iSize / 20.0), QBrush(m_axisColor));
2615         item->setRotation(iDegree);
2616         item->moveBy(iPeak.x(), iPeak.y());
2617         item->setFlag(QGraphicsItem::ItemIsSelectable, false);
2618         item->setZValue(2);
2619     }
2620 }
2621 
drawPoint(qreal iX,qreal iY,qreal iRadius,int iMode,const QBrush & iBrush)2622 QGraphicsItem* SKGTableWithGraph::drawPoint(qreal iX, qreal iY, qreal iRadius, int iMode, const QBrush& iBrush)
2623 {
2624     QGraphicsItem* graphItem = nullptr;
2625     int nbMode = 13;
2626     if (m_scene != nullptr) {
2627         QPen outlinePen(m_outlineColor);
2628         outlinePen.setWidthF(iRadius / 10);
2629 
2630         QPen pen;
2631         if ((iMode % nbMode) <= 4) {
2632             pen = QPen(iBrush.color());
2633             const QGradient* grad = iBrush.gradient();
2634             if (grad != nullptr) {
2635                 auto stops = grad->stops();
2636                 auto stop = stops.last();
2637                 pen = QPen(stop.second);
2638             }
2639             pen.setWidthF(iRadius / 10);
2640         }
2641 
2642         switch (iMode % nbMode) {
2643         case 0: {
2644             graphItem = m_scene->addEllipse(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, pen, m_WhiteColor);
2645             break;
2646         }
2647         case 1: {
2648             graphItem = m_scene->addRect(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, pen, m_WhiteColor);
2649             break;
2650         }
2651         case 2: {
2652             QPolygonF polygon;
2653             polygon << QPointF(iX + iRadius, iY + iRadius) << QPointF(iX + 2 * iRadius, iY - iRadius)
2654                     << QPointF(iX, iY - iRadius) << QPointF(iX + iRadius, iY + iRadius);
2655             graphItem = m_scene->addPolygon(polygon, pen, m_WhiteColor);
2656             break;
2657         }
2658         case 3: {
2659             QPolygonF polygon;
2660             polygon << QPointF(iX, iY) << QPointF(iX + iRadius, iY + iRadius)
2661                     << QPointF(iX + 2 * iRadius, iY) << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX, iY);
2662             graphItem = m_scene->addPolygon(polygon, pen, m_WhiteColor);
2663             break;
2664         }
2665         case 4: {
2666             QPolygonF polygon;
2667             polygon << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius)
2668                     << QPointF(iX, iY + iRadius) << QPointF(iX + iRadius, iY - iRadius);
2669             graphItem = m_scene->addPolygon(polygon, pen, m_WhiteColor);
2670             break;
2671         }
2672 
2673 
2674 
2675         case 5: {
2676             graphItem = m_scene->addEllipse(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, outlinePen, iBrush);
2677             break;
2678         }
2679         case 6: {
2680             graphItem = m_scene->addRect(iX, iY - iRadius, 2 * iRadius, 2 * iRadius, outlinePen, iBrush);
2681             break;
2682         }
2683         case 7: {
2684             QPolygonF polygon;
2685             polygon << QPointF(iX, iY - iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius)
2686                     << QPointF(iX + 2 * iRadius, iY - iRadius) << QPointF(iX, iY + iRadius);
2687             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2688             break;
2689         }
2690         case 8: {
2691             QPolygonF polygon;
2692             polygon << QPointF(iX + iRadius, iY + iRadius) << QPointF(iX + 2 * iRadius, iY - iRadius)
2693                     << QPointF(iX, iY - iRadius) << QPointF(iX + iRadius, iY + iRadius);
2694             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2695             break;
2696         }
2697         case 9: {
2698             QPolygonF polygon;
2699             polygon << QPointF(iX, iY) << QPointF(iX + iRadius, iY + iRadius)
2700                     << QPointF(iX + 2 * iRadius, iY) << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX, iY);
2701             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2702             break;
2703         }
2704         case 10: {
2705             QPolygonF polygon;
2706             polygon << QPointF(iX, iY - iRadius) << QPointF(iX + 2 * iRadius, iY - iRadius)
2707                     << QPointF(iX, iY + iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius);
2708             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2709             break;
2710         }
2711         case 11: {
2712             QPolygonF polygon;
2713             polygon << QPointF(iX + iRadius, iY - iRadius) << QPointF(iX + 2 * iRadius, iY + iRadius)
2714                     << QPointF(iX, iY + iRadius) << QPointF(iX + iRadius, iY - iRadius);
2715             graphItem = m_scene->addPolygon(polygon, outlinePen, iBrush);
2716             break;
2717         }
2718         case 12:
2719         default: {
2720             QPainterPath path;
2721             path.addEllipse(iX, iY - iRadius, 2 * iRadius, 2 * iRadius);
2722             path.closeSubpath();
2723 
2724             QPainterPath path2;
2725             path2.addEllipse(iX + iRadius / 2.0, iY - iRadius / 2.0, iRadius, iRadius);
2726             path -= path2;
2727             graphItem = m_scene->addPath(path, outlinePen, iBrush);
2728             break;
2729         }
2730         }
2731     }
2732     if ((graphItem != nullptr) && (iMode % nbMode) <= 4) {
2733         graphItem->setData(DATA_MODE, 1);
2734     }
2735     return graphItem;
2736 }
2737 
exportInFile(const QString & iFileName)2738 SKGError SKGTableWithGraph::exportInFile(const QString& iFileName)
2739 {
2740     SKGError err;
2741     _SKGTRACEINFUNCRC(10, err)
2742     QString lastCodecUsed = QTextCodec::codecForLocale()->name();
2743 
2744     QString extension = QFileInfo(iFileName).suffix().toUpper();
2745     if (extension == QStringLiteral("CSV")) {
2746         // Write file
2747         QSaveFile file(iFileName);
2748         if (!file.open(QIODevice::WriteOnly)) {
2749             err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", iFileName));
2750         } else {
2751             QTextStream out(&file);
2752             out.setCodec(lastCodecUsed.toLatin1().constData());
2753             QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_CSV);
2754             int nbl = dump.count();
2755             for (int i = 0; i < nbl; ++i) {
2756                 out << dump.at(i) << SKGENDL;
2757             }
2758 
2759             // Close file
2760             file.commit();
2761         }
2762     } else {
2763         // Write file
2764         QSaveFile file(iFileName);
2765         if (!file.open(QIODevice::WriteOnly)) {
2766             err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", iFileName));
2767         } else {
2768             QTextStream out(&file);
2769             out.setCodec(lastCodecUsed.toLatin1().constData());
2770             QStringList dump = SKGServices::tableToDump(getTable(), SKGServices::DUMP_TEXT);
2771             int nbl = dump.count();
2772             for (int i = 0; i < nbl; ++i) {
2773                 out << dump.at(i) << SKGENDL;
2774             }
2775 
2776             // Close file
2777             file.commit();
2778         }
2779     }
2780     return err;
2781 }
2782 
onExport()2783 void SKGTableWithGraph::onExport()
2784 {
2785     _SKGTRACEINFUNC(10)
2786     SKGError err;
2787     QString fileName = SKGMainPanel::getSaveFileName(QStringLiteral("kfiledialog:///IMPEXP"), QStringLiteral("text/csv text/plain"), this);
2788     if (!fileName.isEmpty()) {
2789         err = exportInFile(fileName);
2790 
2791         SKGMainPanel::displayErrorMessage(err);
2792         QDesktopServices::openUrl(QUrl(fileName));
2793     }
2794 }
2795 
setGraphType(SKGTableWithGraph::GraphType iType)2796 void SKGTableWithGraph::setGraphType(SKGTableWithGraph::GraphType iType)
2797 {
2798     if (m_displayMode != nullptr) {
2799         auto newIndex = m_displayMode->findData(static_cast<int>(iType));
2800         if (m_displayMode->currentIndex() != newIndex) {
2801             m_displayMode->setCurrentIndex(newIndex);
2802             Q_EMIT modified();
2803         }
2804     }
2805 }
2806 
getGraphType() const2807 SKGTableWithGraph::GraphType SKGTableWithGraph::getGraphType() const
2808 {
2809     GraphType mode = static_cast<GraphType>(m_displayMode->itemData(m_displayMode->currentIndex()).toInt());
2810     return mode;
2811 }
2812 
setSelectable(bool iSelectable)2813 void SKGTableWithGraph::setSelectable(bool iSelectable)
2814 {
2815     if (m_selectable != iSelectable) {
2816         m_selectable = iSelectable;
2817         Q_EMIT modified();
2818     }
2819 }
2820 
isSelectable() const2821 bool SKGTableWithGraph::isSelectable() const
2822 {
2823     return m_selectable;
2824 }
2825 
setShadowVisible(bool iShadow)2826 void SKGTableWithGraph::setShadowVisible(bool iShadow)
2827 {
2828     if (m_shadow != iShadow) {
2829         m_shadow = iShadow;
2830         Q_EMIT modified();
2831     }
2832 }
2833 
isShadowVisible() const2834 bool SKGTableWithGraph::isShadowVisible() const
2835 {
2836     return m_shadow;
2837 }
2838 
2839 
2840