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

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