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
18QT_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*/
589QXYSeries::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*/
598QXYSeries::~QXYSeries()
599{
600}
601
602/*!
603 Adds the data point with the coordinates \a x and \a y to the series.
604 */
605void 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 */
614void 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 */
628void 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*/
641void 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*/
651void 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*/
665void 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*/
675void 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*/
691void QXYSeries::replace(const QList<QPointF> &points)
692{
693 Q_D(QXYSeries);
694 const bool needUpdateSelection = d->m_points.size() > points.size();
695 d->m_points = points;
696 if (needUpdateSelection) {
697 d->m_selectedPoints.removeIf(predicate: [maxIdx = points.size() - 1](int idx) {
698 return idx > maxIdx;
699 });
700 }
701 emit pointsReplaced();
702 if (needUpdateSelection)
703 emit selectedPointsChanged();
704}
705
706/*!
707 Removes the configuration of a point located at \a index
708 and restores the default look derived from the series' settings.
709
710 \note It doesn't affect the configuration of other points.
711 \sa clearPointsConfiguration(), setPointConfiguration()
712 \since 6.2
713*/
714void QXYSeries::clearPointConfiguration(const int index)
715{
716 Q_D(QXYSeries);
717 if (d->m_pointsConfiguration.contains(key: index)) {
718 d->m_pointsConfiguration.remove(key: index);
719 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
720 }
721}
722
723/*!
724 Removes the configuration property identified by \a key from the point at \a index
725 and restores the default look derived from the series' settings.
726
727 Removes the configuration type, such as color or size,
728 specified by \a key from the point at \a index with configuration customizations,
729 allowing that configuration property to be rendered as the default
730 specified in the series' properties.
731
732 \note It doesn't affect the configuration of other points.
733 \sa clearPointsConfiguration(), setPointConfiguration()
734 \since 6.2
735*/
736void QXYSeries::clearPointConfiguration(const int index, const QXYSeries::PointConfiguration key)
737{
738 Q_D(QXYSeries);
739 if (d->m_pointsConfiguration.contains(key: index)) {
740 auto &conf = d->m_pointsConfiguration[index];
741 if (conf.contains(key)) {
742 conf.remove(key);
743 d->m_pointsConfiguration[index] = conf;
744 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
745 }
746 }
747}
748
749/*!
750 Removes the configuration of all points in the series and restores
751 the default look derived from the series' settings.
752
753 \sa setPointConfiguration()
754 \since 6.2
755*/
756void QXYSeries::clearPointsConfiguration()
757{
758 Q_D(QXYSeries);
759 d->m_pointsConfiguration.clear();
760 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
761}
762
763/*!
764 Removes the configuration property identified by \a key from all
765 points and restores the default look derived from the series' settings.
766
767 Removes the configuration type, such as color or size,
768 specified by \a key from all points with configuration customizations,
769 allowing that configuration property to be rendered as the default
770 specified in the series properties.
771
772 \sa clearPointsConfiguration(), setPointConfiguration()
773 \since 6.2
774*/
775void QXYSeries::clearPointsConfiguration(const QXYSeries::PointConfiguration key)
776{
777 Q_D(QXYSeries);
778 bool needsUpdate = false;
779 for (const int &index : d->m_pointsConfiguration.keys()) {
780 auto &conf = d->m_pointsConfiguration[index];
781 if (conf.contains(key)) {
782 conf.remove(key);
783 d->m_pointsConfiguration[index] = conf;
784 needsUpdate = true;
785 }
786 }
787
788 if (needsUpdate)
789 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
790}
791
792/*!
793 Enables customizing the appearance of a point located at \a index with
794 desired \a configuration.
795
796 With points configuration you can change various aspects of every point's appearance.
797
798 A point's configuration is represented as a hash map with QXYSeries::pointConfiguration
799 keys and QVariant values. For example:
800 \code
801 QLineSeries *series = new QLineSeries();
802 series->setName("Customized series");
803 series->setPointsVisible(true);
804
805 *series << QPointF(0, 6) << QPointF(2, 4) << QPointF(3, 6) << QPointF(7, 4) << QPointF(10, 5)
806 << QPointF(11, 1) << QPointF(13, 3) << QPointF(17, 6) << QPointF(18, 3)
807 << QPointF(20, 2);
808
809 QChart *chart = new QChart();
810 chart->addSeries(series);
811 chart->createDefaultAxes();
812
813 QHash<QXYSeries::PointConfiguration, QVariant> conf;
814 conf[QXYSeries::PointConfiguration::Color] = QColor(Qt::red);
815 conf[QXYSeries::PointConfiguration::Size] = 8;
816 conf[QXYSeries::PointConfiguration::LabelVisibility] = true;
817
818 series->setPointConfiguration(4, conf);
819
820 conf.remove(QXYSeries::PointConfiguration::Color);
821 conf[QXYSeries::PointConfiguration::LabelFormat] = "This Point";
822 series->setPointConfiguration(6, conf);
823 \endcode
824
825 In this example, you can see a default QLineSeries with 10 points and with changed configuration
826 of two points. Both changed points are visibly bigger than the others with a look derived from
827 the series configuration.
828 By default, points don't have labels, but the point at index 4 has a label thanks to the
829 QXYSeries::PointConfiguration::LabelVisibility and QXYSeries::PointConfiguration::LabelFormat
830 configuration values.
831 The point at index 6 has a custom label \e {This Point} thanks to the
832 QXYSeries::PointConfiguration::LabelFormat configuration value.
833 Below is an example of a chart created in this way:
834 \image xyseries_point_configuration.png
835
836 \sa pointsConfiguration(), clearPointsConfiguration()
837 \since 6.2
838*/
839void QXYSeries::setPointConfiguration(
840 const int index, const QHash<QXYSeries::PointConfiguration, QVariant> &configuration)
841{
842 Q_D(QXYSeries);
843 if (d->m_pointsConfiguration[index] != configuration) {
844 d->m_pointsConfiguration[index] = configuration;
845 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
846 }
847}
848
849/*!
850 Enables customizing a particular aspect of a point's configuration.
851
852 \note Points configuration concept provides a flexible way to configure various aspects
853 of a point's appearance. Thus, values need to have an elastic type such as QVariant.
854 See QXYSeries::PointConfiguration to see what \a value should be passed
855 for certain \a key.
856 \sa pointsConfiguration()
857 \since 6.2
858*/
859void QXYSeries::setPointConfiguration(const int index, const QXYSeries::PointConfiguration key,
860 const QVariant &value)
861{
862 Q_D(QXYSeries);
863 if (d->setPointConfiguration(index, key, value))
864 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
865}
866
867/*!
868 Enables customizing the configuration of multiple points as specified
869 by \a pointsConfiguration.
870
871 \sa pointsConfiguration()
872 \since 6.2
873*/
874void QXYSeries::setPointsConfiguration(
875 const QHash<int, QHash<QXYSeries::PointConfiguration, QVariant>> &pointsConfiguration)
876{
877 Q_D(QXYSeries);
878 if (d->m_pointsConfiguration != pointsConfiguration) {
879 d->m_pointsConfiguration = pointsConfiguration;
880 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
881 }
882}
883
884/*!
885 Returns a map representing the configuration of a point at \a index.
886
887 With points configuration you can change various aspects of each point's look.
888
889 \sa setPointConfiguration()
890 \since 6.2
891*/
892QHash<QXYSeries::PointConfiguration, QVariant> QXYSeries::pointConfiguration(const int index) const
893{
894 Q_D(const QXYSeries);
895 return d->m_pointsConfiguration[index];
896}
897
898/*!
899 Returns a map with points' indexes as keys and points' configuration as values.
900
901 \sa setPointConfiguration(), pointConfiguration()
902 \since 6.2
903*/
904QHash<int, QHash<QXYSeries::PointConfiguration, QVariant>> QXYSeries::pointsConfiguration() const
905{
906 Q_D(const QXYSeries);
907 return d->m_pointsConfiguration;
908}
909
910/*!
911 Sets the points' sizes according to a passed list of values. Values from
912 \a sourceData are sorted and mapped to a point size which is between \a minSize
913 and \a maxSize.
914
915 \note If \a sourceData length is smaller than number of points in the series, then
916 size of the points at the end of the series will stay the same.
917 \sa setPointConfiguration(), pointConfiguration()
918 \since 6.2
919*/
920void QXYSeries::sizeBy(const QList<qreal> &sourceData, const qreal minSize, const qreal maxSize)
921{
922 Q_D(QXYSeries);
923
924 Q_ASSERT(minSize <= maxSize);
925 Q_ASSERT(minSize >= 0);
926
927 qreal min = std::numeric_limits<qreal>::max();
928 qreal max = -std::numeric_limits<qreal>::max();
929 for (const auto &p : sourceData) {
930 min = qMin(a: min, b: p);
931 max = qMax(a: max, b: p);
932 }
933
934 const qreal range = max - min;
935 const qreal sizeRange = maxSize - minSize;
936 bool changed = false;
937
938 for (int i = 0; i < sourceData.size() && i < d->m_points.size(); ++i) {
939 qreal pointSize = minSize;
940 if (range != 0) {
941 const qreal startValue = sourceData.at(i) - min;
942 const qreal percentage = startValue / range;
943 pointSize = minSize + (percentage * sizeRange);
944 }
945 if (d->setPointConfiguration(index: i, key: QXYSeries::PointConfiguration::Size, value: pointSize))
946 changed = true;
947 }
948
949 if (changed)
950 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
951}
952
953/*!
954 Sets the points' color according to a passed list of values. Values from
955 \a sourceData are sorted and mapped to the \a gradient.
956
957 If the series has a QColorAxis attached, then a gradient from the axis
958 is going to be used.
959
960 \sa setPointConfiguration(), pointConfiguration(), QColorAxis
961 \since 6.2
962*/
963void QXYSeries::colorBy(const QList<qreal> &sourceData, const QLinearGradient &gradient)
964{
965 Q_D(QXYSeries);
966
967 d->m_colorByData = sourceData;
968 if (d->m_colorByData.isEmpty())
969 return;
970
971 const qreal imgSize = 100.0;
972
973 qreal min = std::numeric_limits<qreal>::max();
974 qreal max = std::numeric_limits<qreal>::min();
975 for (const auto &p : sourceData) {
976 min = qMin(a: min, b: p);
977 max = qMax(a: max, b: p);
978 }
979
980 qreal range = max - min;
981
982 QLinearGradient usedGradient = gradient;
983
984 // Gradient will be taked from the first attached color axis.
985 // If there are more color axis, they will have just changed range.
986 bool axisFound = false;
987 const auto axes = attachedAxes();
988 for (const auto &axis : axes) {
989 if (axis->type() == QAbstractAxis::AxisTypeColor) {
990 QColorAxis *colorAxis = static_cast<QColorAxis *>(axis);
991 if (!axisFound) {
992 usedGradient = QLinearGradient(QPointF(0,0), QPointF(0, imgSize));
993 const auto stops = colorAxis->gradient().stops();
994 for (const auto &stop : stops)
995 usedGradient.setColorAt(pos: stop.first, color: stop.second);
996
997 if (!colorAxis->autoRange()) {
998 min = colorAxis->min();
999 max = colorAxis->max();
1000 range = max - min;
1001 }
1002
1003 axisFound = true;
1004 }
1005
1006 if (colorAxis->autoRange())
1007 colorAxis->setRange(min, max);
1008 }
1009 }
1010
1011 QImage image(imgSize, imgSize, QImage::Format_ARGB32);
1012 QPainter painter(&image);
1013 painter.fillRect(image.rect(), usedGradient);
1014
1015 // To ensure that negative values will be well handled, distance from min to 0
1016 // will be added to min and every single value. This will move entire values
1017 // list to positive only values.
1018 const qreal diff = min < 0 ? qAbs(t: min) : 0;
1019 min += diff;
1020
1021 bool changed = false;
1022
1023 for (int i = 0; i < sourceData.size() && i < d->m_points.size(); ++i) {
1024 const qreal startValue = qMax(a: 0.0, b: sourceData.at(i) + diff - min);
1025 const qreal percentage = startValue / range;
1026 QColor color = image.pixelColor(x: 0, y: qMin(a: percentage * imgSize, b: imgSize - 1));
1027 if (d->setPointConfiguration(index: i, key: QXYSeries::PointConfiguration::Color, value: color))
1028 changed = true;
1029 }
1030
1031 if (changed)
1032 emit pointsConfigurationChanged(configuration: d->m_pointsConfiguration);
1033}
1034
1035/*!
1036 Returns true if point at given \a index is among selected points and false otherwise.
1037 \note Selected points are drawn using the selected color if it was specified.
1038 \sa selectedPoints(), setPointSelected(), setSelectedColor()
1039 \since 6.2
1040 */
1041bool QXYSeries::isPointSelected(int index)
1042{
1043 Q_D(QXYSeries);
1044 return d->isPointSelected(index);
1045}
1046
1047/*!
1048 Marks point at \a index as selected.
1049 \note Emits QXYSeries::selectedPointsChanged
1050 \sa setPointSelected()
1051 \since 6.2
1052 */
1053void QXYSeries::selectPoint(int index)
1054{
1055 setPointSelected(index, selected: true);
1056}
1057
1058/*!
1059 Deselects point at given \a index.
1060 \note Emits QXYSeries::selectedPointsChanged
1061 \sa setPointSelected()
1062 \since 6.2
1063 */
1064void QXYSeries::deselectPoint(int index)
1065{
1066 setPointSelected(index, selected: false);
1067}
1068
1069/*!
1070 Marks point at given \a index as either selected or deselected as specified by \a selected.
1071 \note Selected points are drawn using the selected color if it was specified. Emits QXYSeries::selectedPointsChanged
1072 \sa setPointSelected(), setSelectedColor()
1073 \since 6.2
1074 */
1075void QXYSeries::setPointSelected(int index, bool selected)
1076{
1077 Q_D(QXYSeries);
1078
1079 bool callSignal = false;
1080 d->setPointSelected(index, selected, callSignal);
1081
1082 if (callSignal)
1083 emit selectedPointsChanged();
1084}
1085
1086/*!
1087 Marks all points in the series as selected,
1088 \note Emits QXYSeries::selectedPointsChanged
1089 \sa setPointSelected()
1090 \since 6.2
1091 */
1092void QXYSeries::selectAllPoints()
1093{
1094 Q_D(QXYSeries);
1095
1096 bool callSignal = false;
1097 for (int i = 0; i < d->m_points.size(); ++i)
1098 d->setPointSelected(index: i, selected: true, callSignal);
1099
1100 if (callSignal)
1101 emit selectedPointsChanged();
1102}
1103
1104/*!
1105 Deselects all points in the series.
1106 \note Emits QXYSeries::selectedPointsChanged
1107 \sa setPointSelected()
1108 \since 6.2
1109 */
1110void QXYSeries::deselectAllPoints()
1111{
1112 Q_D(QXYSeries);
1113
1114 bool callSignal = false;
1115 for (int i = 0; i < d->m_points.size(); ++i)
1116 d->setPointSelected(index: i, selected: false, callSignal);
1117
1118 if (callSignal)
1119 emit selectedPointsChanged();
1120}
1121
1122/*!
1123 Marks multiple points passed in a \a indexes list as selected.
1124 \note Emits QXYSeries::selectedPointsChanged
1125 \sa setPointSelected()
1126 \since 6.2
1127 */
1128void QXYSeries::selectPoints(const QList<int> &indexes)
1129{
1130 Q_D(QXYSeries);
1131
1132 bool callSignal = false;
1133 for (const int &index : indexes)
1134 d->setPointSelected(index, selected: true, callSignal);
1135
1136 if (callSignal)
1137 emit selectedPointsChanged();
1138}
1139
1140/*!
1141 Marks multiple points passed in a \a indexes list as deselected.
1142 \note Emits QXYSeries::selectedPointsChanged
1143 \sa setPointSelected()
1144 \since 6.2
1145 */
1146void QXYSeries::deselectPoints(const QList<int> &indexes)
1147{
1148 Q_D(QXYSeries);
1149
1150 bool callSignal = false;
1151 for (const int &index : indexes)
1152 d->setPointSelected(index, selected: false, callSignal);
1153
1154 if (callSignal)
1155 emit selectedPointsChanged();
1156}
1157
1158/*!
1159 Changes selection state of points at given \a indexes to the opposite one. Makes
1160 \note Emits QXYSeries::selectedPointsChanged
1161 \sa setPointSelected()
1162 \since 6.2
1163 */
1164void QXYSeries::toggleSelection(const QList<int> &indexes)
1165{
1166 Q_D(QXYSeries);
1167
1168 bool callSignal = false;
1169 for (const int &index : indexes)
1170 d->setPointSelected(index, selected: !isPointSelected(index), callSignal);
1171
1172 if (callSignal)
1173 emit selectedPointsChanged();
1174}
1175
1176/*!
1177 Returns a list of points indexes marked as selected.
1178 Selected points are visible regardless of points visibility.
1179 \sa setPointSelected(), pointsVisible()
1180 \since 6.2
1181 */
1182QList<int> QXYSeries::selectedPoints() const
1183{
1184 Q_D(const QXYSeries);
1185 return QList<int>(d->m_selectedPoints.begin(), d->m_selectedPoints.end());
1186}
1187
1188/*!
1189 Removes the point that has the coordinates \a x and \a y from the series.
1190 \sa pointRemoved()
1191*/
1192void QXYSeries::remove(qreal x, qreal y)
1193{
1194 remove(point: QPointF(x, y));
1195}
1196
1197/*!
1198 Removes the data point \a point from the series.
1199 \sa pointRemoved()
1200*/
1201void QXYSeries::remove(const QPointF &point)
1202{
1203 Q_D(QXYSeries);
1204 int index = d->m_points.indexOf(t: point);
1205 if (index == -1)
1206 return;
1207 remove(index);
1208}
1209
1210/*!
1211 Removes the point at the position specified by \a index from the series.
1212 \sa pointRemoved()
1213*/
1214void QXYSeries::remove(int index)
1215{
1216 Q_D(QXYSeries);
1217 d->m_points.remove(i: index);
1218
1219 bool callSignal = false;
1220 d->setPointSelected(index, selected: false, callSignal);
1221
1222 emit pointRemoved(index);
1223 if (callSignal)
1224 emit selectedPointsChanged();
1225}
1226
1227/*!
1228 Removes the number of points specified by \a count from the series starting at
1229 the position specified by \a index.
1230 \sa pointsRemoved()
1231*/
1232void QXYSeries::removePoints(int index, int count)
1233{
1234 // This function doesn't overload remove as there is chance for it to get mixed up with
1235 // remove(qreal, qreal) overload in some implicit casting cases.
1236 Q_D(QXYSeries);
1237 if (count > 0) {
1238 d->m_points.remove(i: index, n: count);
1239
1240 bool callSignal = false;
1241 if (!d->m_selectedPoints.empty()) {
1242 QSet<int> selectedAfterRemoving;
1243
1244 for (const int &selectedPointIndex : std::as_const(t&: d->m_selectedPoints)) {
1245 if (selectedPointIndex < index) {
1246 selectedAfterRemoving << selectedPointIndex;
1247 } else if (selectedPointIndex >= index + count) {
1248 selectedAfterRemoving << selectedPointIndex - count;
1249 callSignal = true;
1250 } else {
1251 callSignal = true;
1252 }
1253 }
1254
1255 d->m_selectedPoints = selectedAfterRemoving;
1256 }
1257
1258 emit pointsRemoved(index, count);
1259 if (callSignal)
1260 emit selectedPointsChanged();
1261 }
1262}
1263
1264/*!
1265 Inserts the data point \a point in the series at the position specified by
1266 \a index.
1267 \sa pointAdded()
1268*/
1269void QXYSeries::insert(int index, const QPointF &point)
1270{
1271 Q_D(QXYSeries);
1272 if (isValidValue(point)) {
1273 index = qMax(a: 0, b: qMin(a: index, b: d->m_points.size()));
1274
1275 d->m_points.insert(i: index, t: point);
1276
1277 bool callSignal = false;
1278 if (!d->m_selectedPoints.isEmpty()) {
1279 // if point was inserted we need to move already selected points by 1
1280 QSet<int> selectedAfterInsert;
1281 for (const auto &value : std::as_const(t&: d->m_selectedPoints)) {
1282 if (value >= index) {
1283 selectedAfterInsert << value + 1;
1284 callSignal = true;
1285 } else {
1286 selectedAfterInsert << value;
1287 }
1288 }
1289 d->m_selectedPoints = selectedAfterInsert;
1290 }
1291
1292 emit pointAdded(index);
1293 if (callSignal)
1294 emit selectedPointsChanged();
1295 }
1296}
1297
1298/*!
1299 Removes all points from the series.
1300 \sa pointsRemoved()
1301*/
1302void QXYSeries::clear()
1303{
1304 Q_D(QXYSeries);
1305 removePoints(index: 0, count: d->m_points.size());
1306}
1307
1308/*!
1309 Returns the points in the series.
1310*/
1311QList<QPointF> QXYSeries::points() const
1312{
1313 Q_D(const QXYSeries);
1314 return d->m_points;
1315}
1316
1317#if QT_DEPRECATED_SINCE(6, 0)
1318/*!
1319 \deprecated
1320
1321 Use points() instead.
1322 Returns the points in the series.
1323
1324*/
1325QList<QPointF> QXYSeries::pointsVector() const
1326{
1327 Q_D(const QXYSeries);
1328 return d->m_points;
1329}
1330#endif
1331
1332/*!
1333 Returns the data point at the position specified by \a index in the internal
1334 series of points.
1335*/
1336const QPointF &QXYSeries::at(int index) const
1337{
1338 Q_D(const QXYSeries);
1339 return d->m_points.at(i: index);
1340}
1341
1342/*!
1343 Returns the number of data points in a series.
1344*/
1345int QXYSeries::count() const
1346{
1347 Q_D(const QXYSeries);
1348 return d->m_points.size();
1349}
1350
1351
1352/*!
1353 Sets the pen used for drawing points on the chart to \a pen. If the pen is
1354 not defined, the pen from the chart theme is used.
1355 \sa QChart::setTheme()
1356*/
1357void QXYSeries::setPen(const QPen &pen)
1358{
1359 Q_D(QXYSeries);
1360 if (d->m_pen != pen) {
1361 bool emitColorChanged = d->m_pen.color() != pen.color();
1362 d->m_pen = pen;
1363 emit d->seriesUpdated();
1364 if (emitColorChanged)
1365 emit colorChanged(color: pen.color());
1366 emit penChanged(pen);
1367 }
1368}
1369
1370QPen QXYSeries::pen() const
1371{
1372 Q_D(const QXYSeries);
1373 if (d->m_pen == QChartPrivate::defaultPen())
1374 return QPen();
1375 else
1376 return d->m_pen;
1377}
1378
1379/*!
1380 Sets the brush used for drawing points on the chart to \a brush. If the
1381 brush is not defined, the brush from the chart theme setting is used.
1382 \sa QChart::setTheme()
1383*/
1384void QXYSeries::setBrush(const QBrush &brush)
1385{
1386 Q_D(QXYSeries);
1387 if (d->m_brush != brush) {
1388 d->m_brush = brush;
1389 emit d->seriesUpdated();
1390 }
1391}
1392
1393QBrush QXYSeries::brush() const
1394{
1395 Q_D(const QXYSeries);
1396 if (d->m_brush == QChartPrivate::defaultBrush())
1397 return QBrush();
1398 else
1399 return d->m_brush;
1400}
1401
1402void QXYSeries::setColor(const QColor &color)
1403{
1404 QPen p = pen();
1405 if (p.color() != color) {
1406 p.setColor(color);
1407 setPen(p);
1408 }
1409}
1410
1411QColor QXYSeries::color() const
1412{
1413 return pen().color();
1414}
1415
1416void QXYSeries::setSelectedColor(const QColor &color)
1417{
1418 Q_D(QXYSeries);
1419 if (selectedColor() != color) {
1420 d->m_selectedColor = color;
1421 emit selectedColorChanged(color);
1422 }
1423}
1424
1425QColor QXYSeries::selectedColor() const
1426{
1427 Q_D(const QXYSeries);
1428 return d->m_selectedColor;
1429}
1430
1431void QXYSeries::setPointsVisible(bool visible)
1432{
1433 Q_D(QXYSeries);
1434 if (d->m_pointsVisible != visible) {
1435 d->m_pointsVisible = visible;
1436 emit d->seriesUpdated();
1437 }
1438}
1439
1440bool QXYSeries::pointsVisible() const
1441{
1442 Q_D(const QXYSeries);
1443 return d->m_pointsVisible;
1444}
1445
1446void QXYSeries::setPointLabelsFormat(const QString &format)
1447{
1448 Q_D(QXYSeries);
1449 if (d->m_pointLabelsFormat != format) {
1450 d->m_pointLabelsFormat = format;
1451 emit pointLabelsFormatChanged(format);
1452 }
1453}
1454
1455QString QXYSeries::pointLabelsFormat() const
1456{
1457 Q_D(const QXYSeries);
1458 return d->m_pointLabelsFormat;
1459}
1460
1461void QXYSeries::setPointLabelsVisible(bool visible)
1462{
1463 Q_D(QXYSeries);
1464 if (d->m_pointLabelsVisible != visible) {
1465 d->m_pointLabelsVisible = visible;
1466 emit pointLabelsVisibilityChanged(visible);
1467 }
1468}
1469
1470bool QXYSeries::pointLabelsVisible() const
1471{
1472 Q_D(const QXYSeries);
1473 return d->m_pointLabelsVisible;
1474}
1475
1476void QXYSeries::setPointLabelsFont(const QFont &font)
1477{
1478 Q_D(QXYSeries);
1479 if (d->m_pointLabelsFont != font) {
1480 d->m_pointLabelsFont = font;
1481 emit pointLabelsFontChanged(font);
1482 }
1483}
1484
1485QFont QXYSeries::pointLabelsFont() const
1486{
1487 Q_D(const QXYSeries);
1488 return d->m_pointLabelsFont;
1489}
1490
1491void QXYSeries::setPointLabelsColor(const QColor &color)
1492{
1493 Q_D(QXYSeries);
1494 if (d->m_pointLabelsColor != color) {
1495 d->m_pointLabelsColor = color;
1496 emit pointLabelsColorChanged(color);
1497 }
1498}
1499
1500QColor QXYSeries::pointLabelsColor() const
1501{
1502 Q_D(const QXYSeries);
1503 if (d->m_pointLabelsColor == QChartPrivate::defaultPen().color())
1504 return QPen().color();
1505 else
1506 return d->m_pointLabelsColor;
1507}
1508
1509void QXYSeries::setPointLabelsClipping(bool enabled)
1510{
1511 Q_D(QXYSeries);
1512 if (d->m_pointLabelsClipping != enabled) {
1513 d->m_pointLabelsClipping = enabled;
1514 emit pointLabelsClippingChanged(clipping: enabled);
1515 }
1516}
1517
1518bool QXYSeries::pointLabelsClipping() const
1519{
1520 Q_D(const QXYSeries);
1521 return d->m_pointLabelsClipping;
1522}
1523
1524/*!
1525 Sets the image used for drawing markers on each point of the series as
1526 the value of \a lightMarker.
1527
1528 The default value is a default-QImage() (QImage::isNull() == true), meaning no light marker
1529 will be painted.
1530 You can reset back to default (disabled) by calling this function with a null QImage (QImage()).
1531
1532 The light markers visualize the data points of this series and as such are an alternative
1533 to \c setPointsVisible(true).
1534 If a light marker is set with this method, visible points as set with \c setPointsVisible(true)
1535 are not displayed.
1536
1537 Unlike the elements of \l {QScatterSeries}{QScatterSeries} the light markers
1538 are not represented by QGraphicsItem, but are just painted (no objects created).
1539 However, the mouse-event-signals of QXYSeries behave the same way,
1540 meaning that you'll get the exact domain value of the point if you click/press/hover
1541 the light marker. You'll still get the in between domain value if you click on the line.
1542 The light markers are above the line in terms of painting as well as events.
1543
1544 \sa QXYSeries::lightMarker()
1545 \since 6.2
1546*/
1547void QXYSeries::setLightMarker(const QImage &lightMarker)
1548{
1549 Q_D(QXYSeries);
1550 if (d->m_lightMarker == lightMarker)
1551 return;
1552
1553 d->m_lightMarker = lightMarker;
1554 emit d->seriesUpdated();
1555 emit lightMarkerChanged(lightMarker: d->m_lightMarker);
1556}
1557
1558/*!
1559 Gets the image used for drawing markers on each point of the series.
1560
1561 The default value is QImage(), meaning no light marker will be painted.
1562
1563 The light markers visualize the data points of this series and as such are an alternative
1564 to setPointsVisible(true).
1565 Both features can be enabled independently from each other.
1566
1567 Unlike the elements of \l {QScatterSeries}{QScatterSeries} the light markers
1568 are not represented by QGraphicsItem, but are just painted (no objects created).
1569 However, the mouse-event-signals of QXYSeries behave the same way,
1570 meaning that you'll get the exact domain value of the point if you click/press/hover
1571 the light marker. You'll still get the in between domain value if you click on the line.
1572 The light markers are above the line in terms of painting as well as events.
1573 \sa QXYSeries::setLightMarker()
1574 \since 6.2
1575*/
1576const QImage &QXYSeries::lightMarker() const
1577{
1578 Q_D(const QXYSeries);
1579 return d->m_lightMarker;
1580}
1581
1582/*!
1583 Sets the image used for drawing markers on selected series's points to \a selectedLightMarker.
1584
1585 The default value is QImage(), meaning usual lightMarker() will be painted.
1586
1587 This is an equivalent for \l{selectedColor} if you prefer light markers over
1588 normal points, but still want to distinguish selected points.
1589
1590 \sa lightMarker(), selectedColor, setPointSelected()
1591 \since 6.2
1592*/
1593void QXYSeries::setSelectedLightMarker(const QImage &selectedLightMarker)
1594{
1595 Q_D(QXYSeries);
1596 if (d->m_selectedLightMarker == selectedLightMarker)
1597 return;
1598
1599 d->m_selectedLightMarker = selectedLightMarker;
1600 emit d->seriesUpdated();
1601 emit selectedLightMarkerChanged(selectedLightMarker: d->m_selectedLightMarker);
1602}
1603
1604/*!
1605 Returns the image used for drawing markers on selected series' points.
1606
1607 The default value is QImage(), meaning usual lightMarker() will be painted.
1608
1609 This is equivalent to \l{selectedColor} if you prefer light markers over
1610 normal points, but still want to distinguish selected points.
1611
1612 \sa lightMarker(), selectedColor, setPointSelected()
1613 \since 6.2
1614*/
1615const QImage &QXYSeries::selectedLightMarker() const
1616{
1617 Q_D(const QXYSeries);
1618 return d->m_selectedLightMarker;
1619}
1620
1621/*!
1622 Sets the \a size of the marker used to render points in the series.
1623
1624 The default size is 15.0.
1625 \sa QScatterSeries::markerSize
1626 \since 6.2
1627*/
1628void QXYSeries::setMarkerSize(qreal size)
1629{
1630 Q_D(QXYSeries);
1631
1632 if (!qFuzzyCompare(p1: d->m_markerSize, p2: size)) {
1633 d->m_markerSizeDefault = false;
1634 d->setMarkerSize(size);
1635 emit d->seriesUpdated();
1636 emit markerSizeChanged(size);
1637 }
1638}
1639
1640/*!
1641 Gets the size of the marker used to render points in the series.
1642
1643 The default size depends on the specific QXYSeries type.
1644 QScatterSeries has a default of 15.0
1645 QLineSeries has a default of the series pen size * 1.5
1646 \sa QScatterSeries::markerSize
1647 \since 6.2
1648*/
1649qreal QXYSeries::markerSize() const
1650{
1651 Q_D(const QXYSeries);
1652 return d->m_markerSize;
1653}
1654
1655void QXYSeries::setBestFitLineVisible(bool visible)
1656{
1657 Q_D(QXYSeries);
1658 if (d->m_bestFitLineVisible != visible) {
1659 d->m_bestFitLineVisible = visible;
1660 emit bestFitLineVisibilityChanged(visible);
1661 emit d->seriesUpdated();
1662 }
1663}
1664
1665bool QXYSeries::bestFitLineVisible() const
1666{
1667 Q_D(const QXYSeries);
1668 return d->m_bestFitLineVisible;
1669}
1670
1671/*!
1672 Returns a pair of numbers where the first number is a slope factor
1673 and the second number is intercept of a linear function for a best fit line.
1674
1675 Those factors are calculated using Least Squares Method based
1676 on points passed to the series.
1677
1678 Parameter \a ok is used to report a failure by setting its value to \c false
1679 and to report a success by setting its value to \c true.
1680
1681 \sa QXYSeries::bestFitLineVisible()
1682 \since 6.2
1683*/
1684QPair<qreal, qreal> QXYSeries::bestFitLineEquation(bool &ok) const
1685{
1686 Q_D(const QXYSeries);
1687 return d->bestFitLineEquation(ok);
1688}
1689
1690void QXYSeries::setBestFitLinePen(const QPen &pen)
1691{
1692 Q_D(QXYSeries);
1693 if (d->m_bestFitLinePen != pen) {
1694 bool emitColorChanged = d->m_bestFitLinePen.color() != pen.color();
1695 d->m_bestFitLinePen = pen;
1696 emit d->seriesUpdated();
1697 if (emitColorChanged)
1698 bestFitLineColorChanged(color: pen.color());
1699 emit bestFitLinePenChanged(pen);
1700 }
1701}
1702
1703QPen QXYSeries::bestFitLinePen() const
1704{
1705 Q_D(const QXYSeries);
1706 if (d->m_bestFitLinePen == QChartPrivate::defaultPen())
1707 return QPen();
1708 else
1709 return d->m_bestFitLinePen;
1710}
1711
1712/*!
1713 Stream operator for adding the data point \a point to the series.
1714 \sa append()
1715*/
1716QXYSeries &QXYSeries::operator<< (const QPointF &point)
1717{
1718 append(point);
1719 return *this;
1720}
1721
1722/*!
1723 Stream operator for adding the list of data points specified by \a points
1724 to the series.
1725 \sa append()
1726*/
1727
1728QXYSeries &QXYSeries::operator<< (const QList<QPointF>& points)
1729{
1730 append(points);
1731 return *this;
1732}
1733
1734//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1735
1736QXYSeriesPrivate::QXYSeriesPrivate(QXYSeries *q)
1737 : QAbstractSeriesPrivate(q),
1738 m_pen(QChartPrivate::defaultPen()),
1739 m_brush(QChartPrivate::defaultBrush()),
1740 m_pointsVisible(false),
1741 m_pointLabelsFormat(QLatin1String("@xPoint, @yPoint")),
1742 m_pointLabelsVisible(false),
1743 m_pointLabelsFont(QChartPrivate::defaultFont()),
1744 m_pointLabelsColor(QChartPrivate::defaultPen().color()),
1745 m_pointLabelsClipping(true),
1746 m_bestFitLinePen(QChartPrivate::defaultPen()),
1747 m_bestFitLineVisible(false),
1748 m_markerSize(15.0)
1749{
1750}
1751
1752void QXYSeriesPrivate::initializeDomain()
1753{
1754 qreal minX(0);
1755 qreal minY(0);
1756 qreal maxX(1);
1757 qreal maxY(1);
1758
1759 Q_Q(QXYSeries);
1760
1761 const QList<QPointF> &points = q->points();
1762
1763 if (!points.isEmpty()) {
1764 minX = points[0].x();
1765 minY = points[0].y();
1766 maxX = minX;
1767 maxY = minY;
1768
1769 for (int i = 0; i < points.size(); i++) {
1770 qreal x = points[i].x();
1771 qreal y = points[i].y();
1772 minX = qMin(a: minX, b: x);
1773 minY = qMin(a: minY, b: y);
1774 maxX = qMax(a: maxX, b: x);
1775 maxY = qMax(a: maxY, b: y);
1776 }
1777 }
1778
1779 domain()->setRange(minX, maxX, minY, maxY);
1780}
1781
1782QList<QLegendMarker*> QXYSeriesPrivate::createLegendMarkers(QLegend* legend)
1783{
1784 Q_Q(QXYSeries);
1785 QList<QLegendMarker*> list;
1786 return list << new QXYLegendMarker(q,legend);
1787}
1788
1789void QXYSeriesPrivate::initializeAxes()
1790{
1791
1792}
1793
1794QAbstractAxis::AxisType QXYSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const
1795{
1796 Q_UNUSED(orientation);
1797 return QAbstractAxis::AxisTypeValue;
1798}
1799
1800QAbstractAxis* QXYSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const
1801{
1802 Q_UNUSED(orientation);
1803 return new QValueAxis;
1804}
1805
1806void QXYSeriesPrivate::initializeAnimations(QChart::AnimationOptions options,
1807 int duration, QEasingCurve &curve)
1808{
1809 XYChart *item = static_cast<XYChart *>(m_item.get());
1810 Q_ASSERT(item);
1811 if (item->animation())
1812 item->animation()->stopAndDestroyLater();
1813
1814 if (options.testFlag(flag: QChart::SeriesAnimations))
1815 item->setAnimation(new XYAnimation(item, duration, curve));
1816 else
1817 item->setAnimation(0);
1818 QAbstractSeriesPrivate::initializeAnimations(options, duration, curve);
1819}
1820
1821void QXYSeriesPrivate::drawPointLabels(QPainter *painter, const QList<QPointF> &allPoints,
1822 const int offset)
1823{
1824 if (m_pointLabelsVisible || !m_pointsConfiguration.isEmpty()) {
1825 if (m_pointLabelsClipping)
1826 painter->setClipping(true);
1827 else
1828 painter->setClipping(false);
1829
1830 QList<int> pointsToSkip;
1831 QHash<int, QString> labelFormats;
1832 QHash<int, int> offsets;
1833
1834 if (!m_pointsConfiguration.isEmpty()) {
1835 for (int i = 0; i < allPoints.size(); ++i) {
1836 bool drawLabel = m_pointLabelsVisible;
1837 if (m_pointsConfiguration.contains(key: i)) {
1838 const auto &conf = m_pointsConfiguration[i];
1839 auto key = QXYSeries::PointConfiguration::LabelVisibility;
1840 if (conf.contains(key)) {
1841 drawLabel = m_pointsConfiguration[i][key].toBool();
1842 key = QXYSeries::PointConfiguration::Size;
1843 if (drawLabel && conf.contains(key))
1844 offsets[i] = conf[key].toReal();
1845 }
1846 key = QXYSeries::PointConfiguration::LabelFormat;
1847 if (conf.contains(key) && !conf[key].toString().isEmpty())
1848 labelFormats[i] = conf[key].toString();
1849 }
1850
1851 if (!drawLabel)
1852 pointsToSkip << i;
1853 }
1854 }
1855
1856 drawSeriesPointLabels(painter, points: allPoints, offset, offsets, indexesToSkip: pointsToSkip, customLabels: labelFormats);
1857 }
1858}
1859
1860void QXYSeriesPrivate::drawSeriesPointLabels(QPainter *painter, const QList<QPointF> &points,
1861 const int offset, const QHash<int, int> &offsets,
1862 const QList<int> &indexesToSkip,
1863 const QHash<int, QString> &labelFormats)
1864{
1865 if (points.size() == 0)
1866 return;
1867
1868 static const QString xPointTag(QLatin1String("@xPoint"));
1869 static const QString yPointTag(QLatin1String("@yPoint"));
1870 static const QString indexTag(QLatin1String("@index"));
1871
1872 QFont f(m_pointLabelsFont);
1873 f.setPixelSize(QFontInfo(m_pointLabelsFont).pixelSize());
1874 painter->setFont(f);
1875 painter->setPen(QPen(m_pointLabelsColor));
1876 QFontMetrics fm(painter->font());
1877 // m_points is used for the label here as it has the series point information
1878 // points variable passed is used for positioning because it has the coordinates
1879 const int pointCount = qMin(a: points.size(), b: m_points.size());
1880 for (int i(0); i < pointCount; i++) {
1881 if (indexesToSkip.contains(t: i))
1882 continue;
1883
1884 QString pointLabel = labelFormats.contains(key: i) ? labelFormats[i] : m_pointLabelsFormat;
1885 pointLabel.replace(before: xPointTag, after: presenter()->numberToString(value: m_points.at(i).x()));
1886 pointLabel.replace(before: yPointTag, after: presenter()->numberToString(value: m_points.at(i).y()));
1887 pointLabel.replace(before: indexTag, after: presenter()->numberToString(value: i));
1888
1889 int currOffset = offset;
1890 if (offsets.contains(key: i))
1891 currOffset = offsets[i];
1892
1893 const int labelOffset = currOffset + 2;
1894
1895 // Position text in relation to the point
1896 int pointLabelWidth = fm.horizontalAdvance(pointLabel);
1897 QPointF position(points.at(i));
1898 position.setX(position.x() - pointLabelWidth / 2);
1899 position.setY(position.y() - labelOffset);
1900
1901 painter->drawText(p: position, s: pointLabel);
1902 }
1903}
1904
1905void QXYSeriesPrivate::drawBestFitLine(QPainter *painter, const QRectF &clipRect)
1906{
1907 bool ok = false;
1908 const auto &bestFitLineParams = bestFitLineEquation(ok);
1909
1910 if (!ok)
1911 return;
1912
1913 auto *domain = this->domain();
1914 const auto clipOriginX = domain->isReverseX() ? clipRect.right() : clipRect.left();
1915 const auto clipOriginY = domain->isReverseY() ? clipRect.top() : clipRect.bottom();
1916 const auto domainOrigin = domain->calculateDomainPoint(point: {clipOriginX, clipOriginY});
1917
1918 const qreal x1 = domainOrigin.x();
1919 const qreal y1 = bestFitLineParams.first * x1 + bestFitLineParams.second;
1920 QPointF p1 = domain->calculateGeometryPoint(point: QPointF(x1, y1), ok);
1921
1922 const qreal x2 = domainOrigin.x() + 1;
1923 const qreal y2 = bestFitLineParams.first * x2 + bestFitLineParams.second;
1924 QPointF p2 = domain->calculateGeometryPoint(point: QPointF(x2, y2), ok);
1925
1926 if (ok) {
1927 QLineF bestFitLine { p1, p2 };
1928 // maxLength is length of the diagonal, no line can be longer
1929 const qreal maxLength = qSqrt(v: qPow(x: clipRect.width(), y: 2) * qPow(x: clipRect.height(), y: 2));
1930 bestFitLine.setLength(maxLength);
1931
1932 painter->save();
1933 painter->setPen(m_bestFitLinePen);
1934 painter->drawLine(l: bestFitLine);
1935 painter->restore();
1936 }
1937}
1938
1939void QXYSeries::setBestFitLineColor(const QColor &color)
1940{
1941 QPen p = bestFitLinePen();
1942 if (p.color() != color) {
1943 p.setColor(color);
1944 setBestFitLinePen(p);
1945 }
1946}
1947
1948QColor QXYSeries::bestFitLineColor() const
1949{
1950 return bestFitLinePen().color();
1951}
1952
1953QPair<qreal, qreal> QXYSeriesPrivate::bestFitLineEquation(bool &ok) const
1954{
1955 if (m_points.size() <= 1) {
1956 ok = false;
1957 return { 0, 0 };
1958 }
1959
1960 ok = true;
1961 qreal xSum = 0.0, x2Sum = 0.0, ySum = 0.0, xySum = 0.0;
1962 for (const auto &point : m_points) {
1963 xSum += point.x();
1964 ySum += point.y();
1965 x2Sum += qPow(x: point.x(), y: 2);
1966 xySum += point.x() * point.y();
1967 }
1968
1969 const qreal divisor = m_points.size() * x2Sum - xSum * xSum;
1970 // To prevent crashes in unusual cases
1971 if (divisor == 0.0) {
1972 ok = false;
1973 return { 0, 0 };
1974 }
1975
1976 qreal a = (m_points.size() * xySum - xSum * ySum) / divisor;
1977 qreal b = (x2Sum * ySum - xSum * xySum) / divisor;
1978
1979 return { a, b };
1980}
1981
1982void QXYSeriesPrivate::setPointSelected(int index, bool selected, bool &callSignal)
1983{
1984 if (index < 0 || index > m_points.size() - 1)
1985 return;
1986
1987 if (selected) {
1988 if (!isPointSelected(index)) {
1989 m_selectedPoints << index;
1990 callSignal = true;
1991 }
1992 } else {
1993 if (isPointSelected(index)) {
1994 m_selectedPoints.remove(value: index);
1995 callSignal = true;
1996 }
1997 }
1998}
1999
2000bool QXYSeriesPrivate::isPointSelected(int index)
2001{
2002 return m_selectedPoints.contains(value: index);
2003}
2004
2005bool QXYSeriesPrivate::isMarkerSizeDefault()
2006{
2007 return m_markerSizeDefault;
2008}
2009
2010void QXYSeriesPrivate::setMarkerSize(qreal markerSize)
2011{
2012 m_markerSize = markerSize;
2013}
2014
2015QList<qreal> QXYSeriesPrivate::colorByData() const
2016{
2017 return m_colorByData;
2018}
2019
2020bool QXYSeriesPrivate::setPointConfiguration(const int index,
2021 const QXYSeries::PointConfiguration key,
2022 const QVariant &value)
2023{
2024 QHash<QXYSeries::PointConfiguration, QVariant> conf;
2025 if (m_pointsConfiguration.contains(key: index))
2026 conf = m_pointsConfiguration[index];
2027
2028 bool changed = false;
2029 if (conf.contains(key)) {
2030 if (conf[key] != value)
2031 changed = true;
2032 } else {
2033 changed = true;
2034 }
2035
2036 conf[key] = value;
2037 m_pointsConfiguration[index] = conf;
2038
2039 return changed;
2040}
2041
2042QT_END_NAMESPACE
2043
2044#include "moc_qxyseries.cpp"
2045#include "moc_qxyseries_p.cpp"
2046

source code of qtcharts/src/charts/xychart/qxyseries.cpp