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 }