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 "chart-widget.h"
31
32#include <QtCharts/QLineSeries>
33#include <QtCharts/QLegend>
34#include <QtCharts/QBarCategoryAxis>
35#include <QtCharts/QValueAxis>
36#include <QtCharts/QLogValueAxis>
37#include <QtCharts/QBarSet>
38#include <QtCharts/QHorizontalBarSeries>
39#include <QtCharts/QHorizontalPercentBarSeries>
40#include <QtCharts/QHorizontalStackedBarSeries>
41#include <QtCharts/QPercentBarSeries>
42#include <QtCharts/QStackedBarSeries>
43#include <QElapsedTimer>
44#include <QDebug>
45
46//#define VALUE_LOGGING 1
47
48const int initialCount = 100000;
49const int visibleCount = 20;
50const int storeCount = 100000000;
51const int interval = 500;
52
53const int initialSeriesCount = 2;
54const int maxSeriesCount = 2;
55const bool removeSeries = false;
56const int extraSeriesFrequency = 7;
57
58const int initialSetCount = 3;
59const int maxSetCount = 3;
60const bool removeSets = false;
61const int extraSetFrequency = 3;
62
63const bool initialLabels = true;
64const int labelsTrigger = -1;
65const int visibleTrigger = -1;
66const int appendFrequency = 1;
67const int animationTrigger = -1;
68const bool sameNumberOfBars = false;
69
70const bool animation = true;
71const int animationDuration = 300;
72
73const bool horizontal = false;
74const bool percent = false;
75const bool stacked = true;
76
77const bool negativeValues = false;
78const bool mixedValues = false;
79
80// Negative indexes are counted from end of the set.
81const bool doReplace = false;
82const bool doRemove = false;
83const bool doInsert = false;
84const bool singleReplace = false;
85const bool singleRemove = false;
86const bool singleInsert = false;
87const int removeIndex = -7;
88const int replaceIndex = -3;
89const int insertIndex = -5;
90
91const bool logarithmic = false;
92const bool barCategories = false;
93
94const qreal barWidth = 0.9;
95
96// Note: reverse axes are not fully supported for bars (animation and label positioning break a bit)
97const bool reverseBar = false;
98const bool reverseValue = false;
99
100static int counter = 1;
101static const QString nameTemplate = QStringLiteral("Set %1/%2");
102
103ChartWidget::ChartWidget(QWidget *parent) :
104 QWidget(parent),
105 m_chart(new QChart()),
106 m_chartView(new QChartView(this)),
107 m_setCount(initialSetCount),
108 m_seriesCount(initialSeriesCount),
109 m_extraScroll(0.0)
110{
111 m_elapsedTimer.start();
112
113 if (logarithmic) {
114 QLogValueAxis *logAxis = new QLogValueAxis;
115 logAxis->setBase(2);
116 m_valueAxis = logAxis;
117 } else {
118 m_valueAxis = new QValueAxis;
119 }
120
121 if (barCategories)
122 m_barAxis = new QBarCategoryAxis;
123 else
124 m_barAxis = new QValueAxis;
125
126 m_barAxis->setReverse(reverseBar);
127 m_valueAxis->setReverse(reverseValue);
128
129 for (int i = 0; i < maxSeriesCount; i++) {
130 if (horizontal) {
131 if (percent)
132 m_series.append(t: new QHorizontalPercentBarSeries);
133 else if (stacked)
134 m_series.append(t: new QHorizontalStackedBarSeries);
135 else
136 m_series.append(t: new QHorizontalBarSeries);
137 } else {
138 if (percent)
139 m_series.append(t: new QPercentBarSeries);
140 else if (stacked)
141 m_series.append(t: new QStackedBarSeries);
142 else
143 m_series.append(t: new QBarSeries);
144 }
145 QAbstractBarSeries *series =
146 qobject_cast<QAbstractBarSeries *>(object: m_series.at(i: m_series.size() - 1));
147 QString seriesNameTemplate = QStringLiteral("bar %1");
148 series->setName(seriesNameTemplate.arg(a: i));
149 series->setLabelsPosition(QAbstractBarSeries::LabelsInsideEnd);
150 series->setLabelsVisible(initialLabels);
151 series->setBarWidth(barWidth);
152 }
153
154 resize(w: 800, h: 300);
155 m_horizontalLayout = new QHBoxLayout(this);
156 m_horizontalLayout->setSpacing(6);
157 m_horizontalLayout->setContentsMargins(left: 11, top: 11, right: 11, bottom: 11);
158 m_horizontalLayout->addWidget(m_chartView);
159
160 qDebug() << "UI setup time:"<< m_elapsedTimer.restart();
161
162 m_chartView->setRenderHint(hint: QPainter::Antialiasing);
163
164 createChart();
165 qDebug() << "Chart creation time:"<< m_elapsedTimer.restart();
166
167 QObject::connect(sender: &m_timer, SIGNAL(timeout()), receiver: this, SLOT(handleTimeout()));
168
169 m_timer.setInterval(interval);
170 m_timer.start();
171}
172
173ChartWidget::~ChartWidget()
174{
175}
176
177void ChartWidget::handleTimeout()
178{
179 qDebug() << "Intervening time:" << m_elapsedTimer.restart();
180
181 qDebug() << "----------" << counter << "----------";
182
183 bool doScroll = false;
184
185 if (counter % appendFrequency == 0) {
186 for (int i = 0; i < maxSeriesCount; i++) {
187 for (int j = 0; j < maxSetCount; j++) {
188 QBarSet *set = m_sets.value(akey: m_series.at(i)).at(i: j);
189 qreal value = 5 * i + (counter % 100) / qreal(j+1);
190 if (negativeValues)
191 set->append(value: -value);
192 else if (mixedValues)
193 set->append(value: counter % 2 ? value : -value);
194 else
195 set->append(value);
196#ifdef VALUE_LOGGING
197 qDebug() << "Appended value:" << set->at(set->count() - 1)
198 << "to series:" << i
199 << "to set:" << j
200 << "at index:" << set->count() - 1;
201#endif
202 if (set->count() > storeCount)
203 set->remove(index: 0, count: set->count() - storeCount);
204 if (set->count() > visibleCount)
205 doScroll = true;
206 }
207 }
208 qDebug() << "Append time:" << m_elapsedTimer.restart();
209 }
210
211 if (doScroll) {
212 doScroll = false;
213 qreal scrollAmount = horizontal ? m_chart->plotArea().height() / visibleCount
214 : m_chart->plotArea().width() / visibleCount;
215 // Charts can't scroll without any series, so store the required scroll in those cases
216 if (m_seriesCount == 0) {
217 m_extraScroll += scrollAmount;
218 } else {
219 if (horizontal)
220 m_chart->scroll(dx: 0, dy: scrollAmount + m_extraScroll);
221 else
222 m_chart->scroll(dx: scrollAmount + m_extraScroll, dy: 0);
223 m_extraScroll = 0.0;
224 }
225 qDebug() << "Scroll time:" << m_elapsedTimer.restart();
226 }
227
228 if (doRemove || doReplace || doInsert) {
229 for (int i = 0; i < maxSeriesCount; i++) {
230 for (int j = 0; j < m_setCount; j++) {
231 QBarSet *set = m_sets.value(akey: m_series.at(i)).at(i: j);
232 qreal value = ((counter + 20) % 100) / qreal(j + 1);
233 if (doReplace && (!singleReplace || j == 0)) {
234 int index = replaceIndex < 0 ? set->count() + replaceIndex : replaceIndex;
235 set->replace(index, value);
236 }
237 if (doRemove && (!singleRemove || j == 0)) {
238 int index = removeIndex < 0 ? set->count() + removeIndex : removeIndex;
239 set->remove(index, count: 1);
240 }
241 if (doInsert && (!singleInsert || j == 0)) {
242 int index = insertIndex < 0 ? set->count() + insertIndex : insertIndex;
243 set->insert(index, value);
244 }
245 }
246 }
247 qDebug() << "R/R time:" << m_elapsedTimer.restart();
248 }
249
250 if (counter % extraSeriesFrequency == 0) {
251 if (m_seriesCount <= maxSeriesCount) {
252 qDebug() << "Adjusting series count, current count:" << m_seriesCount;
253 static int seriesCountAdder = 1;
254 if (m_seriesCount == maxSeriesCount) {
255 if (removeSeries)
256 seriesCountAdder = -1;
257 else
258 seriesCountAdder = 0;
259 } else if (m_seriesCount == 0) {
260 seriesCountAdder = 1;
261 }
262 if (seriesCountAdder < 0)
263 m_chart->removeSeries(series: m_series.at(i: m_seriesCount - 1));
264 else if (m_seriesCount < maxSeriesCount)
265 addSeriesToChart(series: m_series.at(i: m_seriesCount));
266 m_seriesCount += seriesCountAdder;
267 }
268 }
269
270 if (counter % extraSetFrequency == 0) {
271 if (m_setCount <= maxSetCount) {
272 qDebug() << "Adjusting setcount, current count:" << m_setCount;
273 static int setCountAdder = 1;
274 if (m_setCount == maxSetCount) {
275 if (removeSets)
276 setCountAdder = -1;
277 else
278 setCountAdder = 0;
279 } else if (m_setCount == 0) {
280 setCountAdder = 1;
281 }
282 for (int i = 0; i < maxSeriesCount; i++) {
283 if (setCountAdder < 0) {
284 int barCount = m_sets.value(akey: m_series.at(i)).at(i: m_setCount - 1)->count();
285 m_series.at(i)->remove(set: m_sets.value(akey: m_series.at(i)).at(i: m_setCount - 1));
286 // Since remove deletes the set, recreate it in our list
287 QBarSet *set = new QBarSet(nameTemplate.arg(a: i).arg(a: m_setCount - 1));
288 m_sets[m_series.at(i)][m_setCount - 1] = set;
289 set->setLabelBrush(QColor("black"));
290 set->setPen(QPen(QColor("black"), 0.3));
291 QList<qreal> valueList;
292 valueList.reserve(alloc: barCount);
293 for (int j = 0; j < barCount; ++j) {
294 qreal value = counter % 100;
295 if (negativeValues)
296 valueList.append(t: -value);
297 else if (mixedValues)
298 valueList.append(t: counter % 2 ? value : -value);
299 else
300 valueList.append(t: value);
301 }
302 set->append(values: valueList);
303
304 } else if (m_setCount < maxSetCount) {
305 m_series.at(i)->append(set: m_sets.value(akey: m_series.at(i)).at(i: m_setCount));
306 }
307 }
308 m_setCount += setCountAdder;
309 }
310 }
311
312 if (labelsTrigger > 0 && counter % labelsTrigger == 0) {
313 m_series.at(i: 0)->setLabelsVisible(!m_series.at(i: 0)->isLabelsVisible());
314 qDebug() << "Label visibility changed";
315 }
316
317 if (visibleTrigger > 0 && counter % visibleTrigger == 0) {
318 m_series.at(i: 0)->setVisible(!m_series.at(i: 0)->isVisible());
319 qDebug() << "Series visibility changed";
320 }
321
322 if (animationTrigger > 0 && counter % animationTrigger == 0) {
323 if (m_chart->animationOptions() == QChart::SeriesAnimations)
324 m_chart->setAnimationOptions(QChart::NoAnimation);
325 else
326 m_chart->setAnimationOptions(QChart::SeriesAnimations);
327 qDebug() << "Series animation changed";
328 }
329
330 qDebug() << "Rest of time:" << m_elapsedTimer.restart();
331
332 qDebug() << "GraphicsItem Count:" << m_chart->scene()->items().size();
333 counter++;
334}
335
336void ChartWidget::createChart()
337{
338 qDebug() << "Initial bar count:" << initialCount;
339
340 QList<qreal> valueList;
341 valueList.reserve(alloc: initialCount);
342 for (int j = 0; j < initialCount; ++j) {
343 qreal value = counter++ % 100;
344 if (negativeValues)
345 valueList.append(t: -value);
346 else if (mixedValues)
347 valueList.append(t: counter % 2 ? value : -value);
348 else
349 valueList.append(t: value);
350 }
351
352 for (int i = 0; i < maxSeriesCount; i++) {
353 for (int j = 0; j < maxSetCount; j++) {
354 QBarSet *set = new QBarSet(nameTemplate.arg(a: i).arg(a: j));
355 m_sets[m_series.at(i)].append(t: set);
356 set->setLabelBrush(QColor("black"));
357 set->setPen(QPen(QColor("black"), 0.3));
358 if (sameNumberOfBars) {
359 set->append(values: valueList);
360 } else {
361 QList<qreal> tempList = valueList;
362 for (int k = 0; k < j; k++)
363 tempList.removeLast();
364 set->append(values: tempList);
365 }
366 if (j < m_setCount)
367 m_series.at(i)->append(set);
368 }
369 }
370 for (int i = 0; i < initialSeriesCount; i++)
371 addSeriesToChart(series: m_series.at(i));
372
373 m_chart->setTitle("Chart");
374 if (animation) {
375 m_chart->setAnimationOptions(QChart::SeriesAnimations);
376 m_chart->setAnimationDuration(animationDuration);
377 }
378
379 if (barCategories) {
380 QBarCategoryAxis *barCatAxis = qobject_cast<QBarCategoryAxis *>(object: m_barAxis);
381 QStringList categories;
382 const int count = qMax(a: initialCount, b: visibleCount);
383 for (int i = 0; i < count; i++)
384 categories.append(t: QString::number(i));
385 barCatAxis->setCategories(categories);
386 } else {
387 qobject_cast<QValueAxis *>(object: m_barAxis)->setTickCount(11);
388 }
389
390 if (initialCount > visibleCount)
391 m_barAxis->setRange(min: initialCount - visibleCount, max: initialCount);
392 else
393 m_barAxis->setRange(min: 0, max: visibleCount);
394
395 qreal rangeValue = stacked ? 200.0 : 100.0;
396 m_valueAxis->setRange(min: logarithmic ? 1.0 : (negativeValues || mixedValues) ? -rangeValue : 0.0,
397 max: (!negativeValues || mixedValues) ? rangeValue : 0.0);
398
399 m_chartView->setChart(m_chart);
400}
401
402void ChartWidget::addSeriesToChart(QAbstractBarSeries *series)
403{
404 qDebug() << "Adding series:" << series->name();
405
406 // HACK: Temporarily take the sets out of the series until axes are set.
407 // This is done because added series defaults to a domain that displays all bars, which can
408 // get extremely slow as the bar count increases.
409 QList<QBarSet *> sets = series->barSets();
410 for (auto set : sets)
411 series->take(set);
412
413 m_chart->addSeries(series);
414 if (horizontal) {
415 m_chart->setAxisX(axis: m_valueAxis,series);
416 m_chart->setAxisY(axis: m_barAxis, series);
417 } else {
418 m_chart->setAxisX(axis: m_barAxis, series);
419 m_chart->setAxisY(axis: m_valueAxis, series);
420 }
421
422 series->append(sets);
423}
424

source code of qtcharts/tests/manual/barcharttester/chart-widget.cpp