File indexing completed on 2024-05-05 16:16:43

0001 /*
0002  * This file is part of KQuickCharts
0003  * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006  */
0007 
0008 #include "PieChartNode.h"
0009 
0010 #include <algorithm>
0011 
0012 #include <QColor>
0013 #include <QSGGeometry>
0014 #include <cmath>
0015 
0016 #include "PieChartMaterial.h"
0017 
0018 static const qreal pi = std::acos(-1.0);
0019 static const qreal sectionSize = pi * 0.5;
0020 
0021 inline QVector4D colorToVec4(const QColor &color)
0022 {
0023     return QVector4D{float(color.redF()), float(color.greenF()), float(color.blueF()), float(color.alphaF())};
0024 }
0025 
0026 inline qreal degToRad(qreal deg)
0027 {
0028     return (deg / 180.0) * pi;
0029 }
0030 
0031 inline QVector2D rotated(const QVector2D vector, qreal angle)
0032 {
0033     auto newX = vector.x() * std::cos(angle) - vector.y() * std::sin(angle);
0034     auto newY = vector.x() * std::sin(angle) + vector.y() * std::cos(angle);
0035     return QVector2D(newX, newY);
0036 }
0037 
0038 PieChartNode::PieChartNode()
0039     : PieChartNode(QRectF{})
0040 {
0041 }
0042 
0043 PieChartNode::PieChartNode(const QRectF &rect)
0044 {
0045     m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4};
0046     QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0, 0, 1, 1});
0047     setGeometry(m_geometry);
0048 
0049     m_material = new PieChartMaterial{};
0050     setMaterial(m_material);
0051 
0052     setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial);
0053 }
0054 
0055 PieChartNode::~PieChartNode()
0056 {
0057 }
0058 
0059 void PieChartNode::setRect(const QRectF &rect)
0060 {
0061     if (rect == m_rect) {
0062         return;
0063     }
0064 
0065     m_rect = rect;
0066     QSGGeometry::updateTexturedRectGeometry(m_geometry, m_rect, QRectF{0, 0, 1, 1});
0067     markDirty(QSGNode::DirtyGeometry);
0068 
0069     auto minDimension = qMin(m_rect.width(), m_rect.height());
0070 
0071     QVector2D aspect{1.0, 1.0};
0072     aspect.setX(rect.width() / minDimension);
0073     aspect.setY(rect.height() / minDimension);
0074     m_material->setAspectRatio(aspect);
0075 
0076     m_material->setInnerRadius(m_innerRadius / minDimension);
0077     m_material->setOuterRadius(m_outerRadius / minDimension);
0078 
0079     markDirty(QSGNode::DirtyMaterial);
0080 }
0081 
0082 void PieChartNode::setInnerRadius(qreal radius)
0083 {
0084     if (qFuzzyCompare(radius, m_innerRadius)) {
0085         return;
0086     }
0087 
0088     m_innerRadius = radius;
0089 
0090     auto minDimension = qMin(m_rect.width(), m_rect.height());
0091     m_material->setInnerRadius(m_innerRadius / minDimension);
0092 
0093     markDirty(QSGNode::DirtyMaterial);
0094 }
0095 
0096 void PieChartNode::setOuterRadius(qreal radius)
0097 {
0098     if (qFuzzyCompare(radius, m_outerRadius)) {
0099         return;
0100     }
0101 
0102     m_outerRadius = radius;
0103 
0104     auto minDimension = qMin(m_rect.width(), m_rect.height());
0105     m_material->setOuterRadius(m_outerRadius / minDimension);
0106 
0107     markDirty(QSGNode::DirtyMaterial);
0108 }
0109 
0110 void PieChartNode::setColors(const QVector<QColor> &colors)
0111 {
0112     m_colors = colors;
0113     updateTriangles();
0114 }
0115 
0116 void PieChartNode::setSections(const QVector<qreal> &sections)
0117 {
0118     m_sections = sections;
0119     updateTriangles();
0120 }
0121 
0122 void PieChartNode::setBackgroundColor(const QColor &color)
0123 {
0124     if (color == m_backgroundColor) {
0125         return;
0126     }
0127 
0128     m_backgroundColor = color;
0129     m_material->setBackgroundColor(color);
0130     markDirty(QSGNode::DirtyMaterial);
0131 }
0132 
0133 void PieChartNode::setFromAngle(qreal angle)
0134 {
0135     if (qFuzzyCompare(angle, m_fromAngle)) {
0136         return;
0137     }
0138 
0139     m_fromAngle = angle;
0140     m_material->setFromAngle(degToRad(angle));
0141     updateTriangles();
0142 }
0143 
0144 void PieChartNode::setToAngle(qreal angle)
0145 {
0146     if (qFuzzyCompare(angle, m_fromAngle)) {
0147         return;
0148     }
0149 
0150     m_toAngle = angle;
0151     m_material->setToAngle(degToRad(angle));
0152     updateTriangles();
0153 }
0154 
0155 void PieChartNode::setSmoothEnds(bool smooth)
0156 {
0157     if (smooth == m_smoothEnds) {
0158         return;
0159     }
0160 
0161     m_smoothEnds = smooth;
0162     m_material->setSmoothEnds(smooth);
0163     markDirty(QSGNode::DirtyMaterial);
0164 }
0165 
0166 void PieChartNode::updateTriangles()
0167 {
0168     if (m_sections.isEmpty() || m_sections.size() != m_colors.size()) {
0169         return;
0170     }
0171 
0172     qreal startAngle = degToRad(m_fromAngle);
0173     qreal totalAngle = degToRad(m_toAngle - m_fromAngle);
0174 
0175     QVector<QVector2D> segments;
0176     QVector<QVector4D> colors;
0177 
0178     for (int i = 0; i < m_sections.size(); ++i) {
0179         QVector2D segment{float(startAngle), float(startAngle + m_sections.at(i) * totalAngle)};
0180         segments << segment;
0181         startAngle = segment.y();
0182         colors << colorToVec4(m_colors.at(i));
0183     }
0184 
0185     if (m_sections.size() == 1 && qFuzzyCompare(m_sections.at(0), 0.0)) {
0186         segments.clear();
0187     }
0188 
0189     m_material->setSegments(segments);
0190     m_material->setColors(colors);
0191 
0192     markDirty(QSGNode::DirtyMaterial);
0193 }