File indexing completed on 2024-04-21 15:02:55

0001 /*
0002  * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005  */
0006 
0007 #include "BarChart.h"
0008 
0009 #include <QDebug>
0010 #include <QSGNode>
0011 
0012 #include "RangeGroup.h"
0013 #include "datasource/ChartDataSource.h"
0014 #include "scenegraph/BarChartNode.h"
0015 
0016 BarChart::BarChart(QQuickItem *parent)
0017     : XYChart(parent)
0018 {
0019 }
0020 
0021 qreal BarChart::spacing() const
0022 {
0023     return m_spacing;
0024 }
0025 
0026 void BarChart::setSpacing(qreal newSpacing)
0027 {
0028     if (newSpacing == m_spacing) {
0029         return;
0030     }
0031 
0032     m_spacing = newSpacing;
0033     update();
0034     Q_EMIT spacingChanged();
0035 }
0036 
0037 qreal BarChart::barWidth() const
0038 {
0039     return m_barWidth;
0040 }
0041 
0042 void BarChart::setBarWidth(qreal newBarWidth)
0043 {
0044     if (newBarWidth == m_barWidth) {
0045         return;
0046     }
0047 
0048     m_barWidth = newBarWidth;
0049     update();
0050     Q_EMIT barWidthChanged();
0051 }
0052 
0053 qreal BarChart::radius() const
0054 {
0055     return m_radius;
0056 }
0057 
0058 void BarChart::setRadius(qreal newRadius)
0059 {
0060     if (newRadius == m_radius) {
0061         return;
0062     }
0063 
0064     m_radius = newRadius;
0065     update();
0066     Q_EMIT radiusChanged();
0067 }
0068 
0069 BarChart::Orientation BarChart::orientation() const
0070 {
0071     return m_orientation;
0072 }
0073 
0074 void BarChart::setOrientation(BarChart::Orientation newOrientation)
0075 {
0076     if (newOrientation == m_orientation) {
0077         return;
0078     }
0079 
0080     m_orientation = newOrientation;
0081     m_orientationChanged = true;
0082     update();
0083     Q_EMIT orientationChanged();
0084 }
0085 
0086 QColor BarChart::backgroundColor() const
0087 {
0088     return m_backgroundColor;
0089 }
0090 
0091 void BarChart::setBackgroundColor(const QColor &newBackgroundColor)
0092 {
0093     if (newBackgroundColor == m_backgroundColor) {
0094         return;
0095     }
0096 
0097     m_backgroundColor = newBackgroundColor;
0098     update();
0099     Q_EMIT backgroundColorChanged();
0100 }
0101 
0102 QSGNode *BarChart::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
0103 {
0104     BarChartNode *barNode = nullptr;
0105 
0106     if (m_orientationChanged) {
0107         delete node;
0108         node = nullptr;
0109         m_orientationChanged = false;
0110     }
0111 
0112     if (!node) {
0113         barNode = new BarChartNode{};
0114         if (m_orientation == VerticalOrientation) {
0115             node = barNode;
0116         } else {
0117             auto transformNode = new QSGTransformNode{};
0118             transformNode->appendChildNode(barNode);
0119             QMatrix4x4 matrix;
0120             matrix.translate(width(), 0.0);
0121             matrix.rotate(90.0, 0.0, 0.0, 1.0);
0122             transformNode->setMatrix(matrix);
0123             node = transformNode;
0124         }
0125     } else {
0126         if (m_orientation == VerticalOrientation) {
0127             barNode = static_cast<BarChartNode *>(node);
0128         } else {
0129             barNode = static_cast<BarChartNode *>(node->childAtIndex(0));
0130         }
0131     }
0132 
0133     if (m_orientation == VerticalOrientation) {
0134         barNode->setRect(boundingRect());
0135     } else {
0136         QMatrix4x4 matrix;
0137         matrix.translate(width(), 0.0);
0138         matrix.rotate(90.0, 0.0, 0.0, 1.0);
0139         static_cast<QSGTransformNode *>(node)->setMatrix(matrix);
0140         barNode->setRect(QRectF{boundingRect().topLeft(), QSizeF{height(), width()}});
0141     }
0142     barNode->setBars(calculateBars());
0143     barNode->setRadius(m_radius);
0144     barNode->setBackgroundColor(m_backgroundColor);
0145 
0146     barNode->update();
0147 
0148     return node;
0149 }
0150 
0151 void BarChart::onDataChanged()
0152 {
0153     if (valueSources().size() == 0 || !colorSource()) {
0154         return;
0155     }
0156 
0157     m_barDataItems.clear();
0158 
0159     updateComputedRange();
0160 
0161     const auto range = computedRange();
0162     const auto sources = valueSources();
0163     auto colors = colorSource();
0164     auto indexMode = indexingMode();
0165     auto colorIndex = 0;
0166 
0167     m_barDataItems.fill(QVector<BarData>{}, range.distanceX);
0168 
0169     auto generator = [&, this, i = range.startX]() mutable -> QVector<BarData> {
0170         QVector<BarData> colorInfos;
0171 
0172         for (int j = 0; j < sources.count(); ++j) {
0173             auto value = (sources.at(j)->item(i).toReal() - range.startY) / range.distanceY;
0174             colorInfos << BarData{value, colors->item(colorIndex).value<QColor>()};
0175 
0176             if (indexMode != Chart::IndexSourceValues) {
0177                 colorIndex++;
0178             }
0179         }
0180 
0181         if (stacked()) {
0182             auto previous = 0.0;
0183             for (auto &[colorVal, _] : colorInfos) {
0184                 colorVal += previous;
0185                 previous = colorVal;
0186             }
0187         }
0188 
0189         if (indexMode == Chart::IndexSourceValues) {
0190             colorIndex++;
0191         } else if (indexMode == Chart::IndexEachSource) {
0192             colorIndex = 0;
0193         }
0194 
0195         i++;
0196         return colorInfos;
0197     };
0198 
0199     if (direction() == Direction::ZeroAtStart) {
0200         std::generate_n(m_barDataItems.begin(), range.distanceX, generator);
0201     } else {
0202         std::generate_n(m_barDataItems.rbegin(), range.distanceX, generator);
0203     }
0204 
0205     update();
0206 }
0207 
0208 QVector<Bar> BarChart::calculateBars()
0209 {
0210     QVector<Bar> result;
0211 
0212     // TODO: Find some way to clean this up and simplify it, since this is pretty ugly.
0213 
0214     auto targetWidth = m_orientation == VerticalOrientation ? width() : height();
0215 
0216     float w = m_barWidth;
0217     if (w < 0.0) {
0218         const auto totalItemCount = stacked() ? m_barDataItems.size() : m_barDataItems.size() * valueSources().count();
0219 
0220         w = targetWidth / totalItemCount - m_spacing;
0221 
0222         auto x = float(m_spacing / 2);
0223         const auto itemSpacing = w + m_spacing;
0224 
0225         for (const auto &items : std::as_const(m_barDataItems)) {
0226             result.reserve(result.size() + items.size());
0227             if (stacked()) {
0228                 std::transform(items.crbegin(), items.crend(), std::back_inserter(result), [x, w](const BarData &entry) {
0229                     return Bar{x, w, float(entry.value), entry.color};
0230                 });
0231                 x += itemSpacing;
0232             } else {
0233                 std::transform(items.cbegin(), items.cend(), std::back_inserter(result), [&x, itemSpacing, w](const BarData &entry) {
0234                     Bar bar{x, w, float(entry.value), entry.color};
0235                     x += itemSpacing;
0236                     return bar;
0237                 });
0238             }
0239         }
0240     } else {
0241         const auto itemSpacing = targetWidth / m_barDataItems.size();
0242         if (stacked()) {
0243             auto x = float(itemSpacing / 2 - m_barWidth / 2);
0244 
0245             for (const auto &items : std::as_const(m_barDataItems)) {
0246                 result.reserve(result.size() + items.size());
0247                 std::transform(items.crbegin(), items.crend(), std::back_inserter(result), [x, w](const BarData &entry) {
0248                     return Bar{x, w, float(entry.value), entry.color};
0249                 });
0250 
0251                 x += itemSpacing;
0252             }
0253         } else {
0254             const auto totalWidth = m_barWidth * valueSources().count() + m_spacing * (valueSources().count() - 1);
0255 
0256             auto x = float(itemSpacing / 2 - totalWidth / 2);
0257 
0258             for (const auto &items : std::as_const(m_barDataItems)) {
0259                 result.reserve(result.size() + items.size());
0260                 for (int i = 0; i < items.count(); ++i) {
0261                     auto entry = items.at(i);
0262                     result << Bar{float(x + i * (m_barWidth + m_spacing)), w, float(entry.value), entry.color};
0263                 }
0264                 x += itemSpacing;
0265             }
0266         }
0267     }
0268 
0269     return result;
0270 }
0271 
0272 #include "moc_BarChart.cpp"