1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtGraphs/qlineseries.h>
5#include <QtGraphs/qscatterseries.h>
6#include <QtGraphs/qsplineseries.h>
7#include <private/pointrenderer_p.h>
8#include <private/axisrenderer_p.h>
9#include <private/qabstractseries_p.h>
10#include <private/qgraphsview_p.h>
11#include <private/qxyseries_p.h>
12
13QT_BEGIN_NAMESPACE
14
15static const char *TAG_POINT_COLOR = "pointColor";
16static const char *TAG_POINT_BORDER_COLOR = "pointBorderColor";
17static const char *TAG_POINT_BORDER_WIDTH = "pointBorderWidth";
18static const char *TAG_POINT_SELECTED_COLOR = "pointSelectedColor";
19static const char *TAG_POINT_SELECTED = "pointSelected";
20static const char *TAG_POINT_VALUE_X = "pointValueX";
21static const char *TAG_POINT_VALUE_Y = "pointValueY";
22
23PointRenderer::PointRenderer(QGraphsView *graph)
24 : QQuickItem(graph)
25 , m_graph(graph)
26{
27 setFlag(flag: QQuickItem::ItemHasContents);
28 setClip(true);
29 m_shape.setParentItem(this);
30 m_shape.setPreferredRendererType(QQuickShape::CurveRenderer);
31
32 const QString qmlData = QLatin1StringView(R"QML(
33 import QtQuick;
34
35 Rectangle {
36 property bool pointSelected
37 property color pointColor
38 property color pointBorderColor
39 property color pointSelectedColor
40 property real pointBorderWidth
41 color: pointSelected ? pointSelectedColor : pointColor
42 border.color: pointBorderColor
43 border.width: pointBorderWidth
44 width: %1
45 height: %1
46 }
47 )QML").arg(args: QString::number((int) defaultSize()));
48 m_tempMarker = new QQmlComponent(qmlEngine(m_graph), this);
49 m_tempMarker->setData(qmlData.toUtf8(), baseUrl: QUrl());
50}
51
52PointRenderer::~PointRenderer()
53{
54 qDeleteAll(c: m_groups);
55}
56
57qreal PointRenderer::defaultSize(QXYSeries *series)
58{
59 qreal size = 16.0;
60 if (series != nullptr) {
61 if (auto line = qobject_cast<QLineSeries *>(object: series))
62 size = qMax(a: size, b: line->width());
63 else if (auto spline = qobject_cast<QSplineSeries *>(object: series))
64 size = qMax(a: size, b: spline->width());
65 }
66 return size;
67}
68
69void PointRenderer::calculateRenderCoordinates(
70 AxisRenderer *axisRenderer, qreal origX, qreal origY, qreal *renderX, qreal *renderY)
71{
72 auto flipX = axisRenderer->m_axisHorizontalMaxValue < axisRenderer->m_axisHorizontalMinValue
73 ? -1
74 : 1;
75 auto flipY = axisRenderer->m_axisVerticalMaxValue < axisRenderer->m_axisVerticalMinValue ? -1
76 : 1;
77
78 *renderX = m_areaWidth * flipX * origX * m_maxHorizontal - m_horizontalOffset;
79 *renderY = m_areaHeight - m_areaHeight * flipY * origY * m_maxVertical
80 + m_verticalOffset;
81}
82
83void PointRenderer::reverseRenderCoordinates(
84 AxisRenderer *axisRenderer, qreal renderX, qreal renderY, qreal *origX, qreal *origY)
85{
86 auto flipX = axisRenderer->m_axisHorizontalMaxValue < axisRenderer->m_axisHorizontalMinValue
87 ? -1
88 : 1;
89 auto flipY = axisRenderer->m_axisVerticalMaxValue < axisRenderer->m_axisVerticalMinValue ? -1
90 : 1;
91
92 *origX = (renderX + m_horizontalOffset) / (m_areaWidth * flipX * m_maxHorizontal);
93 *origY = (renderY - m_areaHeight - m_verticalOffset)
94 / (-1 * m_areaHeight * flipY * m_maxVertical);
95}
96
97PointRenderer::SeriesStyle PointRenderer::getSeriesStyle(PointGroup *group)
98{
99 auto theme = m_graph->theme();
100
101 const auto &seriesColors = theme->seriesColors();
102 const auto &borderColors = theme->borderColors();
103
104 qsizetype index = group->colorIndex % seriesColors.size();
105 QColor color = group->series->color().alpha() != 0 ? group->series->color() : seriesColors.at(i: index);
106
107 QColor selectedColor = group->series->selectedColor().alpha() != 0
108 ? group->series->selectedColor()
109 : m_graph->theme()->singleHighlightColor();
110
111 index = group->colorIndex % borderColors.size();
112 QColor borderColor = borderColors.at(i: index);
113 qreal borderWidth = theme->borderWidth();
114
115 return {
116 .color: color,
117 .selectedColor: selectedColor,
118 .borderColor: borderColor,
119 .borderWidth: borderWidth
120 };
121}
122
123void PointRenderer::updatePointDelegate(
124 QXYSeries *series, PointGroup *group, qsizetype pointIndex, qreal x, qreal y)
125{
126 const auto style = getSeriesStyle(group);
127
128 auto marker = group->markers[pointIndex];
129 auto &rect = group->rects[pointIndex];
130
131 if (marker->property(name: TAG_POINT_SELECTED).isValid())
132 marker->setProperty(name: TAG_POINT_SELECTED, value: series->isPointSelected(index: pointIndex));
133 if (marker->property(name: TAG_POINT_COLOR).isValid())
134 marker->setProperty(name: TAG_POINT_COLOR, value: style.color);
135 if (marker->property(name: TAG_POINT_BORDER_COLOR).isValid())
136 marker->setProperty(name: TAG_POINT_BORDER_COLOR, value: style.borderColor);
137 if (marker->property(name: TAG_POINT_BORDER_WIDTH).isValid())
138 marker->setProperty(name: TAG_POINT_BORDER_WIDTH, value: style.borderWidth);
139 if (marker->property(name: TAG_POINT_SELECTED_COLOR).isValid())
140 marker->setProperty(name: TAG_POINT_SELECTED_COLOR, value: style.selectedColor);
141 const auto point = series->points().at(i: pointIndex);
142 if (marker->property(name: TAG_POINT_VALUE_X).isValid())
143 marker->setProperty(name: TAG_POINT_VALUE_X, value: point.x());
144 if (marker->property(name: TAG_POINT_VALUE_Y).isValid())
145 marker->setProperty(name: TAG_POINT_VALUE_Y, value: point.y());
146
147 marker->setX(x - marker->width() / 2.0);
148 marker->setY(y - marker->height() / 2.0);
149 marker->setVisible(true);
150
151 rect = QRectF(x - marker->width() / 2.0,
152 y - marker->height() / 2.0,
153 marker->width(),
154 marker->height());
155}
156
157void PointRenderer::hidePointDelegates(QXYSeries *series)
158{
159 auto *group = m_groups.value(key: series);
160 if (group->currentMarker) {
161 for (int i = 0; i < group->markers.size(); ++i) {
162 auto *marker = group->markers[i];
163 marker->setVisible(false);
164 }
165 }
166 group->rects.clear();
167}
168
169void PointRenderer::updateLegendData(QXYSeries *series, QLegendData &legendData)
170{
171 QList<QLegendData> legendDataList = {legendData};
172 series->d_func()->setLegendData(legendDataList);
173}
174
175void PointRenderer::updateScatterSeries(QScatterSeries *series, QLegendData &legendData)
176{
177 auto group = m_groups.value(key: series);
178 const auto style = getSeriesStyle(group);
179
180 if (series->isVisible()) {
181 auto &&points = series->points();
182 group->rects.resize(size: points.size());
183 for (int i = 0; i < points.size(); ++i) {
184 qreal x, y;
185 calculateRenderCoordinates(axisRenderer: m_graph->m_axisRenderer, origX: points[i].x(), origY: points[i].y(), renderX: &x, renderY: &y);
186 y *= series->valuesMultiplier();
187 if (group->currentMarker) {
188 updatePointDelegate(series, group, pointIndex: i, x, y);
189 } else {
190 auto &rect = group->rects[i];
191 qreal size = defaultSize(series);
192 rect = QRectF(x - size / 2.0, y - size / 2.0, size, size);
193 }
194 }
195 } else {
196 hidePointDelegates(series);
197 }
198
199 legendData = { .color: style.color, .borderColor: style.borderColor, .label: series->name() };
200}
201
202void PointRenderer::updateLineSeries(QLineSeries *series, QLegendData &legendData)
203{
204 auto group = m_groups.value(key: series);
205 const auto style = getSeriesStyle(group);
206
207 group->shapePath->setStrokeColor(style.color);
208 group->shapePath->setStrokeWidth(series->width());
209 group->shapePath->setFillColor(QColorConstants::Transparent);
210
211 Qt::PenCapStyle capStyle = series->capStyle();
212 if (capStyle == Qt::PenCapStyle::SquareCap)
213 group->shapePath->setCapStyle(QQuickShapePath::CapStyle::SquareCap);
214 else if (capStyle == Qt::PenCapStyle::FlatCap)
215 group->shapePath->setCapStyle(QQuickShapePath::CapStyle::FlatCap);
216 else if (capStyle == Qt::PenCapStyle::RoundCap)
217 group->shapePath->setCapStyle(QQuickShapePath::CapStyle::RoundCap);
218
219 auto &painterPath = group->painterPath;
220 painterPath.clear();
221
222 if (series->isVisible()) {
223 auto &&points = series->points();
224 group->rects.resize(size: points.size());
225 for (int i = 0; i < points.size(); ++i) {
226 qreal x, y;
227 calculateRenderCoordinates(axisRenderer: m_graph->m_axisRenderer, origX: points[i].x(), origY: points[i].y(), renderX: &x, renderY: &y);
228 y *= series->valuesMultiplier();
229 if (i == 0)
230 painterPath.moveTo(x, y);
231 else
232 painterPath.lineTo(x, y);
233
234 if (group->currentMarker) {
235 updatePointDelegate(series, group, pointIndex: i, x, y);
236 } else {
237 auto &rect = group->rects[i];
238 qreal size = defaultSize(series);
239 rect = QRectF(x - size / 2.0, y - size / 2.0, size, size);
240 }
241 }
242 } else {
243 hidePointDelegates(series);
244 }
245 group->shapePath->setPath(painterPath);
246 legendData = { .color: style.color, .borderColor: style.borderColor, .label: series->name() };
247}
248
249void PointRenderer::updateSplineSeries(QSplineSeries *series, QLegendData &legendData)
250{
251 auto group = m_groups.value(key: series);
252 const auto style = getSeriesStyle(group);
253
254 group->shapePath->setStrokeColor(style.color);
255 group->shapePath->setStrokeWidth(series->width());
256 group->shapePath->setFillColor(QColorConstants::Transparent);
257
258 Qt::PenCapStyle capStyle = series->capStyle();
259 if (capStyle == Qt::PenCapStyle::SquareCap)
260 group->shapePath->setCapStyle(QQuickShapePath::CapStyle::SquareCap);
261 else if (capStyle == Qt::PenCapStyle::FlatCap)
262 group->shapePath->setCapStyle(QQuickShapePath::CapStyle::FlatCap);
263 else if (capStyle == Qt::PenCapStyle::RoundCap)
264 group->shapePath->setCapStyle(QQuickShapePath::CapStyle::RoundCap);
265
266 auto &painterPath = group->painterPath;
267 painterPath.clear();
268
269 if (series->isVisible()) {
270 auto &&points = series->points();
271 group->rects.resize(size: points.size());
272 auto fittedPoints = series->getControlPoints();
273
274 for (int i = 0, j = 0; i < points.size(); ++i, ++j) {
275 qreal x, y;
276 calculateRenderCoordinates(axisRenderer: m_graph->m_axisRenderer, origX: points[i].x(), origY: points[i].y(), renderX: &x, renderY: &y);
277
278 qreal valuesMultiplier = series->valuesMultiplier();
279 y *= valuesMultiplier;
280 if (i == 0) {
281 painterPath.moveTo(x, y);
282 } else {
283 qreal x1, y1, x2, y2;
284 calculateRenderCoordinates(axisRenderer: m_graph->m_axisRenderer,
285 origX: fittedPoints[j - 1].x(),
286 origY: fittedPoints[j - 1].y(),
287 renderX: &x1,
288 renderY: &y1);
289 calculateRenderCoordinates(axisRenderer: m_graph->m_axisRenderer,
290 origX: fittedPoints[j].x(),
291 origY: fittedPoints[j].y(),
292 renderX: &x2,
293 renderY: &y2);
294
295 y1 *= valuesMultiplier;
296 y2 *= valuesMultiplier;
297 painterPath.cubicTo(ctrlPt1x: x1, ctrlPt1y: y1, ctrlPt2x: x2, ctrlPt2y: y2, endPtx: x, endPty: y);
298 ++j;
299 }
300
301 if (group->currentMarker) {
302 updatePointDelegate(series, group, pointIndex: i, x, y);
303 } else {
304 auto &rect = group->rects[i];
305 qreal size = defaultSize(series);
306 rect = QRectF(x - size / 2.0, y - size / 2.0, size, size);
307 }
308 }
309 } else {
310 hidePointDelegates(series);
311 }
312
313 group->shapePath->setPath(painterPath);
314 legendData = { .color: style.color, .borderColor: style.borderColor, .label: series->name() };
315}
316
317void PointRenderer::handlePolish(QXYSeries *series)
318{
319 auto theme = m_graph->theme();
320 if (!theme)
321 return;
322
323 if (!m_graph->m_axisRenderer)
324 return;
325
326 if (series->points().isEmpty()) {
327 auto group = m_groups.value(key: series);
328
329 if (group) {
330 if (group->shapePath) {
331 auto &painterPath = group->painterPath;
332 painterPath.clear();
333 group->shapePath->setPath(painterPath);
334 }
335
336 for (auto m : group->markers)
337 m->deleteLater();
338
339 group->markers.clear();
340 }
341
342 return;
343 }
344
345 if (width() <= 0 || height() <= 0)
346 return;
347
348 m_areaWidth = width();
349 m_areaHeight = height();
350
351 m_maxVertical = m_graph->m_axisRenderer->m_axisVerticalValueRange > 0
352 ? 1.0 / m_graph->m_axisRenderer->m_axisVerticalValueRange
353 : 100.0;
354 m_maxHorizontal = m_graph->m_axisRenderer->m_axisHorizontalValueRange > 0
355 ? 1.0 / m_graph->m_axisRenderer->m_axisHorizontalValueRange
356 : 100.0;
357
358 auto vmin = m_graph->m_axisRenderer->m_axisVerticalMinValue
359 > m_graph->m_axisRenderer->m_axisVerticalMaxValue
360 ? std::abs(x: m_graph->m_axisRenderer->m_axisVerticalMinValue)
361 : m_graph->m_axisRenderer->m_axisVerticalMinValue;
362
363 m_verticalOffset = (vmin / m_graph->m_axisRenderer->m_axisVerticalValueRange) * m_areaHeight;
364
365 auto hmin = m_graph->m_axisRenderer->m_axisHorizontalMinValue
366 > m_graph->m_axisRenderer->m_axisHorizontalMaxValue
367 ? std::abs(x: m_graph->m_axisRenderer->m_axisHorizontalMinValue)
368 : m_graph->m_axisRenderer->m_axisHorizontalMinValue;
369
370 m_horizontalOffset = (hmin / m_graph->m_axisRenderer->m_axisHorizontalValueRange) * m_areaWidth;
371
372 if (!m_groups.contains(key: series)) {
373 PointGroup *group = new PointGroup();
374 group->series = series;
375 m_groups.insert(key: series, value: group);
376
377 if (series->type() != QAbstractSeries::SeriesType::Scatter) {
378 group->shapePath = new QQuickShapePath(&m_shape);
379 group->shapePath->setAsynchronous(true);
380 auto data = m_shape.data();
381 data.append(&data, m_groups.value(key: series)->shapePath);
382 }
383 }
384
385 auto group = m_groups.value(key: series);
386
387 qsizetype pointCount = series->points().size();
388
389 if ((series->type() == QAbstractSeries::SeriesType::Scatter) && !series->pointDelegate())
390 group->currentMarker = m_tempMarker;
391 else if (series->pointDelegate())
392 group->currentMarker = series->pointDelegate();
393
394 if (group->currentMarker != group->previousMarker) {
395 for (auto &&marker : group->markers)
396 marker->deleteLater();
397 group->markers.clear();
398 }
399 group->previousMarker = group->currentMarker;
400
401 if (group->currentMarker) {
402 qsizetype markerCount = group->markers.size();
403 if (markerCount < pointCount) {
404 for (qsizetype i = markerCount; i < pointCount; ++i) {
405 QQuickItem *item = qobject_cast<QQuickItem *>(
406 o: group->currentMarker->create(context: group->currentMarker->creationContext()));
407 item->setParent(this);
408 item->setParentItem(this);
409 group->markers << item;
410 }
411 } else if (markerCount > pointCount) {
412 for (qsizetype i = pointCount; i < markerCount; ++i)
413 group->markers[i]->deleteLater();
414 group->markers.resize(size: pointCount);
415 }
416 } else if (group->markers.size() > 0) {
417 for (auto &&marker : group->markers)
418 marker->deleteLater();
419 group->markers.clear();
420 }
421
422 if (group->colorIndex < 0) {
423 group->colorIndex = m_graph->graphSeriesCount();
424 m_graph->setGraphSeriesCount(group->colorIndex + 1);
425 }
426
427 QLegendData legendData;
428 if (auto scatter = qobject_cast<QScatterSeries *>(object: series))
429 updateScatterSeries(series: scatter, legendData);
430 else if (auto line = qobject_cast<QLineSeries *>(object: series))
431 updateLineSeries(series: line, legendData);
432 else if (auto spline = qobject_cast<QSplineSeries *>(object: series))
433 updateSplineSeries(series: spline, legendData);
434
435 updateLegendData(series, legendData);
436}
437
438void PointRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
439{
440 for (auto series : cleanupSeries) {
441 auto xySeries = qobject_cast<QXYSeries *>(object: series);
442 if (xySeries && m_groups.contains(key: xySeries)) {
443 auto group = m_groups.value(key: xySeries);
444
445 for (auto marker : group->markers)
446 marker->deleteLater();
447
448 if (group->shapePath) {
449 auto painterPath = group->painterPath;
450 painterPath.clear();
451 group->shapePath->setPath(painterPath);
452 }
453
454 delete group;
455 m_groups.remove(key: xySeries);
456 }
457 }
458}
459
460void PointRenderer::updateSeries(QXYSeries *series)
461{
462 Q_UNUSED(series);
463}
464
465void PointRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
466{
467 Q_UNUSED(cleanupSeries);
468}
469
470bool PointRenderer::handleMouseMove(QMouseEvent *event)
471{
472 if (!m_pressedGroup || !m_pressedGroup->series->isVisible())
473 return false;
474
475 if (m_pointPressed && m_pressedGroup->series->isDraggable()) {
476 float w = width();
477 float h = height();
478 double maxVertical = m_graph->m_axisRenderer->m_axisVerticalValueRange > 0
479 ? 1.0 / m_graph->m_axisRenderer->m_axisVerticalValueRange
480 : 100.0;
481 double maxHorizontal = m_graph->m_axisRenderer->m_axisHorizontalValueRange > 0
482 ? 1.0 / m_graph->m_axisRenderer->m_axisHorizontalValueRange
483 : 100.0;
484
485 QPoint delta = m_pressStart - event->pos();
486
487 qreal deltaX = -delta.x() / w / maxHorizontal;
488 qreal deltaY = delta.y() / h / maxVertical;
489
490 QPointF point = m_pressedGroup->series->at(index: m_pressedPointIndex) + QPointF(deltaX, deltaY);
491 m_pressedGroup->series->replace(index: m_pressedPointIndex, newPoint: point);
492
493 m_pressStart = event->pos();
494 m_pointDragging = true;
495
496 return true;
497 }
498 return false;
499}
500
501bool PointRenderer::handleMousePress(QMouseEvent *event)
502{
503 bool handled = false;
504 for (auto &&group : m_groups) {
505 if (!group->series->isVisible())
506 continue;
507
508 if (!group->series->isSelectable() && !group->series->isDraggable())
509 continue;
510
511 int index = 0;
512 for (auto &&rect : group->rects) {
513 if (rect.contains(p: event->pos())) {
514 m_pointPressed = true;
515 m_pressStart = event->pos();
516 m_pressedGroup = group;
517 m_pressedPointIndex = index;
518 handled = true;
519 }
520 index++;
521 }
522 }
523 return handled;
524}
525
526bool PointRenderer::handleMouseRelease(QMouseEvent *event)
527{
528 bool handled = false;
529 if (!m_pointDragging && m_pointPressed && m_pressedGroup
530 && m_pressedGroup->series->isSelectable() && m_pressedGroup->series->isVisible()) {
531 if (m_pressedGroup->rects[m_pressedPointIndex].contains(p: event->pos())) {
532 if (m_pressedGroup->series->isPointSelected(index: m_pressedPointIndex)) {
533 m_pressedGroup->series->deselectPoint(index: m_pressedPointIndex);
534 } else {
535 m_pressedGroup->series->selectPoint(index: m_pressedPointIndex);
536 }
537 handled = true;
538 }
539 }
540 m_pointPressed = false;
541 m_pointDragging = false;
542 return handled;
543}
544
545bool PointRenderer::handleHoverMove(QHoverEvent *event)
546{
547 bool handled = false;
548 const QPointF &position = event->position();
549
550 for (auto &&group : m_groups) {
551 if (!group->series->isHoverable() || !group->series->isVisible())
552 continue;
553
554 auto axisRenderer = group->series->graph()->m_axisRenderer;
555 bool isHNegative = axisRenderer->m_axisHorizontalMaxValue
556 < axisRenderer->m_axisHorizontalMinValue;
557 bool isVNegative = axisRenderer->m_axisVerticalMaxValue
558 < axisRenderer->m_axisVerticalMinValue;
559
560 if (group->series->type() == QAbstractSeries::SeriesType::Scatter) {
561 const QString &name = group->series->name();
562
563 bool hovering = false;
564
565 int index = 0;
566 for (auto &&rect : group->rects) {
567 if (rect.contains(p: position.toPoint())) {
568 if (!group->hover) {
569 group->hover = true;
570 emit group->series->hoverEnter(seriesName: name, position, value: group->series->at(index));
571 }
572 emit group->series->hover(seriesName: name, position, value: group->series->at(index));
573 hovering = true;
574 }
575 index++;
576 }
577
578 if (!hovering && group->hover) {
579 group->hover = false;
580 emit group->series->hoverExit(seriesName: name, position);
581 }
582 } else {
583 const qreal x0 = event->position().x();
584 const qreal y0 = event->position().y();
585
586 const qreal hoverSize = defaultSize(series: group->series) / 2.0;
587 const QString &name = group->series->name();
588 auto &&points = group->series->points();
589 // True when line, false when spline
590 const bool isLine = group->series->type() == QAbstractSeries::SeriesType::Line;
591 if (points.size() >= 2) {
592 bool hovering = false;
593 auto subpath = group->painterPath.toSubpathPolygons();
594
595 for (int i = 0; i < points.size() - 1; i++) {
596 qreal x1, y1, x2, y2;
597 if (i == 0) {
598 auto element1 = group->painterPath.elementAt(i: 0);
599 auto element2 = group->painterPath.elementAt(i: isLine ? 1 : 3);
600 x1 = isHNegative ? element2.x : element1.x;
601 y1 = element1.y;
602 x2 = isHNegative ? element1.x : element2.x;
603 y2 = element2.y;
604 } else {
605 bool n = isVNegative | isHNegative;
606 // Each Spline (cubicTo) has 3 elements where third one is the x & y.
607 // So content of elements are:
608 // With Spline:
609 // [0] : MoveToElement
610 // [1] : 1. CurveToElement (c1x, c1y)
611 // [2] : 1. CurveToDataElement (c2x, c2y)
612 // [3] : 1. CurveToDataElement (x, y)
613 // [4] : 2. CurveToElement (c1x, c1y)
614 // ...
615 // With Line:
616 // [0] : MoveToElement
617 // [1] : 1. LineToElement (x, y)
618 // [2] : 2. LineToElement (x, y)
619 // ...
620 int element1Index = n ? (i + 1) : i;
621 int element2Index = n ? i : (i + 1);
622 element1Index = isLine ? element1Index : element1Index * 3;
623 element2Index = isLine ? element2Index : element2Index * 3;
624 auto element1 = group->painterPath.elementAt(i: element1Index);
625 auto element2 = group->painterPath.elementAt(i: element2Index);
626 x1 = element1.x;
627 y1 = element1.y;
628 x2 = element2.x;
629 y2 = element2.y;
630 }
631
632 if (isLine) {
633 qreal denominator = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
634 qreal hoverDistance = qAbs(t: (x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1))
635 / qSqrt(v: denominator);
636
637 if (hoverDistance < hoverSize) {
638 qreal alpha = 0;
639 qreal extrapolation = 0;
640 if (x2 - x1 >= y2 - y1) {
641 if (x2 - x1 != 0) {
642 alpha = ((x2 - x1) - (x0 - x1)) / qAbs(t: x2 - x1);
643 extrapolation = hoverSize / qAbs(t: x2 - x1);
644 }
645 } else {
646 if (y2 - y1 != 0) {
647 alpha = ((y2 - y1) - (y0 - y1)) / qAbs(t: y2 - y1);
648 extrapolation = hoverSize / qAbs(t: y2 - y1);
649 }
650 }
651
652 if (alpha >= -extrapolation && alpha <= 1.0 + extrapolation) {
653 bool n = isVNegative | isHNegative;
654
655 const QPointF &point1 = points[n ? i + 1 : i];
656 const QPointF &point2 = points[n ? i : i + 1];
657
658 QPointF point = (point2 * (1.0 - alpha)) + (point1 * alpha);
659
660 if (!group->hover) {
661 group->hover = true;
662 emit group->series->hoverEnter(seriesName: name, position, value: point);
663 }
664
665 emit group->series->hover(seriesName: name, position, value: point);
666 hovering = true;
667 handled = true;
668 }
669 }
670 } else { // Spline
671 auto segments = subpath[0];
672
673 for (auto it = segments.begin(); it != segments.end(); ++it) {
674 if (std::next(x: it, n: 1) == segments.end())
675 break;
676
677 auto it2 = std::next(x: it, n: 1);
678
679 qreal denominator = (it2->x() - it->x()) * (it2->x() - it->x())
680 + (it2->y() - it->y()) * (it2->y() - it->y());
681 qreal hoverDistance = qAbs(t: (it2->x() - it->x()) * (it->y() - y0)
682 - (it->x() - x0) * (it2->y() - it->y()))
683 / qSqrt(v: denominator);
684
685 if (hoverDistance < hoverSize) {
686 qreal alpha = 0;
687 qreal extrapolation = 0;
688 if (it2->x() - it->x() >= it2->y() - it->y()) {
689 if (it2->x() - it->x() != 0) {
690 alpha = ((it2->x() - it->x()) - (x0 - it->x()))
691 / qAbs(t: it2->x() - it->x());
692 extrapolation = hoverSize / qAbs(t: it2->x() - it->x());
693 }
694 } else {
695 if (it2->y() - it->y() != 0) {
696 alpha = ((it2->y() - it->y()) - (y0 - it->y()))
697 / qAbs(t: it2->y() - it->y());
698 extrapolation = hoverSize / qAbs(t: it2->y() - it->y());
699 }
700 }
701
702 if (alpha >= -extrapolation && alpha <= 1.0 + extrapolation) {
703 qreal cx1, cy1, cx2, cy2;
704
705 reverseRenderCoordinates(axisRenderer,
706 renderX: it->x(),
707 renderY: it->y(),
708 origX: &cx1,
709 origY: &cy1);
710 reverseRenderCoordinates(axisRenderer,
711 renderX: it2->x(),
712 renderY: it2->y(),
713 origX: &cx2,
714 origY: &cy2);
715
716 const QPointF &point1 = {cx1, cy1};
717 const QPointF &point2 = {cx2, cy2};
718
719 QPointF point = (point2 * (1.0 - alpha)) + (point1 * alpha);
720
721 if (!group->hover) {
722 group->hover = true;
723 emit group->series->hoverEnter(seriesName: name, position, value: point);
724 }
725
726 emit group->series->hover(seriesName: name, position, value: point);
727 hovering = true;
728 handled = true;
729 }
730 }
731 }
732 }
733 }
734
735 if (!hovering && group->hover) {
736 group->hover = false;
737 emit group->series->hoverExit(seriesName: name, position);
738 handled = true;
739 }
740 }
741 }
742 }
743 return handled;
744}
745
746QT_END_NAMESPACE
747

source code of qtgraphs/src/graphs2d/qsgrenderer/pointrenderer.cpp