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