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

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