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 <private/boxplotchartitem_p.h>
7#include <private/qboxplotseries_p.h>
8#include <private/bar_p.h>
9#include <private/qboxset_p.h>
10#include <private/qabstractbarseries_p.h>
11#include <QtCharts/QBoxSet>
12#include <private/boxwhiskers_p.h>
13#include <QtGui/QPainter>
14
15QT_BEGIN_NAMESPACE
16
17BoxPlotChartItem::BoxPlotChartItem(QBoxPlotSeries *series, QGraphicsItem *item) :
18 ChartItem(series->d_func(), item),
19 m_series(series),
20 m_animation(0)
21{
22 setAcceptedMouseButtons({});
23 connect(sender: series, SIGNAL(boxsetsRemoved(QList<QBoxSet*>)),
24 receiver: this, SLOT(handleBoxsetRemove(QList<QBoxSet*>)));
25 connect(sender: series, SIGNAL(visibleChanged()), receiver: this, SLOT(handleSeriesVisibleChanged()));
26 connect(sender: series, SIGNAL(opacityChanged()), receiver: this, SLOT(handleOpacityChanged()));
27 connect(sender: series->d_func(), SIGNAL(restructuredBoxes()), receiver: this, SLOT(handleDataStructureChanged()));
28 connect(sender: series->d_func(), SIGNAL(updatedLayout()), receiver: this, SLOT(handleLayoutChanged()));
29 connect(sender: series->d_func(), SIGNAL(updatedBoxes()), receiver: this, SLOT(handleUpdatedBars()));
30 connect(sender: series->d_func(), SIGNAL(updated()), receiver: this, SLOT(handleUpdatedBars()));
31 // QBoxPlotSeriesPrivate calls handleDataStructureChanged(), don't do it here
32 setZValue(ChartPresenter::BoxPlotSeriesZValue);
33}
34
35BoxPlotChartItem::~BoxPlotChartItem()
36{
37}
38
39void BoxPlotChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
40{
41 Q_UNUSED(painter);
42 Q_UNUSED(option);
43 Q_UNUSED(widget);
44}
45
46void BoxPlotChartItem::setAnimation(BoxPlotAnimation *animation)
47{
48 m_animation = animation;
49 if (m_animation) {
50 foreach (BoxWhiskers *item, m_boxTable.values())
51 m_animation->addBox(box: item);
52 handleDomainUpdated();
53 }
54}
55
56void BoxPlotChartItem::handleSeriesVisibleChanged()
57{
58 setVisible(m_series->isVisible());
59}
60
61void BoxPlotChartItem::handleOpacityChanged()
62{
63 setOpacity(m_series->opacity());
64}
65
66void BoxPlotChartItem::handleDataStructureChanged()
67{
68 int setCount = m_series->count();
69
70 for (int s = 0; s < setCount; s++) {
71 QBoxSet *set = m_series->d_func()->boxSetAt(index: s);
72
73 BoxWhiskers *box = m_boxTable.value(key: set);
74 if (!box) {
75 // Item is not yet created, make a box and add it to hash table
76 box = new BoxWhiskers(set, domain(), this);
77 m_boxTable.insert(key: set, value: box);
78 connect(sender: box, SIGNAL(clicked(QBoxSet*)), receiver: m_series, SIGNAL(clicked(QBoxSet*)));
79 connect(sender: box, SIGNAL(hovered(bool,QBoxSet*)),
80 receiver: m_series, SIGNAL(hovered(bool,QBoxSet*)));
81 connect(sender: box, SIGNAL(pressed(QBoxSet*)), receiver: m_series, SIGNAL(pressed(QBoxSet*)));
82 connect(sender: box, SIGNAL(released(QBoxSet*)), receiver: m_series, SIGNAL(released(QBoxSet*)));
83 connect(sender: box, SIGNAL(doubleClicked(QBoxSet*)),
84 receiver: m_series, SIGNAL(doubleClicked(QBoxSet*)));
85 connect(sender: box, SIGNAL(clicked(QBoxSet*)), receiver: set, SIGNAL(clicked()));
86 connect(sender: box, SIGNAL(hovered(bool,QBoxSet*)), receiver: set, SIGNAL(hovered(bool)));
87 connect(sender: box, SIGNAL(pressed(QBoxSet*)), receiver: set, SIGNAL(pressed()));
88 connect(sender: box, SIGNAL(released(QBoxSet*)), receiver: set, SIGNAL(released()));
89 connect(sender: box, SIGNAL(doubleClicked(QBoxSet*)), receiver: set, SIGNAL(doubleClicked()));
90
91 // Set the decorative issues for the newly created box
92 // so that the brush and pen already defined for the set are kept.
93 if (set->brush() == Qt::NoBrush)
94 box->setBrush(m_series->brush());
95 else
96 box->setBrush(set->brush());
97 if (set->pen() == Qt::NoPen)
98 box->setPen(m_series->pen());
99 else
100 box->setPen(set->pen());
101 box->setBoxOutlined(m_series->boxOutlineVisible());
102 box->setBoxWidth(m_series->boxWidth());
103 }
104 updateBoxGeometry(box, index: s);
105
106 box->updateGeometry(domain: domain());
107
108 if (m_animation)
109 m_animation->addBox(box);
110 }
111
112 handleDomainUpdated();
113}
114
115void BoxPlotChartItem::handleUpdatedBars()
116{
117 foreach (BoxWhiskers *item, m_boxTable.values()) {
118 item->setBrush(m_series->brush());
119 item->setPen(m_series->pen());
120 item->setBoxOutlined(m_series->boxOutlineVisible());
121 item->setBoxWidth(m_series->boxWidth());
122 }
123 // Override with QBoxSet specific settings
124 foreach (QBoxSet *set, m_boxTable.keys()) {
125 if (set->brush().style() != Qt::NoBrush)
126 m_boxTable.value(key: set)->setBrush(set->brush());
127 if (set->pen().style() != Qt::NoPen)
128 m_boxTable.value(key: set)->setPen(set->pen());
129 }
130}
131
132void BoxPlotChartItem::handleBoxsetRemove(const QList<QBoxSet *> &barSets)
133{
134 for (auto *set : barSets) {
135 BoxWhiskers *boxItem = m_boxTable.value(key: set);
136 m_boxTable.remove(key: set);
137 delete boxItem;
138 }
139}
140
141void BoxPlotChartItem::handleDomainUpdated()
142{
143 if ((domain()->size().width() <= 0) || (domain()->size().height() <= 0))
144 return;
145
146 // Set my bounding rect to same as domain size. Add one pixel at the top (-1.0) and the bottom as 0.0 would
147 // snip a bit off from the whisker at the grid line
148 m_boundingRect.setRect(ax: 0.0, ay: -1.0, aaw: domain()->size().width(), aah: domain()->size().height() + 1.0);
149
150 foreach (BoxWhiskers *item, m_boxTable.values()) {
151 item->updateGeometry(domain: domain());
152
153 // If the animation is set, start the animation for each BoxWhisker item
154 if (m_animation)
155 presenter()->startAnimation(animation: m_animation->boxAnimation(box: item));
156 }
157}
158
159void BoxPlotChartItem::handleLayoutChanged()
160{
161 foreach (BoxWhiskers *item, m_boxTable.values()) {
162 if (m_animation)
163 m_animation->setAnimationStart(item);
164
165 item->setBoxWidth(m_series->boxWidth());
166
167 bool dirty = updateBoxGeometry(box: item, index: item->m_data.m_index);
168 if (dirty && m_animation)
169 presenter()->startAnimation(animation: m_animation->boxChangeAnimation(box: item));
170 else
171 item->updateGeometry(domain: domain());
172 }
173}
174
175QRectF BoxPlotChartItem::boundingRect() const
176{
177 return m_boundingRect;
178}
179
180void BoxPlotChartItem::initializeLayout()
181{
182}
183
184QList<QRectF> BoxPlotChartItem::calculateLayout()
185{
186 return QList<QRectF>();
187}
188
189bool BoxPlotChartItem::updateBoxGeometry(BoxWhiskers *box, int index)
190{
191 bool changed = false;
192
193 QBoxSet *set = m_series->d_func()->boxSetAt(index);
194 BoxWhiskersData &data = box->m_data;
195
196 if ((data.m_lowerExtreme != set->at(index: 0)) || (data.m_lowerQuartile != set->at(index: 1)) ||
197 (data.m_median != set->at(index: 2)) || (data.m_upperQuartile != set->at(index: 3)) || (data.m_upperExtreme != set->at(index: 4))) {
198 changed = true;
199 }
200
201 data.m_lowerExtreme = set->at(index: 0);
202 data.m_lowerQuartile = set->at(index: 1);
203 data.m_median = set->at(index: 2);
204 data.m_upperQuartile = set->at(index: 3);
205 data.m_upperExtreme = set->at(index: 4);
206 data.m_index = index;
207 data.m_boxItems = m_series->count();
208
209 data.m_maxX = domain()->maxX();
210 data.m_minX = domain()->minX();
211 data.m_maxY = domain()->maxY();
212 data.m_minY = domain()->minY();
213
214 data.m_seriesIndex = m_seriesIndex;
215 data.m_seriesCount = m_seriesCount;
216
217 return changed;
218}
219
220QT_END_NAMESPACE
221
222#include "moc_boxplotchartitem_p.cpp"
223

source code of qtcharts/src/charts/boxplotchart/boxplotchartitem.cpp