#include "lrchartitem.h" #include #include #include "lrdesignelementsfactory.h" #include "lrchartitemeditor.h" #include "lrdatasourcemanager.h" #include "lrpagedesignintf.h" #include "lrreportengine_p.h" #include "lrdatadesignintf.h" #include "lrchartaxiseditor.h" #include "charts/lrpiechart.h" #include "charts/lrverticalbarchart.h" #include "charts/lrhorizontalbarchart.h" #include "charts/lrlineschart.h" #include "charts/lrgridlineschart.h" namespace{ const QString xmlTag = "ChartItem"; LimeReport::BaseDesignIntf * createChartItem(QObject* owner, LimeReport::BaseDesignIntf* parent){ return new LimeReport::ChartItem(owner,parent); } bool registred = LimeReport::DesignElementsFactory::instance().registerCreator( xmlTag, LimeReport::ItemAttribs(QObject::tr("Chart Item"),"Item"), createChartItem ); } namespace LimeReport{ QColor generateColor() { int red = (rand()%(256 - 1)) + 1; int green = (rand()%(256 - 1)) + 1; int blue = (rand()%(256 - 1)) + 1;; return QColor(red,green,blue); } QColor color_map[39] = { QColor(51,102,204), QColor(220,57,18), QColor(225, 153, 0), QColor(16, 150, 24), QColor(153,0,153), QColor(0,153,198), QColor(221, 68, 119), QColor(255,0,0), QColor(0,0,139), QColor(0,205,0), QColor(233,30,99), QColor(255,255,0), QColor(244,67,54), QColor(156,39,176), QColor(103,58,183), QColor(63,81,181), QColor(33,153,243), QColor(0,150,136), QColor(78,175,80), QColor(139,195,74), QColor(205,228,57), QColor(0,139,0), QColor(0,0,255), QColor(255,235,59), QColor(255,193,7), QColor(255,152,0), QColor(255,87,34), QColor(121,85,72), QColor(158,158,158), QColor(96,125,139), QColor(241,153,185), QColor(64,64,64), QColor(188,229,218), QColor(139,0,0), QColor(139,139,0), QColor(171, 130, 255), QColor(139, 123, 139), QColor(255, 0, 255), QColor(139, 69, 19) }; QString SeriesItem::name() const { return m_name; } void SeriesItem::setName(const QString &name) { m_name = name; } QString SeriesItem::valuesColumn() const { return m_valuesColumn; } void SeriesItem::setValuesColumn(const QString &valuesColumn) { m_valuesColumn = valuesColumn; } QString SeriesItem::labelsColumn() const { return m_labelsColumn; } void SeriesItem::setLabelsColumn(const QString &labelsColumn) { m_labelsColumn = labelsColumn; } QString SeriesItem::xAxisColumn() const { return m_xAxisColumn; } void SeriesItem::setXAxisColumn(const QString &xAxisColumn) { m_xAxisColumn = xAxisColumn; } SeriesItem *SeriesItem::clone() { SeriesItem* result = new SeriesItem(); for (int i = 0; i < this->metaObject()->propertyCount(); ++i){ result->setProperty(this->metaObject()->property(i).name(),property(this->metaObject()->property(i).name())); } return result; } void SeriesItem::fillSeriesData(IDataSource *dataSource) { m_data.clear(); if (dataSource){ dataSource->first(); int currentColorIndex = 0; while(!dataSource->eof()){ if (!m_labelsColumn.isEmpty()) m_data.labels().append(dataSource->data(m_labelsColumn).toString()); if (!m_xAxisColumn.isEmpty()) m_data.xAxisValues().append(dataSource->data(m_xAxisColumn).toDouble()); m_data.values().append(dataSource->data(m_valuesColumn).toDouble()); m_data.colors().append((currentColorIndex<32)?color_map[currentColorIndex]:generateColor()); dataSource->next(); currentColorIndex++; } } } QColor SeriesItem::color() const { return m_color; } void SeriesItem::setColor(const QColor &color) { m_color = color; } SeriesItem::SeriesItemPreferredType SeriesItem::preferredType() const { return m_preferredType; } void SeriesItem::setPreferredType(const SeriesItemPreferredType& type) { m_preferredType = type; } ChartItem::ChartItem(QObject *owner, QGraphicsItem *parent) : ItemDesignIntf(xmlTag, owner, parent), m_legendBorder(true), 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_legendStyle(LegendPoints) { m_xAxisData = new AxisData(AxisData::XAxis, this); m_xAxisData->setReverseDirection(true); m_yAxisData = new AxisData(AxisData::YAxis, this); m_labels<<"First"<<"Second"<<"Thrid"; m_chart = new PieChart(this); m_chart->setTitleFont(font()); } ChartItem::~ChartItem() { foreach (SeriesItem* series, m_series) { delete series; } m_series.clear(); delete m_chart; } ChartItem::TitleAlign ChartItem::titleAlign() const { return m_titleAlign; } void ChartItem::setTitleAlign(const TitleAlign &titleAlign) { if (m_titleAlign != titleAlign){ TitleAlign oldValue = m_titleAlign; m_titleAlign = titleAlign; notify("titleAlign",oldValue,m_titleAlign); update(); } } void ChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->save(); setupPainter(painter); painter->setFont(transformToSceneFont(painter->font())); painter->setRenderHint(QPainter::Antialiasing,true); painter->setRenderHint(QPainter::TextAntialiasing,true); qreal borderMargin = (rect().height()*0.01>10)?(10):(rect().height()*0.01); qreal maxTitleHeight = rect().height()*0.5; QFont tmpFont = painter->font(); qreal titleOffset = 0; if (!m_title.isEmpty()) { QFontMetrics fm(titleFont()); const qreal titleHeight = fm.boundingRect(rect().toRect(), Qt::TextWordWrap,chartTitle()).height() + borderMargin * 2; titleOffset = std::min(titleHeight, maxTitleHeight); } const QRectF titleRect = QRectF(borderMargin,borderMargin,rect().width()-borderMargin*2,titleOffset); QRectF legendRect = 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.adjust(0, 0, -legendRect.width(), 0); break; case LegendAlignBottomLeft: case LegendAlignBottomCenter: case LegendAlignBottomRight: diagramRect.adjust(0, 0, 0, -(legendRect.height() + borderMargin * 2)); break; } } paintChartTitle(painter, titleRect); if (m_showLegend) m_chart->paintChartLegend(painter,legendRect); m_chart->paintChart(painter,diagramRect); painter->restore(); ItemDesignIntf::paint(painter,option,widget); } QObject *ChartItem::xAxisSettings() { return m_xAxisData; } void ChartItem::setYAxisSettings(QObject *axis) { AxisData *data = dynamic_cast(axis); if (data) { m_yAxisData->copy(data); } } QObject *ChartItem::yAxisSettings() { return m_yAxisData; } void ChartItem::setXAxisSettings(QObject *axis) { AxisData *data = static_cast(axis); if (data) { m_xAxisData->copy(data); } } AxisData *ChartItem::xAxisData() { return m_xAxisData; } AxisData *ChartItem::yAxisData() { return m_yAxisData; } void ChartItem::showAxisEditorDialog(bool isXAxis) { showDialog(new ChartAxisEditor(this, page(), isXAxis, settings())); } BaseDesignIntf *ChartItem::createSameTypeItem(QObject *owner, QGraphicsItem *parent) { ChartItem* result = new ChartItem(owner,parent); foreach (SeriesItem* series, m_series) { result->m_series.append(series->clone()); } return result; } QObject *ChartItem::createElement(const QString &collectionName, const QString &elementType) { Q_UNUSED(elementType); if (collectionName.compare("series")==0){ SeriesItem* seriesItem = new SeriesItem(); m_series.append(seriesItem); return seriesItem; } return 0; } int ChartItem::elementsCount(const QString &collectionName) { if (collectionName.compare("series")==0) return m_series.count(); return 0; } QObject *ChartItem::elementAt(const QString &collectionName, int index) { if (collectionName.compare("series")==0) return m_series.at(index); return 0; } void ChartItem::updateItemSize(DataSourceManager *dataManager, RenderPass , int ) { m_isEmpty = false; if (dataManager && dataManager->dataSource(m_datasource)){ IDataSource* ds = dataManager->dataSource(m_datasource); foreach (SeriesItem* series, m_series) { if (series->isEmpty()){ series->setLabelsColumn(m_labelsField); series->setXAxisColumn(m_xAxisField); series->fillSeriesData(ds); } } fillLabels(ds); } } void ChartItem::fillLabels(IDataSource *dataSource) { m_labels.clear(); if (dataSource && !m_labelsField.isEmpty()){ dataSource->first(); while(!dataSource->eof()){ m_labels.append(dataSource->data(m_labelsField).toString()); dataSource->next(); } } } QWidget *ChartItem::defaultEditor() { QWidget* editor = new ChartItemEditor(this, page(), settings()); editor->setAttribute(Qt::WA_DeleteOnClose); return editor; } bool ChartItem::isNeedUpdateSize(RenderPass pass) const { return pass == FirstPass && m_isEmpty; } QSettings *ChartItem::settings() { PageDesignIntf *page = this->page(); if (page->settings()) { return page->settings(); } if (page->reportEditor()) { return page->reportEditor()->settings(); } return 0; } bool ChartItem::showLegend() const { return m_showLegend; } void ChartItem::setShowLegend(bool showLegend) { if (m_showLegend != showLegend){ m_showLegend = showLegend; notify("showLegend", !m_showLegend, m_showLegend); update(); } m_showLegend = showLegend; } QList ChartItem::labels() const { return m_labels; } void ChartItem::setLabels(const QList &labels) { m_labels = labels; } QString ChartItem::labelsField() const { return m_labelsField; } void ChartItem::setLabelsField(const QString &labelsField) { m_labelsField = labelsField; } ChartItem::ChartType ChartItem::chartType() const { return m_chartType; } void ChartItem::setChartType(const ChartType &chartType) { if (m_chartType != chartType){ ChartType oldValue = m_chartType; m_chartType = chartType; QFont oldTitleFont = m_chart->titleFont(); delete m_chart; switch (m_chartType) { case Pie: m_chart = new PieChart(this); break; case VerticalBar: m_chart = new VerticalBarChart(this); break; case HorizontalBar: m_chart = new HorizontalBarChart(this); break; case Lines: m_chart = new LinesChart(this); break; case GridLines: m_chart = new GridLinesChart(this); break; } m_chart->setTitleFont(oldTitleFont); notify("chartType",oldValue,m_chartType); update(); } } QString ChartItem::datasource() const { return m_datasource; } void ChartItem::setDatasource(const QString &datasource) { m_datasource = datasource; } void ChartItem::paintChartTitle(QPainter *painter, QRectF titleRect) { painter->save(); QFont tmpFont = transformToSceneFont(titleFont()); QRect titleBoundingRect = QFontMetrics(tmpFont).boundingRect(rect().toRect(), Qt::TextWordWrap, chartTitle()); while ((titleBoundingRect.height() > titleRect.height() || titleBoundingRect.width() > titleRect.width()) && tmpFont.pixelSize() > 1) { tmpFont.setPixelSize(tmpFont.pixelSize()-1); titleBoundingRect = QFontMetrics(tmpFont).boundingRect(rect().toRect(), Qt::TextWordWrap, chartTitle()); } painter->setFont(tmpFont); Qt::AlignmentFlag align = Qt::AlignCenter; switch (m_titleAlign) { case TitleAlignLeft: align = Qt::AlignLeft; break; case TitleAlignCenter: align = Qt::AlignCenter; break; case TitleAlignRight: align = Qt::AlignRight; break; } painter->drawText(titleRect, align | Qt::TextWordWrap, m_title); painter->restore(); } ChartItem::LegendAlign ChartItem::legendAlign() const { return m_legendAlign; } void ChartItem::setLegendAlign(const LegendAlign &legendAlign) { if (m_legendAlign != legendAlign){ LegendAlign oldValue = m_legendAlign; m_legendAlign = legendAlign; notify("legendAlign",oldValue,m_legendAlign); update(); } } 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; } void ChartItem::setDrawLegendBorder(bool legendBorder) { if (m_legendBorder!=legendBorder){ m_legendBorder = legendBorder; notify("legendBorder",!m_legendBorder,m_legendBorder); update(); } } QString ChartItem::chartTitle() const { return m_title; } void ChartItem::setChartTitle(const QString &title) { if (m_title != title){ QString oldValue = m_title; m_title = title; update(); notify("chartTitle",oldValue,title); } } QList &ChartItem::series() { return m_series; } void ChartItem::setSeries(const QList &series) { m_series = series; } bool ChartItem::isSeriesExists(const QString &name) { foreach (SeriesItem* series, m_series) { if (series->name().compare(name)==0) return true; } 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; } QString ChartItem::xAxisField() const { return m_xAxisField; } void ChartItem::setXAxisField(const QString &xAxisField) { m_xAxisField = xAxisField; } bool ChartItem::horizontalAxisOnTop() const { return m_horizontalAxisOnTop; } void ChartItem::setHorizontalAxisOnTop(bool horizontalAxisOnTop) { if (m_horizontalAxisOnTop != horizontalAxisOnTop){ m_horizontalAxisOnTop = horizontalAxisOnTop; notify("horizontalAxisOnTop", !m_horizontalAxisOnTop, m_horizontalAxisOnTop); update(); } m_horizontalAxisOnTop = horizontalAxisOnTop; } ChartItem::GridChartLines ChartItem::gridChartLines() const { return m_gridChartLines; } void ChartItem::setGridChartLines(GridChartLines flags) { if (m_gridChartLines == flags) { return; } GridChartLines oldValue = m_gridChartLines; m_gridChartLines = flags; if (isLoading()) { return; } update(rect()); notify("gridChartLines",QVariant(oldValue),QVariant(flags)); } void ChartItem::setCharItemFont(QFont value) { if (font() == value) { return; } QFont oldValue = font(); setFont(value); if (!isLoading()) update(); notify("font",oldValue,value); } QFont ChartItem::titleFont() const { return m_chart->titleFont(); } void ChartItem::setTitleFont(QFont value) { if (m_chart->titleFont() == value){ return; } QFont oldValue = value; m_chart->setTitleFont(value); if (!isLoading()) update(); notify("titleFont", oldValue, value); } AbstractChart::AbstractChart(ChartItem *chartItem) :m_chartItem(chartItem) { m_designLabels<legendAlign()) { case ChartItem::LegendAlignRightTop: legendTopMargin = titleOffset + borderMargin; legendBottomMargin = parentRect.height() - (legendSize.height() + titleOffset); isVertical = true; break; case ChartItem::LegendAlignRightCenter: legendTopMargin = titleOffset + (parentRect.height() - titleOffset - legendSize.height()) / 2; legendBottomMargin = (parentRect.height() - titleOffset - legendSize.height()) / 2; isVertical = true; break; 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; } 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 ); } } QFont AbstractChart::titleFont() { return m_titleFont; } void AbstractChart::setTitleFont(const QFont &value) { m_titleFont = value; } void AbstractChart::prepareLegendToPaint(QRectF &legendRect, QPainter *painter) { QFont tmpFont = painter->font(); switch(m_chartItem->legendAlign()) { case ChartItem::LegendAlignBottomLeft: case ChartItem::LegendAlignBottomCenter: case ChartItem::LegendAlignBottomRight: { const qreal maxWidth = legendRect.width() * 0.95; 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); 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())) { 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; } } AbstractSeriesChart::AbstractSeriesChart(ChartItem *chartItem) :AbstractChart(chartItem) { m_designValues[0] = 10; m_designValues[1] = 35; m_designValues[2] = 15; m_designValues[3] = 5; m_designValues[4] = 20; m_designValues[5] = 10; m_designValues[6] = 40; m_designValues[7] = 20; m_designValues[8] = 5; } qreal AbstractSeriesChart::maxValue() { return m_chartItem->yAxisData()->maxValue(); } qreal AbstractSeriesChart::minValue() { return m_chartItem->yAxisData()->minValue(); } AxisData &AbstractSeriesChart::xAxisData() const { return *m_chartItem->xAxisData(); } AxisData &AbstractSeriesChart::yAxisData() const { return *m_chartItem->yAxisData(); } void AbstractSeriesChart::updateMinAndMaxValues() { if (m_chartItem->itemMode() == DesignMode) { m_chartItem->xAxisData()->updateForDesignMode(); m_chartItem->yAxisData()->updateForDesignMode(); return; } qreal maxYValue = 0; qreal minYValue = std::numeric_limits::max(); qreal maxXValue = 0; qreal minXValue = std::numeric_limits::max(); for (SeriesItem* series : m_chartItem->series()){ for (qreal value : series->data()->values()){ minYValue = std::min(minYValue, value); 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(valuesCount, maxXValue); } else { for (qreal value : series->data()->xAxisValues()){ minXValue = std::min(value, minXValue); maxXValue = std::max(value, maxXValue); } } } m_chartItem->xAxisData()->update(minXValue, maxXValue); m_chartItem->yAxisData()->update(minYValue, maxYValue); } qreal AbstractSeriesChart::hPadding(QRectF chartRect) { return (chartRect.width() * 0.02); } qreal AbstractSeriesChart::vPadding(QRectF chartRect) { return (chartRect.height() * 0.02); } int AbstractSeriesChart::valuesCount() { if (m_chartItem->itemMode() == DesignMode) return 3; return (m_chartItem->series().isEmpty()) ? 0 : m_chartItem->series().at(0)->data()->values().count(); } int AbstractSeriesChart::seriesCount() { if (m_chartItem->itemMode() == DesignMode) return 3; return m_chartItem->series().count(); } QSizeF AbstractSeriesChart::calcChartLegendSize(const QFont &font, const qreal maxWidth) { QFontMetrics fm(font); 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 } if (m_legendColumnWidths.isEmpty()) { m_legendColumnWidths.append(0); } const qreal columnCount = m_legendColumnWidths.size(); const qreal rowCount = std::ceil(seriesCount / columnCount); QSizeF legendSize(std::accumulate(m_legendColumnWidths.cbegin(), m_legendColumnWidths.cend(), 0.0) + fm.height() / 2, (rowCount + 1) * fm.height()); if (legendSize.width() > maxWidth) { legendSize.setWidth(maxWidth); } return legendSize; } 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) { if (valuesCount() == 0) return false; qreal hStep = (labelsRect.width() / valuesCount()); QFontMetrics fm = painter->fontMetrics(); foreach(QString label, m_chartItem->labels()){ if (fm.boundingRect(label).width() > hStep){ return true; } } return false; } void AbstractSeriesChart::paintHorizontalLabels(QPainter *painter, QRectF labelsRect) { if (valuesCount() == 0) return; painter->save(); qreal hStep = (labelsRect.width() / valuesCount()); if (!m_chartItem->labels().isEmpty()){ if (verticalLabels(painter, labelsRect)){ painter->rotate(270); painter->translate( -(labelsRect.top()+labelsRect.height()), labelsRect.left() ); foreach (QString label, m_chartItem->labels()) { painter->drawText(QRectF(QPoint(0,0), QSize(labelsRect.height()-4, hStep)), Qt::AlignVCenter | Qt::AlignRight, label); painter->translate(0,hStep); } painter->rotate(-270); } else { painter->translate( labelsRect.left(), labelsRect.top() ); foreach (QString label, m_chartItem->labels()) { painter->drawText(QRectF(QPoint(0, 4), QSize(hStep, labelsRect.height()-4)), Qt::AlignHCenter | Qt::AlignTop, label); painter->translate(hStep, 0); } } } painter->restore(); } void AbstractSeriesChart::paintVerticalLabels(QPainter *painter, QRectF labelsRect) { if (valuesCount() == 0) return; painter->save(); painter->setFont(adaptLabelsFont(labelsRect.adjusted(0, 0, -hPadding(m_chartItem->rect()), 0), painter->font())); qreal vStep = (labelsRect.height() / valuesCount()); int curLabel = 0; painter->translate(labelsRect.topLeft()); if (!m_chartItem->labels().isEmpty()){ foreach (QString label, m_chartItem->labels()) { painter->drawText(QRectF(QPoint(0,vStep*curLabel), QSize(labelsRect.width()-hPadding(m_chartItem->rect()),vStep)), Qt::AlignVCenter | Qt::AlignRight | Qt::TextWordWrap,label); curLabel++; } } painter->restore(); } void AbstractSeriesChart::paintHorizontalGrid(QPainter *painter, QRectF gridRect) { painter->save(); const AxisData &yAxisData = this->yAxisData(); 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()) / segmentCount; painter->setFont(adaptFont(hStep-4, painter->font(), yAxisData)); QPointF textPos; if (m_chartItem->horizontalAxisOnTop()) { textPos.setY(gridRect.top()); } else { textPos.setY(gridRect.bottom() - painter->fontMetrics().height()); } for (int i = 0 ; i < lineCount ; i++ ) { const qreal x = gridRect.left() + hStep * i; textPos.setX(x + 4); painter->drawText(QRectF(textPos, QSizeF(hStep, painter->fontMetrics().height())), axisLabel(i, yAxisData)); painter->drawLine(x, gridRect.bottom(), x, gridRect.top()); } painter->restore(); } void AbstractSeriesChart::paintVerticalGrid(QPainter *painter, QRectF gridRect) { const AxisData &yAxisData = this->yAxisData(); painter->setRenderHint(QPainter::Antialiasing,false); 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; 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)), axisLabel(i, yAxisData), verticalTextOption); painter->drawLine(gridRect.bottomLeft()-QPointF(-valuesHMargin,y), gridRect.bottomRight()-QPointF(0,y)); } painter->setRenderHint(QPainter::Antialiasing,true); } void AbstractSeriesChart::paintGrid(QPainter *painter, QRectF gridRect) { painter->save(); const AxisData &yAxisData = this->yAxisData(); 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 int fontHeight = painter->fontMetrics().height(); const int halfFontHeight = fontHeight / 2; const QSizeF gridOffset = QSizeF(hPadding(gridRect), vPadding(gridRect)); const qreal valuesHMargin = this->valuesHMargin(painter); const qreal vStep = gridRect.height() / yAxisSegmentCount; const qreal hStep = (gridRect.width() - valuesHMargin - gridOffset.width()) / xAxisSegmentCount; const qreal textPositionHOffset = valuesHMargin * 0.1; // Vertical axis lines const QTextOption verticalTextOption(Qt::AlignRight); for (int i = 0 ; i < yAxisLineCount ; i++ ) { const qreal y = vStep * i; const bool drawFullLine = m_chartItem->gridChartLines() & ChartItem::HorizontalLine || i == 0 || i == xAxisSegmentCount; painter->drawText(QRectF(gridRect.bottomLeft()-QPointF(textPositionHOffset, y + halfFontHeight), QSizeF(valuesHMargin,fontHeight)), axisLabel(i, yAxisData), verticalTextOption); QPointF lineEndPos = gridRect.bottomRight() - QPointF(0, y); if (!drawFullLine) { lineEndPos.setX(gridRect.left() + valuesHMargin + gridOffset.width()); } painter->drawLine(gridRect.bottomLeft() - QPointF(-valuesHMargin, y), lineEndPos); } // Horizontal axis lines for (int i = 0 ; i < xAxisLineCount ; i++) { const qreal x = gridRect.left() + hStep * i + valuesHMargin + gridOffset.width(); const bool drawFullLine = m_chartItem->gridChartLines() & ChartItem::VerticalLine || i == 0 || i == xAxisSegmentCount; const QString text = axisLabel(i, xAxisData); if (m_chartItem->horizontalAxisOnTop()) { painter->drawLine(x, gridRect.top() - gridOffset.height(), x, (drawFullLine ? gridRect.bottom() : gridRect.top())); painter->drawText(QRectF(x - painter->fontMetrics().boundingRect(text).width() / 2, gridRect.top() - (fontHeight + gridOffset.height()), hStep, fontHeight), text); } else { painter->drawLine(x, gridRect.bottom() + gridOffset.height(), x, (drawFullLine ? gridRect.top() : gridRect.bottom())); painter->drawText(QRectF(x - painter->fontMetrics().boundingRect(text).width() / 2, gridRect.bottom() + halfFontHeight * 0 + gridOffset.height(), hStep, fontHeight), text); } } painter->restore(); } void AbstractSeriesChart::drawSegment(QPainter *painter, QPoint startPoint, QPoint endPoint, QColor color) { 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); painter->drawEllipse(startPointRect.adjusted(radius,radius,-radius,-radius)); painter->drawEllipse(endPointRect.adjusted(radius,radius,-radius,-radius)); } qreal AbstractSeriesChart::valuesHMargin(QPainter *painter) { 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 = axisLabel(i, yAxisData); max = std::max(max, (qreal)painter->fontMetrics().boundingRect(label).width()+offset); } return max; } qreal AbstractSeriesChart::valuesVMargin(QPainter *painter) { return painter->fontMetrics().height(); } QFont AbstractSeriesChart::adaptLabelsFont(QRectF rect, QFont font) { QString maxWord; QFontMetrics fm(font); foreach(QString label, m_chartItem->labels()){ #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 1)) foreach (QString currentWord, label.split(QRegExp("\\W+"))){ #else foreach (QString currentWord, label.split(QRegularExpression("\\W+"))){ #endif if (fm.boundingRect(maxWord).width() < fm.boundingRect(currentWord).width()) maxWord = currentWord; } } qreal curWidth = fm.boundingRect(maxWord).width(); QFont tmpFont = font; while (curWidth>rect.width() && tmpFont.pixelSize() > 1){ tmpFont.setPixelSize(tmpFont.pixelSize() - 1); QFontMetricsF tmpFM(tmpFont); curWidth = tmpFM.boundingRect(maxWord).width(); } return tmpFont; } QFont AbstractSeriesChart::adaptFont(qreal width, QFont font, const AxisData &axisData) { QFont tmpFont = font; const int axisLineCount = axisData.segmentCount() + 1; QScopedPointer fm(new QFontMetricsF(tmpFont)); for (int i = 0 ; i < axisLineCount ; i++) { QString strValue = axisLabel(i, axisData); qreal curWidth = fm->boundingRect(strValue).width(); while (curWidth > width && tmpFont.pixelSize() > 1){ tmpFont.setPixelSize(tmpFont.pixelSize() - 1); fm.reset(new QFontMetricsF(tmpFont)); curWidth = fm->boundingRect(strValue).width(); } } return tmpFont; } QString AbstractSeriesChart::axisLabel(int i, const AxisData &axisData) { const qreal min = axisData.rangeMin(); const qreal step = axisData.step(); qreal value = 0; if (axisData.type() == AxisData::YAxis && 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(round(value * 100.0) / 100.0); } 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) { 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 maxRowWidth) { 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; // 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; // 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++) { 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; return true; } QVector AbstractChart::legendColumnWidths() const { return m_legendColumnWidths; } void AbstractBarChart::paintChartLegend(QPainter *painter, QRectF legendRect) { prepareLegendToPaint(legendRect, painter); painter->setPen(Qt::black); painter->setRenderHint(QPainter::Antialiasing,false); if (m_chartItem->drawLegendBorder()) painter->drawRect(legendRect); painter->setRenderHint(QPainter::Antialiasing,true); 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()){ 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) { 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]); } } } } QRectF AbstractBarChart::verticalLabelsRect(QPainter *painter, QRectF labelsRect) { qreal maxWidth = 0; foreach (QString label, m_chartItem->labels()) { if (painter->fontMetrics().boundingRect(label).width()>maxWidth) maxWidth = painter->fontMetrics().boundingRect(label).width(); } if (maxWidth + hPadding(m_chartItem->rect()) * 2 < labelsRect.width()) return labelsRect; else return labelsRect.adjusted(0, 0, -(labelsRect.width() - (maxWidth + hPadding(m_chartItem->rect()) * 2)), 0); } QRectF AbstractBarChart::horizontalLabelsRect(QPainter *painter, QRectF labelsRect) { qreal maxWidth = 0; foreach (QString label, m_chartItem->labels()) { if (painter->fontMetrics().boundingRect(label).width()>maxWidth) maxWidth = painter->fontMetrics().boundingRect(label).width(); } if ((maxWidth + vPadding(m_chartItem->rect()) < labelsRect.height()) || !verticalLabels(painter, labelsRect)) return labelsRect; else 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