1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses |
5 | |
6 | #include <QtCharts/QXYSeries> |
7 | #include <QtCharts/QColorAxis> |
8 | #include <private/qxyseries_p.h> |
9 | #include <private/abstractdomain_p.h> |
10 | #include <QtCharts/QValueAxis> |
11 | #include <private/xychart_p.h> |
12 | #include <QtCharts/QXYLegendMarker> |
13 | #include <private/charthelpers_p.h> |
14 | #include <private/qchart_p.h> |
15 | #include <QtGui/QPainter> |
16 | #include <QtMath> |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | /*! |
21 | \class QXYSeries |
22 | \inmodule QtCharts |
23 | \brief The QXYSeries class is a base class for line, spline, and scatter |
24 | series. |
25 | |
26 | QXYSeries supports displaying best fit line on a chart. Best fit line is a line |
27 | through a chart that expresses the relationship between points. |
28 | */ |
29 | /*! |
30 | \qmltype XYSeries |
31 | \nativetype QXYSeries |
32 | \inqmlmodule QtCharts |
33 | |
34 | \inherits AbstractSeries |
35 | |
36 | \brief A base type for line, spline, and scatter series. |
37 | |
38 | XYSeries supports displaying best fit line on a chart. Best fit line is a line |
39 | through a chart that expresses the relationship between points. |
40 | */ |
41 | |
42 | /*! |
43 | \enum QXYSeries::PointConfiguration |
44 | |
45 | This enum value describes the particular configuration of a point. |
46 | |
47 | \value Color |
48 | This enum value can be used to change a point's color. If used together |
49 | with QXYSeries::setPointConfiguration, the configuration's value should |
50 | be a valid QColor. |
51 | \value Size |
52 | This enum value can be used to change a point's size. If used together |
53 | with QXYSeries::setPointConfiguration, the configuration's value should |
54 | be a number, such as \c qreal or \c int. |
55 | \value Visibility |
56 | This enum value can be used to hide or show the point. If used |
57 | together with QXYSeries::setPointConfiguration, the configuration's value |
58 | should be boolean. |
59 | \value LabelVisibility |
60 | This enum value can be used to hide or show the label of the point. If used |
61 | together with QXYSeries::setPointConfiguration, the configuration's value |
62 | should be boolean. |
63 | \value [since 6.5] LabelFormat |
64 | This enum value can be used to set custom label text per-point. If used together with |
65 | QXYSeries::setPointConfiguration, the configuration's value should be a string. |
66 | \note If an empty string is set as the LabelFormat, it will be ignored, and the series |
67 | pointLabelsFormat will be used. |
68 | \sa pointLabelsFormat |
69 | |
70 | \sa setPointConfiguration() |
71 | \since 6.2 |
72 | */ |
73 | |
74 | /*! |
75 | \qmlproperty AbstractAxis XYSeries::axisX |
76 | The x-axis used for the series. If you leave both axisX and axisXTop |
77 | undefined, a value axis is created for the series. |
78 | \sa axisXTop, ValueAxis |
79 | */ |
80 | |
81 | /*! |
82 | \qmlproperty AbstractAxis XYSeries::axisY |
83 | The y-axis used for the series. If you leave both axisY and axisYRight |
84 | undefined, a value axis is created for the series. |
85 | \sa axisYRight, ValueAxis |
86 | */ |
87 | |
88 | /*! |
89 | \qmlproperty AbstractAxis XYSeries::axisXTop |
90 | The x-axis used for the series, drawn on top of the chart view. |
91 | |
92 | \note You can only provide either axisX or axisXTop, not both. |
93 | \sa axisX |
94 | */ |
95 | |
96 | /*! |
97 | \qmlproperty AbstractAxis XYSeries::axisYRight |
98 | The y-axis used for the series, drawn to the right on the chart view. |
99 | |
100 | \note You can only provide either axisY or axisYRight, not both. |
101 | \sa axisY |
102 | */ |
103 | |
104 | /*! |
105 | \qmlproperty AbstractAxis XYSeries::axisAngular |
106 | The angular axis used for the series, drawn around the polar chart view. |
107 | \sa axisX |
108 | */ |
109 | |
110 | /*! |
111 | \qmlproperty AbstractAxis XYSeries::axisRadial |
112 | The radial axis used for the series, drawn inside the polar chart view. |
113 | \sa axisY |
114 | */ |
115 | |
116 | /*! |
117 | \property QXYSeries::pointsVisible |
118 | \brief Whether the data points are visible and should be drawn. |
119 | */ |
120 | /*! |
121 | \qmlproperty bool XYSeries::pointsVisible |
122 | Whether the data points are visible and should be drawn. |
123 | */ |
124 | |
125 | /*! |
126 | \fn QPen QXYSeries::pen() const |
127 | Returns the pen used to draw the outline of the data points for the series. |
128 | \sa setPen() |
129 | */ |
130 | |
131 | /*! |
132 | \fn QBrush QXYSeries::brush() const |
133 | Returns the brush used to fill the data points for the series. |
134 | \sa setBrush() |
135 | */ |
136 | |
137 | /*! |
138 | \property QXYSeries::color |
139 | \brief The color of the series. |
140 | |
141 | This is the line (pen) color in case of QLineSeries or QSplineSeries and the |
142 | fill (brush) color in case of QScatterSeries or QAreaSeries. |
143 | \sa pen(), brush() |
144 | */ |
145 | /*! |
146 | \qmlproperty color XYSeries::color |
147 | The color of the series. This is the line (pen) color in case of LineSeries |
148 | or SplineSeries and the fill (brush) color in case of ScatterSeries or |
149 | AreaSeries. |
150 | */ |
151 | |
152 | /*! |
153 | \property QXYSeries::pointLabelsFormat |
154 | \brief The format used for showing labels with data points. |
155 | |
156 | QXYSeries supports the following format tags: |
157 | \table |
158 | \row |
159 | \li @index \li The index in the series of the data point. [since 6.5] |
160 | \row |
161 | \li @xPoint \li The x-coordinate of the data point. |
162 | \row |
163 | \li @yPoint \li The y-coordinate of the data point. |
164 | \endtable |
165 | |
166 | For example, the following usage of the format tags would produce labels |
167 | that display the data point shown inside brackets separated by a comma |
168 | (x, y): |
169 | |
170 | \code |
171 | series->setPointLabelsFormat("@index: (@xPoint, @yPoint)"); |
172 | \endcode |
173 | |
174 | By default, the labels' format is set to \c {@xPoint, @yPoint}. The labels |
175 | are shown on the plot area, and the labels on the edge of the plot area are |
176 | cut. If the points are close to each other, the labels may overlap. |
177 | |
178 | \sa pointLabelsVisible, pointLabelsFont, pointLabelsColor |
179 | */ |
180 | /*! |
181 | \qmlproperty string XYSeries::pointLabelsFormat |
182 | The format used for showing labels with data points. |
183 | |
184 | \sa pointLabelsVisible, pointLabelsFont, pointLabelsColor |
185 | */ |
186 | /*! |
187 | \fn void QXYSeries::pointLabelsFormatChanged(const QString &format) |
188 | This signal is emitted when the format of data point labels changes to |
189 | \a format. |
190 | */ |
191 | |
192 | /*! |
193 | \property QXYSeries::pointLabelsVisible |
194 | \brief The visibility of data point labels. |
195 | |
196 | This property is \c false by default. |
197 | |
198 | \sa pointLabelsFormat, pointLabelsClipping |
199 | */ |
200 | /*! |
201 | \qmlproperty bool XYSeries::pointLabelsVisible |
202 | The visibility of data point labels. This property is \c false by default. |
203 | |
204 | \sa pointLabelsFormat, pointLabelsClipping |
205 | */ |
206 | /*! |
207 | \fn void QXYSeries::pointLabelsVisibilityChanged(bool visible) |
208 | This signal is emitted when the visibility of the data point labels |
209 | changes to \a visible. |
210 | */ |
211 | |
212 | /*! |
213 | \property QXYSeries::pointLabelsFont |
214 | \brief The font used for data point labels. |
215 | |
216 | \sa pointLabelsFormat |
217 | */ |
218 | /*! |
219 | \qmlproperty font XYSeries::pointLabelsFont |
220 | The font used for data point labels. |
221 | |
222 | \sa pointLabelsFormat |
223 | */ |
224 | /*! |
225 | \fn void QXYSeries::pointLabelsFontChanged(const QFont &font); |
226 | This signal is emitted when the font used for data point labels changes to |
227 | \a font. |
228 | */ |
229 | |
230 | /*! |
231 | \property QXYSeries::pointLabelsColor |
232 | \brief The color used for data point labels. By default, the color is the color of the brush |
233 | defined in theme for labels. |
234 | |
235 | \sa pointLabelsFormat |
236 | */ |
237 | /*! |
238 | \qmlproperty font XYSeries::pointLabelsColor |
239 | The color used for data point labels. By default, the color is the color of the brush |
240 | defined in theme for labels. |
241 | |
242 | \sa pointLabelsFormat |
243 | */ |
244 | /*! |
245 | \fn void QXYSeries::pointLabelsColorChanged(const QColor &color); |
246 | This signal is emitted when the color used for data point labels changes to |
247 | \a color. |
248 | */ |
249 | |
250 | /*! |
251 | \property QXYSeries::pointLabelsClipping |
252 | \brief The clipping for data point labels. |
253 | |
254 | This property is \c true by default. The labels on the edge of the plot area |
255 | are cut when clipping is enabled. |
256 | |
257 | \sa pointLabelsVisible |
258 | */ |
259 | /*! |
260 | \qmlproperty bool XYSeries::pointLabelsClipping |
261 | The clipping for data point labels. This property is \c true by default. The |
262 | labels on the edge of the plot area are cut when clipping is enabled. |
263 | |
264 | \sa pointLabelsVisible |
265 | */ |
266 | |
267 | /*! |
268 | \property QXYSeries::selectedColor |
269 | \brief The color of the selected points. |
270 | |
271 | This is the fill (brush) color of points marked as selected. If not specified, |
272 | value of QXYSeries::color is used as default. |
273 | \sa color |
274 | \since 6.2 |
275 | */ |
276 | /*! |
277 | \qmlproperty color XYSeries::selectedColor |
278 | The color of the selected points. This is the fill (brush) color of points marked |
279 | as selected. |
280 | If not specified, value of QXYSeries::color is used as default. |
281 | \sa color |
282 | */ |
283 | |
284 | /*! |
285 | \fn void QXYSeries::markerSizeChanged(qreal size) |
286 | This signal is emitted when the marker size changes to \a size. |
287 | */ |
288 | |
289 | /*! |
290 | \fn void QXYSeries::pointLabelsClippingChanged(bool clipping) |
291 | This signal is emitted when the clipping of the data point labels changes to |
292 | \a clipping. |
293 | */ |
294 | /*! |
295 | \property QXYSeries::bestFitLineVisible |
296 | \brief The visibility of the best fit line. |
297 | |
298 | This property is \c false by default. |
299 | |
300 | \sa bestFitLineEquation |
301 | \since 6.2 |
302 | */ |
303 | /*! |
304 | \qmlproperty bool XYSeries::bestFitLineVisible |
305 | The visibility of the best fit line. |
306 | This property is \c false by default. |
307 | */ |
308 | /*! |
309 | \fn void QXYSeries::bestFitLineVisibilityChanged(bool visible) |
310 | This signal is emitted when the visibility of the best fit line changes to |
311 | \a visible. |
312 | */ |
313 | /*! |
314 | \property QXYSeries::bestFitLineColor |
315 | \brief The color of best fit line. |
316 | |
317 | \sa bestFitLineEquation, bestFitLineVisible |
318 | \since 6.2 |
319 | */ |
320 | /*! |
321 | \qmlproperty color XYSeries::bestFitLineColor |
322 | The color of best fit line. |
323 | \sa bestFitLineVisible |
324 | */ |
325 | /*! |
326 | \fn void QXYSeries::bestFitLineColorChanged(const QColor &color) |
327 | This signal is emitted when the color used for the best fit line changes to |
328 | \a color. |
329 | */ |
330 | |
331 | /*! |
332 | \fn void QXYSeries::clicked(const QPointF& point) |
333 | This signal is emitted when the user triggers a mouse event by |
334 | clicking the point \a point in the chart. |
335 | |
336 | \sa pressed(), released(), doubleClicked() |
337 | */ |
338 | /*! |
339 | \qmlsignal XYSeries::clicked(point point) |
340 | This signal is emitted when the user triggers a mouse event by clicking the |
341 | point \a point in the chart. For example: |
342 | \code |
343 | LineSeries { |
344 | XYPoint { x: 0; y: 0 } |
345 | XYPoint { x: 1.1; y: 2.1 } |
346 | onClicked: console.log("onClicked: " + point.x + ", " + point.y); |
347 | } |
348 | \endcode |
349 | |
350 | The corresponding signal handler is \c onClicked(). |
351 | |
352 | \sa pressed(), released(), doubleClicked() |
353 | */ |
354 | |
355 | /*! |
356 | \fn void QXYSeries::hovered(const QPointF &point, bool state) |
357 | This signal is emitted when a mouse is hovered over the point \a point in |
358 | the chart. When the mouse moves over the point, \a state turns \c true, |
359 | and when the mouse moves away again, it turns \c false. |
360 | */ |
361 | /*! |
362 | \qmlsignal XYSeries::hovered(point point, bool state) |
363 | This signal is emitted when a mouse is hovered over the point \a point in |
364 | the chart. When the mouse moves over the point, \a state turns \c true, |
365 | and when the mouse moves away again, it turns \c false. |
366 | |
367 | The corresponding signal handler is \c onHovered(). |
368 | */ |
369 | |
370 | /*! |
371 | \fn void QXYSeries::pressed(const QPointF& point) |
372 | This signal is emitted when the user presses the data point \a point in the |
373 | chart and holds down the mouse button. |
374 | |
375 | \sa clicked(), released(), doubleClicked() |
376 | */ |
377 | /*! |
378 | \qmlsignal XYSeries::pressed(point point) |
379 | This signal is emitted when the user presses the data point \a point in the |
380 | chart and holds down the mouse button. For example: |
381 | \code |
382 | LineSeries { |
383 | XYPoint { x: 0; y: 0 } |
384 | XYPoint { x: 1.1; y: 2.1 } |
385 | onPressed: console.log("onPressed: " + point.x + ", " + point.y); |
386 | } |
387 | \endcode |
388 | |
389 | The corresponding signal handler is \c onPressed(). |
390 | |
391 | \sa clicked(), released(), doubleClicked() |
392 | */ |
393 | |
394 | /*! |
395 | \fn void QXYSeries::released(const QPointF& point) |
396 | This signal is emitted when the user releases the mouse press on the data |
397 | point specified by \a point. |
398 | \sa pressed(), clicked(), doubleClicked() |
399 | */ |
400 | /*! |
401 | \qmlsignal XYSeries::released(point point) |
402 | This signal is emitted when the user releases the mouse press on the data |
403 | point specified by \a point. |
404 | For example: |
405 | \code |
406 | LineSeries { |
407 | XYPoint { x: 0; y: 0 } |
408 | XYPoint { x: 1.1; y: 2.1 } |
409 | onReleased: console.log("onReleased: " + point.x + ", " + point.y); |
410 | } |
411 | \endcode |
412 | |
413 | The corresponding signal handler is \c onReleased(). |
414 | |
415 | \sa pressed(), clicked(), doubleClicked() |
416 | */ |
417 | |
418 | /*! |
419 | \fn void QXYSeries::doubleClicked(const QPointF& point) |
420 | This signal is emitted when the user double-clicks the data point \a point |
421 | in the chart. The \a point is the point where the first press was triggered. |
422 | \sa pressed(), released(), clicked() |
423 | */ |
424 | /*! |
425 | \qmlsignal XYSeries::doubleClicked(point point) |
426 | This signal is emitted when the user double-clicks the data point \a point |
427 | in the chart. The \a point is the point where the first press was triggered. |
428 | For example: |
429 | \code |
430 | LineSeries { |
431 | XYPoint { x: 0; y: 0 } |
432 | XYPoint { x: 1.1; y: 2.1 } |
433 | onDoubleClicked: console.log("onDoubleClicked: " + point.x + ", " + point.y); |
434 | } |
435 | \endcode |
436 | |
437 | The corresponding signal handler is \c onDoubleClicked(). |
438 | |
439 | \sa pressed(), released(), clicked() |
440 | */ |
441 | |
442 | /*! |
443 | \fn void QXYSeries::pointReplaced(int index) |
444 | This signal is emitted when a point is replaced at the position specified by |
445 | \a index. |
446 | \sa replace() |
447 | */ |
448 | /*! |
449 | \qmlsignal XYSeries::pointReplaced(int index) |
450 | This signal is emitted when a point is replaced at the position specified by |
451 | \a index. |
452 | |
453 | The corresponding signal handler is \c onPointReplaced(). |
454 | */ |
455 | |
456 | /*! |
457 | \fn void QXYSeries::pointsReplaced() |
458 | This signal is emitted when all points are replaced with other points. |
459 | \sa replace() |
460 | */ |
461 | /*! |
462 | \qmlsignal XYSeries::pointsReplaced() |
463 | This signal is emitted when all points are replaced with other points. |
464 | |
465 | The corresponding signal handler is \c onPointsReplaced(). |
466 | */ |
467 | |
468 | /*! |
469 | \fn void QXYSeries::pointAdded(int index) |
470 | This signal is emitted when a point is added at the position specified by |
471 | \a index. |
472 | \sa append(), insert() |
473 | */ |
474 | /*! |
475 | \qmlsignal XYSeries::pointAdded(int index) |
476 | This signal is emitted when a point is added at the position specified by |
477 | \a index. |
478 | |
479 | The corresponding signal handler is \c onPointAdded(). |
480 | */ |
481 | |
482 | /*! |
483 | \fn void QXYSeries::pointRemoved(int index) |
484 | This signal is emitted when a point is removed from the position specified |
485 | by \a index. |
486 | \sa remove() |
487 | */ |
488 | |
489 | /*! |
490 | \qmlsignal XYSeries::pointRemoved(int index) |
491 | This signal is emitted when a point is removed from the position specified |
492 | by \a index. |
493 | |
494 | The corresponding signal handler is \c onPointRemoved(). |
495 | */ |
496 | |
497 | /*! |
498 | \fn void QXYSeries::pointsRemoved(int index, int count) |
499 | This signal is emitted when the number of points specified by \a count |
500 | is removed starting at the position specified by \a index. |
501 | \sa removePoints(), clear() |
502 | */ |
503 | |
504 | /*! |
505 | \qmlsignal XYSeries::pointsRemoved(int index, int count) |
506 | This signal is emitted when the number of points specified by \a count |
507 | is removed starting at the position specified by \a index. |
508 | |
509 | The corresponding signal handler is \c onPointRemoved(). |
510 | */ |
511 | |
512 | /*! |
513 | \fn void QXYSeries::colorChanged(QColor color) |
514 | This signal is emitted when the line (pen) color changes to \a color. |
515 | */ |
516 | |
517 | /*! |
518 | \fn void QXYSeries::penChanged(const QPen &pen) |
519 | This signal is emitted when the pen changes to \a pen. |
520 | */ |
521 | |
522 | /*! |
523 | \fn void QXYSeries::selectedPointsChanged() |
524 | This signal is emitted when the points selection changes. |
525 | */ |
526 | |
527 | /*! |
528 | \fn void QXYSeries::lightMarkerChanged(const QImage &lightMarker) |
529 | This signal is emitted when the light marker image changes to \a lightMarker. |
530 | \sa QXYSeries::setLightMarker() |
531 | \since 6.2 |
532 | */ |
533 | |
534 | /*! |
535 | \fn void QXYSeriesPrivate::seriesUpdated() |
536 | \internal |
537 | */ |
538 | |
539 | /*! |
540 | \qmlmethod XYSeries::append(real x, real y) |
541 | Appends a point with the coordinates \a x and \a y to the series. |
542 | */ |
543 | |
544 | /*! |
545 | \qmlmethod XYSeries::replace(real oldX, real oldY, real newX, real newY) |
546 | Replaces the point with the coordinates \a oldX and \a oldY with the point |
547 | with the coordinates \a newX and \a newY. Does nothing if the old point does |
548 | not exist. |
549 | */ |
550 | |
551 | /*! |
552 | \qmlmethod XYSeries::remove(real x, real y) |
553 | Removes the point with the coordinates \a x and \a y from the series. Does |
554 | nothing if the point does not exist. |
555 | */ |
556 | |
557 | /*! |
558 | \qmlmethod XYSeries::remove(int index) |
559 | Removes the point at the position specified by \a index from the series. |
560 | */ |
561 | |
562 | /*! |
563 | \qmlmethod XYSeries::removePoints(int index, int count) |
564 | Removes the number of points specified by \a count from the series starting |
565 | at the position specified by \a index. |
566 | */ |
567 | |
568 | /*! |
569 | \qmlmethod XYSeries::insert(int index, real x, real y) |
570 | Inserts a point with the coordinates \a x and \a y to the position specified |
571 | by \a index in the series. If the index is 0 or less than 0, the point is |
572 | prepended to the list of points. If the index is equal to or greater than |
573 | than the number of points in the series, the point is appended to the |
574 | list of points. |
575 | */ |
576 | |
577 | /*! |
578 | \qmlmethod QPointF XYSeries::at(int index) |
579 | Returns the point at the position specified by \a index. Returns (0, 0) if |
580 | the index is not valid. |
581 | */ |
582 | |
583 | /*! |
584 | \internal |
585 | |
586 | Constructs an empty series object that is a child of \a parent. |
587 | When the series object is added to QChart, instance ownerships is transferred. |
588 | */ |
589 | QXYSeries::QXYSeries(QXYSeriesPrivate &d, QObject *parent) |
590 | : QAbstractSeries(d, parent) |
591 | { |
592 | } |
593 | |
594 | /*! |
595 | Deletes the series. Series added to QChart instances are owned by them, |
596 | and are deleted when the QChart instances are deleted. |
597 | */ |
598 | QXYSeries::~QXYSeries() |
599 | { |
600 | } |
601 | |
602 | /*! |
603 | Adds the data point with the coordinates \a x and \a y to the series. |
604 | */ |
605 | void QXYSeries::append(qreal x, qreal y) |
606 | { |
607 | append(point: QPointF(x, y)); |
608 | } |
609 | |
610 | /*! |
611 | \overload |
612 | Adds the data point \a point to the series. |
613 | */ |
614 | void QXYSeries::append(const QPointF &point) |
615 | { |
616 | Q_D(QXYSeries); |
617 | |
618 | if (isValidValue(point)) { |
619 | d->m_points << point; |
620 | emit pointAdded(index: d->m_points.size() - 1); |
621 | } |
622 | } |
623 | |
624 | /*! |
625 | \overload |
626 | Adds the list of data points specified by \a points to the series. |
627 | */ |
628 | void QXYSeries::append(const QList<QPointF> &points) |
629 | { |
630 | foreach (const QPointF &point , points) |
631 | append(point); |
632 | } |
633 | |
634 | /*! |
635 | Replaces the point with the coordinates \a oldX and \a oldY with the point |
636 | with the coordinates \a newX and \a newY. Does nothing if the old point does |
637 | not exist. |
638 | |
639 | \sa pointReplaced() |
640 | */ |
641 | void QXYSeries::replace(qreal oldX, qreal oldY, qreal newX, qreal newY) |
642 | { |
643 | replace(oldPoint: QPointF(oldX, oldY), newPoint: QPointF(newX, newY)); |
644 | } |
645 | |
646 | /*! |
647 | Replaces the point specified by \a oldPoint with the one specified by |
648 | \a newPoint. |
649 | \sa pointReplaced() |
650 | */ |
651 | void QXYSeries::replace(const QPointF &oldPoint, const QPointF &newPoint) |
652 | { |
653 | Q_D(QXYSeries); |
654 | int index = d->m_points.indexOf(t: oldPoint); |
655 | if (index == -1) |
656 | return; |
657 | replace(index, newPoint); |
658 | } |
659 | |
660 | /*! |
661 | Replaces the point at the position specified by \a index with the point that |
662 | has the coordinates \a newX and \a newY. |
663 | \sa pointReplaced() |
664 | */ |
665 | void QXYSeries::replace(int index, qreal newX, qreal newY) |
666 | { |
667 | replace(index, newPoint: QPointF(newX, newY)); |
668 | } |
669 | |
670 | /*! |
671 | Replaces the point at the position specified by \a index with the point |
672 | specified by \a newPoint. |
673 | \sa pointReplaced() |
674 | */ |
675 | void QXYSeries::replace(int index, const QPointF &newPoint) |
676 | { |
677 | Q_D(QXYSeries); |
678 | if (isValidValue(point: newPoint)) { |
679 | d->m_points[index] = newPoint; |
680 | emit pointReplaced(index); |
681 | } |
682 | } |
683 | |
684 | /*! |
685 | Replaces the current points with the points specified by \a points. |
686 | \note This is much faster than replacing data points one by one, |
687 | or first clearing all data, and then appending the new data. Emits QXYSeries::pointsReplaced() |
688 | when the points have been replaced. |
689 | \sa pointsReplaced() |
690 | */ |
691 | void QXYSeries::replace(const QList<QPointF> &points) |
692 | { |
693 | Q_D(QXYSeries); |
694 | d->m_points = points; |
695 | emit pointsReplaced(); |
696 | } |
697 | |
698 | /*! |
699 | Removes the configuration of a point located at \a index |
700 | and restores the default look derived from the series' settings. |
701 | |
702 | \note It doesn't affect the configuration of other points. |
703 | \sa clearPointsConfiguration(), setPointConfiguration() |
704 | \since 6.2 |
705 | */ |
706 | void QXYSeries::clearPointConfiguration(const int index) |
707 | { |
708 | Q_D(QXYSeries); |
709 | if (d->m_pointsConfiguration.contains(key: index)) { |
710 | d->m_pointsConfiguration.remove(key: index); |
711 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
712 | } |
713 | } |
714 | |
715 | /*! |
716 | Removes the configuration property identified by \a key from the point at \a index |
717 | and restores the default look derived from the series' settings. |
718 | |
719 | Removes the configuration type, such as color or size, |
720 | specified by \a key from the point at \a index with configuration customizations, |
721 | allowing that configuration property to be rendered as the default |
722 | specified in the series' properties. |
723 | |
724 | \note It doesn't affect the configuration of other points. |
725 | \sa clearPointsConfiguration(), setPointConfiguration() |
726 | \since 6.2 |
727 | */ |
728 | void QXYSeries::clearPointConfiguration(const int index, const QXYSeries::PointConfiguration key) |
729 | { |
730 | Q_D(QXYSeries); |
731 | if (d->m_pointsConfiguration.contains(key: index)) { |
732 | auto &conf = d->m_pointsConfiguration[index]; |
733 | if (conf.contains(key)) { |
734 | conf.remove(key); |
735 | d->m_pointsConfiguration[index] = conf; |
736 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
737 | } |
738 | } |
739 | } |
740 | |
741 | /*! |
742 | Removes the configuration of all points in the series and restores |
743 | the default look derived from the series' settings. |
744 | |
745 | \sa setPointConfiguration() |
746 | \since 6.2 |
747 | */ |
748 | void QXYSeries::clearPointsConfiguration() |
749 | { |
750 | Q_D(QXYSeries); |
751 | d->m_pointsConfiguration.clear(); |
752 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
753 | } |
754 | |
755 | /*! |
756 | Removes the configuration property identified by \a key from all |
757 | points and restores the default look derived from the series' settings. |
758 | |
759 | Removes the configuration type, such as color or size, |
760 | specified by \a key from all points with configuration customizations, |
761 | allowing that configuration property to be rendered as the default |
762 | specified in the series properties. |
763 | |
764 | \sa clearPointsConfiguration(), setPointConfiguration() |
765 | \since 6.2 |
766 | */ |
767 | void QXYSeries::clearPointsConfiguration(const QXYSeries::PointConfiguration key) |
768 | { |
769 | Q_D(QXYSeries); |
770 | bool needsUpdate = false; |
771 | for (const int &index : d->m_pointsConfiguration.keys()) { |
772 | auto &conf = d->m_pointsConfiguration[index]; |
773 | if (conf.contains(key)) { |
774 | conf.remove(key); |
775 | d->m_pointsConfiguration[index] = conf; |
776 | needsUpdate = true; |
777 | } |
778 | } |
779 | |
780 | if (needsUpdate) |
781 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
782 | } |
783 | |
784 | /*! |
785 | Enables customizing the appearance of a point located at \a index with |
786 | desired \a configuration. |
787 | |
788 | With points configuration you can change various aspects of every point's appearance. |
789 | |
790 | A point's configuration is represented as a hash map with QXYSeries::pointConfiguration |
791 | keys and QVariant values. For example: |
792 | \code |
793 | QLineSeries *series = new QLineSeries(); |
794 | series->setName("Customized series"); |
795 | series->setPointsVisible(true); |
796 | |
797 | *series << QPointF(0, 6) << QPointF(2, 4) << QPointF(3, 6) << QPointF(7, 4) << QPointF(10, 5) |
798 | << QPointF(11, 1) << QPointF(13, 3) << QPointF(17, 6) << QPointF(18, 3) |
799 | << QPointF(20, 2); |
800 | |
801 | QChart *chart = new QChart(); |
802 | chart->addSeries(series); |
803 | chart->createDefaultAxes(); |
804 | |
805 | QHash<QXYSeries::PointConfiguration, QVariant> conf; |
806 | conf[QXYSeries::PointConfiguration::Color] = QColor(Qt::red); |
807 | conf[QXYSeries::PointConfiguration::Size] = 8; |
808 | conf[QXYSeries::PointConfiguration::LabelVisibility] = true; |
809 | |
810 | series->setPointConfiguration(4, conf); |
811 | |
812 | conf.remove(QXYSeries::PointConfiguration::Color); |
813 | conf[QXYSeries::PointConfiguration::LabelFormat] = "This Point"; |
814 | series->setPointConfiguration(6, conf); |
815 | \endcode |
816 | |
817 | In this example, you can see a default QLineSeries with 10 points and with changed configuration |
818 | of two points. Both changed points are visibly bigger than the others with a look derived from |
819 | the series configuration. |
820 | By default, points don't have labels, but the point at index 4 has a label thanks to the |
821 | QXYSeries::PointConfiguration::LabelVisibility and QXYSeries::PointConfiguration::LabelFormat |
822 | configuration values. |
823 | The point at index 6 has a custom label \e {This Point} thanks to the |
824 | QXYSeries::PointConfiguration::LabelFormat configuration value. |
825 | Below is an example of a chart created in this way: |
826 | \image xyseries_point_configuration.png |
827 | |
828 | \sa pointsConfiguration(), clearPointsConfiguration() |
829 | \since 6.2 |
830 | */ |
831 | void QXYSeries::setPointConfiguration( |
832 | const int index, const QHash<QXYSeries::PointConfiguration, QVariant> &configuration) |
833 | { |
834 | Q_D(QXYSeries); |
835 | if (d->m_pointsConfiguration[index] != configuration) { |
836 | d->m_pointsConfiguration[index] = configuration; |
837 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
838 | } |
839 | } |
840 | |
841 | /*! |
842 | Enables customizing a particular aspect of a point's configuration. |
843 | |
844 | \note Points configuration concept provides a flexible way to configure various aspects |
845 | of a point's appearance. Thus, values need to have an elastic type such as QVariant. |
846 | See QXYSeries::PointConfiguration to see what \a value should be passed |
847 | for certain \a key. |
848 | \sa pointsConfiguration() |
849 | \since 6.2 |
850 | */ |
851 | void QXYSeries::setPointConfiguration(const int index, const QXYSeries::PointConfiguration key, |
852 | const QVariant &value) |
853 | { |
854 | Q_D(QXYSeries); |
855 | if (d->setPointConfiguration(index, key, value)) |
856 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
857 | } |
858 | |
859 | /*! |
860 | Enables customizing the configuration of multiple points as specified |
861 | by \a pointsConfiguration. |
862 | |
863 | \sa pointsConfiguration() |
864 | \since 6.2 |
865 | */ |
866 | void QXYSeries::setPointsConfiguration( |
867 | const QHash<int, QHash<QXYSeries::PointConfiguration, QVariant>> &pointsConfiguration) |
868 | { |
869 | Q_D(QXYSeries); |
870 | if (d->m_pointsConfiguration != pointsConfiguration) { |
871 | d->m_pointsConfiguration = pointsConfiguration; |
872 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
873 | } |
874 | } |
875 | |
876 | /*! |
877 | Returns a map representing the configuration of a point at \a index. |
878 | |
879 | With points configuration you can change various aspects of each point's look. |
880 | |
881 | \sa setPointConfiguration() |
882 | \since 6.2 |
883 | */ |
884 | QHash<QXYSeries::PointConfiguration, QVariant> QXYSeries::pointConfiguration(const int index) const |
885 | { |
886 | Q_D(const QXYSeries); |
887 | return d->m_pointsConfiguration[index]; |
888 | } |
889 | |
890 | /*! |
891 | Returns a map with points' indexes as keys and points' configuration as values. |
892 | |
893 | \sa setPointConfiguration(), pointConfiguration() |
894 | \since 6.2 |
895 | */ |
896 | QHash<int, QHash<QXYSeries::PointConfiguration, QVariant>> QXYSeries::pointsConfiguration() const |
897 | { |
898 | Q_D(const QXYSeries); |
899 | return d->m_pointsConfiguration; |
900 | } |
901 | |
902 | /*! |
903 | Sets the points' sizes according to a passed list of values. Values from |
904 | \a sourceData are sorted and mapped to a point size which is between \a minSize |
905 | and \a maxSize. |
906 | |
907 | \note If \a sourceData length is smaller than number of points in the series, then |
908 | size of the points at the end of the series will stay the same. |
909 | \sa setPointConfiguration(), pointConfiguration() |
910 | \since 6.2 |
911 | */ |
912 | void QXYSeries::sizeBy(const QList<qreal> &sourceData, const qreal minSize, const qreal maxSize) |
913 | { |
914 | Q_D(QXYSeries); |
915 | |
916 | Q_ASSERT(minSize <= maxSize); |
917 | Q_ASSERT(minSize >= 0); |
918 | |
919 | qreal min = std::numeric_limits<qreal>::max(); |
920 | qreal max = -std::numeric_limits<qreal>::max(); |
921 | for (const auto &p : sourceData) { |
922 | min = qMin(a: min, b: p); |
923 | max = qMax(a: max, b: p); |
924 | } |
925 | |
926 | const qreal range = max - min; |
927 | const qreal sizeRange = maxSize - minSize; |
928 | bool changed = false; |
929 | |
930 | for (int i = 0; i < sourceData.size() && i < d->m_points.size(); ++i) { |
931 | qreal pointSize = minSize; |
932 | if (range != 0) { |
933 | const qreal startValue = sourceData.at(i) - min; |
934 | const qreal percentage = startValue / range; |
935 | pointSize = minSize + (percentage * sizeRange); |
936 | } |
937 | if (d->setPointConfiguration(index: i, key: QXYSeries::PointConfiguration::Size, value: pointSize)) |
938 | changed = true; |
939 | } |
940 | |
941 | if (changed) |
942 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
943 | } |
944 | |
945 | /*! |
946 | Sets the points' color according to a passed list of values. Values from |
947 | \a sourceData are sorted and mapped to the \a gradient. |
948 | |
949 | If the series has a QColorAxis attached, then a gradient from the axis |
950 | is going to be used. |
951 | |
952 | \sa setPointConfiguration(), pointConfiguration(), QColorAxis |
953 | \since 6.2 |
954 | */ |
955 | void QXYSeries::colorBy(const QList<qreal> &sourceData, const QLinearGradient &gradient) |
956 | { |
957 | Q_D(QXYSeries); |
958 | |
959 | d->m_colorByData = sourceData; |
960 | if (d->m_colorByData.isEmpty()) |
961 | return; |
962 | |
963 | const qreal imgSize = 100.0; |
964 | |
965 | qreal min = std::numeric_limits<qreal>::max(); |
966 | qreal max = std::numeric_limits<qreal>::min(); |
967 | for (const auto &p : sourceData) { |
968 | min = qMin(a: min, b: p); |
969 | max = qMax(a: max, b: p); |
970 | } |
971 | |
972 | qreal range = max - min; |
973 | |
974 | QLinearGradient usedGradient = gradient; |
975 | |
976 | // Gradient will be taked from the first attached color axis. |
977 | // If there are more color axis, they will have just changed range. |
978 | bool axisFound = false; |
979 | const auto axes = attachedAxes(); |
980 | for (const auto &axis : axes) { |
981 | if (axis->type() == QAbstractAxis::AxisTypeColor) { |
982 | QColorAxis *colorAxis = static_cast<QColorAxis *>(axis); |
983 | if (!axisFound) { |
984 | usedGradient = QLinearGradient(QPointF(0,0), QPointF(0, imgSize)); |
985 | const auto stops = colorAxis->gradient().stops(); |
986 | for (const auto &stop : stops) |
987 | usedGradient.setColorAt(pos: stop.first, color: stop.second); |
988 | |
989 | if (!colorAxis->autoRange()) { |
990 | min = colorAxis->min(); |
991 | max = colorAxis->max(); |
992 | range = max - min; |
993 | } |
994 | |
995 | axisFound = true; |
996 | } |
997 | |
998 | if (colorAxis->autoRange()) |
999 | colorAxis->setRange(min, max); |
1000 | } |
1001 | } |
1002 | |
1003 | QImage image(imgSize, imgSize, QImage::Format_ARGB32); |
1004 | QPainter painter(&image); |
1005 | painter.fillRect(image.rect(), usedGradient); |
1006 | |
1007 | // To ensure that negative values will be well handled, distance from min to 0 |
1008 | // will be added to min and every single value. This will move entire values |
1009 | // list to positive only values. |
1010 | const qreal diff = min < 0 ? qAbs(t: min) : 0; |
1011 | min += diff; |
1012 | |
1013 | bool changed = false; |
1014 | |
1015 | for (int i = 0; i < sourceData.size() && i < d->m_points.size(); ++i) { |
1016 | const qreal startValue = qMax(a: 0.0, b: sourceData.at(i) + diff - min); |
1017 | const qreal percentage = startValue / range; |
1018 | QColor color = image.pixelColor(x: 0, y: qMin(a: percentage * imgSize, b: imgSize - 1)); |
1019 | if (d->setPointConfiguration(index: i, key: QXYSeries::PointConfiguration::Color, value: color)) |
1020 | changed = true; |
1021 | } |
1022 | |
1023 | if (changed) |
1024 | emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration); |
1025 | } |
1026 | |
1027 | /*! |
1028 | Returns true if point at given \a index is among selected points and false otherwise. |
1029 | \note Selected points are drawn using the selected color if it was specified. |
1030 | \sa selectedPoints(), setPointSelected(), setSelectedColor() |
1031 | \since 6.2 |
1032 | */ |
1033 | bool QXYSeries::isPointSelected(int index) |
1034 | { |
1035 | Q_D(QXYSeries); |
1036 | return d->isPointSelected(index); |
1037 | } |
1038 | |
1039 | /*! |
1040 | Marks point at \a index as selected. |
1041 | \note Emits QXYSeries::selectedPointsChanged |
1042 | \sa setPointSelected() |
1043 | \since 6.2 |
1044 | */ |
1045 | void QXYSeries::selectPoint(int index) |
1046 | { |
1047 | setPointSelected(index, selected: true); |
1048 | } |
1049 | |
1050 | /*! |
1051 | Deselects point at given \a index. |
1052 | \note Emits QXYSeries::selectedPointsChanged |
1053 | \sa setPointSelected() |
1054 | \since 6.2 |
1055 | */ |
1056 | void QXYSeries::deselectPoint(int index) |
1057 | { |
1058 | setPointSelected(index, selected: false); |
1059 | } |
1060 | |
1061 | /*! |
1062 | Marks point at given \a index as either selected or deselected as specified by \a selected. |
1063 | \note Selected points are drawn using the selected color if it was specified. Emits QXYSeries::selectedPointsChanged |
1064 | \sa setPointSelected(), setSelectedColor() |
1065 | \since 6.2 |
1066 | */ |
1067 | void QXYSeries::setPointSelected(int index, bool selected) |
1068 | { |
1069 | Q_D(QXYSeries); |
1070 | |
1071 | bool callSignal = false; |
1072 | d->setPointSelected(index, selected, callSignal); |
1073 | |
1074 | if (callSignal) |
1075 | emit selectedPointsChanged(); |
1076 | } |
1077 | |
1078 | /*! |
1079 | Marks all points in the series as selected, |
1080 | \note Emits QXYSeries::selectedPointsChanged |
1081 | \sa setPointSelected() |
1082 | \since 6.2 |
1083 | */ |
1084 | void QXYSeries::selectAllPoints() |
1085 | { |
1086 | Q_D(QXYSeries); |
1087 | |
1088 | bool callSignal = false; |
1089 | for (int i = 0; i < d->m_points.size(); ++i) |
1090 | d->setPointSelected(index: i, selected: true, callSignal); |
1091 | |
1092 | if (callSignal) |
1093 | emit selectedPointsChanged(); |
1094 | } |
1095 | |
1096 | /*! |
1097 | Deselects all points in the series. |
1098 | \note Emits QXYSeries::selectedPointsChanged |
1099 | \sa setPointSelected() |
1100 | \since 6.2 |
1101 | */ |
1102 | void QXYSeries::deselectAllPoints() |
1103 | { |
1104 | Q_D(QXYSeries); |
1105 | |
1106 | bool callSignal = false; |
1107 | for (int i = 0; i < d->m_points.size(); ++i) |
1108 | d->setPointSelected(index: i, selected: false, callSignal); |
1109 | |
1110 | if (callSignal) |
1111 | emit selectedPointsChanged(); |
1112 | } |
1113 | |
1114 | /*! |
1115 | Marks multiple points passed in a \a indexes list as selected. |
1116 | \note Emits QXYSeries::selectedPointsChanged |
1117 | \sa setPointSelected() |
1118 | \since 6.2 |
1119 | */ |
1120 | void QXYSeries::selectPoints(const QList<int> &indexes) |
1121 | { |
1122 | Q_D(QXYSeries); |
1123 | |
1124 | bool callSignal = false; |
1125 | for (const int &index : indexes) |
1126 | d->setPointSelected(index, selected: true, callSignal); |
1127 | |
1128 | if (callSignal) |
1129 | emit selectedPointsChanged(); |
1130 | } |
1131 | |
1132 | /*! |
1133 | Marks multiple points passed in a \a indexes list as deselected. |
1134 | \note Emits QXYSeries::selectedPointsChanged |
1135 | \sa setPointSelected() |
1136 | \since 6.2 |
1137 | */ |
1138 | void QXYSeries::deselectPoints(const QList<int> &indexes) |
1139 | { |
1140 | Q_D(QXYSeries); |
1141 | |
1142 | bool callSignal = false; |
1143 | for (const int &index : indexes) |
1144 | d->setPointSelected(index, selected: false, callSignal); |
1145 | |
1146 | if (callSignal) |
1147 | emit selectedPointsChanged(); |
1148 | } |
1149 | |
1150 | /*! |
1151 | Changes selection state of points at given \a indexes to the opposite one. Makes |
1152 | \note Emits QXYSeries::selectedPointsChanged |
1153 | \sa setPointSelected() |
1154 | \since 6.2 |
1155 | */ |
1156 | void QXYSeries::toggleSelection(const QList<int> &indexes) |
1157 | { |
1158 | Q_D(QXYSeries); |
1159 | |
1160 | bool callSignal = false; |
1161 | for (const int &index : indexes) |
1162 | d->setPointSelected(index, selected: !isPointSelected(index), callSignal); |
1163 | |
1164 | if (callSignal) |
1165 | emit selectedPointsChanged(); |
1166 | } |
1167 | |
1168 | /*! |
1169 | Returns a list of points indexes marked as selected. |
1170 | Selected points are visible regardless of points visibility. |
1171 | \sa setPointSelected(), pointsVisible() |
1172 | \since 6.2 |
1173 | */ |
1174 | QList<int> QXYSeries::selectedPoints() const |
1175 | { |
1176 | Q_D(const QXYSeries); |
1177 | return QList<int>(d->m_selectedPoints.begin(), d->m_selectedPoints.end()); |
1178 | } |
1179 | |
1180 | /*! |
1181 | Removes the point that has the coordinates \a x and \a y from the series. |
1182 | \sa pointRemoved() |
1183 | */ |
1184 | void QXYSeries::remove(qreal x, qreal y) |
1185 | { |
1186 | remove(point: QPointF(x, y)); |
1187 | } |
1188 | |
1189 | /*! |
1190 | Removes the data point \a point from the series. |
1191 | \sa pointRemoved() |
1192 | */ |
1193 | void QXYSeries::remove(const QPointF &point) |
1194 | { |
1195 | Q_D(QXYSeries); |
1196 | int index = d->m_points.indexOf(t: point); |
1197 | if (index == -1) |
1198 | return; |
1199 | remove(index); |
1200 | } |
1201 | |
1202 | /*! |
1203 | Removes the point at the position specified by \a index from the series. |
1204 | \sa pointRemoved() |
1205 | */ |
1206 | void QXYSeries::remove(int index) |
1207 | { |
1208 | Q_D(QXYSeries); |
1209 | d->m_points.remove(i: index); |
1210 | |
1211 | bool callSignal = false; |
1212 | d->setPointSelected(index, selected: false, callSignal); |
1213 | |
1214 | emit pointRemoved(index); |
1215 | if (callSignal) |
1216 | emit selectedPointsChanged(); |
1217 | } |
1218 | |
1219 | /*! |
1220 | Removes the number of points specified by \a count from the series starting at |
1221 | the position specified by \a index. |
1222 | \sa pointsRemoved() |
1223 | */ |
1224 | void QXYSeries::removePoints(int index, int count) |
1225 | { |
1226 | // This function doesn't overload remove as there is chance for it to get mixed up with |
1227 | // remove(qreal, qreal) overload in some implicit casting cases. |
1228 | Q_D(QXYSeries); |
1229 | if (count > 0) { |
1230 | d->m_points.remove(i: index, n: count); |
1231 | |
1232 | bool callSignal = false; |
1233 | if (!d->m_selectedPoints.empty()) { |
1234 | QSet<int> selectedAfterRemoving; |
1235 | |
1236 | for (const int &selectedPointIndex : std::as_const(t&: d->m_selectedPoints)) { |
1237 | if (selectedPointIndex < index) { |
1238 | selectedAfterRemoving << selectedPointIndex; |
1239 | } else if (selectedPointIndex >= index + count) { |
1240 | selectedAfterRemoving << selectedPointIndex - count; |
1241 | callSignal = true; |
1242 | } else { |
1243 | callSignal = true; |
1244 | } |
1245 | } |
1246 | |
1247 | d->m_selectedPoints = selectedAfterRemoving; |
1248 | } |
1249 | |
1250 | emit pointsRemoved(index, count); |
1251 | if (callSignal) |
1252 | emit selectedPointsChanged(); |
1253 | } |
1254 | } |
1255 | |
1256 | /*! |
1257 | Inserts the data point \a point in the series at the position specified by |
1258 | \a index. |
1259 | \sa pointAdded() |
1260 | */ |
1261 | void QXYSeries::insert(int index, const QPointF &point) |
1262 | { |
1263 | Q_D(QXYSeries); |
1264 | if (isValidValue(point)) { |
1265 | index = qMax(a: 0, b: qMin(a: index, b: d->m_points.size())); |
1266 | |
1267 | d->m_points.insert(i: index, t: point); |
1268 | |
1269 | bool callSignal = false; |
1270 | if (!d->m_selectedPoints.isEmpty()) { |
1271 | // if point was inserted we need to move already selected points by 1 |
1272 | QSet<int> selectedAfterInsert; |
1273 | for (const auto &value : std::as_const(t&: d->m_selectedPoints)) { |
1274 | if (value >= index) { |
1275 | selectedAfterInsert << value + 1; |
1276 | callSignal = true; |
1277 | } else { |
1278 | selectedAfterInsert << value; |
1279 | } |
1280 | } |
1281 | d->m_selectedPoints = selectedAfterInsert; |
1282 | } |
1283 | |
1284 | emit pointAdded(index); |
1285 | if (callSignal) |
1286 | emit selectedPointsChanged(); |
1287 | } |
1288 | } |
1289 | |
1290 | /*! |
1291 | Removes all points from the series. |
1292 | \sa pointsRemoved() |
1293 | */ |
1294 | void QXYSeries::clear() |
1295 | { |
1296 | Q_D(QXYSeries); |
1297 | removePoints(index: 0, count: d->m_points.size()); |
1298 | } |
1299 | |
1300 | /*! |
1301 | Returns the points in the series. |
1302 | */ |
1303 | QList<QPointF> QXYSeries::points() const |
1304 | { |
1305 | Q_D(const QXYSeries); |
1306 | return d->m_points; |
1307 | } |
1308 | |
1309 | #if QT_DEPRECATED_SINCE(6, 0) |
1310 | /*! |
1311 | \deprecated |
1312 | |
1313 | Use points() instead. |
1314 | Returns the points in the series. |
1315 | |
1316 | */ |
1317 | QList<QPointF> QXYSeries::pointsVector() const |
1318 | { |
1319 | Q_D(const QXYSeries); |
1320 | return d->m_points; |
1321 | } |
1322 | #endif |
1323 | |
1324 | /*! |
1325 | Returns the data point at the position specified by \a index in the internal |
1326 | series of points. |
1327 | */ |
1328 | const QPointF &QXYSeries::at(int index) const |
1329 | { |
1330 | Q_D(const QXYSeries); |
1331 | return d->m_points.at(i: index); |
1332 | } |
1333 | |
1334 | /*! |
1335 | Returns the number of data points in a series. |
1336 | */ |
1337 | int QXYSeries::count() const |
1338 | { |
1339 | Q_D(const QXYSeries); |
1340 | return d->m_points.size(); |
1341 | } |
1342 | |
1343 | |
1344 | /*! |
1345 | Sets the pen used for drawing points on the chart to \a pen. If the pen is |
1346 | not defined, the pen from the chart theme is used. |
1347 | \sa QChart::setTheme() |
1348 | */ |
1349 | void QXYSeries::setPen(const QPen &pen) |
1350 | { |
1351 | Q_D(QXYSeries); |
1352 | if (d->m_pen != pen) { |
1353 | bool emitColorChanged = d->m_pen.color() != pen.color(); |
1354 | d->m_pen = pen; |
1355 | emit d->seriesUpdated(); |
1356 | if (emitColorChanged) |
1357 | emit colorChanged(color: pen.color()); |
1358 | emit penChanged(pen); |
1359 | } |
1360 | } |
1361 | |
1362 | QPen QXYSeries::pen() const |
1363 | { |
1364 | Q_D(const QXYSeries); |
1365 | if (d->m_pen == QChartPrivate::defaultPen()) |
1366 | return QPen(); |
1367 | else |
1368 | return d->m_pen; |
1369 | } |
1370 | |
1371 | /*! |
1372 | Sets the brush used for drawing points on the chart to \a brush. If the |
1373 | brush is not defined, the brush from the chart theme setting is used. |
1374 | \sa QChart::setTheme() |
1375 | */ |
1376 | void QXYSeries::setBrush(const QBrush &brush) |
1377 | { |
1378 | Q_D(QXYSeries); |
1379 | if (d->m_brush != brush) { |
1380 | d->m_brush = brush; |
1381 | emit d->seriesUpdated(); |
1382 | } |
1383 | } |
1384 | |
1385 | QBrush QXYSeries::brush() const |
1386 | { |
1387 | Q_D(const QXYSeries); |
1388 | if (d->m_brush == QChartPrivate::defaultBrush()) |
1389 | return QBrush(); |
1390 | else |
1391 | return d->m_brush; |
1392 | } |
1393 | |
1394 | void QXYSeries::setColor(const QColor &color) |
1395 | { |
1396 | QPen p = pen(); |
1397 | if (p.color() != color) { |
1398 | p.setColor(color); |
1399 | setPen(p); |
1400 | } |
1401 | } |
1402 | |
1403 | QColor QXYSeries::color() const |
1404 | { |
1405 | return pen().color(); |
1406 | } |
1407 | |
1408 | void QXYSeries::setSelectedColor(const QColor &color) |
1409 | { |
1410 | Q_D(QXYSeries); |
1411 | if (selectedColor() != color) { |
1412 | d->m_selectedColor = color; |
1413 | emit selectedColorChanged(color); |
1414 | } |
1415 | } |
1416 | |
1417 | QColor QXYSeries::selectedColor() const |
1418 | { |
1419 | Q_D(const QXYSeries); |
1420 | return d->m_selectedColor; |
1421 | } |
1422 | |
1423 | void QXYSeries::setPointsVisible(bool visible) |
1424 | { |
1425 | Q_D(QXYSeries); |
1426 | if (d->m_pointsVisible != visible) { |
1427 | d->m_pointsVisible = visible; |
1428 | emit d->seriesUpdated(); |
1429 | } |
1430 | } |
1431 | |
1432 | bool QXYSeries::pointsVisible() const |
1433 | { |
1434 | Q_D(const QXYSeries); |
1435 | return d->m_pointsVisible; |
1436 | } |
1437 | |
1438 | void QXYSeries::setPointLabelsFormat(const QString &format) |
1439 | { |
1440 | Q_D(QXYSeries); |
1441 | if (d->m_pointLabelsFormat != format) { |
1442 | d->m_pointLabelsFormat = format; |
1443 | emit pointLabelsFormatChanged(format); |
1444 | } |
1445 | } |
1446 | |
1447 | QString QXYSeries::pointLabelsFormat() const |
1448 | { |
1449 | Q_D(const QXYSeries); |
1450 | return d->m_pointLabelsFormat; |
1451 | } |
1452 | |
1453 | void QXYSeries::setPointLabelsVisible(bool visible) |
1454 | { |
1455 | Q_D(QXYSeries); |
1456 | if (d->m_pointLabelsVisible != visible) { |
1457 | d->m_pointLabelsVisible = visible; |
1458 | emit pointLabelsVisibilityChanged(visible); |
1459 | } |
1460 | } |
1461 | |
1462 | bool QXYSeries::pointLabelsVisible() const |
1463 | { |
1464 | Q_D(const QXYSeries); |
1465 | return d->m_pointLabelsVisible; |
1466 | } |
1467 | |
1468 | void QXYSeries::setPointLabelsFont(const QFont &font) |
1469 | { |
1470 | Q_D(QXYSeries); |
1471 | if (d->m_pointLabelsFont != font) { |
1472 | d->m_pointLabelsFont = font; |
1473 | emit pointLabelsFontChanged(font); |
1474 | } |
1475 | } |
1476 | |
1477 | QFont QXYSeries::pointLabelsFont() const |
1478 | { |
1479 | Q_D(const QXYSeries); |
1480 | return d->m_pointLabelsFont; |
1481 | } |
1482 | |
1483 | void QXYSeries::setPointLabelsColor(const QColor &color) |
1484 | { |
1485 | Q_D(QXYSeries); |
1486 | if (d->m_pointLabelsColor != color) { |
1487 | d->m_pointLabelsColor = color; |
1488 | emit pointLabelsColorChanged(color); |
1489 | } |
1490 | } |
1491 | |
1492 | QColor QXYSeries::pointLabelsColor() const |
1493 | { |
1494 | Q_D(const QXYSeries); |
1495 | if (d->m_pointLabelsColor == QChartPrivate::defaultPen().color()) |
1496 | return QPen().color(); |
1497 | else |
1498 | return d->m_pointLabelsColor; |
1499 | } |
1500 | |
1501 | void QXYSeries::setPointLabelsClipping(bool enabled) |
1502 | { |
1503 | Q_D(QXYSeries); |
1504 | if (d->m_pointLabelsClipping != enabled) { |
1505 | d->m_pointLabelsClipping = enabled; |
1506 | emit pointLabelsClippingChanged(clipping: enabled); |
1507 | } |
1508 | } |
1509 | |
1510 | bool QXYSeries::pointLabelsClipping() const |
1511 | { |
1512 | Q_D(const QXYSeries); |
1513 | return d->m_pointLabelsClipping; |
1514 | } |
1515 | |
1516 | /*! |
1517 | Sets the image used for drawing markers on each point of the series as |
1518 | the value of \a lightMarker. |
1519 | |
1520 | The default value is a default-QImage() (QImage::isNull() == true), meaning no light marker |
1521 | will be painted. |
1522 | You can reset back to default (disabled) by calling this function with a null QImage (QImage()). |
1523 | |
1524 | The light markers visualize the data points of this series and as such are an alternative |
1525 | to \c setPointsVisible(true). |
1526 | If a light marker is set with this method, visible points as set with \c setPointsVisible(true) |
1527 | are not displayed. |
1528 | |
1529 | Unlike the elements of \l {QScatterSeries}{QScatterSeries} the light markers |
1530 | are not represented by QGraphicsItem, but are just painted (no objects created). |
1531 | However, the mouse-event-signals of QXYSeries behave the same way, |
1532 | meaning that you'll get the exact domain value of the point if you click/press/hover |
1533 | the light marker. You'll still get the in between domain value if you click on the line. |
1534 | The light markers are above the line in terms of painting as well as events. |
1535 | |
1536 | \sa QXYSeries::lightMarker() |
1537 | \since 6.2 |
1538 | */ |
1539 | void QXYSeries::setLightMarker(const QImage &lightMarker) |
1540 | { |
1541 | Q_D(QXYSeries); |
1542 | if (d->m_lightMarker == lightMarker) |
1543 | return; |
1544 | |
1545 | d->m_lightMarker = lightMarker; |
1546 | emit d->seriesUpdated(); |
1547 | emit lightMarkerChanged(lightMarker: d->m_lightMarker); |
1548 | } |
1549 | |
1550 | /*! |
1551 | Gets the image used for drawing markers on each point of the series. |
1552 | |
1553 | The default value is QImage(), meaning no light marker will be painted. |
1554 | |
1555 | The light markers visualize the data points of this series and as such are an alternative |
1556 | to setPointsVisible(true). |
1557 | Both features can be enabled independently from each other. |
1558 | |
1559 | Unlike the elements of \l {QScatterSeries}{QScatterSeries} the light markers |
1560 | are not represented by QGraphicsItem, but are just painted (no objects created). |
1561 | However, the mouse-event-signals of QXYSeries behave the same way, |
1562 | meaning that you'll get the exact domain value of the point if you click/press/hover |
1563 | the light marker. You'll still get the in between domain value if you click on the line. |
1564 | The light markers are above the line in terms of painting as well as events. |
1565 | \sa QXYSeries::setLightMarker() |
1566 | \since 6.2 |
1567 | */ |
1568 | const QImage &QXYSeries::lightMarker() const |
1569 | { |
1570 | Q_D(const QXYSeries); |
1571 | return d->m_lightMarker; |
1572 | } |
1573 | |
1574 | /*! |
1575 | Sets the image used for drawing markers on selected series's points to \a selectedLightMarker. |
1576 | |
1577 | The default value is QImage(), meaning usual lightMarker() will be painted. |
1578 | |
1579 | This is an equivalent for \l{selectedColor} if you prefer light markers over |
1580 | normal points, but still want to distinguish selected points. |
1581 | |
1582 | \sa lightMarker(), selectedColor, setPointSelected() |
1583 | \since 6.2 |
1584 | */ |
1585 | void QXYSeries::setSelectedLightMarker(const QImage &selectedLightMarker) |
1586 | { |
1587 | Q_D(QXYSeries); |
1588 | if (d->m_selectedLightMarker == selectedLightMarker) |
1589 | return; |
1590 | |
1591 | d->m_selectedLightMarker = selectedLightMarker; |
1592 | emit d->seriesUpdated(); |
1593 | emit selectedLightMarkerChanged(selectedLightMarker: d->m_selectedLightMarker); |
1594 | } |
1595 | |
1596 | /*! |
1597 | Returns the image used for drawing markers on selected series' points. |
1598 | |
1599 | The default value is QImage(), meaning usual lightMarker() will be painted. |
1600 | |
1601 | This is equivalent to \l{selectedColor} if you prefer light markers over |
1602 | normal points, but still want to distinguish selected points. |
1603 | |
1604 | \sa lightMarker(), selectedColor, setPointSelected() |
1605 | \since 6.2 |
1606 | */ |
1607 | const QImage &QXYSeries::selectedLightMarker() const |
1608 | { |
1609 | Q_D(const QXYSeries); |
1610 | return d->m_selectedLightMarker; |
1611 | } |
1612 | |
1613 | /*! |
1614 | Sets the \a size of the marker used to render points in the series. |
1615 | |
1616 | The default size is 15.0. |
1617 | \sa QScatterSeries::markerSize |
1618 | \since 6.2 |
1619 | */ |
1620 | void QXYSeries::setMarkerSize(qreal size) |
1621 | { |
1622 | Q_D(QXYSeries); |
1623 | |
1624 | if (!qFuzzyCompare(p1: d->m_markerSize, p2: size)) { |
1625 | d->m_markerSizeDefault = false; |
1626 | d->setMarkerSize(size); |
1627 | emit d->seriesUpdated(); |
1628 | emit markerSizeChanged(size); |
1629 | } |
1630 | } |
1631 | |
1632 | /*! |
1633 | Gets the size of the marker used to render points in the series. |
1634 | |
1635 | The default size depends on the specific QXYSeries type. |
1636 | QScatterSeries has a default of 15.0 |
1637 | QLineSeries has a default of the series pen size * 1.5 |
1638 | \sa QScatterSeries::markerSize |
1639 | \since 6.2 |
1640 | */ |
1641 | qreal QXYSeries::markerSize() const |
1642 | { |
1643 | Q_D(const QXYSeries); |
1644 | return d->m_markerSize; |
1645 | } |
1646 | |
1647 | void QXYSeries::setBestFitLineVisible(bool visible) |
1648 | { |
1649 | Q_D(QXYSeries); |
1650 | if (d->m_bestFitLineVisible != visible) { |
1651 | d->m_bestFitLineVisible = visible; |
1652 | emit bestFitLineVisibilityChanged(visible); |
1653 | emit d->seriesUpdated(); |
1654 | } |
1655 | } |
1656 | |
1657 | bool QXYSeries::bestFitLineVisible() const |
1658 | { |
1659 | Q_D(const QXYSeries); |
1660 | return d->m_bestFitLineVisible; |
1661 | } |
1662 | |
1663 | /*! |
1664 | Returns a pair of numbers where the first number is a slope factor |
1665 | and the second number is intercept of a linear function for a best fit line. |
1666 | |
1667 | Those factors are calculated using Least Squares Method based |
1668 | on points passed to the series. |
1669 | |
1670 | Parameter \a ok is used to report a failure by setting its value to \c false |
1671 | and to report a success by setting its value to \c true. |
1672 | |
1673 | \sa QXYSeries::bestFitLineVisible() |
1674 | \since 6.2 |
1675 | */ |
1676 | QPair<qreal, qreal> QXYSeries::bestFitLineEquation(bool &ok) const |
1677 | { |
1678 | Q_D(const QXYSeries); |
1679 | return d->bestFitLineEquation(ok); |
1680 | } |
1681 | |
1682 | void QXYSeries::setBestFitLinePen(const QPen &pen) |
1683 | { |
1684 | Q_D(QXYSeries); |
1685 | if (d->m_bestFitLinePen != pen) { |
1686 | bool emitColorChanged = d->m_bestFitLinePen.color() != pen.color(); |
1687 | d->m_bestFitLinePen = pen; |
1688 | emit d->seriesUpdated(); |
1689 | if (emitColorChanged) |
1690 | bestFitLineColorChanged(color: pen.color()); |
1691 | emit bestFitLinePenChanged(pen); |
1692 | } |
1693 | } |
1694 | |
1695 | QPen QXYSeries::bestFitLinePen() const |
1696 | { |
1697 | Q_D(const QXYSeries); |
1698 | if (d->m_bestFitLinePen == QChartPrivate::defaultPen()) |
1699 | return QPen(); |
1700 | else |
1701 | return d->m_bestFitLinePen; |
1702 | } |
1703 | |
1704 | /*! |
1705 | Stream operator for adding the data point \a point to the series. |
1706 | \sa append() |
1707 | */ |
1708 | QXYSeries &QXYSeries::operator<< (const QPointF &point) |
1709 | { |
1710 | append(point); |
1711 | return *this; |
1712 | } |
1713 | |
1714 | /*! |
1715 | Stream operator for adding the list of data points specified by \a points |
1716 | to the series. |
1717 | \sa append() |
1718 | */ |
1719 | |
1720 | QXYSeries &QXYSeries::operator<< (const QList<QPointF>& points) |
1721 | { |
1722 | append(points); |
1723 | return *this; |
1724 | } |
1725 | |
1726 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
1727 | |
1728 | QXYSeriesPrivate::QXYSeriesPrivate(QXYSeries *q) |
1729 | : QAbstractSeriesPrivate(q), |
1730 | m_pen(QChartPrivate::defaultPen()), |
1731 | m_brush(QChartPrivate::defaultBrush()), |
1732 | m_pointsVisible(false), |
1733 | m_pointLabelsFormat(QLatin1String("@xPoint, @yPoint" )), |
1734 | m_pointLabelsVisible(false), |
1735 | m_pointLabelsFont(QChartPrivate::defaultFont()), |
1736 | m_pointLabelsColor(QChartPrivate::defaultPen().color()), |
1737 | m_pointLabelsClipping(true), |
1738 | m_bestFitLinePen(QChartPrivate::defaultPen()), |
1739 | m_bestFitLineVisible(false), |
1740 | m_markerSize(15.0) |
1741 | { |
1742 | } |
1743 | |
1744 | void QXYSeriesPrivate::initializeDomain() |
1745 | { |
1746 | qreal minX(0); |
1747 | qreal minY(0); |
1748 | qreal maxX(1); |
1749 | qreal maxY(1); |
1750 | |
1751 | Q_Q(QXYSeries); |
1752 | |
1753 | const QList<QPointF> &points = q->points(); |
1754 | |
1755 | if (!points.isEmpty()) { |
1756 | minX = points[0].x(); |
1757 | minY = points[0].y(); |
1758 | maxX = minX; |
1759 | maxY = minY; |
1760 | |
1761 | for (int i = 0; i < points.size(); i++) { |
1762 | qreal x = points[i].x(); |
1763 | qreal y = points[i].y(); |
1764 | minX = qMin(a: minX, b: x); |
1765 | minY = qMin(a: minY, b: y); |
1766 | maxX = qMax(a: maxX, b: x); |
1767 | maxY = qMax(a: maxY, b: y); |
1768 | } |
1769 | } |
1770 | |
1771 | domain()->setRange(minX, maxX, minY, maxY); |
1772 | } |
1773 | |
1774 | QList<QLegendMarker*> QXYSeriesPrivate::createLegendMarkers(QLegend* legend) |
1775 | { |
1776 | Q_Q(QXYSeries); |
1777 | QList<QLegendMarker*> list; |
1778 | return list << new QXYLegendMarker(q,legend); |
1779 | } |
1780 | |
1781 | void QXYSeriesPrivate::initializeAxes() |
1782 | { |
1783 | |
1784 | } |
1785 | |
1786 | QAbstractAxis::AxisType QXYSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const |
1787 | { |
1788 | Q_UNUSED(orientation); |
1789 | return QAbstractAxis::AxisTypeValue; |
1790 | } |
1791 | |
1792 | QAbstractAxis* QXYSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const |
1793 | { |
1794 | Q_UNUSED(orientation); |
1795 | return new QValueAxis; |
1796 | } |
1797 | |
1798 | void QXYSeriesPrivate::initializeAnimations(QChart::AnimationOptions options, |
1799 | int duration, QEasingCurve &curve) |
1800 | { |
1801 | XYChart *item = static_cast<XYChart *>(m_item.get()); |
1802 | Q_ASSERT(item); |
1803 | if (item->animation()) |
1804 | item->animation()->stopAndDestroyLater(); |
1805 | |
1806 | if (options.testFlag(flag: QChart::SeriesAnimations)) |
1807 | item->setAnimation(new XYAnimation(item, duration, curve)); |
1808 | else |
1809 | item->setAnimation(0); |
1810 | QAbstractSeriesPrivate::initializeAnimations(options, duration, curve); |
1811 | } |
1812 | |
1813 | void QXYSeriesPrivate::drawPointLabels(QPainter *painter, const QList<QPointF> &allPoints, |
1814 | const int offset) |
1815 | { |
1816 | if (m_pointLabelsVisible || !m_pointsConfiguration.isEmpty()) { |
1817 | if (m_pointLabelsClipping) |
1818 | painter->setClipping(true); |
1819 | else |
1820 | painter->setClipping(false); |
1821 | |
1822 | QList<int> pointsToSkip; |
1823 | QHash<int, QString> labelFormats; |
1824 | QHash<int, int> offsets; |
1825 | |
1826 | if (!m_pointsConfiguration.isEmpty()) { |
1827 | for (int i = 0; i < allPoints.size(); ++i) { |
1828 | bool drawLabel = m_pointLabelsVisible; |
1829 | if (m_pointsConfiguration.contains(key: i)) { |
1830 | const auto &conf = m_pointsConfiguration[i]; |
1831 | auto key = QXYSeries::PointConfiguration::LabelVisibility; |
1832 | if (conf.contains(key)) { |
1833 | drawLabel = m_pointsConfiguration[i][key].toBool(); |
1834 | key = QXYSeries::PointConfiguration::Size; |
1835 | if (drawLabel && conf.contains(key)) |
1836 | offsets[i] = conf[key].toReal(); |
1837 | } |
1838 | key = QXYSeries::PointConfiguration::LabelFormat; |
1839 | if (conf.contains(key) && !conf[key].toString().isEmpty()) |
1840 | labelFormats[i] = conf[key].toString(); |
1841 | } |
1842 | |
1843 | if (!drawLabel) |
1844 | pointsToSkip << i; |
1845 | } |
1846 | } |
1847 | |
1848 | drawSeriesPointLabels(painter, points: allPoints, offset, offsets, indexesToSkip: pointsToSkip, customLabels: labelFormats); |
1849 | } |
1850 | } |
1851 | |
1852 | void QXYSeriesPrivate::drawSeriesPointLabels(QPainter *painter, const QList<QPointF> &points, |
1853 | const int offset, const QHash<int, int> &offsets, |
1854 | const QList<int> &indexesToSkip, |
1855 | const QHash<int, QString> &labelFormats) |
1856 | { |
1857 | if (points.size() == 0) |
1858 | return; |
1859 | |
1860 | static const QString xPointTag(QLatin1String("@xPoint" )); |
1861 | static const QString yPointTag(QLatin1String("@yPoint" )); |
1862 | static const QString indexTag(QLatin1String("@index" )); |
1863 | |
1864 | QFont f(m_pointLabelsFont); |
1865 | f.setPixelSize(QFontInfo(m_pointLabelsFont).pixelSize()); |
1866 | painter->setFont(f); |
1867 | painter->setPen(QPen(m_pointLabelsColor)); |
1868 | QFontMetrics fm(painter->font()); |
1869 | // m_points is used for the label here as it has the series point information |
1870 | // points variable passed is used for positioning because it has the coordinates |
1871 | const int pointCount = qMin(a: points.size(), b: m_points.size()); |
1872 | for (int i(0); i < pointCount; i++) { |
1873 | if (indexesToSkip.contains(t: i)) |
1874 | continue; |
1875 | |
1876 | QString pointLabel = labelFormats.contains(key: i) ? labelFormats[i] : m_pointLabelsFormat; |
1877 | pointLabel.replace(before: xPointTag, after: presenter()->numberToString(value: m_points.at(i).x())); |
1878 | pointLabel.replace(before: yPointTag, after: presenter()->numberToString(value: m_points.at(i).y())); |
1879 | pointLabel.replace(before: indexTag, after: presenter()->numberToString(value: i)); |
1880 | |
1881 | int currOffset = offset; |
1882 | if (offsets.contains(key: i)) |
1883 | currOffset = offsets[i]; |
1884 | |
1885 | const int labelOffset = currOffset + 2; |
1886 | |
1887 | // Position text in relation to the point |
1888 | int pointLabelWidth = fm.horizontalAdvance(pointLabel); |
1889 | QPointF position(points.at(i)); |
1890 | position.setX(position.x() - pointLabelWidth / 2); |
1891 | position.setY(position.y() - labelOffset); |
1892 | |
1893 | painter->drawText(p: position, s: pointLabel); |
1894 | } |
1895 | } |
1896 | |
1897 | void QXYSeriesPrivate::drawBestFitLine(QPainter *painter, const QRectF &clipRect) |
1898 | { |
1899 | bool ok = false; |
1900 | const auto &bestFitLineParams = bestFitLineEquation(ok); |
1901 | |
1902 | if (!ok) |
1903 | return; |
1904 | |
1905 | auto *domain = this->domain(); |
1906 | const auto clipOriginX = domain->isReverseX() ? clipRect.right() : clipRect.left(); |
1907 | const auto clipOriginY = domain->isReverseY() ? clipRect.top() : clipRect.bottom(); |
1908 | const auto domainOrigin = domain->calculateDomainPoint(point: {clipOriginX, clipOriginY}); |
1909 | |
1910 | const qreal x1 = domainOrigin.x(); |
1911 | const qreal y1 = bestFitLineParams.first * x1 + bestFitLineParams.second; |
1912 | QPointF p1 = domain->calculateGeometryPoint(point: QPointF(x1, y1), ok); |
1913 | |
1914 | const qreal x2 = domainOrigin.x() + 1; |
1915 | const qreal y2 = bestFitLineParams.first * x2 + bestFitLineParams.second; |
1916 | QPointF p2 = domain->calculateGeometryPoint(point: QPointF(x2, y2), ok); |
1917 | |
1918 | if (ok) { |
1919 | QLineF bestFitLine { p1, p2 }; |
1920 | // maxLength is length of the diagonal, no line can be longer |
1921 | const qreal maxLength = qSqrt(v: qPow(x: clipRect.width(), y: 2) * qPow(x: clipRect.height(), y: 2)); |
1922 | bestFitLine.setLength(maxLength); |
1923 | |
1924 | painter->save(); |
1925 | painter->setPen(m_bestFitLinePen); |
1926 | painter->drawLine(l: bestFitLine); |
1927 | painter->restore(); |
1928 | } |
1929 | } |
1930 | |
1931 | void QXYSeries::setBestFitLineColor(const QColor &color) |
1932 | { |
1933 | QPen p = bestFitLinePen(); |
1934 | if (p.color() != color) { |
1935 | p.setColor(color); |
1936 | setBestFitLinePen(p); |
1937 | } |
1938 | } |
1939 | |
1940 | QColor QXYSeries::bestFitLineColor() const |
1941 | { |
1942 | return bestFitLinePen().color(); |
1943 | } |
1944 | |
1945 | QPair<qreal, qreal> QXYSeriesPrivate::bestFitLineEquation(bool &ok) const |
1946 | { |
1947 | if (m_points.size() <= 1) { |
1948 | ok = false; |
1949 | return { 0, 0 }; |
1950 | } |
1951 | |
1952 | ok = true; |
1953 | qreal xSum = 0.0, x2Sum = 0.0, ySum = 0.0, xySum = 0.0; |
1954 | for (const auto &point : m_points) { |
1955 | xSum += point.x(); |
1956 | ySum += point.y(); |
1957 | x2Sum += qPow(x: point.x(), y: 2); |
1958 | xySum += point.x() * point.y(); |
1959 | } |
1960 | |
1961 | const qreal divisor = m_points.size() * x2Sum - xSum * xSum; |
1962 | // To prevent crashes in unusual cases |
1963 | if (divisor == 0.0) { |
1964 | ok = false; |
1965 | return { 0, 0 }; |
1966 | } |
1967 | |
1968 | qreal a = (m_points.size() * xySum - xSum * ySum) / divisor; |
1969 | qreal b = (x2Sum * ySum - xSum * xySum) / divisor; |
1970 | |
1971 | return { a, b }; |
1972 | } |
1973 | |
1974 | void QXYSeriesPrivate::setPointSelected(int index, bool selected, bool &callSignal) |
1975 | { |
1976 | if (index < 0 || index > m_points.size() - 1) |
1977 | return; |
1978 | |
1979 | if (selected) { |
1980 | if (!isPointSelected(index)) { |
1981 | m_selectedPoints << index; |
1982 | callSignal = true; |
1983 | } |
1984 | } else { |
1985 | if (isPointSelected(index)) { |
1986 | m_selectedPoints.remove(value: index); |
1987 | callSignal = true; |
1988 | } |
1989 | } |
1990 | } |
1991 | |
1992 | bool QXYSeriesPrivate::isPointSelected(int index) |
1993 | { |
1994 | return m_selectedPoints.contains(value: index); |
1995 | } |
1996 | |
1997 | bool QXYSeriesPrivate::isMarkerSizeDefault() |
1998 | { |
1999 | return m_markerSizeDefault; |
2000 | } |
2001 | |
2002 | void QXYSeriesPrivate::setMarkerSize(qreal markerSize) |
2003 | { |
2004 | m_markerSize = markerSize; |
2005 | } |
2006 | |
2007 | QList<qreal> QXYSeriesPrivate::colorByData() const |
2008 | { |
2009 | return m_colorByData; |
2010 | } |
2011 | |
2012 | bool QXYSeriesPrivate::setPointConfiguration(const int index, |
2013 | const QXYSeries::PointConfiguration key, |
2014 | const QVariant &value) |
2015 | { |
2016 | QHash<QXYSeries::PointConfiguration, QVariant> conf; |
2017 | if (m_pointsConfiguration.contains(key: index)) |
2018 | conf = m_pointsConfiguration[index]; |
2019 | |
2020 | bool changed = false; |
2021 | if (conf.contains(key)) { |
2022 | if (conf[key] != value) |
2023 | changed = true; |
2024 | } else { |
2025 | changed = true; |
2026 | } |
2027 | |
2028 | conf[key] = value; |
2029 | m_pointsConfiguration[index] = conf; |
2030 | |
2031 | return changed; |
2032 | } |
2033 | |
2034 | QT_END_NAMESPACE |
2035 | |
2036 | #include "moc_qxyseries.cpp" |
2037 | #include "moc_qxyseries_p.cpp" |
2038 | |