diff --git a/src/scenegraph/PieChartMaterial.cpp b/src/scenegraph/PieChartMaterial.cpp index 34baa49..8a534b1 100644 --- a/src/scenegraph/PieChartMaterial.cpp +++ b/src/scenegraph/PieChartMaterial.cpp @@ -1,148 +1,172 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "PieChartMaterial.h" PieChartMaterial::PieChartMaterial() { setFlag(QSGMaterial::Blending); } PieChartMaterial::~PieChartMaterial() { } QSGMaterialType *PieChartMaterial::type() const { static QSGMaterialType type; return &type; } QSGMaterialShader *PieChartMaterial::createShader() const { return new PieChartShader(); } QVector2D PieChartMaterial::aspectRatio() const { return m_aspectRatio; } float PieChartMaterial::innerRadius() const { return m_innerRadius; } float PieChartMaterial::outerRadius() const { return m_outerRadius; } QColor PieChartMaterial::backgroundColor() const { return m_backgroundColor; } QVector PieChartMaterial::segments() const { return m_segments; } QVector PieChartMaterial::colors() const { return m_colors; } bool PieChartMaterial::smoothEnds() const { return m_smoothEnds; } +float PieChartMaterial::fromAngle() const +{ + return m_fromAngle; +} + +float PieChartMaterial::toAngle() const +{ + return m_toAngle; +} + void PieChartMaterial::setAspectRatio(const QVector2D &aspect) { m_aspectRatio = aspect; } void PieChartMaterial::setInnerRadius(float radius) { m_innerRadius = radius; } void PieChartMaterial::setOuterRadius(float radius) { m_outerRadius = radius; } void PieChartMaterial::setBackgroundColor(const QColor &color) { m_backgroundColor = color; } void PieChartMaterial::setSegments(const QVector &segments) { m_segments = segments; } void PieChartMaterial::setColors(const QVector &colors) { m_colors = colors; } void PieChartMaterial::setSmoothEnds(bool smooth) { m_smoothEnds = smooth; } +void PieChartMaterial::setFromAngle(float angle) +{ + m_fromAngle = angle; +} + +void PieChartMaterial::setToAngle(float angle) +{ + m_toAngle = angle; +} + PieChartShader::PieChartShader() { setShaders(QStringLiteral("piechart.vert"), QStringLiteral("piechart.frag")); } PieChartShader::~PieChartShader() { } const char *const *PieChartShader::attributeNames() const { static char const *const names[] = {"in_vertex", "in_uv", nullptr}; return names; } void PieChartShader::initialize() { QSGMaterialShader::initialize(); m_matrixLocation = program()->uniformLocation("matrix"); m_opacityLocation = program()->uniformLocation("opacity"); m_innerRadiusLocation = program()->uniformLocation("innerRadius"); m_outerRadiusLocation = program()->uniformLocation("outerRadius"); m_aspectLocation = program()->uniformLocation("aspect"); m_backgroundColorLocation = program()->uniformLocation("backgroundColor"); m_colorsLocation = program()->uniformLocation("colors"); m_segmentsLocation = program()->uniformLocation("segments"); m_segmentCountLocation = program()->uniformLocation("segmentCount"); m_smoothEndsLocation = program()->uniformLocation("smoothEnds"); + m_fromAngleLocation = program()->uniformLocation("fromAngle"); + m_toAngleLocation = program()->uniformLocation("toAngle"); } void PieChartShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { if (state.isMatrixDirty()) program()->setUniformValue(m_matrixLocation, state.combinedMatrix()); if (state.isOpacityDirty()) program()->setUniformValue(m_opacityLocation, state.opacity()); if (!oldMaterial || newMaterial->compare(oldMaterial) != 0) { PieChartMaterial *material = static_cast(newMaterial); program()->setUniformValue(m_innerRadiusLocation, material->innerRadius()); program()->setUniformValue(m_outerRadiusLocation, material->outerRadius()); program()->setUniformValue(m_aspectLocation, material->aspectRatio()); program()->setUniformValue(m_backgroundColorLocation, material->backgroundColor()); program()->setUniformValueArray(m_colorsLocation, material->colors().constData(), material->colors().size()); program()->setUniformValueArray(m_segmentsLocation, material->segments().constData(), material->segments().size()); program()->setUniformValue(m_segmentCountLocation, material->segments().size()); program()->setUniformValue(m_smoothEndsLocation, material->smoothEnds()); + program()->setUniformValue(m_fromAngleLocation, material->fromAngle()); + program()->setUniformValue(m_toAngleLocation, material->toAngle()); } } diff --git a/src/scenegraph/PieChartMaterial.h b/src/scenegraph/PieChartMaterial.h index dd3a466..ae776c0 100644 --- a/src/scenegraph/PieChartMaterial.h +++ b/src/scenegraph/PieChartMaterial.h @@ -1,79 +1,87 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef PIECHARTMATERIAL_H #define PIECHARTMATERIAL_H #include #include #include #include "SDFShader.h" class PieChartMaterial : public QSGMaterial { public: PieChartMaterial(); ~PieChartMaterial(); QSGMaterialType *type() const override; QSGMaterialShader *createShader() const override; QVector2D aspectRatio() const; float innerRadius() const; float outerRadius() const; QColor backgroundColor() const; bool smoothEnds() const; + float fromAngle() const; + float toAngle() const; QVector segments() const; QVector colors() const; void setAspectRatio(const QVector2D &aspect); void setInnerRadius(float radius); void setOuterRadius(float radius); void setBackgroundColor(const QColor &color); void setSmoothEnds(bool smooth); + void setFromAngle(float angle); + void setToAngle(float angle); void setSegments(const QVector &triangles); void setColors(const QVector &colors); private: QVector2D m_aspectRatio; float m_innerRadius = 0.0f; float m_outerRadius = 0.0f; QColor m_backgroundColor; bool m_smoothEnds = false; + float m_fromAngle = 0.0; + float m_toAngle = 6.28318; // 2 * pi QVector m_segments; QVector m_colors; }; class PieChartShader : public SDFShader { public: PieChartShader(); ~PieChartShader(); char const *const *attributeNames() const override; void initialize() override; void updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; private: int m_matrixLocation = 0; int m_opacityLocation = 0; int m_innerRadiusLocation = 0; int m_outerRadiusLocation = 0; int m_aspectLocation = 0; int m_backgroundColorLocation = 0; int m_colorsLocation = 0; int m_segmentsLocation = 0; int m_segmentCountLocation = 0; int m_smoothEndsLocation = 0; + int m_fromAngleLocation = 0; + int m_toAngleLocation = 0; }; #endif // PIECHARTMATERIAL_H diff --git a/src/scenegraph/PieChartNode.cpp b/src/scenegraph/PieChartNode.cpp index 9584b47..7dba3bb 100644 --- a/src/scenegraph/PieChartNode.cpp +++ b/src/scenegraph/PieChartNode.cpp @@ -1,189 +1,191 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "PieChartNode.h" #include #include #include #include #include "PieChartMaterial.h" static const qreal pi = std::acos(-1.0); static const qreal sectionSize = pi * 0.5; inline QVector4D colorToVec4(const QColor &color) { return QVector4D{float(color.redF()), float(color.greenF()), float(color.blueF()), float(color.alphaF())}; } inline qreal degToRad(qreal deg) { return (deg / 180.0) * pi; } inline QVector2D rotated(const QVector2D vector, qreal angle) { auto newX = vector.x() * std::cos(angle) - vector.y() * std::sin(angle); auto newY = vector.x() * std::sin(angle) + vector.y() * std::cos(angle); return QVector2D(newX, newY); } PieChartNode::PieChartNode() : PieChartNode(QRectF{}) { } PieChartNode::PieChartNode(const QRectF &rect) { m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4}; QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0, 0, 1, 1}); setGeometry(m_geometry); m_material = new PieChartMaterial{}; setMaterial(m_material); setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); } PieChartNode::~PieChartNode() { } void PieChartNode::setRect(const QRectF &rect) { if (rect == m_rect) return; m_rect = rect; QSGGeometry::updateTexturedRectGeometry(m_geometry, m_rect, QRectF{0, 0, 1, 1}); markDirty(QSGNode::DirtyGeometry); auto minDimension = qMin(m_rect.width(), m_rect.height()); QVector2D aspect{1.0, 1.0}; aspect.setX(rect.width() / minDimension); aspect.setY(rect.height() / minDimension); m_material->setAspectRatio(aspect); m_material->setInnerRadius(m_innerRadius / minDimension); m_material->setOuterRadius(m_outerRadius / minDimension); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::setInnerRadius(qreal radius) { if (qFuzzyCompare(radius, m_innerRadius)) { return; } m_innerRadius = radius; auto minDimension = qMin(m_rect.width(), m_rect.height()); m_material->setInnerRadius(m_innerRadius / minDimension); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::setOuterRadius(qreal radius) { if (qFuzzyCompare(radius, m_outerRadius)) { return; } m_outerRadius = radius; auto minDimension = qMin(m_rect.width(), m_rect.height()); m_material->setOuterRadius(m_outerRadius / minDimension); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::setColors(const QVector &colors) { m_colors = colors; updateTriangles(); } void PieChartNode::setSections(const QVector §ions) { m_sections = sections; updateTriangles(); } void PieChartNode::setBackgroundColor(const QColor &color) { if (color == m_backgroundColor) return; m_backgroundColor = color; m_material->setBackgroundColor(color); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::setFromAngle(qreal angle) { if (qFuzzyCompare(angle, m_fromAngle)) { return; } m_fromAngle = angle; + m_material->setFromAngle(degToRad(angle)); updateTriangles(); } void PieChartNode::setToAngle(qreal angle) { if (qFuzzyCompare(angle, m_fromAngle)) { return; } m_toAngle = angle; + m_material->setToAngle(degToRad(angle)); updateTriangles(); } void PieChartNode::setSmoothEnds(bool smooth) { if (smooth == m_smoothEnds) { return; } m_smoothEnds = smooth; m_material->setSmoothEnds(smooth); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::updateTriangles() { if (m_sections.isEmpty() || m_sections.size() != m_colors.size()) { return; } qreal startAngle = degToRad(m_fromAngle); qreal totalAngle = degToRad(m_toAngle - m_fromAngle); QVector segments; QVector colors; for (int i = 0; i < m_sections.size(); ++i) { QVector2D segment{float(startAngle), float(startAngle + m_sections.at(i) * totalAngle)}; segments << segment; startAngle = segment.y(); colors << colorToVec4(m_colors.at(i)); } if (m_sections.size() == 1 && qFuzzyCompare(m_sections.at(0), 0.0)) { segments.clear(); } m_material->setSegments(segments); m_material->setColors(colors); markDirty(QSGNode::DirtyMaterial); } diff --git a/src/shaders/piechart.frag b/src/shaders/piechart.frag index a969183..723c952 100644 --- a/src/shaders/piechart.frag +++ b/src/shaders/piechart.frag @@ -1,61 +1,63 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ // This requires "sdf.frag" which is included through SDFShader. // The maximum number of segments we can support for a single pie. // This is based on OpenGL's MAX_FRAGMENT_UNIFORM_COMPONENTS. // MAX_FRAGMENT_UNIFORM_COMPONENTS is required to be at least 1024. // Assuming a segment of size 1, each segment needs // 2 (size of a vec2) * 2 (number of points) + 4 (size of vec4) + 1 (segment size) // components. We also need to leave some room for the other uniforms. #define MAX_SEGMENTS 100 uniform lowp float opacity; uniform lowp float innerRadius; uniform lowp float outerRadius; uniform lowp vec4 backgroundColor; uniform bool smoothEnds; +uniform lowp float fromAngle; +uniform lowp float toAngle; uniform lowp vec2 segments[MAX_SEGMENTS]; uniform lowp vec4 colors[MAX_SEGMENTS]; uniform int segmentCount; #ifdef LEGACY_STAGE_INOUT varying lowp vec2 uv; #else in lowp vec2 uv; out lowp vec4 out_color; #endif const lowp vec2 origin = vec2(0.0, 0.0); const lowp float lineSmooth = 0.001; void main() { lowp vec4 color = vec4(0.0); lowp float thickness = (outerRadius - innerRadius) / 2.0; lowp float rounding = smoothEnds ? thickness : 0.0; // Background first, slightly smaller than the actual pie to avoid antialiasing artifacts. lowp float torus = sdf_annular(sdf_torus_segment(uv, innerRadius + thickness), thickness - 0.001); color = sdf_render(torus, color, backgroundColor); for (int i = 0; i < segmentCount && i < MAX_SEGMENTS; ++i) { lowp vec2 segment = segments[i]; lowp float segment_sdf = sdf_torus_segment(uv, segment.x + rounding, segment.y - rounding, innerRadius + rounding, outerRadius - rounding) - rounding; color = sdf_render(segment_sdf, color, colors[i]); } #ifdef LEGACY_STAGE_INOUT gl_FragColor = color * opacity; #else out_color = color * opacity; #endif }