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

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