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 "dataseriedialog.h" |
32 | #include <QtCharts/QChartView> |
33 | #include <QtCharts/QPieSeries> |
34 | #include <QtCharts/QScatterSeries> |
35 | #include <QtCharts/QLineSeries> |
36 | #include <QtCharts/QAreaSeries> |
37 | #include <QtCharts/QSplineSeries> |
38 | #include <QtCharts/QBarSet> |
39 | #include <QtCharts/QBarSeries> |
40 | #include <QtCharts/QStackedBarSeries> |
41 | #include <QtCharts/QPercentBarSeries> |
42 | #include <QtWidgets/QPushButton> |
43 | #include <QtWidgets/QComboBox> |
44 | #include <QtWidgets/QSpinBox> |
45 | #include <QtWidgets/QCheckBox> |
46 | #include <QtWidgets/QGridLayout> |
47 | #include <QtWidgets/QHBoxLayout> |
48 | #include <QtWidgets/QLabel> |
49 | #include <QtWidgets/QSpacerItem> |
50 | #include <QtWidgets/QMessageBox> |
51 | #include <QtCore/QDebug> |
52 | #include <QtCore/QRandomGenerator> |
53 | #include <QtGui/QStandardItemModel> |
54 | #include <QtCharts/QBarCategoryAxis> |
55 | #include <QtWidgets/QOpenGLWidget> |
56 | |
57 | #include <qmath.h> |
58 | |
59 | QT_CHARTS_USE_NAMESPACE |
60 | |
61 | MainWidget::MainWidget(QWidget *parent) : |
62 | QWidget(parent), |
63 | m_addSerieDialog(0), |
64 | m_chart(0) |
65 | { |
66 | m_chart = new QChart(); |
67 | |
68 | // Grid layout for the controls for configuring the chart widget |
69 | QGridLayout *grid = new QGridLayout(); |
70 | QPushButton *addSeriesButton = new QPushButton("Add series" ); |
71 | connect(sender: addSeriesButton, SIGNAL(clicked()), receiver: this, SLOT(addSeries())); |
72 | grid->addWidget(addSeriesButton, row: 0, column: 1); |
73 | initBackroundCombo(grid); |
74 | initScaleControls(grid); |
75 | initThemeCombo(grid); |
76 | initCheckboxes(grid); |
77 | |
78 | // add row with empty label to make all the other rows static |
79 | grid->addWidget(new QLabel("" ), row: grid->rowCount(), column: 0); |
80 | grid->setRowStretch(row: grid->rowCount() - 1, stretch: 1); |
81 | |
82 | // Create chart view with the chart |
83 | m_chartView = new QChartView(m_chart, this); |
84 | m_chartView->setRubberBand(QChartView::HorizontalRubberBand); |
85 | |
86 | // Another grid layout as a main layout |
87 | QGridLayout *mainLayout = new QGridLayout(); |
88 | mainLayout->addLayout(grid, row: 0, column: 0); |
89 | mainLayout->addWidget(m_chartView, row: 0, column: 1, rowSpan: 3, columnSpan: 1); |
90 | setLayout(mainLayout); |
91 | } |
92 | |
93 | // Combo box for selecting the chart's background |
94 | void MainWidget::initBackroundCombo(QGridLayout *grid) |
95 | { |
96 | QComboBox *backgroundCombo = new QComboBox(this); |
97 | backgroundCombo->addItem(atext: "Color" ); |
98 | backgroundCombo->addItem(atext: "Gradient" ); |
99 | backgroundCombo->addItem(atext: "Image" ); |
100 | connect(sender: backgroundCombo, SIGNAL(currentIndexChanged(int)), |
101 | receiver: this, SLOT(backgroundChanged(int))); |
102 | |
103 | grid->addWidget(new QLabel("Background:" ), row: grid->rowCount(), column: 0); |
104 | grid->addWidget(backgroundCombo, row: grid->rowCount() - 1, column: 1); |
105 | } |
106 | |
107 | // Scale related controls (auto-scale vs. manual min-max values) |
108 | void MainWidget::initScaleControls(QGridLayout *grid) |
109 | { |
110 | m_autoScaleCheck = new QCheckBox("Automatic scaling" ); |
111 | connect(sender: m_autoScaleCheck, SIGNAL(stateChanged(int)), receiver: this, SLOT(autoScaleChanged(int))); |
112 | // Allow setting also non-sense values (like -2147483648 and 2147483647) |
113 | m_xMinSpin = new QSpinBox(); |
114 | m_xMinSpin->setMinimum(INT_MIN); |
115 | m_xMinSpin->setMaximum(INT_MAX); |
116 | m_xMinSpin->setValue(0); |
117 | connect(sender: m_xMinSpin, SIGNAL(valueChanged(int)), receiver: this, SLOT(xMinChanged(int))); |
118 | m_xMaxSpin = new QSpinBox(); |
119 | m_xMaxSpin->setMinimum(INT_MIN); |
120 | m_xMaxSpin->setMaximum(INT_MAX); |
121 | m_xMaxSpin->setValue(10); |
122 | connect(sender: m_xMaxSpin, SIGNAL(valueChanged(int)), receiver: this, SLOT(xMaxChanged(int))); |
123 | m_yMinSpin = new QSpinBox(); |
124 | m_yMinSpin->setMinimum(INT_MIN); |
125 | m_yMinSpin->setMaximum(INT_MAX); |
126 | m_yMinSpin->setValue(0); |
127 | connect(sender: m_yMinSpin, SIGNAL(valueChanged(int)), receiver: this, SLOT(yMinChanged(int))); |
128 | m_yMaxSpin = new QSpinBox(); |
129 | m_yMaxSpin->setMinimum(INT_MIN); |
130 | m_yMaxSpin->setMaximum(INT_MAX); |
131 | m_yMaxSpin->setValue(10); |
132 | connect(sender: m_yMaxSpin, SIGNAL(valueChanged(int)), receiver: this, SLOT(yMaxChanged(int))); |
133 | |
134 | grid->addWidget(m_autoScaleCheck, row: grid->rowCount(), column: 0); |
135 | grid->addWidget(new QLabel("x min:" ), row: grid->rowCount(), column: 0); |
136 | grid->addWidget(m_xMinSpin, row: grid->rowCount() - 1, column: 1); |
137 | grid->addWidget(new QLabel("x max:" ), row: grid->rowCount(), column: 0); |
138 | grid->addWidget(m_xMaxSpin, row: grid->rowCount() - 1, column: 1); |
139 | grid->addWidget(new QLabel("y min:" ), row: grid->rowCount(), column: 0); |
140 | grid->addWidget(m_yMinSpin, row: grid->rowCount() - 1, column: 1); |
141 | grid->addWidget(new QLabel("y max:" ), row: grid->rowCount(), column: 0); |
142 | grid->addWidget(m_yMaxSpin, row: grid->rowCount() - 1, column: 1); |
143 | |
144 | m_autoScaleCheck->setChecked(true); |
145 | } |
146 | |
147 | // Combo box for selecting theme |
148 | void MainWidget::initThemeCombo(QGridLayout *grid) |
149 | { |
150 | QComboBox *chartTheme = new QComboBox(); |
151 | chartTheme->addItem(atext: "Default" ); |
152 | chartTheme->addItem(atext: "Light" ); |
153 | chartTheme->addItem(atext: "Blue Cerulean" ); |
154 | chartTheme->addItem(atext: "Dark" ); |
155 | chartTheme->addItem(atext: "Brown Sand" ); |
156 | chartTheme->addItem(atext: "Blue NCS" ); |
157 | chartTheme->addItem(atext: "High Contrast" ); |
158 | chartTheme->addItem(atext: "Blue Icy" ); |
159 | chartTheme->addItem(atext: "Qt" ); |
160 | connect(sender: chartTheme, SIGNAL(currentIndexChanged(int)), |
161 | receiver: this, SLOT(changeChartTheme(int))); |
162 | grid->addWidget(new QLabel("Chart theme:" ), row: 8, column: 0); |
163 | grid->addWidget(chartTheme, row: 8, column: 1); |
164 | } |
165 | |
166 | // Different check boxes for customizing chart |
167 | void MainWidget::initCheckboxes(QGridLayout *grid) |
168 | { |
169 | // TODO: setZoomEnabled slot has been removed from QChartView -> Re-implement zoom on/off |
170 | QCheckBox *zoomCheckBox = new QCheckBox("Drag'n drop Zoom" ); |
171 | // connect(zoomCheckBox, SIGNAL(toggled(bool)), m_chartView, SLOT(setZoomEnabled(bool))); |
172 | zoomCheckBox->setChecked(true); |
173 | grid->addWidget(zoomCheckBox, row: grid->rowCount(), column: 0); |
174 | |
175 | QCheckBox *aliasCheckBox = new QCheckBox("Anti-alias" ); |
176 | connect(sender: aliasCheckBox, SIGNAL(toggled(bool)), receiver: this, SLOT(antiAliasToggled(bool))); |
177 | aliasCheckBox->setChecked(false); |
178 | grid->addWidget(aliasCheckBox, row: grid->rowCount(), column: 0); |
179 | |
180 | QCheckBox *openGLCheckBox = new QCheckBox("Use QOpenGLWidget" ); |
181 | connect(sender: openGLCheckBox, SIGNAL(toggled(bool)), receiver: this, SLOT(openGLToggled(bool))); |
182 | openGLCheckBox->setChecked(false); |
183 | grid->addWidget(openGLCheckBox, row: grid->rowCount(), column: 0); |
184 | } |
185 | |
186 | void MainWidget::antiAliasToggled(bool enabled) |
187 | { |
188 | m_chartView->setRenderHint(hint: QPainter::Antialiasing, enabled); |
189 | } |
190 | |
191 | void MainWidget::openGLToggled(bool enabled) |
192 | { |
193 | if (enabled) { |
194 | QSurfaceFormat f = QSurfaceFormat::defaultFormat(); |
195 | f.setSamples(4); |
196 | QSurfaceFormat::setDefaultFormat(f); |
197 | QOpenGLWidget *g = new QOpenGLWidget(); |
198 | m_chartView->setViewport(g); |
199 | } else { |
200 | m_chartView->setViewport(0); |
201 | } |
202 | } |
203 | |
204 | void MainWidget::addSeries() |
205 | { |
206 | if (!m_addSerieDialog) { |
207 | m_addSerieDialog = new DataSerieDialog(this); |
208 | connect(sender: m_addSerieDialog, SIGNAL(accepted(QString,int,int,QString,bool)), |
209 | receiver: this, SLOT(addSeries(QString,int,int,QString,bool))); |
210 | } |
211 | m_addSerieDialog->exec(); |
212 | } |
213 | |
214 | QList<RealList> MainWidget::generateTestData(int columnCount, int rowCount, QString dataCharacteristics) |
215 | { |
216 | QList<RealList> testData; |
217 | for (int j(0); j < columnCount; j++) { |
218 | QList <qreal> newColumn; |
219 | for (int i(0); i < rowCount; i++) { |
220 | if (dataCharacteristics == "Sin" ) { |
221 | newColumn.append(t: std::abs(x: sin(M_PI / 50 * i) * 100)); |
222 | } else if (dataCharacteristics == "Sin + random" ) { |
223 | newColumn.append(t: std::abs(x: sin(M_PI / 50 * i) * 100) + QRandomGenerator::global()->bounded(highest: 5)); |
224 | } else if (dataCharacteristics == "Random" ) { |
225 | newColumn.append(t: QRandomGenerator::global()->bounded(highest: 11.0)); |
226 | } else if (dataCharacteristics == "Linear" ) { |
227 | //newColumn.append(i * (j + 1.0)); |
228 | // TODO: temporary hack to make pie work; prevent zero values: |
229 | newColumn.append(t: i * (j + 1.0) + 0.1); |
230 | } else { // "constant" |
231 | newColumn.append(t: (j + 1.0)); |
232 | } |
233 | } |
234 | testData.append(t: newColumn); |
235 | } |
236 | return testData; |
237 | } |
238 | |
239 | QStringList MainWidget::generateLabels(int count) |
240 | { |
241 | QStringList result; |
242 | for (int i(0); i < count; i++) |
243 | result.append(t: "label" + QString::number(i)); |
244 | return result; |
245 | } |
246 | |
247 | void MainWidget::addSeries(QString seriesName, int columnCount, int rowCount, QString dataCharacteristics, bool labelsEnabled) |
248 | { |
249 | qDebug() << "addSeries: " << seriesName |
250 | << " columnCount: " << columnCount |
251 | << " rowCount: " << rowCount |
252 | << " dataCharacteristics: " << dataCharacteristics |
253 | << " labels enabled: " << labelsEnabled; |
254 | m_defaultSeriesName = seriesName; |
255 | |
256 | QList<RealList> data = generateTestData(columnCount, rowCount, dataCharacteristics); |
257 | |
258 | // Line series and scatter series use similar data |
259 | if (seriesName == "Line" ) { |
260 | for (int j(0); j < data.count(); j ++) { |
261 | QList<qreal> column = data.at(i: j); |
262 | QLineSeries *series = new QLineSeries(); |
263 | series->setName("line" + QString::number(j)); |
264 | for (int i(0); i < column.count(); i++) |
265 | series->append(x: i, y: column.at(i)); |
266 | m_chart->addSeries(series); |
267 | } |
268 | } else if (seriesName == "Area" ) { |
269 | // TODO: lower series for the area? |
270 | for (int j(0); j < data.count(); j ++) { |
271 | QList<qreal> column = data.at(i: j); |
272 | QLineSeries *lineSeries = new QLineSeries(); |
273 | for (int i(0); i < column.count(); i++) |
274 | lineSeries->append(x: i, y: column.at(i)); |
275 | QAreaSeries *areaSeries = new QAreaSeries(lineSeries); |
276 | areaSeries->setName("area" + QString::number(j)); |
277 | m_chart->addSeries(series: areaSeries); |
278 | } |
279 | } else if (seriesName == "Scatter" ) { |
280 | for (int j(0); j < data.count(); j++) { |
281 | QList<qreal> column = data.at(i: j); |
282 | QScatterSeries *series = new QScatterSeries(); |
283 | series->setName("scatter" + QString::number(j)); |
284 | for (int i(0); i < column.count(); i++) |
285 | series->append(x: i, y: column.at(i)); |
286 | m_chart->addSeries(series); |
287 | } |
288 | } else if (seriesName == "Pie" ) { |
289 | QStringList labels = generateLabels(count: rowCount); |
290 | for (int j(0); j < data.count(); j++) { |
291 | QPieSeries *series = new QPieSeries(); |
292 | QList<qreal> column = data.at(i: j); |
293 | for (int i(0); i < column.count(); i++) |
294 | series->append(label: labels.at(i), value: column.at(i)); |
295 | m_chart->addSeries(series); |
296 | } |
297 | } else if (seriesName == "Bar" |
298 | || seriesName == "Stacked bar" |
299 | || seriesName == "Percent bar" ) { |
300 | QStringList category; |
301 | QStringList labels = generateLabels(count: rowCount); |
302 | foreach (QString label, labels) |
303 | category << label; |
304 | QAbstractBarSeries* series = 0; |
305 | if (seriesName == "Bar" ) { |
306 | series = new QBarSeries(this); |
307 | QBarCategoryAxis* axis = new QBarCategoryAxis(); |
308 | axis->append(categories: category); |
309 | m_chart->setAxisX(axis,series); |
310 | } else if (seriesName == "Stacked bar" ) { |
311 | series = new QStackedBarSeries(this); |
312 | QBarCategoryAxis* axis = new QBarCategoryAxis(); |
313 | axis->append(categories: category); |
314 | m_chart->setAxisX(axis,series); |
315 | } else { |
316 | series = new QPercentBarSeries(this); |
317 | QBarCategoryAxis* axis = new QBarCategoryAxis(); |
318 | axis->append(categories: category); |
319 | m_chart->setAxisX(axis,series); |
320 | } |
321 | |
322 | for (int j(0); j < data.count(); j++) { |
323 | QList<qreal> column = data.at(i: j); |
324 | QBarSet *set = new QBarSet("set" + QString::number(j)); |
325 | for (int i(0); i < column.count(); i++) |
326 | *set << column.at(i); |
327 | series->append(set); |
328 | } |
329 | |
330 | m_chart->addSeries(series); |
331 | } else if (seriesName == "Spline" ) { |
332 | for (int j(0); j < data.count(); j ++) { |
333 | QList<qreal> column = data.at(i: j); |
334 | QSplineSeries *series = new QSplineSeries(); |
335 | series->setName("spline" + QString::number(j)); |
336 | for (int i(0); i < column.count(); i++) |
337 | series->append(x: i, y: column.at(i)); |
338 | m_chart->addSeries(series); |
339 | } |
340 | } |
341 | m_chart->createDefaultAxes(); |
342 | } |
343 | |
344 | void MainWidget::backgroundChanged(int itemIndex) |
345 | { |
346 | qDebug() << "backgroundChanged: " << itemIndex; |
347 | } |
348 | |
349 | void MainWidget::autoScaleChanged(int value) |
350 | { |
351 | if (value) { |
352 | // TODO: enable auto scaling |
353 | } else { |
354 | // TODO: set scaling manually (and disable auto scaling) |
355 | } |
356 | |
357 | m_xMinSpin->setEnabled(!value); |
358 | m_xMaxSpin->setEnabled(!value); |
359 | m_yMinSpin->setEnabled(!value); |
360 | m_yMaxSpin->setEnabled(!value); |
361 | } |
362 | |
363 | void MainWidget::xMinChanged(int value) |
364 | { |
365 | qDebug() << "xMinChanged: " << value; |
366 | } |
367 | |
368 | void MainWidget::xMaxChanged(int value) |
369 | { |
370 | qDebug() << "xMaxChanged: " << value; |
371 | } |
372 | |
373 | void MainWidget::yMinChanged(int value) |
374 | { |
375 | qDebug() << "yMinChanged: " << value; |
376 | } |
377 | |
378 | void MainWidget::yMaxChanged(int value) |
379 | { |
380 | qDebug() << "yMaxChanged: " << value; |
381 | } |
382 | |
383 | void MainWidget::changeChartTheme(int themeIndex) |
384 | { |
385 | qDebug() << "changeChartTheme: " << themeIndex; |
386 | if (themeIndex == 0) |
387 | m_chart->setTheme(QChart::ChartThemeLight); |
388 | else |
389 | m_chart->setTheme((QChart::ChartTheme) (themeIndex - 1)); |
390 | } |
391 | |