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