diff --git a/CMakeLists.txt b/CMakeLists.txt index 783a58b..6ba0f51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.14) set(LIMEREPORT_VERSION_MAJOR 1) set(LIMEREPORT_VERSION_MINOR 6) -set(LIMEREPORT_VERSION_RELEASE 4) +set(LIMEREPORT_VERSION_RELEASE 6) option(ENABLE_ZINT "Enable libzint build for barcode support" OFF) option(LIMEREPORT_STATIC "Build LimeReport as static library" OFF) @@ -36,6 +36,7 @@ endif() add_subdirectory(3rdparty) add_subdirectory(designer EXCLUDE_FROM_ALL) +add_subdirectory(demo_r1 EXCLUDE_FROM_ALL) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) @@ -66,6 +67,7 @@ ${PROJECT_NAME}/databrowser/lrvariabledialog.cpp ${PROJECT_NAME}/exporters/lrpdfexporter.cpp ${PROJECT_NAME}/items/charts/lrhorizontalbarchart.cpp ${PROJECT_NAME}/items/charts/lrlineschart.cpp +${PROJECT_NAME}/items/charts/lrgridlineschart.cpp ${PROJECT_NAME}/items/charts/lrpiechart.cpp ${PROJECT_NAME}/items/charts/lrverticalbarchart.cpp ${PROJECT_NAME}/items/editors/lrfonteditorwidget.cpp @@ -176,6 +178,7 @@ ${PROJECT_NAME}/databrowser/lrvariabledialog.h ${PROJECT_NAME}/exporters/lrpdfexporter.h ${PROJECT_NAME}/items/charts/lrhorizontalbarchart.h ${PROJECT_NAME}/items/charts/lrlineschart.h +${PROJECT_NAME}/items/charts/lrgridlineschart.h ${PROJECT_NAME}/items/charts/lrpiechart.h ${PROJECT_NAME}/items/charts/lrverticalbarchart.h ${PROJECT_NAME}/items/editors/lrfonteditorwidget.h @@ -276,7 +279,6 @@ ${PROJECT_NAME}/translationeditor/languageselectdialog.h ${PROJECT_NAME}/translationeditor/translationeditor.h - ${PROJECT_NAME}/databrowser/lrconnectiondialog.ui ${PROJECT_NAME}/databrowser/lrdatabrowser.ui ${PROJECT_NAME}/databrowser/lrsqleditdialog.ui diff --git a/demo_r1/CMakeLists.txt b/demo_r1/CMakeLists.txt new file mode 100644 index 0000000..a694da1 --- /dev/null +++ b/demo_r1/CMakeLists.txt @@ -0,0 +1,23 @@ +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +add_executable(demo_r1 main.cpp ${PROJECT_SOURCES}) + +target_include_directories( demo_r1 PRIVATE ../include/ ) +target_link_libraries(demo_r1 PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::PrintSupport + Qt${QT_VERSION_MAJOR}::Qml + Qt${QT_VERSION_MAJOR}::Sql + ${PROJECT_NAME} +) + diff --git a/limereport/items/charts/lrgridlineschart.cpp b/limereport/items/charts/lrgridlineschart.cpp new file mode 100644 index 0000000..5c2f3bc --- /dev/null +++ b/limereport/items/charts/lrgridlineschart.cpp @@ -0,0 +1,112 @@ +#include "lrgridlineschart.h" + +namespace LimeReport { +void GridLinesChart::paintChart(QPainter *painter, QRectF chartRect) +{ + updateMinAndMaxValues(); + + const qreal hPadding = this->hPadding(chartRect); + const qreal vPadding = this->vPadding(chartRect); + + const qreal valuesVMargin = this->valuesVMargin(painter); + + QRectF gridRect = chartRect.adjusted( + hPadding, + vPadding + valuesVMargin * 2, + -hPadding * 3, + -vPadding * 3 + ); + + if (!m_chartItem->horizontalAxisOnTop()) { + // If horizontal axis is on the bottom, move grid a little up + gridRect.adjust(0, -valuesVMargin, 0 , -valuesVMargin); + } + + // Adapt font for horizontal axis + painter->setFont(adaptFont((gridRect.width() - this->valuesHMargin(painter)) / xAxisData().segmentCount() * 0.8, + painter->font(), + xAxisData())); + + const qreal valuesHMargin = this->valuesHMargin(painter); + + // Adjust vertical axis labels padding + gridRect.adjust(valuesHMargin * 0.2, 0, 0, 0); + + paintGrid(painter, gridRect); + + paintSerialLines( + painter, + gridRect.adjusted(hPadding + valuesHMargin, 0, 0, 0) + ); +} + +void GridLinesChart::paintSerialLines(QPainter* painter, QRectF barsRect) +{ + if (valuesCount() == 0) return; + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing,true); + + const AxisData &yAxisData = this->yAxisData(); + const qreal delta = yAxisData.delta(); + + if (m_chartItem->itemMode() == DesignMode){ + const qreal hStep = barsRect.width() / valuesCount(); + const qreal vStep = barsRect.height() / delta; + const qreal topShift = (delta - (maxValue() - minValue())) * vStep + barsRect.top(); + drawDesignMode(painter, hStep, vStep, topShift, barsRect); + painter->restore(); + return; + } + + const AxisData &xAxisData = this->xAxisData(); + const qreal hStep = barsRect.width() / (xAxisData.rangeMax() - xAxisData.rangeMin()); + + qreal leftMargin = 0; + const qreal topMargin = barsRect.top(); + + for (SeriesItem* series : m_chartItem->series()) { + QPen pen(series->color()); + pen.setWidth(m_chartItem->seriesLineWidth()); + painter->setPen(pen); + + const QList &xAxisValues = series->data()->xAxisValues(); + const QList &values = series->data()->values(); + const int xAxisValuesSize = xAxisValues.size(); + qreal lastXPos = 0; + qreal lastYPos = 0; + if (!values.isEmpty()) { + // Calculate first point position on plot before loop + lastYPos = calculatePos(yAxisData, values.first(), barsRect.height()); + } + if (xAxisValues.isEmpty()) { + leftMargin = barsRect.left(); + } else { + leftMargin = barsRect.left(); + lastXPos = calculatePos(xAxisData, xAxisValues.first(), barsRect.width()); + } + for (int i = 0; i < values.count() - 1; ++i ) { + const qreal startY = lastYPos; + const qreal endY = calculatePos(yAxisData, values.at(i+1), barsRect.height()); + // Record last used Y position to only calculate new one + lastYPos = endY; + + qreal startX = lastXPos; + qreal endX = 0; + if (i + 1 < xAxisValuesSize) { + endX = calculatePos(xAxisData, xAxisValues.at(i+1), barsRect.width()); + } else { + endX = startX + hStep; + } + // Record last used X position to only calculate new one + lastXPos = endX; + + QPoint startPoint = QPoint(startX + leftMargin, startY + topMargin); + QPoint endPoint = QPoint(endX + leftMargin, endY + topMargin); + drawSegment(painter, startPoint, endPoint, series->color()); + } + } + + painter->restore(); +} +} diff --git a/limereport/items/charts/lrgridlineschart.h b/limereport/items/charts/lrgridlineschart.h new file mode 100644 index 0000000..a95a07b --- /dev/null +++ b/limereport/items/charts/lrgridlineschart.h @@ -0,0 +1,17 @@ +#ifndef GRIDLINESCHART_H +#define GRIDLINESCHART_H + +#include "lrlineschart.h" + +namespace LimeReport { +class GridLinesChart : public LinesChart{ +public: + GridLinesChart(ChartItem* chartItem):LinesChart(chartItem){} + void paintChart(QPainter *painter, QRectF chartRect); + +private: + void paintSerialLines(QPainter *painter, QRectF barsRect); +}; +} + +#endif // GRIDLINESCHART_H diff --git a/limereport/items/charts/lrhorizontalbarchart.cpp b/limereport/items/charts/lrhorizontalbarchart.cpp index 17858fe..eeb04ac 100644 --- a/limereport/items/charts/lrhorizontalbarchart.cpp +++ b/limereport/items/charts/lrhorizontalbarchart.cpp @@ -20,7 +20,9 @@ void HorizontalBarChart::paintChart(QPainter *painter, QRectF chartRect) paintHorizontalGrid(painter, chartRect.adjusted( hPadding(chartRect) + barsShift, vPadding(chartRect), - -(hPadding(chartRect)),-vPadding(chartRect))); + -(hPadding(chartRect)), + -vPadding(chartRect))); + paintHorizontalBars(painter, chartRect.adjusted( hPadding(chartRect) + barsShift, vPadding(chartRect) * 2, @@ -36,25 +38,33 @@ void HorizontalBarChart::paintHorizontalBars(QPainter *painter, QRectF barsRect) painter->save(); painter->setRenderHint(QPainter::Antialiasing,false); + const AxisData &yAxisData = this->yAxisData(); const qreal delta = yAxisData.delta(); - qreal vStep = (barsRect.height()-painter->fontMetrics().height()) / valuesCount() / seriesCount(); + const qreal verticalOffset = painter->fontMetrics().height(); + qreal vStep = (barsRect.height() - verticalOffset) / valuesCount() / seriesCount(); qreal hStep = (barsRect.width()-painter->fontMetrics().boundingRect(QString::number(maxValue())).width()) / delta; if (!m_chartItem->series().isEmpty() && (m_chartItem->itemMode() != DesignMode)){ - int curSeries = 0; + qreal curVOffset = barsRect.top(); + if (m_chartItem->horizontalAxisOnTop()) { + curVOffset += verticalOffset; + } foreach (SeriesItem* series, m_chartItem->series()) { - qreal curVOffset = curSeries*vStep+barsRect.top(); painter->setBrush(series->color()); + qreal y = curVOffset; foreach (qreal value, series->data()->values()) { - painter->drawRect(QRectF((-minValue()*hStep)+barsRect.left(), curVOffset, value*hStep, vStep)); - curVOffset+=vStep*seriesCount(); + painter->drawRect(QRectF((-minValue()*hStep)+barsRect.left(), y, value*hStep, vStep)); + y+=vStep*seriesCount(); } - curSeries++; + curVOffset += vStep; } } else { qreal curVOffset = barsRect.top(); + if (m_chartItem->horizontalAxisOnTop()) { + curVOffset += verticalOffset; + } int curColor = 0; for (int i=0; i<9; ++i){ if (curColor==3) curColor=0; diff --git a/limereport/items/charts/lrlineschart.cpp b/limereport/items/charts/lrlineschart.cpp index 7bd6c60..36aabc6 100644 --- a/limereport/items/charts/lrlineschart.cpp +++ b/limereport/items/charts/lrlineschart.cpp @@ -68,17 +68,18 @@ void LinesChart::drawDesignMode(QPainter* painter, qreal hStep, qreal vStep, qre } } -qreal LinesChart::calculateValueYPos(qreal, qreal max, qreal value, qreal delta, qreal height) +qreal LinesChart::calculatePos(const AxisData &data, qreal value, qreal rectSize) const { - return (max - value) / delta * height; + return (data.rangeMax() - value) / data.delta() * rectSize; } void LinesChart::paintSeries(QPainter *painter, SeriesItem *series, QRectF barsRect) { const AxisData &yAxisData = this->yAxisData(); - const qreal delta = yAxisData.delta(); + const AxisData &xAxisData = this->xAxisData(); - const qreal hStep = barsRect.width() / valuesCount(); + const qreal xAxisDiff = std::max(1.0, xAxisData.maxValue() - xAxisData.minValue()); + const qreal hStep = barsRect.width() / xAxisDiff; const qreal topMargin = barsRect.top(); QPen pen(series->color()); @@ -91,11 +92,11 @@ void LinesChart::paintSeries(QPainter *painter, SeriesItem *series, QRectF barsR 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()); + lastYValue = calculatePos(yAxisData, values.first(), 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()); + const qreal endY = calculatePos(yAxisData, values.at(i+1), barsRect.height()); // Record last used Y position to only calculate new one lastYValue = endY; diff --git a/limereport/items/charts/lrlineschart.h b/limereport/items/charts/lrlineschart.h index d37cb2c..d9325fc 100644 --- a/limereport/items/charts/lrlineschart.h +++ b/limereport/items/charts/lrlineschart.h @@ -10,7 +10,7 @@ public: 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); + qreal calculatePos(const AxisData &data, qreal value, qreal rectSize) const; void paintSeries(QPainter *painter, SeriesItem *series, QRectF barsRect); private: diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index 6868109..565b3cf 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -13,6 +13,7 @@ #include "charts/lrverticalbarchart.h" #include "charts/lrhorizontalbarchart.h" #include "charts/lrlineschart.h" +#include "charts/lrgridlineschart.h" namespace{ @@ -79,6 +80,16 @@ 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(); @@ -98,6 +109,8 @@ void SeriesItem::fillSeriesData(IDataSource *dataSource) 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(); @@ -130,7 +143,8 @@ 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_drawPoints(true), m_seriesLineWidth(4) + m_showLegend(true), m_drawPoints(true), m_seriesLineWidth(4), + m_horizontalAxisOnTop(false), m_gridChartLines(AllLines) { m_labels<<"First"<<"Second"<<"Thrid"; m_chart = new PieChart(this); @@ -235,6 +249,7 @@ void ChartItem::updateItemSize(DataSourceManager *dataManager, RenderPass , int foreach (SeriesItem* series, m_series) { if (series->isEmpty()){ series->setLabelsColumn(m_labelsField); + series->setXAxisColumn(m_xAxisField); series->fillSeriesData(ds); } } @@ -327,6 +342,10 @@ void ChartItem::setChartType(const ChartType &chartType) break; case Lines: m_chart = new LinesChart(this); + break; + case GridLines: + m_chart = new GridLinesChart(this); + break; } notify("chartType",oldValue,m_chartType); update(); @@ -464,6 +483,50 @@ void ChartItem::setSeriesLineWidth(int newSeriesLineWidth) 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)); +} + AbstractChart::AbstractChart(ChartItem *chartItem) :m_chartItem(chartItem) { @@ -552,22 +615,43 @@ AxisData AbstractSeriesChart::yAxisData() return m_yAxisData; } +AxisData AbstractSeriesChart::xAxisData() +{ + return m_xAxisData; +} + void AbstractSeriesChart::updateMinAndMaxValues() { qreal maxYValue = 0; qreal minYValue = 0; + qreal maxXValue = 0; + qreal minXValue = 0; if (m_chartItem->itemMode() == DesignMode) { maxYValue = 40; + maxXValue = 40; } else { 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_yAxisData = AxisData(minYValue, maxYValue); + m_xAxisData = AxisData(minXValue, maxXValue); } qreal AbstractSeriesChart::hPadding(QRectF chartRect) @@ -686,7 +770,6 @@ void AbstractSeriesChart::paintHorizontalGrid(QPainter *painter, QRectF gridRect painter->save(); const AxisData &yAxisData = this->yAxisData(); - const qreal delta = yAxisData.delta(); const int segmentCount = yAxisData.segmentCount(); const int lineCount = segmentCount + 1; @@ -694,15 +777,20 @@ void AbstractSeriesChart::paintHorizontalGrid(QPainter *painter, QRectF gridRect painter->setRenderHint(QPainter::Antialiasing,false); qreal hStep = (gridRect.width() - painter->fontMetrics().boundingRect(QString::number(maxValue())).width()) / segmentCount; - painter->setFont(adaptValuesFont(hStep-4, painter->font())); + 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++ ) { - painter->drawText(QRectF(gridRect.left() + 4 + hStep * i, gridRect.bottom() - painter->fontMetrics().height(), - hStep, painter->fontMetrics().height()), - QString::number(minValue() + i * delta / segmentCount)); - painter->drawLine( gridRect.left()+hStep*i, gridRect.bottom(), - gridRect.left()+hStep*i, gridRect.top()); - + 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(); } @@ -727,7 +815,7 @@ void AbstractSeriesChart::paintVerticalGrid(QPainter *painter, QRectF gridRect) const qreal y = vStep * i; painter->drawText(QRectF(gridRect.bottomLeft()-QPointF(textPositionOffset,y+halfFontHeight), QSizeF(valuesHMargin,fontHeight)), - verticalLabel(i, yAxisData.step(), yAxisData.rangeMin()), + axisLabel(i, yAxisData), verticalTextOption); painter->drawLine(gridRect.bottomLeft()-QPointF(-valuesHMargin,y), gridRect.bottomRight()-QPointF(0,y)); @@ -736,6 +824,73 @@ void AbstractSeriesChart::paintVerticalGrid(QPainter *painter, QRectF gridRect) painter->setRenderHint(QPainter::Antialiasing,true); } +void AbstractSeriesChart::paintGrid(QPainter *painter, QRectF gridRect) +{ + painter->save(); + + const AxisData &yAxisData = this->yAxisData(); + const AxisData &xAxisData = this->xAxisData(); + + painter->setRenderHint(QPainter::Antialiasing,false); + + const int xAxisSegmentCount = xAxisData.segmentCount(); + const int xAxisLineCount = xAxisSegmentCount + 1; + const int yAxisSegmentCount = yAxisData.segmentCount(); + const int yAxisLineCount = yAxisSegmentCount + 1; + + const 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().width(text) / 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().width(text) / 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(); @@ -761,7 +916,7 @@ qreal AbstractSeriesChart::valuesHMargin(QPainter *painter) const int yAxisLineCount = yAxisData.segmentCount() + 1; for (int i = 0 ; i < yAxisLineCount ; i++) { - const QString label = verticalLabel(i, yAxisData.step(), yAxisData.rangeMin()); + const QString label = axisLabel(i, yAxisData); max = std::max(max, (qreal)painter->fontMetrics().boundingRect(label).width()+offset); } return max; @@ -797,28 +952,33 @@ QFont AbstractSeriesChart::adaptLabelsFont(QRectF rect, QFont font) return tmpFont; } -QFont AbstractSeriesChart::adaptValuesFont(qreal width, QFont font) +QFont AbstractSeriesChart::adaptFont(qreal width, QFont font, const AxisData &axisData) { - QString strValue = QString::number(maxValue()); QFont tmpFont = font; + const int axisLineCount = axisData.segmentCount() + 1; QScopedPointer fm(new QFontMetricsF(tmpFont)); - 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(); + 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::verticalLabel(int i, qreal step, qreal min) +QString AbstractSeriesChart::axisLabel(int i, const AxisData &axisData) { + const qreal min = axisData.rangeMin(); + const qreal step = axisData.step(); 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); + return QString::number(round(value * 100.0) / 100.0); } void AbstractBarChart::paintChartLegend(QPainter *painter, QRectF legendRect) @@ -897,4 +1057,5 @@ 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 7e1ecac..a2f40a8 100644 --- a/limereport/items/lrchartitem.h +++ b/limereport/items/lrchartitem.h @@ -16,11 +16,12 @@ class SeriesItemData : public QObject{ Q_OBJECT public: QList& values(){ return m_values;} + QList& xAxisValues(){ return m_xAxisValues;} QList& labels(){ return m_labels;} QList& colors() { return m_colors;} void clear(){ m_values.clear(); m_labels.clear(); m_colors.clear(); } private: - QList m_values; + QList m_values, m_xAxisValues; QList m_labels; QList m_colors; }; @@ -30,6 +31,7 @@ class SeriesItem : public QObject{ Q_PROPERTY(QString name READ name WRITE setName) Q_PROPERTY(QString valuesColumn READ valuesColumn WRITE setValuesColumn) Q_PROPERTY(QString labelsColumn READ labelsColumn WRITE setLabelsColumn) + Q_PROPERTY(QString xAxisColumn READ xAxisColumn WRITE setXAxisColumn) Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(SeriesItemPreferredType preferredType READ preferredType WRITE setPreferredType) public: @@ -46,6 +48,8 @@ public: void setValuesColumn(const QString &valuesColumn); QString labelsColumn() const; void setLabelsColumn(const QString &labelsColumn); + QString xAxisColumn() const; + void setXAxisColumn(const QString &xAxisColumn); SeriesItem* clone(); void fillSeriesData(IDataSource* dataSource); SeriesItemData* data(){ return &m_data;} @@ -58,6 +62,7 @@ private: QString m_name; QString m_valuesColumn; QString m_labelsColumn; + QString m_xAxisColumn; SeriesItemData m_data; QColor m_color; SeriesItemPreferredType m_preferredType; @@ -84,9 +89,10 @@ class AbstractSeriesChart: public AbstractChart{ public: AbstractSeriesChart(ChartItem* chartItem); protected: + AxisData yAxisData(); + AxisData xAxisData(); qreal maxValue(); qreal minValue(); - AxisData yAxisData(); void updateMinAndMaxValues(); int valuesCount(); int seriesCount(); @@ -98,16 +104,17 @@ protected: virtual void paintHorizontalLabels(QPainter *painter, QRectF labelsRect); virtual void paintVerticalLabels(QPainter *painter, QRectF labelsRect); virtual void paintHorizontalGrid(QPainter *painter, QRectF gridRect); + virtual void paintGrid(QPainter *painter, QRectF gridRect); virtual void paintVerticalGrid(QPainter *painter, QRectF gridRect); virtual void drawSegment(QPainter *painter, QPoint startPoint, QPoint endPoint, QColor color); virtual qreal valuesHMargin(QPainter *painter); virtual qreal valuesVMargin(QPainter *painter); virtual QFont adaptLabelsFont(QRectF rect, QFont font); - virtual QFont adaptValuesFont(qreal width, QFont font); - virtual QString verticalLabel(int i, qreal step, qreal min); + virtual QFont adaptFont(qreal width, QFont font, const AxisData &axisData); + virtual QString axisLabel(int i, const AxisData &axisData); private: - AxisData m_yAxisData; + AxisData m_yAxisData, m_xAxisData; qreal m_designValues [9]; }; @@ -136,21 +143,37 @@ class ChartItem : public LimeReport::ItemDesignIntf //linesChart Q_PROPERTY(bool drawPoints READ drawPoints WRITE setDrawPoints) Q_PROPERTY(int seriesLineWidth READ seriesLineWidth WRITE setSeriesLineWidth) + Q_PROPERTY(bool horizontalAxisOnTop READ horizontalAxisOnTop WRITE setHorizontalAxisOnTop) + + //gridChart + Q_FLAGS(GridChartLines) + Q_PROPERTY(QString xAxisField READ xAxisField WRITE setXAxisField) + Q_PROPERTY(GridChartLines gridChartLines READ gridChartLines WRITE setGridChartLines) friend class AbstractChart; public: enum LegendAlign{LegendAlignTop,LegendAlignCenter,LegendAlignBottom}; + enum LegendStyle{LegendPoints, LegendLines}; enum TitleAlign{TitleAlignLeft, TitleAlignCenter, TitleAlignRight}; - enum ChartType{Pie, VerticalBar, HorizontalBar, Lines}; + enum ChartType{Pie, VerticalBar, HorizontalBar, Lines, GridLines}; + enum LineType { + NoLine = 0, + HorizontalLine = 1, + VerticalLine = 2, + AllLines = 3 + }; #if QT_VERSION >= 0x050500 Q_ENUM(LegendAlign) Q_ENUM(TitleAlign) Q_ENUM(ChartType) + Q_ENUM(LineType) #else Q_ENUMS(LegendAlign) Q_ENUMS(TitleAlign) Q_ENUMS(ChartType) + Q_ENUMS(LineType) #endif + Q_DECLARE_FLAGS(GridChartLines, LineType) ChartItem(QObject* owner, QGraphicsItem* parent); ~ChartItem(); @@ -194,6 +217,15 @@ public: int seriesLineWidth() const; void setSeriesLineWidth(int newSeriesLineWidth); + QString xAxisField() const; + void setXAxisField(const QString &xAxisField); + + bool horizontalAxisOnTop() const; + void setHorizontalAxisOnTop(bool horizontalAxisOnTop); + + GridChartLines gridChartLines() const; + void setGridChartLines(GridChartLines flags); + protected: void paintChartTitle(QPainter* painter, QRectF titleRect); virtual BaseDesignIntf* createSameTypeItem(QObject *owner, QGraphicsItem *parent); @@ -222,6 +254,9 @@ private: bool m_showLegend; bool m_drawPoints; int m_seriesLineWidth; + QString m_xAxisField; + bool m_horizontalAxisOnTop; + GridChartLines m_gridChartLines; }; } //namespace LimeReport #endif // LRCHARTITEM_H diff --git a/limereport/items/lrchartitemeditor.cpp b/limereport/items/lrchartitemeditor.cpp index 31a84ec..c609c46 100644 --- a/limereport/items/lrchartitemeditor.cpp +++ b/limereport/items/lrchartitemeditor.cpp @@ -106,6 +106,7 @@ void ChartItemEditor::init() for (int i=0;icolumnCount();++i){ ui->valuesFieldComboBox->addItem(ds->columnNameByIndex(i)); ui->labelsFieldComboBox->addItem(ds->columnNameByIndex(i)); + ui->xAxisFieldComboBox->addItem(ds->columnNameByIndex(i)); } } } @@ -120,8 +121,10 @@ void ChartItemEditor::init() #if QT_VERSION < 0x050000 ui->labelsFieldComboBox->setCurrentIndex(ui->labelsFieldComboBox->findText( m_charItem->labelsField())); + ui->xAxisFieldComboBox->setCurrentIndex(ui->xAxisFieldComboBox->findText( m_charItem->xAxisField())); #else ui->labelsFieldComboBox->setCurrentText(m_charItem->labelsField()); + ui->xAxisFieldComboBox->setCurrentText(m_charItem->xAxisField()); #endif if (!m_charItem->series().isEmpty()){ enableSeriesEditor(); @@ -287,3 +290,9 @@ void ChartItemEditor::on_seriesTypeComboBox_currentIndexChanged(const QString &a currentSeries()->setPreferredType(static_cast(enumerator.keysToValue(arg1.toLatin1()))); } } + +void ChartItemEditor::on_xAxisFieldComboBox_currentTextChanged(const QString &arg1) +{ + if (!m_initing) + m_charItem->setXAxisField(arg1); +} \ No newline at end of file diff --git a/limereport/items/lrchartitemeditor.h b/limereport/items/lrchartitemeditor.h index f24123f..64e0549 100644 --- a/limereport/items/lrchartitemeditor.h +++ b/limereport/items/lrchartitemeditor.h @@ -41,6 +41,8 @@ private slots: void slotChangeSeriesColor(); void on_seriesTypeComboBox_currentIndexChanged(const QString &arg1); + void on_xAxisFieldComboBox_currentTextChanged(const QString &arg1); + private: void readSetting(); void writeSetting(); diff --git a/limereport/items/lrchartitemeditor.ui b/limereport/items/lrchartitemeditor.ui index a745777..20a5f4b 100644 --- a/limereport/items/lrchartitemeditor.ui +++ b/limereport/items/lrchartitemeditor.ui @@ -144,16 +144,30 @@ - - + + + + + true + + + + Labels field - - + + + + X data field + + + + + true diff --git a/limereport/limereport.pri b/limereport/limereport.pri index bd6030c..4a7c156 100644 --- a/limereport/limereport.pri +++ b/limereport/limereport.pri @@ -57,6 +57,7 @@ SOURCES += \ $$REPORT_PATH/items/lrchartitemeditor.cpp \ $$REPORT_PATH/items/charts/lrhorizontalbarchart.cpp \ $$REPORT_PATH/items/charts/lrlineschart.cpp \ + $$REPORT_PATH/items/charts/lrgridlineschart.cpp \ $$REPORT_PATH/items/charts/lrpiechart.cpp \ $$REPORT_PATH/items/charts/lrverticalbarchart.cpp \ $$REPORT_PATH/lrbanddesignintf.cpp \ @@ -149,6 +150,7 @@ HEADERS += \ $$REPORT_PATH/items/lrchartitemeditor.h \ $$REPORT_PATH/items/charts/lrhorizontalbarchart.h \ $$REPORT_PATH/items/charts/lrlineschart.h \ + $$REPORT_PATH/items/charts/lrgridlineschart.h \ $$REPORT_PATH/items/charts/lrpiechart.h \ $$REPORT_PATH/items/charts/lrverticalbarchart.h \ $$REPORT_PATH/lrbanddesignintf.h \ diff --git a/limereport/lraxisdata.h b/limereport/lraxisdata.h index 84a40c7..820eeb1 100644 --- a/limereport/lraxisdata.h +++ b/limereport/lraxisdata.h @@ -20,9 +20,9 @@ public: 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; diff --git a/limereport/objectinspector/lrobjectitemmodel.cpp b/limereport/objectinspector/lrobjectitemmodel.cpp index 50da3f0..122a921 100644 --- a/limereport/objectinspector/lrobjectitemmodel.cpp +++ b/limereport/objectinspector/lrobjectitemmodel.cpp @@ -144,6 +144,7 @@ void QObjectPropertyModel::translatePropertyName() tr("chartType"); tr("drawLegendBorder"); tr("labelsField"); + tr("xAxisField"); tr("legendAlign"); tr("series"); tr("titleAlign"); @@ -170,6 +171,8 @@ void QObjectPropertyModel::translatePropertyName() tr("removeGap"); tr("dropPrinterMargins"); tr("notPrintIfEmpty"); + tr("gridChartLines"); + tr("horizontalAxisOnTop"); tr("mixWithPriorPage"); }