1 | // Copyright (C) 2023 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtGraphs/qareaseries.h> |
5 | #include <QtGraphs/qbarseries.h> |
6 | #include <QtGraphs/qlineseries.h> |
7 | #include <QtGraphs/qpieseries.h> |
8 | #include <QtGraphs/qscatterseries.h> |
9 | #include <QtGraphs/qsplineseries.h> |
10 | #include <private/qgraphsview_p.h> |
11 | #include <private/arearenderer_p.h> |
12 | #include <private/axisrenderer_p.h> |
13 | #include <private/barsrenderer_p.h> |
14 | #include <private/pierenderer_p.h> |
15 | #include <private/pointrenderer_p.h> |
16 | #include <private/qabstractaxis_p.h> |
17 | #include <QtQuick/private/qquickrectangle_p.h> |
18 | #include <QTimer> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | /*! |
23 | \qmltype GraphsView |
24 | \nativetype QGraphsView |
25 | \inqmlmodule QtGraphs |
26 | \ingroup graphs_qml_2D |
27 | \brief Base type for all Qt Graphs views. |
28 | |
29 | This class collects the series and theming together and draws the graphs. |
30 | You will need to import Qt Graphs module to use this type: |
31 | |
32 | \snippet doc_src_qmlgraphs.cpp 0 |
33 | |
34 | After that you can use GraphsView in your qml files: |
35 | |
36 | \snippet doc_src_qmlgraphs.cpp 10 |
37 | |
38 | \image graphsview-minimal.png |
39 | |
40 | \sa BarSeries, LineSeries, BarCategoryAxis, ValueAxis, GraphsTheme |
41 | */ |
42 | |
43 | QGraphsView::QGraphsView(QQuickItem *parent) : |
44 | QQuickItem(parent) |
45 | { |
46 | setFlag(flag: QQuickItem::ItemHasContents); |
47 | setAcceptedMouseButtons(Qt::LeftButton); |
48 | setAcceptHoverEvents(true); |
49 | m_defaultTheme = new QGraphsTheme(this); |
50 | } |
51 | |
52 | QGraphsView::~QGraphsView() |
53 | { |
54 | const auto slist = m_seriesList; |
55 | for (const auto &s : slist) |
56 | removeSeries(series: s); |
57 | if (m_axisX) |
58 | m_axisX->d_func()->setGraph(nullptr); |
59 | if (m_axisY) |
60 | m_axisY->d_func()->setGraph(nullptr); |
61 | } |
62 | |
63 | /*! |
64 | \qmlmethod GraphsView::addSeries(AbstractSeries series) |
65 | Appends a \a series into GraphsView. |
66 | If the \a series is null, it will not be added. If the \a series already |
67 | belongs to the graph, it will be moved into the end. |
68 | */ |
69 | /*! |
70 | Appends a \a series into GraphsView. |
71 | If the \a series is null, it will not be added. If the \a series already |
72 | belongs to the graph, it will be moved into the end. |
73 | */ |
74 | void QGraphsView::addSeries(QObject *series) |
75 | { |
76 | insertSeries(index: m_seriesList.size(), series); |
77 | } |
78 | |
79 | /*! |
80 | \qmlmethod GraphsView::insertSeries(int index, AbstractSeries series) |
81 | Inserts a \a series at the position specified by \a index. |
82 | If the \a series is null, it will not be inserted. If the \a series already |
83 | belongs to the graph, it will be moved into \a index. |
84 | */ |
85 | /*! |
86 | Inserts a \a series at the position specified by \a index. |
87 | If the \a series is null, it will not be inserted. If the \a series already |
88 | belongs to the graph, it will be moved into \a index. |
89 | */ |
90 | void QGraphsView::insertSeries(qsizetype index, QObject *object) |
91 | { |
92 | if (auto series = qobject_cast<QAbstractSeries *>(object)) { |
93 | series->setGraph(this); |
94 | if (m_seriesList.contains(t: series)) { |
95 | qsizetype oldIndex = m_seriesList.indexOf(t: series); |
96 | if (index != oldIndex) { |
97 | m_seriesList.removeOne(t: series); |
98 | if (oldIndex < index) |
99 | index--; |
100 | m_seriesList.insert(i: index, t: series); |
101 | } |
102 | } else { |
103 | m_seriesList.insert(i: index, t: series); |
104 | |
105 | QObject::connect(sender: series, signal: &QAbstractSeries::update, |
106 | context: this, slot: &QGraphsView::polishAndUpdate); |
107 | QObject::connect(sender: series, signal: &QAbstractSeries::hoverEnter, |
108 | context: this, slot: &QGraphsView::handleHoverEnter); |
109 | QObject::connect(sender: series, signal: &QAbstractSeries::hoverExit, |
110 | context: this, slot: &QGraphsView::handleHoverExit); |
111 | QObject::connect(sender: series, signal: &QAbstractSeries::hover, |
112 | context: this, slot: &QGraphsView::handleHover); |
113 | |
114 | if (auto pie = qobject_cast<QPieSeries *>(object: series)) |
115 | connect(sender: pie, signal: &QPieSeries::removed, context: m_pieRenderer, slot: &PieRenderer::markedDeleted); |
116 | } |
117 | polishAndUpdate(); |
118 | } |
119 | } |
120 | |
121 | /*! |
122 | \qmlmethod GraphsView::removeSeries(AbstractSeries series) |
123 | Removes the \a series from the graph. |
124 | */ |
125 | /*! |
126 | Removes the \a series from the graph. |
127 | */ |
128 | void QGraphsView::removeSeries(QObject *object) |
129 | { |
130 | if (auto series = reinterpret_cast<QAbstractSeries *>(object)) { |
131 | series->setGraph(nullptr); |
132 | m_seriesList.removeAll(t: series); |
133 | auto &cleanupSeriesList = m_cleanupSeriesList[getSeriesRendererIndex(series)]; |
134 | |
135 | if (auto pie = qobject_cast<QPieSeries *>(object: series)) |
136 | disconnect(sender: pie, signal: &QPieSeries::removed, receiver: m_pieRenderer, slot: &PieRenderer::markedDeleted); |
137 | |
138 | cleanupSeriesList.append(t: series); |
139 | polishAndUpdate(); |
140 | } |
141 | } |
142 | |
143 | /*! |
144 | \qmlmethod GraphsView::removeSeries(int index) |
145 | Removes the series specified by \a index from the graph. |
146 | */ |
147 | /*! |
148 | Removes the series specified by \a index from the graph. |
149 | */ |
150 | void QGraphsView::removeSeries(qsizetype index) |
151 | { |
152 | if (index >= 0 && index < m_seriesList.size()) |
153 | removeSeries(object: m_seriesList[index]); |
154 | } |
155 | |
156 | /*! |
157 | \qmlmethod bool GraphsView::hasSeries(AbstractSeries series) |
158 | Returns \c true if the \a series is in the graph. |
159 | */ |
160 | /*! |
161 | Returns \c true if the \a series is in the graph. |
162 | */ |
163 | bool QGraphsView::hasSeries(QObject *series) |
164 | { |
165 | return m_seriesList.contains(t: series); |
166 | } |
167 | |
168 | void QGraphsView::addAxis(QAbstractAxis *axis) |
169 | { |
170 | if (axis) { |
171 | axis->d_func()->setGraph(this); |
172 | // Ensure AxisRenderer exists |
173 | createAxisRenderer(); |
174 | polishAndUpdate(); |
175 | QObject::connect(sender: axis, signal: &QAbstractAxis::update, context: this, slot: &QGraphsView::polishAndUpdate); |
176 | QObject::connect(sender: axis, |
177 | signal: &QAbstractAxis::visibleChanged, |
178 | context: this, |
179 | slot: &QGraphsView::updateComponentSizes); |
180 | } |
181 | } |
182 | |
183 | void QGraphsView::removeAxis(QAbstractAxis *axis) |
184 | { |
185 | if (m_axisX == axis) |
186 | m_axisX = nullptr; |
187 | if (m_axisY == axis) |
188 | m_axisY = nullptr; |
189 | } |
190 | |
191 | qsizetype QGraphsView::graphSeriesCount() const |
192 | { |
193 | return m_graphSeriesCount; |
194 | } |
195 | |
196 | void QGraphsView::setGraphSeriesCount(qsizetype count) |
197 | { |
198 | if (count > m_graphSeriesCount) |
199 | m_graphSeriesCount = count; |
200 | } |
201 | |
202 | void QGraphsView::createBarsRenderer() |
203 | { |
204 | if (!m_barsRenderer) { |
205 | m_barsRenderer = new BarsRenderer(this); |
206 | updateComponentSizes(); |
207 | } |
208 | } |
209 | |
210 | void QGraphsView::createAxisRenderer() |
211 | { |
212 | if (!m_axisRenderer) { |
213 | m_axisRenderer = new AxisRenderer(this); |
214 | m_axisRenderer->setZ(-1); |
215 | updateComponentSizes(); |
216 | } |
217 | } |
218 | |
219 | void QGraphsView::createPointRenderer() |
220 | { |
221 | if (!m_pointRenderer) { |
222 | m_pointRenderer = new PointRenderer(this); |
223 | updateComponentSizes(); |
224 | } |
225 | } |
226 | |
227 | void QGraphsView::createPieRenderer() |
228 | { |
229 | if (!m_pieRenderer) { |
230 | m_pieRenderer = new PieRenderer(this); |
231 | updateComponentSizes(); |
232 | } |
233 | } |
234 | |
235 | void QGraphsView::createAreaRenderer() |
236 | { |
237 | if (!m_areaRenderer) { |
238 | m_areaRenderer = new AreaRenderer(this); |
239 | updateComponentSizes(); |
240 | } |
241 | } |
242 | |
243 | /*! |
244 | \property QGraphsView::axisXSmoothing |
245 | \brief Controls the graph X axis smoothing (antialiasing) amount. |
246 | By default, the smoothing is \c 1.0. |
247 | */ |
248 | /*! |
249 | \qmlproperty real GraphsView::axisXSmoothing |
250 | Controls the graph X axis smoothing (antialiasing) amount. |
251 | By default, the smoothing is \c 1.0. |
252 | */ |
253 | qreal QGraphsView::axisXSmoothing() const |
254 | { |
255 | return m_axisXSmoothing; |
256 | } |
257 | |
258 | void QGraphsView::setAxisXSmoothing(qreal smoothing) |
259 | { |
260 | if (qFuzzyCompare(p1: m_axisXSmoothing, p2: smoothing)) |
261 | return; |
262 | m_axisXSmoothing = smoothing; |
263 | emit axisXSmoothingChanged(); |
264 | polishAndUpdate(); |
265 | } |
266 | |
267 | /*! |
268 | \property QGraphsView::axisYSmoothing |
269 | \brief Controls the graph Y axis smoothing (antialiasing) amount. |
270 | By default, the smoothing is \c 1.0. |
271 | */ |
272 | /*! |
273 | \qmlproperty real GraphsView::axisYSmoothing |
274 | Controls the graph Y axis smoothing (antialiasing) amount. |
275 | By default, the smoothing is \c 1.0. |
276 | */ |
277 | qreal QGraphsView::axisYSmoothing() const |
278 | { |
279 | return m_axisYSmoothing; |
280 | } |
281 | |
282 | void QGraphsView::setAxisYSmoothing(qreal smoothing) |
283 | { |
284 | if (qFuzzyCompare(p1: m_axisYSmoothing, p2: smoothing)) |
285 | return; |
286 | m_axisYSmoothing = smoothing; |
287 | emit axisYSmoothingChanged(); |
288 | polishAndUpdate(); |
289 | } |
290 | |
291 | /*! |
292 | \property QGraphsView::gridSmoothing |
293 | \brief Controls the graph grid smoothing (antialiasing) amount. |
294 | By default, the smoothing is \c 1.0. |
295 | */ |
296 | /*! |
297 | \qmlproperty real GraphsView::gridSmoothing |
298 | Controls the graph grid smoothing (antialiasing) amount. |
299 | By default, the smoothing is \c 1.0. |
300 | */ |
301 | qreal QGraphsView::gridSmoothing() const |
302 | { |
303 | return m_gridSmoothing; |
304 | } |
305 | |
306 | void QGraphsView::setGridSmoothing(qreal smoothing) |
307 | { |
308 | if (qFuzzyCompare(p1: m_gridSmoothing, p2: smoothing)) |
309 | return; |
310 | m_gridSmoothing = smoothing; |
311 | emit gridSmoothingChanged(); |
312 | polishAndUpdate(); |
313 | } |
314 | |
315 | /*! |
316 | \property QGraphsView::shadowVisible |
317 | \brief Controls if the graph grid shadow is visible. |
318 | By default, shadow visibility is set to \c false. |
319 | */ |
320 | /*! |
321 | \qmlproperty bool GraphsView::shadowVisible |
322 | Controls if the graph grid shadow is visible. |
323 | By default, shadow visibility is set to \c false. |
324 | */ |
325 | bool QGraphsView::isShadowVisible() const |
326 | { |
327 | return m_isShadowVisible; |
328 | } |
329 | |
330 | void QGraphsView::setShadowVisible(bool newShadowVisibility) |
331 | { |
332 | if (m_isShadowVisible == newShadowVisibility) |
333 | return; |
334 | m_isShadowVisible = newShadowVisibility; |
335 | emit shadowVisibleChanged(); |
336 | polishAndUpdate(); |
337 | } |
338 | |
339 | /*! |
340 | \property QGraphsView::shadowColor |
341 | \brief Controls the graph grid shadow color. |
342 | By default, shadow color is set to \c black. |
343 | */ |
344 | /*! |
345 | \qmlproperty color GraphsView::shadowColor |
346 | Controls the graph grid shadow color. |
347 | By default, shadow color is set to \c black. |
348 | */ |
349 | QColor QGraphsView::shadowColor() const |
350 | { |
351 | return m_shadowColor; |
352 | } |
353 | |
354 | void QGraphsView::setShadowColor(QColor newShadowColor) |
355 | { |
356 | if (m_shadowColor == newShadowColor) |
357 | return; |
358 | m_shadowColor = newShadowColor; |
359 | emit shadowColorChanged(); |
360 | polishAndUpdate(); |
361 | } |
362 | |
363 | /*! |
364 | \property QGraphsView::shadowBarWidth |
365 | \brief Controls the graph grid shadow width. |
366 | By default, shadow width is set to \c 2.0. |
367 | */ |
368 | /*! |
369 | \qmlproperty real GraphsView::shadowBarWidth |
370 | Controls the graph grid shadow width. |
371 | By default, shadow width is set to \c 2.0. |
372 | */ |
373 | qreal QGraphsView::shadowBarWidth() const |
374 | { |
375 | return m_shadowBarWidth; |
376 | } |
377 | |
378 | void QGraphsView::setShadowBarWidth(qreal newShadowBarWidth) |
379 | { |
380 | if (qFuzzyCompare(p1: m_shadowBarWidth, p2: newShadowBarWidth)) |
381 | return; |
382 | m_shadowBarWidth = newShadowBarWidth; |
383 | emit shadowBarWidthChanged(); |
384 | polishAndUpdate(); |
385 | } |
386 | |
387 | /*! |
388 | \property QGraphsView::shadowXOffset |
389 | \brief Controls the graph grid shadow X offset. |
390 | By default, shadow X offset is set to \c 0.0. |
391 | */ |
392 | /*! |
393 | \qmlproperty real GraphsView::shadowXOffset |
394 | Controls the graph grid shadow X offset. |
395 | By default, shadow X offset is set to \c 0.0. |
396 | */ |
397 | qreal QGraphsView::shadowXOffset() const |
398 | { |
399 | return m_shadowXOffset; |
400 | } |
401 | |
402 | void QGraphsView::setShadowXOffset(qreal newShadowXOffset) |
403 | { |
404 | if (qFuzzyCompare(p1: m_shadowXOffset, p2: newShadowXOffset)) |
405 | return; |
406 | m_shadowXOffset = newShadowXOffset; |
407 | emit shadowXOffsetChanged(); |
408 | polishAndUpdate(); |
409 | } |
410 | |
411 | /*! |
412 | \property QGraphsView::shadowYOffset |
413 | \brief Controls the graph grid shadow Y offset. |
414 | By default, shadow Y offset is set to \c 0.0. |
415 | */ |
416 | /*! |
417 | \qmlproperty real GraphsView::shadowYOffset |
418 | Controls the graph grid shadow Y offset. |
419 | By default, shadow Y offset is set to \c 0.0. |
420 | */ |
421 | qreal QGraphsView::shadowYOffset() const |
422 | { |
423 | return m_shadowYOffset; |
424 | } |
425 | |
426 | void QGraphsView::setShadowYOffset(qreal newShadowYOffset) |
427 | { |
428 | if (qFuzzyCompare(p1: m_shadowYOffset, p2: newShadowYOffset)) |
429 | return; |
430 | m_shadowYOffset = newShadowYOffset; |
431 | emit shadowYOffsetChanged(); |
432 | polishAndUpdate(); |
433 | } |
434 | |
435 | /*! |
436 | \property QGraphsView::shadowSmoothing |
437 | \brief Controls the graph grid shadow smoothing (antialiasing) amount. |
438 | By default, shadow smoothing is set to \c 4.0. |
439 | */ |
440 | /*! |
441 | \qmlproperty real GraphsView::shadowSmoothing |
442 | Controls the graph grid shadow smoothing (antialiasing) amount. |
443 | By default, shadow smoothing is set to \c 4.0. |
444 | */ |
445 | qreal QGraphsView::shadowSmoothing() const |
446 | { |
447 | return m_shadowSmoothing; |
448 | } |
449 | |
450 | void QGraphsView::setShadowSmoothing(qreal smoothing) |
451 | { |
452 | if (qFuzzyCompare(p1: m_shadowSmoothing, p2: smoothing)) |
453 | return; |
454 | m_shadowSmoothing = smoothing; |
455 | emit shadowSmoothingChanged(); |
456 | polishAndUpdate(); |
457 | } |
458 | |
459 | void QGraphsView::handleHoverEnter(const QString &seriesName, QPointF position, QPointF value) |
460 | { |
461 | if (m_hoverCount == 0) |
462 | emit hoverEnter(seriesName, position, value); |
463 | m_hoverCount++; |
464 | } |
465 | |
466 | void QGraphsView::handleHoverExit(const QString &seriesName, QPointF position) |
467 | { |
468 | m_hoverCount--; |
469 | if (m_hoverCount == 0) |
470 | emit hoverExit(seriesName, position); |
471 | } |
472 | |
473 | void QGraphsView::handleHover(const QString &seriesName, QPointF position, QPointF value) |
474 | { |
475 | emit hover(seriesName, position, value); |
476 | } |
477 | |
478 | void QGraphsView::updateComponentSizes() |
479 | { |
480 | updateAxisAreas(); |
481 | updatePlotArea(); |
482 | |
483 | if (m_axisRenderer) |
484 | m_axisRenderer->setSize(size()); |
485 | |
486 | if (m_barsRenderer) { |
487 | m_barsRenderer->setX(m_plotArea.x()); |
488 | m_barsRenderer->setY(m_plotArea.y()); |
489 | m_barsRenderer->setSize(m_plotArea.size()); |
490 | } |
491 | if (m_pointRenderer) { |
492 | m_pointRenderer->setX(m_plotArea.x()); |
493 | m_pointRenderer->setY(m_plotArea.y()); |
494 | m_pointRenderer->setSize(m_plotArea.size()); |
495 | } |
496 | if (m_pieRenderer) { |
497 | m_pieRenderer->setX(m_plotArea.x()); |
498 | m_pieRenderer->setY(m_plotArea.y()); |
499 | |
500 | // Remove axis widths and heights as there aren't any in Pie |
501 | auto s = m_plotArea.size(); |
502 | s.setHeight(s.height() + m_axisHeight); |
503 | s.setWidth(s.width() - m_axisWidth); |
504 | |
505 | m_pieRenderer->setSize(s); |
506 | } |
507 | if (m_areaRenderer) { |
508 | m_areaRenderer->setX(m_plotArea.x()); |
509 | m_areaRenderer->setY(m_plotArea.y()); |
510 | m_areaRenderer->setSize(m_plotArea.size()); |
511 | } |
512 | } |
513 | |
514 | void QGraphsView::componentComplete() |
515 | { |
516 | if (!m_theme) { |
517 | m_theme = m_defaultTheme; |
518 | QObject::connect(sender: m_theme, signal: &QGraphsTheme::update, context: this, slot: &QQuickItem::update); |
519 | m_theme->resetColorTheme(); |
520 | } |
521 | QQuickItem::componentComplete(); |
522 | |
523 | ensurePolished(); |
524 | } |
525 | |
526 | void QGraphsView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
527 | { |
528 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
529 | |
530 | updateComponentSizes(); |
531 | |
532 | ensurePolished(); |
533 | } |
534 | |
535 | void QGraphsView::mouseMoveEvent(QMouseEvent *event) |
536 | { |
537 | bool handled = false; |
538 | |
539 | // Adjust event position to renderers position |
540 | QPointF localPos = event->position() - m_plotArea.topLeft(); |
541 | QMouseEvent mappedEvent(event->type(), localPos, event->scenePosition(), |
542 | event->globalPosition(), event->button(), |
543 | event->buttons(), event->modifiers()); |
544 | mappedEvent.setAccepted(false); |
545 | |
546 | if (m_pointRenderer) |
547 | handled |= m_pointRenderer->handleMouseMove(event: &mappedEvent); |
548 | |
549 | if (!handled) |
550 | event->ignore(); |
551 | else |
552 | polishAndUpdate(); |
553 | } |
554 | |
555 | void QGraphsView::mousePressEvent(QMouseEvent *event) |
556 | { |
557 | bool handled = false; |
558 | |
559 | // Adjust event position to renderers position |
560 | QPointF localPos = event->position() - m_plotArea.topLeft(); |
561 | QMouseEvent mappedEvent(event->type(), localPos, event->scenePosition(), |
562 | event->globalPosition(), event->button(), |
563 | event->buttons(), event->modifiers()); |
564 | mappedEvent.setAccepted(false); |
565 | |
566 | if (m_barsRenderer) |
567 | handled |= m_barsRenderer->handleMousePress(event: &mappedEvent); |
568 | |
569 | if (m_pointRenderer) |
570 | handled |= m_pointRenderer->handleMousePress(event: &mappedEvent); |
571 | |
572 | if (m_areaRenderer) |
573 | handled |= m_areaRenderer->handleMousePress(event: &mappedEvent); |
574 | |
575 | if (!handled) |
576 | event->ignore(); |
577 | else |
578 | polishAndUpdate(); |
579 | } |
580 | |
581 | void QGraphsView::mouseReleaseEvent(QMouseEvent *event) |
582 | { |
583 | bool handled = false; |
584 | |
585 | // Adjust event position to renderers position |
586 | QPointF localPos = event->position() - m_plotArea.topLeft(); |
587 | QMouseEvent mappedEvent(event->type(), localPos, event->scenePosition(), |
588 | event->globalPosition(), event->button(), |
589 | event->buttons(), event->modifiers()); |
590 | mappedEvent.setAccepted(false); |
591 | |
592 | if (m_pointRenderer) |
593 | handled |= m_pointRenderer->handleMouseRelease(event: &mappedEvent); |
594 | |
595 | if (!handled) |
596 | event->ignore(); |
597 | else |
598 | polishAndUpdate(); |
599 | } |
600 | |
601 | void QGraphsView::hoverMoveEvent(QHoverEvent *event) |
602 | { |
603 | bool handled = false; |
604 | |
605 | // Adjust event position to renderers position |
606 | QPointF localPos = event->position() - m_plotArea.topLeft(); |
607 | QHoverEvent mappedEvent(event->type(), localPos,event->globalPosition(), |
608 | event->oldPosF(), event->modifiers()); |
609 | mappedEvent.setAccepted(false); |
610 | |
611 | if (m_barsRenderer) |
612 | handled |= m_barsRenderer->handleHoverMove(event: &mappedEvent); |
613 | |
614 | if (m_pointRenderer) |
615 | handled |= m_pointRenderer->handleHoverMove(event: &mappedEvent); |
616 | |
617 | if (m_areaRenderer) |
618 | handled |= m_areaRenderer->handleHoverMove(event: &mappedEvent); |
619 | |
620 | if (!handled) |
621 | event->ignore(); |
622 | } |
623 | |
624 | QSGNode *QGraphsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) |
625 | { |
626 | Q_UNUSED(updatePaintNodeData); |
627 | |
628 | for (auto series : std::as_const(t&: m_seriesList)) { |
629 | if (m_barsRenderer) { |
630 | if (auto barSeries = qobject_cast<QBarSeries *>(object: series)) |
631 | m_barsRenderer->updateSeries(series: barSeries); |
632 | } |
633 | |
634 | if (m_pointRenderer) { |
635 | if (auto lineSeries = qobject_cast<QLineSeries *>(object: series)) |
636 | m_pointRenderer->updateSeries(series: lineSeries); |
637 | |
638 | if (auto scatterSeries = qobject_cast<QScatterSeries *>(object: series)) |
639 | m_pointRenderer->updateSeries(series: scatterSeries); |
640 | |
641 | if (auto splineSeries = qobject_cast<QSplineSeries *>(object: series)) |
642 | m_pointRenderer->updateSeries(series: splineSeries); |
643 | } |
644 | |
645 | if (m_pieRenderer) { |
646 | if (auto pieSeries = qobject_cast<QPieSeries *>(object: series)) |
647 | m_pieRenderer->updateSeries(series: pieSeries); |
648 | } |
649 | |
650 | if (m_areaRenderer) { |
651 | if (auto areaSeries = qobject_cast<QAreaSeries *>(object: series)) |
652 | m_areaRenderer->updateSeries(series: areaSeries); |
653 | } |
654 | } |
655 | |
656 | if (m_barsRenderer) { |
657 | auto &cleanupSeriesList = m_cleanupSeriesList[0]; |
658 | m_barsRenderer->afterUpdate(cleanupSeries&: cleanupSeriesList); |
659 | cleanupSeriesList.clear(); |
660 | } |
661 | if (m_pointRenderer) { |
662 | auto &cleanupSeriesList = m_cleanupSeriesList[1]; |
663 | m_pointRenderer->afterUpdate(cleanupSeries&: cleanupSeriesList); |
664 | cleanupSeriesList.clear(); |
665 | } |
666 | if (m_areaRenderer) { |
667 | auto &cleanupSeriesList = m_cleanupSeriesList[2]; |
668 | m_areaRenderer->afterUpdate(cleanupSeries&: cleanupSeriesList); |
669 | } |
670 | if (m_pieRenderer) { |
671 | auto &cleanupSeriesList = m_cleanupSeriesList[3]; |
672 | m_pieRenderer->afterUpdate(cleanupSeries&: cleanupSeriesList); |
673 | cleanupSeriesList.clear(); |
674 | } |
675 | |
676 | // Now possibly dirty theme has been taken into use |
677 | m_theme->resetThemeDirty(); |
678 | |
679 | return oldNode; |
680 | } |
681 | |
682 | void QGraphsView::updatePolish() |
683 | { |
684 | if (m_axisRenderer) { |
685 | m_axisRenderer->handlePolish(); |
686 | // Initialize shaders after system's event queue |
687 | QTimer::singleShot(interval: 0, receiver: m_axisRenderer, slot: &AxisRenderer::initialize); |
688 | } |
689 | if (m_theme && m_theme->isBackgroundVisible()) { |
690 | if (!m_backgroundRectangle) { |
691 | // Create m_backgroundRectangle only when it is needed |
692 | m_backgroundRectangle = new QQuickRectangle(this); |
693 | m_backgroundRectangle->setZ(-2); |
694 | } |
695 | m_backgroundRectangle->setColor(m_theme->backgroundColor()); |
696 | m_backgroundRectangle->setWidth(width()); |
697 | m_backgroundRectangle->setHeight(height()); |
698 | m_backgroundRectangle->setVisible(true); |
699 | } else if (m_backgroundRectangle) { |
700 | // Hide and delete the m_backgroundRectangle |
701 | m_backgroundRectangle->setVisible(false); |
702 | m_backgroundRectangle->deleteLater(); |
703 | m_backgroundRectangle = nullptr; |
704 | } |
705 | |
706 | // Polish for all series |
707 | for (auto series : std::as_const(t&: m_seriesList)) { |
708 | if (m_barsRenderer) { |
709 | if (auto barSeries = qobject_cast<QBarSeries*>(object: series)) |
710 | m_barsRenderer->handlePolish(series: barSeries); |
711 | } |
712 | |
713 | if (m_pointRenderer) { |
714 | if (auto lineSeries = qobject_cast<QLineSeries *>(object: series)) |
715 | m_pointRenderer->handlePolish(series: lineSeries); |
716 | |
717 | if (auto scatterSeries = qobject_cast<QScatterSeries *>(object: series)) |
718 | m_pointRenderer->handlePolish(series: scatterSeries); |
719 | |
720 | if (auto splineSeries = qobject_cast<QSplineSeries *>(object: series)) { |
721 | m_pointRenderer->handlePolish(series: splineSeries); |
722 | } |
723 | } |
724 | |
725 | if (m_pieRenderer) { |
726 | if (auto pieSeries = qobject_cast<QPieSeries *>(object: series)) |
727 | m_pieRenderer->handlePolish(series: pieSeries); |
728 | } |
729 | |
730 | if (m_areaRenderer) { |
731 | if (auto areaSeries = qobject_cast<QAreaSeries *>(object: series)) |
732 | m_areaRenderer->handlePolish(series: areaSeries); |
733 | } |
734 | } |
735 | |
736 | if (m_barsRenderer) { |
737 | auto &cleanupSeriesList = m_cleanupSeriesList[0]; |
738 | m_barsRenderer->afterPolish(cleanupSeries&: cleanupSeriesList); |
739 | } |
740 | if (m_pointRenderer) { |
741 | auto &cleanupSeriesList = m_cleanupSeriesList[1]; |
742 | m_pointRenderer->afterPolish(cleanupSeries&: cleanupSeriesList); |
743 | } |
744 | if (m_areaRenderer) { |
745 | auto &cleanupSeriesList = m_cleanupSeriesList[2]; |
746 | m_areaRenderer->afterPolish(cleanupSeries&: cleanupSeriesList); |
747 | } |
748 | if (m_pieRenderer) { |
749 | auto &cleanupSeriesList = m_cleanupSeriesList[3]; |
750 | m_pieRenderer->afterPolish(cleanupSeries&: cleanupSeriesList); |
751 | } |
752 | } |
753 | |
754 | void QGraphsView::polishAndUpdate() |
755 | { |
756 | polish(); |
757 | update(); |
758 | } |
759 | |
760 | // ***** Static QQmlListProperty methods ***** |
761 | |
762 | /*! |
763 | \qmlproperty list GraphsView::seriesList |
764 | |
765 | List of series that are rendered by the GraphsView. Filled automatically |
766 | with the series type children of the GraphsView. |
767 | |
768 | This is the default property, so child elements are automatically added |
769 | into the series list. |
770 | \sa BarSeries, LineSeries, ScatterSeries |
771 | */ |
772 | QQmlListProperty<QObject> QGraphsView::seriesList() |
773 | { |
774 | return QQmlListProperty<QObject>(this, this, |
775 | &QGraphsView::appendSeriesFunc, |
776 | &QGraphsView::countSeriesFunc, |
777 | &QGraphsView::atSeriesFunc, |
778 | &QGraphsView::clearSeriesFunc); |
779 | } |
780 | |
781 | void QGraphsView::appendSeriesFunc(QQmlListProperty<QObject> *list, QObject *series) |
782 | { |
783 | reinterpret_cast<QGraphsView *>(list->data)->addSeries(series); |
784 | } |
785 | |
786 | qsizetype QGraphsView::countSeriesFunc(QQmlListProperty<QObject> *list) |
787 | { |
788 | return reinterpret_cast<QGraphsView *>(list->data)->getSeriesList().size(); |
789 | } |
790 | |
791 | QObject *QGraphsView::atSeriesFunc(QQmlListProperty<QObject> *list, qsizetype index) |
792 | { |
793 | return reinterpret_cast<QGraphsView *>(list->data)->getSeriesList().at(i: index); |
794 | } |
795 | |
796 | void QGraphsView::clearSeriesFunc(QQmlListProperty<QObject> *list) |
797 | { |
798 | QGraphsView *declItems = reinterpret_cast<QGraphsView *>(list->data); |
799 | QList<QObject *> realList = declItems->getSeriesList(); |
800 | qsizetype count = realList.size(); |
801 | for (int i = 0; i < count; i++) |
802 | declItems->removeSeries(object: realList.at(i)); |
803 | } |
804 | |
805 | /*! |
806 | \qmlproperty GraphsTheme GraphsView::theme |
807 | The theme used by the graph. Determines coloring, |
808 | axis lines, fonts etc. If theme has not been set, |
809 | the default theme is used. |
810 | */ |
811 | QGraphsTheme *QGraphsView::theme() const |
812 | { |
813 | return m_theme; |
814 | } |
815 | |
816 | void QGraphsView::setTheme(QGraphsTheme *newTheme) |
817 | { |
818 | if (m_theme == newTheme) |
819 | return; |
820 | |
821 | if (m_theme) |
822 | QObject::disconnect(sender: m_theme, signal: nullptr, receiver: this, member: nullptr); |
823 | |
824 | m_theme = newTheme; |
825 | |
826 | if (!m_theme) { |
827 | m_theme = m_defaultTheme; |
828 | m_theme->resetColorTheme(); |
829 | } |
830 | |
831 | QObject::connect(sender: m_theme, signal: &QGraphsTheme::update, context: this, slot: &QGraphsView::polishAndUpdate); |
832 | emit themeChanged(); |
833 | polishAndUpdate(); |
834 | } |
835 | |
836 | /*! |
837 | \qmlproperty real GraphsView::marginTop |
838 | The amount of empty space on the top of the graph. |
839 | By default, the margin is 20. |
840 | */ |
841 | qreal QGraphsView::marginTop() const |
842 | { |
843 | return m_marginTop; |
844 | } |
845 | |
846 | void QGraphsView::setMarginTop(qreal newMarginTop) |
847 | { |
848 | if (qFuzzyCompare(p1: m_marginTop, p2: newMarginTop)) |
849 | return; |
850 | m_marginTop = newMarginTop; |
851 | updateComponentSizes(); |
852 | polishAndUpdate(); |
853 | emit marginTopChanged(); |
854 | } |
855 | |
856 | /*! |
857 | \qmlproperty real GraphsView::marginBottom |
858 | The amount of empty space on the bottom of the graph. |
859 | By default, the margin is 20. |
860 | */ |
861 | qreal QGraphsView::marginBottom() const |
862 | { |
863 | return m_marginBottom; |
864 | } |
865 | |
866 | void QGraphsView::setMarginBottom(qreal newMarginBottom) |
867 | { |
868 | if (qFuzzyCompare(p1: m_marginBottom, p2: newMarginBottom)) |
869 | return; |
870 | m_marginBottom = newMarginBottom; |
871 | updateComponentSizes(); |
872 | polishAndUpdate(); |
873 | emit marginBottomChanged(); |
874 | } |
875 | |
876 | /*! |
877 | \qmlproperty real GraphsView::marginLeft |
878 | The amount of empty space on the left of the graph. |
879 | By default, the margin is 20. |
880 | */ |
881 | qreal QGraphsView::marginLeft() const |
882 | { |
883 | return m_marginLeft; |
884 | } |
885 | |
886 | void QGraphsView::setMarginLeft(qreal newMarginLeft) |
887 | { |
888 | if (qFuzzyCompare(p1: m_marginLeft, p2: newMarginLeft)) |
889 | return; |
890 | m_marginLeft = newMarginLeft; |
891 | updateComponentSizes(); |
892 | polishAndUpdate(); |
893 | emit marginLeftChanged(); |
894 | } |
895 | |
896 | /*! |
897 | \qmlproperty real GraphsView::marginRight |
898 | The amount of empty space on the right of the graph. |
899 | By default, the margin is 20. |
900 | */ |
901 | qreal QGraphsView::marginRight() const |
902 | { |
903 | return m_marginRight; |
904 | } |
905 | |
906 | void QGraphsView::setMarginRight(qreal newMarginRight) |
907 | { |
908 | if (qFuzzyCompare(p1: m_marginRight, p2: newMarginRight)) |
909 | return; |
910 | m_marginRight = newMarginRight; |
911 | updateComponentSizes(); |
912 | polishAndUpdate(); |
913 | emit marginRightChanged(); |
914 | } |
915 | |
916 | QRectF QGraphsView::plotArea() const |
917 | { |
918 | return m_plotArea; |
919 | } |
920 | |
921 | void QGraphsView::updateAxisAreas() |
922 | { |
923 | if (m_axisX && !m_axisX->isVisible()) { |
924 | m_axisXLabelsMargin = 0; |
925 | m_axisTickersHeight = 0; |
926 | m_axisLabelsHeight = 0; |
927 | } else { |
928 | m_axisTickersHeight = m_defaultAxisTickersHeight; |
929 | m_axisLabelsHeight = m_defaultAxisLabelsHeight; |
930 | m_axisXLabelsMargin = m_defaultAxisXLabelsMargin; |
931 | } |
932 | |
933 | if (m_axisY && !m_axisY->isVisible()) { |
934 | m_axisTickersWidth = 0; |
935 | m_axisLabelsWidth = 0; |
936 | m_axisYLabelsMargin = 0; |
937 | } else { |
938 | m_axisLabelsWidth = m_defaultAxisLabelsWidth; |
939 | m_axisTickersWidth = m_defaultAxisTickersWidth; |
940 | m_axisYLabelsMargin = m_defaultAxisYLabelsMargin; |
941 | } |
942 | |
943 | QRectF r = { m_marginLeft, |
944 | m_marginTop, |
945 | width() - m_marginLeft - m_marginRight, |
946 | height() - m_marginTop - m_marginBottom }; |
947 | m_axisHeight = m_axisLabelsHeight + m_axisXLabelsMargin + m_axisTickersHeight; |
948 | m_axisWidth = m_axisLabelsWidth + m_axisYLabelsMargin + m_axisTickersWidth; |
949 | float leftPadding = m_axisWidth; |
950 | float topPadding = 0; |
951 | m_xAxisArea = { r.x() + leftPadding, r.y() + r.height() - m_axisHeight, |
952 | r.width() - m_axisWidth, m_axisHeight }; |
953 | m_xAxisLabelsArea = { m_xAxisArea.x(), |
954 | m_xAxisArea.y() + m_axisTickersHeight + m_axisXLabelsMargin, |
955 | m_xAxisArea.width(), |
956 | m_axisTickersHeight }; |
957 | m_xAxisTickersArea = { m_xAxisArea.x(), |
958 | m_xAxisArea.y(), |
959 | m_xAxisArea.width(), |
960 | m_axisTickersHeight }; |
961 | m_yAxisArea = { r.x(), r.y() + topPadding, m_axisWidth, r.height() - m_axisHeight }; |
962 | m_yAxisLabelsArea = { m_yAxisArea.x(), |
963 | m_yAxisArea.y(), |
964 | m_axisLabelsWidth, |
965 | m_yAxisArea.height() }; |
966 | m_yAxisTickersArea = { m_yAxisArea.x() + m_axisLabelsWidth + m_axisYLabelsMargin, |
967 | m_yAxisArea.y(), |
968 | m_axisTickersWidth, |
969 | m_yAxisArea.height() }; |
970 | } |
971 | |
972 | void QGraphsView::updatePlotArea() |
973 | { |
974 | // When axis are in left & bottom |
975 | qreal x = m_marginLeft; |
976 | qreal y = m_marginTop; |
977 | qreal w = width() - x - m_marginRight; |
978 | qreal h = height() - y - m_marginBottom; |
979 | x += m_axisWidth; |
980 | h -= m_axisHeight; |
981 | w -= m_axisWidth; |
982 | w = qMax(a: w, b: 0.0); |
983 | h = qMax(a: h, b: 0.0); |
984 | QRectF plotArea = QRectF(x, y, w, h); |
985 | if (plotArea != m_plotArea) |
986 | m_plotArea = plotArea; |
987 | } |
988 | |
989 | /*! |
990 | \property QGraphsView::axisX |
991 | \brief X-axis of this view. |
992 | |
993 | The x-axis used for the series inside this view. |
994 | */ |
995 | /*! |
996 | \qmlproperty AbstractAxis GraphsView::axisX |
997 | The x-axis used for the series inside this view. |
998 | \sa axisY |
999 | */ |
1000 | |
1001 | QAbstractAxis *QGraphsView::axisX() const |
1002 | { |
1003 | return m_axisX; |
1004 | } |
1005 | |
1006 | void QGraphsView::setAxisX(QAbstractAxis *axis) |
1007 | { |
1008 | if (m_axisX == axis) |
1009 | return; |
1010 | removeAxis(axis: m_axisX); |
1011 | m_axisX = axis; |
1012 | if (axis) |
1013 | addAxis(axis); |
1014 | emit axisXChanged(); |
1015 | emit update(); |
1016 | } |
1017 | |
1018 | /*! |
1019 | \property QGraphsView::axisY |
1020 | \brief Y-axis of this view. |
1021 | |
1022 | The y-axis used for the series inside this view. |
1023 | */ |
1024 | /*! |
1025 | \qmlproperty AbstractAxis GraphsView::axisY |
1026 | The y-axis used for the series inside this view. |
1027 | \sa axisX |
1028 | */ |
1029 | |
1030 | QAbstractAxis *QGraphsView::axisY() const |
1031 | { |
1032 | return m_axisY; |
1033 | } |
1034 | |
1035 | void QGraphsView::setAxisY(QAbstractAxis *axis) |
1036 | { |
1037 | if (m_axisY == axis) |
1038 | return; |
1039 | removeAxis(axis: m_axisY); |
1040 | m_axisY = axis; |
1041 | if (axis) |
1042 | addAxis(axis); |
1043 | emit axisYChanged(); |
1044 | emit update(); |
1045 | } |
1046 | |
1047 | /*! |
1048 | \property QGraphsView::orientation |
1049 | \brief Orientation of the GraphsView. |
1050 | |
1051 | Determines the orientation of the QGraphsView. When the orientation is |
1052 | \l {Qt::Horizontal}{Qt::Horizontal}, \l axisX and \l axisY will switch the |
1053 | positions so that \l axisX is rendered vertically and \l axisY horizontally. |
1054 | This property is currently used by the \l QBarSeries. |
1055 | The default value is \l {Qt::Vertical}{Qt::Vertical}. |
1056 | */ |
1057 | /*! |
1058 | \qmlproperty Qt.Orientation GraphsView::orientation |
1059 | Determines the orientation of the GraphsView. When the orientation is |
1060 | \l {Qt::Horizontal}{Qt.Horizontal}, \l axisX and \l axisY will switch the |
1061 | positions so that \l axisX is rendered vertically and \l axisY horizontally. |
1062 | This property is currently used by the \l BarSeries. |
1063 | The default value is \l {Qt::Vertical}{Qt.Vertical}. |
1064 | */ |
1065 | Qt::Orientation QGraphsView::orientation() const |
1066 | { |
1067 | return m_orientation; |
1068 | } |
1069 | |
1070 | void QGraphsView::setOrientation(Qt::Orientation newOrientation) |
1071 | { |
1072 | if (m_orientation == newOrientation) |
1073 | return; |
1074 | m_orientation = newOrientation; |
1075 | emit orientationChanged(); |
1076 | emit update(); |
1077 | } |
1078 | |
1079 | int QGraphsView::getSeriesRendererIndex(QAbstractSeries *series) |
1080 | { |
1081 | int index = 0; |
1082 | if (series) { |
1083 | switch (series->type()) { |
1084 | case QAbstractSeries::SeriesType::Bar: |
1085 | index = 0; |
1086 | break; |
1087 | case QAbstractSeries::SeriesType::Scatter: |
1088 | case QAbstractSeries::SeriesType::Line: |
1089 | case QAbstractSeries::SeriesType::Spline: |
1090 | index = 1; |
1091 | break; |
1092 | case QAbstractSeries::SeriesType::Area: |
1093 | index = 2; |
1094 | break; |
1095 | case QAbstractSeries::SeriesType::Pie: |
1096 | index = 3; |
1097 | break; |
1098 | } |
1099 | } |
1100 | return index; |
1101 | } |
1102 | |
1103 | QT_END_NAMESPACE |
1104 |
Definitions
- QGraphsView
- ~QGraphsView
- addSeries
- insertSeries
- removeSeries
- removeSeries
- hasSeries
- addAxis
- removeAxis
- graphSeriesCount
- setGraphSeriesCount
- createBarsRenderer
- createAxisRenderer
- createPointRenderer
- createPieRenderer
- createAreaRenderer
- axisXSmoothing
- setAxisXSmoothing
- axisYSmoothing
- setAxisYSmoothing
- gridSmoothing
- setGridSmoothing
- isShadowVisible
- setShadowVisible
- shadowColor
- setShadowColor
- shadowBarWidth
- setShadowBarWidth
- shadowXOffset
- setShadowXOffset
- shadowYOffset
- setShadowYOffset
- shadowSmoothing
- setShadowSmoothing
- handleHoverEnter
- handleHoverExit
- handleHover
- updateComponentSizes
- componentComplete
- geometryChange
- mouseMoveEvent
- mousePressEvent
- mouseReleaseEvent
- hoverMoveEvent
- updatePaintNode
- updatePolish
- polishAndUpdate
- seriesList
- appendSeriesFunc
- countSeriesFunc
- atSeriesFunc
- clearSeriesFunc
- theme
- setTheme
- marginTop
- setMarginTop
- marginBottom
- setMarginBottom
- marginLeft
- setMarginLeft
- marginRight
- setMarginRight
- plotArea
- updateAxisAreas
- updatePlotArea
- axisX
- setAxisX
- axisY
- setAxisY
- orientation
- setOrientation
Learn Advanced QML with KDAB
Find out more