From 7c9f8e5a27d8b5570d700aa992a35077f2022c4a 2015-07-03 07:51:57 From: Titta Heikkala Date: 2015-07-03 07:51:57 Subject: [PATCH] Add possibility to set reverse values to axes Added support for reverse axis. This works with line, spline, scatter and area series with cartesian chart. QBarCategoryAxis is not supported, nor different bar series. Change-Id: I60f21372ea9cee7b49918d7d99de24671bdc42c3 Reviewed-by: Miikka Heikkinen --- diff --git a/src/charts/areachart/areachartitem.cpp b/src/charts/areachart/areachartitem.cpp index 99095cf..fea5913 100644 --- a/src/charts/areachart/areachartitem.cpp +++ b/src/charts/areachart/areachartitem.cpp @@ -183,6 +183,9 @@ void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt painter->setClipRegion(QRegion(clipRect.toRect(), QRegion::Ellipse)); else painter->setClipRect(clipRect); + + reversePainter(painter, clipRect); + painter->drawPath(m_path); if (m_pointsVisible) { painter->setPen(m_pointPen); @@ -191,6 +194,8 @@ void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt painter->drawPoints(m_lower->geometryPoints()); } + reversePainter(painter, clipRect); + // Draw series point label if (m_pointLabelsVisible) { static const QString xPointTag(QLatin1String("@xPoint")); @@ -214,9 +219,17 @@ void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt // Position text in relation to the point int pointLabelWidth = fm.width(pointLabel); QPointF position(m_upper->geometryPoints().at(i)); - position.setX(position.x() - pointLabelWidth / 2); - position.setY(position.y() - m_series->upperSeries()->pen().width() / 2 - labelOffset); - + if (!seriesPrivate()->reverseXAxis()) + position.setX(position.x() - pointLabelWidth / 2); + else + position.setX(domain()->size().width() - position.x() - pointLabelWidth / 2); + if (!seriesPrivate()->reverseYAxis()) { + position.setY(position.y() - m_series->upperSeries()->pen().width() / 2 + - labelOffset); + } else { + position.setY(domain()->size().height() - position.y() + - m_series->upperSeries()->pen().width() / 2 - labelOffset); + } painter->drawText(position, pointLabel); } } @@ -232,8 +245,17 @@ void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt // Position text in relation to the point int pointLabelWidth = fm.width(pointLabel); QPointF position(m_lower->geometryPoints().at(i)); - position.setX(position.x() - pointLabelWidth / 2); - position.setY(position.y() - m_series->lowerSeries()->pen().width() / 2 - labelOffset); + if (!seriesPrivate()->reverseXAxis()) + position.setX(position.x() - pointLabelWidth / 2); + else + position.setX(domain()->size().width() - position.x() - pointLabelWidth / 2); + if (!seriesPrivate()->reverseYAxis()) { + position.setY(position.y() - m_series->lowerSeries()->pen().width() / 2 + - labelOffset); + } else { + position.setY(domain()->size().height() - position.y() + - m_series->lowerSeries()->pen().width() / 2 - labelOffset); + } painter->drawText(position, pointLabel); } } diff --git a/src/charts/axis/chartaxiselement.cpp b/src/charts/axis/chartaxiselement.cpp index cf4b417..90cce21 100644 --- a/src/charts/axis/chartaxiselement.cpp +++ b/src/charts/axis/chartaxiselement.cpp @@ -91,6 +91,7 @@ void ChartAxisElement::connectSlots() QObject::connect(axis(), SIGNAL(titleBrushChanged(const QBrush&)), this, SLOT(handleTitleBrushChanged(const QBrush&))); QObject::connect(axis(), SIGNAL(titleVisibleChanged(bool)), this, SLOT(handleTitleVisibleChanged(bool))); QObject::connect(axis()->d_ptr.data(), SIGNAL(rangeChanged(qreal, qreal)), this, SLOT(handleRangeChanged(qreal, qreal))); + QObject::connect(axis(), SIGNAL(reverseChanged(bool)), this, SLOT(handleReverseChanged(bool))); } void ChartAxisElement::handleArrowVisibleChanged(bool visible) @@ -208,6 +209,14 @@ void ChartAxisElement::handleRangeChanged(qreal min, qreal max) } } +void ChartAxisElement::handleReverseChanged(bool reverse) +{ + Q_UNUSED(reverse); + + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); +} + bool ChartAxisElement::isEmpty() { return axisGeometry().isEmpty() diff --git a/src/charts/axis/chartaxiselement_p.h b/src/charts/axis/chartaxiselement_p.h index 755936f..f1acf63 100644 --- a/src/charts/axis/chartaxiselement_p.h +++ b/src/charts/axis/chartaxiselement_p.h @@ -123,6 +123,7 @@ public Q_SLOTS: void handleTitleTextChanged(const QString &title); void handleTitleVisibleChanged(bool visible); void handleRangeChanged(qreal min, qreal max); + void handleReverseChanged(bool reverse); Q_SIGNALS: void clicked(); diff --git a/src/charts/axis/horizontalaxis.cpp b/src/charts/axis/horizontalaxis.cpp index f2dfdcf..875bf47 100644 --- a/src/charts/axis/horizontalaxis.cpp +++ b/src/charts/axis/horizontalaxis.cpp @@ -102,10 +102,20 @@ void HorizontalAxis::updateGeometry() QGraphicsTextItem *labelItem = static_cast(labels.at(i)); //grid line - gridItem->setLine(layout[i], gridRect.top(), layout[i], gridRect.bottom()); + if (axis()->isReverse()) { + gridItem->setLine(gridRect.right() - layout[i] + gridRect.left(), gridRect.top(), + gridRect.right() - layout[i] + gridRect.left(), gridRect.bottom()); + } else { + gridItem->setLine(layout[i], gridRect.top(), layout[i], gridRect.bottom()); + } //label text wrapping - QString text = labelList.at(i); + QString text; + if (axis()->isReverse() && axis()->type() != QAbstractAxis::AxisTypeCategory) + text = labelList.at(labelList.count() - i - 1); + else + text = labelList.at(i); + QRectF boundingRect; // don't truncate empty labels if (text.isEmpty()) { @@ -130,18 +140,50 @@ void HorizontalAxis::updateGeometry() //ticks and label position if (axis()->alignment() == Qt::AlignTop) { - labelItem->setPos(layout[i] - center.x(), axisRect.bottom() - rect.height() + (heightDiff / 2.0) - labelPadding()); - tickItem->setLine(layout[i], axisRect.bottom(), layout[i], axisRect.bottom() - labelPadding()); + if (axis()->isReverse()) { + labelItem->setPos(gridRect.right() - layout[layout.size() - i - 1] + + gridRect.left() - center.x(), + axisRect.bottom() - rect.height() + + (heightDiff / 2.0) - labelPadding()); + tickItem->setLine(gridRect.right() + gridRect.left() - layout[i], + axisRect.bottom(), + gridRect.right() + gridRect.left() - layout[i], + axisRect.bottom() - labelPadding()); + } else { + labelItem->setPos(layout[i] - center.x(), axisRect.bottom() - rect.height() + + (heightDiff / 2.0) - labelPadding()); + tickItem->setLine(layout[i], axisRect.bottom(), + layout[i], axisRect.bottom() - labelPadding()); + } } else if (axis()->alignment() == Qt::AlignBottom) { - labelItem->setPos(layout[i] - center.x(), axisRect.top() - (heightDiff / 2.0) + labelPadding()); - tickItem->setLine(layout[i], axisRect.top(), layout[i], axisRect.top() + labelPadding()); + if (axis()->isReverse()) { + labelItem->setPos(gridRect.right() - layout[layout.size() - i - 1] + + gridRect.left() - center.x(), + axisRect.top() - (heightDiff / 2.0) + labelPadding()); + tickItem->setLine(gridRect.right() + gridRect.left() - layout[i], axisRect.top(), + gridRect.right() + gridRect.left() - layout[i], + axisRect.top() + labelPadding()); + } else { + labelItem->setPos(layout[i] - center.x(), axisRect.top() - (heightDiff / 2.0) + + labelPadding()); + tickItem->setLine(layout[i], axisRect.top(), + layout[i], axisRect.top() + labelPadding()); + } } //label in between bool forceHide = false; if (intervalAxis() && (i + 1) != layout.size()) { - qreal leftBound = qMax(layout[i], gridRect.left()); - qreal rightBound = qMin(layout[i + 1], gridRect.right()); + qreal leftBound; + qreal rightBound; + if (axis()->isReverse()) { + leftBound = qMax(gridRect.right() + gridRect.left() - layout[i + 1], + gridRect.left()); + rightBound = qMin(gridRect.right() + gridRect.left() - layout[i], gridRect.right()); + } else { + leftBound = qMax(layout[i], gridRect.left()); + rightBound = qMin(layout[i + 1], gridRect.right()); + } const qreal delta = rightBound - leftBound; if (axis()->type() != QAbstractAxis::AxisTypeCategory) { // Hide label in case visible part of the category at the grid edge is too narrow @@ -163,7 +205,10 @@ void HorizontalAxis::updateGeometry() } } else if (categoryAxis->labelsPosition() == QCategoryAxis::AxisLabelsPositionOnValue) { - labelItem->setPos(rightBound - center.x(), labelItem->pos().y()); + if (axis()->isReverse()) + labelItem->setPos(leftBound - center.x(), labelItem->pos().y()); + else + labelItem->setPos(rightBound - center.x(), labelItem->pos().y()); } } } @@ -188,14 +233,29 @@ void HorizontalAxis::updateGeometry() qreal leftBound; qreal rightBound; if (i == 0) { - leftBound = gridRect.left(); - rightBound = layout[0]; - } else { - leftBound = layout[i]; - if (i == layout.size() - 1) + if (axis()->isReverse()) { + leftBound = gridRect.right() + gridRect.left() - layout[i]; rightBound = gridRect.right(); - else - rightBound = qMin(layout[i + 1], gridRect.right()); + } else { + leftBound = gridRect.left(); + rightBound = layout[0]; + } + } else { + if (axis()->isReverse()) { + rightBound = gridRect.right() + gridRect.left() - layout[i]; + if (i == layout.size() - 1) { + leftBound = gridRect.left(); + } else { + leftBound = qMax(gridRect.right() + gridRect.left() - layout[i + 1], + gridRect.left()); + } + } else { + leftBound = layout[i]; + if (i == layout.size() - 1) + rightBound = gridRect.right(); + else + rightBound = qMin(layout[i + 1], gridRect.right()); + } } if (leftBound < gridRect.left()) leftBound = gridRect.left(); diff --git a/src/charts/axis/qabstractaxis.cpp b/src/charts/axis/qabstractaxis.cpp index da5bb51..be5d3fe 100644 --- a/src/charts/axis/qabstractaxis.cpp +++ b/src/charts/axis/qabstractaxis.cpp @@ -246,6 +246,23 @@ QT_CHARTS_BEGIN_NAMESPACE */ /*! + \property QAbstractAxis::reverse + The reverse property defines if reverse axis is used. By default the value is false. + + Reverse axis is supported with line, spline, scatter and area series with cartesian chart. + All axes of the same orientation attached to same series must be reversed if one is reversed or + the behavior is undefined. +*/ +/*! + \qmlproperty alignment AbstractAxis::reverse + The reverse property defines if reverse axis is used. By default the value is false. + + Reverse axis is supported with line, spline, scatter and area series with cartesian chart. + All axes of the same orientation attached to same series must be reversed if one is reversed or + the behavior is undefined. +*/ + +/*! \fn void QAbstractAxis::visibleChanged(bool visible) Visibility of the axis has changed to \a visible. */ @@ -831,6 +848,19 @@ Qt::Alignment QAbstractAxis::alignment() const return d_ptr->alignment(); } +bool QAbstractAxis::isReverse() const +{ + return d_ptr->m_reverse; +} + +void QAbstractAxis::setReverse(bool reverse) +{ + if (d_ptr->m_reverse != reverse && type() != QAbstractAxis::AxisTypeBarCategory) { + d_ptr->m_reverse = reverse; + emit reverseChanged(reverse); + } +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QAbstractAxisPrivate::QAbstractAxisPrivate(QAbstractAxis *q) @@ -855,7 +885,8 @@ QAbstractAxisPrivate::QAbstractAxisPrivate(QAbstractAxis *q) m_shadesPen(QChartPrivate::defaultPen()), m_shadesBrush(QChartPrivate::defaultBrush()), m_shadesOpacity(1.0), - m_dirty(false) + m_dirty(false), + m_reverse(false) { } diff --git a/src/charts/axis/qabstractaxis.h b/src/charts/axis/qabstractaxis.h index 1664c53..585a1c6 100644 --- a/src/charts/axis/qabstractaxis.h +++ b/src/charts/axis/qabstractaxis.h @@ -61,6 +61,7 @@ class QT_CHARTS_EXPORT QAbstractAxis : public QObject Q_PROPERTY(Qt::Orientation orientation READ orientation) //aligment Q_PROPERTY(Qt::Alignment alignment READ alignment) + Q_PROPERTY(bool reverse READ isReverse WRITE setReverse NOTIFY reverseChanged) public: @@ -145,6 +146,10 @@ public: void setMax(const QVariant &max); void setRange(const QVariant &min, const QVariant &max); + //reverse handling + void setReverse(bool reverse = true); + bool isReverse() const; + Q_SIGNALS: void visibleChanged(bool visible); void linePenChanged(const QPen &pen); @@ -166,6 +171,7 @@ Q_SIGNALS: void shadesBorderColorChanged(QColor color); void shadesPenChanged(const QPen &pen); void shadesBrushChanged(const QBrush &brush); + void reverseChanged(bool reverse); protected: QScopedPointer d_ptr; diff --git a/src/charts/axis/qabstractaxis_p.h b/src/charts/axis/qabstractaxis_p.h index a0a32a0..6b5f0c2 100644 --- a/src/charts/axis/qabstractaxis_p.h +++ b/src/charts/axis/qabstractaxis_p.h @@ -118,6 +118,8 @@ private: bool m_dirty; + bool m_reverse; + friend class QAbstractAxis; friend class ChartDataSet; friend class ChartPresenter; diff --git a/src/charts/axis/verticalaxis.cpp b/src/charts/axis/verticalaxis.cpp index 89760c3..f42b8e0 100644 --- a/src/charts/axis/verticalaxis.cpp +++ b/src/charts/axis/verticalaxis.cpp @@ -104,10 +104,20 @@ void VerticalAxis::updateGeometry() QGraphicsTextItem *labelItem = static_cast(labels.at(i)); //grid line - gridItem->setLine(gridRect.left(), layout[i], gridRect.right(), layout[i]); + if (axis()->isReverse()) { + gridItem->setLine(gridRect.left(), gridRect.top() + gridRect.bottom() - layout[i], + gridRect.right(), gridRect.top() + gridRect.bottom() - layout[i]); + } else { + gridItem->setLine(gridRect.left(), layout[i], gridRect.right(), layout[i]); + } //label text wrapping - QString text = labelList.at(i); + QString text; + if (axis()->isReverse() && axis()->type() != QAbstractAxis::AxisTypeCategory) + text = labelList.at(labelList.count() - i - 1); + else + text = labelList.at(i); + QRectF boundingRect; // don't truncate empty labels if (text.isEmpty()) { @@ -132,19 +142,54 @@ void VerticalAxis::updateGeometry() //ticks and label position if (axis()->alignment() == Qt::AlignLeft) { - labelItem->setPos(axisRect.right() - rect.width() + (widthDiff / 2.0) - labelPadding(), layout[i] - center.y()); - tickItem->setLine(axisRect.right() - labelPadding(), layout[i], axisRect.right(), layout[i]); + if (axis()->isReverse()) { + labelItem->setPos(axisRect.right() - rect.width() + (widthDiff / 2.0) + - labelPadding(), + gridRect.top() + gridRect.bottom() + - layout[layout.size() - i - 1] - center.y()); + tickItem->setLine(axisRect.right() - labelPadding(), + gridRect.top() + gridRect.bottom() - layout[i], + axisRect.right(), + gridRect.top() + gridRect.bottom() - layout[i]); + } else { + labelItem->setPos(axisRect.right() - rect.width() + (widthDiff / 2.0) + - labelPadding(), + layout[i] - center.y()); + tickItem->setLine(axisRect.right() - labelPadding(), layout[i], + axisRect.right(), layout[i]); + } } else if (axis()->alignment() == Qt::AlignRight) { - labelItem->setPos(axisRect.left() + labelPadding() - (widthDiff / 2.0), layout[i] - center.y()); - tickItem->setLine(axisRect.left(), layout[i], axisRect.left() + labelPadding(), layout[i]); + if (axis()->isReverse()) { + tickItem->setLine(axisRect.left(), + gridRect.top() + gridRect.bottom() - layout[i], + axisRect.left() + labelPadding(), + gridRect.top() + gridRect.bottom() - layout[i]); + labelItem->setPos(axisRect.left() + labelPadding() - (widthDiff / 2.0), + gridRect.top() + gridRect.bottom() + - layout[layout.size() - i - 1] - center.y()); + } else { + labelItem->setPos(axisRect.left() + labelPadding() - (widthDiff / 2.0), + layout[i] - center.y()); + tickItem->setLine(axisRect.left(), layout[i], + axisRect.left() + labelPadding(), layout[i]); + } } //label in between bool forceHide = false; bool labelOnValue = false; if (intervalAxis() && (i + 1) != layout.size()) { - qreal lowerBound = qMin(layout[i], gridRect.bottom()); - qreal upperBound = qMax(layout[i + 1], gridRect.top()); + qreal lowerBound; + qreal upperBound; + if (axis()->isReverse()) { + lowerBound = qMax(gridRect.top() + gridRect.bottom() - layout[i + 1], + gridRect.top()); + upperBound = qMin(gridRect.top() + gridRect.bottom() - layout[i], + gridRect.bottom()); + } else { + lowerBound = qMin(layout[i], gridRect.bottom()); + upperBound = qMax(layout[i + 1], gridRect.top()); + } const qreal delta = lowerBound - upperBound; if (axis()->type() != QAbstractAxis::AxisTypeCategory) { // Hide label in case visible part of the category at the grid edge is too narrow @@ -168,13 +213,21 @@ void VerticalAxis::updateGeometry() } else if (categoryAxis->labelsPosition() == QCategoryAxis::AxisLabelsPositionOnValue) { labelOnValue = true; - labelItem->setPos(labelItem->pos().x(), upperBound - center.y()); + if (axis()->isReverse()) { + labelItem->setPos(labelItem->pos().x(), gridRect.top() + gridRect.bottom() + - layout[i + 1] - center.y()); + } else { + labelItem->setPos(labelItem->pos().x(), upperBound - center.y()); + } } } } //label overlap detection - compensate one pixel for rounding errors - if (labelItem->pos().y() + boundingRect.height() > height || forceHide || + if (axis()->isReverse()) { + if (forceHide) + labelItem->setVisible(false); + } else if (labelItem->pos().y() + boundingRect.height() > height || forceHide || ((labelItem->pos().y() + (heightDiff / 2.0) - 1.0) > axisRect.bottom() && !labelOnValue) || (labelItem->pos().y() + (heightDiff / 2.0) < (axisRect.top() - 1.0) && !labelOnValue)) { @@ -195,14 +248,29 @@ void VerticalAxis::updateGeometry() qreal lowerBound; qreal upperBound; if (i == 0) { - lowerBound = gridRect.bottom(); - upperBound = layout[0]; - } else { - lowerBound = layout[i]; - if (i == layout.size() - 1) + if (axis()->isReverse()) { upperBound = gridRect.top(); - else - upperBound = qMax(layout[i + 1], gridRect.top()); + lowerBound = gridRect.top() + gridRect.bottom() - layout[i]; + } else { + lowerBound = gridRect.bottom(); + upperBound = layout[0]; + } + } else { + if (axis()->isReverse()) { + upperBound = gridRect.top() + gridRect.bottom() - layout[i]; + if (i == layout.size() - 1) { + lowerBound = gridRect.bottom(); + } else { + lowerBound = qMax(gridRect.top() + gridRect.bottom() - layout[i + 1], + gridRect.top()); + } + } else { + lowerBound = layout[i]; + if (i == layout.size() - 1) + upperBound = gridRect.top(); + else + upperBound = qMax(layout[i + 1], gridRect.top()); + } } if (lowerBound > gridRect.bottom()) diff --git a/src/charts/chartitem.cpp b/src/charts/chartitem.cpp index 0141404..42a3f2e 100644 --- a/src/charts/chartitem.cpp +++ b/src/charts/chartitem.cpp @@ -40,6 +40,19 @@ void ChartItem::handleDomainUpdated() qWarning() << __FUNCTION__<< "Slot not implemented"; } +void ChartItem::reversePainter(QPainter *painter, const QRectF &clipRect) +{ + if (m_series->reverseXAxis()) { + painter->translate(clipRect.width(), 0); + painter->scale(-1, 1); + } + + if (m_series->reverseYAxis()) { + painter->translate(0, clipRect.height()); + painter->scale(1, -1); + } +} + #include "moc_chartitem_p.cpp" QT_CHARTS_END_NAMESPACE diff --git a/src/charts/chartitem_p.h b/src/charts/chartitem_p.h index 6c1f807..2fd6be2 100644 --- a/src/charts/chartitem_p.h +++ b/src/charts/chartitem_p.h @@ -44,6 +44,9 @@ public: public Q_SLOTS: virtual void handleDomainUpdated(); + void reversePainter(QPainter *painter, const QRectF &clipRect); + QAbstractSeriesPrivate* seriesPrivate() const {return m_series;} + protected: bool m_validData; private: diff --git a/src/charts/linechart/linechartitem.cpp b/src/charts/linechart/linechartitem.cpp index 2250c05..54f1323 100644 --- a/src/charts/linechart/linechartitem.cpp +++ b/src/charts/linechart/linechartitem.cpp @@ -363,6 +363,8 @@ void LineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt painter->setClipRect(clipRect); } + reversePainter(painter, clipRect); + if (m_pointsVisible) { painter->setBrush(m_linePen.color()); painter->drawPath(m_linePath); @@ -378,6 +380,8 @@ void LineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt } } + reversePainter(painter, clipRect); + if (m_pointLabelsVisible) m_series->d_func()->drawSeriesPointLabels(painter, m_points, m_linePen.width() / 2); diff --git a/src/charts/qabstractseries.cpp b/src/charts/qabstractseries.cpp index 8ea37f0..03a6383 100644 --- a/src/charts/qabstractseries.cpp +++ b/src/charts/qabstractseries.cpp @@ -318,6 +318,40 @@ void QAbstractSeriesPrivate::initializeAnimations(QChart::AnimationOptions optio Q_UNUSED(options); } +bool QAbstractSeriesPrivate::reverseXAxis() +{ + bool reverseXAxis = false; + if (m_axes.size() != 0 && !(m_chart->chartType() == QChart::ChartTypePolar)) { + int i = 0; + while (i < m_axes.size()) { + if (m_axes.at(i)->orientation() == Qt::Horizontal && m_axes.at(i)->isReverse()) { + reverseXAxis = true; + break; + } + i++; + } + } + + return reverseXAxis; +} + +bool QAbstractSeriesPrivate::reverseYAxis() +{ + bool reverseYAxis = false; + if (m_axes.size() != 0 && !(m_chart->chartType() == QChart::ChartTypePolar)) { + int i = 0; + while (i < m_axes.size()) { + if (m_axes.at(i)->orientation() == Qt::Vertical && m_axes.at(i)->isReverse()) { + reverseYAxis = true; + break; + } + i++; + } + } + + return reverseYAxis; +} + #include "moc_qabstractseries.cpp" #include "moc_qabstractseries_p.cpp" diff --git a/src/charts/qabstractseries_p.h b/src/charts/qabstractseries_p.h index 87968c4..4394cb4 100644 --- a/src/charts/qabstractseries_p.h +++ b/src/charts/qabstractseries_p.h @@ -77,6 +77,8 @@ public: ChartPresenter *presenter() const; QChart* chart() { return m_chart; } + bool reverseXAxis(); + bool reverseYAxis(); Q_SIGNALS: void countChanged(); @@ -86,6 +88,7 @@ protected: QChart *m_chart; QScopedPointer m_item; QList m_axes; + private: QScopedPointer m_domain; QString m_name; diff --git a/src/charts/scatterchart/scatterchartitem.cpp b/src/charts/scatterchart/scatterchartitem.cpp index a075e76..0889a0d 100644 --- a/src/charts/scatterchart/scatterchartitem.cpp +++ b/src/charts/scatterchart/scatterchartitem.cpp @@ -169,7 +169,17 @@ void ScatterChartItem::updateGeometry() // if it was caused by an insert, but this shouldn't be a problem as the points are // fake anyway. After remove animation stops, geometry is updated to correct one. m_markerMap[item] = m_series->at(qMin(seriesLastIndex, i)); - item->setPos(point.x() - rect.width() / 2, point.y() - rect.height() / 2); + QPointF position; + if (seriesPrivate()->reverseXAxis()) + position.setX(domain()->size().width() - point.x() - rect.width() / 2); + else + position.setX(point.x() - rect.width() / 2); + if (seriesPrivate()->reverseYAxis()) + position.setY(domain()->size().height() - point.y() - rect.height() / 2); + else + position.setY(point.y() - rect.height() / 2); + item->setPos(position); + if (!m_visible || offGridStatus.at(i)) item->setVisible(false); diff --git a/src/charts/splinechart/splinechartitem.cpp b/src/charts/splinechart/splinechartitem.cpp index e1ad0f8..752658d 100644 --- a/src/charts/splinechart/splinechartitem.cpp +++ b/src/charts/splinechart/splinechartitem.cpp @@ -446,6 +446,8 @@ void SplineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o painter->setClipRect(clipRect); } + reversePainter(painter, clipRect); + painter->drawPath(m_path); if (m_pointsVisible) { @@ -456,6 +458,8 @@ void SplineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o painter->drawPoints(geometryPoints()); } + reversePainter(painter, clipRect); + if (m_pointLabelsVisible) m_series->d_func()->drawSeriesPointLabels(painter, m_points, m_linePen.width() / 2); diff --git a/src/charts/xychart/qxyseries.cpp b/src/charts/xychart/qxyseries.cpp index 618e655..f7c4788 100644 --- a/src/charts/xychart/qxyseries.cpp +++ b/src/charts/xychart/qxyseries.cpp @@ -851,8 +851,14 @@ void QXYSeriesPrivate::drawSeriesPointLabels(QPainter *painter, const QVectorsize().width() - position.x() - pointLabelWidth / 2); + if (!reverseYAxis()) + position.setY(position.y() - labelOffset); + else + position.setY(domain()->size().height() - position.y() - labelOffset); painter->drawText(position, pointLabel); } diff --git a/src/chartsqml2/chartsqml2_plugin.cpp b/src/chartsqml2/chartsqml2_plugin.cpp index 4d5c0ff..10da43b 100644 --- a/src/chartsqml2/chartsqml2_plugin.cpp +++ b/src/chartsqml2/chartsqml2_plugin.cpp @@ -303,6 +303,8 @@ public: // QtCharts 2.1 qmlRegisterType(uri, 2, 1, "CategoryAxis"); + qmlRegisterUncreatableType(uri, 2, 1, "AbstractAxis", + QLatin1String("Trying to create uncreatable: AbstractAxis. Use specific types of axis instead.")); } }; diff --git a/src/chartsqml2/plugins.qmltypes b/src/chartsqml2/plugins.qmltypes index 5097473..58a7a70 100644 --- a/src/chartsqml2/plugins.qmltypes +++ b/src/chartsqml2/plugins.qmltypes @@ -1497,9 +1497,13 @@ Module { Component { name: "QtCharts::QAbstractAxis" prototype: "QObject" - exports: ["QtCharts/AbstractAxis 1.0", "QtCharts/AbstractAxis 2.0"] + exports: [ + "QtCharts/AbstractAxis 1.0", + "QtCharts/AbstractAxis 2.0", + "QtCharts/AbstractAxis 2.1" + ] isCreatable: false - exportMetaObjectRevisions: [0, 0] + exportMetaObjectRevisions: [0, 0, 0] Property { name: "visible"; type: "bool" } Property { name: "lineVisible"; type: "bool" } Property { name: "linePen"; type: "QPen" } @@ -1522,6 +1526,7 @@ Module { Property { name: "titleFont"; type: "QFont" } Property { name: "orientation"; type: "Qt::Orientation"; isReadonly: true } Property { name: "alignment"; type: "Qt::Alignment"; isReadonly: true } + Property { name: "reverse"; type: "bool" } Signal { name: "visibleChanged" Parameter { name: "visible"; type: "bool" } @@ -1602,6 +1607,10 @@ Module { name: "shadesBrushChanged" Parameter { name: "brush"; type: "QBrush" } } + Signal { + name: "reverseChanged" + Parameter { name: "reverse"; type: "bool" } + } } Component { name: "QtCharts::QAbstractBarSeries" diff --git a/tests/auto/qcategoryaxis/tst_qcategoryaxis.cpp b/tests/auto/qcategoryaxis/tst_qcategoryaxis.cpp index 023eae2..bd4bcc1 100644 --- a/tests/auto/qcategoryaxis/tst_qcategoryaxis.cpp +++ b/tests/auto/qcategoryaxis/tst_qcategoryaxis.cpp @@ -56,6 +56,7 @@ private slots: void interval_data(); void interval(); + void reverse(); private: QCategoryAxis* m_categoryaxis; @@ -110,6 +111,8 @@ void tst_QCategoryAxis::qcategoryaxis() QVERIFY(!qFuzzyCompare(m_categoryaxis->max(), 0)); QVERIFY(!qFuzzyCompare(m_categoryaxis->min(), 0)); + + QCOMPARE(m_categoryaxis->isReverse(), false); } void tst_QCategoryAxis::max_raw_data() @@ -313,6 +316,22 @@ void tst_QCategoryAxis::labels_position() QCOMPARE(spy.count(), 1); } +void tst_QCategoryAxis::reverse() +{ + QSignalSpy spy(m_categoryaxis, SIGNAL(reverseChanged(bool))); + QCOMPARE(m_categoryaxis->isReverse(), false); + + m_categoryaxis->setReverse(); + QCOMPARE(m_categoryaxis->isReverse(), true); + + m_chart->setAxisX(m_categoryaxis, m_series); + QCOMPARE(spy.count(), 1); + + m_view->show(); + QTest::qWaitForWindowShown(m_view); + QCOMPARE(m_categoryaxis->isReverse(), true); +} + QTEST_MAIN(tst_QCategoryAxis) #include "tst_qcategoryaxis.moc" diff --git a/tests/auto/qdatetimeaxis/tst_qdatetimeaxis.cpp b/tests/auto/qdatetimeaxis/tst_qdatetimeaxis.cpp index 7d75172..c581c78 100644 --- a/tests/auto/qdatetimeaxis/tst_qdatetimeaxis.cpp +++ b/tests/auto/qdatetimeaxis/tst_qdatetimeaxis.cpp @@ -52,6 +52,7 @@ private slots: void range(); void range_animation_data(); void range_animation(); + void reverse(); private: QDateTimeAxis *m_dateTimeAxisX; @@ -113,6 +114,9 @@ void tst_QDateTimeAxis::qdatetimeaxis() QVERIFY(m_dateTimeAxisX->max().toMSecsSinceEpoch() != 0); QVERIFY(m_dateTimeAxisX->min().toMSecsSinceEpoch() != 0); + + QCOMPARE(m_dateTimeAxisX->isReverse(), false); + QCOMPARE(m_dateTimeAxisY->isReverse(), false); } void tst_QDateTimeAxis::max_raw_data() @@ -307,6 +311,20 @@ void tst_QDateTimeAxis::range_animation() range(); } +void tst_QDateTimeAxis::reverse() +{ + QSignalSpy spy(m_dateTimeAxisX, SIGNAL(reverseChanged(bool))); + QCOMPARE(m_dateTimeAxisX->isReverse(), false); + + m_dateTimeAxisX->setReverse(); + QCOMPARE(m_dateTimeAxisX->isReverse(), true); + QCOMPARE(spy.count(), 1); + + m_view->show(); + QTest::qWaitForWindowShown(m_view); + QCOMPARE(m_dateTimeAxisX->isReverse(), true); +} + QTEST_MAIN(tst_QDateTimeAxis) #include "tst_qdatetimeaxis.moc" diff --git a/tests/auto/qlogvalueaxis/tst_qlogvalueaxis.cpp b/tests/auto/qlogvalueaxis/tst_qlogvalueaxis.cpp index dee748a..eca9958 100644 --- a/tests/auto/qlogvalueaxis/tst_qlogvalueaxis.cpp +++ b/tests/auto/qlogvalueaxis/tst_qlogvalueaxis.cpp @@ -57,6 +57,7 @@ private slots: void autoscale_data(); void autoscale(); void zoom(); + void reverse(); private: QLogValueAxis* m_logvaluesaxis; @@ -109,6 +110,8 @@ void tst_QLogValueAxis::qlogvalueaxis() QCOMPARE(m_logvaluesaxis->max(), (qreal)100); QCOMPARE(m_logvaluesaxis->min(), (qreal)1); + + QCOMPARE(m_logvaluesaxis->isReverse(), false); } void tst_QLogValueAxis::max_raw_data() @@ -313,8 +316,6 @@ void tst_QLogValueAxis::noautoscale() QCOMPARE(spy0.count(), 1); QCOMPARE(spy1.count(), 1); QCOMPARE(spy2.count(), 1); - - m_chart->setAxisX(m_logvaluesaxis, m_series); m_view->show(); QTest::qWaitForWindowShown(m_view); QCOMPARE(m_logvaluesaxis->min(), min); @@ -381,6 +382,20 @@ void tst_QLogValueAxis::zoom() } +void tst_QLogValueAxis::reverse() +{ + QSignalSpy spy(m_logvaluesaxis, SIGNAL(reverseChanged(bool))); + QCOMPARE(m_logvaluesaxis->isReverse(), false); + + m_logvaluesaxis->setReverse(); + QCOMPARE(m_logvaluesaxis->isReverse(), true); + QCOMPARE(spy.count(), 1); + + m_view->show(); + QTest::qWaitForWindowShown(m_view); + QCOMPARE(m_logvaluesaxis->isReverse(), true); +} + QTEST_MAIN(tst_QLogValueAxis) #include "tst_qlogvalueaxis.moc" diff --git a/tests/auto/qml-qtquicktest/tst_categoryaxis.qml b/tests/auto/qml-qtquicktest/tst_categoryaxis.qml index 55d1123..3cb600e 100644 --- a/tests/auto/qml-qtquicktest/tst_categoryaxis.qml +++ b/tests/auto/qml-qtquicktest/tst_categoryaxis.qml @@ -46,12 +46,28 @@ Rectangle { function test_properties() { compare(lineSeries1.axisY.labelsPosition, CategoryAxis.AxisLabelsPositionCenter); + verify(lineSeries1.axisX.reverse == false, "AxisX reverse"); + verify(lineSeries1.axisY.reverse == false, "AxisY reverse"); + + // Modify properties + lineSeries1.axisX.reverse = true; + verify(lineSeries1.axisX.reverse == true, "AxisX reverse"); + lineSeries1.axisX.reverse = false; + verify(lineSeries1.axisX.reverse == false, "AxisX reverse"); } function test_signals() { axisLabelsPositionSpy.clear(); + reverseChangedSpy.clear(); lineSeries1.axisY.labelsPosition = CategoryAxis.AxisLabelsPositionOnValue; - compare(axisLabelsPositionSpy.count, 1, "onLabelsPositionChanged") + compare(axisLabelsPositionSpy.count, 1, "onLabelsPositionChanged"); + + lineSeries1.axisX.reverse = true; + compare(reverseChangedSpy.count, 1, "onReverseChanged"); + + // restore original values + lineSeries1.axisX.reverse = false; + compare(reverseChangedSpy.count, 2, "onReverseChanged"); } } @@ -93,5 +109,10 @@ Rectangle { XYPoint { x: 0; y: 0 } XYPoint { x: 5; y: 5 } } + SignalSpy { + id: reverseChangedSpy + target: axisX + signalName: "reverseChanged" + } } } diff --git a/tests/auto/qml-qtquicktest/tst_valueaxis.qml b/tests/auto/qml-qtquicktest/tst_valueaxis.qml index 6911d3d..cf4f6d3 100644 --- a/tests/auto/qml-qtquicktest/tst_valueaxis.qml +++ b/tests/auto/qml-qtquicktest/tst_valueaxis.qml @@ -40,10 +40,16 @@ Rectangle { verify(axisX.tickCount == 5, "AxisX tick count"); verify(axisY.tickCount == 5, "AxisY tick count"); verify(axisX.labelFormat == "", "label format"); + verify(axisX.reverse == false, "AxisX reverse"); + verify(axisY.reverse == false, "AxisY reverse"); // Modify properties axisX.tickCount = 3; verify(axisX.tickCount == 3, "set tick count"); + axisX.reverse = true; + verify(axisX.reverse == true, "AxisX reverse"); + axisX.reverse = false; + verify(axisX.reverse == false, "AxisX reverse"); } function test_functions() { @@ -67,6 +73,8 @@ Rectangle { function test_signals() { minChangedSpy.clear(); maxChangedSpy.clear(); + reverseChangedSpy.clear(); + axisX.min = 2; compare(minChangedSpy.count, 1, "onMinChanged"); compare(maxChangedSpy.count, 0, "onMaxChanged"); @@ -75,11 +83,16 @@ Rectangle { compare(minChangedSpy.count, 1, "onMinChanged"); compare(maxChangedSpy.count, 1, "onMaxChanged"); + axisX.reverse = true; + compare(reverseChangedSpy.count, 1, "onReverseChanged"); + // restore original values axisX.min = 0; axisX.max = 10; + axisX.reverse = false; compare(minChangedSpy.count, 2, "onMinChanged"); compare(maxChangedSpy.count, 2, "onMaxChanged"); + compare(reverseChangedSpy.count, 2, "onReverseChanged"); } } @@ -110,5 +123,10 @@ Rectangle { target: axisX signalName: "maxChanged" } + SignalSpy { + id: reverseChangedSpy + target: axisX + signalName: "reverseChanged" + } } } diff --git a/tests/auto/qvalueaxis/tst_qvalueaxis.cpp b/tests/auto/qvalueaxis/tst_qvalueaxis.cpp index da61cc5..9c139b0 100644 --- a/tests/auto/qvalueaxis/tst_qvalueaxis.cpp +++ b/tests/auto/qvalueaxis/tst_qvalueaxis.cpp @@ -59,6 +59,7 @@ private slots: void noautoscale(); void autoscale_data(); void autoscale(); + void reverse(); private: QValueAxis* m_valuesaxis; @@ -113,6 +114,8 @@ void tst_QValueAxis::qvalueaxis() QVERIFY(!qFuzzyCompare(m_valuesaxis->max(), 0)); QVERIFY(!qFuzzyCompare(m_valuesaxis->min(), 0)); QCOMPARE(m_valuesaxis->tickCount(), 5); + + QCOMPARE(m_valuesaxis->isReverse(), false); } void tst_QValueAxis::max_raw_data() @@ -406,6 +409,22 @@ void tst_QValueAxis::autoscale() QVERIFY2(qFuzzyCompare(m_valuesaxis->max(), 100), "Max not equal"); } +void tst_QValueAxis::reverse() +{ + QSignalSpy spy(m_valuesaxis, SIGNAL(reverseChanged(bool))); + QCOMPARE(m_valuesaxis->isReverse(), false); + + m_valuesaxis->setReverse(); + QCOMPARE(m_valuesaxis->isReverse(), true); + + m_chart->setAxisX(m_valuesaxis, m_series); + QCOMPARE(spy.count(), 1); + + m_view->show(); + QTest::qWaitForWindowShown(m_view); + QCOMPARE(m_valuesaxis->isReverse(), true); +} + QTEST_MAIN(tst_QValueAxis) #include "tst_qvalueaxis.moc" diff --git a/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxes.qml b/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxes.qml index 5913dff..e7dfdec 100644 --- a/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxes.qml +++ b/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxes.qml @@ -23,23 +23,13 @@ ChartView { id: chartView title: "chart axes" - // TODO: Do we need a property for orientation or properties "axisX" and "axisY" on ChartView - // to make an axis the default axis for all series with no other axes defined...? -// ValueAxis { -// orientation: ValueAxis.AxisX -// min: 0 -// max: 10 -// } -// axisX: ValueAxis { -// min: 0 -// max: 10 -// } - // ...Now that we don't have this implementation, the following axes won't have any affect: ValueAxis { + id: valueAxisX min: 0 max: 10 } ValueAxis { + id: valueAxisY min: 0 max: 5 } @@ -51,6 +41,8 @@ ChartView { XYPoint { x: 2; y: 2 } XYPoint { x: 3; y: 3 } XYPoint { x: 4; y: 4 } + axisX: valueAxisX + axisY: valueAxisY } ScatterSeries { @@ -64,6 +56,8 @@ ChartView { XYPoint { x: 2; y: 2 } XYPoint { x: 3; y: 3 } XYPoint { x: 4; y: 4 } + axisX: valueAxisX + axisY: valueAxisY } // Component.onCompleted: { diff --git a/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxesRevert.qml b/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxesRevert.qml index 0855b3e..851db4d 100644 --- a/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxesRevert.qml +++ b/tests/manual/qmlchartaxis/qml/qmlchartaxis/ChartAxesRevert.qml @@ -24,10 +24,12 @@ ChartView { title: "chart axes reverted" ValueAxis { + id: valueAxisX min: 0 max: 10 } ValueAxis { + id: valueAxisY min: 0 max: 5 } @@ -43,6 +45,8 @@ ChartView { XYPoint { x: 2; y: 2 } XYPoint { x: 3; y: 3 } XYPoint { x: 4; y: 4 } + axisX: valueAxisX + axisY: valueAxisY } LineSeries { @@ -52,5 +56,7 @@ ChartView { XYPoint { x: 2; y: 2 } XYPoint { x: 3; y: 3 } XYPoint { x: 4; y: 4 } + axisX: valueAxisX + axisY: valueAxisY } } diff --git a/tests/manual/qmlchartproperties/qml/qmlchartproperties/Chart.qml b/tests/manual/qmlchartproperties/qml/qmlchartproperties/Chart.qml index 4294da5..e15b137 100644 --- a/tests/manual/qmlchartproperties/qml/qmlchartproperties/Chart.qml +++ b/tests/manual/qmlchartproperties/qml/qmlchartproperties/Chart.qml @@ -34,6 +34,8 @@ ChartView { XYPoint { x: 2.9; y: 4.9 } XYPoint { x: 3.4; y: 3.0 } XYPoint { x: 4.1; y: 3.3 } + axisX: axisX + axisY: axisY } onVisibleChanged: console.log("chart.onVisibleChanged: " + visible); @@ -65,6 +67,7 @@ ChartView { } ValueAxis{ + id: axisX onColorChanged: console.log("axisX.onColorChanged: " + color); onLabelsVisibleChanged: console.log("axisX.onLabelsVisibleChanged: " + visible); onLabelsColorChanged: console.log("axisX.onLabelsColorChanged: " + color); @@ -75,9 +78,11 @@ ChartView { onShadesBorderColorChanged: console.log("axisX.onShadesBorderColorChanged: " + color); onMinChanged: console.log("axisX.onMinChanged: " + min); onMaxChanged: console.log("axisX.onMaxChanged: " + max); + onReverseChanged: console.log("axisX.onReverseChanged: " + reverse); } ValueAxis{ + id: axisY onColorChanged: console.log("axisY.onColorChanged: " + color); onLabelsVisibleChanged: console.log("axisY.onLabelsVisibleChanged: " + visible); onLabelsColorChanged: console.log("axisY.onLabelsColorChanged: " + color); @@ -88,6 +93,7 @@ ChartView { onShadesBorderColorChanged: console.log("axisY.onShadesBorderColorChanged: " + color); onMinChanged: console.log("axisY.onMinChanged: " + min); onMaxChanged: console.log("axisY.onMaxChanged: " + max); + onReverseChanged: console.log("axisY.onReverseChanged: " + reverse); } Rectangle { diff --git a/tests/manual/qmlchartproperties/qml/qmlchartproperties/ChartEditorAxis.qml b/tests/manual/qmlchartproperties/qml/qmlchartproperties/ChartEditorAxis.qml index c821c89..dedd14d 100644 --- a/tests/manual/qmlchartproperties/qml/qmlchartproperties/ChartEditorAxis.qml +++ b/tests/manual/qmlchartproperties/qml/qmlchartproperties/ChartEditorAxis.qml @@ -105,6 +105,10 @@ Row { onClicked: axis.tickCount--; } + Button { + text: "axis reverse" + onClicked: axis.reverse = !axis.reverse; + } FontEditor { id: fontEditor