1 | // Copyright (C) 2023 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qscatter3dseries_p.h" |
5 | #include "scatter3dcontroller_p.h" |
6 | #include "qvalue3daxis.h" |
7 | #include "qcategory3daxis.h" |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | /*! |
12 | * \class QScatter3DSeries |
13 | * \inmodule QtGraphs |
14 | * \brief The QScatter3DSeries class represents a data series in a 3D scatter |
15 | * graph. |
16 | * |
17 | * This class manages the series specific visual elements, as well as the series |
18 | * data (via a data proxy). |
19 | * |
20 | * If no data proxy is set explicitly for the series, the series creates a default |
21 | * proxy. Setting another proxy will destroy the existing proxy and all data added to it. |
22 | * |
23 | * QScatter3DSeries supports the following format tags for QAbstract3DSeries::setItemLabelFormat(): |
24 | * \table |
25 | * \row |
26 | * \li @xTitle \li Title from x-axis |
27 | * \row |
28 | * \li @yTitle \li Title from y-axis |
29 | * \row |
30 | * \li @zTitle \li Title from z-axis |
31 | * \row |
32 | * \li @xLabel \li Item value formatted using the format of the x-axis. |
33 | * For more information, see |
34 | * \l{QValue3DAxis::setLabelFormat()}. |
35 | * \row |
36 | * \li @yLabel \li Item value formatted using the format of the y-axis. |
37 | * For more information, see |
38 | * \l{QValue3DAxis::setLabelFormat()}. |
39 | * \row |
40 | * \li @zLabel \li Item value formatted using the format of the z-axis. |
41 | * For more information, see |
42 | * \l{QValue3DAxis::setLabelFormat()}. |
43 | * \row |
44 | * \li @seriesName \li Name of the series |
45 | * \endtable |
46 | * |
47 | * For example: |
48 | * \snippet doc_src_qtgraphs.cpp 1 |
49 | * |
50 | * \sa {Qt Graphs Data Handling} |
51 | */ |
52 | |
53 | /*! |
54 | * \qmltype Scatter3DSeries |
55 | * \inqmlmodule QtGraphs |
56 | * \ingroup graphs_qml |
57 | * \instantiates QScatter3DSeries |
58 | * \inherits Abstract3DSeries |
59 | * \brief Represents a data series in a 3D scatter graph. |
60 | * |
61 | * This type manages the series specific visual elements, as well as the series |
62 | * data (via a data proxy). |
63 | * |
64 | * For a more complete description, see QScatter3DSeries. |
65 | * |
66 | * \sa {Qt Graphs Data Handling} |
67 | */ |
68 | |
69 | /*! |
70 | * \qmlproperty ScatterDataProxy Scatter3DSeries::dataProxy |
71 | * |
72 | * Sets the active data proxy. The series assumes ownership of any proxy set to |
73 | * it and deletes any previously set proxy when a new one is added. The proxy |
74 | * cannot be null or set to another series. |
75 | */ |
76 | |
77 | /*! |
78 | * \qmlproperty int Scatter3DSeries::selectedItem |
79 | * |
80 | * The item that is selected at the index in the data array of the series. |
81 | * Only one item can be selected at a time. |
82 | * To clear selection from this series, invalidSelectionIndex is set as the index. |
83 | * If this series is added to a graph, the graph can adjust the selection according to user |
84 | * interaction or if it becomes invalid. Selecting an item on another added series will also |
85 | * clear the selection. |
86 | * Removing items from or inserting items to the series before the selected item |
87 | * will adjust the selection so that the same item will stay selected. |
88 | * |
89 | * \sa AbstractGraph3D::clearSelection() |
90 | */ |
91 | |
92 | /*! |
93 | * \qmlproperty float Scatter3DSeries::itemSize |
94 | * |
95 | * Sets the item size for the series. The size must be between \c 0.0 and |
96 | * \c 1.0. Setting the size to \c 0.0 causes the item size to be automatically |
97 | * scaled based on the total number of items in all the series for the graph. |
98 | * The preset default is \c 0.0. |
99 | */ |
100 | |
101 | /*! |
102 | * \qmlproperty int Scatter3DSeries::invalidSelectionIndex |
103 | * A constant property providing an invalid index for selection. This index is |
104 | * set to the selectedItem property to clear the selection from this series. |
105 | * |
106 | * \sa AbstractGraph3D::clearSelection() |
107 | */ |
108 | |
109 | /*! |
110 | * Constructs a scatter 3D series with the parent \a parent. |
111 | */ |
112 | QScatter3DSeries::QScatter3DSeries(QObject *parent) : |
113 | QAbstract3DSeries(new QScatter3DSeriesPrivate(this), parent) |
114 | { |
115 | Q_D(QScatter3DSeries); |
116 | // Default proxy |
117 | d->setDataProxy(new QScatterDataProxy); |
118 | } |
119 | |
120 | /*! |
121 | * Constructs a scatter 3D series with the data proxy \a dataProxy and the |
122 | * parent \a parent. |
123 | */ |
124 | QScatter3DSeries::QScatter3DSeries(QScatterDataProxy *dataProxy, QObject *parent) : |
125 | QAbstract3DSeries(new QScatter3DSeriesPrivate(this), parent) |
126 | { |
127 | Q_D(QScatter3DSeries); |
128 | d->setDataProxy(dataProxy); |
129 | } |
130 | |
131 | /*! |
132 | * \internal |
133 | */ |
134 | QScatter3DSeries::QScatter3DSeries(QScatter3DSeriesPrivate *d, QObject *parent) : |
135 | QAbstract3DSeries(d, parent) |
136 | { |
137 | } |
138 | |
139 | /*! |
140 | * Deletes the scatter 3D series. |
141 | */ |
142 | QScatter3DSeries::~QScatter3DSeries() |
143 | { |
144 | } |
145 | |
146 | /*! |
147 | * \property QScatter3DSeries::dataProxy |
148 | * |
149 | * \brief The active data proxy. |
150 | */ |
151 | |
152 | /*! |
153 | * Sets the active data proxy for the series to \a proxy. The series assumes |
154 | * ownership of any proxy set to it and deletes any previously set proxy when |
155 | * a new one is added. The \a proxy argument cannot be null or set to another |
156 | * series. |
157 | */ |
158 | void QScatter3DSeries::setDataProxy(QScatterDataProxy *proxy) |
159 | { |
160 | Q_D(QScatter3DSeries); |
161 | d->setDataProxy(proxy); |
162 | } |
163 | |
164 | QScatterDataProxy *QScatter3DSeries::dataProxy() const |
165 | { |
166 | const Q_D(QScatter3DSeries); |
167 | return static_cast<QScatterDataProxy *>(d->dataProxy()); |
168 | } |
169 | |
170 | /*! |
171 | * \property QScatter3DSeries::selectedItem |
172 | * |
173 | * \brief The item that is selected in the series. |
174 | */ |
175 | |
176 | /*! |
177 | * Selects the item at the index \a index in the data array of the series. |
178 | * Only one item can be selected at a time. |
179 | * |
180 | * To clear selection from this series, invalidSelectionIndex() is set as \a index. |
181 | * If this series is added to a graph, the graph can adjust the selection according to user |
182 | * interaction or if it becomes invalid. Selecting an item on another added series will also |
183 | * clear the selection. |
184 | * |
185 | * Removing items from or inserting items to the series before the selected item |
186 | * will adjust the selection so that the same item will stay selected. |
187 | * |
188 | * \sa QAbstract3DGraph::clearSelection() |
189 | */ |
190 | void QScatter3DSeries::setSelectedItem(int index) |
191 | { |
192 | Q_D(QScatter3DSeries); |
193 | // Don't do this in private to avoid loops, as that is used for callback from controller. |
194 | if (d->m_controller) |
195 | static_cast<Scatter3DController *>(d->m_controller)->setSelectedItem(index, series: this); |
196 | else |
197 | d->setSelectedItem(index); |
198 | } |
199 | |
200 | int QScatter3DSeries::selectedItem() const |
201 | { |
202 | const Q_D(QScatter3DSeries); |
203 | return d->m_selectedItem; |
204 | } |
205 | |
206 | /*! |
207 | * \property QScatter3DSeries::itemSize |
208 | * |
209 | * \brief Item size for the series. |
210 | * |
211 | * The size must be between \c 0.0f and \c 1.0f. Setting the size to \c 0.0f |
212 | * causes the item size to be automatically scaled based on the total number of |
213 | * items in all the series for the graph. |
214 | * |
215 | * The preset default is \c 0.0f. |
216 | */ |
217 | void QScatter3DSeries::setItemSize(float size) |
218 | { |
219 | Q_D(QScatter3DSeries); |
220 | if (size < 0.0f || size > 1.0f) { |
221 | qWarning(msg: "Invalid size. Valid range for itemSize is 0.0f...1.0f" ); |
222 | } else if (size != d->m_itemSize) { |
223 | d->setItemSize(size); |
224 | emit itemSizeChanged(size); |
225 | } |
226 | } |
227 | |
228 | float QScatter3DSeries::itemSize() const |
229 | { |
230 | const Q_D(QScatter3DSeries); |
231 | return d->m_itemSize; |
232 | } |
233 | |
234 | /*! |
235 | * Returns an invalid index for selection. This index is set to the selectedItem |
236 | * property to clear the selection from this series. |
237 | * |
238 | * \sa QAbstract3DGraph::clearSelection() |
239 | */ |
240 | int QScatter3DSeries::invalidSelectionIndex() |
241 | { |
242 | return Scatter3DController::invalidSelectionIndex(); |
243 | } |
244 | |
245 | // QScatter3DSeriesPrivate |
246 | |
247 | QScatter3DSeriesPrivate::QScatter3DSeriesPrivate(QScatter3DSeries *q) |
248 | : QAbstract3DSeriesPrivate(q, QAbstract3DSeries::SeriesTypeScatter), |
249 | m_selectedItem(Scatter3DController::invalidSelectionIndex()), |
250 | m_itemSize(0.0f) |
251 | { |
252 | m_itemLabelFormat = QStringLiteral("@xLabel, @yLabel, @zLabel" ); |
253 | m_mesh = QAbstract3DSeries::MeshSphere; |
254 | } |
255 | |
256 | QScatter3DSeriesPrivate::~QScatter3DSeriesPrivate() |
257 | { |
258 | } |
259 | |
260 | void QScatter3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy) |
261 | { |
262 | Q_Q(QScatter3DSeries); |
263 | Q_ASSERT(proxy->type() == QAbstractDataProxy::DataTypeScatter); |
264 | |
265 | QAbstract3DSeriesPrivate::setDataProxy(proxy); |
266 | |
267 | emit q->dataProxyChanged(proxy: static_cast<QScatterDataProxy *>(proxy)); |
268 | } |
269 | |
270 | void QScatter3DSeriesPrivate::connectControllerAndProxy(Abstract3DController *newController) |
271 | { |
272 | Q_Q(QScatter3DSeries); |
273 | QScatterDataProxy *scatterDataProxy = static_cast<QScatterDataProxy *>(m_dataProxy); |
274 | |
275 | if (m_controller && scatterDataProxy) { |
276 | //Disconnect old controller/old proxy |
277 | QObject::disconnect(sender: scatterDataProxy, signal: 0, receiver: m_controller, member: 0); |
278 | QObject::disconnect(sender: q_ptr, signal: 0, receiver: m_controller, member: 0); |
279 | } |
280 | |
281 | if (newController && scatterDataProxy) { |
282 | Scatter3DController *controller = static_cast<Scatter3DController *>(newController); |
283 | QObject::connect(sender: scatterDataProxy, signal: &QScatterDataProxy::arrayReset, |
284 | context: controller, slot: &Scatter3DController::handleArrayReset); |
285 | QObject::connect(sender: scatterDataProxy, signal: &QScatterDataProxy::itemsAdded, |
286 | context: controller, slot: &Scatter3DController::handleItemsAdded); |
287 | QObject::connect(sender: scatterDataProxy, signal: &QScatterDataProxy::itemsChanged, |
288 | context: controller, slot: &Scatter3DController::handleItemsChanged); |
289 | QObject::connect(sender: scatterDataProxy, signal: &QScatterDataProxy::itemsRemoved, |
290 | context: controller, slot: &Scatter3DController::handleItemsRemoved); |
291 | QObject::connect(sender: scatterDataProxy, signal: &QScatterDataProxy::itemsInserted, |
292 | context: controller, slot: &Scatter3DController::handleItemsInserted); |
293 | QObject::connect(sender: q, signal: &QScatter3DSeries::dataProxyChanged, |
294 | context: controller, slot: &Scatter3DController::handleArrayReset); |
295 | } |
296 | } |
297 | |
298 | void QScatter3DSeriesPrivate::createItemLabel() |
299 | { |
300 | Q_Q(QScatter3DSeries); |
301 | static const QString xTitleTag(QStringLiteral("@xTitle" )); |
302 | static const QString yTitleTag(QStringLiteral("@yTitle" )); |
303 | static const QString zTitleTag(QStringLiteral("@zTitle" )); |
304 | static const QString xLabelTag(QStringLiteral("@xLabel" )); |
305 | static const QString yLabelTag(QStringLiteral("@yLabel" )); |
306 | static const QString zLabelTag(QStringLiteral("@zLabel" )); |
307 | static const QString seriesNameTag(QStringLiteral("@seriesName" )); |
308 | |
309 | if (m_selectedItem == QScatter3DSeries::invalidSelectionIndex()) { |
310 | m_itemLabel = QString(); |
311 | return; |
312 | } |
313 | |
314 | QValue3DAxis *axisX = static_cast<QValue3DAxis *>(m_controller->axisX()); |
315 | QValue3DAxis *axisY = static_cast<QValue3DAxis *>(m_controller->axisY()); |
316 | QValue3DAxis *axisZ = static_cast<QValue3DAxis *>(m_controller->axisZ()); |
317 | QVector3D selectedPosition = q->dataProxy()->itemAt(index: m_selectedItem)->position(); |
318 | |
319 | m_itemLabel = m_itemLabelFormat; |
320 | |
321 | m_itemLabel.replace(before: xTitleTag, after: axisX->title()); |
322 | m_itemLabel.replace(before: yTitleTag, after: axisY->title()); |
323 | m_itemLabel.replace(before: zTitleTag, after: axisZ->title()); |
324 | |
325 | if (m_itemLabel.contains(s: xLabelTag)) { |
326 | QString valueLabelText = axisX->formatter()->stringForValue( |
327 | value: qreal(selectedPosition.x()), format: axisX->labelFormat()); |
328 | m_itemLabel.replace(before: xLabelTag, after: valueLabelText); |
329 | } |
330 | if (m_itemLabel.contains(s: yLabelTag)) { |
331 | QString valueLabelText = axisY->formatter()->stringForValue( |
332 | value: qreal(selectedPosition.y()), format: axisY->labelFormat()); |
333 | m_itemLabel.replace(before: yLabelTag, after: valueLabelText); |
334 | } |
335 | if (m_itemLabel.contains(s: zLabelTag)) { |
336 | QString valueLabelText = axisZ->formatter()->stringForValue( |
337 | value: qreal(selectedPosition.z()), format: axisZ->labelFormat()); |
338 | m_itemLabel.replace(before: zLabelTag, after: valueLabelText); |
339 | } |
340 | m_itemLabel.replace(before: seriesNameTag, after: m_name); |
341 | } |
342 | |
343 | void QScatter3DSeriesPrivate::setSelectedItem(int index) |
344 | { |
345 | Q_Q(QScatter3DSeries); |
346 | if (index != m_selectedItem) { |
347 | markItemLabelDirty(); |
348 | m_selectedItem = index; |
349 | emit q->selectedItemChanged(index: m_selectedItem); |
350 | } |
351 | } |
352 | |
353 | void QScatter3DSeriesPrivate::setItemSize(float size) |
354 | { |
355 | m_itemSize = size; |
356 | if (m_controller) |
357 | m_controller->markSeriesVisualsDirty(); |
358 | } |
359 | |
360 | QT_END_NAMESPACE |
361 | |