Warning, file /libraries/kqtquickcharts/src/xychartcore.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * Copyright 2015 Jesper Hellesø Hansen <jesperhh@gmail.com> 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Lesser General Public 0006 * License as published by the Free Software Foundation; either 0007 * version 2.1 of the License, or (at your option) version 3, or any 0008 * later version accepted by the membership of KDE e.V. (or its 0009 * successor approved by the membership of KDE e.V.), which shall 0010 * act as a proxy defined in Section 6 of version 3 of the license. 0011 * 0012 * This library is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 * Lesser General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU Lesser General Public 0018 */ 0019 0020 #include "xychartcore.h" 0021 #include <QPainter> 0022 #include <QAbstractTableModel> 0023 #include <assert.h> 0024 #include <cmath> 0025 0026 const qreal divisors[] = { 1, 2, 5}; 0027 const int ndivisors = sizeof(divisors) / sizeof(divisors[0]); 0028 0029 XYChartCore::XYChartCore(QQuickItem* parent) : 0030 ChartCore(parent), 0031 m_pointRadius(5.0), 0032 m_lineWidth(2.0), 0033 m_xAxis(nullptr), 0034 m_yAxis(nullptr), 0035 m_labelFont(), 0036 m_labelFontMetrics(m_labelFont), 0037 m_minorTickSize(2), 0038 m_majorTickSize(4), 0039 m_margin(m_majorTickSize + 4), 0040 m_gridLines(false), 0041 m_axisLabelCountGoal(6) 0042 { 0043 m_labelFont.setPointSize(18); 0044 m_labelFontMetrics = QFontMetrics(m_labelFont); 0045 connect(this, &QQuickItem::widthChanged, this, &XYChartCore::triggerUpdate); 0046 connect(this, &QQuickItem::heightChanged, this, &XYChartCore::triggerUpdate); 0047 connect(this, &XYChartCore::fontChanged, this, &XYChartCore::triggerUpdate); 0048 connect(this, &XYChartCore::textColorChanged, this, &XYChartCore::triggerUpdate); 0049 connect(this, &XYChartCore::pointRadiusChanged, this, &XYChartCore::triggerUpdate); 0050 connect(this, &XYChartCore::lineWidthChanged, this, &XYChartCore::triggerUpdate); 0051 connect(this, &XYChartCore::xAxisChanged, this, &XYChartCore::triggerUpdate); 0052 connect(this, &XYChartCore::yAxisChanged, this, &XYChartCore::triggerUpdate); 0053 connect(this, &XYChartCore::gridLinesChanged, this, &XYChartCore::triggerUpdate); 0054 connect(this, &XYChartCore::axisLabelCountGoalChanged, this, &XYChartCore::triggerUpdate); 0055 connect(this, &ChartCore::updated, this, &XYChartCore::updateAxis); 0056 } 0057 0058 qreal XYChartCore::pointRadius() const 0059 { 0060 return m_pointRadius; 0061 } 0062 0063 void XYChartCore::setPointRadius(qreal pointRadius) 0064 { 0065 if (pointRadius != m_pointRadius) 0066 { 0067 m_pointRadius = pointRadius; 0068 emit pointRadiusChanged(); 0069 } 0070 } 0071 0072 qreal XYChartCore::lineWidth() const 0073 { 0074 return m_lineWidth; 0075 } 0076 0077 void XYChartCore::setLineWidth(qreal lineWidth) 0078 { 0079 if (lineWidth != m_lineWidth) 0080 { 0081 m_lineWidth = lineWidth; 0082 emit lineWidthChanged(); 0083 } 0084 } 0085 0086 void XYChartCore::paint(QPainter* painter) 0087 { 0088 painter->setRenderHint(QPainter::Antialiasing, true); 0089 painter->setFont(m_labelFont); 0090 QPen pen(m_textColor); 0091 pen.setWidthF(m_lineWidth); 0092 painter->setPen(pen); 0093 0094 if (m_gridLines) 0095 paintGrid(painter); 0096 0097 paintAxis(painter); 0098 paintTicks(painter); 0099 paintAxisLabels(painter); 0100 paintDimensionLabels(painter); 0101 } 0102 0103 void XYChartCore::paintAxis(QPainter* painter) 0104 { 0105 const qreal minKey = xAxis()->minimumValue(); 0106 const qreal maxKey = xAxis()->maximumValue(); 0107 const qreal minValue = yAxis()->minimumValue(); 0108 const qreal maxValue = yAxis()->maximumValue(); 0109 0110 const QPointF origo = translatePoint(QPointF(0.0, 0.0)); 0111 const QPointF p1 = translatePoint(QPointF(minKey, 0.0)); 0112 const QPointF p2 = translatePoint(QPointF(maxKey, 0.0)); 0113 const QPointF p3 = translatePoint(QPointF(0.0, minValue)); 0114 const QPointF p4 = translatePoint(QPointF(0.0, maxValue)); 0115 0116 if (origo != p1) 0117 painter->drawLine(origo, p1); 0118 0119 if (origo != p2) 0120 painter->drawLine(origo, p2); 0121 0122 if (origo != p3) 0123 painter->drawLine(origo, p3); 0124 0125 if (origo != p4) 0126 painter->drawLine(origo, p4); 0127 } 0128 0129 void XYChartCore::paintGrid(QPainter* painter) 0130 { 0131 foreach(qreal label, m_xAxisLabels) 0132 { 0133 const qreal x = translatePoint(QPointF(label, 0.0)).x(); 0134 painter->drawLine(x, m_lowerLeftCorner.y(), x, m_lowerLeftCorner.y() - m_graphHeight); 0135 } 0136 0137 foreach(qreal label, m_yAxisLabels) 0138 { 0139 const qreal y = translatePoint(QPointF(0.0, label)).y(); 0140 painter->drawLine(m_lowerLeftCorner.x(), y, m_lowerLeftCorner.x() + m_graphWidth, y); 0141 } 0142 } 0143 0144 void XYChartCore::paintTicks(QPainter* painter) 0145 { 0146 int tick = 2, tock = 4; 0147 0148 qreal label; 0149 QPointF point; 0150 for (int i = 0; i < m_xAxisLabels.size(); i++) 0151 { 0152 label = m_xAxisLabels[i]; 0153 point = translatePoint(QPointF(label, 0.0)); 0154 painter->drawLine(point.x(), point.y() - tock, point.x(), point.y() + tock); 0155 0156 if (i < m_xAxisLabels.size() - 1) 0157 { 0158 label = (m_xAxisLabels[i] + m_xAxisLabels[i + 1]) / 2.0; 0159 point = translatePoint(QPointF(label, 0.0)); 0160 painter->drawLine(point.x(), point.y() - tick, point.x(), point.y() + tick); 0161 } 0162 } 0163 0164 for (int i = 0; i < m_yAxisLabels.size(); i++) 0165 { 0166 label = m_yAxisLabels[i]; 0167 point = translatePoint(QPointF(0.0, label)); 0168 painter->drawLine(point.x() - tock, point.y(), point.x() + tock, point.y()); 0169 0170 if (i < m_yAxisLabels.size() - 1) 0171 { 0172 label = (m_yAxisLabels[i] + m_yAxisLabels[i + 1]) / 2.0; 0173 point = translatePoint(QPointF(0.0, label)); 0174 painter->drawLine(point.x() - tick, point.y(), point.x() + tick, point.y()); 0175 } 0176 } 0177 } 0178 0179 void XYChartCore::paintAxisLabels(QPainter* painter) 0180 { 0181 int labelHeight = painter->fontMetrics().tightBoundingRect(QStringLiteral("0123456789")).height(); 0182 foreach(qreal label, m_xAxisLabels) 0183 { 0184 QString strLabel = formatLabel(label, xAxis()); 0185 int labelWidth = painter->fontMetrics().boundingRect(strLabel).width(); 0186 const QPointF point = translatePoint(QPointF(label, 0.0)); 0187 painter->drawText(point.x() - labelWidth / 2, point.y() + labelHeight + m_margin, strLabel); 0188 } 0189 0190 foreach(qreal label, m_yAxisLabels) 0191 { 0192 QString strLabel = formatLabel(label, yAxis()); 0193 int labelWidth = painter->fontMetrics().boundingRect(strLabel).width(); 0194 const QPointF point = translatePoint(QPointF(0.0, label)); 0195 painter->drawText(point.x() - labelWidth - m_margin, point.y() + (labelHeight / 2), strLabel); 0196 } 0197 } 0198 0199 void XYChartCore::paintDimensionLabels(QPainter* painter) 0200 { 0201 QList<QPair<qreal, QString>> labels; 0202 const int row = model()->rowCount() - 1; 0203 if (row == -1) 0204 return; 0205 0206 const qreal maxKey = model()->data(model()->index(row, xAxis()->dataColumn())).toReal(); 0207 const qreal x = translatePoint(QPointF(maxKey, 0.0)).x(); 0208 foreach(Dimension* dimension, dimensionsList()) 0209 { 0210 const QString label = dimension->label(); 0211 if (label.isEmpty()) 0212 continue; 0213 0214 const int column = dimension->dataColumn(); 0215 const qreal value = model()->data(model()->index(row, column)).toReal(); 0216 0217 // We do not label dimensions that miss values for the last point 0218 // Use point text for labels inside the chart 0219 if (qIsNaN(value)) 0220 continue; 0221 0222 const qreal y = translatePoint(QPointF(maxKey, value)).y(); 0223 labels.append(QPair<qreal, QString>(y, label)); 0224 } 0225 0226 if (labels.empty()) 0227 return; 0228 0229 for (const auto& iter : labels) 0230 { 0231 int labelheight = m_labelFontMetrics.tightBoundingRect(iter.second).height(); 0232 painter->drawText(x + m_margin, iter.first + (labelheight / 2.0), iter.second); 0233 } 0234 } 0235 0236 void XYChartCore::updateAxis() 0237 { 0238 if (!xAxis() || !yAxis()) 0239 return; 0240 0241 const qreal minKey = xAxis()->minimumValue(); 0242 const qreal maxKey = xAxis()->maximumValue(); 0243 m_xAxisLabels = generateAxisLabels(minKey, maxKey); 0244 0245 const qreal minValue = yAxis()->minimumValue(); 0246 const qreal maxValue = yAxis()->maximumValue(); 0247 m_yAxisLabels = generateAxisLabels(minValue, maxValue); 0248 0249 const int minValueStringLength = formatLabel(minValue, yAxis()).length(); 0250 const int maxValueStringLength = formatLabel(maxValue, yAxis()).length(); 0251 const int minKeyStringLength = formatLabel(minKey, xAxis()).length(); 0252 const int maxKeyStringLength = formatLabel(maxKey, xAxis()).length(); 0253 0254 int maxYLabelWidth = m_labelFontMetrics.boundingRect(QStringLiteral("W")).width() * std::max(minValueStringLength, maxValueStringLength); 0255 int maxXLabelWidth = m_labelFontMetrics.boundingRect(QStringLiteral("W")).width() * std::max(minKeyStringLength, maxKeyStringLength); 0256 0257 m_lowerLeftCorner.setX(maxYLabelWidth + m_margin); 0258 m_lowerLeftCorner.setY(height() - m_labelFontMetrics.height() - m_margin); 0259 m_graphWidth = width() - m_lowerLeftCorner.x() - (maxXLabelWidth / 2); 0260 m_graphHeight = m_lowerLeftCorner.y() - (m_labelFontMetrics.height() / 2); 0261 } 0262 0263 QString XYChartCore::formatLabel(const qreal label, const Dimension* dimension) const 0264 { 0265 QLocale locale; 0266 return locale.toString(label, 'f', dimension->precision()); 0267 } 0268 0269 QPointF XYChartCore::translatePoint(QPointF point) 0270 { 0271 const qreal minKey = xAxis()->minimumValue(); 0272 const qreal maxKey = xAxis()->maximumValue(); 0273 const qreal minVal = yAxis()->minimumValue(); 0274 const qreal maxVal = yAxis()->maximumValue(); 0275 0276 const qreal x = m_lowerLeftCorner.x() + (m_graphWidth * (point.x() - minKey) / (maxKey - minKey)); 0277 const qreal y = m_lowerLeftCorner.y() - (m_graphHeight * (point.y() - minVal) / (maxVal - minVal)); 0278 return QPointF(x, y); 0279 } 0280 0281 0282 QList<qreal> XYChartCore::generateAxisLabels(const qreal minValue, const qreal maxValue) 0283 { 0284 // Distance between labels with preferred number of labels 0285 qreal div = fabs(maxValue - minValue) / m_axisLabelCountGoal; 0286 // Find power of 10 to scale preferred increments to 0287 qreal scale = std::pow(10, floor(std::log10(div))); 0288 0289 // Find closest increment that has at maximum div distance between labels 0290 qreal increment = divisors[0] * scale; 0291 for (int i = 0; i < ndivisors; i++) 0292 { 0293 if ((divisors[i] * scale) > div) 0294 break; 0295 0296 increment = divisors[i] * scale; 0297 } 0298 0299 increment *= (maxValue - minValue) < 0.0 ? -1.0 : 1.0; 0300 0301 qreal label = minValue; 0302 QList<qreal> result; 0303 while (label <= maxValue) 0304 { 0305 result << label; 0306 label += increment; 0307 } 0308 0309 return result; 0310 } 0311 0312 Dimension* XYChartCore::xAxis() const 0313 { 0314 return m_xAxis; 0315 } 0316 0317 void XYChartCore::setXAxis(Dimension* xAxis) 0318 { 0319 if (m_xAxis != xAxis) 0320 { 0321 m_xAxis = xAxis; 0322 xAxisChanged(); 0323 } 0324 } 0325 0326 QColor XYChartCore::textColor() const 0327 { 0328 return m_textColor; 0329 } 0330 0331 void XYChartCore::setTextColor(QColor color) 0332 { 0333 if (m_textColor != color) 0334 { 0335 m_textColor = color; 0336 emit textColorChanged(); 0337 } 0338 } 0339 0340 QFont XYChartCore::font() const 0341 { 0342 return m_labelFont; 0343 } 0344 0345 void XYChartCore::setFont(const QFont &font) 0346 { 0347 if (font != m_labelFont) 0348 { 0349 m_labelFont = font; 0350 emit fontChanged(); 0351 } 0352 } 0353 0354 bool XYChartCore::gridLines() const 0355 { 0356 return m_gridLines; 0357 } 0358 0359 void XYChartCore::setGridLines(bool gridLines) 0360 { 0361 if (m_gridLines != gridLines) 0362 { 0363 m_gridLines = gridLines; 0364 emit gridLinesChanged(); 0365 } 0366 } 0367 0368 Dimension* XYChartCore::yAxis() const 0369 { 0370 return m_yAxis; 0371 } 0372 0373 void XYChartCore::setYAxis(Dimension* yAxis) 0374 { 0375 if (m_yAxis != yAxis) 0376 { 0377 m_yAxis = yAxis; 0378 yAxisChanged(); 0379 } 0380 } 0381 0382 unsigned int XYChartCore::axisLabelCountGoal() const 0383 { 0384 return m_axisLabelCountGoal; 0385 } 0386 0387 void XYChartCore::setAxisLabelCountGoal(unsigned int axisLabelCountGoal) 0388 { 0389 if (m_axisLabelCountGoal != axisLabelCountGoal) 0390 { 0391 m_axisLabelCountGoal = axisLabelCountGoal; 0392 axisLabelCountGoalChanged(); 0393 } 0394 }