From fdee26a2b8fbb99912b58bebf072bfc2b01b1868 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Tue, 25 Jan 2022 19:46:14 +0100 Subject: [PATCH] Add grid chart --- limereport/items/charts/lrgridlineschart.cpp | 110 +++++++++++++++++++ limereport/items/charts/lrgridlineschart.h | 17 +++ limereport/items/lrchartitem.cpp | 87 ++++++++++++++- limereport/items/lrchartitem.h | 6 +- limereport/limereport.pri | 2 + 5 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 limereport/items/charts/lrgridlineschart.cpp create mode 100644 limereport/items/charts/lrgridlineschart.h diff --git a/limereport/items/charts/lrgridlineschart.cpp b/limereport/items/charts/lrgridlineschart.cpp new file mode 100644 index 0000000..f96ab1b --- /dev/null +++ b/limereport/items/charts/lrgridlineschart.cpp @@ -0,0 +1,110 @@ +#include "lrgridlineschart.h" + +namespace LimeReport { +void GridLinesChart::paintChart(QPainter *painter, QRectF chartRect) +{ + updateMinAndMaxValues(); + + const qreal valuesHMargin = this->valuesHMargin(painter); + const qreal valuesVMargin = this->valuesVMargin(painter); + + const qreal hPadding = this->hPadding(chartRect); + const qreal vPadding = this->vPadding(chartRect); + + QRectF calcRect = horizontalLabelsRect( + painter, + chartRect.adjusted( + hPadding * 2 + valuesHMargin, + chartRect.height() - (painter->fontMetrics().height() + vPadding*2), + -(hPadding * 2), + -vPadding + ) + ); + const qreal barsShift = calcRect.height(); + const qreal topOffset = painter->fontMetrics().height() * (m_chartItem->horizontalAxisOnTop() ? 1 : -1); + const QRectF gridRect = chartRect.adjusted( + hPadding, + vPadding + valuesVMargin + topOffset, + -hPadding * 3, + -(vPadding + barsShift) + ); + + paintGrid(painter, gridRect); + + paintSerialLines( + painter, + gridRect.adjusted(hPadding + valuesHMargin, 0, 0, 0) + ); + paintHorizontalLabels(painter, calcRect); +} + +void GridLinesChart::paintSerialLines(QPainter* painter, QRectF barsRect) +{ + if (valuesCount() == 0) return; + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing,true); + + const AxisData &yAxisData = this->yAxisData(); + const qreal delta = yAxisData.delta(); + + if (m_chartItem->itemMode() == DesignMode){ + const qreal hStep = barsRect.width() / valuesCount(); + const qreal vStep = barsRect.height() / delta; + const qreal topShift = (delta - (maxValue() - minValue())) * vStep + barsRect.top(); + drawDesignMode(painter, hStep, vStep, topShift, barsRect); + painter->restore(); + return; + } + + const AxisData &xAxisData = this->xAxisData(); + const qreal hStep = barsRect.width() / (xAxisData.rangeMax() - xAxisData.rangeMin()); + + qreal leftMargin = 0; + const qreal topMargin = barsRect.top(); + + for (SeriesItem* series : m_chartItem->series()) { + QPen pen(series->color()); + pen.setWidth(m_chartItem->seriesLineWidth()); + painter->setPen(pen); + + const QList &xAxisValues = series->data()->xAxisValues(); + const QList &values = series->data()->values(); + const int xAxisValuesSize = xAxisValues.size(); + qreal lastXPos = 0; + qreal lastYPos = 0; + if (!values.isEmpty()) { + // Calculate first point position on plot before loop + lastYPos = calculatePos(yAxisData, values.first(), barsRect.height()); + } + if (xAxisValues.isEmpty()) { + leftMargin = barsRect.left(); + } else { + leftMargin = barsRect.left(); + lastXPos = calculatePos(xAxisData, xAxisValues.first(), barsRect.width()); + } + for (int i = 0; i < values.count() - 1; ++i ) { + const qreal startY = lastYPos; + const qreal endY = calculatePos(yAxisData, values.at(i+1), barsRect.height()); + // Record last used Y position to only calculate new one + lastYPos = endY; + + qreal startX = lastXPos; + qreal endX = 0; + if (i + 1 < xAxisValuesSize) { + endX = calculatePos(xAxisData, xAxisValues.at(i+1), barsRect.width()); + } else { + endX = startX + hStep; + } + // Record last used X position to only calculate new one + lastXPos = endX; + + QPoint startPoint = QPoint(startX + leftMargin, startY + topMargin); + QPoint endPoint = QPoint(endX + leftMargin, endY + topMargin); + drawSegment(painter, startPoint, endPoint, series->color()); + } + } + + painter->restore(); +} +} diff --git a/limereport/items/charts/lrgridlineschart.h b/limereport/items/charts/lrgridlineschart.h new file mode 100644 index 0000000..a95a07b --- /dev/null +++ b/limereport/items/charts/lrgridlineschart.h @@ -0,0 +1,17 @@ +#ifndef GRIDLINESCHART_H +#define GRIDLINESCHART_H + +#include "lrlineschart.h" + +namespace LimeReport { +class GridLinesChart : public LinesChart{ +public: + GridLinesChart(ChartItem* chartItem):LinesChart(chartItem){} + void paintChart(QPainter *painter, QRectF chartRect); + +private: + void paintSerialLines(QPainter *painter, QRectF barsRect); +}; +} + +#endif // GRIDLINESCHART_H diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index 0130c67..defaea9 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -13,6 +13,7 @@ #include "charts/lrverticalbarchart.h" #include "charts/lrhorizontalbarchart.h" #include "charts/lrlineschart.h" +#include "charts/lrgridlineschart.h" namespace{ @@ -341,6 +342,10 @@ void ChartItem::setChartType(const ChartType &chartType) break; case Lines: m_chart = new LinesChart(this); + break; + case GridLines: + m_chart = new GridLinesChart(this); + break; } notify("chartType",oldValue,m_chartType); update(); @@ -612,8 +617,11 @@ void AbstractSeriesChart::updateMinAndMaxValues() maxYValue = std::max(maxYValue, value); } if (series->data()->xAxisValues().isEmpty()) { + // Grid plot starts from 0 on x axis so x range must be decresed by 1 + const bool startingFromZero = m_chartItem->chartType() == ChartItem::GridLines; + const qreal valuesCount = this->valuesCount() - (startingFromZero ? 1 : 0); minXValue = std::min(0.0, minXValue); - maxXValue = std::max((qreal)valuesCount(), maxXValue); + maxXValue = std::max(valuesCount, maxXValue); } else { for (qreal value : series->data()->xAxisValues()){ minXValue = std::min(value, minXValue); @@ -785,7 +793,7 @@ void AbstractSeriesChart::paintVerticalGrid(QPainter *painter, QRectF gridRect) const qreal y = vStep * i; painter->drawText(QRectF(gridRect.bottomLeft()-QPointF(textPositionOffset,y+halfFontHeight), QSizeF(valuesHMargin,fontHeight)), - verticalLabel(i, yAxisData.step(), yAxisData.rangeMin()), + axisLabel(i, yAxisData), verticalTextOption); painter->drawLine(gridRect.bottomLeft()-QPointF(-valuesHMargin,y), gridRect.bottomRight()-QPointF(0,y)); @@ -794,6 +802,65 @@ void AbstractSeriesChart::paintVerticalGrid(QPainter *painter, QRectF gridRect) painter->setRenderHint(QPainter::Antialiasing,true); } +void AbstractSeriesChart::paintGrid(QPainter *painter, QRectF gridRect) +{ + painter->save(); + + const AxisData &yAxisData = this->yAxisData(); + const AxisData &xAxisData = this->xAxisData(); + + painter->setRenderHint(QPainter::Antialiasing,false); + + const int xAxisSegmentCount = xAxisData.segmentCount(); + const int xAxisLineCount = xAxisSegmentCount + 1; + const int yAxisSegmentCount = yAxisData.segmentCount(); + const int yAxisLineCount = yAxisSegmentCount + 1; + + const qreal gridOffset = hPadding(gridRect); + const int fontHeight = painter->fontMetrics().height(); + const int halfFontHeight = fontHeight / 2; + const qreal valuesHMargin = this->valuesHMargin(painter); + const qreal vStep = gridRect.height() / yAxisSegmentCount; + const qreal hStep = (gridRect.width() - valuesHMargin - gridOffset) / xAxisSegmentCount; + + // Vertical axis lines + const QTextOption verticalTextOption(Qt::AlignRight); + for (int i = 0 ; i < yAxisLineCount ; i++ ) { + const qreal y = vStep * i; + painter->drawText(QRectF(gridRect.bottomLeft()-QPointF(halfFontHeight, y + halfFontHeight), + QSizeF(valuesHMargin,fontHeight)), + axisLabel(i, yAxisData), + verticalTextOption); + painter->drawLine(gridRect.bottomLeft()-QPointF(-valuesHMargin, y), + gridRect.bottomRight()-QPointF(0, y)); + } + + // Horizontal axis lines + for (int i = 0 ; i < xAxisLineCount ; i++) { + const qreal x = gridRect.left() + hStep * i + valuesHMargin + gridOffset; + const bool drawFullLine = i == 0 || i == xAxisSegmentCount; + const QString text = QString::number(xAxisData.rangeMin() + i * xAxisData.step()); + + if (m_chartItem->horizontalAxisOnTop()) { + painter->drawLine(x, gridRect.top() - gridOffset, + x, (drawFullLine ? gridRect.bottom() : gridRect.top())); + painter->drawText(QRectF(x - painter->fontMetrics().width(text) / 2, + gridRect.top() - (fontHeight + gridOffset), + hStep, fontHeight), + text); + } else { + painter->drawLine(x, gridRect.bottom() + gridOffset, + x, (drawFullLine ? gridRect.top() : gridRect.bottom())); + painter->drawText(QRectF(x - painter->fontMetrics().width(text) / 2, + gridRect.bottom() + halfFontHeight + gridOffset, + hStep, fontHeight), + text); + } + } + + painter->restore(); +} + void AbstractSeriesChart::drawSegment(QPainter *painter, QPoint startPoint, QPoint endPoint, QColor color) { int radius = m_chartItem->seriesLineWidth(); @@ -819,7 +886,7 @@ qreal AbstractSeriesChart::valuesHMargin(QPainter *painter) const int yAxisLineCount = yAxisData.segmentCount() + 1; for (int i = 0 ; i < yAxisLineCount ; i++) { - const QString label = verticalLabel(i, yAxisData.step(), yAxisData.rangeMin()); + const QString label = axisLabel(i, yAxisData); max = std::max(max, (qreal)painter->fontMetrics().boundingRect(label).width()+offset); } return max; @@ -869,14 +936,22 @@ QFont AbstractSeriesChart::adaptValuesFont(qreal width, QFont font) return tmpFont; } -QString AbstractSeriesChart::verticalLabel(int i, qreal step, qreal min) +QString AbstractSeriesChart::axisLabel(int i, const AxisData &axisData) { - qreal value = min + i * step; + const qreal min = axisData.rangeMin(); + const qreal step = axisData.step(); + qreal value = 0; + // For values below 0, axis is already reversed (value closer to 0 is higher) + if (axisData.reverseDirection() && min >= 0) { + value = min + (axisData.segmentCount() - i) * step; + } else { + value = min + i * step; + } if (std::floor(step) == step) { return QString::number(value); } // For float round numbers to small precision - return QString::number(value, 'g', 2); + return QString::number(round(value * 100.0) / 100.0); } void AbstractBarChart::paintChartLegend(QPainter *painter, QRectF legendRect) diff --git a/limereport/items/lrchartitem.h b/limereport/items/lrchartitem.h index 7a20767..1efc827 100644 --- a/limereport/items/lrchartitem.h +++ b/limereport/items/lrchartitem.h @@ -104,13 +104,14 @@ protected: virtual void paintHorizontalLabels(QPainter *painter, QRectF labelsRect); virtual void paintVerticalLabels(QPainter *painter, QRectF labelsRect); virtual void paintHorizontalGrid(QPainter *painter, QRectF gridRect); + virtual void paintGrid(QPainter *painter, QRectF gridRect); virtual void paintVerticalGrid(QPainter *painter, QRectF gridRect); virtual void drawSegment(QPainter *painter, QPoint startPoint, QPoint endPoint, QColor color); virtual qreal valuesHMargin(QPainter *painter); virtual qreal valuesVMargin(QPainter *painter); virtual QFont adaptLabelsFont(QRectF rect, QFont font); virtual QFont adaptValuesFont(qreal width, QFont font); - virtual QString verticalLabel(int i, qreal step, qreal min); + virtual QString axisLabel(int i, const AxisData &axisData); private: AxisData m_yAxisData, m_xAxisData; @@ -148,8 +149,9 @@ class ChartItem : public LimeReport::ItemDesignIntf public: enum LegendAlign{LegendAlignTop,LegendAlignCenter,LegendAlignBottom}; + enum LegendStyle{LegendPoints, LegendLines}; enum TitleAlign{TitleAlignLeft, TitleAlignCenter, TitleAlignRight}; - enum ChartType{Pie, VerticalBar, HorizontalBar, Lines}; + enum ChartType{Pie, VerticalBar, HorizontalBar, Lines, GridLines}; #if QT_VERSION >= 0x050500 Q_ENUM(LegendAlign) Q_ENUM(TitleAlign) diff --git a/limereport/limereport.pri b/limereport/limereport.pri index e6862a9..4ed78f6 100644 --- a/limereport/limereport.pri +++ b/limereport/limereport.pri @@ -49,6 +49,7 @@ SOURCES += \ $$REPORT_PATH/items/lrchartitemeditor.cpp \ $$REPORT_PATH/items/charts/lrhorizontalbarchart.cpp \ $$REPORT_PATH/items/charts/lrlineschart.cpp \ + $$REPORT_PATH/items/charts/lrgridlineschart.cpp \ $$REPORT_PATH/items/charts/lrpiechart.cpp \ $$REPORT_PATH/items/charts/lrverticalbarchart.cpp \ $$REPORT_PATH/lrbanddesignintf.cpp \ @@ -132,6 +133,7 @@ HEADERS += \ $$REPORT_PATH/items/lrchartitemeditor.h \ $$REPORT_PATH/items/charts/lrhorizontalbarchart.h \ $$REPORT_PATH/items/charts/lrlineschart.h \ + $$REPORT_PATH/items/charts/lrgridlineschart.h \ $$REPORT_PATH/items/charts/lrpiechart.h \ $$REPORT_PATH/items/charts/lrverticalbarchart.h \ $$REPORT_PATH/lrbanddesignintf.h \