0
0
mirror of https://github.com/fralx/LimeReport.git synced 2024-12-23 16:22:58 +03:00

Merge pull request #373 from emil-sawicki9/feature/drawing-chart-refactoring

Refactor axis data. Remove duplicate paint code
This commit is contained in:
Alexander Arin 2022-01-22 19:33:33 +03:00 committed by GitHub
commit fdf7807cfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 296 additions and 92 deletions

View File

@ -36,8 +36,8 @@ void HorizontalBarChart::paintHorizontalBars(QPainter *painter, QRectF barsRect)
painter->save();
painter->setRenderHint(QPainter::Antialiasing,false);
int delta = int(maxValue()-minValue());
delta = genNextValue(delta);
const AxisData &yAxisData = this->yAxisData();
const qreal delta = yAxisData.delta();
qreal vStep = (barsRect.height()-painter->fontMetrics().height()) / valuesCount() / seriesCount();
qreal hStep = (barsRect.width()-painter->fontMetrics().boundingRect(QString::number(maxValue())).width()) / delta;

View File

@ -68,35 +68,70 @@ void LinesChart::drawDesignMode(QPainter* painter, qreal hStep, qreal vStep, qre
}
}
qreal LinesChart::calculateValueYPos(qreal, qreal max, qreal value, qreal delta, qreal height)
{
return (max - value) / delta * height;
}
void LinesChart::paintSeries(QPainter *painter, SeriesItem *series, QRectF barsRect)
{
const AxisData &yAxisData = this->yAxisData();
const qreal delta = yAxisData.delta();
const qreal hStep = barsRect.width() / valuesCount();
const qreal topMargin = barsRect.top();
QPen pen(series->color());
pen.setWidth(4);
painter->setPen(pen);
const QList<qreal> &values = series->data()->values();
qreal lastYValue = 0;
qreal lastXValue = barsRect.left() + hStep/2;
if (!values.isEmpty()) {
// Calculate first point position on plot before loop
lastYValue = calculateValueYPos(yAxisData.rangeMin(), yAxisData.rangeMax(), values.first(), delta, barsRect.height());
}
for (int i = 0; i < values.count()-1; ++i ){
const qreal startY = lastYValue;
const qreal endY = calculateValueYPos(yAxisData.rangeMin(), yAxisData.rangeMax(), values.at(i+1), delta, barsRect.height());
// Record last used Y position to only calculate new one
lastYValue = endY;
const qreal startX = lastXValue;
const qreal endX = startX + hStep;
// Record last used X position to only calculate new one
lastXValue = endX;
QPoint startPoint = QPoint(startX, startY + topMargin);
QPoint endPoint = QPoint(endX, endY + topMargin);
drawSegment(painter, startPoint, endPoint, series->color());
}
}
void LinesChart::paintSerialLines(QPainter* painter, QRectF barsRect)
{
if (valuesCount() == 0) return;
painter->save();
painter->setRenderHint(QPainter::Antialiasing,true);
int delta = int(maxValue() - minValue());
delta = genNextValue(delta);
qreal vStep = barsRect.height() / delta;
qreal hStep = barsRect.width() / valuesCount();
qreal topShift = (delta - (maxValue() - minValue())) * vStep +barsRect.top();
if (m_chartItem->itemMode() != DesignMode){
foreach (SeriesItem* series, m_chartItem->series()) {
QPen pen(series->color());
pen.setWidth(4);
painter->setPen(pen);
for (int i = 0; i < series->data()->values().count()-1; ++i ){
QPoint startPoint = QPoint((i+1) * hStep + barsRect.left() - hStep/2,
(maxValue()*vStep+topShift) - series->data()->values().at(i) * vStep);
QPoint endPoint = QPoint((i+2) * hStep + barsRect.left() - hStep/2,
(maxValue() * vStep+topShift) - series->data()->values().at(i+1) * vStep);
drawSegment(painter, startPoint, endPoint, series->color());
}
}
} else {
if (m_chartItem->itemMode() == DesignMode){
const AxisData &yAxisData = this->yAxisData();
const qreal delta = yAxisData.delta();
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;
}
for (SeriesItem *series : m_chartItem->series()) {
paintSeries(painter, series, barsRect);
}
painter->restore();
}

View File

@ -8,9 +8,13 @@ class LinesChart: public AbstractBarChart{
public:
LinesChart(ChartItem* chartItem):AbstractBarChart(chartItem){}
void paintChart(QPainter *painter, QRectF chartRect);
protected:
void drawDesignMode(QPainter *painter, qreal hStep, qreal vStep, qreal topShift, QRectF barsRect);
qreal calculateValueYPos(qreal min, qreal max, qreal value, qreal delta, qreal height);
void paintSeries(QPainter *painter, SeriesItem *series, QRectF barsRect);
private:
void paintSerialLines(QPainter *painter, QRectF barsRect);
void drawDesignMode(QPainter *painter, qreal hStep, qreal vStep, qreal topShift, QRectF barsRect);
};
}

View File

@ -54,8 +54,8 @@ void VerticalBarChart::paintVerticalBars(QPainter *painter, QRectF barsRect)
if (valuesCount() == 0) return;
int delta = int(maxValue() - minValue());
delta = genNextValue(delta);
const AxisData &yAxisData = this->yAxisData();
const qreal delta = yAxisData.delta();
int barSeriesCount = 0;
foreach(SeriesItem* series, m_chartItem->series()){
@ -102,30 +102,14 @@ void VerticalBarChart::paintVerticalBars(QPainter *painter, QRectF barsRect)
void VerticalBarChart::paintSerialLines(QPainter* painter, QRectF barsRect)
{
if (valuesCount() == 0 ) return;
if (valuesCount() == 0 || m_chartItem->series().isEmpty() ) return;
painter->save();
painter->setRenderHint(QPainter::Antialiasing,true);
int delta = int(maxValue() - minValue());
delta = genNextValue(delta);
qreal vStep = barsRect.height() / delta;
qreal hStep = (barsRect.width() / valuesCount());
qreal topShift = (delta - (maxValue()-minValue())) * vStep + barsRect.top();
if (!m_chartItem->series().isEmpty()){
foreach (SeriesItem* series, m_chartItem->series()) {
if (series->preferredType() == SeriesItem::Line){
for (int i = 0; i < series->data()->values().count()-1; ++i ){
QPoint startPoint = QPoint((i+1)*hStep + barsRect.left()-hStep/2,
(maxValue() * vStep+topShift) - series->data()->values().at(i) * vStep
);
QPoint endPoint = QPoint((i+2)*hStep + barsRect.left()-hStep/2,
(maxValue() * vStep+topShift) - series->data()->values().at(i+1) * vStep
);
drawSegment(painter, startPoint, endPoint, series->color());
}
}
for (SeriesItem *series : m_chartItem->series()) {
if (series->preferredType() == SeriesItem::Line){
paintSeries(painter, series, barsRect);
}
}
painter->restore();

View File

@ -1,13 +1,13 @@
#ifndef VERTICALBARCHART_H
#define VERTICALBARCHART_H
#include "lrchartitem.h"
#include "lrlineschart.h"
namespace LimeReport{
class VerticalBarChart: public AbstractBarChart{
class VerticalBarChart: public LinesChart{
public:
VerticalBarChart(ChartItem* chartItem):AbstractBarChart(chartItem){}
VerticalBarChart(ChartItem* chartItem):LinesChart(chartItem){}
void paintChart(QPainter *painter, QRectF chartRect);
// void paintVerticalGrid(QPainter *painter, QRectF gridRect);
void paintVerticalBars(QPainter *painter, QRectF barsRect);

View File

@ -130,7 +130,7 @@ ChartItem::ChartItem(QObject *owner, QGraphicsItem *parent)
: ItemDesignIntf(xmlTag, owner, parent), m_legendBorder(true),
m_legendAlign(LegendAlignCenter), m_titleAlign(TitleAlignCenter),
m_chartType(Pie), m_labelsField(""), m_isEmpty(true),
m_showLegend(true)
m_showLegend(true), m_drawPoints(true), m_seriesLineWidth(4)
{
m_labels<<"First"<<"Second"<<"Thrid";
m_chart = new PieChart(this);
@ -433,6 +433,37 @@ bool ChartItem::isSeriesExists(const QString &name)
return false;
}
int ChartItem::seriesLineWidth() const
{
return m_seriesLineWidth;
}
bool ChartItem::drawPoints() const
{
return m_drawPoints;
}
void ChartItem::setDrawPoints(bool drawPoints)
{
if (m_drawPoints != drawPoints){
m_drawPoints = drawPoints;
notify("drawPoints", !m_drawPoints, m_drawPoints);
update();
}
m_drawPoints = drawPoints;
}
void ChartItem::setSeriesLineWidth(int newSeriesLineWidth)
{
if (m_seriesLineWidth != newSeriesLineWidth){
int oldValue = m_seriesLineWidth;
m_seriesLineWidth = newSeriesLineWidth;
notify("seriesLineWidth", oldValue, m_seriesLineWidth);
update();
}
m_seriesLineWidth = newSeriesLineWidth;
}
AbstractChart::AbstractChart(ChartItem *chartItem)
:m_chartItem(chartItem)
{
@ -492,14 +523,6 @@ void AbstractChart::prepareLegendToPaint(QRectF &legendRect, QPainter *painter)
}
}
int genNextValue(int value){
int curValue = value;
while (curValue % 4 != 0){
curValue++;
}
return curValue;
}
AbstractSeriesChart::AbstractSeriesChart(ChartItem *chartItem)
:AbstractChart(chartItem)
{
@ -516,29 +539,35 @@ AbstractSeriesChart::AbstractSeriesChart(ChartItem *chartItem)
qreal AbstractSeriesChart::maxValue()
{
return m_maxValue;
return m_yAxisData.maxValue();
}
qreal AbstractSeriesChart::minValue()
{
return m_minValue;
return m_yAxisData.minValue();
}
AxisData AbstractSeriesChart::yAxisData()
{
return m_yAxisData;
}
void AbstractSeriesChart::updateMinAndMaxValues()
{
qreal maxYValue = 0;
qreal minYValue = 0;
if (m_chartItem->itemMode() == DesignMode) {
m_maxValue = 40;
m_minValue = 0;
return;
}
m_minValue = 0;
m_maxValue = 0;
foreach(SeriesItem* series, m_chartItem->series()){
foreach(qreal value, series->data()->values()){
if (value<m_minValue) m_minValue=value;
if (value>m_maxValue) m_maxValue=value;
maxYValue = 40;
} else {
for (SeriesItem* series : m_chartItem->series()){
for (qreal value : series->data()->values()){
minYValue = std::min(minYValue, value);
maxYValue = std::max(maxYValue, value);
}
}
}
m_yAxisData = AxisData(minYValue, maxYValue);
}
qreal AbstractSeriesChart::hPadding(QRectF chartRect)
@ -655,18 +684,22 @@ void AbstractSeriesChart::paintVerticalLabels(QPainter *painter, QRectF labelsRe
void AbstractSeriesChart::paintHorizontalGrid(QPainter *painter, QRectF gridRect)
{
painter->save();
int delta = int(maxValue() - minValue());
delta = genNextValue(delta);
const AxisData &yAxisData = this->yAxisData();
const qreal delta = yAxisData.delta();
const int segmentCount = yAxisData.segmentCount();
const int lineCount = segmentCount + 1;
painter->setRenderHint(QPainter::Antialiasing,false);
qreal hStep = (gridRect.width() - painter->fontMetrics().boundingRect(QString::number(maxValue())).width()) / 4;
qreal hStep = (gridRect.width() - painter->fontMetrics().boundingRect(QString::number(maxValue())).width()) / segmentCount;
painter->setFont(adaptValuesFont(hStep-4, painter->font()));
for (int i=0;i<5;i++){
for (int i = 0 ; i < lineCount ; i++ ) {
painter->drawText(QRectF(gridRect.left() + 4 + hStep * i, gridRect.bottom() - painter->fontMetrics().height(),
hStep, painter->fontMetrics().height()),
QString::number(minValue() + i * delta / 4));
QString::number(minValue() + i * delta / segmentCount));
painter->drawLine( gridRect.left()+hStep*i, gridRect.bottom(),
gridRect.left()+hStep*i, gridRect.top());
@ -676,20 +709,28 @@ void AbstractSeriesChart::paintHorizontalGrid(QPainter *painter, QRectF gridRect
void AbstractSeriesChart::paintVerticalGrid(QPainter *painter, QRectF gridRect)
{
int delta = int(maxValue()-minValue());
delta = genNextValue(delta);
const AxisData &yAxisData = this->yAxisData();
painter->setRenderHint(QPainter::Antialiasing,false);
qreal vStep = gridRect.height() / 4;
const int segmentCount = yAxisData.segmentCount();
const int lineCount = segmentCount + 1;
qreal vStep = gridRect.height() / segmentCount;
const qreal valuesHMargin = this->valuesHMargin(painter);
const int fontHeight = painter->fontMetrics().height();
const int halfFontHeight = fontHeight / 2;
const qreal textPositionOffset = valuesHMargin * 0.2;
for (int i=0;i<5;i++){
painter->drawText(QRectF(gridRect.bottomLeft()-QPointF(0,vStep*i+painter->fontMetrics().height()),
QSizeF(valuesHMargin,painter->fontMetrics().height())),
QString::number(minValue()+i*delta/4));
painter->drawLine(gridRect.bottomLeft()-QPointF(-valuesHMargin,vStep*i),
gridRect.bottomRight()-QPointF(0,vStep*i));
const QTextOption verticalTextOption(Qt::AlignRight);
for (int i = 0 ; i < lineCount ; i++ ) {
const qreal y = vStep * i;
painter->drawText(QRectF(gridRect.bottomLeft()-QPointF(textPositionOffset,y+halfFontHeight),
QSizeF(valuesHMargin,fontHeight)),
verticalLabel(i, yAxisData.step(), yAxisData.rangeMin()),
verticalTextOption);
painter->drawLine(gridRect.bottomLeft()-QPointF(-valuesHMargin,y),
gridRect.bottomRight()-QPointF(0,y));
}
painter->setRenderHint(QPainter::Antialiasing,true);
@ -697,11 +738,14 @@ void AbstractSeriesChart::paintVerticalGrid(QPainter *painter, QRectF gridRect)
void AbstractSeriesChart::drawSegment(QPainter *painter, QPoint startPoint, QPoint endPoint, QColor color)
{
int radius = 4;
int radius = m_chartItem->seriesLineWidth();
QPen pen(color);
pen.setWidth(radius);
painter->setPen(pen);
painter->drawLine(startPoint, endPoint);
if (!m_chartItem->drawPoints()) {
return;
}
QRect startPointRect(startPoint,startPoint);
QRect endPointRect(endPoint,endPoint);
painter->setBrush(color);
@ -711,9 +755,16 @@ void AbstractSeriesChart::drawSegment(QPainter *painter, QPoint startPoint, QPoi
qreal AbstractSeriesChart::valuesHMargin(QPainter *painter)
{
int delta = int(maxValue()-minValue());
delta = genNextValue(delta);
return painter->fontMetrics().boundingRect(QString::number(delta)).width()+4;
qreal max = 0;
const AxisData &yAxisData = this->yAxisData();
const int offset = 4;
const int yAxisLineCount = yAxisData.segmentCount() + 1;
for (int i = 0 ; i < yAxisLineCount ; i++) {
const QString label = verticalLabel(i, yAxisData.step(), yAxisData.rangeMin());
max = std::max(max, (qreal)painter->fontMetrics().boundingRect(label).width()+offset);
}
return max;
}
qreal AbstractSeriesChart::valuesVMargin(QPainter *painter)
@ -760,6 +811,16 @@ QFont AbstractSeriesChart::adaptValuesFont(qreal width, QFont font)
return tmpFont;
}
QString AbstractSeriesChart::verticalLabel(int i, qreal step, qreal min)
{
qreal 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);
}
void AbstractBarChart::paintChartLegend(QPainter *painter, QRectF legendRect)
{
prepareLegendToPaint(legendRect, painter);
@ -836,5 +897,4 @@ QRectF AbstractBarChart::horizontalLabelsRect(QPainter *painter, QRectF labelsRe
else
return labelsRect.adjusted(0, (labelsRect.height() - maxWidth), 0, 0);
}
} // namespace LimeReport

View File

@ -2,6 +2,7 @@
#define LRCHARTITEM_H
#include "lritemdesignintf.h"
#include "lrglobal.h"
#include "lraxisdata.h"
#include <QtGlobal>
namespace LimeReport{
@ -85,6 +86,7 @@ public:
protected:
qreal maxValue();
qreal minValue();
AxisData yAxisData();
void updateMinAndMaxValues();
int valuesCount();
int seriesCount();
@ -102,14 +104,13 @@ protected:
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);
private:
qreal m_minValue = 0, m_maxValue = 0;
AxisData m_yAxisData;
qreal m_designValues [9];
};
int genNextValue(int value);
class AbstractBarChart: public AbstractSeriesChart{
public:
AbstractBarChart(ChartItem* chartItem):AbstractSeriesChart(chartItem){}
@ -131,6 +132,10 @@ class ChartItem : public LimeReport::ItemDesignIntf
Q_PROPERTY(ChartType chartType READ chartType WRITE setChartType)
Q_PROPERTY(QString labelsField READ labelsField WRITE setLabelsField)
Q_PROPERTY(bool showLegend READ showLegend WRITE setShowLegend)
//linesChart
Q_PROPERTY(bool drawPoints READ drawPoints WRITE setDrawPoints)
Q_PROPERTY(int seriesLineWidth READ seriesLineWidth WRITE setSeriesLineWidth)
friend class AbstractChart;
public:
@ -183,6 +188,12 @@ public:
bool showLegend() const;
void setShowLegend(bool showLegend);
bool drawPoints() const;
void setDrawPoints(bool drawPoints);
int seriesLineWidth() const;
void setSeriesLineWidth(int newSeriesLineWidth);
protected:
void paintChartTitle(QPainter* painter, QRectF titleRect);
virtual BaseDesignIntf* createSameTypeItem(QObject *owner, QGraphicsItem *parent);
@ -209,7 +220,8 @@ private:
QList<QString> m_labels;
bool m_isEmpty;
bool m_showLegend;
bool m_drawPoints;
int m_seriesLineWidth;
};
} //namespace LimeReport
#endif // LRCHARTITEM_H

View File

@ -75,6 +75,7 @@ SOURCES += \
$$REPORT_PATH/lrcolorindicator.cpp \
$$REPORT_PATH/lrreporttranslation.cpp \
$$REPORT_PATH/exporters/lrpdfexporter.cpp \
$$REPORT_PATH/lraxisdata.cpp \
$$REPORT_PATH/lrpreparedpages.cpp
CONFIG(staticlib) {
@ -170,6 +171,7 @@ HEADERS += \
$$REPORT_PATH/lrexportersfactory.h \
$$REPORT_PATH/exporters/lrpdfexporter.h \
$$REPORT_PATH/lrpreparedpages.h \
$$REPORT_PATH/lraxisdata.h \
$$REPORT_PATH/lrpreparedpagesintf.h
CONFIG(staticlib) {

68
limereport/lraxisdata.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "lraxisdata.h"
namespace LimeReport {
AxisData::AxisData()
: m_rangeMin(0), m_rangeMax(0),
m_minValue(0), m_maxValue(0), m_step(0),
m_delta(0), m_segmentCount(4)
{
}
AxisData::AxisData(qreal minValue, qreal maxValue)
: AxisData()
{
m_minValue = minValue;
m_maxValue = maxValue;
calculateValuesAboveMax(minValue, maxValue, 4);
m_delta = m_step * m_segmentCount;
}
int AxisData::segmentCount() const
{
return m_segmentCount;
}
qreal AxisData::rangeMin() const
{
return m_rangeMin;
}
qreal AxisData::rangeMax() const
{
return m_rangeMax;
}
qreal AxisData::minValue() const
{
return m_minValue;
}
qreal AxisData::maxValue() const
{
return m_maxValue;
}
qreal AxisData::step() const
{
return m_step;
}
qreal AxisData::delta() const
{
return m_delta;
}
void AxisData::calculateValuesAboveMax(qreal minValue, qreal maxValue, int segments)
{
const int delta = maxValue - minValue;
int max = delta;
while (max % segments != 0){
max++;
}
m_rangeMax = max;
m_step = max / segments;
m_rangeMin = minValue;
m_segmentCount = segments;
}
}

37
limereport/lraxisdata.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef AXISDATA_H
#define AXISDATA_H
#include <QtGlobal>
namespace LimeReport {
class AxisData
{
public:
AxisData();
AxisData(qreal minValue, qreal maxValue);
int segmentCount() const;
qreal rangeMin() const;
qreal rangeMax() const;
qreal minValue() const;
qreal maxValue() const;
qreal step() const;
qreal delta() const;
private:
void calculateValuesAboveMax(qreal minValue, qreal maxValue, int segments);
qreal calculateNiceNum(qreal range, bool round);
qreal m_rangeMin;
qreal m_rangeMax;
qreal m_minValue;
qreal m_maxValue;
qreal m_step;
qreal m_delta;
int m_segmentCount;
};
};
#endif // AXISDATA_H

View File

@ -165,6 +165,8 @@ void QObjectPropertyModel::translatePropertyName()
tr("printBehavior");
tr("shiftItems");
tr("showLegend");
tr("seriesLineWidth");
tr("drawPoints");
tr("removeGap");
tr("dropPrinterMargins");
tr("notPrintIfEmpty");