1 | // Copyright (C) 2017 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 "declarativechart_p.h" |
7 | #include <QtGui/QPainter> |
8 | #if QT_CONFIG(charts_line_chart) |
9 | #include "declarativelineseries_p.h" |
10 | #endif |
11 | #if QT_CONFIG(charts_area_chart) |
12 | #include "declarativeareaseries_p.h" |
13 | #endif |
14 | #include "declarativebarseries_p.h" |
15 | #if QT_CONFIG(charts_pie_chart) |
16 | #include "declarativepieseries_p.h" |
17 | #endif |
18 | #if QT_CONFIG(charts_spline_chart) |
19 | #include "declarativesplineseries_p.h" |
20 | #endif |
21 | #if QT_CONFIG(charts_boxplot_chart) |
22 | #include "declarativeboxplotseries_p.h" |
23 | #endif |
24 | #if QT_CONFIG(charts_candlestick_chart) |
25 | #include "declarativecandlestickseries_p.h" |
26 | #endif |
27 | #if QT_CONFIG(charts_scatter_chart) |
28 | #include "declarativescatterseries_p.h" |
29 | #endif |
30 | #include "declarativechartnode_p.h" |
31 | #include "declarativeabstractrendernode_p.h" |
32 | #include "declarativemargins_p.h" |
33 | #include "declarativeaxes_p.h" |
34 | #include <QtCharts/private/qabstractseries_p.h> |
35 | #include <QtCharts/private/chartdataset_p.h> |
36 | #include <QtCharts/private/qchart_p.h> |
37 | #include <QtCharts/private/chartpresenter_p.h> |
38 | #include <QtCharts/QBarCategoryAxis> |
39 | #include <QtCharts/QValueAxis> |
40 | #include <QtCharts/QLogValueAxis> |
41 | #include <QtCharts/QCategoryAxis> |
42 | #include <QtCharts/QPolarChart> |
43 | #include <QtWidgets/QGraphicsSceneMouseEvent> |
44 | #include <QtWidgets/QGraphicsSceneHoverEvent> |
45 | #include <QtWidgets/QApplication> |
46 | #include <QtCore/QTimer> |
47 | #include <QtCore/QThread> |
48 | #include <QtQuick/QQuickWindow> |
49 | #include <QtWidgets/QGraphicsLayout> |
50 | |
51 | #if QT_CONFIG(charts_datetime_axis) |
52 | #include <QtCharts/QDateTimeAxis> |
53 | #endif |
54 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | /*! |
58 | \qmltype ChartView |
59 | \inqmlmodule QtCharts |
60 | |
61 | \brief Manages the graphical representation of the chart's series, legends, |
62 | and axes. |
63 | |
64 | The ChartView type displays different series types as charts. |
65 | |
66 | This example shows how to create a simple line chart: |
67 | |
68 | \image examples_qmlchart2.png |
69 | \snippet qmlchartsgallery/qml/LineSeries.qml 1 |
70 | */ |
71 | |
72 | /*! |
73 | \qmlproperty enumeration ChartView::theme |
74 | |
75 | The theme used by the chart. |
76 | |
77 | A theme is a built-in collection of UI style related settings applied to all |
78 | the visual elements of a chart, such as colors, pens, brushes, and fonts of |
79 | series, as well as axes, title, and legend. The \l {Qml Oscilloscope} |
80 | example illustrates how to set a theme. |
81 | |
82 | \note Changing the theme will overwrite all customizations previously |
83 | applied to the series. |
84 | |
85 | The following values are supported: |
86 | |
87 | \value ChartView.ChartThemeLight |
88 | The light theme, which is the default theme. |
89 | \value ChartView.ChartThemeBlueCerulean |
90 | The cerulean blue theme. |
91 | \value ChartView.ChartThemeDark |
92 | The dark theme. |
93 | \value ChartView.ChartThemeBrownSand |
94 | The sand brown theme. |
95 | \value ChartView.ChartThemeBlueNcs |
96 | The natural color system (NCS) blue theme. |
97 | \value ChartView.ChartThemeHighContrast |
98 | The high contrast theme. |
99 | \value ChartView.ChartThemeBlueIcy |
100 | The icy blue theme. |
101 | \value ChartView.ChartThemeQt |
102 | The Qt theme. |
103 | */ |
104 | |
105 | /*! |
106 | \qmlproperty enumeration ChartView::animationOptions |
107 | |
108 | The animations enabled in the chart: |
109 | |
110 | \value ChartView.NoAnimation |
111 | Animation is disabled in the chart. This is the default value. |
112 | \value ChartView.GridAxisAnimations |
113 | Grid axis animation is enabled in the chart. |
114 | \value ChartView.SeriesAnimations |
115 | Series animation is enabled in the chart. |
116 | \value ChartView.AllAnimations |
117 | All animation types are enabled in the chart. |
118 | */ |
119 | |
120 | /*! |
121 | \qmlproperty int ChartView::animationDuration |
122 | The duration of the animation for the chart. |
123 | */ |
124 | |
125 | /*! |
126 | \qmlproperty easing ChartView::animationEasingCurve |
127 | The easing curve of the animation for the chart. |
128 | */ |
129 | |
130 | /*! |
131 | \qmlproperty font ChartView::titleFont |
132 | The title font of the chart. |
133 | |
134 | For more information, see \l [QML]{font}. |
135 | */ |
136 | |
137 | /*! |
138 | \qmlproperty string ChartView::title |
139 | The title is shown as a headline on top of the chart. Chart titles support |
140 | HTML formatting. |
141 | |
142 | \sa titleColor |
143 | */ |
144 | |
145 | /*! |
146 | \qmlproperty color ChartView::titleColor |
147 | The color of the title text. |
148 | */ |
149 | |
150 | /*! |
151 | \qmlproperty Legend ChartView::legend |
152 | The legend of the chart. The legend lists all the series, pie slices, and bar |
153 | sets added to the chart. |
154 | */ |
155 | |
156 | /*! |
157 | \qmlproperty int ChartView::count |
158 | The number of series added to the chart. |
159 | */ |
160 | |
161 | /*! |
162 | \qmlproperty color ChartView::backgroundColor |
163 | The color of the chart's background. By default, the background color is |
164 | specified by the chart theme. |
165 | |
166 | \sa theme |
167 | */ |
168 | |
169 | /*! |
170 | \qmlproperty real ChartView::backgroundRoundness |
171 | The diameter of the rounding circle at the corners of the chart background. |
172 | */ |
173 | |
174 | /*! |
175 | \qmlproperty color ChartView::plotAreaColor |
176 | The color of the background of the chart's plot area. By default, the plot |
177 | area background uses the chart's background color, which is specified by the |
178 | chart theme. |
179 | |
180 | \sa backgroundColor, theme |
181 | */ |
182 | |
183 | /*! |
184 | \qmlproperty list<AbstractAxis> ChartView::axes |
185 | The axes of the chart. |
186 | */ |
187 | |
188 | /*! |
189 | \qmlproperty bool ChartView::dropShadowEnabled |
190 | Whether the background drop shadow effect is enabled. |
191 | |
192 | If set to \c true, the background drop shadow effect is enabled. If set to |
193 | \c false, it is disabled. |
194 | */ |
195 | |
196 | /*! |
197 | \qmlproperty rect ChartView::plotArea |
198 | The rectangle within which the chart is drawn. |
199 | |
200 | The plot area does not include the area defined by margins. By default this will resize if inside |
201 | a ChartView. If an explicit rectangle is set for the plot area then it will respect this, to revert |
202 | back to the default behavior, then setting it to \c{Qt.rect(0, 0, 0, 0)} will achieve this. |
203 | |
204 | \sa margins |
205 | */ |
206 | |
207 | /*! |
208 | \qmlproperty Margins ChartView::margins |
209 | The minimum margins allowed between the edge of the chart rectangle and the |
210 | plot area. The margins are used for drawing the title, axes, and legend. |
211 | */ |
212 | |
213 | /*! |
214 | \qmlproperty bool ChartView::localizeNumbers |
215 | \since QtCharts 2.0 |
216 | |
217 | Whether numbers are localized. |
218 | |
219 | When \c true, all generated numbers appearing in various series and axis |
220 | labels will be localized using the QLocale set with the \l locale property. |
221 | When \c{false}, the \e C locale is always used. Defaults to \c{false}. |
222 | |
223 | \note This property does not affect DateTimeAxis labels, which always use the |
224 | QLocale set with the locale property. |
225 | |
226 | \sa locale |
227 | */ |
228 | |
229 | /*! |
230 | \qmlproperty locale ChartView::locale |
231 | \since QtCharts 2.0 |
232 | |
233 | The locale used to format various chart labels. |
234 | |
235 | Labels are localized only when \l localizeNumbers is \c true, except for |
236 | DateTimeAxis labels, which always use the QLocale set with this property. |
237 | |
238 | Defaults to the application default locale at the time when the chart is |
239 | constructed. |
240 | |
241 | \sa localizeNumbers |
242 | */ |
243 | |
244 | /*! |
245 | \qmlmethod AbstractSeries ChartView::series(int index) |
246 | Returns the series with the index \a index on the chart. Together with the |
247 | \l count property of the chart, this enables looping through the series of a |
248 | chart. |
249 | |
250 | \sa count |
251 | */ |
252 | |
253 | /*! |
254 | \qmlmethod AbstractSeries ChartView::series(string name) |
255 | Returns the first series in the chart with the name \a name. If there is no |
256 | series with that name, returns null. |
257 | */ |
258 | |
259 | /*! |
260 | \qmlmethod AbstractSeries ChartView::createSeries(enumeration type, string name, AbstractAxis axisX, AbstractAxis axisY) |
261 | Adds a series of the type \a type to the chart with the name \a name |
262 | and, optionally, the axes \a axisX and \a axisY. For example: |
263 | \code |
264 | // lineSeries is a LineSeries object that has already been added to the ChartView; re-use its axes |
265 | var myAxisX = chartView.axisX(lineSeries); |
266 | var myAxisY = chartView.axisY(lineSeries); |
267 | var scatter = chartView.createSeries(ChartView.SeriesTypeScatter, "scatter series", myAxisX, myAxisY); |
268 | \endcode |
269 | |
270 | The following enumeration values can be used as values of \a type: |
271 | |
272 | \value ChartView.SeriesTypeLine |
273 | A line series. |
274 | \value ChartView.SeriesTypeArea |
275 | An area series. |
276 | \value ChartView.SeriesTypeBar |
277 | A bar series. |
278 | \value ChartView.SeriesTypeStackedBar |
279 | A stacked bar series. |
280 | \value ChartView.SeriesTypePercentBar |
281 | A percent bar series. |
282 | \value ChartView.SeriesTypeBoxPlot |
283 | A box plot series. |
284 | \value ChartView.SeriesTypeCandlestick |
285 | A candlestick series. |
286 | \value ChartView.SeriesTypePie |
287 | A pie series. |
288 | \value ChartView.SeriesTypeScatter |
289 | A scatter series. |
290 | \value ChartView.SeriesTypeSpline |
291 | A spline series. |
292 | \value ChartView.SeriesTypeHorizontalBar |
293 | A horizontal bar series. |
294 | \value ChartView.SeriesTypeHorizontalStackedBar |
295 | A horizontal stacked bar series. |
296 | \value ChartView.SeriesTypeHorizontalPercentBar |
297 | A horizontal percent bar series. |
298 | */ |
299 | |
300 | /*! |
301 | \qmlmethod ChartView::removeSeries(AbstractSeries series) |
302 | Removes the series \a series from the chart and permanently deletes the series |
303 | object. |
304 | */ |
305 | |
306 | /*! |
307 | \qmlmethod ChartView::removeAllSeries() |
308 | Removes all series from the chart and permanently deletes all the series |
309 | objects. |
310 | */ |
311 | |
312 | /*! |
313 | \qmlmethod Axis ChartView::axisX(AbstractSeries series) |
314 | The x-axis of the \a series. |
315 | */ |
316 | |
317 | /*! |
318 | \qmlmethod ChartView::setAxisX(AbstractAxis axis, AbstractSeries series) |
319 | Sets the x-axis of the \a series to \a axis. |
320 | */ |
321 | |
322 | /*! |
323 | \qmlmethod Axis ChartView::axisY(AbstractSeries series) |
324 | The y-axis of the \a series. |
325 | */ |
326 | |
327 | /*! |
328 | \qmlmethod ChartView::setAxisY(AbstractAxis axis, AbstractSeries series) |
329 | Sets the y-axis of the \a series to \a axis. |
330 | */ |
331 | |
332 | /*! |
333 | \qmlmethod ChartView::zoom(real factor) |
334 | Zooms into the chart by the custom factor \a factor. |
335 | |
336 | A factor over 1.0 zooms into the view in and a factor between 0.0 and 1.0 |
337 | zooms out of it. |
338 | */ |
339 | |
340 | /*! |
341 | \qmlmethod ChartView::zoomIn() |
342 | Zooms into the view by a factor of two. |
343 | */ |
344 | |
345 | /*! |
346 | \qmlmethod ChartView::zoomIn(rect rectangle) |
347 | Zooms into the view to a maximum level at which the rectangle \a rectangle is |
348 | still fully visible. |
349 | \note This is not supported for polar charts. |
350 | */ |
351 | |
352 | /*! |
353 | \qmlmethod ChartView::zoomOut() |
354 | Zooms out of the view by a factor of two. |
355 | \note This will do nothing if the result would contain an invalid logarithmic axis range. |
356 | */ |
357 | |
358 | /*! |
359 | \qmlmethod ChartView::zoomReset() |
360 | Resets the series domains to what they were before any zoom method was called. |
361 | |
362 | \note This will also reset scrolling and explicit axis range settings |
363 | specified between the first zoom operation and calling this method. If no zoom |
364 | operation has been performed, this method does nothing. |
365 | */ |
366 | |
367 | /*! |
368 | \qmlmethod ChartView::isZoomed() |
369 | Returns \c true if any series has a zoomed domain. |
370 | */ |
371 | |
372 | /*! |
373 | \qmlmethod ChartView::scrollLeft(real pixels) |
374 | Scrolls to left by the number of pixels specified by \a pixels. This is a |
375 | convenience method suitable for key navigation, for example. |
376 | */ |
377 | |
378 | /*! |
379 | \qmlmethod ChartView::scrollRight(real pixels) |
380 | Scrolls to right by by the number of pixels specified by \a pixels. This is a |
381 | convenience method suitable for key navigation, for example. |
382 | */ |
383 | |
384 | /*! |
385 | \qmlmethod ChartView::scrollUp(real pixels) |
386 | Scrolls up by the number of pixels specified by \a pixels. This is a |
387 | convenience method suitable for key navigation, for example. |
388 | */ |
389 | |
390 | /*! |
391 | \qmlmethod ChartView::scrollDown(real pixels) |
392 | Scrolls down by the number of pixels specified by \a pixels. This is a |
393 | convenience method suitable for key navigation, for example. |
394 | */ |
395 | |
396 | /*! |
397 | \qmlmethod point ChartView::mapToValue(point position, AbstractSeries series) |
398 | Returns the value in the series \a series located at the position \a position |
399 | in the chart. |
400 | */ |
401 | |
402 | /*! |
403 | \qmlmethod point ChartView::mapToPosition(point value, AbstractSeries series) |
404 | Returns the position in the chart of the value \a value in the series |
405 | \a series. |
406 | */ |
407 | |
408 | /*! |
409 | \qmlsignal ChartView::seriesAdded(AbstractSeries series) |
410 | This signal is emitted when the series \a series is added to the chart. |
411 | */ |
412 | |
413 | /*! |
414 | \qmlsignal ChartView::seriesRemoved(AbstractSeries series) |
415 | This signal is emitted when the series \a series is removed from the chart. |
416 | The series object becomes invalid when the signal handler completes. |
417 | */ |
418 | |
419 | DeclarativeChart::DeclarativeChart(QQuickItem *parent) |
420 | : QQuickItem(parent) |
421 | { |
422 | initChart(type: QChart::ChartTypeCartesian); |
423 | } |
424 | |
425 | DeclarativeChart::DeclarativeChart(QChart::ChartType type, QQuickItem *parent) |
426 | : QQuickItem(parent) |
427 | { |
428 | initChart(type); |
429 | } |
430 | |
431 | #if QT_CONFIG(charts_bar_chart) |
432 | // QTBUG-71013 |
433 | // The symbol resides in qbarmodelmapper.cpp#548 in the C++ module. |
434 | // Here, it gets imported and reset to the DeclarativeBarSet allocator |
435 | #if defined(Q_OS_WIN) && !defined(QT_STATIC) |
436 | Q_CHARTS_EXPORT |
437 | #else |
438 | extern |
439 | #endif |
440 | QBarSet *(*qt_allocate_bar_set)(const QString &label); |
441 | |
442 | QBarSet *qt_allocate_bar_set_qml(const QString &label) |
443 | { |
444 | auto bar = new DeclarativeBarSet(); |
445 | bar->setLabel(label); |
446 | return bar; |
447 | } |
448 | #endif |
449 | |
450 | void DeclarativeChart::initChart(QChart::ChartType type) |
451 | { |
452 | m_sceneImage = 0; |
453 | m_sceneImageDirty = false; |
454 | m_sceneImageNeedsClear = false; |
455 | m_guiThreadId = QThread::currentThreadId(); |
456 | m_paintThreadId = 0; |
457 | m_updatePending = false; |
458 | |
459 | setFlag(flag: ItemHasContents, enabled: true); |
460 | |
461 | #if QT_CONFIG(charts_bar_chart) |
462 | // Reset allocator for QBarSet to create |
463 | // Declarative BarSets by default |
464 | qt_allocate_bar_set = &qt_allocate_bar_set_qml; |
465 | #endif |
466 | |
467 | if (type == QChart::ChartTypePolar) |
468 | m_chart = new QPolarChart(); |
469 | else |
470 | m_chart = new QChart(); |
471 | |
472 | m_chart->d_ptr->m_presenter->glSetUseWidget(enable: false); |
473 | m_glXYDataManager = m_chart->d_ptr->m_dataset->glXYSeriesDataManager(); |
474 | |
475 | m_scene = new QGraphicsScene(this); |
476 | m_scene->addItem(item: m_chart); |
477 | |
478 | setAntialiasing(QQuickItem::antialiasing()); |
479 | connect(sender: m_scene, signal: &QGraphicsScene::changed, context: this, slot: &DeclarativeChart::sceneChanged); |
480 | connect(sender: this, signal: &DeclarativeChart::needRender, context: this, slot: &DeclarativeChart::renderScene, |
481 | type: Qt::QueuedConnection); |
482 | connect(sender: this, SIGNAL(antialiasingChanged(bool)), receiver: this, SLOT(handleAntialiasingChanged(bool))); |
483 | connect(sender: this, signal: &DeclarativeChart::pendingRenderNodeMouseEventResponses, |
484 | context: this, slot: &DeclarativeChart::handlePendingRenderNodeMouseEventResponses, |
485 | type: Qt::QueuedConnection); |
486 | |
487 | setAcceptedMouseButtons(Qt::AllButtons); |
488 | setAcceptHoverEvents(true); |
489 | |
490 | m_margins = new DeclarativeMargins(this); |
491 | m_margins->setTop(m_chart->margins().top()); |
492 | m_margins->setLeft(m_chart->margins().left()); |
493 | m_margins->setRight(m_chart->margins().right()); |
494 | m_margins->setBottom(m_chart->margins().bottom()); |
495 | connect(sender: m_margins, SIGNAL(topChanged(int,int,int,int)), |
496 | receiver: this, SLOT(changeMargins(int,int,int,int))); |
497 | connect(sender: m_margins, SIGNAL(bottomChanged(int,int,int,int)), |
498 | receiver: this, SLOT(changeMargins(int,int,int,int))); |
499 | connect(sender: m_margins, SIGNAL(leftChanged(int,int,int,int)), |
500 | receiver: this, SLOT(changeMargins(int,int,int,int))); |
501 | connect(sender: m_margins, SIGNAL(rightChanged(int,int,int,int)), |
502 | receiver: this, SLOT(changeMargins(int,int,int,int))); |
503 | connect(sender: m_chart->d_ptr->m_dataset, SIGNAL(seriesAdded(QAbstractSeries*)), receiver: this, SLOT(handleSeriesAdded(QAbstractSeries*))); |
504 | connect(sender: m_chart->d_ptr->m_dataset, SIGNAL(seriesRemoved(QAbstractSeries*)), receiver: this, SIGNAL(seriesRemoved(QAbstractSeries*))); |
505 | connect(sender: m_chart, SIGNAL(plotAreaChanged(QRectF)), receiver: this, SIGNAL(plotAreaChanged(QRectF))); |
506 | } |
507 | |
508 | void DeclarativeChart::handleSeriesAdded(QAbstractSeries *series) |
509 | { |
510 | emit seriesAdded(series); |
511 | } |
512 | |
513 | void DeclarativeChart::handlePendingRenderNodeMouseEventResponses() |
514 | { |
515 | const int count = m_pendingRenderNodeMouseEventResponses.size(); |
516 | if (count) { |
517 | QXYSeries *lastSeries = nullptr; // Small optimization; events are likely for same series |
518 | QList<QAbstractSeries *> seriesList = m_chart->series(); |
519 | for (int i = 0; i < count; i++) { |
520 | const MouseEventResponse &response = m_pendingRenderNodeMouseEventResponses.at(i); |
521 | QXYSeries *series = nullptr; |
522 | if (lastSeries == response.series) { |
523 | series = lastSeries; |
524 | } else { |
525 | for (int j = 0; j < seriesList.size(); j++) { |
526 | QAbstractSeries *chartSeries = seriesList.at(i: j); |
527 | if (response.series == chartSeries) { |
528 | series = qobject_cast<QXYSeries *>(object: chartSeries); |
529 | break; |
530 | } |
531 | } |
532 | } |
533 | if (series) { |
534 | lastSeries = series; |
535 | QSizeF normalizedPlotSize( |
536 | m_chart->plotArea().size().width() |
537 | / m_adjustedPlotArea.size().width(), |
538 | m_chart->plotArea().size().height() |
539 | / m_adjustedPlotArea.size().height()); |
540 | |
541 | QPoint adjustedPoint( |
542 | response.point.x() * normalizedPlotSize.width(), |
543 | response.point.y() * normalizedPlotSize.height()); |
544 | |
545 | QPointF domPoint = series->d_ptr->domain()->calculateDomainPoint(point: adjustedPoint); |
546 | switch (response.type) { |
547 | case MouseEventResponse::Pressed: |
548 | emit series->pressed(point: domPoint); |
549 | break; |
550 | case MouseEventResponse::Released: |
551 | emit series->released(point: domPoint); |
552 | break; |
553 | case MouseEventResponse::Clicked: |
554 | emit series->clicked(point: domPoint); |
555 | break; |
556 | case MouseEventResponse::DoubleClicked: |
557 | emit series->doubleClicked(point: domPoint); |
558 | break; |
559 | case MouseEventResponse::HoverEnter: |
560 | emit series->hovered(point: domPoint, state: true); |
561 | break; |
562 | case MouseEventResponse::HoverLeave: |
563 | emit series->hovered(point: domPoint, state: false); |
564 | break; |
565 | default: |
566 | // No action |
567 | break; |
568 | } |
569 | } |
570 | } |
571 | m_pendingRenderNodeMouseEventResponses.clear(); |
572 | } |
573 | } |
574 | |
575 | void DeclarativeChart::changeMargins(int top, int bottom, int left, int right) |
576 | { |
577 | m_chart->setMargins(QMargins(left, top, right, bottom)); |
578 | emit marginsChanged(); |
579 | } |
580 | |
581 | DeclarativeChart::~DeclarativeChart() |
582 | { |
583 | delete m_chart; |
584 | delete m_sceneImage; |
585 | } |
586 | |
587 | void DeclarativeChart::childEvent(QChildEvent *event) |
588 | { |
589 | if (event->type() == QEvent::ChildAdded) { |
590 | if (qobject_cast<QAbstractSeries *>(object: event->child())) { |
591 | m_chart->addSeries(series: qobject_cast<QAbstractSeries *>(object: event->child())); |
592 | } |
593 | } |
594 | } |
595 | |
596 | void DeclarativeChart::componentComplete() |
597 | { |
598 | foreach (QObject *child, children()) { |
599 | if (qobject_cast<QAbstractSeries *>(object: child)) { |
600 | // Add series to the chart |
601 | QAbstractSeries *series = qobject_cast<QAbstractSeries *>(object: child); |
602 | m_chart->addSeries(series); |
603 | |
604 | // Connect to axis changed signals (unless this is a pie series) |
605 | #if QT_CONFIG(charts_pie_chart) |
606 | if (!qobject_cast<DeclarativePieSeries *>(object: series)) |
607 | #endif |
608 | { |
609 | connect(sender: series, SIGNAL(axisXChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisXSet(QAbstractAxis*))); |
610 | connect(sender: series, SIGNAL(axisXTopChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisXTopSet(QAbstractAxis*))); |
611 | connect(sender: series, SIGNAL(axisYChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisYSet(QAbstractAxis*))); |
612 | connect(sender: series, SIGNAL(axisYRightChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisYRightSet(QAbstractAxis*))); |
613 | } |
614 | |
615 | initializeAxes(series); |
616 | } |
617 | } |
618 | |
619 | QQuickItem::componentComplete(); |
620 | } |
621 | |
622 | void DeclarativeChart::seriesAxisAttachHelper(QAbstractSeries *series, QAbstractAxis *axis, |
623 | Qt::Orientations orientation, |
624 | Qt::Alignment alignment) |
625 | { |
626 | if (!series->attachedAxes().contains(t: axis)) { |
627 | // Remove & delete old axes that are not attached to any other series |
628 | // Detach old axis from series so that if it is shared with other series |
629 | // It can be deleted. |
630 | const auto axes = m_chart->axes(orientation, series); |
631 | for (QAbstractAxis *oldAxis : axes) { |
632 | bool otherAttachments = false; |
633 | if (oldAxis != axis) { |
634 | series->detachAxis(axis: oldAxis); |
635 | const auto allSeries = m_chart->series(); |
636 | for (QAbstractSeries *oldSeries : allSeries) { |
637 | if (oldSeries != series && oldSeries->attachedAxes().contains(t: oldAxis)) { |
638 | otherAttachments = true; |
639 | break; |
640 | } |
641 | } |
642 | if (!otherAttachments) { |
643 | m_chart->removeAxis(axis: oldAxis); |
644 | delete oldAxis; |
645 | } |
646 | } |
647 | } |
648 | if (!m_chart->axes(orientation).contains(t: axis)) |
649 | m_chart->addAxis(axis, alignment); |
650 | |
651 | series->attachAxis(axis); |
652 | } |
653 | } |
654 | |
655 | void DeclarativeChart::handleAxisXSet(QAbstractAxis *axis) |
656 | { |
657 | QAbstractSeries *s = qobject_cast<QAbstractSeries *>(object: sender()); |
658 | if (axis && s) { |
659 | seriesAxisAttachHelper(series: s, axis, orientation: Qt::Horizontal, alignment: Qt::AlignBottom); |
660 | } else { |
661 | qWarning() << "Trying to set axisX to null."; |
662 | } |
663 | } |
664 | |
665 | void DeclarativeChart::handleAxisXTopSet(QAbstractAxis *axis) |
666 | { |
667 | QAbstractSeries *s = qobject_cast<QAbstractSeries *>(object: sender()); |
668 | if (axis && s) { |
669 | seriesAxisAttachHelper(series: s, axis, orientation: Qt::Horizontal, alignment: Qt::AlignTop); |
670 | } else { |
671 | qWarning() << "Trying to set axisXTop to null."; |
672 | } |
673 | } |
674 | |
675 | void DeclarativeChart::handleAxisYSet(QAbstractAxis *axis) |
676 | { |
677 | QAbstractSeries *s = qobject_cast<QAbstractSeries *>(object: sender()); |
678 | if (axis && s) { |
679 | seriesAxisAttachHelper(series: s, axis, orientation: Qt::Vertical, alignment: Qt::AlignLeft); |
680 | } else { |
681 | qWarning() << "Trying to set axisY to null."; |
682 | } |
683 | } |
684 | |
685 | void DeclarativeChart::handleAxisYRightSet(QAbstractAxis *axis) |
686 | { |
687 | QAbstractSeries *s = qobject_cast<QAbstractSeries *>(object: sender()); |
688 | if (axis && s) { |
689 | seriesAxisAttachHelper(series: s, axis, orientation: Qt::Vertical, alignment: Qt::AlignRight); |
690 | } else { |
691 | qWarning() << "Trying to set axisYRight to null."; |
692 | } |
693 | } |
694 | |
695 | void DeclarativeChart::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
696 | { |
697 | if (newGeometry.isValid()) { |
698 | if (newGeometry.width() > 0 && newGeometry.height() > 0) { |
699 | m_chart->resize(w: newGeometry.width(), h: newGeometry.height()); |
700 | } |
701 | } |
702 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
703 | } |
704 | |
705 | QSGNode *DeclarativeChart::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) |
706 | { |
707 | DeclarativeChartNode *node = static_cast<DeclarativeChartNode *>(oldNode); |
708 | |
709 | if (!node) { |
710 | node = new DeclarativeChartNode(window()); |
711 | // Ensure that chart is rendered whenever node is recreated |
712 | if (m_sceneImage) |
713 | m_sceneImageDirty = true; |
714 | } |
715 | |
716 | const QRectF &bRect = boundingRect(); |
717 | // Update renderNode data |
718 | if (node->renderNode()) { |
719 | if (m_glXYDataManager->dataMap().size() || m_glXYDataManager->mapDirty()) { |
720 | // Convert plotArea to QRect and back to QRectF to get rid of sub-pixel widths/heights |
721 | // which can cause unwanted partial antialising of the graph. |
722 | const QRectF plotArea = QRectF(m_chart->plotArea().toRect()); |
723 | const QSizeF &chartAreaSize = m_chart->size(); |
724 | |
725 | // We can't use chart's plot area directly, as chart enforces a minimum size |
726 | // internally, so that axes and labels always fit the chart area. |
727 | const qreal normalizedX = plotArea.x() / chartAreaSize.width(); |
728 | const qreal normalizedY = plotArea.y() / chartAreaSize.height(); |
729 | const qreal normalizedWidth = plotArea.width() / chartAreaSize.width(); |
730 | const qreal normalizedHeight = plotArea.height() / chartAreaSize.height(); |
731 | |
732 | m_adjustedPlotArea = QRectF(normalizedX * bRect.width(), |
733 | normalizedY * bRect.height(), |
734 | normalizedWidth * bRect.width(), |
735 | normalizedHeight * bRect.height()); |
736 | |
737 | const QSize &adjustedPlotSize = m_adjustedPlotArea.size().toSize(); |
738 | if (adjustedPlotSize != node->renderNode()->textureSize()) |
739 | node->renderNode()->setTextureSize(adjustedPlotSize); |
740 | |
741 | node->renderNode()->setRect(m_adjustedPlotArea); |
742 | node->renderNode()->setSeriesData(mapDirty: m_glXYDataManager->mapDirty(), |
743 | dataMap: m_glXYDataManager->dataMap()); |
744 | node->renderNode()->setAntialiasing(antialiasing()); |
745 | |
746 | // Clear dirty flags from original xy data |
747 | m_glXYDataManager->clearAllDirty(); |
748 | } |
749 | |
750 | node->renderNode()->takeMouseEventResponses(responses&: m_pendingRenderNodeMouseEventResponses); |
751 | if (m_pendingRenderNodeMouseEventResponses.size()) |
752 | emit pendingRenderNodeMouseEventResponses(); |
753 | if (m_pendingRenderNodeMouseEvents.size()) { |
754 | node->renderNode()->addMouseEvents(events: m_pendingRenderNodeMouseEvents); |
755 | // Queue another update to receive responses |
756 | update(); |
757 | } |
758 | } |
759 | |
760 | m_pendingRenderNodeMouseEvents.clear(); |
761 | |
762 | // Copy chart (if dirty) to chart node |
763 | if (m_sceneImageDirty) { |
764 | node->createTextureFromImage(chartImage: *m_sceneImage); |
765 | m_sceneImageDirty = false; |
766 | } |
767 | |
768 | node->setRect(bRect); |
769 | |
770 | return node; |
771 | } |
772 | |
773 | void DeclarativeChart::sceneChanged(const QList<QRectF> ®ion) |
774 | { |
775 | const int count = region.size(); |
776 | const qreal limitSize = 0.01; |
777 | if (count && !m_updatePending) { |
778 | qreal totalSize = 0.0; |
779 | for (int i = 0; i < count; i++) { |
780 | const QRectF ® = region.at(i); |
781 | totalSize += (reg.height() * reg.width()); |
782 | if (totalSize >= limitSize) |
783 | break; |
784 | } |
785 | // Ignore region updates that change less than small fraction of a pixel, as there is |
786 | // little point regenerating the image in these cases. These are typically cases |
787 | // where OpenGL series are drawn to otherwise static chart. |
788 | if (totalSize >= limitSize) { |
789 | m_updatePending = true; |
790 | // Do async render to avoid some unnecessary renders. |
791 | emit needRender(); |
792 | } else { |
793 | // We do want to call update to trigger possible gl series updates. |
794 | update(); |
795 | } |
796 | } |
797 | } |
798 | |
799 | void DeclarativeChart::renderScene() |
800 | { |
801 | m_updatePending = false; |
802 | m_sceneImageDirty = true; |
803 | QSize chartSize = m_chart->size().toSize(); |
804 | if (!m_sceneImage || chartSize != m_sceneImage->size()) { |
805 | delete m_sceneImage; |
806 | qreal dpr = window() ? window()->devicePixelRatio() : 1.0; |
807 | m_sceneImage = new QImage(chartSize * dpr, QImage::Format_ARGB32); |
808 | m_sceneImage->setDevicePixelRatio(dpr); |
809 | m_sceneImageNeedsClear = true; |
810 | } |
811 | |
812 | if (m_sceneImageNeedsClear) { |
813 | m_sceneImage->fill(color: Qt::transparent); |
814 | // Don't clear the flag if chart background has any transparent element to it |
815 | if (m_chart->backgroundBrush().color().alpha() == 0xff && !m_chart->isDropShadowEnabled()) |
816 | m_sceneImageNeedsClear = false; |
817 | } |
818 | QPainter painter(m_sceneImage); |
819 | if (antialiasing()) { |
820 | painter.setRenderHints(hints: QPainter::Antialiasing | QPainter::TextAntialiasing |
821 | | QPainter::SmoothPixmapTransform); |
822 | } |
823 | QRect renderRect(QPoint(0, 0), chartSize); |
824 | m_scene->render(painter: &painter, target: renderRect, source: renderRect); |
825 | update(); |
826 | } |
827 | |
828 | void DeclarativeChart::mousePressEvent(QMouseEvent *event) |
829 | { |
830 | m_mousePressScenePoint = event->position(); |
831 | m_mousePressScreenPoint = event->globalPosition().toPoint(); |
832 | m_lastMouseMoveScenePoint = m_mousePressScenePoint; |
833 | m_lastMouseMoveScreenPoint = m_mousePressScreenPoint; |
834 | m_mousePressButton = event->button(); |
835 | m_mousePressButtons = event->buttons(); |
836 | |
837 | QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMousePress); |
838 | mouseEvent.setWidget(0); |
839 | mouseEvent.setButtonDownScenePos(button: m_mousePressButton, pos: m_mousePressScenePoint); |
840 | mouseEvent.setButtonDownScreenPos(button: m_mousePressButton, pos: m_mousePressScreenPoint); |
841 | mouseEvent.setScenePos(m_mousePressScenePoint); |
842 | mouseEvent.setScreenPos(m_mousePressScreenPoint); |
843 | mouseEvent.setLastScenePos(m_lastMouseMoveScenePoint); |
844 | mouseEvent.setLastScreenPos(m_lastMouseMoveScreenPoint); |
845 | mouseEvent.setButtons(m_mousePressButtons); |
846 | mouseEvent.setButton(m_mousePressButton); |
847 | mouseEvent.setModifiers(event->modifiers()); |
848 | mouseEvent.setAccepted(false); |
849 | |
850 | QApplication::sendEvent(receiver: m_scene, event: &mouseEvent); |
851 | |
852 | queueRendererMouseEvent(event); |
853 | } |
854 | |
855 | void DeclarativeChart::mouseReleaseEvent(QMouseEvent *event) |
856 | { |
857 | QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseRelease); |
858 | mouseEvent.setWidget(0); |
859 | mouseEvent.setButtonDownScenePos(button: m_mousePressButton, pos: m_mousePressScenePoint); |
860 | mouseEvent.setButtonDownScreenPos(button: m_mousePressButton, pos: m_mousePressScreenPoint); |
861 | mouseEvent.setScenePos(event->position()); |
862 | mouseEvent.setScreenPos(event->globalPosition().toPoint()); |
863 | mouseEvent.setLastScenePos(m_lastMouseMoveScenePoint); |
864 | mouseEvent.setLastScreenPos(m_lastMouseMoveScreenPoint); |
865 | mouseEvent.setButtons(event->buttons()); |
866 | mouseEvent.setButton(event->button()); |
867 | mouseEvent.setModifiers(event->modifiers()); |
868 | mouseEvent.setAccepted(false); |
869 | |
870 | QApplication::sendEvent(receiver: m_scene, event: &mouseEvent); |
871 | |
872 | m_mousePressButtons = event->buttons(); |
873 | m_mousePressButton = Qt::NoButton; |
874 | |
875 | queueRendererMouseEvent(event); |
876 | } |
877 | |
878 | void DeclarativeChart::hoverMoveEvent(QHoverEvent *event) |
879 | { |
880 | QPointF previousLastScenePoint = m_lastMouseMoveScenePoint; |
881 | |
882 | // Convert hover move to mouse move, since we don't seem to get actual mouse move events. |
883 | // QGraphicsScene generates hover events from mouse move events, so we don't need |
884 | // to pass hover events there. |
885 | QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove); |
886 | mouseEvent.setWidget(0); |
887 | mouseEvent.setButtonDownScenePos(button: m_mousePressButton, pos: m_mousePressScenePoint); |
888 | mouseEvent.setButtonDownScreenPos(button: m_mousePressButton, pos: m_mousePressScreenPoint); |
889 | mouseEvent.setScenePos(event->position()); |
890 | // Hover events do not have global pos in them, and the screen position doesn't seem to |
891 | // matter anyway in this use case, so just pass event pos instead of trying to |
892 | // calculate the real screen position. |
893 | mouseEvent.setScreenPos(event->position().toPoint()); |
894 | mouseEvent.setLastScenePos(m_lastMouseMoveScenePoint); |
895 | mouseEvent.setLastScreenPos(m_lastMouseMoveScreenPoint); |
896 | mouseEvent.setButtons(m_mousePressButtons); |
897 | mouseEvent.setButton(m_mousePressButton); |
898 | mouseEvent.setModifiers(event->modifiers()); |
899 | m_lastMouseMoveScenePoint = mouseEvent.scenePos(); |
900 | m_lastMouseMoveScreenPoint = mouseEvent.screenPos(); |
901 | mouseEvent.setAccepted(false); |
902 | |
903 | QApplication::sendEvent(receiver: m_scene, event: &mouseEvent); |
904 | |
905 | // Update triggers another hover event, so let's not handle successive hovers at same |
906 | // position to avoid infinite loop. |
907 | if (m_glXYDataManager->dataMap().size() && previousLastScenePoint != m_lastMouseMoveScenePoint) { |
908 | QMouseEvent *newEvent = new QMouseEvent(QEvent::MouseMove, |
909 | event->position() - m_adjustedPlotArea.topLeft(), |
910 | event->globalPosition() - m_adjustedPlotArea.topLeft(), |
911 | m_mousePressButton, |
912 | m_mousePressButtons, |
913 | event->modifiers()); |
914 | m_pendingRenderNodeMouseEvents.append(t: newEvent); |
915 | update(); |
916 | } |
917 | } |
918 | |
919 | void DeclarativeChart::mouseDoubleClickEvent(QMouseEvent *event) |
920 | { |
921 | m_mousePressScenePoint = event->position(); |
922 | m_mousePressScreenPoint = event->globalPosition().toPoint(); |
923 | m_lastMouseMoveScenePoint = m_mousePressScenePoint; |
924 | m_lastMouseMoveScreenPoint = m_mousePressScreenPoint; |
925 | m_mousePressButton = event->button(); |
926 | m_mousePressButtons = event->buttons(); |
927 | |
928 | QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseDoubleClick); |
929 | mouseEvent.setWidget(0); |
930 | mouseEvent.setButtonDownScenePos(button: m_mousePressButton, pos: m_mousePressScenePoint); |
931 | mouseEvent.setButtonDownScreenPos(button: m_mousePressButton, pos: m_mousePressScreenPoint); |
932 | mouseEvent.setScenePos(m_mousePressScenePoint); |
933 | mouseEvent.setScreenPos(m_mousePressScreenPoint); |
934 | mouseEvent.setLastScenePos(m_lastMouseMoveScenePoint); |
935 | mouseEvent.setLastScreenPos(m_lastMouseMoveScreenPoint); |
936 | mouseEvent.setButtons(m_mousePressButtons); |
937 | mouseEvent.setButton(m_mousePressButton); |
938 | mouseEvent.setModifiers(event->modifiers()); |
939 | mouseEvent.setAccepted(false); |
940 | |
941 | QApplication::sendEvent(receiver: m_scene, event: &mouseEvent); |
942 | |
943 | queueRendererMouseEvent(event); |
944 | } |
945 | |
946 | void DeclarativeChart::handleAntialiasingChanged(bool enable) |
947 | { |
948 | setAntialiasing(enable); |
949 | emit needRender(); |
950 | } |
951 | |
952 | void DeclarativeChart::setTheme(DeclarativeChart::Theme theme) |
953 | { |
954 | QChart::ChartTheme chartTheme = (QChart::ChartTheme) theme; |
955 | if (chartTheme != m_chart->theme()) |
956 | m_chart->setTheme(chartTheme); |
957 | } |
958 | |
959 | DeclarativeChart::Theme DeclarativeChart::theme() |
960 | { |
961 | return (DeclarativeChart::Theme) m_chart->theme(); |
962 | } |
963 | |
964 | void DeclarativeChart::setAnimationOptions(DeclarativeChart::Animation animations) |
965 | { |
966 | QChart::AnimationOption animationOptions = (QChart::AnimationOption) animations; |
967 | if (animationOptions != m_chart->animationOptions()) |
968 | m_chart->setAnimationOptions(animationOptions); |
969 | } |
970 | |
971 | DeclarativeChart::Animation DeclarativeChart::animationOptions() |
972 | { |
973 | if (m_chart->animationOptions().testFlag(flag: QChart::AllAnimations)) |
974 | return DeclarativeChart::AllAnimations; |
975 | else if (m_chart->animationOptions().testFlag(flag: QChart::GridAxisAnimations)) |
976 | return DeclarativeChart::GridAxisAnimations; |
977 | else if (m_chart->animationOptions().testFlag(flag: QChart::SeriesAnimations)) |
978 | return DeclarativeChart::SeriesAnimations; |
979 | else |
980 | return DeclarativeChart::NoAnimation; |
981 | } |
982 | |
983 | void DeclarativeChart::setAnimationDuration(int msecs) |
984 | { |
985 | if (msecs != m_chart->animationDuration()) { |
986 | m_chart->setAnimationDuration(msecs); |
987 | emit animationDurationChanged(msecs); |
988 | } |
989 | } |
990 | |
991 | int DeclarativeChart::animationDuration() const |
992 | { |
993 | return m_chart->animationDuration(); |
994 | } |
995 | |
996 | void DeclarativeChart::setAnimationEasingCurve(const QEasingCurve &curve) |
997 | { |
998 | if (curve != m_chart->animationEasingCurve()) { |
999 | m_chart->setAnimationEasingCurve(curve); |
1000 | emit animationEasingCurveChanged(curve); |
1001 | } |
1002 | } |
1003 | |
1004 | QEasingCurve DeclarativeChart::animationEasingCurve() const |
1005 | { |
1006 | return m_chart->animationEasingCurve(); |
1007 | } |
1008 | |
1009 | void DeclarativeChart::setTitle(QString title) |
1010 | { |
1011 | if (title != m_chart->title()) |
1012 | m_chart->setTitle(title); |
1013 | } |
1014 | QString DeclarativeChart::title() |
1015 | { |
1016 | return m_chart->title(); |
1017 | } |
1018 | |
1019 | QAbstractAxis *DeclarativeChart::axisX(QAbstractSeries *series) |
1020 | { |
1021 | QList<QAbstractAxis *> axes = m_chart->axes(orientation: Qt::Horizontal, series); |
1022 | if (axes.size()) |
1023 | return axes[0]; |
1024 | return 0; |
1025 | } |
1026 | |
1027 | QAbstractAxis *DeclarativeChart::axisY(QAbstractSeries *series) |
1028 | { |
1029 | QList<QAbstractAxis *> axes = m_chart->axes(orientation: Qt::Vertical, series); |
1030 | if (axes.size()) |
1031 | return axes[0]; |
1032 | return 0; |
1033 | } |
1034 | |
1035 | QLegend *DeclarativeChart::legend() |
1036 | { |
1037 | return m_chart->legend(); |
1038 | } |
1039 | |
1040 | void DeclarativeChart::setTitleColor(QColor color) |
1041 | { |
1042 | QBrush b = m_chart->titleBrush(); |
1043 | if (color != b.color()) { |
1044 | b.setColor(color); |
1045 | m_chart->setTitleBrush(b); |
1046 | emit titleColorChanged(color); |
1047 | } |
1048 | } |
1049 | |
1050 | QFont DeclarativeChart::titleFont() const |
1051 | { |
1052 | return m_chart->titleFont(); |
1053 | } |
1054 | |
1055 | void DeclarativeChart::setTitleFont(const QFont &font) |
1056 | { |
1057 | m_chart->setTitleFont(font); |
1058 | } |
1059 | |
1060 | QColor DeclarativeChart::titleColor() |
1061 | { |
1062 | return m_chart->titleBrush().color(); |
1063 | } |
1064 | |
1065 | void DeclarativeChart::setBackgroundColor(QColor color) |
1066 | { |
1067 | QBrush b = m_chart->backgroundBrush(); |
1068 | if (b.style() != Qt::SolidPattern || color != b.color()) { |
1069 | if (color.alpha() < 0xff) |
1070 | m_sceneImageNeedsClear = true; |
1071 | b.setStyle(Qt::SolidPattern); |
1072 | b.setColor(color); |
1073 | m_chart->setBackgroundBrush(b); |
1074 | emit backgroundColorChanged(); |
1075 | } |
1076 | } |
1077 | |
1078 | QColor DeclarativeChart::backgroundColor() |
1079 | { |
1080 | return m_chart->backgroundBrush().color(); |
1081 | } |
1082 | |
1083 | void DeclarativeChart::setPlotAreaColor(QColor color) |
1084 | { |
1085 | QBrush b = m_chart->plotAreaBackgroundBrush(); |
1086 | if (b.style() != Qt::SolidPattern || color != b.color()) { |
1087 | b.setStyle(Qt::SolidPattern); |
1088 | b.setColor(color); |
1089 | m_chart->setPlotAreaBackgroundBrush(b); |
1090 | m_chart->setPlotAreaBackgroundVisible(true); |
1091 | emit plotAreaColorChanged(); |
1092 | } |
1093 | } |
1094 | |
1095 | QColor DeclarativeChart::plotAreaColor() |
1096 | { |
1097 | return m_chart->plotAreaBackgroundBrush().color(); |
1098 | } |
1099 | |
1100 | void DeclarativeChart::setLocalizeNumbers(bool localize) |
1101 | { |
1102 | if (m_chart->localizeNumbers() != localize) { |
1103 | m_chart->setLocalizeNumbers(localize); |
1104 | emit localizeNumbersChanged(); |
1105 | } |
1106 | } |
1107 | |
1108 | bool DeclarativeChart::localizeNumbers() const |
1109 | { |
1110 | return m_chart->localizeNumbers(); |
1111 | } |
1112 | |
1113 | void DeclarativeChart::setLocale(const QLocale &locale) |
1114 | { |
1115 | if (m_chart->locale() != locale) { |
1116 | m_chart->setLocale(locale); |
1117 | emit localeChanged(); |
1118 | } |
1119 | } |
1120 | |
1121 | QLocale DeclarativeChart::locale() const |
1122 | { |
1123 | return m_chart->locale(); |
1124 | } |
1125 | |
1126 | int DeclarativeChart::count() |
1127 | { |
1128 | return m_chart->series().size(); |
1129 | } |
1130 | |
1131 | void DeclarativeChart::setDropShadowEnabled(bool enabled) |
1132 | { |
1133 | if (enabled != m_chart->isDropShadowEnabled()) { |
1134 | m_sceneImageNeedsClear = true; |
1135 | m_chart->setDropShadowEnabled(enabled); |
1136 | dropShadowEnabledChanged(enabled); |
1137 | } |
1138 | } |
1139 | |
1140 | bool DeclarativeChart::dropShadowEnabled() |
1141 | { |
1142 | return m_chart->isDropShadowEnabled(); |
1143 | } |
1144 | |
1145 | qreal DeclarativeChart::backgroundRoundness() const |
1146 | { |
1147 | return m_chart->backgroundRoundness(); |
1148 | } |
1149 | |
1150 | void DeclarativeChart::setBackgroundRoundness(qreal diameter) |
1151 | { |
1152 | if (m_chart->backgroundRoundness() != diameter) { |
1153 | m_sceneImageNeedsClear = true; |
1154 | m_chart->setBackgroundRoundness(diameter); |
1155 | emit backgroundRoundnessChanged(diameter); |
1156 | } |
1157 | } |
1158 | |
1159 | void DeclarativeChart::zoom(qreal factor) |
1160 | { |
1161 | m_chart->zoom(factor); |
1162 | } |
1163 | |
1164 | void DeclarativeChart::zoomIn() |
1165 | { |
1166 | m_chart->zoomIn(); |
1167 | } |
1168 | |
1169 | void DeclarativeChart::zoomIn(const QRectF &rectangle) |
1170 | { |
1171 | m_chart->zoomIn(rect: rectangle); |
1172 | } |
1173 | |
1174 | void DeclarativeChart::zoomOut() |
1175 | { |
1176 | m_chart->zoomOut(); |
1177 | } |
1178 | |
1179 | void DeclarativeChart::zoomReset() |
1180 | { |
1181 | m_chart->zoomReset(); |
1182 | } |
1183 | |
1184 | bool DeclarativeChart::isZoomed() |
1185 | { |
1186 | return m_chart->isZoomed(); |
1187 | } |
1188 | |
1189 | void DeclarativeChart::scrollLeft(qreal pixels) |
1190 | { |
1191 | m_chart->scroll(dx: -pixels, dy: 0); |
1192 | } |
1193 | |
1194 | void DeclarativeChart::scrollRight(qreal pixels) |
1195 | { |
1196 | m_chart->scroll(dx: pixels, dy: 0); |
1197 | } |
1198 | |
1199 | void DeclarativeChart::scrollUp(qreal pixels) |
1200 | { |
1201 | m_chart->scroll(dx: 0, dy: pixels); |
1202 | } |
1203 | |
1204 | void DeclarativeChart::scrollDown(qreal pixels) |
1205 | { |
1206 | m_chart->scroll(dx: 0, dy: -pixels); |
1207 | } |
1208 | |
1209 | QQmlListProperty<QAbstractAxis> DeclarativeChart::axes() |
1210 | { |
1211 | return QQmlListProperty<QAbstractAxis>(this, 0, |
1212 | &DeclarativeChart::axesAppendFunc, |
1213 | &DeclarativeChart::axesCountFunc, |
1214 | &DeclarativeChart::axesAtFunc, |
1215 | &DeclarativeChart::axesClearFunc); |
1216 | } |
1217 | |
1218 | void DeclarativeChart::axesAppendFunc(QQmlListProperty<QAbstractAxis> *list, QAbstractAxis *element) |
1219 | { |
1220 | // Empty implementation |
1221 | Q_UNUSED(list); |
1222 | Q_UNUSED(element); |
1223 | } |
1224 | |
1225 | qsizetype DeclarativeChart::axesCountFunc(QQmlListProperty<QAbstractAxis> *list) |
1226 | { |
1227 | if (qobject_cast<DeclarativeChart *>(object: list->object)) { |
1228 | DeclarativeChart *chart = qobject_cast<DeclarativeChart *>(object: list->object); |
1229 | return chart->m_chart->axes(orientation: Qt::Horizontal | Qt::Vertical).size(); |
1230 | } |
1231 | return 0; |
1232 | } |
1233 | |
1234 | QAbstractAxis *DeclarativeChart::axesAtFunc(QQmlListProperty<QAbstractAxis> *list, qsizetype index) |
1235 | { |
1236 | if (qobject_cast<DeclarativeChart *>(object: list->object)) { |
1237 | DeclarativeChart *chart = qobject_cast<DeclarativeChart *>(object: list->object); |
1238 | QList<QAbstractAxis *> axes = chart->m_chart->axes(orientation: Qt::Horizontal | Qt::Vertical); |
1239 | return axes.at(i: index); |
1240 | } |
1241 | return 0; |
1242 | } |
1243 | |
1244 | void DeclarativeChart::axesClearFunc(QQmlListProperty<QAbstractAxis> *list) |
1245 | { |
1246 | // Empty implementation |
1247 | Q_UNUSED(list); |
1248 | } |
1249 | |
1250 | |
1251 | QAbstractSeries *DeclarativeChart::series(int index) |
1252 | { |
1253 | if (index < m_chart->series().size()) { |
1254 | return m_chart->series().at(i: index); |
1255 | } |
1256 | return 0; |
1257 | } |
1258 | |
1259 | QAbstractSeries *DeclarativeChart::series(QString seriesName) |
1260 | { |
1261 | const auto allSeries = m_chart->series(); |
1262 | for (QAbstractSeries *series : allSeries) { |
1263 | if (series->name() == seriesName) |
1264 | return series; |
1265 | } |
1266 | return 0; |
1267 | } |
1268 | |
1269 | QAbstractSeries *DeclarativeChart::createSeries(int type, QString name, QAbstractAxis *axisX, QAbstractAxis *axisY) |
1270 | { |
1271 | QAbstractSeries *series = 0; |
1272 | |
1273 | switch (type) { |
1274 | #if QT_CONFIG(charts_line_chart) |
1275 | case DeclarativeChart::SeriesTypeLine: |
1276 | series = new DeclarativeLineSeries(); |
1277 | break; |
1278 | #endif |
1279 | #if QT_CONFIG(charts_area_chart) |
1280 | case DeclarativeChart::SeriesTypeArea: { |
1281 | DeclarativeAreaSeries *area = new DeclarativeAreaSeries(); |
1282 | DeclarativeLineSeries *line = new DeclarativeLineSeries(); |
1283 | line->setParent(area); |
1284 | area->setUpperSeries(line); |
1285 | series = area; |
1286 | break; |
1287 | } |
1288 | #endif |
1289 | #if QT_CONFIG(charts_bar_chart) |
1290 | case DeclarativeChart::SeriesTypeStackedBar: |
1291 | series = new DeclarativeStackedBarSeries(); |
1292 | break; |
1293 | case DeclarativeChart::SeriesTypePercentBar: |
1294 | series = new DeclarativePercentBarSeries(); |
1295 | break; |
1296 | case DeclarativeChart::SeriesTypeBar: |
1297 | series = new DeclarativeBarSeries(); |
1298 | break; |
1299 | case DeclarativeChart::SeriesTypeHorizontalBar: |
1300 | series = new DeclarativeHorizontalBarSeries(); |
1301 | break; |
1302 | case DeclarativeChart::SeriesTypeHorizontalPercentBar: |
1303 | series = new DeclarativeHorizontalPercentBarSeries(); |
1304 | break; |
1305 | case DeclarativeChart::SeriesTypeHorizontalStackedBar: |
1306 | series = new DeclarativeHorizontalStackedBarSeries(); |
1307 | break; |
1308 | #endif |
1309 | #if QT_CONFIG(charts_boxplot_chart) |
1310 | case DeclarativeChart::SeriesTypeBoxPlot: |
1311 | series = new DeclarativeBoxPlotSeries(); |
1312 | break; |
1313 | #endif |
1314 | #if QT_CONFIG(charts_candlestick_chart) |
1315 | case DeclarativeChart::SeriesTypeCandlestick: |
1316 | series = new DeclarativeCandlestickSeries(); |
1317 | break; |
1318 | #endif |
1319 | #if QT_CONFIG(charts_pie_chart) |
1320 | case DeclarativeChart::SeriesTypePie: |
1321 | series = new DeclarativePieSeries(); |
1322 | break; |
1323 | #endif |
1324 | #if QT_CONFIG(charts_scatter_chart) |
1325 | case DeclarativeChart::SeriesTypeScatter: |
1326 | series = new DeclarativeScatterSeries(); |
1327 | break; |
1328 | #endif |
1329 | #if QT_CONFIG(charts_spline_chart) |
1330 | case DeclarativeChart::SeriesTypeSpline: |
1331 | series = new DeclarativeSplineSeries(); |
1332 | break; |
1333 | #endif |
1334 | default: |
1335 | qWarning() << "Illegal series type"; |
1336 | } |
1337 | |
1338 | if (series) { |
1339 | // Connect to axis changed signals (unless this is a pie series) |
1340 | #if QT_CONFIG(charts_pie_chart) |
1341 | if (!qobject_cast<DeclarativePieSeries *>(object: series)) |
1342 | #endif |
1343 | { |
1344 | connect(sender: series, SIGNAL(axisXChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisXSet(QAbstractAxis*))); |
1345 | connect(sender: series, SIGNAL(axisXTopChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisXSet(QAbstractAxis*))); |
1346 | connect(sender: series, SIGNAL(axisYChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisYSet(QAbstractAxis*))); |
1347 | connect(sender: series, SIGNAL(axisYRightChanged(QAbstractAxis*)), receiver: this, SLOT(handleAxisYRightSet(QAbstractAxis*))); |
1348 | } |
1349 | |
1350 | series->setName(name); |
1351 | m_chart->addSeries(series); |
1352 | |
1353 | if (!axisX || !axisY) |
1354 | initializeAxes(series); |
1355 | |
1356 | if (axisX) |
1357 | setAxisX(axis: axisX, series); |
1358 | if (axisY) |
1359 | setAxisY(axis: axisY, series); |
1360 | } |
1361 | |
1362 | return series; |
1363 | } |
1364 | |
1365 | void DeclarativeChart::removeSeries(QAbstractSeries *series) |
1366 | { |
1367 | if (series) |
1368 | m_chart->removeSeries(series); |
1369 | else |
1370 | qWarning(msg: "removeSeries: cannot remove null"); |
1371 | } |
1372 | |
1373 | void DeclarativeChart::setAxisX(QAbstractAxis *axis, QAbstractSeries *series) |
1374 | { |
1375 | if (axis && series) |
1376 | seriesAxisAttachHelper(series, axis, orientation: Qt::Horizontal, alignment: Qt::AlignBottom); |
1377 | } |
1378 | |
1379 | void DeclarativeChart::setAxisY(QAbstractAxis *axis, QAbstractSeries *series) |
1380 | { |
1381 | if (axis && series) |
1382 | seriesAxisAttachHelper(series, axis, orientation: Qt::Vertical, alignment: Qt::AlignLeft); |
1383 | } |
1384 | |
1385 | QAbstractAxis *DeclarativeChart::defaultAxis(Qt::Orientation orientation, QAbstractSeries *series) |
1386 | { |
1387 | if (!series) { |
1388 | qWarning() << "No axis type defined for null series"; |
1389 | return 0; |
1390 | } |
1391 | |
1392 | const auto axes = m_chart->axes(orientation); |
1393 | for (QAbstractAxis *existingAxis : axes) { |
1394 | if (existingAxis->type() == series->d_ptr->defaultAxisType(orientation)) |
1395 | return existingAxis; |
1396 | } |
1397 | |
1398 | switch (series->d_ptr->defaultAxisType(orientation)) { |
1399 | case QAbstractAxis::AxisTypeValue: |
1400 | return new QValueAxis(this); |
1401 | case QAbstractAxis::AxisTypeBarCategory: |
1402 | return new QBarCategoryAxis(this); |
1403 | case QAbstractAxis::AxisTypeCategory: |
1404 | return new QCategoryAxis(this); |
1405 | #if QT_CONFIG(charts_datetime_axis) |
1406 | case QAbstractAxis::AxisTypeDateTime: |
1407 | return new QDateTimeAxis(this); |
1408 | #endif |
1409 | case QAbstractAxis::AxisTypeLogValue: |
1410 | return new QLogValueAxis(this); |
1411 | default: |
1412 | // assume AxisTypeNoAxis |
1413 | return 0; |
1414 | } |
1415 | } |
1416 | |
1417 | void DeclarativeChart::initializeAxes(QAbstractSeries *series) |
1418 | { |
1419 | if (false) { |
1420 | } |
1421 | #if QT_CONFIG(charts_line_chart) |
1422 | else if (qobject_cast<DeclarativeLineSeries *>(object: series)) |
1423 | doInitializeAxes(series, axes: qobject_cast<DeclarativeLineSeries *>(object: series)->m_axes); |
1424 | #endif |
1425 | #if QT_CONFIG(charts_scatter_chart) |
1426 | else if (qobject_cast<DeclarativeScatterSeries *>(object: series)) |
1427 | doInitializeAxes(series, axes: qobject_cast<DeclarativeScatterSeries *>(object: series)->m_axes); |
1428 | #endif |
1429 | #if QT_CONFIG(charts_spline_chart) |
1430 | else if (qobject_cast<DeclarativeSplineSeries *>(object: series)) |
1431 | doInitializeAxes(series, axes: qobject_cast<DeclarativeSplineSeries *>(object: series)->m_axes); |
1432 | #endif |
1433 | #if QT_CONFIG(charts_area_chart) |
1434 | else if (qobject_cast<DeclarativeAreaSeries *>(object: series)) |
1435 | doInitializeAxes(series, axes: qobject_cast<DeclarativeAreaSeries *>(object: series)->m_axes); |
1436 | #endif |
1437 | #if QT_CONFIG(charts_bar_chart) |
1438 | else if (qobject_cast<DeclarativeBarSeries *>(object: series)) |
1439 | doInitializeAxes(series, axes: qobject_cast<DeclarativeBarSeries *>(object: series)->m_axes); |
1440 | else if (qobject_cast<DeclarativeStackedBarSeries *>(object: series)) |
1441 | doInitializeAxes(series, axes: qobject_cast<DeclarativeStackedBarSeries *>(object: series)->m_axes); |
1442 | else if (qobject_cast<DeclarativePercentBarSeries *>(object: series)) |
1443 | doInitializeAxes(series, axes: qobject_cast<DeclarativePercentBarSeries *>(object: series)->m_axes); |
1444 | else if (qobject_cast<DeclarativeHorizontalBarSeries *>(object: series)) |
1445 | doInitializeAxes(series, axes: qobject_cast<DeclarativeHorizontalBarSeries *>(object: series)->m_axes); |
1446 | else if (qobject_cast<DeclarativeHorizontalStackedBarSeries *>(object: series)) |
1447 | doInitializeAxes(series, axes: qobject_cast<DeclarativeHorizontalStackedBarSeries *>(object: series)->m_axes); |
1448 | else if (qobject_cast<DeclarativeHorizontalPercentBarSeries *>(object: series)) |
1449 | doInitializeAxes(series, axes: qobject_cast<DeclarativeHorizontalPercentBarSeries *>(object: series)->m_axes); |
1450 | #endif |
1451 | #if QT_CONFIG(charts_boxplot_chart) |
1452 | else if (qobject_cast<DeclarativeBoxPlotSeries *>(object: series)) |
1453 | doInitializeAxes(series, axes: qobject_cast<DeclarativeBoxPlotSeries *>(object: series)->m_axes); |
1454 | #endif |
1455 | #if QT_CONFIG(charts_candlestick_chart) |
1456 | else if (qobject_cast<DeclarativeCandlestickSeries *>(object: series)) |
1457 | doInitializeAxes(series, axes: qobject_cast<DeclarativeCandlestickSeries *>(object: series)->m_axes); |
1458 | #endif |
1459 | // else: do nothing |
1460 | } |
1461 | |
1462 | void DeclarativeChart::doInitializeAxes(QAbstractSeries *series, DeclarativeAxes *axes) |
1463 | { |
1464 | qreal min; |
1465 | qreal max; |
1466 | // Initialize axis X |
1467 | if (axes->axisX()) { |
1468 | axes->emitAxisXChanged(); |
1469 | } else if (axes->axisXTop()) { |
1470 | axes->emitAxisXTopChanged(); |
1471 | } else { |
1472 | axes->setAxisX(defaultAxis(orientation: Qt::Horizontal, series)); |
1473 | findMinMaxForSeries(series, orientation: Qt::Horizontal, min, max); |
1474 | axes->axisX()->setRange(min, max); |
1475 | } |
1476 | |
1477 | // Initialize axis Y |
1478 | if (axes->axisY()) { |
1479 | axes->emitAxisYChanged(); |
1480 | } else if (axes->axisYRight()) { |
1481 | axes->emitAxisYRightChanged(); |
1482 | } else { |
1483 | axes->setAxisY(defaultAxis(orientation: Qt::Vertical, series)); |
1484 | findMinMaxForSeries(series, orientation: Qt::Vertical, min, max); |
1485 | axes->axisY()->setRange(min, max); |
1486 | } |
1487 | } |
1488 | |
1489 | void DeclarativeChart::findMinMaxForSeries(QAbstractSeries *series, Qt::Orientations orientation, |
1490 | qreal &min, qreal &max) |
1491 | { |
1492 | if (!series) { |
1493 | min = 0.5; |
1494 | max = 0.5; |
1495 | } else { |
1496 | AbstractDomain *domain = series->d_ptr->domain(); |
1497 | min = (orientation == Qt::Vertical) ? domain->minY() : domain->minX(); |
1498 | max = (orientation == Qt::Vertical) ? domain->maxY() : domain->maxX(); |
1499 | |
1500 | if (min == max) { |
1501 | min -= 0.5; |
1502 | max += 0.5; |
1503 | } |
1504 | } |
1505 | } |
1506 | |
1507 | void DeclarativeChart::queueRendererMouseEvent(QMouseEvent *event) |
1508 | { |
1509 | if (m_glXYDataManager->dataMap().size()) { |
1510 | QMouseEvent *newEvent = new QMouseEvent(event->type(), |
1511 | event->position() - m_adjustedPlotArea.topLeft(), |
1512 | event->globalPosition() - m_adjustedPlotArea.topLeft(), |
1513 | event->button(), |
1514 | event->buttons(), |
1515 | event->modifiers()); |
1516 | |
1517 | m_pendingRenderNodeMouseEvents.append(t: newEvent); |
1518 | |
1519 | update(); |
1520 | } |
1521 | } |
1522 | |
1523 | QPointF DeclarativeChart::mapToValue(const QPointF &position, QAbstractSeries *series) |
1524 | { |
1525 | return m_chart->mapToValue(position, series); |
1526 | } |
1527 | |
1528 | QPointF DeclarativeChart::mapToPosition(const QPointF &value, QAbstractSeries *series) |
1529 | { |
1530 | return m_chart->mapToPosition(value, series); |
1531 | } |
1532 | |
1533 | void DeclarativeChart::setPlotArea(const QRectF &rect) |
1534 | { |
1535 | m_chart->setPlotArea(rect); |
1536 | |
1537 | // If plotArea is set inside ChartView, contentGeometry is updated too early and we end up |
1538 | // with incorrect plotArea. Invalidate the layout to correct the geometry. |
1539 | m_chart->layout()->invalidate(); |
1540 | } |
1541 | |
1542 | QT_END_NAMESPACE |
1543 | |
1544 | #include "moc_declarativechart_p.cpp" |
1545 |
Definitions
- DeclarativeChart
- DeclarativeChart
- qt_allocate_bar_set_qml
- initChart
- handleSeriesAdded
- handlePendingRenderNodeMouseEventResponses
- changeMargins
- ~DeclarativeChart
- childEvent
- componentComplete
- seriesAxisAttachHelper
- handleAxisXSet
- handleAxisXTopSet
- handleAxisYSet
- handleAxisYRightSet
- geometryChange
- updatePaintNode
- sceneChanged
- renderScene
- mousePressEvent
- mouseReleaseEvent
- hoverMoveEvent
- mouseDoubleClickEvent
- handleAntialiasingChanged
- setTheme
- theme
- setAnimationOptions
- animationOptions
- setAnimationDuration
- animationDuration
- setAnimationEasingCurve
- animationEasingCurve
- setTitle
- title
- axisX
- axisY
- legend
- setTitleColor
- titleFont
- setTitleFont
- titleColor
- setBackgroundColor
- backgroundColor
- setPlotAreaColor
- plotAreaColor
- setLocalizeNumbers
- localizeNumbers
- setLocale
- locale
- count
- setDropShadowEnabled
- dropShadowEnabled
- backgroundRoundness
- setBackgroundRoundness
- zoom
- zoomIn
- zoomIn
- zoomOut
- zoomReset
- isZoomed
- scrollLeft
- scrollRight
- scrollUp
- scrollDown
- axes
- axesAppendFunc
- axesCountFunc
- axesAtFunc
- axesClearFunc
- series
- series
- createSeries
- removeSeries
- setAxisX
- setAxisY
- defaultAxis
- initializeAxes
- doInitializeAxes
- findMinMaxForSeries
- queueRendererMouseEvent
- mapToValue
- mapToPosition
Learn Advanced QML with KDAB
Find out more