1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Charts module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include "mainwidget.h"
31#include "customtablemodel.h"
32#include "pentool.h"
33#include <QtCharts/QVBoxPlotModelMapper>
34#include <QtWidgets/QTableView>
35#include <QtWidgets/QHeaderView>
36#include <QtCharts/QChartView>
37#include <QtCharts/QBoxPlotSeries>
38#include <QtCharts/QBoxSet>
39#include <QtCharts/QLegend>
40#include <QtCharts/QBarCategoryAxis>
41#include <QtGui/QBrush>
42#include <QtGui/QColor>
43#include <QtWidgets/QPushButton>
44#include <QtWidgets/QComboBox>
45#include <QtWidgets/QSpinBox>
46#include <QtWidgets/QCheckBox>
47#include <QtWidgets/QGridLayout>
48#include <QtWidgets/QHBoxLayout>
49#include <QtWidgets/QLabel>
50#include <QtWidgets/QSpacerItem>
51#include <QtWidgets/QMessageBox>
52#include <cmath>
53#include <QtCore/QDebug>
54#include <QtGui/QStandardItemModel>
55#include <QtCharts/QBarCategoryAxis>
56#include <QtCharts/QLogValueAxis>
57
58QT_CHARTS_USE_NAMESPACE
59
60static const QString allCategories[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
61static const int maxCategories = 12;
62
63MainWidget::MainWidget(QWidget *parent) :
64 QWidget(parent),
65 m_chart(0),
66 m_axis(0),
67 m_rowPos(0),
68 m_seriesCount(0)
69{
70 m_chart = new QChart();
71
72 m_penTool = new PenTool("Whiskers pen", this);
73
74 // Grid layout for the controls for configuring the chart widget
75 QGridLayout *grid = new QGridLayout();
76
77 // Create add a series button
78 QPushButton *addSeriesButton = new QPushButton("Add a series");
79 connect(sender: addSeriesButton, SIGNAL(clicked()), receiver: this, SLOT(addSeries()));
80 grid->addWidget(addSeriesButton, row: m_rowPos++, column: 1);
81
82 // Create remove a series button
83 QPushButton *removeSeriesButton = new QPushButton("Remove a series");
84 connect(sender: removeSeriesButton, SIGNAL(clicked()), receiver: this, SLOT(removeSeries()));
85 grid->addWidget(removeSeriesButton, row: m_rowPos++, column: 1);
86
87 // Create add a single box button
88 QPushButton *addBoxButton = new QPushButton("Add a box");
89 connect(sender: addBoxButton, SIGNAL(clicked()), receiver: this, SLOT(addBox()));
90 grid->addWidget(addBoxButton, row: m_rowPos++, column: 1);
91
92 // Create insert a box button
93 QPushButton *insertBoxButton = new QPushButton("Insert a box");
94 connect(sender: insertBoxButton, SIGNAL(clicked()), receiver: this, SLOT(insertBox()));
95 grid->addWidget(insertBoxButton, row: m_rowPos++, column: 1);
96
97 // Create add a single box button
98 QPushButton *removeBoxButton = new QPushButton("Remove a box");
99 connect(sender: removeBoxButton, SIGNAL(clicked()), receiver: this, SLOT(removeBox()));
100 grid->addWidget(removeBoxButton, row: m_rowPos++, column: 1);
101
102 // Create clear button
103 QPushButton *clearButton = new QPushButton("Clear");
104 connect(sender: clearButton, SIGNAL(clicked()), receiver: this, SLOT(clear()));
105 grid->addWidget(clearButton, row: m_rowPos++, column: 1);
106
107 // Create clear button
108 QPushButton *clearBoxButton = new QPushButton("ClearBox");
109 connect(sender: clearBoxButton, SIGNAL(clicked()), receiver: this, SLOT(clearBox()));
110 grid->addWidget(clearBoxButton, row: m_rowPos++, column: 1);
111
112 // Create set brush button
113 QPushButton *setBrushButton = new QPushButton("Set brush");
114 connect(sender: setBrushButton, SIGNAL(clicked()), receiver: this, SLOT(setBrush()));
115 grid->addWidget(setBrushButton, row: m_rowPos++, column: 1);
116
117 // Create set whiskers pen button
118 QPushButton *setWhiskersButton = new QPushButton("Whiskers pen");
119 connect(sender: setWhiskersButton, SIGNAL(clicked()), receiver: m_penTool, SLOT(show()));
120 connect(sender: m_penTool, SIGNAL(changed()), receiver: this, SLOT(changePen()));
121 grid->addWidget(setWhiskersButton, row: m_rowPos++, column: 1);
122
123 // Box width setting
124 m_boxWidthSB = new QDoubleSpinBox();
125 m_boxWidthSB->setMinimum(-1.0);
126 m_boxWidthSB->setMaximum(2.0);
127 m_boxWidthSB->setSingleStep(0.1);
128 m_boxWidthSB->setValue(0.5);
129 grid->addWidget(new QLabel("Box width:"), row: m_rowPos, column: 0);
130 grid->addWidget(m_boxWidthSB, row: m_rowPos++, column: 1);
131 connect(sender: m_boxWidthSB, SIGNAL(valueChanged(double)), receiver: this, SLOT(setBoxWidth(double)));
132
133 initThemeCombo(grid);
134 initCheckboxes(grid);
135
136 QTableView *tableView = new QTableView;
137 m_model = new CustomTableModel(tableView);
138 tableView->setModel(m_model);
139 tableView->setMaximumWidth(200);
140 grid->addWidget(tableView, row: m_rowPos++, column: 0, rowSpan: 3, columnSpan: 2, Qt::AlignLeft);
141 tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
142 tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
143
144 // add row with empty label to make all the other rows static
145 grid->addWidget(new QLabel(""), row: grid->rowCount(), column: 0);
146 grid->setRowStretch(row: grid->rowCount() - 1, stretch: 1);
147
148 // Create chart view with the chart
149 m_chartView = new QChartView(m_chart, this);
150
151 // As a default antialiasing is off
152 m_chartView->setRenderHint(hint: QPainter::Antialiasing, enabled: false);
153
154 // Another grid layout as a main layout
155 QGridLayout *mainLayout = new QGridLayout();
156 mainLayout->addLayout(grid, row: 0, column: 0);
157 mainLayout->addWidget(m_chartView, row: 0, column: 1, rowSpan: 3, columnSpan: 1);
158 setLayout(mainLayout);
159
160 legendToggled(enabled: false);
161 animationToggled(enabled: false);
162}
163
164// Combo box for selecting theme
165void MainWidget::initThemeCombo(QGridLayout *grid)
166{
167 QComboBox *chartTheme = new QComboBox();
168 chartTheme->addItem(atext: "Default");
169 chartTheme->addItem(atext: "Light");
170 chartTheme->addItem(atext: "Blue Cerulean");
171 chartTheme->addItem(atext: "Dark");
172 chartTheme->addItem(atext: "Brown Sand");
173 chartTheme->addItem(atext: "Blue NCS");
174 chartTheme->addItem(atext: "High Contrast");
175 chartTheme->addItem(atext: "Blue Icy");
176 chartTheme->addItem(atext: "Qt");
177 connect(sender: chartTheme, SIGNAL(currentIndexChanged(int)),
178 receiver: this, SLOT(changeChartTheme(int)));
179 grid->addWidget(new QLabel("Chart theme:"), row: m_rowPos, column: 0);
180 grid->addWidget(chartTheme, row: m_rowPos++, column: 1);
181}
182
183// Different check boxes for customizing chart
184void MainWidget::initCheckboxes(QGridLayout *grid)
185{
186 QCheckBox *animationCheckBox = new QCheckBox("Animation");
187 connect(sender: animationCheckBox, SIGNAL(toggled(bool)), receiver: this, SLOT(animationToggled(bool)));
188 animationCheckBox->setChecked(false);
189 grid->addWidget(animationCheckBox, row: m_rowPos++, column: 0);
190
191 QCheckBox *legendCheckBox = new QCheckBox("Legend");
192 connect(sender: legendCheckBox, SIGNAL(toggled(bool)), receiver: this, SLOT(legendToggled(bool)));
193 legendCheckBox->setChecked(false);
194 grid->addWidget(legendCheckBox, row: m_rowPos++, column: 0);
195
196 QCheckBox *titleCheckBox = new QCheckBox("Title");
197 connect(sender: titleCheckBox, SIGNAL(toggled(bool)), receiver: this, SLOT(titleToggled(bool)));
198 titleCheckBox->setChecked(false);
199 grid->addWidget(titleCheckBox, row: m_rowPos++, column: 0);
200
201 QCheckBox *antialiasingCheckBox = new QCheckBox("Antialiasing");
202 connect(sender: antialiasingCheckBox, SIGNAL(toggled(bool)), receiver: this, SLOT(antialiasingToggled(bool)));
203 antialiasingCheckBox->setChecked(false);
204 grid->addWidget(antialiasingCheckBox, row: m_rowPos++, column: 0);
205
206 QCheckBox *modelMapperCheckBox = new QCheckBox("Use model mapper");
207 connect(sender: modelMapperCheckBox, SIGNAL(toggled(bool)), receiver: this, SLOT(modelMapperToggled(bool)));
208 modelMapperCheckBox->setChecked(false);
209 grid->addWidget(modelMapperCheckBox, row: m_rowPos++, column: 0);
210
211 m_boxOutlined = new QCheckBox("Box outlined");
212 connect(sender: m_boxOutlined, SIGNAL(toggled(bool)), receiver: this, SLOT(boxOutlineToggled(bool)));
213 m_boxOutlined->setChecked(true);
214 grid->addWidget(m_boxOutlined, row: m_rowPos++, column: 0);
215}
216
217void MainWidget::updateAxis(int categoryCount)
218{
219 if (!m_axis) {
220 m_chart->createDefaultAxes();
221 m_axis = new QBarCategoryAxis();
222 }
223 QStringList categories;
224 for (int i = 0; i < categoryCount; i++)
225 categories << allCategories[i];
226 m_axis->setCategories(categories);
227}
228
229void MainWidget::addSeries()
230{
231 qDebug() << "BoxPlotTester::MainWidget::addSeries()";
232
233 if (m_seriesCount > 9)
234 return;
235
236 // Initial data
237 QBoxSet *set0 = new QBoxSet();
238 QBoxSet *set1 = new QBoxSet();
239 QBoxSet *set2 = new QBoxSet();
240 QBoxSet *set3 = new QBoxSet();
241 QBoxSet *set4 = new QBoxSet();
242 QBoxSet *set5 = new QBoxSet();
243
244 // low bot med top upp
245 *set0 << -1 << 2 << 4 << 13 << 15;
246 *set1 << 5 << 6 << 7.5 << 8 << 12;
247 *set2 << 3 << 5 << 5.7 << 8 << 9;
248 *set3 << 5 << 6 << 6.8 << 7 << 8;
249 *set4 << 4 << 5 << 5.2 << 6 << 7;
250 *set5 << 4 << 7 << 8.2 << 9 << 10;
251
252 m_series[m_seriesCount] = new QBoxPlotSeries();
253 m_series[m_seriesCount]->append(box: set0);
254 m_series[m_seriesCount]->append(box: set1);
255 m_series[m_seriesCount]->append(box: set2);
256 m_series[m_seriesCount]->append(box: set3);
257 m_series[m_seriesCount]->append(box: set4);
258 m_series[m_seriesCount]->append(box: set5);
259 m_series[m_seriesCount]->setName("Box & Whiskers");
260
261 connect(sender: m_series[m_seriesCount], SIGNAL(clicked(QBoxSet*)), receiver: this, SLOT(boxClicked(QBoxSet*)));
262 connect(sender: m_series[m_seriesCount], SIGNAL(pressed(QBoxSet*)), receiver: this, SLOT(boxPressed(QBoxSet*)));
263 connect(sender: m_series[m_seriesCount], SIGNAL(released(QBoxSet*)), receiver: this, SLOT(boxReleased(QBoxSet*)));
264 connect(sender: m_series[m_seriesCount], SIGNAL(doubleClicked(QBoxSet*)),
265 receiver: this, SLOT(boxDoubleClicked(QBoxSet*)));
266 connect(sender: m_series[m_seriesCount], SIGNAL(hovered(bool, QBoxSet*)), receiver: this, SLOT(boxHovered(bool, QBoxSet*)));
267 connect(sender: set1, SIGNAL(clicked()), receiver: this, SLOT(singleBoxClicked()));
268 connect(sender: set1, SIGNAL(pressed()), receiver: this, SLOT(singleBoxPressed()));
269 connect(sender: set1, SIGNAL(released()), receiver: this, SLOT(singleBoxReleased()));
270 connect(sender: set1, SIGNAL(doubleClicked()), receiver: this, SLOT(singleBoxDoubleClicked()));
271 connect(sender: set2, SIGNAL(hovered(bool)), receiver: this, SLOT(singleBoxHovered(bool)));
272
273 m_series[m_seriesCount]->setBoxOutlineVisible(m_boxOutlined->checkState());
274 m_series[m_seriesCount]->setBoxWidth(m_boxWidthSB->value());
275
276 m_chart->addSeries(series: m_series[m_seriesCount]);
277
278 updateAxis(categoryCount: m_series[0]->count());
279 m_chart->setAxisX(axis: m_axis, series: m_series[m_seriesCount]);
280
281 m_seriesCount++;
282}
283
284void MainWidget::removeSeries()
285{
286 qDebug() << "BoxPlotTester::MainWidget::removeSeries()";
287
288 if (m_seriesCount > 0) {
289 m_seriesCount--;
290 m_chart->removeSeries(series: m_series[m_seriesCount]);
291 delete m_series[m_seriesCount];
292 m_series[m_seriesCount] = 0;
293 } else {
294 qDebug() << "Create a series first";
295 }
296}
297
298void MainWidget::addBox()
299{
300 qDebug() << "BoxPlotTester::MainWidget::addBox()";
301
302 if (m_seriesCount > 0 && m_series[0]->count() < maxCategories) {
303 QBoxSet *newSet = new QBoxSet();
304 newSet->setValue(index: QBoxSet::LowerExtreme, value: 5.0);
305 newSet->setValue(index: QBoxSet::LowerQuartile, value: 6.0);
306 newSet->setValue(index: QBoxSet::Median, value: 6.8);
307 newSet->setValue(index: QBoxSet::UpperQuartile, value: 7.0);
308 newSet->setValue(index: QBoxSet::UpperExtreme, value: 8.0);
309
310 updateAxis(categoryCount: m_series[0]->count() + 1);
311
312 m_series[0]->append(box: newSet);
313 }
314}
315
316void MainWidget::insertBox()
317{
318 qDebug() << "BoxPlotTester::MainWidget::insertBox()";
319
320 if (m_seriesCount > 0 && m_series[0]->count() < maxCategories) {
321 updateAxis(categoryCount: m_series[0]->count() + 1);
322 for (int i = 0; i < m_seriesCount; i++) {
323 QBoxSet *newSet = new QBoxSet();
324 *newSet << 2 << 6 << 6.8 << 7 << 10;
325 m_series[i]->insert(index: 1, box: newSet);
326 }
327 }
328}
329
330void MainWidget::removeBox()
331{
332 qDebug() << "BoxPlotTester::MainWidget::removeBox";
333
334 if (m_seriesCount > 0) {
335 for (int i = 0; i < m_seriesCount; i++) {
336 qDebug() << "m_series[i]->count() = " << m_series[i]->count();
337 if (m_series[i]->count()) {
338 QList<QBoxSet *> sets = m_series[i]->boxSets();
339 m_series[i]->remove(box: sets.at(i: m_series[i]->count() - 1));
340 }
341 }
342
343 updateAxis(categoryCount: m_series[0]->count());
344 } else {
345 qDebug() << "Create a series first";
346 }
347}
348
349void MainWidget::clear()
350{
351 qDebug() << "BoxPlotTester::MainWidget::clear";
352
353 if (m_seriesCount > 0)
354 m_series[0]->clear();
355 else
356 qDebug() << "Create a series first";
357}
358
359void MainWidget::clearBox()
360{
361 qDebug() << "BoxPlotTester::MainWidget::clearBox";
362
363 if (m_seriesCount > 0) {
364 QList<QBoxSet *> sets = m_series[0]->boxSets();
365 if (sets.count() > 1)
366 sets.at(i: 1)->clear();
367 else
368 qDebug() << "Create a series with at least two items first";
369 } else {
370 qDebug() << "Create a series first";
371 }
372}
373
374void MainWidget::setBrush()
375{
376 qDebug() << "BoxPlotTester::MainWidget::setBrush";
377
378 if (m_seriesCount > 0) {
379 QList<QBoxSet *> sets = m_series[0]->boxSets();
380 if (sets.count() > 1)
381 sets.at(i: 1)->setBrush(QBrush(QColor(Qt::yellow)));
382 else
383 qDebug() << "Create a series with at least two items first";
384 } else {
385 qDebug() << "Create a series first";
386 }
387}
388
389void MainWidget::animationToggled(bool enabled)
390{
391 qDebug() << "BoxPlotTester::Animation toggled to " << enabled;
392 if (enabled)
393 m_chart->setAnimationOptions(QChart::SeriesAnimations);
394 else
395 m_chart->setAnimationOptions(QChart::NoAnimation);
396}
397
398void MainWidget::legendToggled(bool enabled)
399{
400 qDebug() << "BoxPlotTester::Legend toggled to " << enabled;
401 m_chart->legend()->setVisible(enabled);
402 if (enabled)
403 m_chart->legend()->setAlignment(Qt::AlignBottom);
404}
405
406void MainWidget::titleToggled(bool enabled)
407{
408 qDebug() << "BoxPlotTester::Title toggled to " << enabled;
409 if (enabled)
410 m_chart->setTitle("Simple boxplotchart example");
411 else
412 m_chart->setTitle("");
413}
414
415void MainWidget::antialiasingToggled(bool enabled)
416{
417 qDebug() << "BoxPlotTester::antialiasingToggled toggled to " << enabled;
418 m_chartView->setRenderHint(hint: QPainter::Antialiasing, enabled);
419}
420
421void MainWidget::boxOutlineToggled(bool visible)
422{
423 qDebug() << "BoxPlotTester::boxOutlineToggled toggled to " << visible;
424 for (int i = 0; i < m_seriesCount; i++)
425 m_series[i]->setBoxOutlineVisible(visible);
426}
427
428void MainWidget::modelMapperToggled(bool enabled)
429{
430 if (enabled) {
431 m_series[m_seriesCount] = new QBoxPlotSeries();
432
433 int first = 0;
434 int count = 5;
435 QVBoxPlotModelMapper *mapper = new QVBoxPlotModelMapper(this);
436 mapper->setFirstBoxSetColumn(0);
437 mapper->setLastBoxSetColumn(5);
438 mapper->setFirstRow(first);
439 mapper->setRowCount(count);
440 mapper->setSeries(m_series[m_seriesCount]);
441 mapper->setModel(m_model);
442 m_chart->addSeries(series: m_series[m_seriesCount]);
443
444 m_seriesCount++;
445 } else {
446 removeSeries();
447 }
448}
449
450void MainWidget::changeChartTheme(int themeIndex)
451{
452 qDebug() << "BoxPlotTester::changeChartTheme: " << themeIndex;
453 if (themeIndex == 0)
454 m_chart->setTheme(QChart::ChartThemeLight);
455 else
456 m_chart->setTheme((QChart::ChartTheme) (themeIndex - 1));
457}
458
459void MainWidget::boxClicked(QBoxSet *set)
460{
461 qDebug() << "boxClicked, median = " << set->at(index: QBoxSet::Median);
462}
463
464void MainWidget::boxHovered(bool state, QBoxSet *set)
465{
466 if (state)
467 qDebug() << "box median " << set->at(index: QBoxSet::Median) << " hover started";
468 else
469 qDebug() << "box median " << set->at(index: QBoxSet::Median) << " hover ended";
470}
471
472void MainWidget::boxPressed(QBoxSet *set)
473{
474 qDebug() << "boxPressed, median = " << set->at(index: QBoxSet::Median);
475}
476
477void MainWidget::boxReleased(QBoxSet *set)
478{
479 qDebug() << "boxReleased, median = " << set->at(index: QBoxSet::Median);
480}
481
482void MainWidget::boxDoubleClicked(QBoxSet *set)
483{
484 qDebug() << "boxDoubleClicked, median = " << set->at(index: QBoxSet::Median);
485}
486
487void MainWidget::singleBoxClicked()
488{
489 qDebug() << "singleBoxClicked";
490}
491
492void MainWidget::singleBoxPressed()
493{
494 qDebug() << "singleBoxPressed";
495}
496
497void MainWidget::singleBoxReleased()
498{
499 qDebug() << "singleBoxReleased";
500}
501
502void MainWidget::singleBoxDoubleClicked()
503{
504 qDebug() << "singleBoxDoubleClicked";
505}
506
507void MainWidget::singleBoxHovered(bool state)
508{
509 if (state)
510 qDebug() << "single box hover started";
511 else
512 qDebug() << "single box hover ended";
513}
514
515void MainWidget::changePen()
516{
517 qDebug() << "changePen() = " << m_penTool->pen();
518 for (int i = 0; i < m_seriesCount; i++)
519 m_series[i]->setPen(m_penTool->pen());
520}
521
522void MainWidget::setBoxWidth(double width)
523{
524 qDebug() << "setBoxWidth to " << width;
525
526 for (int i = 0; i < m_seriesCount; i++)
527 m_series[i]->setBoxWidth(qreal(width));
528}
529

source code of qtcharts/tests/manual/boxplottester/mainwidget.cpp