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