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

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