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