From b14f0be7bca38a02e0713021eecc34e8937fe9b0 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Mon, 7 Feb 2022 23:16:07 +0100 Subject: [PATCH 1/5] Add horizontal legend --- limereport/items/charts/lrpiechart.cpp | 3 +- limereport/items/charts/lrpiechart.h | 2 +- limereport/items/lrchartitem.cpp | 386 +++++++++++++++++++------ limereport/items/lrchartitem.h | 24 +- 4 files changed, 325 insertions(+), 90 deletions(-) diff --git a/limereport/items/charts/lrpiechart.cpp b/limereport/items/charts/lrpiechart.cpp index 5932c9e..1f61190 100644 --- a/limereport/items/charts/lrpiechart.cpp +++ b/limereport/items/charts/lrpiechart.cpp @@ -91,6 +91,7 @@ void PieChart::paintChart(QPainter *painter, QRectF chartRect) void PieChart::paintChartLegend(QPainter *painter, QRectF legendRect) { +// TODO_ES Fix bottom legend when axis is at the bottom prepareLegendToPaint(legendRect, painter); int indicatorSize = painter->fontMetrics().height()/2; @@ -143,7 +144,7 @@ void PieChart::paintChartLegend(QPainter *painter, QRectF legendRect) } } -QSizeF PieChart::calcChartLegendSize(const QFont &font) +QSizeF PieChart::calcChartLegendSize(const QFont &font, qreal) { QFontMetrics fm(font); diff --git a/limereport/items/charts/lrpiechart.h b/limereport/items/charts/lrpiechart.h index 2b3fb6f..88337f2 100644 --- a/limereport/items/charts/lrpiechart.h +++ b/limereport/items/charts/lrpiechart.h @@ -8,7 +8,7 @@ namespace LimeReport{ class PieChart : public AbstractChart{ public: PieChart(ChartItem* chartItem):AbstractChart(chartItem){} - QSizeF calcChartLegendSize(const QFont &font); + QSizeF calcChartLegendSize(const QFont &font, qreal maxWidth = 0); void paintChart(QPainter *painter, QRectF chartRect); void paintChartLegend(QPainter *painter, QRectF legendRect); protected: diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index ef2b735..3746a50 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -141,10 +141,11 @@ void SeriesItem::setPreferredType(const SeriesItemPreferredType& type) ChartItem::ChartItem(QObject *owner, QGraphicsItem *parent) : ItemDesignIntf(xmlTag, owner, parent), m_legendBorder(true), - m_legendAlign(LegendAlignCenter), m_titleAlign(TitleAlignCenter), + m_legendAlign(LegendAlignRightCenter), m_titleAlign(TitleAlignCenter), m_chartType(Pie), m_labelsField(""), m_isEmpty(true), m_showLegend(true), m_drawPoints(true), m_seriesLineWidth(4), - m_horizontalAxisOnTop(false), m_gridChartLines(AllLines) + m_horizontalAxisOnTop(false), m_gridChartLines(AllLines), + m_legendStyle(LegendPoints) { m_labels<<"First"<<"Second"<<"Thrid"; m_chart = new PieChart(this); @@ -195,11 +196,28 @@ void ChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, } const QRectF titleRect = QRectF(borderMargin,borderMargin,rect().width()-borderMargin*2,titleOffset); - QRectF legendRect = QRectF(0,0,0,0); - if (m_showLegend) + QRectF legendRect = QRectF(0, 0, 0, 0); + QRectF diagramRect = QRectF(0, 0, 0, 0); + if (m_showLegend) { legendRect = m_chart->calcChartLegendRect(painter->font(), rect(), false, borderMargin, titleOffset); - QRectF diagramRect = rect().adjusted(borderMargin,titleOffset+borderMargin, - -(legendRect.width()+borderMargin*2),-borderMargin); + switch(legendAlign()) { + case LegendAlignRightTop: + case LegendAlignRightBottom: + case LegendAlignRightCenter: + diagramRect = rect().adjusted(borderMargin, titleOffset + borderMargin, + -(legendRect.width() + borderMargin * 2), -borderMargin); + break; + case LegendAlignBottomLeft: + case LegendAlignBottomCenter: + case LegendAlignBottomRight: + diagramRect = rect().adjusted(borderMargin, titleOffset + borderMargin, + (borderMargin * 2), -legendRect.height()); + break; + } + } else { + diagramRect = rect().adjusted(borderMargin, titleOffset + borderMargin, + -(borderMargin * 2), -borderMargin); + } paintChartTitle(painter, titleRect); if (m_showLegend) @@ -414,6 +432,22 @@ void ChartItem::setLegendAlign(const LegendAlign &legendAlign) } } +ChartItem::LegendStyle ChartItem::legendStyle() const +{ + return m_legendStyle; +} + +void ChartItem::setLegendStyle(const LegendStyle &legendStyle) +{ + if (m_legendStyle == legendStyle) { + return; + } + LegendStyle oldValue = m_legendStyle; + m_legendStyle = legendStyle; + notify("legendStyle", oldValue, m_legendStyle); + update(); +} + bool ChartItem::drawLegendBorder() const { return m_legendBorder; @@ -571,37 +605,61 @@ AbstractChart::AbstractChart(ChartItem *chartItem) QRectF AbstractChart::calcChartLegendRect(const QFont &font, const QRectF &parentRect, bool takeAllRect, qreal borderMargin, qreal titleOffset) { - QSizeF legendSize = calcChartLegendSize(font); + const QSizeF legendSize = calcChartLegendSize(font, parentRect.width() * 0.9); qreal legendTopMargin = 0; qreal legendBottomMargin = 0; + qreal legendLeftMargin = 0; + bool isVertical = true; switch (m_chartItem->legendAlign()) { - case ChartItem::LegendAlignTop: - legendTopMargin = titleOffset+borderMargin; - legendBottomMargin = parentRect.height()-(legendSize.height()+titleOffset); + case ChartItem::LegendAlignRightTop: + legendTopMargin = titleOffset + borderMargin; + legendBottomMargin = parentRect.height() - (legendSize.height() + titleOffset); + isVertical = true; break; - case ChartItem::LegendAlignCenter: - legendTopMargin = titleOffset+(parentRect.height()-titleOffset-legendSize.height())/2; - legendBottomMargin = (parentRect.height()-titleOffset-legendSize.height())/2; + case ChartItem::LegendAlignRightCenter: + legendTopMargin = titleOffset + (parentRect.height() - titleOffset - legendSize.height()) / 2; + legendBottomMargin = (parentRect.height() - titleOffset - legendSize.height()) / 2; + isVertical = true; break; - case ChartItem::LegendAlignBottom: - legendTopMargin = parentRect.height()-(legendSize.height()+titleOffset); - legendBottomMargin = borderMargin; + case ChartItem::LegendAlignRightBottom: + legendTopMargin = parentRect.height() - (legendSize.height() + titleOffset); + legendBottomMargin = borderMargin; + isVertical = true; + break; + case ChartItem::LegendAlignBottomLeft: + legendLeftMargin = QFontMetrics(font).height() / 2; + isVertical = false; + break; + case ChartItem::LegendAlignBottomCenter: + legendLeftMargin = (parentRect.width() - legendSize.width()) / 2; + isVertical = false; + break; + case ChartItem::LegendAlignBottomRight: + legendLeftMargin = parentRect.width() - legendSize.width() - QFontMetrics(font).height() / 2; + isVertical = false; break; } - qreal rightOffset = !takeAllRect?((legendSize.width()>parentRect.width()/2-borderMargin)? - (parentRect.width()/2): - (parentRect.width()-legendSize.width())):0; - - QRectF legendRect = parentRect.adjusted( - rightOffset, - (legendSize.height()>(parentRect.height()-titleOffset))?(titleOffset):(legendTopMargin), - -borderMargin, - (legendSize.height()>(parentRect.height()-titleOffset))?(0):(-legendBottomMargin) - ); - - return legendRect; + if (isVertical) { + qreal rightOffset = !takeAllRect ? ((legendSize.width() > parentRect.width() / 2 - borderMargin) ? + (parentRect.width() / 2) : + (parentRect.width() - legendSize.width())) : 0; + return parentRect.adjusted( + rightOffset, + (legendSize.height()>(parentRect.height()-titleOffset))?(titleOffset):(legendTopMargin), + -borderMargin, + (legendSize.height()>(parentRect.height()-titleOffset))?(0):(-legendBottomMargin) + ); + } else { + const qreal verticalOffset = borderMargin * 2; + return parentRect.adjusted( + legendLeftMargin, + (parentRect.height()) - (legendSize.height() + verticalOffset), + -(parentRect.width() - (legendSize.width() + legendLeftMargin)), + -verticalOffset + ); + } } @@ -618,18 +676,28 @@ void AbstractChart::setTitleFont(const QFont &value) void AbstractChart::prepareLegendToPaint(QRectF &legendRect, QPainter *painter) { QFont tmpFont = painter->font(); - QSizeF legendSize = calcChartLegendSize(tmpFont); - - if ((legendSize.height()>legendRect.height() || legendSize.width()>legendRect.width())){ - while ( (legendSize.height()>legendRect.height() || legendSize.width()>legendRect.width()) - && tmpFont.pixelSize()>1) - { - tmpFont.setPixelSize(tmpFont.pixelSize()-1); + switch(m_chartItem->legendAlign()) { + case ChartItem::LegendAlignBottomLeft: + case ChartItem::LegendAlignBottomCenter: + case ChartItem::LegendAlignBottomRight: { + // TODO_ES handle resize horizontal legend (wrapping text) + break; + } + case ChartItem::LegendAlignRightTop: + case ChartItem::LegendAlignRightCenter: + case ChartItem::LegendAlignRightBottom: + QSizeF legendSize = calcChartLegendSize(tmpFont, legendRect.width()); + if ((legendSize.height() > legendRect.height() || legendSize.width() > legendRect.width())) { + while ( (legendSize.height() > legendRect.height() || legendSize.width() > legendRect.width()) + && tmpFont.pixelSize() > 1) + { + tmpFont.setPixelSize(tmpFont.pixelSize()-1); + legendSize = calcChartLegendSize(tmpFont, legendRect.width()); + } painter->setFont(tmpFont); - legendSize = calcChartLegendSize(tmpFont); + legendRect = calcChartLegendRect(tmpFont, legendRect, true, 0, 0); } - painter->setFont(tmpFont); - legendRect = calcChartLegendRect(tmpFont, legendRect, true, 0, 0); + break; } } @@ -723,28 +791,54 @@ int AbstractSeriesChart::seriesCount() return m_chartItem->series().count(); } -QSizeF AbstractSeriesChart::calcChartLegendSize(const QFont &font) +QSizeF AbstractSeriesChart::calcChartLegendSize(const QFont &font, const qreal maxWidth) { QFontMetrics fm(font); - qreal cw = 0; - qreal maxWidth = 0; - - if (!m_chartItem->series().isEmpty()){ - foreach(SeriesItem* series, m_chartItem->series()){ - cw += fm.height(); - if (maxWidthname()).width()) - maxWidth = fm.boundingRect(series->name()).width()+10; + switch(m_chartItem->legendAlign()) { + case ChartItem::LegendAlignBottomLeft: + case ChartItem::LegendAlignBottomCenter: + case ChartItem::LegendAlignBottomRight: { + const qreal seriesCount = m_chartItem->series().isEmpty() ? m_designLabels.size() : m_chartItem->series().size(); + const qreal indicatorWidth = fm.height() * 1.5; + m_legendColumnWidths.clear(); + while (!calculateLegendColumnWidths(indicatorWidth, maxWidth, fm)) { + // Nothing to do here } - } else { - foreach(QString label, m_designLabels){ - cw += fm.height(); - if (maxWidth maxWidth) { + legendSize.setWidth(maxWidth); + } + return legendSize; } - cw += fm.height(); - return QSizeF(maxWidth+fm.height()*2,cw); + default: { + qreal cw = 0; + qreal maxWidth = 0; + + if (m_chartItem->series().isEmpty()) { + foreach(QString label, m_designLabels){ + cw += fm.height(); + if (maxWidthseries()){ + cw += fm.height(); + if (maxWidthname()).width()) + maxWidth = fm.boundingRect(series->name()).width()+10; + } + } + cw += fm.height(); + return QSizeF(maxWidth+fm.height()*2,cw); + } + } + return QSizeF(); } bool AbstractSeriesChart::verticalLabels(QPainter* painter, QRectF labelsRect) @@ -1028,48 +1122,108 @@ QString AbstractSeriesChart::axisLabel(int i, const AxisData &axisData) return QString::number(round(value * 100.0) / 100.0); } +bool AbstractSeriesChart::calculateLegendColumnWidths(qreal indicatorWidth, qreal maxWidth, const QFontMetrics &fm) +{ + qreal currentRowWidth = 0; + int currentColumn = 0; + int maxColumnCount = m_legendColumnWidths.size(); + if (m_chartItem->series().isEmpty()) { + for (int i=0 ; i < m_designLabels.size() ; ++i) { + const qreal itemWidth = (qreal)(fm.boundingRect(m_designLabels[i]).width()) + indicatorWidth; + if (!calculateLegendSingleColumnWidth(currentRowWidth, currentColumn, maxColumnCount, itemWidth, maxWidth)) { + return false; + } + } + } else { + for (int i = 0 ; i < m_chartItem->series().size() ; ++i) { + SeriesItem* series = m_chartItem->series().at(i); + const qreal itemWidth = (qreal)(fm.boundingRect(series->name()).width()) + indicatorWidth; + if (!calculateLegendSingleColumnWidth(currentRowWidth, currentColumn, maxColumnCount, itemWidth, maxWidth)) { + return false; + } + } + } + return true; +} + +bool AbstractSeriesChart::calculateLegendSingleColumnWidth(qreal ¤tRowWidth, int ¤tColumn, int &maxColumnCount, const qreal itemWidth, const qreal maxWidth) +{ + // TODO_ES optimise ?, it's hard to read + const bool isEnoughSpaceInRowForItem = currentRowWidth + itemWidth > maxWidth; + const bool lastColumnReached = (maxColumnCount > 0 && currentColumn >= maxColumnCount); + if (isEnoughSpaceInRowForItem || lastColumnReached) { + currentColumn = 0; + if (maxColumnCount == 0) { + maxColumnCount = currentColumn + 1; + } + currentRowWidth = itemWidth; + } else { + currentRowWidth += itemWidth; + } + if (currentColumn >= m_legendColumnWidths.size()) { + m_legendColumnWidths.append(itemWidth); + } else if (m_legendColumnWidths.at(currentColumn) < itemWidth) { + m_legendColumnWidths[currentColumn] = itemWidth; + qreal tmpWidth = itemWidth; + for (int c = 1 ; c < m_legendColumnWidths.size() ; c++) { + tmpWidth += m_legendColumnWidths.at(c); + if (tmpWidth > maxWidth) { + m_legendColumnWidths.remove(c, m_legendColumnWidths.size() - c); + break; + } + } + return false; + } + ++currentColumn; + return true; +} + +QVector AbstractChart::legendColumnWidths() const +{ + return m_legendColumnWidths; +} + void AbstractBarChart::paintChartLegend(QPainter *painter, QRectF legendRect) { prepareLegendToPaint(legendRect, painter); - int indicatorSize = painter->fontMetrics().height()/2; painter->setPen(Qt::black); painter->setRenderHint(QPainter::Antialiasing,false); if (m_chartItem->drawLegendBorder()) painter->drawRect(legendRect); painter->setRenderHint(QPainter::Antialiasing,true); - QRectF indicatorsRect = legendRect.adjusted(painter->fontMetrics().height()/2,painter->fontMetrics().height()/2,0,0); + + const qreal halfFontSize = painter->fontMetrics().height() / 2; + int indicatorSize = halfFontSize; + const QRectF indicatorsRect = legendRect.adjusted(halfFontSize, halfFontSize, 0, 0); + + bool isHorizontal = false; + switch(m_chartItem->legendAlign()) { + case ChartItem::LegendAlignBottomLeft: + case ChartItem::LegendAlignBottomCenter: + case ChartItem::LegendAlignBottomRight: + isHorizontal = true; + break; + default: + isHorizontal = false; + break; + } if (!m_chartItem->series().isEmpty()){ - qreal cw = 0; - foreach(SeriesItem* series, m_chartItem->series()){ - QString label = series->name(); - painter->drawText(indicatorsRect.adjusted(indicatorSize+indicatorSize/2,cw,0,0),label); - painter->setBrush(series->color()); - painter->drawEllipse( - indicatorsRect.adjusted( - 0, - cw+indicatorSize/2, - -(indicatorsRect.width()-indicatorSize), - -(indicatorsRect.height()-(cw+indicatorSize+indicatorSize/2)) - ) - ); - cw += painter->fontMetrics().height(); + for (int i = 0 ; i < m_chartItem->series().size() ; ++i) { + SeriesItem* series = m_chartItem->series().at(i); + if (isHorizontal) { + drawHorizontalLegendItem(painter, i, series->name(), indicatorSize, indicatorsRect, series->color()); + } else { + drawVerticalLegendItem(painter, i, series->name(), indicatorSize, indicatorsRect, series->color()); + } } - } else if (m_chartItem->itemMode() == DesignMode){ - qreal cw = 0; - for (int i=0;idrawText(indicatorsRect.adjusted(indicatorSize+indicatorSize/2,cw,0,0),label); - painter->setBrush(color_map[i]); - painter->drawEllipse( - indicatorsRect.adjusted( - 0, - cw+indicatorSize/2, - -(indicatorsRect.width()-indicatorSize), - -(indicatorsRect.height()-(cw+indicatorSize+indicatorSize/2)) - ) - ); - cw += painter->fontMetrics().height(); + } else if (m_chartItem->itemMode() == DesignMode) { + for (int i = 0 ; i < m_designLabels.size() ; ++i){ + if (isHorizontal) { + drawHorizontalLegendItem(painter, i, m_designLabels.at(i), indicatorSize, indicatorsRect, color_map[i]); + } else { + drawVerticalLegendItem(painter, i, m_designLabels.at(i), indicatorSize, indicatorsRect, color_map[i]); + } } } @@ -1105,4 +1259,66 @@ QRectF AbstractBarChart::horizontalLabelsRect(QPainter *painter, QRectF labelsRe return labelsRect.adjusted(0, (labelsRect.height() - maxWidth), 0, 0); } +void AbstractBarChart::drawVerticalLegendItem(QPainter *painter, int i, const QString &text, int indicatorSize, + const QRectF &indicatorsRect, const QColor &indicatorColor) +{ + const qreal y = i * painter->fontMetrics().height(); + painter->drawText(indicatorsRect.adjusted(indicatorSize+indicatorSize * 1.5, y, 0, 0),text); + switch(m_chartItem->legendStyle()) { + case ChartItem::LegendPoints: { + painter->setBrush(indicatorColor); + painter->drawEllipse( + indicatorsRect.adjusted( + 0, + y+indicatorSize/2, + -(indicatorsRect.width()-indicatorSize), + -(indicatorsRect.height()-(y+indicatorSize+indicatorSize/2)) + ) + ); + break; + } + case ChartItem::LegendLines: { + const QPen tmpPen = painter->pen(); + QPen indicatorPen(indicatorColor); + indicatorPen.setWidth(4); + painter->setPen(indicatorPen); + const QPointF linePos = QPointF(indicatorsRect.left(), indicatorsRect.top() + y + painter->fontMetrics().height()/2); + painter->drawLine(linePos, linePos + QPointF(indicatorSize, 0)); + painter->setPen(tmpPen); + break; + } + } +} + +void AbstractBarChart::drawHorizontalLegendItem(QPainter *painter, int i, const QString &text, + int indicatorSize, const QRectF &indicatorsRect, const QColor &indicatorColor) +{ + const QVector &columnWidths = legendColumnWidths(); + if (columnWidths.isEmpty()) + return; + const int column = i % columnWidths.size(); + const int row = std::floor(i / columnWidths.size()); + const qreal halfTextSize = painter->fontMetrics().height() / 2; + + const qreal x = indicatorsRect.x() + std::accumulate(columnWidths.cbegin(), columnWidths.cbegin() + column, 0.0); + const qreal y = indicatorsRect.y() + (row + 1) * painter->fontMetrics().height(); + painter->drawText(QPointF(x + indicatorSize * 1.5, y), text); + switch(m_chartItem->legendStyle()) { + case ChartItem::LegendPoints: { + painter->setBrush(indicatorColor); + painter->drawEllipse(x, y - halfTextSize, indicatorSize, indicatorSize); + break; + } + case ChartItem::LegendLines: { + const QPen tmpPen = painter->pen(); + QPen indicatorPen(indicatorColor); + indicatorPen.setWidth(4); + painter->setPen(indicatorPen); + painter->drawLine(x, y - halfTextSize * 0.7, x + indicatorSize, y - halfTextSize * 0.7); + painter->setPen(tmpPen); + break; + } + } +} + } // namespace LimeReport diff --git a/limereport/items/lrchartitem.h b/limereport/items/lrchartitem.h index fda9546..8a0c6cc 100644 --- a/limereport/items/lrchartitem.h +++ b/limereport/items/lrchartitem.h @@ -76,12 +76,13 @@ public: virtual ~AbstractChart(){} virtual void paintChart(QPainter *painter, QRectF rect) = 0; virtual void paintChartLegend(QPainter *painter, QRectF legendRect) =0; - virtual QSizeF calcChartLegendSize(const QFont &font) = 0; + virtual QSizeF calcChartLegendSize(const QFont &font, qreal maxWidth = 0) = 0; virtual QRectF calcChartLegendRect(const QFont& font, const QRectF& parentRect, bool takeAllRect, qreal borderMargin, qreal titleOffset); QFont titleFont(); void setTitleFont(const QFont &value); protected: + QVector legendColumnWidths() const; virtual void prepareLegendToPaint(QRectF& legendRect, QPainter *painter); protected: // Title font must be placed here instead of CharItem, becuase @@ -89,6 +90,7 @@ protected: QFont m_titleFont; ChartItem* m_chartItem; QList m_designLabels; + QVector m_legendColumnWidths; }; class AbstractSeriesChart: public AbstractChart{ @@ -103,7 +105,7 @@ protected: int valuesCount(); int seriesCount(); bool verticalLabels(QPainter* painter, QRectF labelsRect); - QSizeF calcChartLegendSize(const QFont &font); + QSizeF calcChartLegendSize(const QFont &font, qreal maxWidth); qreal* designValues(){ return m_designValues;} virtual qreal hPadding(QRectF chartRect); virtual qreal vPadding(QRectF chartRect); @@ -120,6 +122,9 @@ protected: virtual QString axisLabel(int i, const AxisData &axisData); private: + bool calculateLegendColumnWidths(qreal indicatorWidth, qreal maxWidth, const QFontMetrics &fm); + bool calculateLegendSingleColumnWidth(qreal ¤tRowWidth, int ¤tColumn, int &maxColumnCount, + const qreal itemWidth, const qreal maxWidth); AxisData m_yAxisData, m_xAxisData; qreal m_designValues [9]; }; @@ -131,6 +136,11 @@ public: protected: QRectF verticalLabelsRect(QPainter* painter, QRectF horizontalLabelsRect); virtual QRectF horizontalLabelsRect(QPainter* painter, QRectF horizontalLabelsRect); +private: + void drawVerticalLegendItem(QPainter *painter, int i, const QString &text, + int indicatorSize, const QRectF &indicatorsRect, const QColor &indicatorColor); + void drawHorizontalLegendItem(QPainter *painter, int i, const QString &text, + int indicatorSize, const QRectF &indicatorsRect, const QColor &indicatorColor); }; class ChartItem : public LimeReport::ItemDesignIntf @@ -141,6 +151,7 @@ class ChartItem : public LimeReport::ItemDesignIntf Q_PROPERTY(QString chartTitle READ chartTitle WRITE setChartTitle) Q_PROPERTY(bool drawLegendBorder READ drawLegendBorder WRITE setDrawLegendBorder) Q_PROPERTY(LegendAlign legendAlign READ legendAlign WRITE setLegendAlign) + Q_PROPERTY(LegendStyle legendStyle READ legendStyle WRITE setLegendStyle) Q_PROPERTY(TitleAlign titleAlign READ titleAlign WRITE setTitleAlign) Q_PROPERTY(ChartType chartType READ chartType WRITE setChartType) Q_PROPERTY(QString labelsField READ labelsField WRITE setLabelsField) @@ -160,7 +171,8 @@ class ChartItem : public LimeReport::ItemDesignIntf friend class AbstractChart; public: - enum LegendAlign{LegendAlignTop,LegendAlignCenter,LegendAlignBottom}; + enum LegendAlign{LegendAlignRightTop,LegendAlignRightCenter,LegendAlignRightBottom, + LegendAlignBottomLeft,LegendAlignBottomCenter,LegendAlignBottomRight}; enum LegendStyle{LegendPoints, LegendLines}; enum TitleAlign{TitleAlignLeft, TitleAlignCenter, TitleAlignRight}; enum ChartType{Pie, VerticalBar, HorizontalBar, Lines, GridLines}; @@ -172,11 +184,13 @@ public: }; #if QT_VERSION >= 0x050500 Q_ENUM(LegendAlign) + Q_ENUM(LegendStyle) Q_ENUM(TitleAlign) Q_ENUM(ChartType) Q_ENUM(LineType) #else Q_ENUMS(LegendAlign) + Q_ENUMS(LegendStyle) Q_ENUMS(TitleAlign) Q_ENUMS(ChartType) Q_ENUMS(LineType) @@ -203,6 +217,9 @@ public: LegendAlign legendAlign() const; void setLegendAlign(const LegendAlign &legendAlign); + LegendStyle legendStyle() const; + void setLegendStyle(const LegendStyle &legendStyle); + TitleAlign titleAlign() const; void setTitleAlign(const TitleAlign &titleAlign); @@ -269,6 +286,7 @@ private: QString m_xAxisField; bool m_horizontalAxisOnTop; GridChartLines m_gridChartLines; + LegendStyle m_legendStyle; }; } //namespace LimeReport #endif // LRCHARTITEM_H From f03ccdd23b890007c125dcdb45df162968d51fb6 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Tue, 15 Feb 2022 17:39:14 +0100 Subject: [PATCH 2/5] Fix diagram size when legend is showed --- limereport/items/charts/lrpiechart.cpp | 1 - limereport/items/lrchartitem.cpp | 13 +++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/limereport/items/charts/lrpiechart.cpp b/limereport/items/charts/lrpiechart.cpp index 1f61190..dbc7687 100644 --- a/limereport/items/charts/lrpiechart.cpp +++ b/limereport/items/charts/lrpiechart.cpp @@ -91,7 +91,6 @@ void PieChart::paintChart(QPainter *painter, QRectF chartRect) void PieChart::paintChartLegend(QPainter *painter, QRectF legendRect) { -// TODO_ES Fix bottom legend when axis is at the bottom prepareLegendToPaint(legendRect, painter); int indicatorSize = painter->fontMetrics().height()/2; diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index 3746a50..fa3b6ed 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -197,28 +197,25 @@ void ChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, const QRectF titleRect = QRectF(borderMargin,borderMargin,rect().width()-borderMargin*2,titleOffset); QRectF legendRect = QRectF(0, 0, 0, 0); - QRectF diagramRect = QRectF(0, 0, 0, 0); + QRectF diagramRect = rect().adjusted(borderMargin, titleOffset + borderMargin, + -(borderMargin * 2), -borderMargin); if (m_showLegend) { legendRect = m_chart->calcChartLegendRect(painter->font(), rect(), false, borderMargin, titleOffset); switch(legendAlign()) { case LegendAlignRightTop: case LegendAlignRightBottom: case LegendAlignRightCenter: - diagramRect = rect().adjusted(borderMargin, titleOffset + borderMargin, - -(legendRect.width() + borderMargin * 2), -borderMargin); + diagramRect.adjust(0, 0, -legendRect.width(), 0); break; case LegendAlignBottomLeft: case LegendAlignBottomCenter: case LegendAlignBottomRight: - diagramRect = rect().adjusted(borderMargin, titleOffset + borderMargin, - (borderMargin * 2), -legendRect.height()); + diagramRect.adjust(0, 0, 0, -(legendRect.height() + borderMargin * 2)); break; } - } else { - diagramRect = rect().adjusted(borderMargin, titleOffset + borderMargin, - -(borderMargin * 2), -borderMargin); } + painter->fillRect(diagramRect, Qt::yellow); paintChartTitle(painter, titleRect); if (m_showLegend) m_chart->paintChartLegend(painter,legendRect); From 2ce293f85d93373bc304902dbccabd17343dece9 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Sat, 5 Mar 2022 16:40:12 +0100 Subject: [PATCH 3/5] Add comments explaining calculations --- limereport/items/lrchartitem.cpp | 36 +++++++++++++++++++++++++------- limereport/items/lrchartitem.h | 2 +- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index fa3b6ed..5ab1891 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -1121,8 +1121,15 @@ QString AbstractSeriesChart::axisLabel(int i, const AxisData &axisData) bool AbstractSeriesChart::calculateLegendColumnWidths(qreal indicatorWidth, qreal maxWidth, const QFontMetrics &fm) { + // This method is called in the loop, because to handle case when we get + // 3 small series names in first row and then in second row small name and long name. + // In this case we need to set maximum column count to 2 and iterate from the start to recalculate + // all the sizes qreal currentRowWidth = 0; int currentColumn = 0; + // During first iteration it is updated when moving to second row + // After first iteration some column width are already calculated and are set as max, + // because all rows need to have same column count (except last one) int maxColumnCount = m_legendColumnWidths.size(); if (m_chartItem->series().isEmpty()) { for (int i=0 ; i < m_designLabels.size() ; ++i) { @@ -1143,32 +1150,45 @@ bool AbstractSeriesChart::calculateLegendColumnWidths(qreal indicatorWidth, qrea return true; } -bool AbstractSeriesChart::calculateLegendSingleColumnWidth(qreal ¤tRowWidth, int ¤tColumn, int &maxColumnCount, const qreal itemWidth, const qreal maxWidth) +bool AbstractSeriesChart::calculateLegendSingleColumnWidth(qreal ¤tRowWidth, int ¤tColumn, int &maxColumnCount, + const qreal itemWidth, const qreal maxRowWidth) { - // TODO_ES optimise ?, it's hard to read - const bool isEnoughSpaceInRowForItem = currentRowWidth + itemWidth > maxWidth; - const bool lastColumnReached = (maxColumnCount > 0 && currentColumn >= maxColumnCount); + const bool maxColumnCountDefined = maxColumnCount > 0; + // Check if there is enough space for current item in the row + const bool isEnoughSpaceInRowForItem = currentRowWidth + itemWidth > maxRowWidth; + // Check if it is last column already + const bool lastColumnReached = (maxColumnCountDefined && currentColumn >= maxColumnCount); if (isEnoughSpaceInRowForItem || lastColumnReached) { + // Move to next row currentColumn = 0; - if (maxColumnCount == 0) { + // Set column count when moving to second row (next rows cannot have more columns) + if (!maxColumnCountDefined) { maxColumnCount = currentColumn + 1; } currentRowWidth = itemWidth; } else { + // Add next column in the row currentRowWidth += itemWidth; } + + // Add new column or update already existing column width if (currentColumn >= m_legendColumnWidths.size()) { + // Append new column m_legendColumnWidths.append(itemWidth); } else if (m_legendColumnWidths.at(currentColumn) < itemWidth) { + // Update size if item in column is bigger than items in same column in previous rows m_legendColumnWidths[currentColumn] = itemWidth; - qreal tmpWidth = itemWidth; + // After any updating column size we must recheck if all columns fit in the max row width + qreal rowWidth = itemWidth; for (int c = 1 ; c < m_legendColumnWidths.size() ; c++) { - tmpWidth += m_legendColumnWidths.at(c); - if (tmpWidth > maxWidth) { + rowWidth += m_legendColumnWidths.at(c); + // When column widths exceed max row width remove columns at the end + if (rowWidth > maxRowWidth) { m_legendColumnWidths.remove(c, m_legendColumnWidths.size() - c); break; } } + // Return back and re-iterate from start to make sure everything fits return false; } ++currentColumn; diff --git a/limereport/items/lrchartitem.h b/limereport/items/lrchartitem.h index 8a0c6cc..9c0b05f 100644 --- a/limereport/items/lrchartitem.h +++ b/limereport/items/lrchartitem.h @@ -124,7 +124,7 @@ protected: private: bool calculateLegendColumnWidths(qreal indicatorWidth, qreal maxWidth, const QFontMetrics &fm); bool calculateLegendSingleColumnWidth(qreal ¤tRowWidth, int ¤tColumn, int &maxColumnCount, - const qreal itemWidth, const qreal maxWidth); + const qreal itemWidth, const qreal maxRowWidth); AxisData m_yAxisData, m_xAxisData; qreal m_designValues [9]; }; From 47ba3d0b4f67d8647903c795e8cbe6e0460f5637 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Sat, 5 Mar 2022 17:36:37 +0100 Subject: [PATCH 4/5] Update font size if there is not enough space --- limereport/items/lrchartitem.cpp | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index 5ab1891..2233765 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -677,23 +677,35 @@ void AbstractChart::prepareLegendToPaint(QRectF &legendRect, QPainter *painter) case ChartItem::LegendAlignBottomLeft: case ChartItem::LegendAlignBottomCenter: case ChartItem::LegendAlignBottomRight: { - // TODO_ES handle resize horizontal legend (wrapping text) + const qreal maxWidth = legendRect.width() * 0.9; + qreal legendWidth = std::accumulate(m_legendColumnWidths.cbegin(), m_legendColumnWidths.cend(), 0.0); + if (legendWidth < maxWidth) { + return; + } + while ( (legendWidth > maxWidth) && tmpFont.pixelSize() > 1) { + tmpFont.setPixelSize(tmpFont.pixelSize() - 1); + calcChartLegendSize(tmpFont, legendRect.width()); + legendWidth = std::accumulate(m_legendColumnWidths.cbegin(), m_legendColumnWidths.cend(), 0.0); + } + painter->setFont(tmpFont); + legendRect = calcChartLegendRect(tmpFont, legendRect, true, 0, 0); break; } case ChartItem::LegendAlignRightTop: case ChartItem::LegendAlignRightCenter: case ChartItem::LegendAlignRightBottom: QSizeF legendSize = calcChartLegendSize(tmpFont, legendRect.width()); - if ((legendSize.height() > legendRect.height() || legendSize.width() > legendRect.width())) { - while ( (legendSize.height() > legendRect.height() || legendSize.width() > legendRect.width()) - && tmpFont.pixelSize() > 1) - { - tmpFont.setPixelSize(tmpFont.pixelSize()-1); - legendSize = calcChartLegendSize(tmpFont, legendRect.width()); - } - painter->setFont(tmpFont); - legendRect = calcChartLegendRect(tmpFont, legendRect, true, 0, 0); + if ((legendSize.height() <= legendRect.height() && legendSize.width() <= legendRect.width())) { + return; } + while ((legendSize.height() > legendRect.height() || legendSize.width() > legendRect.width()) + && tmpFont.pixelSize() > 1) + { + tmpFont.setPixelSize(tmpFont.pixelSize() - 1); + legendSize = calcChartLegendSize(tmpFont, legendRect.width()); + } + painter->setFont(tmpFont); + legendRect = calcChartLegendRect(tmpFont, legendRect, true, 0, 0); break; } } @@ -1203,6 +1215,7 @@ QVector AbstractChart::legendColumnWidths() const void AbstractBarChart::paintChartLegend(QPainter *painter, QRectF legendRect) { prepareLegendToPaint(legendRect, painter); + // TODO_ES after calculating bottom legend size, handle difference of sizes painter->setPen(Qt::black); painter->setRenderHint(QPainter::Antialiasing,false); if (m_chartItem->drawLegendBorder()) From 9bde04feca05456f30606b8f75e2139227f95db2 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Sun, 6 Mar 2022 15:00:55 +0100 Subject: [PATCH 5/5] Fixed legend size when resizing font --- limereport/items/lrchartitem.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index 2233765..357b8cb 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -215,7 +215,6 @@ void ChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, } } - painter->fillRect(diagramRect, Qt::yellow); paintChartTitle(painter, titleRect); if (m_showLegend) m_chart->paintChartLegend(painter,legendRect); @@ -677,7 +676,7 @@ void AbstractChart::prepareLegendToPaint(QRectF &legendRect, QPainter *painter) case ChartItem::LegendAlignBottomLeft: case ChartItem::LegendAlignBottomCenter: case ChartItem::LegendAlignBottomRight: { - const qreal maxWidth = legendRect.width() * 0.9; + const qreal maxWidth = legendRect.width() * 0.95; qreal legendWidth = std::accumulate(m_legendColumnWidths.cbegin(), m_legendColumnWidths.cend(), 0.0); if (legendWidth < maxWidth) { return; @@ -688,7 +687,6 @@ void AbstractChart::prepareLegendToPaint(QRectF &legendRect, QPainter *painter) legendWidth = std::accumulate(m_legendColumnWidths.cbegin(), m_legendColumnWidths.cend(), 0.0); } painter->setFont(tmpFont); - legendRect = calcChartLegendRect(tmpFont, legendRect, true, 0, 0); break; } case ChartItem::LegendAlignRightTop: @@ -1215,7 +1213,6 @@ QVector AbstractChart::legendColumnWidths() const void AbstractBarChart::paintChartLegend(QPainter *painter, QRectF legendRect) { prepareLegendToPaint(legendRect, painter); - // TODO_ES after calculating bottom legend size, handle difference of sizes painter->setPen(Qt::black); painter->setRenderHint(QPainter::Antialiasing,false); if (m_chartItem->drawLegendBorder())