1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtGraphs/qareaseries.h>
5#include <QtGraphs/qsplineseries.h>
6#include <private/arearenderer_p.h>
7#include <private/pointrenderer_p.h>
8#include <private/axisrenderer_p.h>
9#include <private/qabstractseries_p.h>
10#include <private/qareaseries_p.h>
11#include <private/qgraphsview_p.h>
12#include <private/qxyseries_p.h>
13
14QT_BEGIN_NAMESPACE
15
16AreaRenderer::AreaRenderer(QGraphsView *graph)
17 : QQuickItem(graph)
18 , m_graph(graph)
19{
20 setFlag(flag: QQuickItem::ItemHasContents);
21 setClip(true);
22 m_shape.setParentItem(this);
23 m_shape.setPreferredRendererType(QQuickShape::CurveRenderer);
24}
25
26AreaRenderer::~AreaRenderer()
27{
28 qDeleteAll(c: m_groups);
29}
30
31void AreaRenderer::calculateRenderCoordinates(qreal origX,
32 qreal origY,
33 qreal *renderX,
34 qreal *renderY) const
35{
36 *renderX = m_areaWidth * origX * m_maxHorizontal - m_horizontalOffset;
37 *renderY = m_areaHeight - m_areaHeight * origY * m_maxVertical
38 + m_verticalOffset;
39}
40
41void AreaRenderer::calculateAxisCoordinates(qreal origX,
42 qreal origY,
43 qreal *axisX,
44 qreal *axisY) const
45{
46 *axisX = origX / m_areaWidth
47 / m_maxHorizontal;
48 *axisY = m_graph->m_axisRenderer->m_axisVerticalValueRange
49 - origY / m_areaHeight / m_maxVertical;
50}
51
52void AreaRenderer::handlePolish(QAreaSeries *series)
53{
54 auto theme = m_graph->theme();
55 if (!theme)
56 return;
57
58 if (!m_graph->m_axisRenderer)
59 return;
60
61 QXYSeries *upper = series->upperSeries();
62 QXYSeries *lower = series->lowerSeries();
63
64 if (!upper)
65 return;
66
67 if (!m_groups.contains(key: series)) {
68 PointGroup *group = new PointGroup();
69 group->series = series;
70 m_groups.insert(key: series, value: group);
71
72 group->shapePath = new QQuickShapePath(&m_shape);
73 auto data = m_shape.data();
74 data.append(&data, m_groups.value(key: series)->shapePath);
75 }
76
77 auto group = m_groups.value(key: series);
78
79 if (upper->points().count() < 2 || (lower && lower->points().count() < 2)) {
80 auto painterPath = group->painterPath;
81 painterPath.clear();
82 group->shapePath->setPath(painterPath);
83 return;
84 }
85
86 m_areaWidth = width();
87 m_areaHeight = height();
88
89 m_maxVertical = m_graph->m_axisRenderer->m_axisVerticalValueRange > 0
90 ? 1.0 / m_graph->m_axisRenderer->m_axisVerticalValueRange
91 : 100.0;
92 m_maxHorizontal = m_graph->m_axisRenderer->m_axisHorizontalValueRange > 0
93 ? 1.0 / m_graph->m_axisRenderer->m_axisHorizontalValueRange
94 : 100.0;
95 m_verticalOffset = (m_graph->m_axisRenderer->m_axisVerticalMinValue
96 / m_graph->m_axisRenderer->m_axisVerticalValueRange)
97 * m_areaHeight;
98 m_horizontalOffset = (m_graph->m_axisRenderer->m_axisHorizontalMinValue
99 / m_graph->m_axisRenderer->m_axisHorizontalValueRange)
100 * m_areaWidth;
101
102 auto &painterPath = group->painterPath;
103 painterPath.clear();
104
105 if (group->colorIndex < 0) {
106 group->colorIndex = m_graph->graphSeriesCount();
107 m_graph->setGraphSeriesCount(group->colorIndex + 1);
108 }
109
110 const auto &seriesColors = theme->seriesColors();
111 qsizetype index = group->colorIndex % seriesColors.size();
112 QColor color = series->color().alpha() != 0
113 ? series->color()
114 : seriesColors.at(i: index);
115 const auto &borderColors = theme->borderColors();
116 index = group->colorIndex % borderColors.size();
117 QColor borderColor = series->borderColor().alpha() != 0
118 ? series->borderColor()
119 : borderColors.at(i: index);
120
121 if (series->isSelected()) {
122 color = series->selectedColor().alpha() != 0 ? series->selectedColor() : color.lighter();
123 borderColor = series->selectedBorderColor().alpha() != 0 ? series->selectedBorderColor()
124 : borderColor.lighter();
125 }
126
127 qreal borderWidth = series->borderWidth();
128 if (qFuzzyCompare(p1: borderWidth, p2: qreal(-1.0)))
129 borderWidth = theme->borderWidth();
130
131 group->shapePath->setStrokeWidth(borderWidth);
132 group->shapePath->setStrokeColor(borderColor);
133 group->shapePath->setFillColor(color);
134 group->shapePath->setCapStyle(QQuickShapePath::CapStyle::SquareCap);
135
136 auto &&upperPoints = upper->points();
137 QList<QPointF> fittedPoints;
138 if (upper->type() == QAbstractSeries::SeriesType::Spline)
139 fittedPoints = qobject_cast<QSplineSeries *>(object: upper)->getControlPoints();
140
141 int extraPointCount = lower ? 0 : 3;
142
143 if (series->isVisible()) {
144 for (int i = 0, j = 0; i < upperPoints.size() + extraPointCount; ++i, ++j) {
145 qreal x, y;
146 if (i == upperPoints.size())
147 calculateRenderCoordinates(origX: upperPoints[upperPoints.size() - 1].x(), origY: 0, renderX: &x, renderY: &y);
148 else if (i == upperPoints.size() + 1)
149 calculateRenderCoordinates(origX: upperPoints[0].x(), origY: 0, renderX: &x, renderY: &y);
150 else if (i == upperPoints.size() + 2)
151 calculateRenderCoordinates(origX: upperPoints[0].x(), origY: upperPoints[0].y(), renderX: &x, renderY: &y);
152 else
153 calculateRenderCoordinates(origX: upperPoints[i].x(), origY: upperPoints[i].y(), renderX: &x, renderY: &y);
154
155 if (i == 0) {
156 painterPath.moveTo(x, y);
157 } else {
158 if (i < upper->points().size()
159 && upper->type() == QAbstractSeries::SeriesType::Spline) {
160 qreal x1, y1, x2, y2;
161 calculateRenderCoordinates(origX: fittedPoints[j - 1].x(),
162 origY: fittedPoints[j - 1].y(),
163 renderX: &x1,
164 renderY: &y1);
165 calculateRenderCoordinates(origX: fittedPoints[j].x(), origY: fittedPoints[j].y(), renderX: &x2, renderY: &y2);
166
167 painterPath.cubicTo(ctrlPt1x: x1, ctrlPt1y: y1, ctrlPt2x: x2, ctrlPt2y: y2, endPtx: x, endPty: y);
168 ++j;
169 } else {
170 painterPath.lineTo(x, y);
171 }
172 }
173 }
174 }
175
176 if (lower && series->isVisible()) {
177 auto &&lowerPoints = lower->points();
178 QList<QPointF> fittedPoints;
179 if (lower->type() == QAbstractSeries::SeriesType::Spline)
180 fittedPoints = qobject_cast<QSplineSeries *>(object: lower)->getControlPoints();
181
182 for (int i = 0, j = 0; i < lowerPoints.size(); ++i, ++j) {
183 qreal x, y;
184 calculateRenderCoordinates(origX: lowerPoints[lowerPoints.size() - 1 - i].x(),
185 origY: lowerPoints[lowerPoints.size() - 1 - i].y(),
186 renderX: &x,
187 renderY: &y);
188
189 if (i > 0 && lower->type() == QAbstractSeries::SeriesType::Spline) {
190 qreal x1, y1, x2, y2;
191 calculateRenderCoordinates(origX: fittedPoints[fittedPoints.size() - 1 - j + 1].x(),
192 origY: fittedPoints[fittedPoints.size() - 1 - j + 1].y(),
193 renderX: &x1,
194 renderY: &y1);
195 calculateRenderCoordinates(origX: fittedPoints[fittedPoints.size() - 1 - j].x(),
196 origY: fittedPoints[fittedPoints.size() - 1 - j].y(),
197 renderX: &x2,
198 renderY: &y2);
199
200 painterPath.cubicTo(ctrlPt1x: x1, ctrlPt1y: y1, ctrlPt2x: x2, ctrlPt2y: y2, endPtx: x, endPty: y);
201 ++j;
202 } else {
203 painterPath.lineTo(x, y);
204 }
205 }
206
207 qreal x, y;
208 calculateRenderCoordinates(origX: upperPoints[0].x(), origY: upperPoints[0].y(), renderX: &x, renderY: &y);
209 painterPath.lineTo(x, y);
210 }
211
212 group->shapePath->setPath(painterPath);
213
214 QList<QLegendData> legendDataList = {{.color: color, .borderColor: borderColor, .label: series->name()}};
215 series->d_func()->setLegendData(legendDataList);
216}
217
218void AreaRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
219{
220 for (auto series : cleanupSeries) {
221 auto areaSeries = qobject_cast<QAreaSeries *>(object: series);
222 if (areaSeries && m_groups.contains(key: areaSeries)) {
223 auto group = m_groups.value(key: areaSeries);
224
225 auto painterPath = group->painterPath;
226 painterPath.clear();
227 group->shapePath->setPath(painterPath);
228
229 delete group;
230 m_groups.remove(key: areaSeries);
231 }
232 }
233}
234
235void AreaRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
236{
237 Q_UNUSED(cleanupSeries);
238}
239
240void AreaRenderer::updateSeries(QAreaSeries *series)
241{
242 Q_UNUSED(series);
243}
244
245// Point inside triangle code from
246// https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle
247float sign(QPoint p1, QPoint p2, QPoint p3)
248{
249 return (p1.x() - p3.x()) * (p2.y() - p3.y()) - (p2.x() - p3.x()) * (p1.y() - p3.y());
250}
251
252bool pointInTriangle(QPoint pt, QPoint v1, QPoint v2, QPoint v3)
253{
254 float d1, d2, d3;
255 bool hasNeg, hasPos;
256
257 d1 = sign(p1: pt, p2: v1, p3: v2);
258 d2 = sign(p1: pt, p2: v2, p3: v3);
259 d3 = sign(p1: pt, p2: v3, p3: v1);
260
261 hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
262 hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);
263
264 return !(hasNeg && hasPos);
265}
266
267bool AreaRenderer::pointInArea(QPoint pt, QAreaSeries *series) const
268{
269 QList<QPointF> upperPoints = series->upperSeries()->points();
270 QList<QPointF> lowerPoints;
271
272 if (series->lowerSeries())
273 lowerPoints = series->lowerSeries()->points();
274
275 QList<QPointF> *firstPoints = &upperPoints;
276 if (lowerPoints.size() > upperPoints.size())
277 firstPoints = &lowerPoints;
278
279 for (int i = 0; i < firstPoints->size() - 1; ++i) {
280 qreal x1, y1, x2, y2, x3, y3, x4, y4;
281 calculateRenderCoordinates(origX: (*firstPoints)[i].x(), origY: (*firstPoints)[i].y(), renderX: &x1, renderY: &y1);
282 calculateRenderCoordinates(origX: (*firstPoints)[i + 1].x(), origY: (*firstPoints)[i + 1].y(), renderX: &x2, renderY: &y2);
283
284 bool needSecondTriangleTest = true;
285 if (series->lowerSeries()) {
286 QList<QPointF> *secondPoints = &lowerPoints;
287 if (lowerPoints.size() > upperPoints.size())
288 secondPoints = &upperPoints;
289
290 qsizetype firstIndex = i;
291 qsizetype secondIndex = i + 1;
292
293 if (firstIndex >= secondPoints->size())
294 firstIndex = secondPoints->size() - 1;
295 if (secondIndex >= secondPoints->size())
296 needSecondTriangleTest = false;
297
298 calculateRenderCoordinates(origX: (*secondPoints)[firstIndex].x(),
299 origY: (*secondPoints)[firstIndex].y(),
300 renderX: &x3,
301 renderY: &y3);
302
303 if (needSecondTriangleTest) {
304 calculateRenderCoordinates(origX: (*secondPoints)[secondIndex].x(),
305 origY: (*secondPoints)[secondIndex].y(),
306 renderX: &x4,
307 renderY: &y4);
308 } else {
309 x4 = 0.0;
310 y4 = 0.0;
311 }
312 } else {
313 calculateRenderCoordinates(origX: upperPoints[i].x(), origY: 0, renderX: &x3, renderY: &y3);
314 calculateRenderCoordinates(origX: upperPoints[i + 1].x(), origY: 0, renderX: &x4, renderY: &y4);
315 }
316
317 QPoint point1(x1, y1);
318 QPoint point2(x2, y2);
319 QPoint point3(x3, y3);
320 QPoint point4(x4, y4);
321
322 if (pointInTriangle(pt, v1: point1, v2: point2, v3: point3)
323 || (needSecondTriangleTest && pointInTriangle(pt, v1: point2, v2: point3, v3: point4))) {
324 return true;
325 }
326 }
327
328 return false;
329}
330
331bool AreaRenderer::handleMousePress(QMouseEvent *event)
332{
333 bool handled = false;
334 for (auto &&group : m_groups) {
335 if (!group->series->isSelectable() || !group->series->isVisible())
336 continue;
337
338 if (!group->series->upperSeries() || group->series->upperSeries()->count() < 2)
339 continue;
340
341 if (group->series->lowerSeries() && group->series->lowerSeries()->count() < 2)
342 continue;
343
344 if (pointInArea(pt: event->pos(), series: group->series)) {
345 group->series->setSelected(!group->series->isSelected());
346 handled = true;
347 }
348 }
349 return handled;
350}
351
352bool AreaRenderer::handleHoverMove(QHoverEvent *event)
353{
354 bool handled = false;
355 const QPointF &position = event->position();
356
357 for (auto &&group : m_groups) {
358 if (!group->series->isHoverable() || !group->series->isVisible())
359 continue;
360
361 if (!group->series->upperSeries() || group->series->upperSeries()->count() < 2)
362 continue;
363
364 if (group->series->lowerSeries() && group->series->lowerSeries()->count() < 2)
365 continue;
366
367 const QString &name = group->series->name();
368
369 bool hovering = false;
370 if (pointInArea(pt: position.toPoint(), series: group->series)) {
371 qreal x, y;
372 calculateAxisCoordinates(origX: position.x(), origY: position.y(), axisX: &x, axisY: &y);
373
374 if (!group->hover) {
375 group->hover = true;
376 emit group->series->hoverEnter(seriesName: name, position, value: QPointF(x, y));
377 }
378
379 emit group->series->hover(seriesName: name, position, value: QPointF(x, y));
380 hovering = true;
381 handled = true;
382 }
383
384 if (!hovering && group->hover) {
385 group->hover = false;
386 emit group->series->hoverExit(seriesName: name, position);
387 handled = true;
388 }
389 }
390 return handled;
391}
392
393QT_END_NAMESPACE
394

Provided by KDAB

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

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