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

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