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 "PieChartNode.h"
9
10 #include <algorithm>
11
12 #include <QColor>
13 #include <QSGGeometry>
14 #include <cmath>
15
16 #include "PieChartMaterial.h"
17
18 static const qreal pi = std::acos(-1.0);
19 static const qreal sectionSize = pi * 0.5;
20
colorToVec4(const QColor & color)21 inline QVector4D colorToVec4(const QColor &color)
22 {
23 return QVector4D{float(color.redF()), float(color.greenF()), float(color.blueF()), float(color.alphaF())};
24 }
25
degToRad(qreal deg)26 inline qreal degToRad(qreal deg)
27 {
28 return (deg / 180.0) * pi;
29 }
30
rotated(const QVector2D vector,qreal angle)31 inline QVector2D rotated(const QVector2D vector, qreal angle)
32 {
33 auto newX = vector.x() * std::cos(angle) - vector.y() * std::sin(angle);
34 auto newY = vector.x() * std::sin(angle) + vector.y() * std::cos(angle);
35 return QVector2D(newX, newY);
36 }
37
PieChartNode()38 PieChartNode::PieChartNode()
39 : PieChartNode(QRectF{})
40 {
41 }
42
PieChartNode(const QRectF & rect)43 PieChartNode::PieChartNode(const QRectF &rect)
44 {
45 m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4};
46 QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0, 0, 1, 1});
47 setGeometry(m_geometry);
48
49 m_material = new PieChartMaterial{};
50 setMaterial(m_material);
51
52 setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial);
53 }
54
~PieChartNode()55 PieChartNode::~PieChartNode()
56 {
57 }
58
setRect(const QRectF & rect)59 void PieChartNode::setRect(const QRectF &rect)
60 {
61 if (rect == m_rect) {
62 return;
63 }
64
65 m_rect = rect;
66 QSGGeometry::updateTexturedRectGeometry(m_geometry, m_rect, QRectF{0, 0, 1, 1});
67 markDirty(QSGNode::DirtyGeometry);
68
69 auto minDimension = qMin(m_rect.width(), m_rect.height());
70
71 QVector2D aspect{1.0, 1.0};
72 aspect.setX(rect.width() / minDimension);
73 aspect.setY(rect.height() / minDimension);
74 m_material->setAspectRatio(aspect);
75
76 m_material->setInnerRadius(m_innerRadius / minDimension);
77 m_material->setOuterRadius(m_outerRadius / minDimension);
78
79 markDirty(QSGNode::DirtyMaterial);
80 }
81
setInnerRadius(qreal radius)82 void PieChartNode::setInnerRadius(qreal radius)
83 {
84 if (qFuzzyCompare(radius, m_innerRadius)) {
85 return;
86 }
87
88 m_innerRadius = radius;
89
90 auto minDimension = qMin(m_rect.width(), m_rect.height());
91 m_material->setInnerRadius(m_innerRadius / minDimension);
92
93 markDirty(QSGNode::DirtyMaterial);
94 }
95
setOuterRadius(qreal radius)96 void PieChartNode::setOuterRadius(qreal radius)
97 {
98 if (qFuzzyCompare(radius, m_outerRadius)) {
99 return;
100 }
101
102 m_outerRadius = radius;
103
104 auto minDimension = qMin(m_rect.width(), m_rect.height());
105 m_material->setOuterRadius(m_outerRadius / minDimension);
106
107 markDirty(QSGNode::DirtyMaterial);
108 }
109
setColors(const QVector<QColor> & colors)110 void PieChartNode::setColors(const QVector<QColor> &colors)
111 {
112 m_colors = colors;
113 updateTriangles();
114 }
115
setSections(const QVector<qreal> & sections)116 void PieChartNode::setSections(const QVector<qreal> §ions)
117 {
118 m_sections = sections;
119 updateTriangles();
120 }
121
setBackgroundColor(const QColor & color)122 void PieChartNode::setBackgroundColor(const QColor &color)
123 {
124 if (color == m_backgroundColor) {
125 return;
126 }
127
128 m_backgroundColor = color;
129 m_material->setBackgroundColor(color);
130 markDirty(QSGNode::DirtyMaterial);
131 }
132
setFromAngle(qreal angle)133 void PieChartNode::setFromAngle(qreal angle)
134 {
135 if (qFuzzyCompare(angle, m_fromAngle)) {
136 return;
137 }
138
139 m_fromAngle = angle;
140 m_material->setFromAngle(degToRad(angle));
141 updateTriangles();
142 }
143
setToAngle(qreal angle)144 void PieChartNode::setToAngle(qreal angle)
145 {
146 if (qFuzzyCompare(angle, m_fromAngle)) {
147 return;
148 }
149
150 m_toAngle = angle;
151 m_material->setToAngle(degToRad(angle));
152 updateTriangles();
153 }
154
setSmoothEnds(bool smooth)155 void PieChartNode::setSmoothEnds(bool smooth)
156 {
157 if (smooth == m_smoothEnds) {
158 return;
159 }
160
161 m_smoothEnds = smooth;
162 m_material->setSmoothEnds(smooth);
163 markDirty(QSGNode::DirtyMaterial);
164 }
165
updateTriangles()166 void PieChartNode::updateTriangles()
167 {
168 if (m_sections.isEmpty() || m_sections.size() != m_colors.size()) {
169 return;
170 }
171
172 qreal startAngle = degToRad(m_fromAngle);
173 qreal totalAngle = degToRad(m_toAngle - m_fromAngle);
174
175 QVector<QVector2D> segments;
176 QVector<QVector4D> colors;
177
178 for (int i = 0; i < m_sections.size(); ++i) {
179 QVector2D segment{float(startAngle), float(startAngle + m_sections.at(i) * totalAngle)};
180 segments << segment;
181 startAngle = segment.y();
182 colors << colorToVec4(m_colors.at(i));
183 }
184
185 if (m_sections.size() == 1 && qFuzzyCompare(m_sections.at(0), 0.0)) {
186 segments.clear();
187 }
188
189 m_material->setSegments(segments);
190 m_material->setColors(colors);
191
192 markDirty(QSGNode::DirtyMaterial);
193 }
194