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