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
14QT_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*/
141QBarCategoryAxis::QBarCategoryAxis(QObject *parent):
142 QAbstractAxis(*new QBarCategoryAxisPrivate(this), parent)
143{
144}
145
146/*!
147 Destroys the axis object.
148*/
149QBarCategoryAxis::~QBarCategoryAxis()
150{
151 Q_D(QBarCategoryAxis);
152 if (d->m_chart)
153 d->m_chart->removeAxis(axis: this);
154}
155
156/*!
157 \internal
158*/
159QBarCategoryAxis::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*/
174void 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*/
209void 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*/
234void 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*/
261void 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*/
293void 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 */
314void 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*/
329void 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*/
344QStringList QBarCategoryAxis::categories()
345{
346 Q_D(QBarCategoryAxis);
347 return d->m_categories;
348}
349
350/*!
351 Returns the number of categories.
352 */
353int 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*/
362QString 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*/
371void 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*/
380QString 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*/
389void 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*/
398QString 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*/
407void 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*/
416QAbstractAxis::AxisType QBarCategoryAxis::type() const
417{
418 return AxisTypeBarCategory;
419}
420
421//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
422
423QBarCategoryAxisPrivate::QBarCategoryAxisPrivate(QBarCategoryAxis *q)
424 : QAbstractAxisPrivate(q),
425 m_min(0.0),
426 m_max(0.0),
427 m_count(0)
428{
429
430}
431
432QBarCategoryAxisPrivate::~QBarCategoryAxisPrivate()
433{
434
435}
436
437void QBarCategoryAxisPrivate::setMin(const QVariant &min)
438{
439 setRange(min, max: m_maxCategory);
440}
441
442void QBarCategoryAxisPrivate::setMax(const QVariant &max)
443{
444 setRange(min: m_minCategory, max);
445}
446
447void 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
454void 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
504void 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
549void 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
562void 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
583void 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
607QT_END_NAMESPACE
608
609#include "moc_qbarcategoryaxis.cpp"
610#include "moc_qbarcategoryaxis_p.cpp"
611

source code of qtcharts/src/charts/axis/barcategoryaxis/qbarcategoryaxis.cpp