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