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