diff --git a/limereport/items/charts/lrhorizontalbarchart.cpp b/limereport/items/charts/lrhorizontalbarchart.cpp index 78c081c..17858fe 100644 --- a/limereport/items/charts/lrhorizontalbarchart.cpp +++ b/limereport/items/charts/lrhorizontalbarchart.cpp @@ -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; diff --git a/limereport/items/charts/lrlineschart.cpp b/limereport/items/charts/lrlineschart.cpp index 0bff366..7bd6c60 100644 --- a/limereport/items/charts/lrlineschart.cpp +++ b/limereport/items/charts/lrlineschart.cpp @@ -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 &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(); } diff --git a/limereport/items/charts/lrlineschart.h b/limereport/items/charts/lrlineschart.h index 6e10cf4..d37cb2c 100644 --- a/limereport/items/charts/lrlineschart.h +++ b/limereport/items/charts/lrlineschart.h @@ -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); }; } diff --git a/limereport/items/charts/lrverticalbarchart.cpp b/limereport/items/charts/lrverticalbarchart.cpp index 83af650..36109b0 100644 --- a/limereport/items/charts/lrverticalbarchart.cpp +++ b/limereport/items/charts/lrverticalbarchart.cpp @@ -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(); diff --git a/limereport/items/charts/lrverticalbarchart.h b/limereport/items/charts/lrverticalbarchart.h index 9cfe8f8..8f302c5 100644 --- a/limereport/items/charts/lrverticalbarchart.h +++ b/limereport/items/charts/lrverticalbarchart.h @@ -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); diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index 8227fdb..6868109 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -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 (valuem_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 diff --git a/limereport/items/lrchartitem.h b/limereport/items/lrchartitem.h index 2059a9d..7e1ecac 100644 --- a/limereport/items/lrchartitem.h +++ b/limereport/items/lrchartitem.h @@ -2,6 +2,7 @@ #define LRCHARTITEM_H #include "lritemdesignintf.h" #include "lrglobal.h" +#include "lraxisdata.h" #include 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 m_labels; bool m_isEmpty; bool m_showLegend; + bool m_drawPoints; + int m_seriesLineWidth; }; - } //namespace LimeReport #endif // LRCHARTITEM_H diff --git a/limereport/limereport.pri b/limereport/limereport.pri index 6615978..e6862a9 100644 --- a/limereport/limereport.pri +++ b/limereport/limereport.pri @@ -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) { diff --git a/limereport/lraxisdata.cpp b/limereport/lraxisdata.cpp new file mode 100644 index 0000000..fbe7f2a --- /dev/null +++ b/limereport/lraxisdata.cpp @@ -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; +} +} diff --git a/limereport/lraxisdata.h b/limereport/lraxisdata.h new file mode 100644 index 0000000..84a40c7 --- /dev/null +++ b/limereport/lraxisdata.h @@ -0,0 +1,37 @@ +#ifndef AXISDATA_H +#define AXISDATA_H + +#include + +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 diff --git a/limereport/objectinspector/lrobjectitemmodel.cpp b/limereport/objectinspector/lrobjectitemmodel.cpp index 6b5ca7d..50da3f0 100644 --- a/limereport/objectinspector/lrobjectitemmodel.cpp +++ b/limereport/objectinspector/lrobjectitemmodel.cpp @@ -165,6 +165,8 @@ void QObjectPropertyModel::translatePropertyName() tr("printBehavior"); tr("shiftItems"); tr("showLegend"); + tr("seriesLineWidth"); + tr("drawPoints"); tr("removeGap"); tr("dropPrinterMargins"); tr("notPrintIfEmpty");