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 "declarativebarseries_p.h"
31 #include "declarativeboxplotseries_p.h"
32 #include <QtCharts/QBoxSet>
33 #include <QtCharts/QHBoxPlotModelMapper>
34 #include <QtCharts/QVBoxPlotModelMapper>
35
36 QT_CHARTS_BEGIN_NAMESPACE
37
38 /*!
39 \qmltype BoxSet
40 \instantiates QBoxSet
41 \inqmlmodule QtCharts
42
43 \brief Represents one item in a box-and-whiskers chart.
44
45 A box-and-whiskers item is a graphical representation of a range and three median values
46 that is constructed from five different values. There are two ways to specify the values.
47 The first one is by using a constructor or the append() method. The values have to be
48 specified in the following order: lower extreme, lower quartile, median, upper quartile,
49 and upper extreme.
50
51 The second way is to create an empty BoxSet instance and specify the values using the
52 setValue() method.
53
54 \sa BoxPlotSeries
55 */
56
57 /*!
58 \qmlproperty list BoxSet::values
59 The values of the box-and-whiskers item. The following enumerations can be
60 used as indexes when accessing the list of values:
61
62 \value BoxSet.LowerExtreme The smallest value of the box-and-whiskers item.
63 \value BoxSet.LowerQuartile The median value of the lower half of the box-and-whiskers item.
64 \value BoxSet.Median The median value of the box-and-whiskers item.
65 \value BoxSet.UpperQuartile The median value of the upper half of the box-and-whiskers item.
66 \value BoxSet.UpperExtreme The largest value of the box-and-whiskers item.
67
68 \sa at(), setValue()
69 */
70 /*!
71 \qmlproperty string BoxSet::label
72 The label of the category of the box-and-whiskers item.
73 */
74 /*!
75 \qmlproperty int BoxSet::count
76 The number of values of the box-and-whiskers item.
77 */
78
79 /*!
80 \qmlproperty string BoxSet::brushFilename
81 The name of the file used as a brush for the box-and-whiskers item.
82 */
83
84 /*!
85 \qmlmethod void BoxSet::at(int index)
86 Returns the value in the position specified by \a index.
87 */
88 /*!
89 \qmlmethod void BoxSet::append(qreal value)
90 Appends the new value specified by \a value to the end of the box-and-whiskers item.
91 */
92 /*!
93 \qmlmethod void BoxSet::clear()
94 Sets all the values of the box-and-whiskers item to 0.
95 */
96 /*!
97 \qmlmethod void BoxSet::setValue(int index, qreal value)
98 Sets the value specified by \a value in the position specified by \a index.
99 */
100 /*!
101 \qmlsignal BoxSet::clicked()
102 This signal is emitted when the user clicks a box-and-whiskers item in the chart.
103
104 The corresponding signal handler is \c onClicked().
105 */
106 /*!
107 \qmlsignal BoxSet::pressed()
108 This signal is emitted when the user clicks a box-and-whiskers item in the chart
109 and holds down the mouse button.
110
111 The corresponding signal handler is \c onPressed.
112 */
113 /*!
114 \qmlsignal BoxSet::released()
115 This signal is emitted when the user releases the mouse press on a box-and-whiskers item.
116
117 The corresponding signal handler is \c onReleased().
118 */
119 /*!
120 \qmlsignal BoxSet::doubleClicked()
121 This signal is emitted when the user double-clicks a box-and-whiskers item.
122
123 The corresponding signal handler is \c onDoubleClicked().
124 */
125 /*!
126 \qmlsignal BoxSet::hovered(bool status)
127 This signal is emitted when a mouse is hovered over a box-and-whiskers item in a chart.
128 When the mouse moves over the item, \a status turns \c true, and when the mouse moves
129 away again, it turns \c false.
130
131 The corresponding signal handler is \c onHovered().
132 */
133 /*!
134 \qmlsignal BoxSet::valuesChanged()
135 This signal is emitted when multiple values of the box-and-whiskers item change.
136
137 The corresponding signal handler is \c onValuesChanged().
138 */
139 /*!
140 \qmlsignal BoxSet::valueChanged(int index)
141 This signal is emitted when the value of the box-and-whiskers item specified by \a index
142 changes.
143
144 The corresponding signal handler is \c onValueChanged().
145 */
146 /*!
147 \qmlsignal BoxSet::cleared()
148 This signal is emitted when all the values of the box-and-whiskers item are set to 0.
149
150 The corresponding signal handler is \c onCleared().
151 */
152
153 /*!
154 \qmltype BoxPlotSeries
155 \instantiates QBoxPlotSeries
156 \inqmlmodule QtCharts
157
158 \inherits AbstractSeries
159
160 \brief Presents data in box-and-whiskers charts.
161
162 A box plot series acts as a container for box-and-whiskers items. Items from multiple series
163 are grouped into categories according to their index value.
164
165 The BarCategoryAxis class is used to add the categories to the chart's axis. Category labels
166 have to be unique. If the same category label is defined for several box-and-whiskers items,
167 only the first one is drawn.
168
169 The following QML code snippet shows how to create a simple box-and-whiskers chart:
170 \code
171 import QtQuick 2.0
172 import QtCharts 2.0
173
174 ChartView {
175 title: "Box Plot series"
176 width: 400
177 height: 300
178 theme: ChartView.ChartThemeBrownSand
179 legend.alignment: Qt.AlignBottom
180
181 BoxPlotSeries {
182 id: plotSeries
183 name: "Income"
184 BoxSet { label: "Jan"; values: [3, 4, 5.1, 6.2, 8.5] }
185 BoxSet { label: "Feb"; values: [5, 6, 7.5, 8.6, 11.8] }
186 BoxSet { label: "Mar"; values: [3.2, 5, 5.7, 8, 9.2] }
187 BoxSet { label: "Apr"; values: [3.8, 5, 6.4, 7, 8] }
188 BoxSet { label: "May"; values: [4, 5, 5.2, 6, 7] }
189 }
190 }
191 \endcode
192
193 \beginfloatleft
194 \image examples_qmlboxplot.png
195 \endfloat
196 \clearfloat
197
198 \sa BoxSet, BarCategoryAxis
199 */
200
201 /*!
202 \qmlmethod BoxPlotSeries::at(int index)
203 Returns the box-and-whiskers item in the position specified by \a index.
204 */
205
206 /*!
207 \qmlmethod BoxPlotSeries::append(string label, VariantList values)
208 Appends a new box-and-whiskers item with the label specified by \a label and the values
209 specified by \a values to the series.
210 */
211 /*!
212 \qmlmethod BoxPlotSeries::append(BoxSet box)
213 Appends the box-and-whiskers item specified by \a box to the series.
214 */
215 /*!
216 \qmlmethod BoxPlotSeries::insert(int index, string label, VariantList values)
217 Inserts a new box-and-whiskers item with the label specified by \a label and the values
218 specified by \a values to the series at the position specified by \a index.
219 */
220 /*!
221 \qmlmethod BoxPlotSeries::remove(QBoxSet boxset)
222 Removes the box-and-whiskers item specified by \a boxset from the series.
223 */
224 /*!
225 \qmlmethod BoxPlotSeries::clear()
226 Removes all box-and-whiskers items from the series and permanently deletes them.
227 */
228 /*!
229 \qmlsignal BoxPlotSeries::clicked(BoxSet boxset);
230 This signal is emitted when the user clicks the box-and-whiskers item specified by
231 \a boxset in the chart.
232
233 The corresponding signal handler is \c onClicked().
234 */
235 /*!
236 \qmlsignal BoxPlotSeries::hovered(bool status, BoxSet boxset);
237 This signal is emitted when a mouse is hovered over the box-and-whiskers item specified by
238 \a boxset in the chart. When the mouse moves over the item, \a status turns \c true, and
239 when the mouse moves away again, it turns \c false.
240
241 The corresponding signal handler is \c onHovered().
242 */
243 /*!
244 \qmlsignal BoxPlotSeries::pressed(BoxSet boxset)
245 This signal is emitted when the user presses the \a boxset on the chart.
246
247 The corresponding signal handler is \c onPressed.
248 */
249 /*!
250 \qmlsignal BoxPlotSeries::released(BoxSet boxset)
251 This signal is emitted when the user releases the mouse press on the box-and-whiskers
252 item specified by \a boxset in the chart.
253
254 The corresponding signal handler is \c onReleased().
255 */
256 /*!
257 \qmlsignal BoxPlotSeries::doubleClicked(BoxSet boxset)
258 This signal is emitted when the user double-clicks the box-and-whiskers item specified by
259 \a boxset in the chart.
260
261 The corresponding signal handler is \c onDoubleClicked().
262 */
263 /*!
264 \qmlsignal BoxPlotSeries::boxsetsAdded(list sets)
265 This signal is emitted when the box-and-whiskers items specified by \a sets
266 are added to the series.
267
268 The corresponding signal handler is \c onBoxsetsAdded().
269 */
270 /*!
271 \qmlsignal BoxPlotSeries::boxsetsRemoved(list sets)
272 This signal is emitted when the box-and-whiskers items specified by \a sets
273 are removed from the series.
274
275 The corresponding signal handler is \c onBoxsetsRemoved().
276 */
277 /*!
278 \qmlproperty AbstractAxis BoxPlotSeries::axisX
279 The x-axis used for the series. If you leave both axisX and axisXTop undefined, a
280 BarCategoryAxis is created for the series.
281 \sa axisXTop
282 */
283 /*!
284 \qmlproperty AbstractAxis BoxPlotSeries::axisY
285 The y-axis used for the series. If you leave both axisY and axisYRight undefined, a
286 ValueAxis is created for the series.
287 \sa axisYRight
288 */
289 /*!
290 \qmlproperty AbstractAxis BoxPlotSeries::axisXTop
291 The x-axis used for the series, drawn on top of the chart view.
292
293 \note You can only provide either axisX or axisXTop, but not both.
294 \sa axisX
295
296 \sa axisX
297 */
298 /*!
299 \qmlproperty AbstractAxis BoxPlotSeries::axisYRight
300 The y-axis used for the series, drawn to the right on the chart view.
301
302 \note You can only provide either axisY or axisYRight, but not both.
303 \sa axisY
304 */
305 /*!
306 \qmlproperty bool BoxPlotSeries::boxOutlineVisible
307 The visibility of the box outline.
308 */
309 /*!
310 \qmlproperty real BoxPlotSeries::boxWidth
311 \brief The width of the box-and-whiskers item. The value indicates the relative
312 width of the item within its category. The value can be between 0.0 and 1.0. Negative values
313 are replaced with 0.0 and values greater than 1.0 are replaced with 1.0.
314 */
315
316 /*!
317 \qmlproperty string BoxPlotSeries::brushFilename
318 The name of the file used as a brush for the series.
319 */
320
321 /*!
322 \qmlproperty int BoxPlotSeries::count
323 The number of box-and-whiskers items in a box plot series.
324 */
325
326
DeclarativeBoxSet(const QString label,QObject * parent)327 DeclarativeBoxSet::DeclarativeBoxSet(const QString label, QObject *parent)
328 : QBoxSet(label, parent)
329 {
330 connect(this, SIGNAL(valuesChanged()), this, SIGNAL(changedValues()));
331 connect(this, SIGNAL(valueChanged(int)), this, SIGNAL(changedValue(int)));
332 connect(this, SIGNAL(brushChanged()), this, SLOT(handleBrushChanged()));
333 }
334
values()335 QVariantList DeclarativeBoxSet::values()
336 {
337 QVariantList values;
338 for (int i(0); i < 5; i++)
339 values.append(QVariant(QBoxSet::at(i)));
340 return values;
341 }
342
setValues(QVariantList values)343 void DeclarativeBoxSet::setValues(QVariantList values)
344 {
345 for (int i(0); i < values.count(); i++) {
346 if (values.at(i).canConvert(QVariant::Double))
347 QBoxSet::append(values[i].toDouble());
348 }
349 }
350
brushFilename() const351 QString DeclarativeBoxSet::brushFilename() const
352 {
353 return m_brushFilename;
354 }
355
setBrushFilename(const QString & brushFilename)356 void DeclarativeBoxSet::setBrushFilename(const QString &brushFilename)
357 {
358 QImage brushImage(brushFilename);
359 if (QBoxSet::brush().textureImage() != brushImage) {
360 QBrush brush = QBoxSet::brush();
361 brush.setTextureImage(brushImage);
362 QBoxSet::setBrush(brush);
363 m_brushFilename = brushFilename;
364 m_brushImage = brushImage;
365 emit brushFilenameChanged(brushFilename);
366 }
367 }
368
handleBrushChanged()369 void DeclarativeBoxSet::handleBrushChanged()
370 {
371 // If the texture image of the brush has changed along the brush
372 // the brush file name needs to be cleared.
373 if (!m_brushFilename.isEmpty() && QBoxSet::brush().textureImage() != m_brushImage) {
374 m_brushFilename.clear();
375 emit brushFilenameChanged(QString(""));
376 }
377 }
378
379 // =====================================================
380
DeclarativeBoxPlotSeries(QQuickItem * parent)381 DeclarativeBoxPlotSeries::DeclarativeBoxPlotSeries(QQuickItem *parent) :
382 QBoxPlotSeries(parent),
383 m_axes(new DeclarativeAxes(this))
384 {
385 connect(m_axes, SIGNAL(axisXChanged(QAbstractAxis*)), this, SIGNAL(axisXChanged(QAbstractAxis*)));
386 connect(m_axes, SIGNAL(axisYChanged(QAbstractAxis*)), this, SIGNAL(axisYChanged(QAbstractAxis*)));
387 connect(m_axes, SIGNAL(axisXTopChanged(QAbstractAxis*)), this, SIGNAL(axisXTopChanged(QAbstractAxis*)));
388 connect(m_axes, SIGNAL(axisYRightChanged(QAbstractAxis*)), this, SIGNAL(axisYRightChanged(QAbstractAxis*)));
389 connect(this, SIGNAL(hovered(bool, QBoxSet*)), this, SLOT(onHovered(bool, QBoxSet*)));
390 connect(this, SIGNAL(clicked(QBoxSet*)), this, SLOT(onClicked(QBoxSet*)));
391 connect(this, SIGNAL(brushChanged()), this, SLOT(handleBrushChanged()));
392 connect(this, SIGNAL(pressed(QBoxSet*)), this, SLOT(onPressed(QBoxSet*)));
393 connect(this, SIGNAL(released(QBoxSet*)), this, SLOT(onReleased(QBoxSet*)));
394 connect(this, SIGNAL(doubleClicked(QBoxSet*)), this, SLOT(onDoubleClicked(QBoxSet*)));
395 }
396
classBegin()397 void DeclarativeBoxPlotSeries::classBegin()
398 {
399 }
400
componentComplete()401 void DeclarativeBoxPlotSeries::componentComplete()
402 {
403 foreach (QObject *child, children()) {
404 if (qobject_cast<DeclarativeBoxSet *>(child)) {
405 QBoxPlotSeries::append(qobject_cast<DeclarativeBoxSet *>(child));
406 } else if (qobject_cast<QVBoxPlotModelMapper *>(child)) {
407 QVBoxPlotModelMapper *mapper = qobject_cast<QVBoxPlotModelMapper *>(child);
408 mapper->setSeries(this);
409 } else if (QHBoxPlotModelMapper *mapper = qobject_cast<QHBoxPlotModelMapper *>(child)) {
410 mapper->setSeries(this);
411 }
412 }
413 }
414
seriesChildren()415 QQmlListProperty<QObject> DeclarativeBoxPlotSeries::seriesChildren()
416 {
417 return QQmlListProperty<QObject>(this, 0, &DeclarativeBoxPlotSeries::appendSeriesChildren ,0,0,0);
418 }
419
appendSeriesChildren(QQmlListProperty<QObject> * list,QObject * element)420 void DeclarativeBoxPlotSeries::appendSeriesChildren(QQmlListProperty<QObject> *list, QObject *element)
421 {
422 // Empty implementation; the children are parsed in componentComplete instead
423 Q_UNUSED(list);
424 Q_UNUSED(element);
425 }
426
at(int index)427 DeclarativeBoxSet *DeclarativeBoxPlotSeries::at(int index)
428 {
429 QList<QBoxSet *> setList = boxSets();
430 if (index >= 0 && index < setList.count())
431 return qobject_cast<DeclarativeBoxSet *>(setList[index]);
432
433 return 0;
434 }
435
insert(int index,const QString label,QVariantList values)436 DeclarativeBoxSet *DeclarativeBoxPlotSeries::insert(int index, const QString label, QVariantList values)
437 {
438 DeclarativeBoxSet *barset = new DeclarativeBoxSet(label, this);
439 barset->setValues(values);
440 if (QBoxPlotSeries::insert(index, barset))
441 return barset;
442 delete barset;
443 return 0;
444 }
445
onHovered(bool status,QBoxSet * boxset)446 void DeclarativeBoxPlotSeries::onHovered(bool status, QBoxSet *boxset)
447 {
448 emit hovered(status, qobject_cast<DeclarativeBoxSet *>(boxset));
449 }
450
onClicked(QBoxSet * boxset)451 void DeclarativeBoxPlotSeries::onClicked(QBoxSet *boxset)
452 {
453 emit clicked(qobject_cast<DeclarativeBoxSet *>(boxset));
454 }
455
onPressed(QBoxSet * boxset)456 void DeclarativeBoxPlotSeries::onPressed(QBoxSet *boxset)
457 {
458 emit pressed(qobject_cast<DeclarativeBoxSet *>(boxset));
459 }
460
onReleased(QBoxSet * boxset)461 void DeclarativeBoxPlotSeries::onReleased(QBoxSet *boxset)
462 {
463 emit released(qobject_cast<DeclarativeBoxSet *>(boxset));
464 }
465
onDoubleClicked(QBoxSet * boxset)466 void DeclarativeBoxPlotSeries::onDoubleClicked(QBoxSet *boxset)
467 {
468 emit doubleClicked(qobject_cast<DeclarativeBoxSet *>(boxset));
469 }
470
brushFilename() const471 QString DeclarativeBoxPlotSeries::brushFilename() const
472 {
473 return m_brushFilename;
474 }
475
setBrushFilename(const QString & brushFilename)476 void DeclarativeBoxPlotSeries::setBrushFilename(const QString &brushFilename)
477 {
478 QImage brushImage(brushFilename);
479 if (QBoxPlotSeries::brush().textureImage() != brushImage) {
480 QBrush brush = QBoxPlotSeries::brush();
481 brush.setTextureImage(brushImage);
482 QBoxPlotSeries::setBrush(brush);
483 m_brushFilename = brushFilename;
484 m_brushImage = brushImage;
485 emit brushFilenameChanged(brushFilename);
486 }
487 }
488
handleBrushChanged()489 void DeclarativeBoxPlotSeries::handleBrushChanged()
490 {
491 // If the texture image of the brush has changed along the brush
492 // the brush file name needs to be cleared.
493 if (!m_brushFilename.isEmpty() && QBoxPlotSeries::brush().textureImage() != m_brushImage) {
494 m_brushFilename.clear();
495 emit brushFilenameChanged(QString(""));
496 }
497 }
498
499 QT_CHARTS_END_NAMESPACE
500
501 #include "moc_declarativeboxplotseries_p.cpp"
502