File indexing completed on 2024-10-06 03:40:52
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(QList<BarData>{}, range.distanceX); 0168 0169 auto generator = [&, this, i = range.startX]() mutable -> QList<BarData> { 0170 QList<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 QList<Bar> BarChart::calculateBars() 0209 { 0210 QList<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"