From 8d4b1aaadcff883d4432d51612a78ad2bee61031 Mon Sep 17 00:00:00 2001 From: Emil Sawicki Date: Tue, 8 Mar 2022 23:44:49 +0100 Subject: [PATCH] Extend Axis data class --- limereport/items/lrchartitem.cpp | 72 +++++++-- limereport/items/lrchartitem.h | 17 ++- limereport/lraxisdata.cpp | 253 +++++++++++++++++++++++++++++-- limereport/lraxisdata.h | 56 ++++++- limereport/lrbasedesignintf.cpp | 46 +++--- limereport/lrbasedesignintf.h | 3 + 6 files changed, 392 insertions(+), 55 deletions(-) diff --git a/limereport/items/lrchartitem.cpp b/limereport/items/lrchartitem.cpp index 357b8cb..170e6f6 100644 --- a/limereport/items/lrchartitem.cpp +++ b/limereport/items/lrchartitem.cpp @@ -147,6 +147,9 @@ ChartItem::ChartItem(QObject *owner, QGraphicsItem *parent) m_horizontalAxisOnTop(false), m_gridChartLines(AllLines), m_legendStyle(LegendPoints) { + m_xAxisData = new AxisData(this); + m_xAxisData->setReverseDirection(true); + m_yAxisData = new AxisData(this); m_labels<<"First"<<"Second"<<"Thrid"; m_chart = new PieChart(this); m_chart->setTitleFont(font()); @@ -224,6 +227,42 @@ void ChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 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; +} + BaseDesignIntf *ChartItem::createSameTypeItem(QObject *owner, QGraphicsItem *parent) { ChartItem* result = new ChartItem(owner,parent); @@ -289,10 +328,7 @@ void ChartItem::fillLabels(IDataSource *dataSource) QWidget *ChartItem::defaultEditor() { - QSettings* l_settings = (page()->settings() != 0) ? - page()->settings() : - (page()->reportEditor()!=0) ? page()->reportEditor()->settings() : 0; - QWidget* editor = new ChartItemEditor(this, page(), l_settings); + QWidget* editor = new ChartItemEditor(this, page(), settings()); editor->setAttribute(Qt::WA_DeleteOnClose); return editor; } @@ -302,6 +338,18 @@ 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; @@ -724,22 +772,22 @@ AbstractSeriesChart::AbstractSeriesChart(ChartItem *chartItem) qreal AbstractSeriesChart::maxValue() { - return m_yAxisData.maxValue(); + return m_chartItem->yAxisData()->maxValue(); } qreal AbstractSeriesChart::minValue() { - return m_yAxisData.minValue(); + return m_chartItem->yAxisData()->minValue(); } -AxisData AbstractSeriesChart::yAxisData() +AxisData &AbstractSeriesChart::xAxisData() const { - return m_yAxisData; + return *m_chartItem->xAxisData(); } -AxisData AbstractSeriesChart::xAxisData() +AxisData &AbstractSeriesChart::yAxisData() const { - return m_xAxisData; + return *m_chartItem->yAxisData(); } void AbstractSeriesChart::updateMinAndMaxValues() @@ -772,8 +820,8 @@ void AbstractSeriesChart::updateMinAndMaxValues() } } - m_yAxisData = AxisData(minYValue, maxYValue); - m_xAxisData = AxisData(minXValue, maxXValue); + m_chartItem->xAxisData()->update(minXValue, maxXValue); + m_chartItem->yAxisData()->update(minYValue, maxYValue); } qreal AbstractSeriesChart::hPadding(QRectF chartRect) diff --git a/limereport/items/lrchartitem.h b/limereport/items/lrchartitem.h index 9c0b05f..076873b 100644 --- a/limereport/items/lrchartitem.h +++ b/limereport/items/lrchartitem.h @@ -97,8 +97,8 @@ class AbstractSeriesChart: public AbstractChart{ public: AbstractSeriesChart(ChartItem* chartItem); protected: - AxisData yAxisData(); - AxisData xAxisData(); + AxisData &xAxisData() const; + AxisData &yAxisData() const; qreal maxValue(); qreal minValue(); void updateMinAndMaxValues(); @@ -125,7 +125,6 @@ private: bool calculateLegendColumnWidths(qreal indicatorWidth, qreal maxWidth, const QFontMetrics &fm); bool calculateLegendSingleColumnWidth(qreal ¤tRowWidth, int ¤tColumn, int &maxColumnCount, const qreal itemWidth, const qreal maxRowWidth); - AxisData m_yAxisData, m_xAxisData; qreal m_designValues [9]; }; @@ -146,6 +145,8 @@ private: class ChartItem : public LimeReport::ItemDesignIntf { Q_OBJECT + Q_PROPERTY(QObject* xAxisSettings READ xAxisSettings WRITE setXAxisSettings) + Q_PROPERTY(QObject* yAxisSettings READ yAxisSettings WRITE setYAxisSettings) Q_PROPERTY(ACollectionProperty series READ fakeCollectionReader WRITE setSeries) Q_PROPERTY(QString datasource READ datasource WRITE setDatasource) Q_PROPERTY(QString chartTitle READ chartTitle WRITE setChartTitle) @@ -201,6 +202,13 @@ public: ~ChartItem(); virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QObject* xAxisSettings(); + void setYAxisSettings(QObject *axis); + QObject* yAxisSettings(); + void setXAxisSettings(QObject *axis); + + AxisData *xAxisData(); + AxisData *yAxisData(); QList &series(); void setSeries(const QList &series); bool isSeriesExists(const QString& name); @@ -255,6 +263,8 @@ public: void setTitleFont(QFont value); void setCharItemFont(QFont value); + QSettings *settings(); + protected: void paintChartTitle(QPainter* painter, QRectF titleRect); virtual BaseDesignIntf* createSameTypeItem(QObject *owner, QGraphicsItem *parent); @@ -287,6 +297,7 @@ private: bool m_horizontalAxisOnTop; GridChartLines m_gridChartLines; LegendStyle m_legendStyle; + AxisData *m_xAxisData, *m_yAxisData; }; } //namespace LimeReport #endif // LRCHARTITEM_H diff --git a/limereport/lraxisdata.cpp b/limereport/lraxisdata.cpp index fbe7f2a..7b5803b 100644 --- a/limereport/lraxisdata.cpp +++ b/limereport/lraxisdata.cpp @@ -1,21 +1,46 @@ #include "lraxisdata.h" -namespace LimeReport { -AxisData::AxisData() - : m_rangeMin(0), m_rangeMax(0), - m_minValue(0), m_maxValue(0), m_step(0), - m_delta(0), m_segmentCount(4) -{ +#include +#include +namespace LimeReport { +AxisData::AxisData(QObject *parent) + : QObject(parent), m_rangeMin(0), m_rangeMax(0), + m_minValue(0), m_maxValue(0), m_step(0), + m_delta(0), m_segmentCount(4), m_calculateAxisScale(false), + m_reverseDirection(false), m_manualMaximum(0), + m_manualMinimum(0), m_manualStep(0), m_isMaximumAutomatic(true), + m_isMinimumAutomatic(true), m_isStepAutomatic(true) +{ } -AxisData::AxisData(qreal minValue, qreal maxValue) - : AxisData() +void AxisData::copy(AxisData *other) +{ + m_calculateAxisScale = other->calculateAxisScale(); + m_reverseDirection = other->reverseDirection(); + m_manualMaximum = other->manualMaximum(); + m_manualMinimum = other->manualMinimum(); + m_manualStep = other->manualStep(); + m_isMaximumAutomatic = other->isMaximumAutomatic(); + m_isMinimumAutomatic = other->isMinimumAutomatic(); + m_isStepAutomatic = other->isStepAutomatic(); +} + +void AxisData::update() +{ + if (m_calculateAxisScale) { + calculateRoundedAxisScale(); + } else { + calculateSimpleAxisScale(); + } + m_delta = m_step * m_segmentCount; +} + +void AxisData::update(qreal minValue, qreal maxValue) { m_minValue = minValue; m_maxValue = maxValue; - calculateValuesAboveMax(minValue, maxValue, 4); - m_delta = m_step * m_segmentCount; + update(); } int AxisData::segmentCount() const @@ -23,6 +48,11 @@ int AxisData::segmentCount() const return m_segmentCount; } +bool AxisData::calculateAxisScale() const +{ + return m_calculateAxisScale; +} + qreal AxisData::rangeMin() const { return m_rangeMin; @@ -53,16 +83,207 @@ qreal AxisData::delta() const return m_delta; } -void AxisData::calculateValuesAboveMax(qreal minValue, qreal maxValue, int segments) +void AxisData::calculateRoundedAxisScale() { - const int delta = maxValue - minValue; + const int maximumSegmentCount = 10; + + bool calculateStep = isStepAutomatic(); + const bool calculateMinimum = isMinimumAutomatic(); + const bool calculateMaximum = isMaximumAutomatic(); + + qreal temporaryMin = calculateMinimum ? minValue() < 0 ? minValue() : 0 : manualMinimum(); + qreal temporaryMax = calculateMaximum ? maxValue() : manualMaximum(); + m_step = calculateStep ? 0 : manualStep(); + + if (temporaryMax == temporaryMin) { + if (temporaryMax == 0) { + temporaryMax = 1; + } else { + temporaryMax *= 2; + } + } + + const qreal minAndMaxSpacingOffset = 0.95; + + qreal stepMagnitude = 0.0; + qreal normalizedStep = 0.0; + bool isStepNormalized = false; + bool isLoopFinished = false; + + // Calculate until segment count is below maximum + while( !isLoopFinished ) { + if (calculateStep) { + if(isStepNormalized) { + if( normalizedStep == 1.0 ) { + normalizedStep = 2.0; + } else if( normalizedStep == 2.0 ) { + normalizedStep = 5.0; + } else { + normalizedStep = 1.0; + stepMagnitude *= 10; + } + } else { + const double startingStep = (temporaryMax - temporaryMin) / maximumSegmentCount; + const int exponent = static_cast< int >( floor( log10( startingStep ) ) ); + stepMagnitude = pow(10.0, static_cast(exponent)); + normalizedStep = startingStep / stepMagnitude; + if( normalizedStep <= 1.0 ) { + normalizedStep = 1.0; + } else if( normalizedStep <= 2.0 ) { + normalizedStep = 2.0; + } else if( normalizedStep <= 5.0 ) { + normalizedStep = 5.0; + } else { + normalizedStep = 1.0; + stepMagnitude *= 10; + } + isStepNormalized = true; + } + m_step = normalizedStep * stepMagnitude; + } + + qreal currentAxisMinimum = temporaryMin; + qreal currentAxisMaximum = temporaryMax; + + if (calculateMinimum) { + currentAxisMinimum = calculateNewMinimum(currentAxisMinimum, m_step); + const qreal currentDelta = currentAxisMaximum - currentAxisMinimum; + const qreal actualDelta = currentAxisMaximum - minValue(); + if ((currentAxisMinimum != 0.0) && ((actualDelta / currentDelta) > minAndMaxSpacingOffset)) { + currentAxisMinimum -= m_step; + } + } + + if (calculateMaximum) { + currentAxisMaximum = calculateNewMaximum(currentAxisMaximum, m_step); + const qreal currentDelta = currentAxisMaximum - currentAxisMinimum; + const qreal actualDelta = maxValue() - currentAxisMinimum; + if ((currentAxisMaximum != 0.0) && ((actualDelta / currentDelta) > minAndMaxSpacingOffset)) { + currentAxisMaximum += m_step; + } + } + + m_segmentCount = static_cast(round((currentAxisMaximum - currentAxisMinimum) / m_step)); + m_rangeMin = currentAxisMinimum; + m_rangeMax = currentAxisMaximum; + isLoopFinished = m_segmentCount <= maximumSegmentCount; + if (!isLoopFinished) { + // Configured step may be invalid, calculating it automatically + calculateStep = true; + } + } +} + +void AxisData::calculateSimpleAxisScale() +{ + m_segmentCount = 4; + const int delta = maxValue() - minValue(); int max = delta; - while (max % segments != 0){ + while (max % m_segmentCount != 0){ max++; } m_rangeMax = max; - m_step = max / segments; - m_rangeMin = minValue; - m_segmentCount = segments; + m_step = max / m_segmentCount; + m_rangeMin = minValue(); } + +double AxisData::calculateNewMinimum(qreal min, qreal step) const +{ + if (step <= 0.0) + return min; + + double ret = floor(min / step) * step; + if (ret > min && !qFuzzyCompare(ret, min)) { + ret -= step; + } + return ret; +} + +double AxisData::calculateNewMaximum(qreal max, qreal step) const +{ + if (step <= 0.0) + return max; + + double ret = floor(max / step) * step; + if (ret < max && !qFuzzyCompare(ret, max)) { + ret += step; + } + return ret; +} + +void AxisData::setCalculateAxisScale(bool newCalculateAxisScale) +{ + m_calculateAxisScale = newCalculateAxisScale; +} + +bool AxisData::reverseDirection() const +{ + return m_reverseDirection; +} + +void AxisData::setReverseDirection(bool reverseDirection) +{ + m_reverseDirection = reverseDirection; +} + +qreal AxisData::manualMaximum() const +{ + return m_manualMaximum; +} + +void AxisData::setManualMaximum(qreal newManualMaximum) +{ + m_manualMaximum = newManualMaximum; +} + +qreal AxisData::manualMinimum() const +{ + return m_manualMinimum; +} + +void AxisData::setManualMinimum(qreal newManualMinimum) +{ + m_manualMinimum = newManualMinimum; +} + +qreal AxisData::manualStep() const +{ + return m_manualStep; +} + +void AxisData::setManualStep(qreal newManualStep) +{ + m_manualStep = newManualStep; +} + +bool AxisData::isMaximumAutomatic() const +{ + return m_isMaximumAutomatic; +} + +void AxisData::setIsMaximumAutomatic(bool newIsMaximumAutomatic) +{ + m_isMaximumAutomatic = newIsMaximumAutomatic; +} + +bool AxisData::isMinimumAutomatic() const +{ + return m_isMinimumAutomatic; +} + +void AxisData::setIsMinimumAutomatic(bool newIsMinimumAutomatic) +{ + m_isMinimumAutomatic = newIsMinimumAutomatic; +} + +bool AxisData::isStepAutomatic() const +{ + return m_isStepAutomatic; +} + +void AxisData::setIsStepAutomatic(bool newIsStepAutomatic) +{ + m_isStepAutomatic = newIsStepAutomatic; +} + } diff --git a/limereport/lraxisdata.h b/limereport/lraxisdata.h index 820eeb1..0492b4a 100644 --- a/limereport/lraxisdata.h +++ b/limereport/lraxisdata.h @@ -1,14 +1,26 @@ #ifndef AXISDATA_H #define AXISDATA_H -#include +#include namespace LimeReport { -class AxisData +class AxisData : public QObject { + Q_OBJECT + Q_PROPERTY(bool reverseDirection READ reverseDirection WRITE setReverseDirection) + Q_PROPERTY(bool calculateAxisScale READ calculateAxisScale WRITE setCalculateAxisScale) + Q_PROPERTY(bool isStepAutomatic READ isStepAutomatic WRITE setIsStepAutomatic) + Q_PROPERTY(bool isMinimumAutomatic READ isMinimumAutomatic WRITE setIsMinimumAutomatic) + Q_PROPERTY(bool isMaximumAutomatic READ isMaximumAutomatic WRITE setIsMaximumAutomatic) + Q_PROPERTY(qreal manualStep READ manualStep WRITE setManualStep) + Q_PROPERTY(qreal manualMinimum READ manualMinimum WRITE setManualMinimum) + Q_PROPERTY(qreal manualMaximum READ manualMaximum WRITE setManualMaximum) public: - AxisData(); - AxisData(qreal minValue, qreal maxValue); + AxisData(QObject *parent = nullptr); + + void copy(AxisData *other); + void update(); + void update(qreal minValue, qreal maxValue); int segmentCount() const; @@ -21,8 +33,34 @@ public: qreal delta() const; + bool reverseDirection() const; + void setReverseDirection(bool newReverseDirection); + bool calculateAxisScale() const; + void setCalculateAxisScale(bool newCalculateAxisScale); + + qreal manualMaximum() const; + void setManualMaximum(qreal newManualMaximum); + + qreal manualMinimum() const; + void setManualMinimum(qreal newManualMinimum); + + qreal manualStep() const; + void setManualStep(qreal newManualStep); + + bool isMaximumAutomatic() const; + void setIsMaximumAutomatic(bool newIsMaximumAutomatic); + + bool isMinimumAutomatic() const; + void setIsMinimumAutomatic(bool newIsMinimumAutomatic); + + bool isStepAutomatic() const; + void setIsStepAutomatic(bool newIsStepAutomatic); + private: - void calculateValuesAboveMax(qreal minValue, qreal maxValue, int segments); + void calculateRoundedAxisScale(); + void calculateSimpleAxisScale(); + qreal calculateNewMinimum(qreal min, qreal step) const; + qreal calculateNewMaximum(qreal max, qreal step) const; qreal m_rangeMin; qreal m_rangeMax; @@ -31,6 +69,14 @@ private: qreal m_step; qreal m_delta; int m_segmentCount; + bool m_calculateAxisScale; + bool m_reverseDirection; + qreal m_manualMaximum; + qreal m_manualMinimum; + qreal m_manualStep; + bool m_isMaximumAutomatic; + bool m_isMinimumAutomatic; + bool m_isStepAutomatic; }; }; diff --git a/limereport/lrbasedesignintf.cpp b/limereport/lrbasedesignintf.cpp index 8d35fab..41082e6 100644 --- a/limereport/lrbasedesignintf.cpp +++ b/limereport/lrbasedesignintf.cpp @@ -1290,32 +1290,40 @@ void BaseDesignIntf::setItemPos(const QPointF &newPos) } -QWidget* findRootWidget(QWidget* widget){ +QWidget* BaseDesignIntf::findRootWidget(QWidget* widget) +{ while (widget->parentWidget()) { widget = widget->parentWidget(); } return widget; } -void BaseDesignIntf::showEditorDialog(){ - QWidget *editor = defaultEditor(); - if (editor) { - editor->setStyleSheet(findRootWidget(scene()->views().at(0))->styleSheet()); - QDialog* dialog = new QDialog(QApplication::activeWindow()); - dialog->setAttribute(Qt::WA_DeleteOnClose); -#ifdef Q_OS_MAC - dialog->setWindowModality(Qt::WindowModal); -#else - dialog->setWindowModality(Qt::ApplicationModal); -#endif - dialog->setLayout(new QVBoxLayout()); - dialog->resize(editor->size()); - dialog->layout()->setContentsMargins(2,2,2,2); - dialog->layout()->addWidget(editor); - connect(editor,SIGNAL(destroyed()),dialog,SLOT(close())); - dialog->setWindowTitle(editor->windowTitle()); - dialog->exec(); +void BaseDesignIntf::showDialog(QWidget *widget) +{ + if (!widget) { + return; } + widget->setStyleSheet(findRootWidget(scene()->views().at(0))->styleSheet()); + QDialog dialog; + widget->setParent(&dialog); + widget->setAttribute(Qt::WA_DeleteOnClose); +#ifdef Q_OS_MAC + dialog.setWindowModality(Qt::WindowModal); +#else + dialog.setWindowModality(Qt::ApplicationModal); +#endif + dialog.setLayout(new QVBoxLayout()); + dialog.resize(widget->size()); + dialog.layout()->setContentsMargins(2,2,2,2); + dialog.layout()->addWidget(widget); + connect(widget,SIGNAL(destroyed()),&dialog,SLOT(close())); + dialog.setWindowTitle(widget->windowTitle()); + dialog.exec(); +} + +void BaseDesignIntf::showEditorDialog() +{ + showDialog(defaultEditor()); } void BaseDesignIntf::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) diff --git a/limereport/lrbasedesignintf.h b/limereport/lrbasedesignintf.h index 511afce..c311f16 100644 --- a/limereport/lrbasedesignintf.h +++ b/limereport/lrbasedesignintf.h @@ -397,6 +397,9 @@ protected: qreal calcAbsolutePosY(qreal currentOffset, BaseDesignIntf* item); qreal calcAbsolutePosX(qreal currentOffset, BaseDesignIntf* item); + QWidget* findRootWidget(QWidget* widget); + void showDialog(QWidget *widget); + private: int resizeDirectionFlags(QPointF position); void moveSelectedItems(QPointF delta);