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/qpieslice.h>
7#include <QtCharts/qpieseries.h>
8#include <QtWidgets/qgraphicssceneevent.h>
9
10#include <private/piechartitem_p.h>
11#include <private/piesliceitem_p.h>
12#include <private/qpieslice_p.h>
13#include <private/qpieseries_p.h>
14#include <private/chartpresenter_p.h>
15#include <private/chartdataset_p.h>
16#include <private/pieanimation_p.h>
17
18QT_BEGIN_NAMESPACE
19
20PieChartItem::PieChartItem(QPieSeries *series, QGraphicsItem* item)
21 : ChartItem(series->d_func(),item),
22 m_series(series),
23 m_animation(0)
24{
25 Q_ASSERT(series);
26
27 QPieSeriesPrivate *p = QPieSeriesPrivate::fromSeries(series);
28 connect(sender: series, SIGNAL(visibleChanged()), receiver: this, SLOT(handleSeriesVisibleChanged()));
29 connect(sender: series, SIGNAL(opacityChanged()), receiver: this, SLOT(handleOpacityChanged()));
30 connect(sender: series, SIGNAL(added(QList<QPieSlice*>)), receiver: this, SLOT(handleSlicesAdded(QList<QPieSlice*>)));
31 connect(sender: series, SIGNAL(removed(QList<QPieSlice*>)), receiver: this, SLOT(handleSlicesRemoved(QList<QPieSlice*>)));
32 connect(sender: p, SIGNAL(horizontalPositionChanged()), receiver: this, SLOT(updateLayout()));
33 connect(sender: p, SIGNAL(verticalPositionChanged()), receiver: this, SLOT(updateLayout()));
34 connect(sender: p, SIGNAL(pieSizeChanged()), receiver: this, SLOT(updateLayout()));
35 connect(sender: p, SIGNAL(calculatedDataChanged()), receiver: this, SLOT(updateLayout()));
36
37 // Note: the following does not affect as long as the item does not have anything to paint
38 setZValue(ChartPresenter::PieSeriesZValue);
39
40 // Note: will not create slice items until we have a proper rectangle to draw on.
41
42 setFlag(flag: QGraphicsItem::ItemIsSelectable);
43}
44
45PieChartItem::~PieChartItem()
46{
47 cleanup();
48}
49
50void PieChartItem::setAnimation(PieAnimation *animation)
51{
52 m_animation = animation;
53}
54
55ChartAnimation *PieChartItem::animation() const
56{
57 return m_animation;
58}
59
60void PieChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
61{
62 event->ignore();
63}
64
65void PieChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
66{
67 event->ignore();
68}
69
70void PieChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
71{
72 event->ignore();
73}
74
75void PieChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
76{
77 event->ignore();
78}
79
80void PieChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
81{
82 event->ignore();
83}
84
85void PieChartItem::cleanup()
86{
87 ChartItem::cleanup();
88
89 // slice items deleted automatically through QGraphicsItem
90 if (m_series) {
91 m_series->disconnect(receiver: this);
92 QPieSeriesPrivate::fromSeries(series: m_series)->disconnect(receiver: this);
93 m_series = 0;
94 }
95 foreach (QPieSlice *slice, m_sliceItems.keys()) {
96 slice->disconnect(receiver: this);
97 QPieSlicePrivate::fromSlice(slice)->disconnect(receiver: this);
98 }
99 m_sliceItems.clear();
100}
101
102void PieChartItem::handleDomainUpdated()
103{
104 QRectF rect(QPointF(0,0),domain()->size());
105 if(m_rect!=rect){
106 prepareGeometryChange();
107 m_rect = rect;
108 updateLayout();
109
110 if (m_sliceItems.isEmpty())
111 handleSlicesAdded(slices: m_series->slices());
112 }
113}
114
115void PieChartItem::updateLayout()
116{
117 // find pie center coordinates
118 m_pieCenter.setX(m_rect.left() + (m_rect.width() * m_series->horizontalPosition()));
119 m_pieCenter.setY(m_rect.top() + (m_rect.height() * m_series->verticalPosition()));
120
121 // find maximum radius for pie
122 m_pieRadius = m_rect.height() / 2;
123 if (m_rect.width() < m_rect.height())
124 m_pieRadius = m_rect.width() / 2;
125
126 m_holeSize = m_pieRadius;
127 // apply size factor
128 m_pieRadius *= m_series->pieSize();
129 m_holeSize *= m_series->holeSize();
130
131 // set layouts for existing slice items
132 const auto slices = m_series->slices();
133 for (QPieSlice *slice : slices) {
134 PieSliceItem *sliceItem = m_sliceItems.value(key: slice);
135 if (sliceItem) {
136 const PieSliceData sliceData = updateSliceGeometry(slice);
137 if (m_animation)
138 presenter()->startAnimation(animation: m_animation->updateValue(sliceItem, newValue: sliceData));
139 else
140 sliceItem->setLayout(sliceData);
141 }
142 }
143
144 update();
145}
146
147void PieChartItem::handleSlicesAdded(const QList<QPieSlice *> &slices)
148{
149 // delay creating slice items until there is a proper rectangle
150 if (!m_rect.isValid() && m_sliceItems.isEmpty())
151 return;
152
153 themeManager()->updateSeries(series: m_series);
154
155 bool startupAnimation = m_sliceItems.isEmpty();
156
157 for (auto *slice : slices) {
158 PieSliceItem *sliceItem = new PieSliceItem(this);
159 m_sliceItems.insert(key: slice, value: sliceItem);
160
161 // Note: no need to connect to slice valueChanged() etc.
162 // This is handled through calculatedDataChanged signal.
163 connect(sender: slice, SIGNAL(labelChanged()), receiver: this, SLOT(handleSliceChanged()));
164 connect(sender: slice, SIGNAL(labelVisibleChanged()), receiver: this, SLOT(handleSliceChanged()));
165 connect(sender: slice, SIGNAL(penChanged()), receiver: this, SLOT(handleSliceChanged()));
166 connect(sender: slice, SIGNAL(brushChanged()), receiver: this, SLOT(handleSliceChanged()));
167 connect(sender: slice, SIGNAL(labelBrushChanged()), receiver: this, SLOT(handleSliceChanged()));
168 connect(sender: slice, SIGNAL(labelFontChanged()), receiver: this, SLOT(handleSliceChanged()));
169
170 QPieSlicePrivate *p = QPieSlicePrivate::fromSlice(slice);
171 connect(sender: p, SIGNAL(labelPositionChanged()), receiver: this, SLOT(handleSliceChanged()));
172 connect(sender: p, SIGNAL(explodedChanged()), receiver: this, SLOT(handleSliceChanged()));
173 connect(sender: p, SIGNAL(labelArmLengthFactorChanged()), receiver: this, SLOT(handleSliceChanged()));
174 connect(sender: p, SIGNAL(explodeDistanceFactorChanged()), receiver: this, SLOT(handleSliceChanged()));
175
176 connect(sender: sliceItem, SIGNAL(clicked(Qt::MouseButtons)), receiver: slice, SIGNAL(clicked()));
177 connect(sender: sliceItem, SIGNAL(hovered(bool)), receiver: slice, SIGNAL(hovered(bool)));
178 connect(sender: sliceItem, SIGNAL(pressed(Qt::MouseButtons)), receiver: slice, SIGNAL(pressed()));
179 connect(sender: sliceItem, SIGNAL(released(Qt::MouseButtons)), receiver: slice, SIGNAL(released()));
180 connect(sender: sliceItem, SIGNAL(doubleClicked(Qt::MouseButtons)), receiver: slice, SIGNAL(doubleClicked()));
181
182 PieSliceData sliceData = updateSliceGeometry(slice);
183 if (m_animation)
184 presenter()->startAnimation(animation: m_animation->addSlice(sliceItem, endValue: sliceData, startupAnimation));
185 else
186 sliceItem->setLayout(sliceData);
187 }
188}
189
190void PieChartItem::handleSlicesRemoved(const QList<QPieSlice *> &slices)
191{
192 themeManager()->updateSeries(series: m_series);
193
194 for (auto *slice : slices) {
195
196 PieSliceItem *sliceItem = m_sliceItems.value(key: slice);
197
198 // this can happen if you call append() & remove() in a row so that PieSliceItem is not even created
199 if (!sliceItem)
200 continue;
201
202 m_sliceItems.remove(key: slice);
203 slice->disconnect(receiver: this);
204 QPieSlicePrivate::fromSlice(slice)->disconnect(receiver: this);
205
206 if (m_animation)
207 presenter()->startAnimation(animation: m_animation->removeSlice(sliceItem)); // animator deletes the PieSliceItem
208 else
209 delete sliceItem;
210 }
211}
212
213void PieChartItem::handleSliceChanged()
214{
215 QPieSlice *slice = qobject_cast<QPieSlice *>(object: sender());
216 if (!slice) {
217 QPieSlicePrivate *slicep = qobject_cast<QPieSlicePrivate *>(object: sender());
218 slice = slicep->q_ptr;
219 }
220 Q_ASSERT(m_sliceItems.contains(slice));
221
222 PieSliceItem *sliceItem = m_sliceItems.value(key: slice);
223 PieSliceData sliceData = updateSliceGeometry(slice);
224 if (m_animation)
225 presenter()->startAnimation(animation: m_animation->updateValue(sliceItem, newValue: sliceData));
226 else
227 sliceItem->setLayout(sliceData);
228
229 update();
230}
231
232void PieChartItem::handleSeriesVisibleChanged()
233{
234 setVisible(m_series->isVisible());
235}
236
237void PieChartItem::handleOpacityChanged()
238{
239 setOpacity(m_series->opacity());
240}
241
242PieSliceData PieChartItem::updateSliceGeometry(QPieSlice *slice)
243{
244 PieSliceData &sliceData = QPieSlicePrivate::fromSlice(slice)->m_data;
245 sliceData.m_center = PieSliceItem::sliceCenter(point: m_pieCenter, radius: m_pieRadius, slice);
246 sliceData.m_radius = m_pieRadius;
247 sliceData.m_holeRadius = m_holeSize;
248 return sliceData;
249}
250
251QT_END_NAMESPACE
252
253#include "moc_piechartitem_p.cpp"
254

source code of qtcharts/src/charts/piechart/piechartitem.cpp