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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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