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> &sections)
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