1/*
2 * This file is part of KQuickCharts
3 * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8#include "LineSegmentNode.h"
9
10#include <QSGGeometry>
11
12#include "LineChartMaterial.h"
13
14constexpr int MaxPointsSize = (6 + 8) * 2;
15
16struct LineVertex {
17 float position[2];
18
19 float uv[2];
20
21 float lineColor[4];
22 float fillColor[4];
23
24 float bounds[2];
25
26 float pointCount;
27 float points[MaxPointsSize];
28
29 void set(const QPointF &newPosition,
30 const QPointF &newUv,
31 const QList<QVector2D> &newPoints,
32 const QColor &newLineColor,
33 const QColor &newFillColor,
34 const QVector2D &newBounds)
35 {
36 position[0] = newPosition.x();
37 position[1] = newPosition.y();
38
39 uv[0] = newUv.x();
40 uv[1] = newUv.y();
41
42 lineColor[0] = newLineColor.redF();
43 lineColor[1] = newLineColor.greenF();
44 lineColor[2] = newLineColor.blueF();
45 lineColor[3] = newLineColor.alphaF();
46
47 fillColor[0] = newFillColor.redF();
48 fillColor[1] = newFillColor.greenF();
49 fillColor[2] = newFillColor.blueF();
50 fillColor[3] = newFillColor.alphaF();
51
52 bounds[0] = newBounds.x();
53 bounds[1] = newBounds.y();
54
55 setPoints(newPoints);
56 }
57
58 void setPoints(const QList<QVector2D> &newPoints)
59 {
60 memset(s: points, c: 0, n: MaxPointsSize * sizeof(float));
61
62 Q_ASSERT_X(newPoints.size() <= (MaxPointsSize / 2),
63 "LineVertex::setPoints",
64 qPrintable(QStringLiteral("Too many points in new points array: %1").arg(newPoints.size())));
65
66 for (int i = 0; i < newPoints.size(); ++i) {
67 points[i * 2 + 0] = newPoints[i].x();
68 points[i * 2 + 1] = newPoints[i].y();
69 }
70
71 pointCount = newPoints.size();
72 }
73};
74
75/* clang-format off */
76QSGGeometry::Attribute LineAttributes[] = {
77 QSGGeometry::Attribute::createWithAttributeType(pos: 0, tupleSize: 2, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::PositionAttribute), // in_position
78 QSGGeometry::Attribute::createWithAttributeType(pos: 1, tupleSize: 2, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::TexCoordAttribute), // in_uv
79
80 QSGGeometry::Attribute::createWithAttributeType(pos: 2, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_lineColor
81 QSGGeometry::Attribute::createWithAttributeType(pos: 3, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_fillColor
82
83 QSGGeometry::Attribute::createWithAttributeType(pos: 4, tupleSize: 2, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_bounds
84
85 QSGGeometry::Attribute::createWithAttributeType(pos: 5, tupleSize: 1, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_count
86
87 QSGGeometry::Attribute::createWithAttributeType(pos: 8, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_points_0
88 QSGGeometry::Attribute::createWithAttributeType(pos: 9, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_points_1
89 QSGGeometry::Attribute::createWithAttributeType(pos: 10, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_points_2
90 QSGGeometry::Attribute::createWithAttributeType(pos: 11, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_points_3
91 QSGGeometry::Attribute::createWithAttributeType(pos: 12, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_points_4
92 QSGGeometry::Attribute::createWithAttributeType(pos: 13, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_points_5
93 QSGGeometry::Attribute::createWithAttributeType(pos: 14, tupleSize: 4, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::UnknownAttribute), // in_points_6
94};
95/* clang-format on */
96
97QSGGeometry::AttributeSet LineAttributeSet = {.count: 13, .stride: sizeof(LineVertex), .attributes: LineAttributes};
98
99void updateLineGeometry(QSGGeometry *geometry,
100 const QRectF &rect,
101 const QRectF &uvRect,
102 const QList<QVector2D> &points,
103 const QColor &lineColor,
104 const QColor &fillColor,
105 const QVector2D &bounds)
106{
107 auto vertices = static_cast<LineVertex *>(geometry->vertexData());
108 vertices[0].set(newPosition: rect.topLeft(), newUv: uvRect.topLeft(), newPoints: points, newLineColor: lineColor, newFillColor: fillColor, newBounds: bounds);
109 vertices[1].set(newPosition: rect.bottomLeft(), newUv: uvRect.bottomLeft(), newPoints: points, newLineColor: lineColor, newFillColor: fillColor, newBounds: bounds);
110 vertices[2].set(newPosition: rect.topRight(), newUv: uvRect.topRight(), newPoints: points, newLineColor: lineColor, newFillColor: fillColor, newBounds: bounds);
111 vertices[3].set(newPosition: rect.bottomRight(), newUv: uvRect.bottomRight(), newPoints: points, newLineColor: lineColor, newFillColor: fillColor, newBounds: bounds);
112 geometry->markVertexDataDirty();
113}
114
115LineSegmentNode::LineSegmentNode()
116 : LineSegmentNode(QRectF{})
117{
118}
119
120LineSegmentNode::LineSegmentNode(const QRectF &rect)
121{
122 m_geometry = new QSGGeometry{LineAttributeSet, 4};
123 m_geometry->setVertexDataPattern(QSGGeometry::DynamicPattern);
124
125 setGeometry(m_geometry);
126
127 m_rect = rect;
128
129 m_material = new LineChartMaterial{};
130 setMaterial(m_material);
131
132 setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial);
133}
134
135LineSegmentNode::~LineSegmentNode()
136{
137}
138
139void LineSegmentNode::setRect(const QRectF &rect)
140{
141 m_rect = rect;
142}
143
144void LineSegmentNode::setAspect(float xAspect, float yAspect)
145{
146 if (qFuzzyCompare(p1: xAspect, p2: m_xAspect) && qFuzzyCompare(p1: yAspect, p2: m_yAspect)) {
147 return;
148 }
149
150 m_yAspect = yAspect;
151 m_material->aspect = m_yAspect;
152 markDirty(bits: QSGNode::DirtyMaterial);
153
154 m_xAspect = xAspect;
155}
156
157void LineSegmentNode::setSmoothing(float smoothing)
158{
159 if (qFuzzyCompare(p1: smoothing, p2: m_smoothing)) {
160 return;
161 }
162
163 m_smoothing = smoothing;
164 m_material->smoothing = m_smoothing;
165 markDirty(bits: QSGNode::DirtyMaterial);
166}
167
168void LineSegmentNode::setLineWidth(float width)
169{
170 if (qFuzzyCompare(p1: width, p2: m_lineWidth)) {
171 return;
172 }
173
174 m_lineWidth = width;
175 m_material->lineWidth = m_lineWidth;
176 markDirty(bits: QSGNode::DirtyMaterial);
177}
178
179void LineSegmentNode::setLineColor(const QColor &color)
180{
181 m_lineColor = color;
182}
183
184void LineSegmentNode::setFillColor(const QColor &color)
185{
186 m_fillColor = color;
187}
188
189void LineSegmentNode::setValues(const QList<QVector2D> &values)
190{
191 m_values = values;
192}
193
194void LineSegmentNode::setFarLeft(const QVector2D &value)
195{
196 m_farLeft = value;
197}
198
199void LineSegmentNode::setFarRight(const QVector2D &value)
200{
201 m_farRight = value;
202}
203
204void LineSegmentNode::update()
205{
206 if (m_values.isEmpty() || !m_rect.isValid()) {
207 updateLineGeometry(geometry: m_geometry, rect: QRectF{}, uvRect: QRectF{}, points: QList<QVector2D>{}, lineColor: m_lineColor, fillColor: m_fillColor, bounds: QVector2D{});
208 markDirty(bits: QSGNode::DirtyGeometry);
209 return;
210 }
211
212 QList<QVector2D> points;
213 points.reserve(asize: m_values.size() + 8);
214
215 points << QVector2D{0.0, -0.5};
216 points << QVector2D{-0.5, -0.5};
217
218 auto min = std::numeric_limits<float>::max();
219 auto max = std::numeric_limits<float>::min();
220
221 if (!m_farLeft.isNull()) {
222 points << QVector2D(-0.5, m_farLeft.y() * m_yAspect);
223 points << QVector2D(((m_farLeft.x() - m_rect.left()) / m_rect.width()) * m_xAspect, m_farLeft.y() * m_yAspect);
224 min = std::min(a: m_farLeft.y() * m_yAspect, b: min);
225 max = std::max(a: m_farLeft.y() * m_yAspect, b: max);
226 } else {
227 points << QVector2D(-0.5, m_values[0].y() * m_yAspect);
228 }
229
230 for (auto value : std::as_const(t&: m_values)) {
231 auto x = ((value.x() - m_rect.left()) / m_rect.width()) * m_xAspect;
232 points << QVector2D(x, value.y() * m_yAspect);
233 min = std::min(a: value.y() * m_yAspect, b: min);
234 max = std::max(a: value.y() * m_yAspect, b: max);
235 }
236
237 if (!m_farRight.isNull()) {
238 points << QVector2D(((m_farRight.x() - m_rect.left()) / m_rect.width()) * m_xAspect, m_farRight.y() * m_yAspect);
239 points << QVector2D(1.5, m_farRight.y() * m_yAspect);
240 min = std::min(a: m_farRight.y() * m_yAspect, b: min);
241 max = std::max(a: m_farRight.y() * m_yAspect, b: max);
242 } else {
243 points << QVector2D(1.5, points.last().y());
244 }
245
246 points << QVector2D{1.5, -0.5};
247 points << QVector2D{0.0, -0.5};
248
249 updateLineGeometry(geometry: m_geometry, rect: m_rect, uvRect: {0.0, 0.0, m_xAspect, 1.0}, points, lineColor: m_lineColor, fillColor: m_fillColor, bounds: QVector2D{min, max});
250 markDirty(bits: QSGNode::DirtyGeometry);
251}
252

source code of kquickcharts/src/scenegraph/LineSegmentNode.cpp