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/QCategoryAxis> |
31 | #include <private/qcategoryaxis_p.h> |
32 | #include <private/chartcategoryaxisx_p.h> |
33 | #include <private/chartcategoryaxisy_p.h> |
34 | #include <private/polarchartcategoryaxisangular_p.h> |
35 | #include <private/polarchartcategoryaxisradial_p.h> |
36 | #include <QtCharts/QChart> |
37 | #include <QtCore/QtMath> |
38 | #include <QtCore/QDebug> |
39 | |
40 | QT_CHARTS_BEGIN_NAMESPACE |
41 | /*! |
42 | \class QCategoryAxis |
43 | \inmodule QtCharts |
44 | \brief The QCategoryAxis class places named ranges on the axis. |
45 | |
46 | This class can be used to explain the underlying data by adding labeled categories. |
47 | Unlike QBarCategoryAxis, QCategoryAxis allows the widths of the category ranges to |
48 | be specified freely. |
49 | |
50 | Example code on how to use QCategoryAxis: |
51 | \image api_category_axis.png |
52 | \code |
53 | QChartView *chartView = new QChartView; |
54 | QLineSeries *series = new QLineSeries; |
55 | // ... |
56 | chartView->chart()->addSeries(series); |
57 | |
58 | QCategoryAxis *axisY = new QCategoryAxis; |
59 | axisY->setMin(0); |
60 | axisY->setMax(52); |
61 | axisY->setStartValue(15); |
62 | axisY->append("First", 20); |
63 | axisY->append("Second", 37); |
64 | axisY->append("Third", 52); |
65 | chartView->chart()->setAxisY(axisY, series); |
66 | \endcode |
67 | */ |
68 | /*! |
69 | \qmltype CategoryAxis |
70 | \instantiates QCategoryAxis |
71 | \inqmlmodule QtCharts |
72 | |
73 | \inherits AbstractAxis |
74 | \brief CategoryAxis places named ranges on the axis. |
75 | |
76 | This type can be used to explain the underlying data by adding labeled categories. |
77 | The widths of the category ranges can be specified freely. |
78 | |
79 | For example: |
80 | \image examples_qmlaxes3.png |
81 | \snippet qmlaxes/qml/qmlaxes/View3.qml 1 |
82 | */ |
83 | |
84 | /*! |
85 | \property QCategoryAxis::startValue |
86 | \brief The low end of the first category on the axis. |
87 | */ |
88 | /*! |
89 | \qmlproperty int CategoryAxis::startValue |
90 | The low end of the first category on the axis. |
91 | */ |
92 | |
93 | /*! |
94 | \property QCategoryAxis::count |
95 | \brief The number of categories. |
96 | */ |
97 | /*! |
98 | \qmlproperty int CategoryAxis::count |
99 | The number of categories. |
100 | */ |
101 | |
102 | /*! |
103 | \property QCategoryAxis::categoriesLabels |
104 | \brief The category labels as a string list. |
105 | */ |
106 | /*! |
107 | \qmlproperty StringList CategoryAxis::categoriesLabels |
108 | The category labels as a list of strings. |
109 | */ |
110 | |
111 | /*! |
112 | \fn void QCategoryAxis::categoriesChanged() |
113 | This signal is emitted when the categories of the axis change. |
114 | */ |
115 | |
116 | /*! |
117 | \enum QCategoryAxis::AxisLabelsPosition |
118 | |
119 | This enum describes the position of the category labels. |
120 | |
121 | \value AxisLabelsPositionCenter Labels are centered to category. |
122 | \value AxisLabelsPositionOnValue Labels are positioned to the high end limit of the category. |
123 | */ |
124 | /*! |
125 | \property QCategoryAxis::labelsPosition |
126 | \brief The position of the category labels. The labels in the beginning and in the end of the |
127 | axes may overlap other axes' labels when positioned on value. |
128 | */ |
129 | /*! |
130 | \qmlproperty enumeration CategoryAxis::labelsPosition |
131 | |
132 | The position of the category labels. The labels in the beginning and in the end of the |
133 | axes may overlap other axes' labels when positioned on value. |
134 | |
135 | \value CategoryAxis.AxisLabelsPositionCenter |
136 | Labels are centered to category. |
137 | \value CategoryAxis.AxisLabelsPositionOnValue |
138 | Labels are positioned to the high end limit of the category. |
139 | */ |
140 | |
141 | |
142 | /*! |
143 | Constructs an axis object that is a child of \a parent. |
144 | */ |
145 | QCategoryAxis::QCategoryAxis(QObject *parent): |
146 | QValueAxis(*new QCategoryAxisPrivate(this), parent) |
147 | { |
148 | } |
149 | |
150 | /*! |
151 | Destroys the object. |
152 | */ |
153 | QCategoryAxis::~QCategoryAxis() |
154 | { |
155 | Q_D(QCategoryAxis); |
156 | if (d->m_chart) |
157 | d->m_chart->removeAxis(axis: this); |
158 | } |
159 | |
160 | /*! |
161 | \internal |
162 | */ |
163 | QCategoryAxis::QCategoryAxis(QCategoryAxisPrivate &d, QObject *parent): QValueAxis(d, parent) |
164 | { |
165 | |
166 | } |
167 | |
168 | /*! |
169 | \qmlmethod CategoryAxis::append(string label, real endValue) |
170 | Appends a new category to the axis with the label \a label. A category label has to be unique. |
171 | \a endValue specifies the high end limit of the category. |
172 | It has to be greater than the high end limit of the previous category. |
173 | Otherwise the method returns without adding a new category. |
174 | */ |
175 | /*! |
176 | Appends a new category to the axis with the label \a categoryLabel. |
177 | A category label has to be unique. |
178 | \a categoryEndValue specifies the high end limit of the category. |
179 | It has to be greater than the high end limit of the previous category. |
180 | Otherwise the method returns without adding a new category. |
181 | */ |
182 | void QCategoryAxis::append(const QString &categoryLabel, qreal categoryEndValue) |
183 | { |
184 | Q_D(QCategoryAxis); |
185 | |
186 | if (!d->m_categories.contains(str: categoryLabel)) { |
187 | if (d->m_categories.isEmpty()) { |
188 | Range range(d->m_categoryMinimum, categoryEndValue); |
189 | d->m_categoriesMap.insert(akey: categoryLabel, avalue: range); |
190 | d->m_categories.append(t: categoryLabel); |
191 | emit categoriesChanged(); |
192 | } else if (categoryEndValue > endValue(categoryLabel: d->m_categories.last())) { |
193 | Range previousRange = d->m_categoriesMap.value(akey: d->m_categories.last()); |
194 | d->m_categoriesMap.insert(akey: categoryLabel, avalue: Range(previousRange.second, categoryEndValue)); |
195 | d->m_categories.append(t: categoryLabel); |
196 | emit categoriesChanged(); |
197 | } |
198 | } |
199 | } |
200 | |
201 | /*! |
202 | Sets \a min to be the low end limit of the first category on the axis. |
203 | If categories have already been added to the axis, the passed value must be less |
204 | than the high end value of the already defined first category range. |
205 | Otherwise nothing is done. |
206 | */ |
207 | void QCategoryAxis::setStartValue(qreal min) |
208 | { |
209 | Q_D(QCategoryAxis); |
210 | if (d->m_categories.isEmpty()) { |
211 | d->m_categoryMinimum = min; |
212 | emit categoriesChanged(); |
213 | } else { |
214 | Range range = d->m_categoriesMap.value(akey: d->m_categories.first()); |
215 | if (min < range.second) { |
216 | d->m_categoriesMap.insert(akey: d->m_categories.first(), avalue: Range(min, range.second)); |
217 | emit categoriesChanged(); |
218 | } |
219 | } |
220 | } |
221 | |
222 | /*! |
223 | Returns the low end limit of the category specified by \a categoryLabel. |
224 | */ |
225 | qreal QCategoryAxis::startValue(const QString &categoryLabel) const |
226 | { |
227 | Q_D(const QCategoryAxis); |
228 | if (categoryLabel.isEmpty()) |
229 | return d->m_categoryMinimum; |
230 | return d->m_categoriesMap.value(akey: categoryLabel).first; |
231 | } |
232 | |
233 | /*! |
234 | Returns the high end limit of the category specified by \a categoryLabel. |
235 | */ |
236 | qreal QCategoryAxis::endValue(const QString &categoryLabel) const |
237 | { |
238 | Q_D(const QCategoryAxis); |
239 | return d->m_categoriesMap.value(akey: categoryLabel).second; |
240 | } |
241 | |
242 | /*! |
243 | \qmlmethod CategoryAxis::remove(string label) |
244 | Removes a category specified by the label \a label from the axis. |
245 | */ |
246 | /*! |
247 | Removes a category specified by the label \a categoryLabel from the axis. |
248 | */ |
249 | void QCategoryAxis::remove(const QString &categoryLabel) |
250 | { |
251 | Q_D(QCategoryAxis); |
252 | int labelIndex = d->m_categories.indexOf(t: categoryLabel); |
253 | |
254 | // check if such label exists |
255 | if (labelIndex != -1) { |
256 | d->m_categories.removeAt(i: labelIndex); |
257 | d->m_categoriesMap.remove(akey: categoryLabel); |
258 | |
259 | // the range of the interval that follows (if exists) needs to be updated |
260 | if (labelIndex < d->m_categories.count()) { |
261 | QString label = d->m_categories.at(i: labelIndex); |
262 | Range range = d->m_categoriesMap.value(akey: label); |
263 | |
264 | // set the range |
265 | if (labelIndex == 0) { |
266 | range.first = d->m_categoryMinimum; |
267 | d->m_categoriesMap.insert(akey: label, avalue: range); |
268 | } else { |
269 | range.first = d->m_categoriesMap.value(akey: d->m_categories.at(i: labelIndex - 1)).second; |
270 | d->m_categoriesMap.insert(akey: label, avalue: range); |
271 | } |
272 | } |
273 | emit categoriesChanged(); |
274 | } |
275 | } |
276 | |
277 | /*! |
278 | \qmlmethod CategoryAxis::replace(string oldLabel, string newLabel) |
279 | Replaces an existing category label specified by \a oldLabel with \a newLabel. |
280 | If the old label does not exist, the method returns without making any changes. |
281 | */ |
282 | /*! |
283 | Replaces an existing category label specified by \a oldLabel with \a newLabel. |
284 | If the old label does not exist, the method returns without making any changes. |
285 | */ |
286 | void QCategoryAxis::replaceLabel(const QString &oldLabel, const QString &newLabel) |
287 | { |
288 | Q_D(QCategoryAxis); |
289 | int labelIndex = d->m_categories.indexOf(t: oldLabel); |
290 | |
291 | // check if such label exists |
292 | if (labelIndex != -1) { |
293 | d->m_categories.replace(i: labelIndex, t: newLabel); |
294 | Range range = d->m_categoriesMap.value(akey: oldLabel); |
295 | d->m_categoriesMap.remove(akey: oldLabel); |
296 | d->m_categoriesMap.insert(akey: newLabel, avalue: range); |
297 | emit categoriesChanged(); |
298 | } |
299 | } |
300 | |
301 | /*! |
302 | Returns the list of the categories' labels. |
303 | */ |
304 | QStringList QCategoryAxis::categoriesLabels() |
305 | { |
306 | Q_D(QCategoryAxis); |
307 | return d->m_categories; |
308 | } |
309 | |
310 | /*! |
311 | Returns the number of categories. |
312 | */ |
313 | int QCategoryAxis::count() const |
314 | { |
315 | Q_D(const QCategoryAxis); |
316 | return d->m_categories.count(); |
317 | } |
318 | |
319 | /*! |
320 | Returns the type of the axis. |
321 | */ |
322 | QAbstractAxis::AxisType QCategoryAxis::type() const |
323 | { |
324 | return QAbstractAxis::AxisTypeCategory; |
325 | } |
326 | |
327 | void QCategoryAxis::setLabelsPosition(QCategoryAxis::AxisLabelsPosition position) |
328 | { |
329 | Q_D(QCategoryAxis); |
330 | if (d->m_labelsPosition != position) { |
331 | d->m_labelsPosition = position; |
332 | emit labelsPositionChanged(position); |
333 | } |
334 | } |
335 | |
336 | QCategoryAxis::AxisLabelsPosition QCategoryAxis::labelsPosition() const |
337 | { |
338 | Q_D(const QCategoryAxis); |
339 | return d->m_labelsPosition; |
340 | } |
341 | |
342 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
343 | |
344 | QCategoryAxisPrivate::QCategoryAxisPrivate(QCategoryAxis *q) |
345 | : QValueAxisPrivate(q), |
346 | m_categoryMinimum(0), |
347 | m_labelsPosition(QCategoryAxis::AxisLabelsPositionCenter) |
348 | { |
349 | |
350 | } |
351 | |
352 | QCategoryAxisPrivate::~QCategoryAxisPrivate() |
353 | { |
354 | |
355 | } |
356 | |
357 | int QCategoryAxisPrivate::ticksCount() const |
358 | { |
359 | return m_categories.count() + 1; |
360 | } |
361 | |
362 | void QCategoryAxisPrivate::initializeGraphics(QGraphicsItem *parent) |
363 | { |
364 | Q_Q(QCategoryAxis); |
365 | ChartAxisElement *axis(0); |
366 | if (m_chart->chartType() == QChart::ChartTypeCartesian) { |
367 | if (orientation() == Qt::Vertical) |
368 | axis = new ChartCategoryAxisY(q,parent); |
369 | else if (orientation() == Qt::Horizontal) |
370 | axis = new ChartCategoryAxisX(q,parent); |
371 | } |
372 | |
373 | if (m_chart->chartType() == QChart::ChartTypePolar) { |
374 | if (orientation() == Qt::Vertical) |
375 | axis = new PolarChartCategoryAxisRadial(q, parent); |
376 | if (orientation() == Qt::Horizontal) |
377 | axis = new PolarChartCategoryAxisAngular(q, parent); |
378 | } |
379 | |
380 | m_item.reset(other: axis); |
381 | QAbstractAxisPrivate::initializeGraphics(parent); |
382 | } |
383 | |
384 | QT_CHARTS_END_NAMESPACE |
385 | |
386 | #include "moc_qcategoryaxis.cpp" |
387 | #include "moc_qcategoryaxis_p.cpp" |
388 | |