1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
5
6#include <private/chartdataset_p.h>
7#include <private/chartpresenter_p.h>
8#include <QtCharts/QChart>
9#include <private/qchart_p.h>
10#include <QtCharts/QValueAxis>
11#include <QtCharts/QBarCategoryAxis>
12#include <private/qvalueaxis_p.h>
13#include <QtCharts/QCategoryAxis>
14#include <QtCharts/QColorAxis>
15#include <private/qabstractseries_p.h>
16#include <QtCharts/QAbstractBarSeries>
17#include <QtCharts/QStackedBarSeries>
18#include <QtCharts/QPercentBarSeries>
19#include <QtCharts/QPieSeries>
20#include <private/chartitem_p.h>
21#include <private/xydomain_p.h>
22#include <private/xypolardomain_p.h>
23#include <private/xlogydomain_p.h>
24#include <private/logxydomain_p.h>
25#include <private/logxlogydomain_p.h>
26#include <private/xlogypolardomain_p.h>
27#include <private/logxypolardomain_p.h>
28#include <private/logxlogypolardomain_p.h>
29#include <private/glxyseriesdata_p.h>
30
31#if QT_CONFIG(charts_datetime_axis)
32#include <QtCharts/QDateTimeAxis>
33#endif
34
35QT_BEGIN_NAMESPACE
36
37ChartDataSet::ChartDataSet(QChart *chart)
38 : QObject(chart),
39 m_chart(chart),
40 m_glXYSeriesDataManager(new GLXYSeriesDataManager(this))
41{
42
43}
44
45ChartDataSet::~ChartDataSet()
46{
47 deleteAllSeries();
48 deleteAllAxes();
49}
50
51/*
52 * This method adds series to chartdataset, series ownership is taken from caller.
53 */
54void ChartDataSet::addSeries(QAbstractSeries *series)
55{
56 if (m_seriesList.contains(t: series)) {
57 qWarning() << QObject::tr(s: "Can not add series. Series already on the chart.");
58 return;
59 }
60
61 // Ignore unsupported series added to polar chart
62 if (m_chart && m_chart->chartType() == QChart::ChartTypePolar) {
63 if (!(series->type() == QAbstractSeries::SeriesTypeArea
64 || series->type() == QAbstractSeries::SeriesTypeLine
65 || series->type() == QAbstractSeries::SeriesTypeScatter
66 || series->type() == QAbstractSeries::SeriesTypeSpline)) {
67 qWarning() << QObject::tr(s: "Can not add series. Series type is not supported by a polar chart.");
68 return;
69 }
70 // Disable OpenGL for series in polar charts
71 series->setUseOpenGL(false);
72 series->d_ptr->setDomain(new XYPolarDomain());
73 // Set the correct domain for upper and lower series too
74 if (series->type() == QAbstractSeries::SeriesTypeArea) {
75 foreach (QObject *child, series->children()) {
76 if (qobject_cast<QAbstractSeries *>(object: child)) {
77 QAbstractSeries *childSeries = qobject_cast<QAbstractSeries *>(object: child);
78 childSeries->d_ptr->setDomain(new XYPolarDomain());
79 }
80 }
81 }
82 } else {
83 series->d_ptr->setDomain(new XYDomain());
84 }
85
86 series->d_ptr->initializeDomain();
87 m_seriesList.append(t: series);
88
89 series->setParent(this); // take ownership
90 series->d_ptr->m_chart = m_chart;
91
92 emit seriesAdded(series);
93}
94
95/*
96 * This method adds axis to chartdataset, axis ownership is taken from caller.
97 */
98void ChartDataSet::addAxis(QAbstractAxis *axis, Qt::Alignment alignment)
99{
100 if (m_axisList.contains(t: axis)) {
101 qWarning() << QObject::tr(s: "Can not add axis. Axis already on the chart.");
102 return;
103 }
104
105 axis->d_ptr->setAlignment(alignment);
106
107 if (!axis->alignment()) {
108 qWarning() << QObject::tr(s: "No alignment specified !");
109 return;
110 };
111
112 AbstractDomain *newDomain;
113 if (m_chart && m_chart->chartType() == QChart::ChartTypePolar)
114 newDomain = new XYPolarDomain();
115 else
116 newDomain = new XYDomain();
117
118 QSharedPointer<AbstractDomain> domain(newDomain);
119 axis->d_ptr->initializeDomain(domain: domain.data());
120
121 axis->setParent(this);
122 axis->d_ptr->m_chart = m_chart;
123 m_axisList.append(t: axis);
124
125 emit axisAdded(axis);
126}
127
128/*
129 * This method removes series form chartdataset, series ownership is passed back to caller.
130 */
131void ChartDataSet::removeSeries(QAbstractSeries *series)
132{
133 if (! m_seriesList.contains(t: series)) {
134 qWarning() << QObject::tr(s: "Can not remove series. Series not found on the chart.");
135 return;
136 }
137
138 QList<QAbstractAxis *> axes = series->d_ptr->m_axes;
139
140 foreach (QAbstractAxis *axis, axes) {
141 detachAxis(series, axis);
142 }
143
144 m_seriesList.removeAll(t: series);
145 emit seriesRemoved(series);
146
147 // Reset domain to default
148 series->d_ptr->setDomain(new XYDomain());
149 series->setParent(0);
150 series->d_ptr->m_chart = 0;
151
152 QXYSeries *xySeries = qobject_cast<QXYSeries *>(object: series);
153 if (xySeries)
154 m_glXYSeriesDataManager->removeSeries(series: xySeries);
155}
156
157/*
158 * This method removes axis form chartdataset, series ownership is passed back to caller.
159 */
160void ChartDataSet::removeAxis(QAbstractAxis *axis)
161{
162 if (! m_axisList.contains(t: axis)) {
163 qWarning() << QObject::tr(s: "Can not remove axis. Axis not found on the chart.");
164 return;
165 }
166
167 QList<QAbstractSeries*> series = axis->d_ptr->m_series;
168
169 foreach(QAbstractSeries* s, series) {
170 detachAxis(series: s,axis);
171 }
172
173 emit axisRemoved(axis);
174 m_axisList.removeAll(t: axis);
175
176 axis->setParent(0);
177 axis->d_ptr->m_chart = 0;
178}
179
180/*
181 * This method attaches axis to series, return true if success.
182 */
183bool ChartDataSet::attachAxis(QAbstractSeries *series,QAbstractAxis *axis)
184{
185 Q_ASSERT(axis);
186
187 if (!series)
188 return false;
189
190 QList<QAbstractSeries *> attachedSeriesList = axis->d_ptr->m_series;
191 QList<QAbstractAxis *> attachedAxisList = series->d_ptr->m_axes;
192
193 if (!m_seriesList.contains(t: series)) {
194 qWarning() << QObject::tr(s: "Can not find series on the chart.");
195 return false;
196 }
197
198 if (!m_axisList.contains(t: axis)) {
199 qWarning() << QObject::tr(s: "Can not find axis on the chart.");
200 return false;
201 }
202
203 if (attachedAxisList.contains(t: axis)) {
204 qWarning() << QObject::tr(s: "Axis already attached to series.");
205 return false;
206 }
207
208 if (attachedSeriesList.contains(t: series)) {
209 qWarning() << QObject::tr(s: "Axis already attached to series.");
210 return false;
211 }
212
213 AbstractDomain *domain = series->d_ptr->domain();
214 AbstractDomain::DomainType type = selectDomain(axes: attachedAxisList << axis);
215
216 if (type == AbstractDomain::UndefinedDomain)
217 return false;
218
219 if (domain->type() != type) {
220 AbstractDomain *old = domain;
221 domain = createDomain(type);
222 domain->setRange(minX: old->minX(), maxX: old->maxX(), minY: old->minY(), maxY: old->maxY());
223 // Initialize domain size to old domain size, as it won't get updated
224 // unless geometry changes.
225 domain->setSize(old->size());
226 }
227
228 if (!domain)
229 return false;
230
231 if (!domain->attachAxis(axis))
232 return false;
233
234 domain->blockRangeSignals(block: true);
235 QList<AbstractDomain *> blockedDomains { domain };
236
237 if (domain != series->d_ptr->domain()) {
238 foreach (QAbstractAxis *axis, series->d_ptr->m_axes) {
239 series->d_ptr->domain()->detachAxis(axis);
240 domain->attachAxis(axis);
241 foreach (QAbstractSeries *otherSeries, axis->d_ptr->m_series) {
242 if (otherSeries != series && otherSeries->d_ptr->domain()) {
243 if (!otherSeries->d_ptr->domain()->rangeSignalsBlocked()) {
244 otherSeries->d_ptr->domain()->blockRangeSignals(block: true);
245 blockedDomains << otherSeries->d_ptr->domain();
246 }
247 }
248 }
249 }
250 series->d_ptr->setDomain(domain);
251 series->d_ptr->initializeDomain();
252
253 // Reinitialize domain based on old axes, as the series domain initialization above
254 // has trashed the old ranges, if there were any.
255 for (QAbstractAxis *oldAxis : series->d_ptr->m_axes)
256 oldAxis->d_ptr->initializeDomain(domain);
257 }
258
259 series->d_ptr->m_axes << axis;
260 axis->d_ptr->m_series << series;
261
262 series->d_ptr->initializeAxes();
263 axis->d_ptr->initializeDomain(domain);
264 connect(sender: axis, signal: &QAbstractAxis::reverseChanged, context: this, slot: &ChartDataSet::reverseChanged);
265 foreach (AbstractDomain *blockedDomain, blockedDomains)
266 blockedDomain->blockRangeSignals(block: false);
267
268 return true;
269}
270
271/*
272 * This method detaches axis to series, return true if success.
273 */
274bool ChartDataSet::detachAxis(QAbstractSeries* series,QAbstractAxis *axis)
275{
276 Q_ASSERT(series);
277 Q_ASSERT(axis);
278
279 QList<QAbstractSeries* > attachedSeriesList = axis->d_ptr->m_series;
280 QList<QAbstractAxis* > attachedAxisList = series->d_ptr->m_axes;
281 AbstractDomain* domain = series->d_ptr->domain();
282
283 if (!m_seriesList.contains(t: series)) {
284 qWarning() << QObject::tr(s: "Can not find series on the chart.");
285 return false;
286 }
287
288 if (axis && !m_axisList.contains(t: axis)) {
289 qWarning() << QObject::tr(s: "Can not find axis on the chart.");
290 return false;
291 }
292
293 if (!attachedAxisList.contains(t: axis)) {
294 qWarning() << QObject::tr(s: "Axis not attached to series.");
295 return false;
296 }
297
298 Q_ASSERT(axis->d_ptr->m_series.contains(series));
299
300 domain->detachAxis(axis);
301 series->d_ptr->m_axes.removeAll(t: axis);
302 axis->d_ptr->m_series.removeAll(t: series);
303 disconnect(sender: axis, signal: &QAbstractAxis::reverseChanged, receiver: this, slot: &ChartDataSet::reverseChanged);
304 return true;
305}
306
307void ChartDataSet::createDefaultAxes()
308{
309 if (m_seriesList.isEmpty())
310 return;
311
312 QAbstractAxis::AxisTypes typeX;
313 QAbstractAxis::AxisTypes typeY;
314
315 // Remove possibly existing axes
316 deleteAllAxes();
317
318 Q_ASSERT(m_axisList.isEmpty());
319
320 // Select the required axis x and axis y types based on the types of the current series
321 foreach(QAbstractSeries* s, m_seriesList) {
322 typeX |= s->d_ptr->defaultAxisType(Qt::Horizontal);
323 typeY |= s->d_ptr->defaultAxisType(Qt::Vertical);
324 }
325
326 createAxes(type: typeX, orientation: Qt::Horizontal);
327 createAxes(type: typeY, orientation: Qt::Vertical);
328}
329
330void ChartDataSet::createAxes(QAbstractAxis::AxisTypes type, Qt::Orientation orientation)
331{
332 QAbstractAxis *axis = 0;
333 //decide what axis should be created
334
335 switch (type) {
336 case QAbstractAxis::AxisTypeValue:
337 axis = new QValueAxis(this);
338 break;
339 case QAbstractAxis::AxisTypeBarCategory:
340 axis = new QBarCategoryAxis(this);
341 break;
342 case QAbstractAxis::AxisTypeCategory:
343 axis = new QCategoryAxis(this);
344 break;
345 case QAbstractAxis::AxisTypeColor:
346 axis = new QColorAxis(this);
347 break;
348#if QT_CONFIG(charts_datetime_axis)
349 case QAbstractAxis::AxisTypeDateTime:
350 axis = new QDateTimeAxis(this);
351 break;
352#endif
353 default:
354 axis = 0;
355 break;
356 }
357
358 if (axis) {
359 //create one axis for all
360
361 addAxis(axis,alignment: orientation==Qt::Horizontal?Qt::AlignBottom:Qt::AlignLeft);
362 qreal min = 0;
363 qreal max = 0;
364 findMinMaxForSeries(series: m_seriesList,orientation,min,max);
365 foreach(QAbstractSeries *s, m_seriesList) {
366 attachAxis(series: s,axis);
367 }
368 axis->setRange(min,max);
369 } else {
370 // Create separate axis for each series
371 foreach(QAbstractSeries *s, m_seriesList) {
372 QAbstractAxis *axis = s->d_ptr->createDefaultAxis(orientation);
373 if(axis) {
374 addAxis(axis,alignment: orientation==Qt::Horizontal?Qt::AlignBottom:Qt::AlignLeft);
375 attachAxis(series: s,axis);
376 }
377 }
378 }
379}
380
381void ChartDataSet::findMinMaxForSeries(const QList<QAbstractSeries *> &series,
382 Qt::Orientations orientation, qreal &min, qreal &max)
383{
384 Q_ASSERT(!series.isEmpty());
385
386 AbstractDomain *domain = series.first()->d_ptr->domain();
387 min = (orientation == Qt::Vertical) ? domain->minY() : domain->minX();
388 max = (orientation == Qt::Vertical) ? domain->maxY() : domain->maxX();
389
390 for (int i = 1; i< series.size(); i++) {
391 AbstractDomain *domain = series.at(i)->d_ptr->domain();
392 min = qMin(a: (orientation == Qt::Vertical) ? domain->minY() : domain->minX(), b: min);
393 max = qMax(a: (orientation == Qt::Vertical) ? domain->maxY() : domain->maxX(), b: max);
394 }
395 if (min == max) {
396 min -= 0.5;
397 max += 0.5;
398 }
399}
400
401void ChartDataSet::deleteAllSeries()
402{
403 foreach (QAbstractSeries *s , m_seriesList){
404 removeSeries(series: s);
405 delete s;
406 }
407 Q_ASSERT(m_seriesList.size() == 0);
408}
409
410void ChartDataSet::deleteAllAxes()
411{
412 foreach (QAbstractAxis *a , m_axisList){
413 removeAxis(axis: a);
414 delete a;
415 }
416 Q_ASSERT(m_axisList.size() == 0);
417}
418
419void ChartDataSet::zoomInDomain(const QRectF &rect)
420{
421 QList<AbstractDomain*> domains;
422 foreach(QAbstractSeries *s, m_seriesList) {
423 AbstractDomain* domain = s->d_ptr->domain();
424 s->d_ptr->m_domain->blockRangeSignals(block: true);
425 domains<<domain;
426 }
427
428 foreach(AbstractDomain *domain, domains)
429 domain->zoomIn(rect);
430
431 foreach(AbstractDomain *domain, domains)
432 domain->blockRangeSignals(block: false);
433}
434
435void ChartDataSet::zoomOutDomain(const QRectF &rect)
436{
437 QList<AbstractDomain*> domains;
438 foreach(QAbstractSeries *s, m_seriesList) {
439 AbstractDomain* domain = s->d_ptr->domain();
440 s->d_ptr->m_domain->blockRangeSignals(block: true);
441 domains<<domain;
442 }
443
444 foreach(AbstractDomain *domain, domains)
445 domain->zoomOut(rect);
446
447 foreach(AbstractDomain *domain, domains)
448 domain->blockRangeSignals(block: false);
449}
450
451void ChartDataSet::zoomResetDomain()
452{
453 QList<AbstractDomain*> domains;
454 foreach (QAbstractSeries *s, m_seriesList) {
455 AbstractDomain *domain = s->d_ptr->domain();
456 s->d_ptr->m_domain->blockRangeSignals(block: true);
457 domains << domain;
458 }
459
460 foreach (AbstractDomain *domain, domains)
461 domain->zoomReset();
462
463 foreach (AbstractDomain *domain, domains)
464 domain->blockRangeSignals(block: false);
465}
466
467bool ChartDataSet::isZoomedDomain()
468{
469 foreach (QAbstractSeries *s, m_seriesList) {
470 if (s->d_ptr->domain()->isZoomed())
471 return true;
472 }
473 return false;
474}
475
476void ChartDataSet::scrollDomain(qreal dx, qreal dy)
477{
478 QList<AbstractDomain*> domains;
479 foreach(QAbstractSeries *s, m_seriesList) {
480 AbstractDomain* domain = s->d_ptr->domain();
481 s->d_ptr->m_domain->blockRangeSignals(block: true);
482 domains<<domain;
483 }
484
485 foreach(AbstractDomain *domain, domains)
486 domain->move(dx, dy);
487
488 foreach(AbstractDomain *domain, domains)
489 domain->blockRangeSignals(block: false);
490}
491
492QPointF ChartDataSet::mapToValue(const QPointF &position, QAbstractSeries *series)
493{
494 QPointF point;
495 if (series == 0 && !m_seriesList.isEmpty())
496 series = m_seriesList.first();
497
498 if (series && series->type() == QAbstractSeries::SeriesTypePie)
499 return point;
500
501 if (series && m_seriesList.contains(t: series))
502 point = series->d_ptr->m_domain->calculateDomainPoint(point: position - m_chart->plotArea().topLeft());
503 return point;
504}
505
506QPointF ChartDataSet::mapToPosition(const QPointF &value, QAbstractSeries *series)
507{
508 QPointF point = m_chart->plotArea().topLeft();
509 if (series == 0 && !m_seriesList.isEmpty())
510 series = m_seriesList.first();
511
512 if (series && series->type() == QAbstractSeries::SeriesTypePie)
513 return QPoint(0, 0);
514
515 bool ok;
516 if (series && m_seriesList.contains(t: series))
517 point += series->d_ptr->m_domain->calculateGeometryPoint(point: value, ok);
518 return point;
519}
520
521QList<QAbstractAxis *> ChartDataSet::axes() const
522{
523 return m_axisList;
524}
525
526QList<QAbstractSeries *> ChartDataSet::series() const
527{
528 return m_seriesList;
529}
530
531AbstractDomain::DomainType ChartDataSet::selectDomain(const QList<QAbstractAxis *> &axes)
532{
533 enum Type {
534 Undefined = 0,
535 LogType = 0x1,
536 ValueType = 0x2
537 };
538
539 int horizontal(Undefined);
540 int vertical(Undefined);
541
542 // Assume cartesian chart type, unless chart is set
543 QChart::ChartType chartType(QChart::ChartTypeCartesian);
544 if (m_chart)
545 chartType = m_chart->chartType();
546
547 for (auto *axis : axes) {
548 switch (axis->type()) {
549 case QAbstractAxis::AxisTypeLogValue:
550 if (axis->orientation() == Qt::Horizontal)
551 horizontal |= LogType;
552 if (axis->orientation() == Qt::Vertical)
553 vertical |= LogType;
554 break;
555 case QAbstractAxis::AxisTypeValue:
556 case QAbstractAxis::AxisTypeBarCategory:
557 case QAbstractAxis::AxisTypeCategory:
558 case QAbstractAxis::AxisTypeColor:
559 case QAbstractAxis::AxisTypeDateTime:
560 if (axis->orientation() == Qt::Horizontal)
561 horizontal |= ValueType;
562 if (axis->orientation() == Qt::Vertical)
563 vertical |= ValueType;
564 break;
565 default:
566 qWarning() << "Undefined type";
567 break;
568 }
569 }
570
571 if (vertical == Undefined)
572 vertical = ValueType;
573 if (horizontal == Undefined)
574 horizontal = ValueType;
575
576 if (vertical == ValueType && horizontal == ValueType) {
577 if (chartType == QChart::ChartTypeCartesian)
578 return AbstractDomain::XYDomain;
579 else if (chartType == QChart::ChartTypePolar)
580 return AbstractDomain::XYPolarDomain;
581 }
582
583 if (vertical == LogType && horizontal == ValueType) {
584 if (chartType == QChart::ChartTypeCartesian)
585 return AbstractDomain::XLogYDomain;
586 if (chartType == QChart::ChartTypePolar)
587 return AbstractDomain::XLogYPolarDomain;
588 }
589
590 if (vertical == ValueType && horizontal == LogType) {
591 if (chartType == QChart::ChartTypeCartesian)
592 return AbstractDomain::LogXYDomain;
593 else if (chartType == QChart::ChartTypePolar)
594 return AbstractDomain::LogXYPolarDomain;
595 }
596
597 if (vertical == LogType && horizontal == LogType) {
598 if (chartType == QChart::ChartTypeCartesian)
599 return AbstractDomain::LogXLogYDomain;
600 else if (chartType == QChart::ChartTypePolar)
601 return AbstractDomain::LogXLogYPolarDomain;
602 }
603
604 return AbstractDomain::UndefinedDomain;
605}
606
607//refactor create factory
608AbstractDomain* ChartDataSet::createDomain(AbstractDomain::DomainType type)
609{
610 switch (type)
611 {
612 case AbstractDomain::LogXLogYDomain:
613 return new LogXLogYDomain();
614 case AbstractDomain::XYDomain:
615 return new XYDomain();
616 case AbstractDomain::XLogYDomain:
617 return new XLogYDomain();
618 case AbstractDomain::LogXYDomain:
619 return new LogXYDomain();
620 case AbstractDomain::XYPolarDomain:
621 return new XYPolarDomain();
622 case AbstractDomain::XLogYPolarDomain:
623 return new XLogYPolarDomain();
624 case AbstractDomain::LogXYPolarDomain:
625 return new LogXYPolarDomain();
626 case AbstractDomain::LogXLogYPolarDomain:
627 return new LogXLogYPolarDomain();
628 default:
629 return 0;
630 }
631}
632
633AbstractDomain *ChartDataSet::domainForSeries(QAbstractSeries *series) const
634{
635 return series->d_ptr->domain();
636}
637
638void ChartDataSet::reverseChanged()
639{
640 QAbstractAxis *axis = qobject_cast<QAbstractAxis *>(object: sender());
641 if (axis)
642 m_glXYSeriesDataManager->handleAxisReverseChanged(seriesList: axis->d_ptr->m_series);
643}
644
645QT_END_NAMESPACE
646
647#include "moc_chartdataset_p.cpp"
648

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtcharts/src/charts/chartdataset.cpp