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