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 <QtCharts/QBarCategoryAxis> |
31 | #include <private/qbarcategoryaxis_p.h> |
32 | #include <private/chartbarcategoryaxisx_p.h> |
33 | #include <private/chartbarcategoryaxisy_p.h> |
34 | #include <private/abstractdomain_p.h> |
35 | #include <QtCharts/QChart> |
36 | #include <QtCore/QtMath> |
37 | |
38 | QT_CHARTS_BEGIN_NAMESPACE |
39 | /*! |
40 | \class QBarCategoryAxis |
41 | \inmodule QtCharts |
42 | \brief The QBarCategoryAxis class adds categories to a chart's axes. |
43 | |
44 | QBarCategoryAxis can be set up to show an axis line with tick marks, grid lines, and shades. |
45 | Categories are drawn between the ticks. It can be used also with a line series, as demonstrated |
46 | by the \l {Line and BarChart Example} {Line and BarChart Example}. |
47 | |
48 | The following code illustrates how to use QBarCategoryAxis: |
49 | \code |
50 | QChartView *chartView = new QChartView; |
51 | QBarSeries *series = new QBarSeries; |
52 | // ... |
53 | chartView->chart()->addSeries(series); |
54 | chartView->chart()->createDefaultAxes(); |
55 | |
56 | QBarCategoryAxis *axisX = new QBarCategoryAxis; |
57 | QStringList categories; |
58 | categories << "Jan" << "Feb" << "Mar" << "Apr" << "May" << "Jun"; |
59 | axisX->append(categories); |
60 | axisX->setRange("Feb", "May"); |
61 | chartView->chart()->setAxisX(axisX, series); |
62 | \endcode |
63 | */ |
64 | |
65 | /*! |
66 | \qmltype BarCategoryAxis |
67 | \instantiates QBarCategoryAxis |
68 | \inqmlmodule QtCharts |
69 | |
70 | \inherits AbstractAxis |
71 | |
72 | \brief Adds categories to a chart's axes. |
73 | |
74 | The BarCategoryAxis type can be set up to show an axis line with tick marks, grid lines, and |
75 | shades. Categories are drawn between the ticks. It can be used also with a line series. |
76 | |
77 | The following QML snippet illustrates how to use BarCategoryAxis: |
78 | \code |
79 | ChartView { |
80 | BarCategoryAxis { |
81 | id: categoryAxis |
82 | categories: ["Jan", "Feb", "Mar", "Apr", "May", "Jun" ] |
83 | } |
84 | // Add a few series... |
85 | } |
86 | \endcode |
87 | */ |
88 | |
89 | /*! |
90 | \property QBarCategoryAxis::categories |
91 | \brief The categories of an axis. |
92 | */ |
93 | /*! |
94 | \qmlproperty QStringList BarCategoryAxis::categories |
95 | The categories of an axis. |
96 | */ |
97 | |
98 | /*! |
99 | \property QBarCategoryAxis::min |
100 | \brief The minimum value on the axis. |
101 | */ |
102 | /*! |
103 | \qmlproperty string BarCategoryAxis::min |
104 | The minimum value on the axis. |
105 | */ |
106 | |
107 | /*! |
108 | \property QBarCategoryAxis::max |
109 | \brief The maximum value on the axis. |
110 | */ |
111 | /*! |
112 | \qmlproperty string BarCategoryAxis::max |
113 | The maximum value on the axis. |
114 | */ |
115 | |
116 | /*! |
117 | \property QBarCategoryAxis::count |
118 | \brief The number of categories of an axis. |
119 | */ |
120 | /*! |
121 | \qmlproperty int BarCategoryAxis::count |
122 | The number of categories of an axis. |
123 | */ |
124 | |
125 | /*! |
126 | \fn void QBarCategoryAxis::categoriesChanged() |
127 | This signal is emitted when the categories of the axis change. |
128 | */ |
129 | |
130 | /*! |
131 | \fn void QBarCategoryAxis::minChanged(const QString &min) |
132 | This signal is emitted when the \a min value of the axis changes. |
133 | */ |
134 | |
135 | /*! |
136 | \fn void QBarCategoryAxis::maxChanged(const QString &max) |
137 | This signal is emitted when the \a max value of the axis changes. |
138 | */ |
139 | |
140 | /*! |
141 | \fn void QBarCategoryAxis::countChanged() |
142 | This signal is emitted when the number of categories of an axis changes. |
143 | */ |
144 | |
145 | /*! |
146 | \fn void QBarCategoryAxis::rangeChanged(const QString &min, const QString &max) |
147 | This signal is emitted when \a min or \a max value of the axis changes. |
148 | */ |
149 | |
150 | /*! |
151 | \qmlsignal BarCategoryAxis::rangeChanged(string min, string max) |
152 | This signal is emitted when \a min or \a max value of the axis changes. |
153 | |
154 | The corresponding signal handler is \c onRangeChanged. |
155 | */ |
156 | |
157 | /*! |
158 | \qmlmethod void BarCategoryAxis::clear() |
159 | Removes all categories. Sets the maximum and minimum values of the axis range to QString::null. |
160 | */ |
161 | |
162 | /*! |
163 | Constructs an axis object that is the child of \a parent. |
164 | */ |
165 | QBarCategoryAxis::QBarCategoryAxis(QObject *parent): |
166 | QAbstractAxis(*new QBarCategoryAxisPrivate(this), parent) |
167 | { |
168 | } |
169 | |
170 | /*! |
171 | Destroys the axis object. |
172 | */ |
173 | QBarCategoryAxis::~QBarCategoryAxis() |
174 | { |
175 | Q_D(QBarCategoryAxis); |
176 | if (d->m_chart) |
177 | d->m_chart->removeAxis(axis: this); |
178 | } |
179 | |
180 | /*! |
181 | \internal |
182 | */ |
183 | QBarCategoryAxis::QBarCategoryAxis(QBarCategoryAxisPrivate &d, QObject *parent) |
184 | : QAbstractAxis(d, parent) |
185 | { |
186 | |
187 | } |
188 | |
189 | /*! |
190 | Appends \a categories to an axis. The maximum value on the axis will be changed |
191 | to match the last category in \a categories. If no categories were previously defined, |
192 | the minimum value on the axis will also be changed to match the first category in |
193 | \a categories. |
194 | |
195 | A category has to be a valid QString and it cannot be duplicated. Duplicated |
196 | categories will not be appended. |
197 | */ |
198 | void QBarCategoryAxis::append(const QStringList &categories) |
199 | { |
200 | if (categories.isEmpty()) |
201 | return; |
202 | |
203 | Q_D(QBarCategoryAxis); |
204 | |
205 | int count = d->m_categories.count(); |
206 | |
207 | foreach(QString category, categories) { |
208 | if (!d->m_categories.contains(str: category) && !category.isNull()) { |
209 | d->m_categories.append(t: category); |
210 | } |
211 | } |
212 | |
213 | if (d->m_categories.count() == count) |
214 | return; |
215 | |
216 | if (count == 0) |
217 | setRange(minCategory: d->m_categories.first(), maxCategory: d->m_categories.last()); |
218 | else |
219 | setRange(minCategory: d->m_minCategory, maxCategory: d->m_categories.last()); |
220 | |
221 | emit categoriesChanged(); |
222 | emit countChanged(); |
223 | } |
224 | |
225 | /*! |
226 | Appends \a category to an axis. The maximum value on the axis will be changed |
227 | to match the last \a category. If no categories were previously defined, the minimum |
228 | value on the axis will also be changed to match \a category. |
229 | |
230 | A category has to be a valid QString and it cannot be duplicated. Duplicated |
231 | categories will not be appended. |
232 | */ |
233 | void QBarCategoryAxis::append(const QString &category) |
234 | { |
235 | Q_D(QBarCategoryAxis); |
236 | |
237 | int count = d->m_categories.count(); |
238 | |
239 | if (!d->m_categories.contains(str: category) && !category.isNull()) |
240 | d->m_categories.append(t: category); |
241 | |
242 | if (d->m_categories.count() == count) |
243 | return; |
244 | |
245 | if (count == 0) |
246 | setRange(minCategory: d->m_categories.last(), maxCategory: d->m_categories.last()); |
247 | else |
248 | setRange(minCategory: d->m_minCategory, maxCategory: d->m_categories.last()); |
249 | |
250 | emit categoriesChanged(); |
251 | emit countChanged(); |
252 | } |
253 | |
254 | /*! |
255 | Removes \a category from the axis. Removing a category that currently sets the |
256 | maximum or minimum value on the axis will affect the axis range. |
257 | */ |
258 | void QBarCategoryAxis::remove(const QString &category) |
259 | { |
260 | Q_D(QBarCategoryAxis); |
261 | |
262 | if (d->m_categories.contains(str: category)) { |
263 | d->m_categories.removeAt(i: d->m_categories.indexOf(t: category)); |
264 | if (!d->m_categories.isEmpty()) { |
265 | if (d->m_minCategory == category) { |
266 | setRange(minCategory: d->m_categories.first(), maxCategory: d->m_maxCategory); |
267 | } else if (d->m_maxCategory == category) { |
268 | setRange(minCategory: d->m_minCategory, maxCategory: d->m_categories.last()); |
269 | } else { |
270 | d->updateCategoryDomain(); |
271 | } |
272 | } else { |
273 | setRange(minCategory: QString(), maxCategory: QString()); |
274 | } |
275 | emit categoriesChanged(); |
276 | emit countChanged(); |
277 | } |
278 | } |
279 | |
280 | /*! |
281 | Inserts \a category to the axis at \a index. \a category has to be a valid QString |
282 | and it cannot be duplicated. If \a category is prepended or appended to other |
283 | categories, the minimum and maximum values on the axis are updated accordingly. |
284 | */ |
285 | void QBarCategoryAxis::insert(int index, const QString &category) |
286 | { |
287 | Q_D(QBarCategoryAxis); |
288 | |
289 | int count = d->m_categories.count(); |
290 | |
291 | if (!d->m_categories.contains(str: category) && !category.isNull()) |
292 | d->m_categories.insert(i: index, t: category); |
293 | |
294 | if (d->m_categories.count() == count) |
295 | return; |
296 | |
297 | if (count == 0) { |
298 | setRange(minCategory: d->m_categories.first(), maxCategory: d->m_categories.first()); |
299 | } else if (index == 0) { |
300 | setRange(minCategory: d->m_categories.first(), maxCategory: d->m_maxCategory); |
301 | } else if (index == count) { |
302 | setRange(minCategory: d->m_minCategory, maxCategory: d->m_categories.last()); |
303 | } else { |
304 | d->updateCategoryDomain(); |
305 | } |
306 | |
307 | emit categoriesChanged(); |
308 | emit countChanged(); |
309 | } |
310 | |
311 | /*! |
312 | Replaces \a oldCategory with \a newCategory. If \a oldCategory does not exist on the axis, |
313 | nothing is done. \a newCategory has to be a valid QString and it cannot be duplicated. If |
314 | the minimum or maximum category is replaced, the minimum and maximum values on the axis are |
315 | updated accordingly. |
316 | */ |
317 | void QBarCategoryAxis::replace(const QString &oldCategory, const QString &newCategory) |
318 | { |
319 | Q_D(QBarCategoryAxis); |
320 | |
321 | int pos = d->m_categories.indexOf(t: oldCategory); |
322 | |
323 | if (pos != -1 && !d->m_categories.contains(str: newCategory) && !newCategory.isNull()) { |
324 | d->m_categories.replace(i: pos, t: newCategory); |
325 | if (d->m_minCategory == oldCategory) |
326 | setRange(minCategory: newCategory, maxCategory: d->m_maxCategory); |
327 | else if (d->m_maxCategory == oldCategory) |
328 | setRange(minCategory: d->m_minCategory, maxCategory: newCategory); |
329 | |
330 | emit categoriesChanged(); |
331 | emit countChanged(); |
332 | } |
333 | } |
334 | |
335 | /*! |
336 | Removes all categories. Sets the maximum and minimum values of the axis range to QString::null. |
337 | */ |
338 | void QBarCategoryAxis::clear() |
339 | { |
340 | Q_D(QBarCategoryAxis); |
341 | d->m_categories.clear(); |
342 | setRange(minCategory: QString(), maxCategory: QString()); |
343 | emit categoriesChanged(); |
344 | emit countChanged(); |
345 | } |
346 | |
347 | /*! |
348 | Sets \a categories and discards the old ones. The axis range is adjusted to match the |
349 | first and last category in \a categories. |
350 | |
351 | A category has to be a valid QString and it cannot be duplicated. |
352 | */ |
353 | void QBarCategoryAxis::setCategories(const QStringList &categories) |
354 | { |
355 | Q_D(QBarCategoryAxis); |
356 | d->m_categories.clear(); |
357 | d->m_minCategory = QString(); |
358 | d->m_maxCategory = QString(); |
359 | d->m_min = 0; |
360 | d->m_max = 0; |
361 | d->m_count = 0; |
362 | append(categories); |
363 | } |
364 | |
365 | /*! |
366 | Returns categories. |
367 | */ |
368 | QStringList QBarCategoryAxis::categories() |
369 | { |
370 | Q_D(QBarCategoryAxis); |
371 | return d->m_categories; |
372 | } |
373 | |
374 | /*! |
375 | Returns the number of categories. |
376 | */ |
377 | int QBarCategoryAxis::count() const |
378 | { |
379 | Q_D(const QBarCategoryAxis); |
380 | return d->m_categories.count(); |
381 | } |
382 | |
383 | /*! |
384 | Returns the category at \a index. The index must be valid. |
385 | */ |
386 | QString QBarCategoryAxis::at(int index) const |
387 | { |
388 | Q_D(const QBarCategoryAxis); |
389 | return d->m_categories.at(i: index); |
390 | } |
391 | |
392 | /*! |
393 | Sets the minimum category to \a min. |
394 | */ |
395 | void QBarCategoryAxis::setMin(const QString &min) |
396 | { |
397 | Q_D(QBarCategoryAxis); |
398 | d->setRange(minCategory: min, maxCategory: d->m_maxCategory); |
399 | } |
400 | |
401 | /*! |
402 | Returns the minimum category. |
403 | */ |
404 | QString QBarCategoryAxis::min() const |
405 | { |
406 | Q_D(const QBarCategoryAxis); |
407 | return d->m_minCategory; |
408 | } |
409 | |
410 | /*! |
411 | Sets the maximum category to \a max. |
412 | */ |
413 | void QBarCategoryAxis::setMax(const QString &max) |
414 | { |
415 | Q_D(QBarCategoryAxis); |
416 | d->setRange(minCategory: d->m_minCategory, maxCategory: max); |
417 | } |
418 | |
419 | /*! |
420 | Returns the maximum category. |
421 | */ |
422 | QString QBarCategoryAxis::max() const |
423 | { |
424 | Q_D(const QBarCategoryAxis); |
425 | return d->m_maxCategory; |
426 | } |
427 | |
428 | /*! |
429 | Sets the axis range from \a minCategory to \a maxCategory. |
430 | */ |
431 | void QBarCategoryAxis::setRange(const QString &minCategory, const QString &maxCategory) |
432 | { |
433 | Q_D(QBarCategoryAxis); |
434 | d->setRange(minCategory,maxCategory); |
435 | } |
436 | |
437 | /*! |
438 | Returns the type of the axis. |
439 | */ |
440 | QAbstractAxis::AxisType QBarCategoryAxis::type() const |
441 | { |
442 | return AxisTypeBarCategory; |
443 | } |
444 | |
445 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
446 | |
447 | QBarCategoryAxisPrivate::QBarCategoryAxisPrivate(QBarCategoryAxis *q) |
448 | : QAbstractAxisPrivate(q), |
449 | m_min(0.0), |
450 | m_max(0.0), |
451 | m_count(0) |
452 | { |
453 | |
454 | } |
455 | |
456 | QBarCategoryAxisPrivate::~QBarCategoryAxisPrivate() |
457 | { |
458 | |
459 | } |
460 | |
461 | void QBarCategoryAxisPrivate::setMin(const QVariant &min) |
462 | { |
463 | setRange(min, max: m_maxCategory); |
464 | } |
465 | |
466 | void QBarCategoryAxisPrivate::setMax(const QVariant &max) |
467 | { |
468 | setRange(min: m_minCategory, max); |
469 | } |
470 | |
471 | void QBarCategoryAxisPrivate::setRange(const QVariant &min, const QVariant &max) |
472 | { |
473 | QString value1 = min.toString(); |
474 | QString value2 = max.toString(); |
475 | setRange(minCategory: value1, maxCategory: value2); |
476 | } |
477 | |
478 | void QBarCategoryAxisPrivate::setRange(qreal min, qreal max) |
479 | { |
480 | Q_Q(QBarCategoryAxis); |
481 | |
482 | bool categoryChanged = false; |
483 | bool changed = false; |
484 | |
485 | if (min > max) |
486 | return; |
487 | |
488 | if (!qFuzzyIsNull(d: m_min - min)) { |
489 | m_min = min; |
490 | changed = true; |
491 | |
492 | int imin = m_min + 0.5; |
493 | if (imin >= 0 && imin < m_categories.count()) { |
494 | QString minCategory = m_categories.at(i: imin); |
495 | if (m_minCategory != minCategory && !minCategory.isEmpty()) { |
496 | m_minCategory = minCategory; |
497 | categoryChanged = true; |
498 | emit q->minChanged(min: minCategory); |
499 | } |
500 | } |
501 | |
502 | } |
503 | |
504 | if (!qFuzzyIsNull(d: m_max - max)) { |
505 | m_max = max; |
506 | changed = true; |
507 | |
508 | int imax = m_max - 0.5; |
509 | if (imax >= 0 && imax < m_categories.count()) { |
510 | QString maxCategory = m_categories.at(i: imax); |
511 | if (m_maxCategory != maxCategory && !maxCategory.isEmpty()) { |
512 | m_maxCategory = maxCategory; |
513 | categoryChanged = true; |
514 | emit q->maxChanged(max: maxCategory); |
515 | } |
516 | } |
517 | } |
518 | |
519 | if (categoryChanged){ |
520 | emit q->rangeChanged(min: m_minCategory, max: m_maxCategory); |
521 | } |
522 | |
523 | if (changed) { |
524 | emit rangeChanged(min: m_min,max: m_max); |
525 | } |
526 | } |
527 | |
528 | void QBarCategoryAxisPrivate::setRange(const QString &minCategory, const QString &maxCategory) |
529 | { |
530 | Q_Q(QBarCategoryAxis); |
531 | bool changed = false; |
532 | |
533 | //special case in case or clearing all categories |
534 | if (minCategory.isNull() && maxCategory.isNull()) { |
535 | m_minCategory = minCategory; |
536 | m_maxCategory = maxCategory; |
537 | m_min = 0; |
538 | m_max = 0; |
539 | m_count = 0; |
540 | emit q->minChanged(min: minCategory); |
541 | emit q->maxChanged(max: maxCategory); |
542 | emit q->rangeChanged(min: m_minCategory, max: m_maxCategory); |
543 | emit rangeChanged(min: m_min,max: m_max); |
544 | return; |
545 | } |
546 | |
547 | if (m_categories.indexOf(t: maxCategory) < m_categories.indexOf(t: minCategory)) |
548 | return; |
549 | |
550 | if (!minCategory.isNull() && (m_minCategory != minCategory || m_minCategory.isNull()) |
551 | && m_categories.contains(str: minCategory)) { |
552 | m_minCategory = minCategory; |
553 | m_min = m_categories.indexOf(t: m_minCategory) - 0.5; |
554 | changed = true; |
555 | emit q->minChanged(min: minCategory); |
556 | } |
557 | |
558 | if (!maxCategory.isNull() && (m_maxCategory != maxCategory || m_maxCategory.isNull()) |
559 | && m_categories.contains(str: maxCategory)) { |
560 | m_maxCategory = maxCategory; |
561 | m_max = m_categories.indexOf(t: m_maxCategory) + 0.5; |
562 | changed = true; |
563 | emit q->maxChanged(max: maxCategory); |
564 | } |
565 | |
566 | if (changed) { |
567 | m_count = m_max - m_min; |
568 | emit q->rangeChanged(min: m_minCategory, max: m_maxCategory); |
569 | emit rangeChanged(min: m_min,max: m_max); |
570 | } |
571 | } |
572 | |
573 | void QBarCategoryAxisPrivate::initializeGraphics(QGraphicsItem* parent) |
574 | { |
575 | Q_Q(QBarCategoryAxis); |
576 | ChartAxisElement* axis(0); |
577 | if (orientation() == Qt::Vertical) |
578 | axis = new ChartBarCategoryAxisY(q,parent); |
579 | if (orientation() == Qt::Horizontal) |
580 | axis = new ChartBarCategoryAxisX(q,parent); |
581 | |
582 | m_item.reset(other: axis); |
583 | QAbstractAxisPrivate::initializeGraphics(parent); |
584 | } |
585 | |
586 | void QBarCategoryAxisPrivate::updateCategoryDomain() |
587 | { |
588 | bool changed = false; |
589 | |
590 | qreal tmpMin = m_categories.indexOf(t: m_minCategory) - 0.5; |
591 | if (!qFuzzyIsNull(d: m_min - tmpMin)) { |
592 | m_min = tmpMin; |
593 | changed = true; |
594 | } |
595 | qreal tmpMax = m_categories.indexOf(t: m_maxCategory) + 0.5; |
596 | if (!qFuzzyIsNull(d: m_max - tmpMax)) { |
597 | m_max = tmpMax; |
598 | changed = true; |
599 | } |
600 | m_count = m_max - m_min; |
601 | |
602 | if (changed) |
603 | emit rangeChanged(min: m_min,max: m_max); |
604 | } |
605 | |
606 | |
607 | void QBarCategoryAxisPrivate::initializeDomain(AbstractDomain *domain) |
608 | { |
609 | Q_Q(QBarCategoryAxis); |
610 | if (m_max == m_min) { |
611 | int min; |
612 | int max; |
613 | if (orientation() == Qt::Vertical) { |
614 | min = domain->minY() + 0.5; |
615 | max = domain->maxY() - 0.5; |
616 | } else { |
617 | min = domain->minX() + 0.5; |
618 | max = domain->maxX() - 0.5; |
619 | } |
620 | |
621 | if (min > 0 && min < m_categories.count() && max > 0 && max < m_categories.count()) |
622 | q->setRange(minCategory: m_categories.at(i: min), maxCategory: m_categories.at(i: max)); |
623 | } else { |
624 | if (orientation() == Qt::Vertical) |
625 | domain->setRangeY(min: m_min, max: m_max); |
626 | else |
627 | domain->setRangeX(min: m_min, max: m_max); |
628 | } |
629 | } |
630 | |
631 | QT_CHARTS_END_NAMESPACE |
632 | |
633 | #include "moc_qbarcategoryaxis.cpp" |
634 | #include "moc_qbarcategoryaxis_p.cpp" |
635 | |