diff options
author | Miikka Heikkinen <miikka.heikkinen@digia.com> | 2013-04-16 10:07:13 +0300 |
---|---|---|
committer | Miikka Heikkinen <miikka.heikkinen@digia.com> | 2013-04-17 10:14:43 +0300 |
commit | f494279b6366b06e3eeeb4f8c006ce76b08f10d7 (patch) | |
tree | 26951efa14e26eb0791d13ea32624e9afcf48851 /src | |
parent | 56fd46a395765db6818f890676e42cc59a9f4a81 (diff) |
Add Polar chart support
This commit also heavily refactors things as polar chart needs
separate implementation of various classes that previously
only needed one, such as ChartAxis and ChartLayout.
Task-number: QTRD-1757
Change-Id: I3d3db23920314987ceef3ae92879960b833b7136
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@digia.com>
Diffstat (limited to 'src')
115 files changed, 6189 insertions, 1409 deletions
diff --git a/src/animations/axisanimation.cpp b/src/animations/axisanimation.cpp index e59d9aae..fab017e1 100644 --- a/src/animations/axisanimation.cpp +++ b/src/animations/axisanimation.cpp @@ -19,14 +19,15 @@ ****************************************************************************/ #include "axisanimation_p.h" -#include "chartaxis_p.h" +#include "chartaxiselement_p.h" +#include "qabstractaxis_p.h" Q_DECLARE_METATYPE(QVector<qreal>) QTCOMMERCIALCHART_BEGIN_NAMESPACE -AxisAnimation::AxisAnimation(ChartAxis *axis) +AxisAnimation::AxisAnimation(ChartAxisElement *axis) : ChartAnimation(axis), m_axis(axis), m_type(DefaultAnimation) @@ -68,13 +69,13 @@ void AxisAnimation::setValues(QVector<qreal> &oldLayout, QVector<qreal> &newLayo oldLayout.resize(newLayout.count()); for (int i = 0, j = oldLayout.count() - 1; i < (oldLayout.count() + 1) / 2; ++i, --j) { - oldLayout[i] = m_axis->orientation() == Qt::Horizontal ? rect.left() : rect.bottom(); - oldLayout[j] = m_axis->orientation() == Qt::Horizontal ? rect.right() : rect.top(); + oldLayout[i] = m_axis->axis()->orientation() == Qt::Horizontal ? rect.left() : rect.bottom(); + oldLayout[j] = m_axis->axis()->orientation() == Qt::Horizontal ? rect.right() : rect.top(); } } break; case ZoomInAnimation: { - int index = qMin(oldLayout.count() * (m_axis->orientation() == Qt::Horizontal ? m_point.x() : (1 - m_point.y())), newLayout.count() - (qreal)1.0); + int index = qMin(oldLayout.count() * (m_axis->axis()->orientation() == Qt::Horizontal ? m_point.x() : (1 - m_point.y())), newLayout.count() - (qreal)1.0); oldLayout.resize(newLayout.count()); for (int i = 0; i < oldLayout.count(); i++) @@ -99,7 +100,7 @@ void AxisAnimation::setValues(QVector<qreal> &oldLayout, QVector<qreal> &newLayo oldLayout.resize(newLayout.count()); QRectF rect = m_axis->gridGeometry(); for (int i = 0, j = oldLayout.count() - 1; i < oldLayout.count(); ++i, --j) - oldLayout[i] = m_axis->orientation() == Qt::Horizontal ? rect.left() : rect.top(); + oldLayout[i] = m_axis->axis()->orientation() == Qt::Horizontal ? rect.left() : rect.top(); } break; } diff --git a/src/animations/axisanimation_p.h b/src/animations/axisanimation_p.h index 6a213eae..d25cceda 100644 --- a/src/animations/axisanimation_p.h +++ b/src/animations/axisanimation_p.h @@ -35,13 +35,13 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -class ChartAxis; +class ChartAxisElement; class AxisAnimation: public ChartAnimation { public: enum Animation { DefaultAnimation, ZoomOutAnimation, ZoomInAnimation, MoveForwardAnimation, MoveBackwordAnimation}; - AxisAnimation(ChartAxis *axis); + AxisAnimation(ChartAxisElement *axis); ~AxisAnimation(); void setAnimationType(Animation type); void setAnimationPoint(const QPointF &point); @@ -50,7 +50,7 @@ protected: QVariant interpolated(const QVariant &from, const QVariant &to, qreal progress) const; void updateCurrentValue(const QVariant &value); private: - ChartAxis *m_axis; + ChartAxisElement *m_axis; Animation m_type; QPointF m_point; }; diff --git a/src/areachart/areachartitem.cpp b/src/areachart/areachartitem.cpp index 92f89999..10adfa6f 100644 --- a/src/areachart/areachartitem.cpp +++ b/src/areachart/areachartitem.cpp @@ -88,12 +88,26 @@ void AreaChartItem::updatePath() path = m_upper->path(); if (m_lower) { + // Note: Polarcharts always draw area correctly only when both series have equal width or are + // fully displayed. If one series is partally off-chart, the connecting line between + // the series does not attach to the end of the partially hidden series but to the point + // where it intersects the axis line. The problem is especially noticeable when one of the series + // is entirely off-chart, in which case the connecting line connects two ends of the + // visible series. + // This happens because we get the paths from linechart, which omits off-chart segments. + // To properly fix, linechart would need to provide true full path, in right, left, and the rest + // portions to enable proper clipping. However, combining those to single visually unified area + // would be a nightmare, since they would have to be painted separately. path.connectPath(m_lower->path().toReversed()); } else { QPointF first = path.pointAtPercent(0); QPointF last = path.pointAtPercent(1); - path.lineTo(last.x(), rect.bottom()); - path.lineTo(first.x(), rect.bottom()); + if (presenter()->chartType() == QChart::ChartTypeCartesian) { + path.lineTo(last.x(), rect.bottom()); + path.lineTo(first.x(), rect.bottom()); + } else { // polar + path.lineTo(rect.center()); + } } path.closeSubpath(); prepareGeometryChange(); @@ -137,7 +151,11 @@ void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt painter->save(); painter->setPen(m_linePen); painter->setBrush(m_brush); - painter->setClipRect(QRectF(QPointF(0,0),domain()->size())); + QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); + if (presenter()->chartType() == QChart::ChartTypePolar) + painter->setClipRegion(QRegion(clipRect.toRect(), QRegion::Ellipse)); + else + painter->setClipRect(clipRect); painter->drawPath(m_path); if (m_pointsVisible) { painter->setPen(m_pointPen); diff --git a/src/areachart/areachartitem_p.h b/src/areachart/areachartitem_p.h index 0c09bade..60d3e07f 100644 --- a/src/areachart/areachartitem_p.h +++ b/src/areachart/areachartitem_p.h @@ -32,11 +32,11 @@ #include "qchartglobal.h" #include "linechartitem_p.h" +#include "qareaseries.h" #include <QPen> QTCOMMERCIALCHART_BEGIN_NAMESPACE -class QAreaSeries; class AreaChartItem; class AreaChartItem : public ChartItem @@ -57,6 +57,8 @@ public: void updatePath(); void setPresenter(ChartPresenter *presenter); + QAreaSeries *series() const { return m_series; } + protected: void mousePressEvent(QGraphicsSceneMouseEvent *event); void hoverEnterEvent(QGraphicsSceneHoverEvent *event); @@ -89,6 +91,9 @@ public: AreaBoundItem(AreaChartItem *area, QLineSeries *lineSeries,QGraphicsItem* item = 0) : LineChartItem(lineSeries, item), m_item(area) { + // We do not actually want to draw anything from LineChartItem. + // Drawing is done in AreaChartItem only. + setVisible(false); } ~AreaBoundItem() {} @@ -97,6 +102,9 @@ public: // Turn off points drawing from component line chart item, as that // messes up the fill for area series. suppressPoints(); + // Component lineseries are not necessarily themselves on the chart, + // so get the chart type for them from area chart. + forceChartType(m_item->series()->chart()->chartType()); LineChartItem::updateGeometry(); m_item->updatePath(); } diff --git a/src/axis/axis.pri b/src/axis/axis.pri index 264ed896..6f706aac 100644 --- a/src/axis/axis.pri +++ b/src/axis/axis.pri @@ -13,7 +13,8 @@ DEPENDPATH += $$PWD \ $$PWD/logvalueaxis SOURCES += \ - $$PWD/chartaxis.cpp \ + $$PWD/chartaxiselement.cpp \ + $$PWD/cartesianchartaxis.cpp \ $$PWD/qabstractaxis.cpp \ $$PWD/verticalaxis.cpp \ $$PWD/horizontalaxis.cpp \ @@ -31,10 +32,12 @@ SOURCES += \ $$PWD/logvalueaxis/qlogvalueaxis.cpp PRIVATE_HEADERS += \ - $$PWD/chartaxis_p.h \ + $$PWD/chartaxiselement_p.h \ + $$PWD/cartesianchartaxis_p.h \ $$PWD/qabstractaxis_p.h \ $$PWD/verticalaxis_p.h \ $$PWD/horizontalaxis_p.h \ + $$PWD/linearrowitem_p.h \ $$PWD/valueaxis/chartvalueaxisx_p.h \ $$PWD/valueaxis/chartvalueaxisy_p.h \ $$PWD/valueaxis/qvalueaxis_p.h \ @@ -55,6 +58,29 @@ PUBLIC_HEADERS += \ $$PWD/categoryaxis/qcategoryaxis.h \ $$PWD/logvalueaxis/qlogvalueaxis.h \ +# polar +SOURCES += \ + $$PWD/polarchartaxis.cpp \ + $$PWD/polarchartaxisangular.cpp \ + $$PWD/polarchartaxisradial.cpp \ + $$PWD/valueaxis/polarchartvalueaxisangular.cpp \ + $$PWD/valueaxis/polarchartvalueaxisradial.cpp \ + $$PWD/logvalueaxis/polarchartlogvalueaxisangular.cpp \ + $$PWD/logvalueaxis/polarchartlogvalueaxisradial.cpp \ + $$PWD/categoryaxis/polarchartcategoryaxisangular.cpp \ + $$PWD/categoryaxis/polarchartcategoryaxisradial.cpp + +PRIVATE_HEADERS += \ + $$PWD/polarchartaxis_p.h \ + $$PWD/polarchartaxisangular_p.h \ + $$PWD/polarchartaxisradial_p.h \ + $$PWD/valueaxis/polarchartvalueaxisangular_p.h \ + $$PWD/valueaxis/polarchartvalueaxisradial_p.h \ + $$PWD/logvalueaxis/polarchartlogvalueaxisangular_p.h \ + $$PWD/logvalueaxis/polarchartlogvalueaxisradial_p.h \ + $$PWD/categoryaxis/polarchartcategoryaxisangular_p.h \ + $$PWD/categoryaxis/polarchartcategoryaxisradial_p.h + !linux-arm*: { INCLUDEPATH += \ $$PWD/datetimeaxis @@ -65,12 +91,16 @@ DEPENDPATH += \ SOURCES += \ $$PWD/datetimeaxis/chartdatetimeaxisx.cpp \ $$PWD/datetimeaxis/chartdatetimeaxisy.cpp \ - $$PWD/datetimeaxis/qdatetimeaxis.cpp + $$PWD/datetimeaxis/qdatetimeaxis.cpp \ + $$PWD/datetimeaxis/polarchartdatetimeaxisangular.cpp \ + $$PWD/datetimeaxis/polarchartdatetimeaxisradial.cpp PRIVATE_HEADERS += \ $$PWD/datetimeaxis/chartdatetimeaxisx_p.h \ $$PWD/datetimeaxis/chartdatetimeaxisy_p.h \ - $$PWD/datetimeaxis/qdatetimeaxis_p.h + $$PWD/datetimeaxis/qdatetimeaxis_p.h \ + $$PWD/datetimeaxis/polarchartdatetimeaxisangular_p.h \ + $$PWD/datetimeaxis/polarchartdatetimeaxisradial_p.h PUBLIC_HEADERS += \ $$PWD/datetimeaxis/qdatetimeaxis.h diff --git a/src/axis/barcategoryaxis/chartbarcategoryaxisx.cpp b/src/axis/barcategoryaxis/chartbarcategoryaxisx.cpp index 8d86b120..9d09aebf 100644 --- a/src/axis/barcategoryaxis/chartbarcategoryaxisx.cpp +++ b/src/axis/barcategoryaxis/chartbarcategoryaxisx.cpp @@ -21,7 +21,7 @@ #include "chartbarcategoryaxisx_p.h" #include "chartpresenter_p.h" #include "qbarcategoryaxis_p.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <QFontMetrics> #include <QDebug> #include <qmath.h> @@ -87,7 +87,7 @@ QStringList ChartBarCategoryAxisX::createCategoryLabels(const QVector<qreal>& la void ChartBarCategoryAxisX::updateGeometry() { - const QVector<qreal>& layout = ChartAxis::layout(); + const QVector<qreal>& layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; setLabels(createCategoryLabels(layout)); @@ -104,7 +104,7 @@ QSizeF ChartBarCategoryAxisX::sizeHint(Qt::SizeHint which, const QSizeF &constra { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = HorizontalAxis::sizeHint(which, constraint); QStringList ticksList = m_categoriesAxis->categories(); diff --git a/src/axis/barcategoryaxis/chartbarcategoryaxisy.cpp b/src/axis/barcategoryaxis/chartbarcategoryaxisy.cpp index 0ca8116c..20194bbc 100644 --- a/src/axis/barcategoryaxis/chartbarcategoryaxisy.cpp +++ b/src/axis/barcategoryaxis/chartbarcategoryaxisy.cpp @@ -21,7 +21,7 @@ #include "chartbarcategoryaxisy_p.h" #include "chartpresenter_p.h" #include "qbarcategoryaxis_p.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <qmath.h> #include <QFontMetrics> #include <QDebug> @@ -86,7 +86,7 @@ QStringList ChartBarCategoryAxisY::createCategoryLabels(const QVector<qreal>& la void ChartBarCategoryAxisY::updateGeometry() { - const QVector<qreal>& layout = ChartAxis::layout(); + const QVector<qreal>& layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; setLabels(createCategoryLabels(layout)); @@ -103,7 +103,7 @@ QSizeF ChartBarCategoryAxisY::sizeHint(Qt::SizeHint which, const QSizeF &constra { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = VerticalAxis::sizeHint(which, constraint); QStringList ticksList = m_categoriesAxis->categories(); diff --git a/src/axis/barcategoryaxis/qbarcategoryaxis.cpp b/src/axis/barcategoryaxis/qbarcategoryaxis.cpp index 0dffff0d..0dfbea1f 100644 --- a/src/axis/barcategoryaxis/qbarcategoryaxis.cpp +++ b/src/axis/barcategoryaxis/qbarcategoryaxis.cpp @@ -111,7 +111,7 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE /*! \fn void QBarCategoryAxis::categoriesChanged() - Axis emits signal when the categories of the axis has changed. + Axis emits signal when the categories of the axis have changed. */ /*! @@ -550,7 +550,7 @@ void QBarCategoryAxisPrivate::setRange(const QString &minCategory, const QStrin void QBarCategoryAxisPrivate::initializeGraphics(QGraphicsItem* parent) { Q_Q(QBarCategoryAxis); - ChartAxis* axis(0); + ChartAxisElement* axis(0); if (orientation() == Qt::Vertical) axis = new ChartBarCategoryAxisY(q,parent); if (orientation() == Qt::Horizontal) diff --git a/src/axis/cartesianchartaxis.cpp b/src/axis/cartesianchartaxis.cpp new file mode 100644 index 00000000..85978202 --- /dev/null +++ b/src/axis/cartesianchartaxis.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "cartesianchartaxis_p.h" +#include "qabstractaxis.h" +#include "qabstractaxis_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include "abstractdomain_p.h" +#include "linearrowitem_p.h" +#include <QValueAxis> +#include <QLogValueAxis> +#include <QGraphicsLayout> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +CartesianChartAxis::CartesianChartAxis(QAbstractAxis *axis, QGraphicsItem *item , bool intervalAxis) + : ChartAxisElement(axis, item, intervalAxis) +{ + Q_ASSERT(item); +} + + +CartesianChartAxis::~CartesianChartAxis() +{ +} + +void CartesianChartAxis::createItems(int count) +{ + if (arrowItems().size() == 0) { + QGraphicsLineItem *arrow = new LineArrowItem(this, this); + arrow->setPen(axis()->linePen()); + arrowGroup()->addToGroup(arrow); + } + + if (intervalAxis() && gridItems().size() == 0) { + for (int i = 0 ; i < 2 ; i ++){ + QGraphicsLineItem *item = new QGraphicsLineItem(this); + item->setPen(axis()->gridLinePen()); + gridGroup()->addToGroup(item); + } + } + + for (int i = 0; i < count; ++i) { + QGraphicsLineItem *arrow = new QGraphicsLineItem(this); + QGraphicsLineItem *grid = new QGraphicsLineItem(this); + QGraphicsSimpleTextItem *label = new QGraphicsSimpleTextItem(this); + QGraphicsSimpleTextItem *title = titleItem(); + arrow->setPen(axis()->linePen()); + grid->setPen(axis()->gridLinePen()); + label->setFont(axis()->labelsFont()); + label->setPen(axis()->labelsPen()); + label->setBrush(axis()->labelsBrush()); + label->setRotation(axis()->labelsAngle()); + title->setFont(axis()->titleFont()); + title->setPen(axis()->titlePen()); + title->setBrush(axis()->titleBrush()); + title->setText(axis()->titleText()); + arrowGroup()->addToGroup(arrow); + gridGroup()->addToGroup(grid); + labelGroup()->addToGroup(label); + + if ((gridItems().size()) % 2 && gridItems().size() > 2) { + QGraphicsRectItem* shades = new QGraphicsRectItem(this); + shades->setPen(axis()->shadesPen()); + shades->setBrush(axis()->shadesBrush()); + shadeGroup()->addToGroup(shades); + } + } + +} + +void CartesianChartAxis::deleteItems(int count) +{ + QList<QGraphicsItem *> lines = gridItems(); + QList<QGraphicsItem *> labels = labelItems(); + QList<QGraphicsItem *> shades = shadeItems(); + QList<QGraphicsItem *> axis = arrowItems(); + + for (int i = 0; i < count; ++i) { + if (lines.size() % 2 && lines.size() > 1) + delete(shades.takeLast()); + delete(lines.takeLast()); + delete(labels.takeLast()); + delete(axis.takeLast()); + } +} + +void CartesianChartAxis::updateLayout(QVector<qreal> &layout) +{ + int diff = ChartAxisElement::layout().size() - layout.size(); + + if (diff > 0) + deleteItems(diff); + else if (diff < 0) + createItems(-diff); + + if (animation()) { + switch (presenter()->state()) { + case ChartPresenter::ZoomInState: + animation()->setAnimationType(AxisAnimation::ZoomInAnimation); + animation()->setAnimationPoint(presenter()->statePoint()); + break; + case ChartPresenter::ZoomOutState: + animation()->setAnimationType(AxisAnimation::ZoomOutAnimation); + animation()->setAnimationPoint(presenter()->statePoint()); + break; + case ChartPresenter::ScrollUpState: + case ChartPresenter::ScrollLeftState: + animation()->setAnimationType(AxisAnimation::MoveBackwordAnimation); + break; + case ChartPresenter::ScrollDownState: + case ChartPresenter::ScrollRightState: + animation()->setAnimationType(AxisAnimation::MoveForwardAnimation); + break; + case ChartPresenter::ShowState: + animation()->setAnimationType(AxisAnimation::DefaultAnimation); + break; + } + animation()->setValues(ChartAxisElement::layout(), layout); + presenter()->startAnimation(animation()); + } else { + setLayout(layout); + updateGeometry(); + } +} + +bool CartesianChartAxis::isEmpty() +{ + return axisGeometry().isEmpty() + || gridGeometry().isEmpty() + || qFuzzyCompare(min(), max()); +} + +void CartesianChartAxis::setGeometry(const QRectF &axis, const QRectF &grid) +{ + m_gridRect = grid; + setAxisGeometry(axis); + + if (isEmpty()) + return; + + QVector<qreal> layout = calculateLayout(); + updateLayout(layout); +} + +QSizeF CartesianChartAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_UNUSED(which); + Q_UNUSED(constraint); + return QSizeF(); +} + +void CartesianChartAxis::handleArrowPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, arrowItems()) + static_cast<QGraphicsLineItem *>(item)->setPen(pen); +} + +void CartesianChartAxis::handleGridPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, gridItems()) + static_cast<QGraphicsLineItem *>(item)->setPen(pen); +} + +void CartesianChartAxis::handleShadesBrushChanged(const QBrush &brush) +{ + foreach (QGraphicsItem *item, shadeItems()) + static_cast<QGraphicsRectItem *>(item)->setBrush(brush); +} + +void CartesianChartAxis::handleShadesPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, shadeItems()) + static_cast<QGraphicsRectItem *>(item)->setPen(pen); +} + +#include "moc_cartesianchartaxis_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/cartesianchartaxis_p.h b/src/axis/cartesianchartaxis_p.h new file mode 100644 index 00000000..51ed1326 --- /dev/null +++ b/src/axis/cartesianchartaxis_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CARTESIANCHARTAXIS_H +#define CARTESIANCHARTAXIS_H + +#include "qchartglobal.h" +#include "chartaxiselement_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QAbstractAxis; + +class CartesianChartAxis : public ChartAxisElement +{ + Q_OBJECT + Q_INTERFACES(QGraphicsLayoutItem) +public: + + CartesianChartAxis(QAbstractAxis *axis, QGraphicsItem *item = 0, bool intervalAxis = false); + ~CartesianChartAxis(); + + void setGeometry(const QRectF &axis, const QRectF &grid); + QRectF gridGeometry() const { return m_gridRect; } + bool isEmpty(); + + virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + +protected: + void setGeometry(const QRectF &size) { Q_UNUSED(size);} + virtual void updateGeometry() = 0; + void updateLayout(QVector<qreal> &layout); + +public Q_SLOTS: + virtual void handleArrowPenChanged(const QPen &pen); + virtual void handleGridPenChanged(const QPen &pen); + virtual void handleShadesBrushChanged(const QBrush &brush); + virtual void handleShadesPenChanged(const QPen &pen); + +private: + void createItems(int count); + void deleteItems(int count); + +private: + QRectF m_gridRect; + + friend class AxisAnimation; + friend class LineArrowItem; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif /* CARTESIANCHARTAXIS_H */ diff --git a/src/axis/categoryaxis/chartcategoryaxisx.cpp b/src/axis/categoryaxis/chartcategoryaxisx.cpp index 9c9813c4..cb1ede68 100644 --- a/src/axis/categoryaxis/chartcategoryaxisx.cpp +++ b/src/axis/categoryaxis/chartcategoryaxisx.cpp @@ -22,6 +22,7 @@ #include "qcategoryaxis.h" #include "qabstractaxis.h" #include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QFontMetrics> #include <qmath.h> @@ -32,6 +33,7 @@ ChartCategoryAxisX::ChartCategoryAxisX(QCategoryAxis *axis, QGraphicsItem* item) : HorizontalAxis(axis, item, true), m_axis(axis) { + QObject::connect(axis, SIGNAL(categoriesChanged()), this, SLOT(handleCategoriesChanged())); } ChartCategoryAxisX::~ChartCategoryAxisX() @@ -72,16 +74,11 @@ void ChartCategoryAxisX::updateGeometry() HorizontalAxis::updateGeometry(); } -void ChartCategoryAxisX::handleAxisUpdated() -{ - updateGeometry(); -} - QSizeF ChartCategoryAxisX::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = HorizontalAxis::sizeHint(which, constraint); QStringList ticksList = m_axis->categoriesLabels(); @@ -114,4 +111,12 @@ QSizeF ChartCategoryAxisX::sizeHint(Qt::SizeHint which, const QSizeF &constraint return sh; } +void ChartCategoryAxisX::handleCategoriesChanged() +{ + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); +} + +#include "moc_chartcategoryaxisx_p.cpp" + QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/categoryaxis/chartcategoryaxisx_p.h b/src/axis/categoryaxis/chartcategoryaxisx_p.h index df338cc5..2fced9cf 100644 --- a/src/axis/categoryaxis/chartcategoryaxisx_p.h +++ b/src/axis/categoryaxis/chartcategoryaxisx_p.h @@ -35,23 +35,23 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QCategoryAxis; -class ChartPresenter; class ChartCategoryAxisX : public HorizontalAxis { + Q_OBJECT public: ChartCategoryAxisX(QCategoryAxis *axis, QGraphicsItem* item = 0); ~ChartCategoryAxisX(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; +public Q_SLOTS: + void handleCategoriesChanged(); + protected: QVector<qreal> calculateLayout() const; void updateGeometry(); -public Q_SLOTS: - void handleAxisUpdated(); - private: QCategoryAxis *m_axis; }; diff --git a/src/axis/categoryaxis/chartcategoryaxisy.cpp b/src/axis/categoryaxis/chartcategoryaxisy.cpp index ea86b561..96f5a2d8 100644 --- a/src/axis/categoryaxis/chartcategoryaxisy.cpp +++ b/src/axis/categoryaxis/chartcategoryaxisy.cpp @@ -22,6 +22,7 @@ #include "qcategoryaxis.h" #include "qabstractaxis.h" #include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QFontMetrics> #include <qmath.h> @@ -33,6 +34,7 @@ ChartCategoryAxisY::ChartCategoryAxisY(QCategoryAxis *axis, QGraphicsItem* item) : VerticalAxis(axis, item, true), m_axis(axis) { + QObject::connect(axis, SIGNAL(categoriesChanged()), this, SLOT(handleCategoriesChanged())); } ChartCategoryAxisY::~ChartCategoryAxisY() @@ -72,16 +74,11 @@ void ChartCategoryAxisY::updateGeometry() VerticalAxis::updateGeometry(); } -void ChartCategoryAxisY::handleAxisUpdated() -{ - updateGeometry(); -} - QSizeF ChartCategoryAxisY::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = VerticalAxis::sizeHint(which, constraint); QStringList ticksList = m_axis->categoriesLabels(); @@ -113,4 +110,12 @@ QSizeF ChartCategoryAxisY::sizeHint(Qt::SizeHint which, const QSizeF &constraint return sh; } +void ChartCategoryAxisY::handleCategoriesChanged() +{ + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); +} + +#include "moc_chartcategoryaxisy_p.cpp" + QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/categoryaxis/chartcategoryaxisy_p.h b/src/axis/categoryaxis/chartcategoryaxisy_p.h index a9dcbd81..20760b82 100644 --- a/src/axis/categoryaxis/chartcategoryaxisy_p.h +++ b/src/axis/categoryaxis/chartcategoryaxisy_p.h @@ -35,23 +35,23 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QCategoryAxis; -class ChartPresenter; class ChartCategoryAxisY : public VerticalAxis { + Q_OBJECT public: ChartCategoryAxisY(QCategoryAxis *axis, QGraphicsItem* item = 0); ~ChartCategoryAxisY(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; +public Q_SLOTS: + void handleCategoriesChanged(); + protected: QVector<qreal> calculateLayout() const; void updateGeometry(); -public Q_SLOTS: - void handleAxisUpdated(); - private: QCategoryAxis *m_axis; }; diff --git a/src/axis/categoryaxis/polarchartcategoryaxisangular.cpp b/src/axis/categoryaxis/polarchartcategoryaxisangular.cpp new file mode 100644 index 00000000..ead087f8 --- /dev/null +++ b/src/axis/categoryaxis/polarchartcategoryaxisangular.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartcategoryaxisangular_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include "qcategoryaxis.h" +#include <QDebug> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartCategoryAxisAngular::PolarChartCategoryAxisAngular(QCategoryAxis *axis, QGraphicsItem *item) + : PolarChartAxisAngular(axis, item, true) +{ + QObject::connect(axis, SIGNAL(categoriesChanged()), this, SLOT(handleCategoriesChanged())); +} + +PolarChartCategoryAxisAngular::~PolarChartCategoryAxisAngular() +{ +} + +QVector<qreal> PolarChartCategoryAxisAngular::calculateLayout() const +{ + QCategoryAxis *catAxis = static_cast<QCategoryAxis *>(axis()); + int tickCount = catAxis->categoriesLabels().count() + 1; + QVector<qreal> points; + + if (tickCount < 2) + return points; + + qreal range = max() - min(); + if (range > 0) { + points.resize(tickCount); + qreal scale = 360.0 / range; + qreal angle; + for (int i = 0; i < tickCount; ++i) { + if (i < tickCount - 1) + angle = (catAxis->startValue(catAxis->categoriesLabels().at(i)) - min()) * scale; + else + angle = (catAxis->endValue(catAxis->categoriesLabels().at(i - 1)) - min()) * scale; + points[i] = angle; + } + } + + return points; +} + +void PolarChartCategoryAxisAngular::createAxisLabels(const QVector<qreal> &layout) +{ + Q_UNUSED(layout); + setLabels(static_cast<QCategoryAxis *>(axis())->categoriesLabels() << ""); +} + +void PolarChartCategoryAxisAngular::handleCategoriesChanged() +{ + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); +} + + +#include "moc_polarchartcategoryaxisangular_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/categoryaxis/polarchartcategoryaxisangular_p.h b/src/axis/categoryaxis/polarchartcategoryaxisangular_p.h new file mode 100644 index 00000000..40d8d923 --- /dev/null +++ b/src/axis/categoryaxis/polarchartcategoryaxisangular_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTCATEGORYAXISANGULAR_P_H +#define POLARCHARTCATEGORYAXISANGULAR_P_H + +#include "polarchartaxisangular_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QCategoryAxis; + +class PolarChartCategoryAxisAngular : public PolarChartAxisAngular +{ + Q_OBJECT + +public: + PolarChartCategoryAxisAngular(QCategoryAxis *axis, QGraphicsItem *item); + ~PolarChartCategoryAxisAngular(); + + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +public Q_SLOTS: + void handleCategoriesChanged(); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTCATEGORYAXISANGULAR_P_H diff --git a/src/axis/categoryaxis/polarchartcategoryaxisradial.cpp b/src/axis/categoryaxis/polarchartcategoryaxisradial.cpp new file mode 100644 index 00000000..f4fe3b7b --- /dev/null +++ b/src/axis/categoryaxis/polarchartcategoryaxisradial.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartcategoryaxisradial_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include "qcategoryaxis.h" +#include <QDebug> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartCategoryAxisRadial::PolarChartCategoryAxisRadial(QCategoryAxis *axis, QGraphicsItem *item) + : PolarChartAxisRadial(axis, item, true) +{ + QObject::connect(axis, SIGNAL(categoriesChanged()), this, SLOT(handleCategoriesChanged())); +} + +PolarChartCategoryAxisRadial::~PolarChartCategoryAxisRadial() +{ +} + +QVector<qreal> PolarChartCategoryAxisRadial::calculateLayout() const +{ + QCategoryAxis *catAxis = static_cast<QCategoryAxis *>(axis()); + int tickCount = catAxis->categoriesLabels().count() + 1; + QVector<qreal> points; + + if (tickCount < 2) + return points; + + qreal range = max() - min(); + if (range > 0) { + points.resize(tickCount); + qreal scale = (axisGeometry().width() / 2) / range; + qreal angle; + for (int i = 0; i < tickCount; ++i) { + if (i < tickCount - 1) + angle = (catAxis->startValue(catAxis->categoriesLabels().at(i)) - min()) * scale; + else + angle = (catAxis->endValue(catAxis->categoriesLabels().at(i - 1)) - min()) * scale; + points[i] = angle; + } + } + + return points; +} + +void PolarChartCategoryAxisRadial::createAxisLabels(const QVector<qreal> &layout) +{ + Q_UNUSED(layout); + setLabels(static_cast<QCategoryAxis *>(axis())->categoriesLabels() << ""); +} + +void PolarChartCategoryAxisRadial::handleCategoriesChanged() +{ + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); +} + +#include "moc_polarchartcategoryaxisradial_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/categoryaxis/polarchartcategoryaxisradial_p.h b/src/axis/categoryaxis/polarchartcategoryaxisradial_p.h new file mode 100644 index 00000000..83be8d8a --- /dev/null +++ b/src/axis/categoryaxis/polarchartcategoryaxisradial_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTCATEGORYAXISRADIAL_P_H +#define POLARCHARTCATEGORYAXISRADIAL_P_H + +#include "polarchartaxisradial_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QCategoryAxis; + +class PolarChartCategoryAxisRadial : public PolarChartAxisRadial +{ + Q_OBJECT + +public: + PolarChartCategoryAxisRadial(QCategoryAxis *axis, QGraphicsItem *item); + ~PolarChartCategoryAxisRadial(); + + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +public Q_SLOTS: + void handleCategoriesChanged(); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTCATEGORYAXISRADIAL_P_H diff --git a/src/axis/categoryaxis/qcategoryaxis.cpp b/src/axis/categoryaxis/qcategoryaxis.cpp index 40d8cc25..d187da84 100644 --- a/src/axis/categoryaxis/qcategoryaxis.cpp +++ b/src/axis/categoryaxis/qcategoryaxis.cpp @@ -22,6 +22,8 @@ #include "qcategoryaxis_p.h" #include "chartcategoryaxisx_p.h" #include "chartcategoryaxisy_p.h" +#include "polarchartcategoryaxisangular_p.h" +#include "polarchartcategoryaxisradial_p.h" #include "qchart.h" #include <qmath.h> #include <QDebug> @@ -103,6 +105,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE */ /*! + \fn void QCategoryAxis::categoriesChanged() + Axis emits signal when the categories of the axis have changed. +*/ + + +/*! Constructs an axis object which is a child of \a parent. */ QCategoryAxis::QCategoryAxis(QObject *parent): @@ -151,10 +159,12 @@ void QCategoryAxis::append(const QString &categoryLabel, qreal categoryEndValue) Range range(d->m_categoryMinimum, categoryEndValue); d->m_categoriesMap.insert(categoryLabel, range); d->m_categories.append(categoryLabel); + emit categoriesChanged(); } else if (categoryEndValue > endValue(d->m_categories.last())) { Range previousRange = d->m_categoriesMap.value(d->m_categories.last()); d->m_categoriesMap.insert(categoryLabel, Range(previousRange.second, categoryEndValue)); d->m_categories.append(categoryLabel); + emit categoriesChanged(); } } } @@ -169,10 +179,13 @@ void QCategoryAxis::setStartValue(qreal min) Q_D(QCategoryAxis); if (d->m_categories.isEmpty()) { d->m_categoryMinimum = min; + emit categoriesChanged(); } else { Range range = d->m_categoriesMap.value(d->m_categories.first()); - if (min < range.second) + if (min < range.second) { d->m_categoriesMap.insert(d->m_categories.first(), Range(min, range.second)); + emit categoriesChanged(); + } } } @@ -227,7 +240,7 @@ void QCategoryAxis::remove(const QString &categoryLabel) d->m_categoriesMap.insert(label, range); } } - //TODO:: d->emitUpdated(); + emit categoriesChanged(); } } @@ -251,7 +264,7 @@ void QCategoryAxis::replaceLabel(const QString &oldLabel, const QString &newLabe Range range = d->m_categoriesMap.value(oldLabel); d->m_categoriesMap.remove(oldLabel); d->m_categoriesMap.insert(newLabel, range); - //TODO:: d->emitUpdated(); + emit categoriesChanged(); } } @@ -300,14 +313,23 @@ int QCategoryAxisPrivate::ticksCount() const return m_categories.count() + 1; } -void QCategoryAxisPrivate::initializeGraphics(QGraphicsItem* parent) +void QCategoryAxisPrivate::initializeGraphics(QGraphicsItem *parent) { Q_Q(QCategoryAxis); - ChartAxis* axis(0); - if (orientation() == Qt::Vertical) - axis = new ChartCategoryAxisY(q,parent); - else if(orientation() == Qt::Horizontal) - axis = new ChartCategoryAxisX(q,parent); + ChartAxisElement *axis(0); + if (m_chart->chartType() == QChart::ChartTypeCartesian) { + if (orientation() == Qt::Vertical) + axis = new ChartCategoryAxisY(q,parent); + else if (orientation() == Qt::Horizontal) + axis = new ChartCategoryAxisX(q,parent); + } + + if (m_chart->chartType() == QChart::ChartTypePolar) { + if (orientation() == Qt::Vertical) + axis = new PolarChartCategoryAxisRadial(q, parent); + if (orientation() == Qt::Horizontal) + axis = new PolarChartCategoryAxisAngular(q, parent); + } m_item.reset(axis); QAbstractAxisPrivate::initializeGraphics(parent); diff --git a/src/axis/categoryaxis/qcategoryaxis.h b/src/axis/categoryaxis/qcategoryaxis.h index a7789275..73a33e21 100644 --- a/src/axis/categoryaxis/qcategoryaxis.h +++ b/src/axis/categoryaxis/qcategoryaxis.h @@ -57,6 +57,9 @@ public: QStringList categoriesLabels(); int count() const; +Q_SIGNALS: + void categoriesChanged(); + private: Q_DECLARE_PRIVATE(QCategoryAxis) Q_DISABLE_COPY(QCategoryAxis) diff --git a/src/axis/categoryaxis/qcategoryaxis_p.h b/src/axis/categoryaxis/qcategoryaxis_p.h index 09d80a57..466deaef 100644 --- a/src/axis/categoryaxis/qcategoryaxis_p.h +++ b/src/axis/categoryaxis/qcategoryaxis_p.h @@ -48,9 +48,6 @@ public: void initializeGraphics(QGraphicsItem* parent); int ticksCount() const; -Q_SIGNALS: - void changed(qreal min, qreal max, int tickCount, bool niceNumbers); - private: QMap<QString , Range> m_categoriesMap; QStringList m_categories; diff --git a/src/axis/chartaxis.cpp b/src/axis/chartaxis.cpp deleted file mode 100644 index 7e565acc..00000000 --- a/src/axis/chartaxis.cpp +++ /dev/null @@ -1,547 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc -** All rights reserved. -** For any questions to Digia, please use contact form at http://qt.digia.com -** -** This file is part of the Qt Commercial Charts Add-on. -** -** $QT_BEGIN_LICENSE$ -** Licensees holding valid Qt Commercial licenses may use this file in -** accordance with the Qt Commercial License Agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. -** -** If you have questions regarding the use of this file, please use -** contact form at http://qt.digia.com -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "chartaxis_p.h" -#include "qabstractaxis.h" -#include "qabstractaxis_p.h" -#include "chartpresenter_p.h" -#include "chartlayout_p.h" -#include "abstractdomain_p.h" -#include <qmath.h> -#include <QDateTime> -#include <QValueAxis> -#include <QLogValueAxis> -#include <QGraphicsLayout> -#include <QFontMetrics> - -QTCOMMERCIALCHART_BEGIN_NAMESPACE - -ChartAxis::ChartAxis(QAbstractAxis *axis, QGraphicsItem* item , bool intervalAxis) - : ChartElement(item), - m_axis(axis), - m_labelsAngle(axis->labelsAngle()), - m_grid(new QGraphicsItemGroup(item)), - m_arrow(new QGraphicsItemGroup(item)), - m_shades(new QGraphicsItemGroup(item)), - m_labels(new QGraphicsItemGroup(item)), - m_title(new QGraphicsSimpleTextItem(item)), - m_animation(0), - m_labelPadding(5), - m_intervalAxis(intervalAxis), - m_titlePadding(3) -{ - Q_ASSERT(item); - //initial initialization - m_arrow->setHandlesChildEvents(false); - m_arrow->setZValue(ChartPresenter::AxisZValue); - m_labels->setZValue(ChartPresenter::AxisZValue); - m_shades->setZValue(ChartPresenter::ShadesZValue); - m_grid->setZValue(ChartPresenter::GridZValue); - m_title->setZValue(ChartPresenter::GridZValue); - handleVisibleChanged(m_axis->isVisible()); - connectSlots(); - - setFlag(QGraphicsItem::ItemHasNoContents,true); -} - -void ChartAxis::connectSlots() -{ - QObject::connect(m_axis,SIGNAL(visibleChanged(bool)),this,SLOT(handleVisibleChanged(bool))); - QObject::connect(m_axis,SIGNAL(lineVisibleChanged(bool)),this,SLOT(handleArrowVisibleChanged(bool))); - QObject::connect(m_axis,SIGNAL(gridVisibleChanged(bool)),this,SLOT(handleGridVisibleChanged(bool))); - QObject::connect(m_axis,SIGNAL(labelsVisibleChanged(bool)),this,SLOT(handleLabelsVisibleChanged(bool))); - QObject::connect(m_axis,SIGNAL(shadesVisibleChanged(bool)),this,SLOT(handleShadesVisibleChanged(bool))); - QObject::connect(m_axis,SIGNAL(labelsAngleChanged(int)),this,SLOT(handleLabelsAngleChanged(int))); - QObject::connect(m_axis,SIGNAL(linePenChanged(const QPen&)),this,SLOT(handleArrowPenChanged(const QPen&))); - QObject::connect(m_axis,SIGNAL(labelsPenChanged(const QPen&)),this,SLOT(handleLabelsPenChanged(const QPen&))); - QObject::connect(m_axis,SIGNAL(labelsBrushChanged(const QBrush&)),this,SLOT(handleLabelsBrushChanged(const QBrush&))); - QObject::connect(m_axis,SIGNAL(labelsFontChanged(const QFont&)),this,SLOT(handleLabelsFontChanged(const QFont&))); - QObject::connect(m_axis,SIGNAL(gridLinePenChanged(const QPen&)),this,SLOT(handleGridPenChanged(const QPen&))); - QObject::connect(m_axis,SIGNAL(shadesPenChanged(const QPen&)),this,SLOT(handleShadesPenChanged(const QPen&))); - QObject::connect(m_axis,SIGNAL(shadesBrushChanged(const QBrush&)),this,SLOT(handleShadesBrushChanged(const QBrush&))); - QObject::connect(m_axis,SIGNAL(titleTextChanged(const QString&)),this,SLOT(handleTitleTextChanged(const QString&))); - QObject::connect(m_axis,SIGNAL(titleFontChanged(const QFont&)),this,SLOT(handleTitleFontChanged(const QFont&))); - QObject::connect(m_axis,SIGNAL(titlePenChanged(const QPen&)),this,SLOT(handleTitlePenChanged(const QPen&))); - QObject::connect(m_axis,SIGNAL(titleBrushChanged(const QBrush&)),this,SLOT(handleTitleBrushChanged(const QBrush&))); - QObject::connect(m_axis,SIGNAL(titleVisibleChanged(bool)),this,SLOT(handleTitleVisibleChanged(bool))); - QObject::connect(m_axis->d_ptr.data(),SIGNAL(rangeChanged(qreal,qreal)),this,SLOT(handleRangeChanged(qreal,qreal))); -} - -ChartAxis::~ChartAxis() -{ -} - -void ChartAxis::setAnimation(AxisAnimation *animation) -{ - m_animation = animation; -} - -void ChartAxis::setLayout(QVector<qreal> &layout) -{ - m_layoutVector = layout; -} - -void ChartAxis::createItems(int count) -{ - if (m_arrow->childItems().size() == 0){ - QGraphicsLineItem* arrow = new ArrowItem(this, this); - arrow->setPen(m_axis->linePen()); - m_arrow->addToGroup(arrow); - } - - if (m_intervalAxis && m_grid->childItems().size() == 0) { - for (int i = 0 ; i < 2 ; i ++){ - QGraphicsLineItem* item = new QGraphicsLineItem(this); - item->setPen(m_axis->gridLinePen()); - m_grid->addToGroup(item); - } - } - - for (int i = 0; i < count; ++i) { - QGraphicsLineItem* arrow = new QGraphicsLineItem(this); - arrow->setPen(m_axis->linePen()); - QGraphicsLineItem* grid = new QGraphicsLineItem(this); - grid->setPen(m_axis->gridLinePen()); - QGraphicsSimpleTextItem* label = new QGraphicsSimpleTextItem(this); - label->setFont(m_axis->labelsFont()); - label->setPen(m_axis->labelsPen()); - label->setBrush(m_axis->labelsBrush()); - label->setRotation(m_labelsAngle); - m_arrow->addToGroup(arrow); - m_grid->addToGroup(grid); - m_labels->addToGroup(label); - - if ((m_grid->childItems().size()) % 2 && m_grid->childItems().size() > 2){ - QGraphicsRectItem* shades = new QGraphicsRectItem(this); - shades->setPen(m_axis->shadesPen()); - shades->setBrush(m_axis->shadesBrush()); - m_shades->addToGroup(shades); - } - } - -} - -void ChartAxis::deleteItems(int count) -{ - QList<QGraphicsItem *> lines = m_grid->childItems(); - QList<QGraphicsItem *> labels = m_labels->childItems(); - QList<QGraphicsItem *> shades = m_shades->childItems(); - QList<QGraphicsItem *> axis = m_arrow->childItems(); - - for (int i = 0; i < count; ++i) { - if (lines.size() % 2 && lines.size() > 1) - delete(shades.takeLast()); - delete(lines.takeLast()); - delete(labels.takeLast()); - delete(axis.takeLast()); - } -} - -void ChartAxis::updateLayout(QVector<qreal> &layout) -{ - int diff = m_layoutVector.size() - layout.size(); - - if (diff > 0) - deleteItems(diff); - else if (diff < 0) - createItems(-diff); - - if (m_animation) { - switch (presenter()->state()) { - case ChartPresenter::ZoomInState: - m_animation->setAnimationType(AxisAnimation::ZoomInAnimation); - m_animation->setAnimationPoint(presenter()->statePoint()); - break; - case ChartPresenter::ZoomOutState: - m_animation->setAnimationType(AxisAnimation::ZoomOutAnimation); - m_animation->setAnimationPoint(presenter()->statePoint()); - break; - case ChartPresenter::ScrollUpState: - case ChartPresenter::ScrollLeftState: - m_animation->setAnimationType(AxisAnimation::MoveBackwordAnimation); - break; - case ChartPresenter::ScrollDownState: - case ChartPresenter::ScrollRightState: - m_animation->setAnimationType(AxisAnimation::MoveForwardAnimation); - break; - case ChartPresenter::ShowState: - m_animation->setAnimationType(AxisAnimation::DefaultAnimation); - break; - } - m_animation->setValues(m_layoutVector, layout); - presenter()->startAnimation(m_animation); - } else { - setLayout(layout); - updateGeometry(); - } -} - -void ChartAxis::setLabelPadding(int padding) -{ - m_labelPadding = padding; -} - -void ChartAxis::setTitlePadding(int padding) -{ - m_titlePadding = padding; -} - -bool ChartAxis::isEmpty() -{ - return m_axisRect.isEmpty() || m_gridRect.isEmpty() || qFuzzyCompare(min(),max()); -} - -void ChartAxis::setGeometry(const QRectF &axis, const QRectF &grid) -{ - m_gridRect = grid; - m_axisRect = axis; - - if (isEmpty()) - return; - - QVector<qreal> layout = calculateLayout(); - updateLayout(layout); -} - -qreal ChartAxis::min() const -{ - return m_axis->d_ptr->min(); -} - -qreal ChartAxis::max() const -{ - return m_axis->d_ptr->max(); -} - -QFont ChartAxis::font() const -{ - return m_axis->labelsFont(); -} - -QFont ChartAxis::titleFont() const -{ - return m_axis->titleFont(); -} - -QString ChartAxis::titleText() const -{ - return m_axis->titleText(); -} - -void ChartAxis::axisSelected() -{ - emit clicked(); -} - -Qt::Orientation ChartAxis::orientation() const -{ - return m_axis->orientation(); -} - -Qt::Alignment ChartAxis::alignment() const -{ - return m_axis->alignment(); -} - -void ChartAxis::setLabels(const QStringList &labels) -{ - m_labelsList = labels; -} - -QSizeF ChartAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const -{ - Q_UNUSED(which); - Q_UNUSED(constraint); - return QSizeF(); -} - -//handlers - -void ChartAxis::handleArrowVisibleChanged(bool visible) -{ - m_arrow->setVisible(visible); -} - -void ChartAxis::handleGridVisibleChanged(bool visible) -{ - m_grid->setVisible(visible); -} - -void ChartAxis::handleLabelsVisibleChanged(bool visible) -{ - m_labels->setVisible(visible); -} - -void ChartAxis::handleShadesVisibleChanged(bool visible) -{ - m_shades->setVisible(visible); -} - -void ChartAxis::handleTitleVisibleChanged(bool visible) -{ - QGraphicsLayoutItem::updateGeometry(); - presenter()->layout()->invalidate(); - m_title->setVisible(visible); -} - -void ChartAxis::handleLabelsAngleChanged(int angle) -{ - foreach (QGraphicsItem *item, m_labels->childItems()) - item->setRotation(angle); - - m_labelsAngle = angle; -} - -void ChartAxis::handleLabelsPenChanged(const QPen &pen) -{ - foreach (QGraphicsItem *item , m_labels->childItems()) - static_cast<QGraphicsSimpleTextItem *>(item)->setPen(pen); -} - -void ChartAxis::handleLabelsBrushChanged(const QBrush &brush) -{ - foreach (QGraphicsItem *item , m_labels->childItems()) - static_cast<QGraphicsSimpleTextItem *>(item)->setBrush(brush); -} - -void ChartAxis::handleLabelsFontChanged(const QFont &font) -{ - foreach (QGraphicsItem *item , m_labels->childItems()) - static_cast<QGraphicsSimpleTextItem *>(item)->setFont(font); - QGraphicsLayoutItem::updateGeometry(); - presenter()->layout()->invalidate(); -} - -void ChartAxis::handleShadesBrushChanged(const QBrush &brush) -{ - foreach (QGraphicsItem *item , m_shades->childItems()) - static_cast<QGraphicsRectItem *>(item)->setBrush(brush); -} - -void ChartAxis::handleShadesPenChanged(const QPen &pen) -{ - foreach (QGraphicsItem *item , m_shades->childItems()) - static_cast<QGraphicsRectItem *>(item)->setPen(pen); -} - -void ChartAxis::handleArrowPenChanged(const QPen &pen) -{ - foreach (QGraphicsItem *item , m_arrow->childItems()) - static_cast<QGraphicsLineItem *>(item)->setPen(pen); -} - -void ChartAxis::handleGridPenChanged(const QPen &pen) -{ - foreach (QGraphicsItem *item , m_grid->childItems()) - static_cast<QGraphicsLineItem *>(item)->setPen(pen); -} - -void ChartAxis::handleTitleTextChanged(const QString &title) -{ - QGraphicsLayoutItem::updateGeometry(); - presenter()->layout()->invalidate(); - m_title->setText(title); -} - - -void ChartAxis::handleTitlePenChanged(const QPen &pen) -{ - m_title->setPen(pen); -} - -void ChartAxis::handleTitleBrushChanged(const QBrush &brush) -{ - m_title->setBrush(brush); -} - -void ChartAxis::handleTitleFontChanged(const QFont &font) -{ - if(m_title->font() != font){ - m_title->setFont(font); - QGraphicsLayoutItem::updateGeometry(); - presenter()->layout()->invalidate(); - } -} - -void ChartAxis::handleVisibleChanged(bool visible) -{ - setVisible(visible); - if(!visible) { - m_grid->setVisible(visible); - m_arrow->setVisible(visible); - m_shades->setVisible(visible); - m_labels->setVisible(visible); - m_title->setVisible(visible); - }else { - m_grid->setVisible(m_axis->isGridLineVisible()); - m_arrow->setVisible(m_axis->isLineVisible()); - m_shades->setVisible(m_axis->shadesVisible()); - m_labels->setVisible(m_axis->labelsVisible()); - m_title->setVisible(m_axis->isTitleVisible()); - } - - if(presenter()) presenter()->layout()->invalidate(); -} - -void ChartAxis::handleRangeChanged(qreal min, qreal max) -{ - Q_UNUSED(min); - Q_UNUSED(max); - - if (!isEmpty()) { - - QVector<qreal> layout = calculateLayout(); - updateLayout(layout); - QSizeF before = effectiveSizeHint(Qt::PreferredSize); - QSizeF after = sizeHint(Qt::PreferredSize); - - if (before != after) { - QGraphicsLayoutItem::updateGeometry(); - //we don't want to call invalidate on layout, since it will change minimum size of component, - //which we would like to avoid since it causes nasty flips when scrolling or zooming, - //instead recalculate layout and use plotArea for extra space. - presenter()->layout()->setGeometry(presenter()->layout()->geometry()); - } - } - -} - -//helpers - -QStringList ChartAxis::createValueLabels(qreal min, qreal max, int ticks,const QString& format) -{ - QStringList labels; - - if (max <= min || ticks < 1) - return labels; - - int n = qMax(int(-qFloor(log10((max - min) / (ticks - 1)))), 0); - n++; - - if (format.isNull()) { - for (int i = 0; i < ticks; i++) { - qreal value = min + (i * (max - min) / (ticks - 1)); - labels << QString::number(value, 'f', n); - } - } else { - QByteArray array = format.toLatin1(); - for (int i = 0; i < ticks; i++) { - qreal value = min + (i * (max - min) / (ticks - 1)); - if (format.contains("d") - || format.contains("i") - || format.contains("c")) - labels << QString().sprintf(array, (qint64)value); - else if (format.contains("u") - || format.contains("o") - || format.contains("x", Qt::CaseInsensitive)) - labels << QString().sprintf(array, (quint64)value); - else if (format.contains("f", Qt::CaseInsensitive) - || format.contains("e", Qt::CaseInsensitive) - || format.contains("g", Qt::CaseInsensitive)) - labels << QString().sprintf(array, value); - else - labels << QString(); - } - } - - return labels; -} - -QStringList ChartAxis::createLogValueLabels(qreal min, qreal max, qreal base, int ticks, const QString& format) -{ -// Q_ASSERT(m_max > m_min); - // Q_ASSERT(ticks > 1); - - QStringList labels; - - int n = 0; - if (ticks > 1) - n = qMax(int(-qFloor(log10((max - min) / (ticks - 1)))), 0); - n++; - - int firstTick; - if (base > 1) - firstTick = ceil(log10(min) / log10(base)); - else - firstTick = ceil(log10(max) / log10(base)); - - if (format.isNull()) { - for (int i = firstTick; i < ticks + firstTick; i++) { - qreal value = qPow(base, i); - labels << QString::number(value, 'f', n); - } - } else { - QByteArray array = format.toLatin1(); - for (int i = firstTick; i < ticks + firstTick; i++) { - qreal value = qPow(base, i); - if (format.contains("d") - || format.contains("i") - || format.contains("c")) - labels << QString().sprintf(array, (qint64)value); - else if (format.contains("u") - || format.contains("o") - || format.contains("x", Qt::CaseInsensitive)) - labels << QString().sprintf(array, (quint64)value); - else if (format.contains("f", Qt::CaseInsensitive) - || format.contains("e", Qt::CaseInsensitive) - || format.contains("g", Qt::CaseInsensitive)) - labels << QString().sprintf(array, value); - else - labels << QString(); - } - } - - return labels; -} - -QStringList ChartAxis::createDateTimeLabels(qreal min, qreal max,int ticks,const QString& format) -{ - QStringList labels; - - if (max <= min || ticks < 1) { - return labels; - } - - int n = qMax(int(-floor(log10((max - min) / (ticks - 1)))), 0); - n++; - for (int i = 0; i < ticks; i++) { - qreal value = min + (i * (max - min) / (ticks - 1)); - labels << QDateTime::fromMSecsSinceEpoch(value).toString(format); - } - return labels; -} - -QRect ChartAxis::labelBoundingRect(const QFontMetrics &fn, const QString &label) const -{ - QRect boundingRect = fn.boundingRect(label); - - // Take label rotation into account - if (m_labelsAngle) { - QTransform transform; - transform.rotate(m_labelsAngle); - boundingRect = transform.mapRect(boundingRect); - } - - return boundingRect; -} - -#include "moc_chartaxis_p.cpp" - -QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/chartaxis_p.h b/src/axis/chartaxis_p.h deleted file mode 100644 index 681613f8..00000000 --- a/src/axis/chartaxis_p.h +++ /dev/null @@ -1,201 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc -** All rights reserved. -** For any questions to Digia, please use contact form at http://qt.digia.com -** -** This file is part of the Qt Commercial Charts Add-on. -** -** $QT_BEGIN_LICENSE$ -** Licensees holding valid Qt Commercial licenses may use this file in -** accordance with the Qt Commercial License Agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. -** -** If you have questions regarding the use of this file, please use -** contact form at http://qt.digia.com -** $QT_END_LICENSE$ -** -****************************************************************************/ - -// W A R N I N G -// ------------- -// -// This file is not part of the QtCommercial Chart API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. - -#ifndef CHARTAXIS_H -#define CHARTAXIS_H - -#include "qchartglobal.h" -#include "chartelement_p.h" -#include "axisanimation_p.h" -#include <QGraphicsItem> -#include <QGraphicsLayoutItem> -#include <QFont> - -QTCOMMERCIALCHART_BEGIN_NAMESPACE - -class QAbstractAxis; -class ChartPresenter; - -class ChartAxis : public ChartElement, public QGraphicsLayoutItem -{ - Q_OBJECT - Q_INTERFACES(QGraphicsLayoutItem) -public: - - ChartAxis(QAbstractAxis *axis, QGraphicsItem* item = 0, bool intervalAxis = false); - ~ChartAxis(); - - QAbstractAxis* axis() const { return m_axis; } - - void setLabelPadding(int padding); - int labelPadding() const { return m_labelPadding;}; - - void setTitlePadding(int padding); - int titlePadding() const { return m_titlePadding;}; - - QFont titleFont() const; - QString titleText() const; - - void setLayout(QVector<qreal> &layout); - QVector<qreal> layout() const { return m_layoutVector; } - - void setAnimation(AxisAnimation *animation); - ChartAnimation *animation() const { return m_animation; }; - - Qt::Orientation orientation() const; - Qt::Alignment alignment() const; - - void setGeometry(const QRectF &axis, const QRectF &grid); - QRectF axisGeometry() const { return m_axisRect; } - QRectF gridGeometry() const { return m_gridRect; } - - void setLabels(const QStringList &labels); - QStringList labels() const { return m_labelsList; } - - //this flag indicates that axis is used to show intervals it means labels are in between ticks - bool intervalAxis() const { return m_intervalAxis; } - - virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; - - - QRectF boundingRect() const{ - return QRectF(); - } - - void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) - { - - } - -//helpers - static QStringList createValueLabels(qreal max, qreal min, int ticks, const QString &format); - static QStringList createLogValueLabels(qreal min, qreal max, qreal base, int ticks, const QString& format); - static QStringList createDateTimeLabels(qreal max, qreal min, int ticks, const QString &format); - -protected: - void setGeometry(const QRectF &size) { Q_UNUSED(size);}; - virtual void updateGeometry() = 0; - virtual QVector<qreal> calculateLayout() const = 0; - - QList<QGraphicsItem *> lineItems() { return m_grid->childItems(); }; - QList<QGraphicsItem *> labelItems() { return m_labels->childItems();}; - QList<QGraphicsItem *> shadeItems() { return m_shades->childItems();}; - QList<QGraphicsItem *> arrowItems() { return m_arrow->childItems();}; - QGraphicsSimpleTextItem* titleItem() const { return m_title.data();} - - QFont font() const; - qreal min() const; - qreal max() const; - QRect labelBoundingRect(const QFontMetrics &fn, const QString &label) const; - -//handlers -public Q_SLOTS: - void handleVisibleChanged(bool visible); - void handleArrowVisibleChanged(bool visible); - void handleGridVisibleChanged(bool visible); - void handleLabelsVisibleChanged(bool visible); - void handleShadesVisibleChanged(bool visible); - void handleLabelsAngleChanged(int angle); - void handleShadesBrushChanged(const QBrush &brush); - void handleShadesPenChanged(const QPen &pen); - void handleArrowPenChanged(const QPen &pen); - void handleGridPenChanged(const QPen &pen); - void handleLabelsPenChanged(const QPen &pen); - void handleLabelsBrushChanged(const QBrush &brush); - void handleLabelsFontChanged(const QFont &font); - void handleTitlePenChanged(const QPen &pen); - void handleTitleBrushChanged(const QBrush &brush); - void handleTitleFontChanged(const QFont &font); - void handleTitleTextChanged(const QString &title); - void handleTitleVisibleChanged(bool visible); - void handleRangeChanged(qreal min , qreal max); - -Q_SIGNALS: - void clicked(); - -private: - inline bool isEmpty(); - void createItems(int count); - void deleteItems(int count); - void updateLayout(QVector<qreal> &layout); - void axisSelected(); - void connectSlots(); - -private: - QAbstractAxis *m_axis; - int m_labelsAngle; - QRectF m_axisRect; - QRectF m_gridRect; - QScopedPointer<QGraphicsItemGroup> m_grid; - QScopedPointer<QGraphicsItemGroup> m_arrow; - QScopedPointer<QGraphicsItemGroup> m_shades; - QScopedPointer<QGraphicsItemGroup> m_labels; - QScopedPointer<QGraphicsSimpleTextItem> m_title; - QVector<qreal> m_layoutVector; - AxisAnimation *m_animation; - int m_labelPadding; - QStringList m_labelsList; - bool m_intervalAxis; - int m_titlePadding; - - friend class AxisAnimation; - friend class ArrowItem; - -}; - -class ArrowItem: public QGraphicsLineItem -{ - -public: - explicit ArrowItem(ChartAxis *axis, QGraphicsItem *parent = 0) : QGraphicsLineItem(parent), m_axis(axis) {} - -protected: - void mousePressEvent(QGraphicsSceneMouseEvent *event) { - Q_UNUSED(event) - m_axis->axisSelected(); - } - - QRectF boundingRect() const { - return shape().boundingRect(); - } - - QPainterPath shape() const { - QPainterPath path = QGraphicsLineItem::shape(); - QRectF rect = path.boundingRect(); - path.addRect(rect.adjusted(0, 0, m_axis->orientation() != Qt::Horizontal ? 8 : 0, m_axis->orientation() != Qt::Vertical ? 8 : 0)); - return path; - } - -private: - ChartAxis *m_axis; -}; - -QTCOMMERCIALCHART_END_NAMESPACE - -#endif /* CHARTAXI_H */ diff --git a/src/axis/chartaxiselement.cpp b/src/axis/chartaxiselement.cpp new file mode 100644 index 00000000..19fa8e17 --- /dev/null +++ b/src/axis/chartaxiselement.cpp @@ -0,0 +1,349 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "chartaxiselement_p.h" +#include "qabstractaxis_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include <qmath.h> +#include <QDateTime> +#include <QFontMetrics> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +ChartAxisElement::ChartAxisElement(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) + : ChartElement(item), + m_axis(axis), + m_animation(0), + m_grid(new QGraphicsItemGroup(item)), + m_arrow(new QGraphicsItemGroup(item)), + m_shades(new QGraphicsItemGroup(item)), + m_labels(new QGraphicsItemGroup(item)), + m_title(new QGraphicsSimpleTextItem(item)), + m_intervalAxis(intervalAxis) + +{ + //initial initialization + m_arrow->setHandlesChildEvents(false); + m_arrow->setZValue(ChartPresenter::AxisZValue); + m_labels->setZValue(ChartPresenter::AxisZValue); + m_shades->setZValue(ChartPresenter::ShadesZValue); + m_grid->setZValue(ChartPresenter::GridZValue); + m_title->setZValue(ChartPresenter::GridZValue); + handleVisibleChanged(axis->isVisible()); + connectSlots(); + + setFlag(QGraphicsItem::ItemHasNoContents, true); +} + +ChartAxisElement::~ChartAxisElement() +{ +} + +void ChartAxisElement::connectSlots() +{ + QObject::connect(axis(), SIGNAL(visibleChanged(bool)), this, SLOT(handleVisibleChanged(bool))); + QObject::connect(axis(), SIGNAL(lineVisibleChanged(bool)), this, SLOT(handleArrowVisibleChanged(bool))); + QObject::connect(axis(), SIGNAL(gridVisibleChanged(bool)), this, SLOT(handleGridVisibleChanged(bool))); + QObject::connect(axis(), SIGNAL(labelsVisibleChanged(bool)), this, SLOT(handleLabelsVisibleChanged(bool))); + QObject::connect(axis(), SIGNAL(shadesVisibleChanged(bool)), this, SLOT(handleShadesVisibleChanged(bool))); + QObject::connect(axis(), SIGNAL(labelsAngleChanged(int)), this, SLOT(handleLabelsAngleChanged(int))); + QObject::connect(axis(), SIGNAL(linePenChanged(const QPen&)), this, SLOT(handleArrowPenChanged(const QPen&))); + QObject::connect(axis(), SIGNAL(labelsPenChanged(const QPen&)), this, SLOT(handleLabelsPenChanged(const QPen&))); + QObject::connect(axis(), SIGNAL(labelsBrushChanged(const QBrush&)), this, SLOT(handleLabelsBrushChanged(const QBrush&))); + QObject::connect(axis(), SIGNAL(labelsFontChanged(const QFont&)), this, SLOT(handleLabelsFontChanged(const QFont&))); + QObject::connect(axis(), SIGNAL(gridLinePenChanged(const QPen&)), this, SLOT(handleGridPenChanged(const QPen&))); + QObject::connect(axis(), SIGNAL(shadesPenChanged(const QPen&)), this, SLOT(handleShadesPenChanged(const QPen&))); + QObject::connect(axis(), SIGNAL(shadesBrushChanged(const QBrush&)), this, SLOT(handleShadesBrushChanged(const QBrush&))); + QObject::connect(axis(), SIGNAL(titleTextChanged(const QString&)), this, SLOT(handleTitleTextChanged(const QString&))); + QObject::connect(axis(), SIGNAL(titleFontChanged(const QFont&)), this, SLOT(handleTitleFontChanged(const QFont&))); + QObject::connect(axis(), SIGNAL(titlePenChanged(const QPen&)), this, SLOT(handleTitlePenChanged(const QPen&))); + 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))); +} + +void ChartAxisElement::handleArrowVisibleChanged(bool visible) +{ + m_arrow->setVisible(visible); +} + +void ChartAxisElement::handleGridVisibleChanged(bool visible) +{ + m_grid->setVisible(visible); +} + +void ChartAxisElement::handleLabelsVisibleChanged(bool visible) +{ + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); + m_labels->setVisible(visible); +} + +void ChartAxisElement::handleShadesVisibleChanged(bool visible) +{ + m_shades->setVisible(visible); +} + +void ChartAxisElement::handleTitleVisibleChanged(bool visible) +{ + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); + m_title->setVisible(visible); +} + +void ChartAxisElement::handleLabelsAngleChanged(int angle) +{ + foreach (QGraphicsItem *item, m_labels->childItems()) + item->setRotation(angle); + + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); +} + +void ChartAxisElement::handleLabelsPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, m_labels->childItems()) + static_cast<QGraphicsSimpleTextItem *>(item)->setPen(pen); +} + +void ChartAxisElement::handleLabelsBrushChanged(const QBrush &brush) +{ + foreach (QGraphicsItem *item, m_labels->childItems()) + static_cast<QGraphicsSimpleTextItem *>(item)->setBrush(brush); +} + +void ChartAxisElement::handleLabelsFontChanged(const QFont &font) +{ + foreach (QGraphicsItem *item, m_labels->childItems()) + static_cast<QGraphicsSimpleTextItem *>(item)->setFont(font); + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); +} + +void ChartAxisElement::handleTitleTextChanged(const QString &title) +{ + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); + m_title->setText(title); +} + +void ChartAxisElement::handleTitlePenChanged(const QPen &pen) +{ + m_title->setPen(pen); +} + +void ChartAxisElement::handleTitleBrushChanged(const QBrush &brush) +{ + m_title->setBrush(brush); +} + +void ChartAxisElement::handleTitleFontChanged(const QFont &font) +{ + if (m_title->font() != font) { + m_title->setFont(font); + QGraphicsLayoutItem::updateGeometry(); + presenter()->layout()->invalidate(); + } +} + +void ChartAxisElement::handleVisibleChanged(bool visible) +{ + setVisible(visible); + if (!visible) { + m_grid->setVisible(visible); + m_arrow->setVisible(visible); + m_shades->setVisible(visible); + m_labels->setVisible(visible); + m_title->setVisible(visible); + } else { + m_grid->setVisible(axis()->isGridLineVisible()); + m_arrow->setVisible(axis()->isLineVisible()); + m_shades->setVisible(axis()->shadesVisible()); + m_labels->setVisible(axis()->labelsVisible()); + m_title->setVisible(axis()->isTitleVisible()); + } + + if (presenter()) presenter()->layout()->invalidate(); +} + +QRect ChartAxisElement::labelBoundingRect(const QFontMetrics &fn, const QString &label) const +{ + QRect boundingRect = fn.boundingRect(label); + // Take label rotation into account + if (axis()->labelsAngle()) { + QTransform transform; + transform.rotate(axis()->labelsAngle()); + boundingRect = transform.mapRect(boundingRect); + } + + return boundingRect; +} + +void ChartAxisElement::handleRangeChanged(qreal min, qreal max) +{ + Q_UNUSED(min); + Q_UNUSED(max); + + if (!isEmpty()) { + QVector<qreal> layout = calculateLayout(); + updateLayout(layout); + QSizeF before = effectiveSizeHint(Qt::PreferredSize); + QSizeF after = sizeHint(Qt::PreferredSize); + + if (before != after) { + QGraphicsLayoutItem::updateGeometry(); + // We don't want to call invalidate on layout, since it will change minimum size of + // component, which we would like to avoid since it causes nasty flips when scrolling + // or zooming, instead recalculate layout and use plotArea for extra space. + presenter()->layout()->setGeometry(presenter()->layout()->geometry()); + } + } +} + +bool ChartAxisElement::isEmpty() +{ + return axisGeometry().isEmpty() + || gridGeometry().isEmpty() + || qFuzzyCompare(min(), max()); +} + +qreal ChartAxisElement::min() const +{ + return m_axis->d_ptr->min(); +} + +qreal ChartAxisElement::max() const +{ + return m_axis->d_ptr->max(); +} + +QStringList ChartAxisElement::createValueLabels(qreal min, qreal max, int ticks, const QString &format) +{ + QStringList labels; + + if (max <= min || ticks < 1) + return labels; + + int n = qMax(int(-qFloor(log10((max - min) / (ticks - 1)))), 0); + n++; + + if (format.isNull()) { + for (int i = 0; i < ticks; i++) { + qreal value = min + (i * (max - min) / (ticks - 1)); + labels << QString::number(value, 'f', n); + } + } else { + QByteArray array = format.toLatin1(); + for (int i = 0; i < ticks; i++) { + qreal value = min + (i * (max - min) / (ticks - 1)); + if (format.contains("d") + || format.contains("i") + || format.contains("c")) { + labels << QString().sprintf(array, (qint64)value); + } else if (format.contains("u") + || format.contains("o") + || format.contains("x", Qt::CaseInsensitive)) { + labels << QString().sprintf(array, (quint64)value); + } else if (format.contains("f", Qt::CaseInsensitive) + || format.contains("e", Qt::CaseInsensitive) + || format.contains("g", Qt::CaseInsensitive)) { + labels << QString().sprintf(array, value); + } else { + labels << QString(); + } + } + } + + return labels; +} + +QStringList ChartAxisElement::createLogValueLabels(qreal min, qreal max, qreal base, int ticks, const QString &format) +{ + QStringList labels; + + if (max <= min || ticks < 1) + return labels; + + int n = 0; + if (ticks > 1) + n = qMax(int(-qFloor(log10((max - min) / (ticks - 1)))), 0); + n++; + + int firstTick; + if (base > 1) + firstTick = ceil(log10(min) / log10(base)); + else + firstTick = ceil(log10(max) / log10(base)); + + if (format.isNull()) { + for (int i = firstTick; i < ticks + firstTick; i++) { + qreal value = qPow(base, i); + labels << QString::number(value, 'f', n); + } + } else { + QByteArray array = format.toLatin1(); + for (int i = firstTick; i < ticks + firstTick; i++) { + qreal value = qPow(base, i); + if (format.contains("d") + || format.contains("i") + || format.contains("c")) { + labels << QString().sprintf(array, (qint64)value); + } else if (format.contains("u") + || format.contains("o") + || format.contains("x", Qt::CaseInsensitive)) { + labels << QString().sprintf(array, (quint64)value); + } else if (format.contains("f", Qt::CaseInsensitive) + || format.contains("e", Qt::CaseInsensitive) + || format.contains("g", Qt::CaseInsensitive)) { + labels << QString().sprintf(array, value); + } else { + labels << QString(); + } + } + } + + return labels; +} + +QStringList ChartAxisElement::createDateTimeLabels(qreal min, qreal max,int ticks,const QString &format) +{ + QStringList labels; + + if (max <= min || ticks < 1) + return labels; + + int n = qMax(int(-floor(log10((max - min) / (ticks - 1)))), 0); + n++; + for (int i = 0; i < ticks; i++) { + qreal value = min + (i * (max - min) / (ticks - 1)); + labels << QDateTime::fromMSecsSinceEpoch(value).toString(format); + } + return labels; +} + +void ChartAxisElement::axisSelected() +{ + emit clicked(); +} + +#include "moc_chartaxiselement_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/chartaxiselement_p.h b/src/axis/chartaxiselement_p.h new file mode 100644 index 00000000..0fdd207d --- /dev/null +++ b/src/axis/chartaxiselement_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CHARTAXISELEMENT_H +#define CHARTAXISELEMENT_H + +#include "qchartglobal.h" +#include "chartelement_p.h" +#include "axisanimation_p.h" +#include <QGraphicsItem> +#include <QGraphicsLayoutItem> +#include <QFont> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class ChartPresenter; +class QAbstractAxis; + +class ChartAxisElement : public ChartElement, public QGraphicsLayoutItem +{ + Q_OBJECT +public: + ChartAxisElement(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis = false); + ~ChartAxisElement(); + + virtual QRectF gridGeometry() const = 0; + virtual void setGeometry(const QRectF &axis, const QRectF &grid) = 0; + virtual bool isEmpty() = 0; + + void setAnimation(AxisAnimation *animation) { m_animation = animation; } + AxisAnimation *animation() const { return m_animation; } + + QAbstractAxis *axis() const { return m_axis; } + void setLayout(QVector<qreal> &layout) { m_layout = layout; } + QVector<qreal> &layout() { return m_layout; } // Modifiable reference + int labelPadding() const { return 5; } + int titlePadding() const { return 3; } + void setLabels(const QStringList &labels) { m_labelsList = labels; } + QStringList labels() const { return m_labelsList; } + + qreal min() const; + qreal max() const; + + QRectF axisGeometry() const { return m_axisRect; } + void setAxisGeometry(const QRectF &axisGeometry) { m_axisRect = axisGeometry; } + + QRect labelBoundingRect(const QFontMetrics &fn, const QString &label) const; + + void axisSelected(); + + //this flag indicates that axis is used to show intervals it means labels are in between ticks + bool intervalAxis() const { return m_intervalAxis; } + + static QStringList createValueLabels(qreal max, qreal min, int ticks, const QString &format); + static QStringList createLogValueLabels(qreal min, qreal max, qreal base, int ticks, const QString &format); + static QStringList createDateTimeLabels(qreal max, qreal min, int ticks, const QString &format); + + // from QGraphicsLayoutItem + QRectF boundingRect() const + { + return QRectF(); + } + + // from QGraphicsLayoutItem + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) + { + } + +protected: + virtual QVector<qreal> calculateLayout() const = 0; + virtual void updateLayout(QVector<qreal> &layout) = 0; + + QList<QGraphicsItem *> gridItems() { return m_grid->childItems(); } + QList<QGraphicsItem *> labelItems() { return m_labels->childItems(); } + QList<QGraphicsItem *> shadeItems() { return m_shades->childItems(); } + QList<QGraphicsItem *> arrowItems() { return m_arrow->childItems(); } + QGraphicsSimpleTextItem *titleItem() const { return m_title.data(); } + QGraphicsItemGroup *gridGroup() { return m_grid.data(); } + QGraphicsItemGroup *labelGroup() { return m_labels.data(); } + QGraphicsItemGroup *shadeGroup() { return m_shades.data(); } + QGraphicsItemGroup *arrowGroup() { return m_arrow.data(); } + +public Q_SLOTS: + void handleVisibleChanged(bool visible); + void handleArrowVisibleChanged(bool visible); + void handleGridVisibleChanged(bool visible); + void handleLabelsVisibleChanged(bool visible); + void handleShadesVisibleChanged(bool visible); + void handleLabelsAngleChanged(int angle); + virtual void handleShadesBrushChanged(const QBrush &brush) = 0; + virtual void handleShadesPenChanged(const QPen &pen) = 0; + virtual void handleArrowPenChanged(const QPen &pen) = 0; + virtual void handleGridPenChanged(const QPen &pen) = 0; + void handleLabelsPenChanged(const QPen &pen); + void handleLabelsBrushChanged(const QBrush &brush); + void handleLabelsFontChanged(const QFont &font); + void handleTitlePenChanged(const QPen &pen); + void handleTitleBrushChanged(const QBrush &brush); + void handleTitleFontChanged(const QFont &font); + void handleTitleTextChanged(const QString &title); + void handleTitleVisibleChanged(bool visible); + void handleRangeChanged(qreal min, qreal max); + +Q_SIGNALS: + void clicked(); + +private: + void connectSlots(); + + QAbstractAxis *m_axis; + AxisAnimation *m_animation; + QVector<qreal> m_layout; + QStringList m_labelsList; + QRectF m_axisRect; + QScopedPointer<QGraphicsItemGroup> m_grid; + QScopedPointer<QGraphicsItemGroup> m_arrow; + QScopedPointer<QGraphicsItemGroup> m_shades; + QScopedPointer<QGraphicsItemGroup> m_labels; + QScopedPointer<QGraphicsSimpleTextItem> m_title; + bool m_intervalAxis; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif /* CHARTAXISELEMENT_H */ diff --git a/src/axis/datetimeaxis/chartdatetimeaxisx.cpp b/src/axis/datetimeaxis/chartdatetimeaxisx.cpp index 39fc3ba7..6ff32c5e 100644 --- a/src/axis/datetimeaxis/chartdatetimeaxisx.cpp +++ b/src/axis/datetimeaxis/chartdatetimeaxisx.cpp @@ -21,7 +21,7 @@ #include "chartdatetimeaxisx_p.h" #include "chartpresenter_p.h" #include "qdatetimeaxis.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QDateTime> #include <QFontMetrics> @@ -29,12 +29,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -ChartDateTimeAxisX::ChartDateTimeAxisX(QDateTimeAxis *axis, QGraphicsItem* item) +ChartDateTimeAxisX::ChartDateTimeAxisX(QDateTimeAxis *axis, QGraphicsItem *item) : HorizontalAxis(axis, item), m_axis(axis) { - QObject::connect(m_axis,SIGNAL(tickCountChanged(int)),this, SLOT(handleTickCountChanged(int))); - QObject::connect(m_axis,SIGNAL(formatChanged(QString)),this, SLOT(handleFormatChanged(QString))); + QObject::connect(m_axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(m_axis, SIGNAL(formatChanged(QString)), this, SLOT(handleFormatChanged(QString))); } ChartDateTimeAxisX::~ChartDateTimeAxisX() @@ -58,10 +58,10 @@ QVector<qreal> ChartDateTimeAxisX::calculateLayout() const void ChartDateTimeAxisX::updateGeometry() { - const QVector<qreal>& layout = ChartAxis::layout(); + const QVector<qreal>& layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; - setLabels(createDateTimeLabels(min(),max(), layout.size(),m_axis->format())); + setLabels(createDateTimeLabels(min(), max(), layout.size(), m_axis->format())); HorizontalAxis::updateGeometry(); } @@ -69,25 +69,27 @@ void ChartDateTimeAxisX::handleTickCountChanged(int tick) { Q_UNUSED(tick) QGraphicsLayoutItem::updateGeometry(); - if(presenter()) presenter()->layout()->invalidate(); + if (presenter()) + presenter()->layout()->invalidate(); } void ChartDateTimeAxisX::handleFormatChanged(const QString &format) { Q_UNUSED(format); QGraphicsLayoutItem::updateGeometry(); - if(presenter()) presenter()->layout()->invalidate(); + if (presenter()) + presenter()->layout()->invalidate(); } QSizeF ChartDateTimeAxisX::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = HorizontalAxis::sizeHint(which, constraint); - QStringList ticksList = createDateTimeLabels(min(),max(),m_axis->tickCount(),m_axis->format()); + QStringList ticksList = createDateTimeLabels(min(), max(), m_axis->tickCount(), m_axis->format()); // Width of horizontal axis sizeHint indicates the maximum distance labels can extend past // first and last ticks. Base width is irrelevant. qreal width = 0; @@ -97,7 +99,7 @@ QSizeF ChartDateTimeAxisX::sizeHint(Qt::SizeHint which, const QSizeF &constraint return sh; switch (which) { - case Qt::MinimumSize:{ + case Qt::MinimumSize: { QRectF boundingRect = labelBoundingRect(fn, "..."); width = boundingRect.width() / 2.0; height = boundingRect.height() + labelPadding(); diff --git a/src/axis/datetimeaxis/chartdatetimeaxisx_p.h b/src/axis/datetimeaxis/chartdatetimeaxisx_p.h index 841ae1ea..03fe98bb 100644 --- a/src/axis/datetimeaxis/chartdatetimeaxisx_p.h +++ b/src/axis/datetimeaxis/chartdatetimeaxisx_p.h @@ -35,7 +35,6 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QDateTimeAxis; -class ChartPresenter; class ChartDateTimeAxisX : public HorizontalAxis { diff --git a/src/axis/datetimeaxis/chartdatetimeaxisy.cpp b/src/axis/datetimeaxis/chartdatetimeaxisy.cpp index a534617f..a8fe58a2 100644 --- a/src/axis/datetimeaxis/chartdatetimeaxisy.cpp +++ b/src/axis/datetimeaxis/chartdatetimeaxisy.cpp @@ -21,7 +21,7 @@ #include "chartdatetimeaxisy_p.h" #include "chartpresenter_p.h" #include "qdatetimeaxis.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QFontMetrics> #include <QDateTime> @@ -29,12 +29,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -ChartDateTimeAxisY::ChartDateTimeAxisY(QDateTimeAxis *axis, QGraphicsItem* item) +ChartDateTimeAxisY::ChartDateTimeAxisY(QDateTimeAxis *axis, QGraphicsItem *item) : VerticalAxis(axis, item), m_axis(axis) { - QObject::connect(m_axis,SIGNAL(tickCountChanged(int)),this, SLOT(handleTickCountChanged(int))); - QObject::connect(m_axis,SIGNAL(formatChanged(QString)),this, SLOT(handleFormatChanged(QString))); + QObject::connect(m_axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(m_axis, SIGNAL(formatChanged(QString)), this, SLOT(handleFormatChanged(QString))); } ChartDateTimeAxisY::~ChartDateTimeAxisY() @@ -59,10 +59,10 @@ QVector<qreal> ChartDateTimeAxisY::calculateLayout() const void ChartDateTimeAxisY::updateGeometry() { - const QVector<qreal> &layout = ChartAxis::layout(); + const QVector<qreal> &layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; - setLabels(createDateTimeLabels(min(),max(), layout.size(),m_axis->format())); + setLabels(createDateTimeLabels(min(), max(), layout.size(), m_axis->format())); VerticalAxis::updateGeometry(); } @@ -70,25 +70,27 @@ void ChartDateTimeAxisY::handleTickCountChanged(int tick) { Q_UNUSED(tick) QGraphicsLayoutItem::updateGeometry(); - if(presenter()) presenter()->layout()->invalidate(); + if (presenter()) + presenter()->layout()->invalidate(); } void ChartDateTimeAxisY::handleFormatChanged(const QString &format) { Q_UNUSED(format); QGraphicsLayoutItem::updateGeometry(); - if(presenter()) presenter()->layout()->invalidate(); + if (presenter()) + presenter()->layout()->invalidate(); } QSizeF ChartDateTimeAxisY::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = VerticalAxis::sizeHint(which, constraint); - QStringList ticksList = createDateTimeLabels(min(),max(),m_axis->tickCount(),m_axis->format()); + QStringList ticksList = createDateTimeLabels(min(), max(), m_axis->tickCount(), m_axis->format()); qreal width = 0; // Height of vertical axis sizeHint indicates the maximum distance labels can extend past // first and last ticks. Base height is irrelevant. diff --git a/src/axis/datetimeaxis/chartdatetimeaxisy_p.h b/src/axis/datetimeaxis/chartdatetimeaxisy_p.h index 0bbe0d5a..be3adc54 100644 --- a/src/axis/datetimeaxis/chartdatetimeaxisy_p.h +++ b/src/axis/datetimeaxis/chartdatetimeaxisy_p.h @@ -35,7 +35,6 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QDateTimeAxis; -class ChartPresenter; class ChartDateTimeAxisY : public VerticalAxis { diff --git a/src/axis/datetimeaxis/polarchartdatetimeaxisangular.cpp b/src/axis/datetimeaxis/polarchartdatetimeaxisangular.cpp new file mode 100644 index 00000000..6b1d86af --- /dev/null +++ b/src/axis/datetimeaxis/polarchartdatetimeaxisangular.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartdatetimeaxisangular_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include "qdatetimeaxis.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartDateTimeAxisAngular::PolarChartDateTimeAxisAngular(QDateTimeAxis *axis, QGraphicsItem *item) + : PolarChartAxisAngular(axis, item) +{ + QObject::connect(axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(axis, SIGNAL(formatChanged(QString)), this, SLOT(handleFormatChanged(QString))); +} + +PolarChartDateTimeAxisAngular::~PolarChartDateTimeAxisAngular() +{ +} + +QVector<qreal> PolarChartDateTimeAxisAngular::calculateLayout() const +{ + int tickCount = static_cast<QDateTimeAxis *>(axis())->tickCount(); + Q_ASSERT(tickCount >= 2); + + QVector<qreal> points; + points.resize(tickCount); + + const qreal d = 360.0 / qreal(tickCount - 1); + + for (int i = 0; i < tickCount; ++i) { + qreal angularCoordinate = qreal(i) * d; + points[i] = angularCoordinate; + } + + return points; +} +void PolarChartDateTimeAxisAngular::createAxisLabels(const QVector<qreal> &layout) +{ + QStringList labelList = createDateTimeLabels(min(), max(), layout.size(), static_cast<QDateTimeAxis *>(axis())->format()); + setLabels(labelList); +} + +void PolarChartDateTimeAxisAngular::handleTickCountChanged(int tick) +{ + Q_UNUSED(tick); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +void PolarChartDateTimeAxisAngular::handleFormatChanged(const QString &format) +{ + Q_UNUSED(format); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +#include "moc_polarchartdatetimeaxisangular_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/datetimeaxis/polarchartdatetimeaxisangular_p.h b/src/axis/datetimeaxis/polarchartdatetimeaxisangular_p.h new file mode 100644 index 00000000..9a7ecffe --- /dev/null +++ b/src/axis/datetimeaxis/polarchartdatetimeaxisangular_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTDATETIMEAXISANGULAR_P_H +#define POLARCHARTDATETIMEAXISANGULAR_P_H + +#include "polarchartaxisangular_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QDateTimeAxis; + +class PolarChartDateTimeAxisAngular : public PolarChartAxisAngular +{ + Q_OBJECT +public: + PolarChartDateTimeAxisAngular(QDateTimeAxis *axis, QGraphicsItem *item); + ~PolarChartDateTimeAxisAngular(); + + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +private Q_SLOTS: + void handleTickCountChanged(int tick); + void handleFormatChanged(const QString &format); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTDATETIMEAXISANGULAR_P_H diff --git a/src/axis/datetimeaxis/polarchartdatetimeaxisradial.cpp b/src/axis/datetimeaxis/polarchartdatetimeaxisradial.cpp new file mode 100644 index 00000000..bcd04283 --- /dev/null +++ b/src/axis/datetimeaxis/polarchartdatetimeaxisradial.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartdatetimeaxisradial_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include "qdatetimeaxis.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartDateTimeAxisRadial::PolarChartDateTimeAxisRadial(QDateTimeAxis *axis, QGraphicsItem *item) + : PolarChartAxisRadial(axis, item) +{ + QObject::connect(axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(axis, SIGNAL(formatChanged(QString)), this, SLOT(handleFormatChanged(QString))); +} + +PolarChartDateTimeAxisRadial::~PolarChartDateTimeAxisRadial() +{ +} + +QVector<qreal> PolarChartDateTimeAxisRadial::calculateLayout() const +{ + int tickCount = static_cast<QDateTimeAxis *>(axis())->tickCount(); + Q_ASSERT(tickCount >= 2); + + QVector<qreal> points; + points.resize(tickCount); + + const qreal d = (axisGeometry().width() / 2) / qreal(tickCount - 1); + + for (int i = 0; i < tickCount; ++i) { + qreal radialCoordinate = qreal(i) * d; + points[i] = radialCoordinate; + } + + return points; +} +void PolarChartDateTimeAxisRadial::createAxisLabels(const QVector<qreal> &layout) +{ + setLabels(createDateTimeLabels(min(), max(), layout.size(), static_cast<QDateTimeAxis *>(axis())->format())); +} + +void PolarChartDateTimeAxisRadial::handleTickCountChanged(int tick) +{ + Q_UNUSED(tick); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +void PolarChartDateTimeAxisRadial::handleFormatChanged(const QString &format) +{ + Q_UNUSED(format); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +#include "moc_polarchartdatetimeaxisradial_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/datetimeaxis/polarchartdatetimeaxisradial_p.h b/src/axis/datetimeaxis/polarchartdatetimeaxisradial_p.h new file mode 100644 index 00000000..e10e9194 --- /dev/null +++ b/src/axis/datetimeaxis/polarchartdatetimeaxisradial_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTDATETIMEAXISRADIAL_P_H +#define POLARCHARTDATETIMEAXISRADIAL_P_H + +#include "polarchartaxisradial_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QDateTimeAxis; + +class PolarChartDateTimeAxisRadial : public PolarChartAxisRadial +{ + Q_OBJECT +public: + PolarChartDateTimeAxisRadial(QDateTimeAxis *axis, QGraphicsItem *item); + ~PolarChartDateTimeAxisRadial(); + + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +private Q_SLOTS: + void handleTickCountChanged(int tick); + void handleFormatChanged(const QString &format); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTDATETIMEAXISRADIAL_P_H diff --git a/src/axis/datetimeaxis/qdatetimeaxis.cpp b/src/axis/datetimeaxis/qdatetimeaxis.cpp index 3e00ee90..ed1b4a1c 100644 --- a/src/axis/datetimeaxis/qdatetimeaxis.cpp +++ b/src/axis/datetimeaxis/qdatetimeaxis.cpp @@ -22,6 +22,8 @@ #include "qdatetimeaxis_p.h" #include "chartdatetimeaxisx_p.h" #include "chartdatetimeaxisy_p.h" +#include "polarchartdatetimeaxisangular_p.h" +#include "polarchartdatetimeaxisradial_p.h" #include "abstractdomain_p.h" #include "qchart.h" #include <float.h> @@ -342,11 +344,20 @@ void QDateTimeAxisPrivate::setRange(const QVariant &min, const QVariant &max) void QDateTimeAxisPrivate::initializeGraphics(QGraphicsItem* parent) { Q_Q(QDateTimeAxis); - ChartAxis* axis(0); - if (orientation() == Qt::Vertical) - axis = new ChartDateTimeAxisY(q,parent); - if (orientation() == Qt::Horizontal) - axis = new ChartDateTimeAxisX(q,parent); + ChartAxisElement *axis(0); + if (m_chart->chartType() == QChart::ChartTypeCartesian) { + if (orientation() == Qt::Vertical) + axis = new ChartDateTimeAxisY(q,parent); + if (orientation() == Qt::Horizontal) + axis = new ChartDateTimeAxisX(q,parent); + } + + if (m_chart->chartType() == QChart::ChartTypePolar) { + if (orientation() == Qt::Vertical) + axis = new PolarChartDateTimeAxisRadial(q, parent); + if (orientation() == Qt::Horizontal) + axis = new PolarChartDateTimeAxisAngular(q, parent); + } m_item.reset(axis); QAbstractAxisPrivate::initializeGraphics(parent); diff --git a/src/axis/horizontalaxis.cpp b/src/axis/horizontalaxis.cpp index 9dcd7750..7068d597 100644 --- a/src/axis/horizontalaxis.cpp +++ b/src/axis/horizontalaxis.cpp @@ -19,15 +19,15 @@ ****************************************************************************/ #include "horizontalaxis_p.h" -#include "qabstractaxis.h" +#include "qabstractaxis_p.h" #include <QFontMetrics> #include <qmath.h> #include <QDebug> QTCOMMERCIALCHART_BEGIN_NAMESPACE -HorizontalAxis::HorizontalAxis(QAbstractAxis *axis, QGraphicsItem* item , bool intervalAxis) - : ChartAxis(axis, item, intervalAxis) +HorizontalAxis::HorizontalAxis(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) + : CartesianChartAxis(axis, item, intervalAxis) { } @@ -37,18 +37,18 @@ HorizontalAxis::~HorizontalAxis() void HorizontalAxis::updateGeometry() { - const QVector<qreal>& layout = ChartAxis::layout(); + const QVector<qreal> &layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; QStringList labelList = labels(); - QList<QGraphicsItem *> lines = lineItems(); + QList<QGraphicsItem *> lines = gridItems(); QList<QGraphicsItem *> labels = labelItems(); QList<QGraphicsItem *> shades = shadeItems(); - QList<QGraphicsItem *> axis = arrowItems(); - QGraphicsSimpleTextItem* title = titleItem(); + QList<QGraphicsItem *> arrow = arrowItems(); + QGraphicsSimpleTextItem *title = titleItem(); Q_ASSERT(labels.size() == labelList.size()); Q_ASSERT(layout.size() == labelList.size()); @@ -57,24 +57,24 @@ void HorizontalAxis::updateGeometry() const QRectF &gridRect = gridGeometry(); //arrow - QGraphicsLineItem *arrowItem = static_cast<QGraphicsLineItem *>(axis.at(0)); + QGraphicsLineItem *arrowItem = static_cast<QGraphicsLineItem *>(arrow.at(0)); - if (alignment() == Qt::AlignTop) + if (axis()->alignment() == Qt::AlignTop) arrowItem->setLine(gridRect.left(), axisRect.bottom(), gridRect.right(), axisRect.bottom()); - else if (alignment() == Qt::AlignBottom) + else if (axis()->alignment() == Qt::AlignBottom) arrowItem->setLine(gridRect.left(), axisRect.top(), gridRect.right(), axisRect.top()); qreal width = 0; - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); //title int titlePad = 0; QRectF titleBoundingRect; - if (!titleText().isEmpty() && titleItem()->isVisible()) { + QString titleText = axis()->titleText(); + if (!titleText.isEmpty() && titleItem()->isVisible()) { QFontMetrics fn(title->font()); int size(0); size = gridRect.width(); - QString titleText = this->titleText(); if (fn.boundingRect(titleText).width() > size) { QString string = titleText + "..."; @@ -89,9 +89,9 @@ void HorizontalAxis::updateGeometry() titleBoundingRect = title->boundingRect(); QPointF center = gridRect.center() - titleBoundingRect.center(); - if (alignment() == Qt::AlignTop) { + if (axis()->alignment() == Qt::AlignTop) { title->setPos(center.x(), axisRect.top() + titlePad); - } else if (alignment() == Qt::AlignBottom) { + } else if (axis()->alignment() == Qt::AlignBottom) { title->setPos(center.x(), axisRect.bottom() - titleBoundingRect.height() - titlePad); } } @@ -100,7 +100,7 @@ void HorizontalAxis::updateGeometry() //items QGraphicsLineItem *gridItem = static_cast<QGraphicsLineItem*>(lines.at(i)); - QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem*>(axis.at(i + 1)); + QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem*>(arrow.at(i + 1)); QGraphicsSimpleTextItem *labelItem = static_cast<QGraphicsSimpleTextItem *>(labels.at(i)); //grid line @@ -128,10 +128,10 @@ void HorizontalAxis::updateGeometry() int heightDiff = rect.height() - boundingRect.height(); //ticks and label position - if (alignment() == Qt::AlignTop) { + if (axis()->alignment() == Qt::AlignTop) { labelItem->setPos(layout[i] - center.x(), axisRect.bottom() - rect.height() + (heightDiff / 2) - labelPadding()); tickItem->setLine(layout[i], axisRect.bottom(), layout[i], axisRect.bottom() - labelPadding()); - } else if (alignment() == Qt::AlignBottom) { + } else if (axis()->alignment() == Qt::AlignBottom) { labelItem->setPos(layout[i] - center.x(), axisRect.top() - (heightDiff / 2) + labelPadding()); tickItem->setLine(layout[i], axisRect.top(), layout[i], axisRect.top() + labelPadding()); } @@ -178,7 +178,7 @@ void HorizontalAxis::updateGeometry() if (x < gridRect.left() || x > gridRect.right()) { gridItem->setVisible(false); tickItem->setVisible(false); - }else{ + } else { gridItem->setVisible(true); tickItem->setVisible(true); } @@ -200,10 +200,10 @@ void HorizontalAxis::updateGeometry() QSizeF HorizontalAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { Q_UNUSED(constraint); - QFontMetrics fn(titleFont()); + QFontMetrics fn(axis()->titleFont()); QSizeF sh(0,0); - if (titleText().isEmpty() || !titleItem()->isVisible()) + if (axis()->titleText().isEmpty() || !titleItem()->isVisible()) return sh; switch (which) { diff --git a/src/axis/horizontalaxis_p.h b/src/axis/horizontalaxis_p.h index 02aa2587..23116792 100644 --- a/src/axis/horizontalaxis_p.h +++ b/src/axis/horizontalaxis_p.h @@ -30,14 +30,14 @@ #ifndef HORIZONTALAXIS_P_H_ #define HORIZONTALAXIS_P_H_ -#include "chartaxis_p.h" +#include "cartesianchartaxis_p.h" QTCOMMERCIALCHART_BEGIN_NAMESPACE -class HorizontalAxis : public ChartAxis +class HorizontalAxis : public CartesianChartAxis { public: - HorizontalAxis(QAbstractAxis *axis, QGraphicsItem* item = 0, bool intervalAxis = false); + HorizontalAxis(QAbstractAxis *axis, QGraphicsItem *item = 0, bool intervalAxis = false); ~HorizontalAxis(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; protected: diff --git a/src/axis/linearrowitem_p.h b/src/axis/linearrowitem_p.h new file mode 100644 index 00000000..1adfd3bb --- /dev/null +++ b/src/axis/linearrowitem_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef LINEARROWITEM_P_H +#define LINEARROWITEM_P_H + +#include "chartaxiselement_p.h" +#include "qabstractaxis_p.h" +#include <QGraphicsLineItem> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class LineArrowItem: public QGraphicsLineItem +{ +public: + explicit LineArrowItem(ChartAxisElement *axis, QGraphicsItem *parent = 0) + : QGraphicsLineItem(parent), + m_axis(axis), + m_axisOrientation(axis->axis()->orientation()) + { + } + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) + { + Q_UNUSED(event) + m_axis->axisSelected(); + } + + QRectF boundingRect() const + { + return shape().boundingRect(); + } + + QPainterPath shape() const + { + QPainterPath path = QGraphicsLineItem::shape(); + QRectF rect = path.boundingRect(); + path.addRect(rect.adjusted(0, 0, m_axisOrientation != Qt::Horizontal ? 8 : 0, m_axisOrientation != Qt::Vertical ? 8 : 0)); + return path; + } + +private: + ChartAxisElement *m_axis; + Qt::Orientation m_axisOrientation; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif /* LINEARROWITEM_P_H */ diff --git a/src/axis/logvalueaxis/chartlogvalueaxisx.cpp b/src/axis/logvalueaxis/chartlogvalueaxisx.cpp index 3edc6d95..ac8f1f08 100644 --- a/src/axis/logvalueaxis/chartlogvalueaxisx.cpp +++ b/src/axis/logvalueaxis/chartlogvalueaxisx.cpp @@ -21,7 +21,7 @@ #include "chartlogvalueaxisx_p.h" #include "chartpresenter_p.h" #include "qlogvalueaxis.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QFontMetrics> #include <qmath.h> @@ -29,12 +29,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -ChartLogValueAxisX::ChartLogValueAxisX(QLogValueAxis *axis, QGraphicsItem* item) +ChartLogValueAxisX::ChartLogValueAxisX(QLogValueAxis *axis, QGraphicsItem *item) : HorizontalAxis(axis, item), m_axis(axis) { - QObject::connect(m_axis,SIGNAL(baseChanged(qreal)),this, SLOT(handleBaseChanged(qreal))); - QObject::connect(m_axis,SIGNAL(labelFormatChanged(QString)),this, SLOT(handleLabelFormatChanged(QString))); + QObject::connect(m_axis, SIGNAL(baseChanged(qreal)), this, SLOT(handleBaseChanged(qreal))); + QObject::connect(m_axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); } ChartLogValueAxisX::~ChartLogValueAxisX() @@ -55,14 +55,14 @@ QVector<qreal> ChartLogValueAxisX::calculateLayout() const const QRectF &gridRect = gridGeometry(); const qreal deltaX = gridRect.width() / qAbs(logMax - logMin); for (int i = 0; i < tickCount; ++i) - points[i] = (ceilEdge + i) * deltaX - leftEdge * deltaX + gridRect.left(); + points[i] = (ceilEdge + qreal(i)) * deltaX - leftEdge * deltaX + gridRect.left(); return points; } void ChartLogValueAxisX::updateGeometry() { - const QVector<qreal>& layout = ChartAxis::layout(); + const QVector<qreal>& layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; setLabels(createLogValueLabels(m_axis->min(), m_axis->max(), m_axis->base(), layout.size(), m_axis->labelFormat())); @@ -87,7 +87,7 @@ QSizeF ChartLogValueAxisX::sizeHint(Qt::SizeHint which, const QSizeF &constraint { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = HorizontalAxis::sizeHint(which, constraint); diff --git a/src/axis/logvalueaxis/chartlogvalueaxisx_p.h b/src/axis/logvalueaxis/chartlogvalueaxisx_p.h index c0b24a58..3017e260 100644 --- a/src/axis/logvalueaxis/chartlogvalueaxisx_p.h +++ b/src/axis/logvalueaxis/chartlogvalueaxisx_p.h @@ -35,20 +35,18 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QLogValueAxis; -class ChartPresenter; class ChartLogValueAxisX : public HorizontalAxis { Q_OBJECT public: - ChartLogValueAxisX(QLogValueAxis *axis, QGraphicsItem* item); + ChartLogValueAxisX(QLogValueAxis *axis, QGraphicsItem *item); ~ChartLogValueAxisX(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; protected: - void handleAxisUpdated(); QVector<qreal> calculateLayout() const; void updateGeometry(); diff --git a/src/axis/logvalueaxis/chartlogvalueaxisy.cpp b/src/axis/logvalueaxis/chartlogvalueaxisy.cpp index ec474e2f..42ae875c 100644 --- a/src/axis/logvalueaxis/chartlogvalueaxisy.cpp +++ b/src/axis/logvalueaxis/chartlogvalueaxisy.cpp @@ -21,7 +21,7 @@ #include "chartlogvalueaxisy_p.h" #include "chartpresenter_p.h" #include "qlogvalueaxis.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QFontMetrics> #include <qmath.h> @@ -29,12 +29,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -ChartLogValueAxisY::ChartLogValueAxisY(QLogValueAxis *axis, QGraphicsItem* item) +ChartLogValueAxisY::ChartLogValueAxisY(QLogValueAxis *axis, QGraphicsItem *item) : VerticalAxis(axis, item), m_axis(axis) { - QObject::connect(m_axis, SIGNAL(baseChanged(qreal)),this, SLOT(handleBaseChanged(qreal))); - QObject::connect(m_axis,SIGNAL(labelFormatChanged(QString)),this, SLOT(handleLabelFormatChanged(QString))); + QObject::connect(m_axis, SIGNAL(baseChanged(qreal)), this, SLOT(handleBaseChanged(qreal))); + QObject::connect(m_axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); } ChartLogValueAxisY::~ChartLogValueAxisY() @@ -54,7 +54,7 @@ QVector<qreal> ChartLogValueAxisY::calculateLayout() const const QRectF &gridRect = gridGeometry(); const qreal deltaY = gridRect.height() / qAbs(logMax - logMin); for (int i = 0; i < tickCount; ++i) - points[i] = (ceilEdge + i) * -deltaY - leftEdge * -deltaY + gridRect.bottom(); + points[i] = (ceilEdge + qreal(i)) * -deltaY - leftEdge * -deltaY + gridRect.bottom(); return points; } @@ -62,7 +62,7 @@ QVector<qreal> ChartLogValueAxisY::calculateLayout() const void ChartLogValueAxisY::updateGeometry() { - const QVector<qreal> &layout = ChartAxis::layout(); + const QVector<qreal> &layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; setLabels(createLogValueLabels(m_axis->min(), m_axis->max(), m_axis->base(), layout.size(), m_axis->labelFormat())); @@ -87,7 +87,7 @@ QSizeF ChartLogValueAxisY::sizeHint(Qt::SizeHint which, const QSizeF &constraint { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = VerticalAxis::sizeHint(which, constraint); diff --git a/src/axis/logvalueaxis/chartlogvalueaxisy_p.h b/src/axis/logvalueaxis/chartlogvalueaxisy_p.h index 8506cc29..39750976 100644 --- a/src/axis/logvalueaxis/chartlogvalueaxisy_p.h +++ b/src/axis/logvalueaxis/chartlogvalueaxisy_p.h @@ -35,20 +35,18 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QLogValueAxis; -class ChartPresenter; class ChartLogValueAxisY : public VerticalAxis { Q_OBJECT public: - ChartLogValueAxisY(QLogValueAxis *axis, QGraphicsItem* item); + ChartLogValueAxisY(QLogValueAxis *axis, QGraphicsItem *item); ~ChartLogValueAxisY(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; protected: - void handleAxisUpdated(); QVector<qreal> calculateLayout() const; void updateGeometry(); diff --git a/src/axis/logvalueaxis/polarchartlogvalueaxisangular.cpp b/src/axis/logvalueaxis/polarchartlogvalueaxisangular.cpp new file mode 100644 index 00000000..ddbd9fe6 --- /dev/null +++ b/src/axis/logvalueaxis/polarchartlogvalueaxisangular.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartlogvalueaxisangular_p.h" +#include "abstractchartlayout_p.h" +#include "chartpresenter_p.h" +#include "qlogvalueaxis.h" +#include <qmath.h> +#include <QDebug> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartLogValueAxisAngular::PolarChartLogValueAxisAngular(QLogValueAxis *axis, QGraphicsItem *item) + : PolarChartAxisAngular(axis, item) +{ + QObject::connect(axis, SIGNAL(baseChanged(qreal)), this, SLOT(handleBaseChanged(qreal))); + QObject::connect(axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); +} + +PolarChartLogValueAxisAngular::~PolarChartLogValueAxisAngular() +{ +} + +QVector<qreal> PolarChartLogValueAxisAngular::calculateLayout() const +{ + QLogValueAxis *logValueAxis = static_cast<QLogValueAxis *>(axis()); + const qreal logMax = log10(logValueAxis->max()) / log10(logValueAxis->base()); + const qreal logMin = log10(logValueAxis->min()) / log10(logValueAxis->base()); + const qreal startEdge = logMin < logMax ? logMin : logMax; + const qreal delta = 360.0 / qAbs(logMax - logMin); + const qreal initialSpan = (ceil(startEdge) - startEdge) * delta; + int tickCount = qAbs(ceil(logMax) - ceil(logMin)); + + QVector<qreal> points; + points.resize(tickCount); + + for (int i = 0; i < tickCount; ++i) { + qreal angularCoordinate = initialSpan + (delta * qreal(i)); + points[i] = angularCoordinate; + } + + return points; +} + +void PolarChartLogValueAxisAngular::createAxisLabels(const QVector<qreal> &layout) +{ + QLogValueAxis *logValueAxis = static_cast<QLogValueAxis *>(axis()); + setLabels(createLogValueLabels(logValueAxis->min(), + logValueAxis->max(), + logValueAxis->base(), + layout.size(), + logValueAxis->labelFormat())); +} + +void PolarChartLogValueAxisAngular::handleBaseChanged(qreal base) +{ + Q_UNUSED(base); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +void PolarChartLogValueAxisAngular::handleLabelFormatChanged(const QString &format) +{ + Q_UNUSED(format); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +#include "moc_polarchartlogvalueaxisangular_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/logvalueaxis/polarchartlogvalueaxisangular_p.h b/src/axis/logvalueaxis/polarchartlogvalueaxisangular_p.h new file mode 100644 index 00000000..231172aa --- /dev/null +++ b/src/axis/logvalueaxis/polarchartlogvalueaxisangular_p.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTLOGVALUEAXISANGULAR_P_H +#define POLARCHARTLOGVALUEAXISANGULAR_P_H + +#include "polarchartaxisangular_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QLogValueAxis; + +class PolarChartLogValueAxisAngular : public PolarChartAxisAngular +{ + Q_OBJECT +public: + PolarChartLogValueAxisAngular(QLogValueAxis *axis, QGraphicsItem *item); + ~PolarChartLogValueAxisAngular(); + +protected: + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +private Q_SLOTS: + void handleBaseChanged(qreal base); + void handleLabelFormatChanged(const QString &format); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTLOGVALUEAXISANGULAR_P_H diff --git a/src/axis/logvalueaxis/polarchartlogvalueaxisradial.cpp b/src/axis/logvalueaxis/polarchartlogvalueaxisradial.cpp new file mode 100644 index 00000000..d0719dbc --- /dev/null +++ b/src/axis/logvalueaxis/polarchartlogvalueaxisradial.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartlogvalueaxisradial_p.h" +#include "abstractchartlayout_p.h" +#include "chartpresenter_p.h" +#include "qlogvalueaxis.h" +#include <qmath.h> +#include <QDebug> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartLogValueAxisRadial::PolarChartLogValueAxisRadial(QLogValueAxis *axis, QGraphicsItem *item) + : PolarChartAxisRadial(axis, item) +{ + QObject::connect(axis, SIGNAL(baseChanged(qreal)), this, SLOT(handleBaseChanged(qreal))); + QObject::connect(axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); +} + +PolarChartLogValueAxisRadial::~PolarChartLogValueAxisRadial() +{ +} + +QVector<qreal> PolarChartLogValueAxisRadial::calculateLayout() const +{ + QLogValueAxis *logValueAxis = static_cast<QLogValueAxis *>(axis()); + const qreal logMax = log10(logValueAxis->max()) / log10(logValueAxis->base()); + const qreal logMin = log10(logValueAxis->min()) / log10(logValueAxis->base()); + const qreal innerEdge = logMin < logMax ? logMin : logMax; + const qreal outerEdge = logMin > logMax ? logMin : logMax; + const qreal delta = (axisGeometry().width() / 2.0) / qAbs(logMax - logMin); + const qreal initialSpan = (ceil(innerEdge) - innerEdge) * delta; + int tickCount = qAbs(ceil(logMax) - ceil(logMin)); + + // Extra tick if outer edge is exactly at the tick + if (outerEdge == ceil(outerEdge)) + tickCount++; + + QVector<qreal> points; + points.resize(tickCount); + + for (int i = 0; i < tickCount; ++i) { + qreal radialCoordinate = initialSpan + (delta * qreal(i)); + points[i] = radialCoordinate; + } + + return points; +} + +void PolarChartLogValueAxisRadial::createAxisLabels(const QVector<qreal> &layout) +{ + QLogValueAxis *logValueAxis = static_cast<QLogValueAxis *>(axis()); + setLabels(createLogValueLabels(logValueAxis->min(), + logValueAxis->max(), + logValueAxis->base(), + layout.size(), + logValueAxis->labelFormat())); +} + +void PolarChartLogValueAxisRadial::handleBaseChanged(qreal base) +{ + Q_UNUSED(base); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +void PolarChartLogValueAxisRadial::handleLabelFormatChanged(const QString &format) +{ + Q_UNUSED(format); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +#include "moc_polarchartlogvalueaxisradial_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/logvalueaxis/polarchartlogvalueaxisradial_p.h b/src/axis/logvalueaxis/polarchartlogvalueaxisradial_p.h new file mode 100644 index 00000000..bc7e8982 --- /dev/null +++ b/src/axis/logvalueaxis/polarchartlogvalueaxisradial_p.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTLOGVALUEAXISRADIAL_P_H +#define POLARCHARTLOGVALUEAXISRADIAL_P_H + +#include "polarchartaxisradial_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QLogValueAxis; + +class PolarChartLogValueAxisRadial : public PolarChartAxisRadial +{ + Q_OBJECT +public: + PolarChartLogValueAxisRadial(QLogValueAxis *axis, QGraphicsItem *item); + ~PolarChartLogValueAxisRadial(); + +protected: + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +private Q_SLOTS: + void handleBaseChanged(qreal base); + void handleLabelFormatChanged(const QString &format); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTLOGVALUEAXISRADIAL_P_H diff --git a/src/axis/logvalueaxis/qlogvalueaxis.cpp b/src/axis/logvalueaxis/qlogvalueaxis.cpp index 12170b8b..27896b51 100644 --- a/src/axis/logvalueaxis/qlogvalueaxis.cpp +++ b/src/axis/logvalueaxis/qlogvalueaxis.cpp @@ -22,6 +22,8 @@ #include "qlogvalueaxis_p.h" #include "chartlogvalueaxisx_p.h" #include "chartlogvalueaxisy_p.h" +#include "polarchartlogvalueaxisangular_p.h" +#include "polarchartlogvalueaxisradial_p.h" #include "abstractdomain_p.h" #include <float.h> #include <cmath> @@ -287,14 +289,24 @@ void QLogValueAxisPrivate::setRange(qreal min, qreal max) } } -void QLogValueAxisPrivate::initializeGraphics(QGraphicsItem* parent) +void QLogValueAxisPrivate::initializeGraphics(QGraphicsItem *parent) { Q_Q(QLogValueAxis); - ChartAxis* axis(0); - if (orientation() == Qt::Vertical) - axis = new ChartLogValueAxisY(q,parent); - if (orientation() == Qt::Horizontal) - axis = new ChartLogValueAxisX(q,parent); + ChartAxisElement *axis(0); + + if (m_chart->chartType() == QChart::ChartTypeCartesian) { + if (orientation() == Qt::Vertical) + axis = new ChartLogValueAxisY(q,parent); + if (orientation() == Qt::Horizontal) + axis = new ChartLogValueAxisX(q,parent); + } + + if (m_chart->chartType() == QChart::ChartTypePolar) { + if (orientation() == Qt::Vertical) + axis = new PolarChartLogValueAxisRadial(q, parent); + if (orientation() == Qt::Horizontal) + axis = new PolarChartLogValueAxisAngular(q, parent); + } m_item.reset(axis); QAbstractAxisPrivate::initializeGraphics(parent); @@ -304,7 +316,7 @@ void QLogValueAxisPrivate::initializeGraphics(QGraphicsItem* parent) void QLogValueAxisPrivate::initializeDomain(AbstractDomain *domain) { if (orientation() == Qt::Vertical) { - if(!qFuzzyCompare(m_max, m_min)) { + if (!qFuzzyCompare(m_max, m_min)) { domain->setRangeY(m_min, m_max); } else if ( domain->minY() > 0) { setRange(domain->minY(), domain->maxY()); @@ -315,7 +327,7 @@ void QLogValueAxisPrivate::initializeDomain(AbstractDomain *domain) } } if (orientation() == Qt::Horizontal) { - if(!qFuzzyCompare(m_max, m_min)) { + if (!qFuzzyCompare(m_max, m_min)) { domain->setRangeX(m_min, m_max); } else if (domain->minX() > 0){ setRange(domain->minX(), domain->maxX()); diff --git a/src/axis/polarchartaxis.cpp b/src/axis/polarchartaxis.cpp new file mode 100644 index 00000000..8bb80420 --- /dev/null +++ b/src/axis/polarchartaxis.cpp @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartaxis_p.h" +#include "qabstractaxis_p.h" +#include "chartpresenter_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartAxis::PolarChartAxis(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) + : ChartAxisElement(axis, item, intervalAxis) +{ +} + +PolarChartAxis::~PolarChartAxis() +{ + +} + +void PolarChartAxis::setGeometry(const QRectF &axis, const QRectF &grid) +{ + Q_UNUSED(grid); + setAxisGeometry(axis); + + if (isEmpty()) + return; + + QVector<qreal> layout = calculateLayout(); + updateLayout(layout); +} + +QRectF PolarChartAxis::gridGeometry() const +{ + return QRectF(); +} + +void PolarChartAxis::updateLayout(QVector<qreal> &layout) +{ + int diff = ChartAxisElement::layout().size() - layout.size(); + + if (animation()) { + switch (presenter()->state()) { + case ChartPresenter::ShowState: + animation()->setAnimationType(AxisAnimation::DefaultAnimation); + break; + } + // Update to "old" geometry before starting animation to avoid incorrectly sized + // axes lingering in wrong position compared to series plot before animation can kick in. + // Note that the position mismatch still exists even with this update, but it will be + // far less ugly. + updateGeometry(); + } + + if (diff > 0) + deleteItems(diff); + else if (diff < 0) + createItems(-diff); + + if (animation()) { + animation()->setValues(ChartAxisElement::layout(), layout); + presenter()->startAnimation(animation()); + } else { + setLayout(layout); + updateGeometry(); + } +} + +bool PolarChartAxis::isEmpty() +{ + return !axisGeometry().isValid() || qFuzzyIsNull(min() - max()); +} + +void PolarChartAxis::deleteItems(int count) +{ + QList<QGraphicsItem *> gridLines = gridItems(); + QList<QGraphicsItem *> labels = labelItems(); + QList<QGraphicsItem *> shades = shadeItems(); + QList<QGraphicsItem *> axis = arrowItems(); + + for (int i = 0; i < count; ++i) { + if (gridItems().size() == 1 || (((gridLines.size() + 1) % 2) && gridLines.size() > 0)) + delete(shades.takeLast()); + delete(gridLines.takeLast()); + delete(labels.takeLast()); + delete(axis.takeLast()); + } +} + +void PolarChartAxis::handleShadesBrushChanged(const QBrush &brush) +{ + foreach (QGraphicsItem *item, shadeItems()) + static_cast<QGraphicsPathItem *>(item)->setBrush(brush); +} + +void PolarChartAxis::handleShadesPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, shadeItems()) + static_cast<QGraphicsPathItem *>(item)->setPen(pen); +} + +#include "moc_polarchartaxis_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/polarchartaxis_p.h b/src/axis/polarchartaxis_p.h new file mode 100644 index 00000000..605b8fee --- /dev/null +++ b/src/axis/polarchartaxis_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTAXIS_P_H +#define POLARCHARTAXIS_P_H + +#include "chartaxiselement_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class PolarChartAxis : public ChartAxisElement +{ + Q_OBJECT + Q_INTERFACES(QGraphicsLayoutItem) +public: + PolarChartAxis(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis = false); + ~PolarChartAxis(); + + void setGeometry(const QRectF &axis, const QRectF &grid); + virtual qreal preferredAxisRadius(const QSizeF &maxSize) = 0; + int tickWidth() { return 3; } + +public: // from ChartAxisElement + QRectF gridGeometry() const; + bool isEmpty(); + +protected: + void updateLayout(QVector<qreal> &layout); + +protected: // virtual functions + virtual void createItems(int count) = 0; + virtual void createAxisLabels(const QVector<qreal> &layout) = 0; + +public Q_SLOTS: + virtual void handleShadesBrushChanged(const QBrush &brush); + virtual void handleShadesPenChanged(const QPen &pen); + +private: + void deleteItems(int count); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTAXIS_P_H diff --git a/src/axis/polarchartaxisangular.cpp b/src/axis/polarchartaxisangular.cpp new file mode 100644 index 00000000..8eed1f9d --- /dev/null +++ b/src/axis/polarchartaxisangular.cpp @@ -0,0 +1,428 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartaxisangular_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include "qabstractaxis.h" +#include "qabstractaxis_p.h" +#include <QFontMetrics> +#include <QDebug> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartAxisAngular::PolarChartAxisAngular(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) + : PolarChartAxis(axis, item, intervalAxis) +{ +} + +PolarChartAxisAngular::~PolarChartAxisAngular() +{ +} + +void PolarChartAxisAngular::updateGeometry() +{ + QGraphicsLayoutItem::updateGeometry(); + + const QVector<qreal> &layout = this->layout(); + if (layout.isEmpty()) + return; + + createAxisLabels(layout); + QStringList labelList = labels(); + QPointF center = axisGeometry().center(); + QList<QGraphicsItem *> arrowItemList = arrowItems(); + QList<QGraphicsItem *> gridItemList = gridItems(); + QList<QGraphicsItem *> labelItemList = labelItems(); + QList<QGraphicsItem *> shadeItemList = shadeItems(); + QGraphicsSimpleTextItem *title = titleItem(); + + QGraphicsEllipseItem *axisLine = static_cast<QGraphicsEllipseItem *>(arrowItemList.at(0)); + axisLine->setRect(axisGeometry()); + + qreal radius = axisGeometry().height() / 2.0; + + QFontMetrics fn(axis()->labelsFont()); + QRectF previousLabelRect; + QRectF firstLabelRect; + + qreal labelHeight = 0; + + bool firstShade = true; + bool nextTickVisible = false; + if (layout.size()) + nextTickVisible = !(layout.at(0) < 0.0 || layout.at(0) > 360.0); + + for (int i = 0; i < layout.size(); ++i) { + qreal angularCoordinate = layout.at(i); + + QGraphicsLineItem *gridLineItem = static_cast<QGraphicsLineItem *>(gridItemList.at(i)); + QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrowItemList.at(i + 1)); + QGraphicsSimpleTextItem *labelItem = static_cast<QGraphicsSimpleTextItem *>(labelItemList.at(i)); + QGraphicsPathItem *shadeItem = 0; + if (i == 0) + shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0)); + else if (i % 2) + shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at((i / 2) + 1)); + + // Ignore ticks outside valid range + bool currentTickVisible = nextTickVisible; + if ((i == layout.size() - 1) + || layout.at(i + 1) < 0.0 + || layout.at(i + 1) > 360.0) { + nextTickVisible = false; + } else { + nextTickVisible = true; + } + + qreal labelCoordinate = angularCoordinate; + qreal labelVisible = currentTickVisible; + if (intervalAxis()) { + qreal farEdge; + if (i == (layout.size() - 1)) + farEdge = 360.0; + else + farEdge = qMin(360.0, layout.at(i + 1)); + + // Adjust the labelCoordinate to show it if next tick is visible + if (nextTickVisible) + labelCoordinate = qMax(0.0, labelCoordinate); + + labelCoordinate = (labelCoordinate + farEdge) / 2.0; + // Don't display label once the category gets too small near the axis + if (labelCoordinate < 5.0 || labelCoordinate > 355.0) + labelVisible = false; + else + labelVisible = true; + } + + // Need this also in label calculations, so determine it first + QLineF tickLine(QLineF::fromPolar(radius - tickWidth(), 90.0 - angularCoordinate).p2(), + QLineF::fromPolar(radius + tickWidth(), 90.0 - angularCoordinate).p2()); + tickLine.translate(center); + + // Angular axis label + if (axis()->labelsVisible() && labelVisible) { + labelItem->setText(labelList.at(i)); + const QRectF &rect = labelItem->boundingRect(); + QPointF labelCenter = rect.center(); + labelItem->setTransformOriginPoint(labelCenter.x(), labelCenter.y()); + QRectF boundingRect = labelBoundingRect(fn, labelList.at(i)); + boundingRect.moveCenter(labelCenter); + QPointF positionDiff(rect.topLeft() - boundingRect.topLeft()); + + QPointF labelPoint; + if (intervalAxis()) { + QLineF labelLine = QLineF::fromPolar(radius + tickWidth(), 90.0 - labelCoordinate); + labelLine.translate(center); + labelPoint = labelLine.p2(); + } else { + labelPoint = tickLine.p2(); + } + + QRectF labelRect = moveLabelToPosition(labelCoordinate, labelPoint, boundingRect); + labelItem->setPos(labelRect.topLeft() + positionDiff); + + // Store height for title calculations + qreal labelClearance = axisGeometry().top() - labelRect.top(); + labelHeight = qMax(labelHeight, labelClearance); + + // Label overlap detection + if (i && (previousLabelRect.intersects(labelRect) || firstLabelRect.intersects(labelRect))) { + labelVisible = false; + } else { + // Store labelRect for future comparison. Some area is deducted to make things look + // little nicer, as usually intersection happens at label corner with angular labels. + labelRect.adjust(-2.0, -4.0, -2.0, -4.0); + if (firstLabelRect.isEmpty()) + firstLabelRect = labelRect; + + previousLabelRect = labelRect; + labelVisible = true; + } + } + + labelItem->setVisible(labelVisible); + if (!currentTickVisible) { + gridLineItem->setVisible(false); + tickItem->setVisible(false); + if (shadeItem) + shadeItem->setVisible(false); + continue; + } + + // Angular grid line + QLineF gridLine = QLineF::fromPolar(radius, 90.0 - angularCoordinate); + gridLine.translate(center); + gridLineItem->setLine(gridLine); + gridLineItem->setVisible(true); + + // Tick + tickItem->setLine(tickLine); + tickItem->setVisible(true); + + // Shades + if (i % 2 || (i == 0 && !nextTickVisible)) { + QPainterPath path; + path.moveTo(center); + if (i == 0) { + // If first tick is also the last, we need to custom fill the first partial arc + // or it won't get filled. + path.arcTo(axisGeometry(), 90.0 - layout.at(0), layout.at(0)); + path.closeSubpath(); + } else { + qreal nextCoordinate; + if (!nextTickVisible) // Last visible tick + nextCoordinate = 360.0; + else + nextCoordinate = layout.at(i + 1); + qreal arcSpan = angularCoordinate - nextCoordinate; + path.arcTo(axisGeometry(), 90.0 - angularCoordinate, arcSpan); + path.closeSubpath(); + + // Add additional arc for first shade item if there is a partial arc to be filled + if (firstShade) { + QGraphicsPathItem *specialShadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0)); + if (layout.at(i - 1) > 0.0) { + QPainterPath specialPath; + specialPath.moveTo(center); + specialPath.arcTo(axisGeometry(), 90.0 - layout.at(i - 1), layout.at(i - 1)); + specialPath.closeSubpath(); + specialShadeItem->setPath(specialPath); + specialShadeItem->setVisible(true); + } else { + specialShadeItem->setVisible(false); + } + } + } + shadeItem->setPath(path); + shadeItem->setVisible(true); + firstShade = false; + } + } + + // Title, centered above the chart + QString titleText = axis()->titleText(); + if (!titleText.isEmpty() && axis()->isTitleVisible()) { + int size(0); + size = axisGeometry().width(); + + QFontMetrics titleMetrics(axis()->titleFont()); + if (titleMetrics.boundingRect(titleText).width() > size) { + QString string = titleText + "..."; + while (titleMetrics.boundingRect(string).width() > size && string.length() > 3) + string.remove(string.length() - 4, 1); + title->setText(string); + } else { + title->setText(titleText); + } + + QRectF titleBoundingRect; + titleBoundingRect = title->boundingRect(); + QPointF titleCenter = center - titleBoundingRect.center(); + title->setPos(titleCenter.x(), axisGeometry().top() - qreal(titlePadding()) * 2.0 - titleBoundingRect.height() - labelHeight); + } +} + +Qt::Orientation PolarChartAxisAngular::orientation() const +{ + return Qt::Horizontal; +} + +void PolarChartAxisAngular::createItems(int count) +{ + if (arrowItems().count() == 0) { + // angular axis line + // TODO: need class similar to LineArrowItem for click handling? + QGraphicsEllipseItem *arrow = new QGraphicsEllipseItem(presenter()->rootItem()); + arrow->setPen(axis()->linePen()); + arrowGroup()->addToGroup(arrow); + } + + for (int i = 0; i < count; ++i) { + QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem()); + QGraphicsLineItem *grid = new QGraphicsLineItem(presenter()->rootItem()); + QGraphicsSimpleTextItem *label = new QGraphicsSimpleTextItem(presenter()->rootItem()); + QGraphicsSimpleTextItem *title = titleItem(); + arrow->setPen(axis()->linePen()); + grid->setPen(axis()->gridLinePen()); + label->setFont(axis()->labelsFont()); + label->setPen(axis()->labelsPen()); + label->setBrush(axis()->labelsBrush()); + label->setRotation(axis()->labelsAngle()); + title->setFont(axis()->titleFont()); + title->setPen(axis()->titlePen()); + title->setBrush(axis()->titleBrush()); + title->setText(axis()->titleText()); + arrowGroup()->addToGroup(arrow); + gridGroup()->addToGroup(grid); + labelGroup()->addToGroup(label); + if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) { + QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem()); + shade->setPen(axis()->shadesPen()); + shade->setBrush(axis()->shadesBrush()); + shadeGroup()->addToGroup(shade); + } + } +} + +void PolarChartAxisAngular::handleArrowPenChanged(const QPen &pen) +{ + bool first = true; + foreach (QGraphicsItem *item, arrowItems()) { + if (first) { + first = false; + // First arrow item is the outer circle of axis + static_cast<QGraphicsEllipseItem *>(item)->setPen(pen); + } else { + static_cast<QGraphicsLineItem *>(item)->setPen(pen); + } + } +} + +void PolarChartAxisAngular::handleGridPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, gridItems()) + static_cast<QGraphicsLineItem *>(item)->setPen(pen); +} + +QSizeF PolarChartAxisAngular::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_UNUSED(which); + Q_UNUSED(constraint); + return QSizeF(-1, -1); +} + +qreal PolarChartAxisAngular::preferredAxisRadius(const QSizeF &maxSize) +{ + qreal radius = maxSize.height() / 2.0; + if (maxSize.width() < maxSize.height()) + radius = maxSize.width() / 2.0; + + int labelHeight = 0; + + if (axis()->labelsVisible()) { + QVector<qreal> layout = calculateLayout(); + if (layout.isEmpty()) + return radius; + + createAxisLabels(layout); + QStringList labelList = labels(); + QFont font = axis()->labelsFont(); + + QRectF maxRect; + maxRect.setSize(maxSize); + maxRect.moveCenter(QPointF(0.0, 0.0)); + + // This is a horrible way to find out the maximum radius for angular axis and its labels. + // It just increments the radius down until everyhing fits the constraint size. + // Proper way would be to actually calculate it but this seems to work reasonably fast as it is. + QFontMetrics fm(font); + bool nextTickVisible = false; + for (int i = 0; i < layout.size(); ) { + if ((i == layout.size() - 1) + || layout.at(i + 1) < 0.0 + || layout.at(i + 1) > 360.0) { + nextTickVisible = false; + } else { + nextTickVisible = true; + } + + qreal labelCoordinate = layout.at(i); + qreal labelVisible; + + if (intervalAxis()) { + qreal farEdge; + if (i == (layout.size() - 1)) + farEdge = 360.0; + else + farEdge = qMin(360.0, layout.at(i + 1)); + + // Adjust the labelCoordinate to show it if next tick is visible + if (nextTickVisible) + labelCoordinate = qMax(0.0, labelCoordinate); + + labelCoordinate = (labelCoordinate + farEdge) / 2.0; + } + + if (labelCoordinate < 0.0 || labelCoordinate > 360.0) + labelVisible = false; + else + labelVisible = true; + + if (!labelVisible) { + i++; + continue; + } + + QRectF boundingRect = labelBoundingRect(fm, labelList.at(i)); + labelHeight = boundingRect.height(); + QPointF labelPoint = QLineF::fromPolar(radius + tickWidth(), 90.0 - labelCoordinate).p2(); + + boundingRect = moveLabelToPosition(labelCoordinate, labelPoint, boundingRect); + if (boundingRect.isEmpty() || maxRect.intersected(boundingRect) == boundingRect) { + i++; + } else { + radius -= 1.0; + if (radius < 1.0) // safeguard + return 1.0; + } + } + } + + if (!axis()->titleText().isEmpty() && axis()->isTitleVisible()) { + QFontMetrics titleMetrics(axis()->titleFont()); + int titleHeight = titleMetrics.boundingRect(axis()->titleText()).height(); + radius -= titlePadding() + (titleHeight / 2); + if (radius < 1.0) // safeguard + return 1.0; + } + + return radius; +} + +QRectF PolarChartAxisAngular::moveLabelToPosition(qreal angularCoordinate, QPointF labelPoint, QRectF labelRect) const +{ + // TODO use fuzzy compare for "==" cases? + // TODO Adjust the rect position near 0, 90, 180, and 270 angles for smoother animation? + if (angularCoordinate == 0.0) + labelRect.moveCenter(labelPoint + QPointF(0, -labelRect.height() / 2.0)); + else if (angularCoordinate < 90.0) + labelRect.moveBottomLeft(labelPoint); + else if (angularCoordinate == 90.0) + labelRect.moveCenter(labelPoint + QPointF(labelRect.width() / 2.0 + 2.0, 0)); // +2 so that it does not hit the radial axis + else if (angularCoordinate < 180.0) + labelRect.moveTopLeft(labelPoint); + else if (angularCoordinate == 180.0) + labelRect.moveCenter(labelPoint + QPointF(0, labelRect.height() / 2.0)); + else if (angularCoordinate < 270.0) + labelRect.moveTopRight(labelPoint); + else if (angularCoordinate == 270.0) + labelRect.moveCenter(labelPoint + QPointF(-labelRect.width() / 2.0 - 2.0, 0)); // -2 so that it does not hit the radial axis + else if (angularCoordinate < 360.0) + labelRect.moveBottomRight(labelPoint); + else + labelRect.moveCenter(labelPoint + QPointF(0, -labelRect.height() / 2.0)); + return labelRect; +} + +#include "moc_polarchartaxisangular_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/polarchartaxisangular_p.h b/src/axis/polarchartaxisangular_p.h new file mode 100644 index 00000000..7e7b292d --- /dev/null +++ b/src/axis/polarchartaxisangular_p.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTAXISANGULAR_P_H +#define POLARCHARTAXISANGULAR_P_H + +#include "polarchartaxis_p.h" +#include "qvalueaxis.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class PolarChartAxisAngular : public PolarChartAxis +{ + Q_OBJECT +public: + PolarChartAxisAngular(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis = false); + ~PolarChartAxisAngular(); + + Qt::Orientation orientation() const; + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + + virtual void updateGeometry(); + virtual void createItems(int count); + + qreal preferredAxisRadius(const QSizeF &maxSize); + +public Q_SLOTS: + virtual void handleArrowPenChanged(const QPen &pen); + virtual void handleGridPenChanged(const QPen &pen); + +private: + QRectF moveLabelToPosition(qreal angularCoordinate, QPointF labelPoint, QRectF labelRect) const; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTAXISANGULAR_P_H diff --git a/src/axis/polarchartaxisradial.cpp b/src/axis/polarchartaxisradial.cpp new file mode 100644 index 00000000..55923985 --- /dev/null +++ b/src/axis/polarchartaxisradial.cpp @@ -0,0 +1,301 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartaxisradial_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" +#include "qabstractaxis_p.h" +#include "linearrowitem_p.h" +#include <QFontMetrics> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartAxisRadial::PolarChartAxisRadial(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) + : PolarChartAxis(axis, item, intervalAxis) +{ +} + +PolarChartAxisRadial::~PolarChartAxisRadial() +{ +} + +void PolarChartAxisRadial::updateGeometry() +{ + const QVector<qreal> &layout = this->layout(); + if (layout.isEmpty()) + return; + + createAxisLabels(layout); + QStringList labelList = labels(); + QPointF center = axisGeometry().center(); + QList<QGraphicsItem *> arrowItemList = arrowItems(); + QList<QGraphicsItem *> gridItemList = gridItems(); + QList<QGraphicsItem *> labelItemList = labelItems(); + QList<QGraphicsItem *> shadeItemList = shadeItems(); + QGraphicsSimpleTextItem* title = titleItem(); + qreal radius = axisGeometry().height() / 2.0; + + QLineF line(center, center + QPointF(0, -radius)); + QGraphicsLineItem *axisLine = static_cast<QGraphicsLineItem *>(arrowItemList.at(0)); + axisLine->setLine(line); + + QFontMetrics fn(axis()->labelsFont()); + QRectF previousLabelRect; + bool firstShade = true; + bool nextTickVisible = false; + if (layout.size()) + nextTickVisible = !(layout.at(0) < 0.0 || layout.at(0) > radius); + + for (int i = 0; i < layout.size(); ++i) { + qreal radialCoordinate = layout.at(i); + + QGraphicsEllipseItem *gridItem = static_cast<QGraphicsEllipseItem *>(gridItemList.at(i)); + QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrowItemList.at(i + 1)); + QGraphicsSimpleTextItem *labelItem = static_cast<QGraphicsSimpleTextItem *>(labelItemList.at(i)); + QGraphicsPathItem *shadeItem = 0; + if (i == 0) + shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0)); + else if (i % 2) + shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at((i / 2) + 1)); + + // Ignore ticks outside valid range + bool currentTickVisible = nextTickVisible; + if ((i == layout.size() - 1) + || layout.at(i + 1) < 0.0 + || layout.at(i + 1) > radius) { + nextTickVisible = false; + } else { + nextTickVisible = true; + } + + qreal labelCoordinate = radialCoordinate; + qreal labelVisible = currentTickVisible; + qreal labelPad = labelPadding() / 2.0; + if (intervalAxis()) { + qreal farEdge; + if (i == (layout.size() - 1)) + farEdge = radius; + else + farEdge = qMin(radius, layout.at(i + 1)); + + // Adjust the labelCoordinate to show it if next tick is visible + if (nextTickVisible) + labelCoordinate = qMax(0.0, labelCoordinate); + + labelCoordinate = (labelCoordinate + farEdge) / 2.0; + if (labelCoordinate > 0.0 && labelCoordinate < radius) + labelVisible = true; + else + labelVisible = false; + } + + // Radial axis label + if (axis()->labelsVisible() && labelVisible) { + labelItem->setText(labelList.at(i)); + QRectF labelRect = labelItem->boundingRect(); + QPointF labelCenter = labelRect.center(); + labelItem->setTransformOriginPoint(labelCenter.x(), labelCenter.y()); + QRectF boundingRect = labelBoundingRect(fn, labelList.at(i)); + boundingRect.moveCenter(labelCenter); + QPointF positionDiff(labelRect.topLeft() - boundingRect.topLeft()); + QPointF labelPoint = center; + if (intervalAxis()) + labelPoint += QPointF(labelPad, -labelCoordinate - (boundingRect.height() / 2.0)); + else + labelPoint += QPointF(labelPad, labelPad - labelCoordinate); + labelRect.moveTopLeft(labelPoint); + labelItem->setPos(labelRect.topLeft() + positionDiff); + + // Label overlap detection + labelRect.setSize(boundingRect.size()); + if ((i && previousLabelRect.intersects(labelRect)) + || !axisGeometry().contains(labelRect)) { + labelVisible = false; + } else { + previousLabelRect = labelRect; + labelVisible = true; + } + } + + labelItem->setVisible(labelVisible); + if (!currentTickVisible) { + gridItem->setVisible(false); + tickItem->setVisible(false); + if (shadeItem) + shadeItem->setVisible(false); + continue; + } + + // Radial grid line + QRectF gridRect; + gridRect.setWidth(radialCoordinate * 2.0); + gridRect.setHeight(radialCoordinate * 2.0); + gridRect.moveCenter(center); + + gridItem->setRect(gridRect); + gridItem->setVisible(true); + + // Tick + QLineF tickLine(-tickWidth(), 0.0, tickWidth(), 0.0); + tickLine.translate(center.rx(), gridRect.top()); + tickItem->setLine(tickLine); + tickItem->setVisible(true); + + // Shades + if (i % 2 || (i == 0 && !nextTickVisible)) { + QPainterPath path; + if (i == 0) { + // If first tick is also the last, we need to custom fill the inner circle + // or it won't get filled. + QRectF innerCircle(0.0, 0.0, layout.at(0) * 2.0, layout.at(0) * 2.0); + innerCircle.moveCenter(center); + path.addEllipse(innerCircle); + } else { + QRectF otherGridRect; + if (!nextTickVisible) { // Last visible tick + otherGridRect = axisGeometry(); + } else { + qreal otherGridRectDimension = layout.at(i + 1) * 2.0; + otherGridRect.setWidth(otherGridRectDimension); + otherGridRect.setHeight(otherGridRectDimension); + otherGridRect.moveCenter(center); + } + path.addEllipse(gridRect); + path.addEllipse(otherGridRect); + + // Add additional shading in first visible shade item if there is a partial tick + // to be filled at the center (log & category axes) + if (firstShade) { + QGraphicsPathItem *specialShadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0)); + if (layout.at(i - 1) > 0.0) { + QRectF innerCircle(0.0, 0.0, layout.at(i - 1) * 2.0, layout.at(i - 1) * 2.0); + innerCircle.moveCenter(center); + QPainterPath specialPath; + specialPath.addEllipse(innerCircle); + specialShadeItem->setPath(specialPath); + specialShadeItem->setVisible(true); + } else { + specialShadeItem->setVisible(false); + } + } + } + shadeItem->setPath(path); + shadeItem->setVisible(true); + firstShade = false; + } + } + + // Title, along the 0 axis + QString titleText = axis()->titleText(); + if (!titleText.isEmpty() && axis()->isTitleVisible()) { + QFontMetrics titleMetrics(axis()->titleFont()); + if (titleMetrics.boundingRect(titleText).width() > radius) { + QString string = titleText + "..."; + while (titleMetrics.boundingRect(string).width() > radius && string.length() > 3) + string.remove(string.length() - 4, 1); + title->setText(string); + } else { + title->setText(titleText); + } + + QRectF titleBoundingRect; + titleBoundingRect = title->boundingRect(); + QPointF titleCenter = titleBoundingRect.center(); + QPointF arrowCenter = axisLine->boundingRect().center(); + QPointF titleCenterDiff = arrowCenter - titleCenter; + title->setPos(titleCenterDiff.x() - qreal(titlePadding()) - (titleBoundingRect.height() / 2.0), titleCenterDiff.y()); + title->setTransformOriginPoint(titleCenter); + title->setRotation(270.0); + } + + QGraphicsLayoutItem::updateGeometry(); +} + +Qt::Orientation PolarChartAxisRadial::orientation() const +{ + return Qt::Vertical; +} + +void PolarChartAxisRadial::createItems(int count) +{ + if (arrowItems().count() == 0) { + // radial axis center line + QGraphicsLineItem *arrow = new LineArrowItem(this, presenter()->rootItem()); + arrow->setPen(axis()->linePen()); + arrowGroup()->addToGroup(arrow); + } + + for (int i = 0; i < count; ++i) { + QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem()); + QGraphicsEllipseItem *grid = new QGraphicsEllipseItem(presenter()->rootItem()); + QGraphicsSimpleTextItem *label = new QGraphicsSimpleTextItem(presenter()->rootItem()); + QGraphicsSimpleTextItem *title = titleItem(); + arrow->setPen(axis()->linePen()); + grid->setPen(axis()->gridLinePen()); + label->setFont(axis()->labelsFont()); + label->setPen(axis()->labelsPen()); + label->setBrush(axis()->labelsBrush()); + label->setRotation(axis()->labelsAngle()); + title->setFont(axis()->titleFont()); + title->setPen(axis()->titlePen()); + title->setBrush(axis()->titleBrush()); + title->setText(axis()->titleText()); + arrowGroup()->addToGroup(arrow); + gridGroup()->addToGroup(grid); + labelGroup()->addToGroup(label); + if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) { + QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem()); + shade->setPen(axis()->shadesPen()); + shade->setBrush(axis()->shadesBrush()); + shadeGroup()->addToGroup(shade); + } + } +} + +void PolarChartAxisRadial::handleArrowPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, arrowItems()) + static_cast<QGraphicsLineItem *>(item)->setPen(pen); +} + +void PolarChartAxisRadial::handleGridPenChanged(const QPen &pen) +{ + foreach (QGraphicsItem *item, gridItems()) + static_cast<QGraphicsEllipseItem *>(item)->setPen(pen); +} + +QSizeF PolarChartAxisRadial::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_UNUSED(which); + Q_UNUSED(constraint); + return QSizeF(-1.0, -1.0); +} + +qreal PolarChartAxisRadial::preferredAxisRadius(const QSizeF &maxSize) +{ + qreal radius = maxSize.height() / 2.0; + if (maxSize.width() < maxSize.height()) + radius = maxSize.width() / 2.0; + return radius; +} + +#include "moc_polarchartaxisradial_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/polarchartaxisradial_p.h b/src/axis/polarchartaxisradial_p.h new file mode 100644 index 00000000..9874f28a --- /dev/null +++ b/src/axis/polarchartaxisradial_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTAXISRADIAL_P_H +#define POLARCHARTAXISRADIAL_P_H + +#include "polarchartaxis_p.h" +#include "qvalueaxis.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class PolarChartAxisRadial : public PolarChartAxis +{ + Q_OBJECT +public: + PolarChartAxisRadial(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis = false); + ~PolarChartAxisRadial(); + + Qt::Orientation orientation() const; + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + + virtual void updateGeometry(); + virtual void createItems(int count); + + qreal preferredAxisRadius(const QSizeF &maxSize); + +public Q_SLOTS: + virtual void handleArrowPenChanged(const QPen &pen); + virtual void handleGridPenChanged(const QPen &pen); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTAXISRADIAL_P_H diff --git a/src/axis/qabstractaxis.cpp b/src/axis/qabstractaxis.cpp index 60e071d6..77c89b53 100644 --- a/src/axis/qabstractaxis.cpp +++ b/src/axis/qabstractaxis.cpp @@ -254,11 +254,11 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE /*! \property QAbstractAxis::alignment - The alignment of the axis. Either Qt::AlignLeft or Qt::AlignBottom. + The alignment of the axis. Can be Qt::AlignLeft, Qt::AlignRight, Qt::AlignBottom, or Qt::AlignTop. */ /*! \qmlproperty alignment AbstractAxis::alignment - The alignment of the axis. Either Qt.AlignLeft or Qt.AlignBottom. + The alignment of the axis. Can be Qt.AlignLeft, Qt.AlignRight, Qt.AlignBottom, or Qt.AlignTop. */ /*! @@ -990,11 +990,11 @@ void QAbstractAxisPrivate::initializeGraphics(QGraphicsItem* parent) void QAbstractAxisPrivate::initializeAnimations(QChart::AnimationOptions options) { - ChartAxis* axis = m_item.data(); + ChartAxisElement *axis = m_item.data(); Q_ASSERT(axis); - if(options.testFlag(QChart::GridAxisAnimations)) { + if (options.testFlag(QChart::GridAxisAnimations)) { axis->setAnimation(new AxisAnimation(axis)); - }else{ + } else { axis->setAnimation(0); } } diff --git a/src/axis/qabstractaxis.h b/src/axis/qabstractaxis.h index 5ebd7974..e29325de 100644 --- a/src/axis/qabstractaxis.h +++ b/src/axis/qabstractaxis.h @@ -155,36 +155,37 @@ public: Q_SIGNALS: void visibleChanged(bool visible); - void linePenChanged(const QPen& pen); + void linePenChanged(const QPen &pen); void lineVisibleChanged(bool visible); void labelsVisibleChanged(bool visible); - void labelsPenChanged(const QPen& pen); - void labelsBrushChanged(const QBrush& brush); - void labelsFontChanged(const QFont& pen); + void labelsPenChanged(const QPen &pen); + void labelsBrushChanged(const QBrush &brush); + void labelsFontChanged(const QFont &pen); void labelsAngleChanged(int angle); - void gridLinePenChanged(const QPen& pen); + void gridLinePenChanged(const QPen &pen); void gridVisibleChanged(bool visible); void colorChanged(QColor color); void labelsColorChanged(QColor color); - void titleTextChanged(const QString& title); - void titlePenChanged(const QPen& pen); - void titleBrushChanged(const QBrush& brush); + void titleTextChanged(const QString &title); + void titlePenChanged(const QPen &pen); + void titleBrushChanged(const QBrush &brush); void titleVisibleChanged(bool visible); - void titleFontChanged(const QFont& font); + void titleFontChanged(const QFont &font); void shadesVisibleChanged(bool visible); void shadesColorChanged(QColor color); void shadesBorderColorChanged(QColor color); - void shadesPenChanged(const QPen& pen); - void shadesBrushChanged(const QBrush& brush); + void shadesPenChanged(const QPen &pen); + void shadesBrushChanged(const QBrush &brush); protected: QScopedPointer<QAbstractAxisPrivate> d_ptr; Q_DISABLE_COPY(QAbstractAxis) friend class ChartDataSet; - friend class ChartAxis; friend class ChartPresenter; friend class ChartThemeManager; friend class AbstractDomain; + friend class ChartAxisElement; + friend class XYChart; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/qabstractaxis_p.h b/src/axis/qabstractaxis_p.h index a284d46f..9ab993de 100644 --- a/src/axis/qabstractaxis_p.h +++ b/src/axis/qabstractaxis_p.h @@ -31,7 +31,7 @@ #define QABSTRACTAXIS_P_H #include "qabstractaxis.h" -#include "chartaxis_p.h" +#include "chartaxiselement_p.h" #include "qchart.h" #include <QDebug> @@ -59,7 +59,7 @@ public: void setAlignment( Qt::Alignment alignment); virtual void initializeDomain(AbstractDomain *domain) = 0; - virtual void initializeGraphics(QGraphicsItem* parent) = 0; + virtual void initializeGraphics(QGraphicsItem *parent) = 0; virtual void initializeTheme(ChartTheme* theme, bool forced = false); virtual void initializeAnimations(QChart::AnimationOptions options); @@ -73,7 +73,7 @@ public: virtual qreal min() = 0; virtual qreal max() = 0; - ChartAxis* axisItem() { return m_item.data(); } + ChartAxisElement *axisItem() { return m_item.data(); } public Q_SLOTS: void handleRangeChanged(qreal min, qreal max); @@ -84,7 +84,7 @@ Q_SIGNALS: protected: QAbstractAxis *q_ptr; QChart *m_chart; - QScopedPointer<ChartAxis> m_item; + QScopedPointer<ChartAxisElement> m_item; private: QList<QAbstractSeries*> m_series; diff --git a/src/axis/valueaxis/chartvalueaxisx.cpp b/src/axis/valueaxis/chartvalueaxisx.cpp index a084ac32..da6055f1 100644 --- a/src/axis/valueaxis/chartvalueaxisx.cpp +++ b/src/axis/valueaxis/chartvalueaxisx.cpp @@ -22,7 +22,7 @@ #include "qabstractaxis.h" #include "chartpresenter_p.h" #include "qvalueaxis.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QFontMetrics> #include <qmath.h> @@ -31,12 +31,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -ChartValueAxisX::ChartValueAxisX(QValueAxis *axis, QGraphicsItem* item ) +ChartValueAxisX::ChartValueAxisX(QValueAxis *axis, QGraphicsItem *item ) : HorizontalAxis(axis, item), m_axis(axis) { - QObject::connect(m_axis,SIGNAL(tickCountChanged(int)),this, SLOT(handleTickCountChanged(int))); - QObject::connect(m_axis,SIGNAL(labelFormatChanged(QString)),this, SLOT(handleLabelFormatChanged(QString))); + QObject::connect(m_axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(m_axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); } ChartValueAxisX::~ChartValueAxisX() @@ -61,10 +61,10 @@ QVector<qreal> ChartValueAxisX::calculateLayout() const void ChartValueAxisX::updateGeometry() { - const QVector<qreal>& layout = ChartAxis::layout(); + const QVector<qreal>& layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; - setLabels(createValueLabels(min(),max(),layout.size(),m_axis->labelFormat())); + setLabels(createValueLabels(min(), max(), layout.size(), m_axis->labelFormat())); HorizontalAxis::updateGeometry(); } @@ -86,7 +86,7 @@ QSizeF ChartValueAxisX::sizeHint(Qt::SizeHint which, const QSizeF &constraint) c { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = HorizontalAxis::sizeHint(which, constraint); diff --git a/src/axis/valueaxis/chartvalueaxisx_p.h b/src/axis/valueaxis/chartvalueaxisx_p.h index e0556b60..bebfdb38 100644 --- a/src/axis/valueaxis/chartvalueaxisx_p.h +++ b/src/axis/valueaxis/chartvalueaxisx_p.h @@ -35,13 +35,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QValueAxis; -class ChartPresenter; class ChartValueAxisX : public HorizontalAxis { Q_OBJECT public: - ChartValueAxisX(QValueAxis *axis, QGraphicsItem* item = 0); + ChartValueAxisX(QValueAxis *axis, QGraphicsItem *item = 0); ~ChartValueAxisX(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; diff --git a/src/axis/valueaxis/chartvalueaxisy.cpp b/src/axis/valueaxis/chartvalueaxisy.cpp index 14b2d207..54b58242 100644 --- a/src/axis/valueaxis/chartvalueaxisy.cpp +++ b/src/axis/valueaxis/chartvalueaxisy.cpp @@ -22,7 +22,7 @@ #include "qabstractaxis.h" #include "chartpresenter_p.h" #include "qvalueaxis.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include <QGraphicsLayout> #include <QFontMetrics> #include <qmath.h> @@ -30,12 +30,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -ChartValueAxisY::ChartValueAxisY(QValueAxis *axis, QGraphicsItem* item) +ChartValueAxisY::ChartValueAxisY(QValueAxis *axis, QGraphicsItem *item) : VerticalAxis(axis, item), m_axis(axis) { - QObject::connect(m_axis,SIGNAL(tickCountChanged(int)),this, SLOT(handleTickCountChanged(int))); - QObject::connect(m_axis,SIGNAL(labelFormatChanged(QString)),this, SLOT(handleLabelFormatChanged(QString))); + QObject::connect(m_axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(m_axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); } ChartValueAxisY::~ChartValueAxisY() @@ -62,7 +62,7 @@ QVector<qreal> ChartValueAxisY::calculateLayout() const void ChartValueAxisY::updateGeometry() { - const QVector<qreal> &layout = ChartAxis::layout(); + const QVector<qreal> &layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; setLabels(createValueLabels(min(),max(),layout.size(),m_axis->labelFormat())); @@ -87,7 +87,7 @@ QSizeF ChartValueAxisY::sizeHint(Qt::SizeHint which, const QSizeF &constraint) c { Q_UNUSED(constraint) - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); QSizeF sh; QSizeF base = VerticalAxis::sizeHint(which, constraint); QStringList ticksList = createValueLabels(min(),max(),m_axis->tickCount(),m_axis->labelFormat()); diff --git a/src/axis/valueaxis/chartvalueaxisy_p.h b/src/axis/valueaxis/chartvalueaxisy_p.h index 5a01b9b2..3b586698 100644 --- a/src/axis/valueaxis/chartvalueaxisy_p.h +++ b/src/axis/valueaxis/chartvalueaxisy_p.h @@ -35,13 +35,12 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QValueAxis; -class ChartPresenter; class ChartValueAxisY : public VerticalAxis { Q_OBJECT public: - ChartValueAxisY(QValueAxis *axis, QGraphicsItem* item = 0); + ChartValueAxisY(QValueAxis *axis, QGraphicsItem *item = 0); ~ChartValueAxisY(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; diff --git a/src/axis/valueaxis/polarchartvalueaxisangular.cpp b/src/axis/valueaxis/polarchartvalueaxisangular.cpp new file mode 100644 index 00000000..5fdbcb4b --- /dev/null +++ b/src/axis/valueaxis/polarchartvalueaxisangular.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartvalueaxisangular_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartValueAxisAngular::PolarChartValueAxisAngular(QValueAxis *axis, QGraphicsItem *item) + : PolarChartAxisAngular(axis, item) +{ + QObject::connect(axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); +} + +PolarChartValueAxisAngular::~PolarChartValueAxisAngular() +{ +} + +QVector<qreal> PolarChartValueAxisAngular::calculateLayout() const +{ + int tickCount = static_cast<QValueAxis *>(axis())->tickCount(); + Q_ASSERT(tickCount >= 2); + + QVector<qreal> points; + points.resize(tickCount); + + const qreal d = 360.0 / qreal(tickCount - 1); + + for (int i = 0; i < tickCount; ++i) { + qreal angularCoordinate = qreal(i) * d; + points[i] = angularCoordinate; + } + + return points; +} + +void PolarChartValueAxisAngular::createAxisLabels(const QVector<qreal> &layout) +{ + QStringList labelList = createValueLabels(min(), max(), layout.size(), static_cast<QValueAxis *>(axis())->labelFormat()); + setLabels(labelList); +} + +void PolarChartValueAxisAngular::handleTickCountChanged(int tick) +{ + Q_UNUSED(tick); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +void PolarChartValueAxisAngular::handleLabelFormatChanged(const QString &format) +{ + Q_UNUSED(format); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +#include "moc_polarchartvalueaxisangular_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/valueaxis/polarchartvalueaxisangular_p.h b/src/axis/valueaxis/polarchartvalueaxisangular_p.h new file mode 100644 index 00000000..afe5192a --- /dev/null +++ b/src/axis/valueaxis/polarchartvalueaxisangular_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTVALUEAXISANGULAR_P_H +#define POLARCHARTVALUEAXISANGULAR_P_H + +#include "polarchartaxisangular_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QValueAxis; + +class PolarChartValueAxisAngular : public PolarChartAxisAngular +{ + Q_OBJECT +public: + PolarChartValueAxisAngular(QValueAxis *axis, QGraphicsItem *item); + ~PolarChartValueAxisAngular(); + + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +private Q_SLOTS: + void handleTickCountChanged(int tick); + void handleLabelFormatChanged(const QString &format); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTVALUEAXISANGULAR_P_H diff --git a/src/axis/valueaxis/polarchartvalueaxisradial.cpp b/src/axis/valueaxis/polarchartvalueaxisradial.cpp new file mode 100644 index 00000000..f11e9265 --- /dev/null +++ b/src/axis/valueaxis/polarchartvalueaxisradial.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartvalueaxisradial_p.h" +#include "chartpresenter_p.h" +#include "abstractchartlayout_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarChartValueAxisRadial::PolarChartValueAxisRadial(QValueAxis *axis, QGraphicsItem *item) + : PolarChartAxisRadial(axis, item) +{ + QObject::connect(axis, SIGNAL(tickCountChanged(int)), this, SLOT(handleTickCountChanged(int))); + QObject::connect(axis, SIGNAL(labelFormatChanged(QString)), this, SLOT(handleLabelFormatChanged(QString))); +} + +PolarChartValueAxisRadial::~PolarChartValueAxisRadial() +{ +} + +QVector<qreal> PolarChartValueAxisRadial::calculateLayout() const +{ + int tickCount = static_cast<QValueAxis *>(axis())->tickCount(); + Q_ASSERT(tickCount >= 2); + + QVector<qreal> points; + points.resize(tickCount); + + const qreal d = (axisGeometry().width() / 2) / qreal(tickCount - 1); + + for (int i = 0; i < tickCount; ++i) { + qreal radialCoordinate = qreal(i) * d; + points[i] = radialCoordinate; + } + + return points; +} + +void PolarChartValueAxisRadial::createAxisLabels(const QVector<qreal> &layout) +{ + setLabels(createValueLabels(min(), max(), layout.size(), static_cast<QValueAxis *>(axis())->labelFormat())); +} + +void PolarChartValueAxisRadial::handleTickCountChanged(int tick) +{ + Q_UNUSED(tick); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +void PolarChartValueAxisRadial::handleLabelFormatChanged(const QString &format) +{ + Q_UNUSED(format); + QGraphicsLayoutItem::updateGeometry(); + if (presenter()) + presenter()->layout()->invalidate(); +} + +#include "moc_polarchartvalueaxisradial_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/axis/valueaxis/polarchartvalueaxisradial_p.h b/src/axis/valueaxis/polarchartvalueaxisradial_p.h new file mode 100644 index 00000000..f443d1dc --- /dev/null +++ b/src/axis/valueaxis/polarchartvalueaxisradial_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTVALUEAXISRADIAL_P_H +#define POLARCHARTVALUEAXISRADIAL_P_H + +#include "polarchartaxisradial_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QValueAxis; + +class PolarChartValueAxisRadial : public PolarChartAxisRadial +{ + Q_OBJECT +public: + PolarChartValueAxisRadial(QValueAxis *axis, QGraphicsItem *item); + ~PolarChartValueAxisRadial(); + + virtual QVector<qreal> calculateLayout() const; + virtual void createAxisLabels(const QVector<qreal> &layout); + +private Q_SLOTS: + void handleTickCountChanged(int tick); + void handleLabelFormatChanged(const QString &format); +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTVALUEAXISRADIAL_P_H diff --git a/src/axis/valueaxis/qvalueaxis.cpp b/src/axis/valueaxis/qvalueaxis.cpp index 032fb4e1..80aa25ce 100644 --- a/src/axis/valueaxis/qvalueaxis.cpp +++ b/src/axis/valueaxis/qvalueaxis.cpp @@ -23,6 +23,8 @@ #include "chartvalueaxisx_p.h" #include "chartvalueaxisy_p.h" #include "abstractdomain_p.h" +#include "polarchartvalueaxisangular_p.h" +#include "polarchartvalueaxisradial_p.h" #include "chartdataset_p.h" #include "chartpresenter_p.h" #include "charttheme_p.h" @@ -387,14 +389,24 @@ void QValueAxisPrivate::setRange(qreal min, qreal max) } } -void QValueAxisPrivate::initializeGraphics(QGraphicsItem* parent) +void QValueAxisPrivate::initializeGraphics(QGraphicsItem *parent) { Q_Q(QValueAxis); - ChartAxis* axis(0); - if (orientation() == Qt::Vertical) - axis = new ChartValueAxisY(q,parent); - if (orientation() == Qt::Horizontal) - axis = new ChartValueAxisX(q,parent); + ChartAxisElement *axis(0); + + if (m_chart->chartType() == QChart::ChartTypeCartesian) { + if (orientation() == Qt::Vertical) + axis = new ChartValueAxisY(q,parent); + if (orientation() == Qt::Horizontal) + axis = new ChartValueAxisX(q,parent); + } + + if (m_chart->chartType() == QChart::ChartTypePolar) { + if (orientation() == Qt::Vertical) + axis = new PolarChartValueAxisRadial(q, parent); + if (orientation() == Qt::Horizontal) + axis = new PolarChartValueAxisAngular(q, parent); + } m_item.reset(axis); QAbstractAxisPrivate::initializeGraphics(parent); @@ -404,20 +416,16 @@ void QValueAxisPrivate::initializeGraphics(QGraphicsItem* parent) void QValueAxisPrivate::initializeDomain(AbstractDomain *domain) { if (orientation() == Qt::Vertical) { - if(!qFuzzyIsNull(m_max - m_min)) { + if (!qFuzzyIsNull(m_max - m_min)) domain->setRangeY(m_min, m_max); - } - else { + else setRange(domain->minY(), domain->maxY()); - } } if (orientation() == Qt::Horizontal) { - if(!qFuzzyIsNull(m_max - m_min)) { + if (!qFuzzyIsNull(m_max - m_min)) domain->setRangeX(m_min, m_max); - } - else { + else setRange(domain->minX(), domain->maxX()); - } } } diff --git a/src/axis/verticalaxis.cpp b/src/axis/verticalaxis.cpp index d3c5efa6..3726359b 100644 --- a/src/axis/verticalaxis.cpp +++ b/src/axis/verticalaxis.cpp @@ -25,31 +25,29 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -VerticalAxis::VerticalAxis(QAbstractAxis *axis, QGraphicsItem* item, bool intervalAxis) - : ChartAxis(axis, item, intervalAxis) +VerticalAxis::VerticalAxis(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) + : CartesianChartAxis(axis, item, intervalAxis) { - } VerticalAxis::~VerticalAxis() { - } void VerticalAxis::updateGeometry() { - const QVector<qreal> &layout = ChartAxis::layout(); + const QVector<qreal> &layout = ChartAxisElement::layout(); if (layout.isEmpty()) return; QStringList labelList = labels(); - QList<QGraphicsItem *> lines = lineItems(); + QList<QGraphicsItem *> lines = gridItems(); QList<QGraphicsItem *> labels = labelItems(); QList<QGraphicsItem *> shades = shadeItems(); - QList<QGraphicsItem *> axis = arrowItems(); - QGraphicsSimpleTextItem* title = titleItem(); + QList<QGraphicsItem *> arrow = arrowItems(); + QGraphicsSimpleTextItem *title = titleItem(); Q_ASSERT(labels.size() == labelList.size()); Q_ASSERT(layout.size() == labelList.size()); @@ -61,32 +59,31 @@ void VerticalAxis::updateGeometry() //arrow - QGraphicsLineItem *arrowItem = static_cast<QGraphicsLineItem*>(axis.at(0)); + QGraphicsLineItem *arrowItem = static_cast<QGraphicsLineItem*>(arrow.at(0)); //arrow position - if (alignment()==Qt::AlignLeft) - arrowItem->setLine( axisRect.right() , gridRect.top(), axisRect.right(), gridRect.bottom()); - else if(alignment()==Qt::AlignRight) - arrowItem->setLine( axisRect.left() , gridRect.top(), axisRect.left(), gridRect.bottom()); + if (axis()->alignment() == Qt::AlignLeft) + arrowItem->setLine(axisRect.right(), gridRect.top(), axisRect.right(), gridRect.bottom()); + else if (axis()->alignment() == Qt::AlignRight) + arrowItem->setLine(axisRect.left(), gridRect.top(), axisRect.left(), gridRect.bottom()); - QFontMetrics fn(font()); + QFontMetrics fn(axis()->labelsFont()); //title int titlePad = 0; QRectF titleBoundingRect; - if (!titleText().isEmpty() && titleItem()->isVisible()) { + QString titleText = axis()->titleText(); + if (!titleText.isEmpty() && titleItem()->isVisible()) { QFontMetrics fn(title->font()); int size(0); size = gridRect.height(); - QString titleText = this->titleText(); if (fn.boundingRect(titleText).width() > size) { QString string = titleText + "..."; while (fn.boundingRect(string).width() > size && string.length() > 3) - string.remove(string.length() - 4, 1); + string.remove(string.length() - 4, 1); title->setText(string); - } - else { + } else { title->setText(titleText); } @@ -94,10 +91,10 @@ void VerticalAxis::updateGeometry() titleBoundingRect = title->boundingRect(); QPointF center = gridRect.center() - titleBoundingRect.center(); - if (alignment() == Qt::AlignLeft) { + if (axis()->alignment() == Qt::AlignLeft) { title->setPos(axisRect.left() - titleBoundingRect.width() / 2 + titleBoundingRect.height() / 2 + titlePad, center.y()); } - else if (alignment() == Qt::AlignRight) { + else if (axis()->alignment() == Qt::AlignRight) { title->setPos(axisRect.right() - titleBoundingRect.width() / 2 - titleBoundingRect.height() / 2 - titlePad, center.y()); } title->setTransformOriginPoint(titleBoundingRect.center()); @@ -105,14 +102,13 @@ void VerticalAxis::updateGeometry() } for (int i = 0; i < layout.size(); ++i) { - //items QGraphicsLineItem *gridItem = static_cast<QGraphicsLineItem *>(lines.at(i)); - QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(axis.at(i + 1)); + QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrow.at(i + 1)); QGraphicsSimpleTextItem *labelItem = static_cast<QGraphicsSimpleTextItem *>(labels.at(i)); //grid line - gridItem->setLine(gridRect.left() , layout[i], gridRect.right(), layout[i]); + gridItem->setLine(gridRect.left(), layout[i], gridRect.right(), layout[i]); //label text wrapping QString text = labelList.at(i); @@ -138,10 +134,10 @@ void VerticalAxis::updateGeometry() int widthDiff = rect.width() - boundingRect.width(); //ticks and label position - if (alignment() == Qt::AlignLeft) { + if (axis()->alignment() == Qt::AlignLeft) { labelItem->setPos(axisRect.right() - rect.width() + (widthDiff / 2) - labelPadding(), layout[i] - center.y()); tickItem->setLine(axisRect.right() - labelPadding(), layout[i], axisRect.right(), layout[i]); - } else if (alignment() == Qt::AlignRight) { + } else if (axis()->alignment() == Qt::AlignRight) { labelItem->setPos(axisRect.left() + labelPadding() - (widthDiff / 2), layout[i] - center.y()); tickItem->setLine(axisRect.left(), layout[i], axisRect.left() + labelPadding(), layout[i]); } @@ -202,7 +198,7 @@ void VerticalAxis::updateGeometry() gridLine = static_cast<QGraphicsLineItem *>(lines.at(layout.size())); gridLine->setLine(gridRect.left(), gridRect.top(), gridRect.right(), gridRect.top()); gridLine->setVisible(true); - gridLine = static_cast<QGraphicsLineItem*>(lines.at(layout.size()+1)); + gridLine = static_cast<QGraphicsLineItem*>(lines.at(layout.size() + 1)); gridLine->setLine(gridRect.left(), gridRect.bottom(), gridRect.right(), gridRect.bottom()); gridLine->setVisible(true); } @@ -212,10 +208,10 @@ QSizeF VerticalAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) cons { Q_UNUSED(constraint); - QFontMetrics fn(titleFont()); - QSizeF sh(0,0); + QFontMetrics fn(axis()->titleFont()); + QSizeF sh(0, 0); - if (titleText().isEmpty() || !titleItem()->isVisible()) + if (axis()->titleText().isEmpty() || !titleItem()->isVisible()) return sh; switch (which) { diff --git a/src/axis/verticalaxis_p.h b/src/axis/verticalaxis_p.h index 4d186c44..159bba63 100644 --- a/src/axis/verticalaxis_p.h +++ b/src/axis/verticalaxis_p.h @@ -30,14 +30,14 @@ #ifndef VERTICALAXIS_P_H_ #define VERTICALAXIS_P_H_ -#include "chartaxis_p.h" +#include "cartesianchartaxis_p.h" QTCOMMERCIALCHART_BEGIN_NAMESPACE -class VerticalAxis : public ChartAxis +class VerticalAxis : public CartesianChartAxis { public: - VerticalAxis(QAbstractAxis *axis, QGraphicsItem* item = 0, bool intervalAxis = false); + VerticalAxis(QAbstractAxis *axis, QGraphicsItem *item = 0, bool intervalAxis = false); ~VerticalAxis(); QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; protected: diff --git a/src/chartdataset.cpp b/src/chartdataset.cpp index d5401663..4d7e1a09 100644 --- a/src/chartdataset.cpp +++ b/src/chartdataset.cpp @@ -33,9 +33,13 @@ #include "qpieseries.h" #include "chartitem_p.h" #include "xydomain_p.h" +#include "xypolardomain_p.h" #include "xlogydomain_p.h" #include "logxydomain_p.h" #include "logxlogydomain_p.h" +#include "xlogypolardomain_p.h" +#include "logxypolardomain_p.h" +#include "logxlogypolardomain_p.h" #ifndef QT_ON_ARM #include "qdatetimeaxis.h" @@ -66,6 +70,20 @@ void ChartDataSet::addSeries(QAbstractSeries *series) return; } + // Ignore unsupported series added to polar chart + if (m_chart && m_chart->chartType() == QChart::ChartTypePolar) { + if (!(series->type() == QAbstractSeries::SeriesTypeArea + || series->type() == QAbstractSeries::SeriesTypeLine + || series->type() == QAbstractSeries::SeriesTypeScatter + || series->type() == QAbstractSeries::SeriesTypeSpline)) { + qWarning() << QObject::tr("Can not add series. Series type is not supported by a polar chart."); + return; + } + series->d_ptr->setDomain(new XYPolarDomain()); + } else { + series->d_ptr->setDomain(new XYDomain()); + } + series->d_ptr->initializeDomain(); m_seriesList.append(series); @@ -78,7 +96,7 @@ void ChartDataSet::addSeries(QAbstractSeries *series) /* * This method adds axis to chartdataset, axis ownership is taken from caller. */ -void ChartDataSet::addAxis(QAbstractAxis *axis,Qt::Alignment aligment) +void ChartDataSet::addAxis(QAbstractAxis *axis, Qt::Alignment aligment) { if (m_axisList.contains(axis)) { qWarning() << QObject::tr("Can not add axis. Axis already on the chart."); @@ -87,12 +105,25 @@ void ChartDataSet::addAxis(QAbstractAxis *axis,Qt::Alignment aligment) axis->d_ptr->setAlignment(aligment); - if(!axis->alignment()) { - qWarning()<< QObject::tr("No alignment specified !"); + if (!axis->alignment()) { + qWarning() << QObject::tr("No alignment specified !"); return; }; - QSharedPointer<AbstractDomain> domain(new XYDomain()); + AbstractDomain *newDomain; + if (m_chart && m_chart->chartType() == QChart::ChartTypePolar) { + foreach (QAbstractAxis *existingAxis, axes()) { + if (existingAxis->orientation() == axis->orientation()) { + qWarning() << QObject::tr("Cannot add multiple axes of same orientation to a polar chart!"); + return; + } + } + newDomain = new XYPolarDomain(); + } else { + newDomain = new XYDomain(); + } + + QSharedPointer<AbstractDomain> domain(newDomain); axis->d_ptr->initializeDomain(domain.data()); axis->setParent(this); @@ -122,6 +153,8 @@ void ChartDataSet::removeSeries(QAbstractSeries *series) emit seriesRemoved(series); m_seriesList.removeAll(series); + // Reset domain to default + series->d_ptr->setDomain(new XYDomain()); series->setParent(0); series->d_ptr->m_chart = 0; } @@ -152,13 +185,13 @@ void ChartDataSet::removeAxis(QAbstractAxis *axis) /* * This method attaches axis to series, return true if success. */ -bool ChartDataSet::attachAxis(QAbstractSeries* series,QAbstractAxis *axis) +bool ChartDataSet::attachAxis(QAbstractSeries *series,QAbstractAxis *axis) { Q_ASSERT(series); Q_ASSERT(axis); - QList<QAbstractSeries* > attachedSeriesList = axis->d_ptr->m_series; - QList<QAbstractAxis* > attachedAxisList = series->d_ptr->m_axes; + QList<QAbstractSeries *> attachedSeriesList = axis->d_ptr->m_series; + QList<QAbstractAxis *> attachedAxisList = series->d_ptr->m_axes; if (!m_seriesList.contains(series)) { qWarning() << QObject::tr("Can not find series on the chart."); @@ -180,25 +213,42 @@ bool ChartDataSet::attachAxis(QAbstractSeries* series,QAbstractAxis *axis) return false; } - AbstractDomain* domain = series->d_ptr->domain(); + AbstractDomain *domain = series->d_ptr->domain(); AbstractDomain::DomainType type = selectDomain(attachedAxisList<<axis); - if(type == AbstractDomain::UndefinedDomain) return false; + if (type == AbstractDomain::UndefinedDomain) return false; - if(domain->type()!=type){ + if (domain->type() != type) { AbstractDomain *old = domain; - domain = createDomain(type); + domain = createDomain(type); domain->setRange(old->minX(), old->maxX(), old->minY(), old->maxY()); + // Initialize domain size to old domain size, as it won't get updated + // unless geometry changes. + domain->setSize(old->size()); } - if(!domain) return false; + if (!domain) + return false; + + if (!domain->attachAxis(axis)) + return false; - if(!domain->attachAxis(axis)) return false; + QList<AbstractDomain *> blockedDomains; + domain->blockRangeSignals(true); + blockedDomains << domain; - if(domain!=series->d_ptr->domain()){ - foreach(QAbstractAxis* axis,series->d_ptr->m_axes){ + if (domain != series->d_ptr->domain()) { + foreach (QAbstractAxis *axis, series->d_ptr->m_axes) { series->d_ptr->domain()->detachAxis(axis); domain->attachAxis(axis); + foreach (QAbstractSeries *otherSeries, axis->d_ptr->m_series) { + if (otherSeries != series && otherSeries->d_ptr->domain()) { + if (!otherSeries->d_ptr->domain()->rangeSignalsBlocked()) { + otherSeries->d_ptr->domain()->blockRangeSignals(true); + blockedDomains << otherSeries->d_ptr->domain(); + } + } + } } series->d_ptr->setDomain(domain); series->d_ptr->initializeDomain(); @@ -210,6 +260,9 @@ bool ChartDataSet::attachAxis(QAbstractSeries* series,QAbstractAxis *axis) series->d_ptr->initializeAxes(); axis->d_ptr->initializeDomain(domain); + foreach (AbstractDomain *blockedDomain, blockedDomains) + blockedDomain->blockRangeSignals(false); + return true; } @@ -327,12 +380,12 @@ void ChartDataSet::findMinMaxForSeries(QList<QAbstractSeries *> series,Qt::Orien { Q_ASSERT(!series.isEmpty()); - AbstractDomain* domain = series.first()->d_ptr->domain(); + AbstractDomain *domain = series.first()->d_ptr->domain(); min = (orientation == Qt::Vertical) ? domain->minY() : domain->minX(); max = (orientation == Qt::Vertical) ? domain->maxY() : domain->maxX(); - for(int i = 1; i< series.size(); i++) { - AbstractDomain* domain = series[i]->d_ptr->domain(); + for (int i = 1; i< series.size(); i++) { + AbstractDomain *domain = series[i]->d_ptr->domain(); min = qMin((orientation == Qt::Vertical) ? domain->minY() : domain->minX(), min); max = qMax((orientation == Qt::Vertical) ? domain->maxY() : domain->maxX(), max); } @@ -437,7 +490,7 @@ QPointF ChartDataSet::mapToPosition(const QPointF &value, QAbstractSeries *serie return point; } -QList<QAbstractAxis*> ChartDataSet::axes() const +QList<QAbstractAxis *> ChartDataSet::axes() const { return m_axisList; } @@ -447,7 +500,7 @@ QList<QAbstractSeries *> ChartDataSet::series() const return m_seriesList; } -AbstractDomain::DomainType ChartDataSet::selectDomain(QList<QAbstractAxis*> axes) +AbstractDomain::DomainType ChartDataSet::selectDomain(QList<QAbstractAxis *> axes) { enum Type { Undefined = 0, @@ -458,75 +511,95 @@ AbstractDomain::DomainType ChartDataSet::selectDomain(QList<QAbstractAxis*> axes int horizontal(Undefined); int vertical(Undefined); - foreach(QAbstractAxis* axis, axes) - { - switch(axis->type()) { - case QAbstractAxis::AxisTypeLogValue: - - if(axis->orientation()==Qt::Horizontal) { - horizontal|=LogType; - } - if(axis->orientation()==Qt::Vertical) { - vertical|=LogType; - } + // Assume cartesian chart type, unless chart is set + QChart::ChartType chartType(QChart::ChartTypeCartesian); + if (m_chart) + chartType = m_chart->chartType(); + foreach (QAbstractAxis *axis, axes) + { + switch (axis->type()) { + case QAbstractAxis::AxisTypeLogValue: + if (axis->orientation() == Qt::Horizontal) + horizontal |= LogType; + if (axis->orientation() == Qt::Vertical) + vertical |= LogType; break; - case QAbstractAxis::AxisTypeValue: - case QAbstractAxis::AxisTypeBarCategory: - case QAbstractAxis::AxisTypeCategory: - case QAbstractAxis::AxisTypeDateTime: - if(axis->orientation()==Qt::Horizontal) { - horizontal|=ValueType; - } - if(axis->orientation()==Qt::Vertical) { - vertical|=ValueType; - } + case QAbstractAxis::AxisTypeValue: + case QAbstractAxis::AxisTypeBarCategory: + case QAbstractAxis::AxisTypeCategory: + case QAbstractAxis::AxisTypeDateTime: + if (axis->orientation() == Qt::Horizontal) + horizontal |= ValueType; + if (axis->orientation() == Qt::Vertical) + vertical |= ValueType; break; - default: - qWarning()<<"Undefined type"; + default: + qWarning() << "Undefined type"; break; } } - if(vertical==Undefined) vertical=ValueType; - if(horizontal==Undefined) horizontal=ValueType; + if (vertical == Undefined) + vertical = ValueType; + if (horizontal == Undefined) + horizontal = ValueType; - if(vertical==ValueType && horizontal== ValueType) { - return AbstractDomain::XYDomain; + if (vertical == ValueType && horizontal == ValueType) { + if (chartType == QChart::ChartTypeCartesian) + return AbstractDomain::XYDomain; + else if (chartType == QChart::ChartTypePolar) + return AbstractDomain::XYPolarDomain; } - if(vertical==LogType && horizontal== ValueType) { - return AbstractDomain::XLogYDomain; + if (vertical == LogType && horizontal == ValueType) { + if (chartType == QChart::ChartTypeCartesian) + return AbstractDomain::XLogYDomain; + if (chartType == QChart::ChartTypePolar) + return AbstractDomain::XLogYPolarDomain; } - if(vertical==ValueType && horizontal== LogType) { - return AbstractDomain::LogXYDomain; + if (vertical == ValueType && horizontal == LogType) { + if (chartType == QChart::ChartTypeCartesian) + return AbstractDomain::LogXYDomain; + else if (chartType == QChart::ChartTypePolar) + return AbstractDomain::LogXYPolarDomain; } - if(vertical==LogType && horizontal== LogType) { - return AbstractDomain::LogXLogYDomain; + if (vertical == LogType && horizontal == LogType) { + if (chartType == QChart::ChartTypeCartesian) + return AbstractDomain::LogXLogYDomain; + else if (chartType == QChart::ChartTypePolar) + return AbstractDomain::LogXLogYPolarDomain; } return AbstractDomain::UndefinedDomain; } - //refactor create factory AbstractDomain* ChartDataSet::createDomain(AbstractDomain::DomainType type) { - switch(type) - { - case AbstractDomain::LogXLogYDomain: - return new LogXLogYDomain(); - case AbstractDomain::XYDomain: - return new XYDomain(); - case AbstractDomain::XLogYDomain: - return new XLogYDomain(); - case AbstractDomain::LogXYDomain: - return new LogXYDomain(); - default: - return 0; - } + switch (type) + { + case AbstractDomain::LogXLogYDomain: + return new LogXLogYDomain(); + case AbstractDomain::XYDomain: + return new XYDomain(); + case AbstractDomain::XLogYDomain: + return new XLogYDomain(); + case AbstractDomain::LogXYDomain: + return new LogXYDomain(); + case AbstractDomain::XYPolarDomain: + return new XYPolarDomain(); + case AbstractDomain::XLogYPolarDomain: + return new XLogYPolarDomain(); + case AbstractDomain::LogXYPolarDomain: + return new LogXYPolarDomain(); + case AbstractDomain::LogXLogYPolarDomain: + return new LogXLogYPolarDomain(); + default: + return 0; + } } #include "moc_chartdataset_p.cpp" diff --git a/src/chartpresenter.cpp b/src/chartpresenter.cpp index 8194f084..f8242aac 100644 --- a/src/chartpresenter.cpp +++ b/src/chartpresenter.cpp @@ -27,24 +27,28 @@ #include "chartanimation_p.h" #include "qabstractseries_p.h" #include "qareaseries.h" -#include "chartaxis_p.h" +#include "chartaxiselement_p.h" #include "chartbackground_p.h" -#include "chartlayout_p.h" +#include "cartesianchartlayout_p.h" +#include "polarchartlayout_p.h" #include "charttitle_p.h" #include <QTimer> QTCOMMERCIALCHART_BEGIN_NAMESPACE -ChartPresenter::ChartPresenter(QChart *chart) +ChartPresenter::ChartPresenter(QChart *chart, QChart::ChartType type) : QObject(chart), m_chart(chart), m_options(QChart::NoAnimation), m_state(ShowState), - m_layout(new ChartLayout(this)), m_background(0), m_title(0) { - + if (type == QChart::ChartTypeCartesian) + m_layout = new CartesianChartLayout(this); + else if (type == QChart::ChartTypePolar) + m_layout = new PolarChartLayout(this); + Q_ASSERT(m_layout); } ChartPresenter::~ChartPresenter() @@ -54,25 +58,25 @@ ChartPresenter::~ChartPresenter() void ChartPresenter::setGeometry(const QRectF rect) { - if(m_rect != rect) { - m_rect=rect; - foreach (ChartItem *chart, m_chartItems){ - chart->domain()->setSize(rect.size()); - chart->setPos(rect.topLeft()); - } - } + if (m_rect != rect) { + m_rect = rect; + foreach (ChartItem *chart, m_chartItems) { + chart->domain()->setSize(rect.size()); + chart->setPos(rect.topLeft()); + } + } } QRectF ChartPresenter::geometry() const { - return m_rect; + return m_rect; } void ChartPresenter::handleAxisAdded(QAbstractAxis *axis) { axis->d_ptr->initializeGraphics(rootItem()); axis->d_ptr->initializeAnimations(m_options); - ChartAxis *item = axis->d_ptr->axisItem(); + ChartAxisElement *item = axis->d_ptr->axisItem(); item->setPresenter(this); item->setThemeManager(m_chart->d_ptr->m_themeManager); m_axisItems<<item; @@ -82,7 +86,7 @@ void ChartPresenter::handleAxisAdded(QAbstractAxis *axis) void ChartPresenter::handleAxisRemoved(QAbstractAxis *axis) { - ChartAxis *item = axis->d_ptr->m_item.take(); + ChartAxisElement *item = axis->d_ptr->m_item.take(); item->hide(); item->disconnect(); item->deleteLater(); @@ -275,7 +279,7 @@ bool ChartPresenter::isBackgroundDropShadowEnabled() const } -ChartLayout *ChartPresenter::layout() +AbstractChartLayout *ChartPresenter::layout() { return m_layout; } @@ -295,7 +299,7 @@ ChartBackground *ChartPresenter::backgroundElement() return m_background; } -QList<ChartAxis *> ChartPresenter::axisItems() const +QList<ChartAxisElement *> ChartPresenter::axisItems() const { return m_axisItems; } diff --git a/src/chartpresenter_p.h b/src/chartpresenter_p.h index 9f1c8b89..19bc5c42 100644 --- a/src/chartpresenter_p.h +++ b/src/chartpresenter_p.h @@ -42,12 +42,12 @@ class AxisItem; class QAbstractSeries; class ChartDataSet; class AbstractDomain; -class ChartAxis; +class ChartAxisElement; class ChartAnimator; class ChartBackground; class ChartTitle; class ChartAnimation; -class ChartLayout; +class AbstractChartLayout; class ChartPresenter: public QObject { @@ -78,7 +78,7 @@ public: ZoomOutState }; - ChartPresenter(QChart *chart); + ChartPresenter(QChart *chart, QChart::ChartType type); virtual ~ChartPresenter(); @@ -88,12 +88,9 @@ public: QGraphicsItem *rootItem(){ return m_chart; } ChartBackground *backgroundElement(); ChartTitle *titleElement(); - QList<ChartAxis *> axisItems() const; + QList<ChartAxisElement *> axisItems() const; QList<ChartItem *> chartItems() const; - ChartItem* chartElement(QAbstractSeries* series) const; - ChartAxis* chartElement(QAbstractAxis* axis) const; - QLegend *legend(); void setBackgroundBrush(const QBrush &brush); @@ -128,7 +125,9 @@ public: void setState(State state,QPointF point); State state() const { return m_state; } QPointF statePoint() const { return m_statePoint; } - ChartLayout *layout(); + AbstractChartLayout *layout(); + + QChart::ChartType chartType() const { return m_chart->chartType(); } private: void createBackgroundItem(); @@ -149,14 +148,14 @@ Q_SIGNALS: private: QChart *m_chart; QList<ChartItem *> m_chartItems; - QList<ChartAxis *> m_axisItems; + QList<ChartAxisElement *> m_axisItems; QList<QAbstractSeries *> m_series; QList<QAbstractAxis *> m_axes; QChart::AnimationOptions m_options; State m_state; QPointF m_statePoint; QList<ChartAnimation *> m_animations; - ChartLayout *m_layout; + AbstractChartLayout *m_layout; ChartBackground *m_background; ChartTitle *m_title; QRectF m_rect; diff --git a/src/domain/abstractdomain.cpp b/src/domain/abstractdomain.cpp index bcaa5164..277472d1 100644 --- a/src/domain/abstractdomain.cpp +++ b/src/domain/abstractdomain.cpp @@ -38,7 +38,7 @@ AbstractDomain::~AbstractDomain() { } -void AbstractDomain::setSize(const QSizeF& size) +void AbstractDomain::setSize(const QSizeF &size) { if(m_size!=size) { @@ -122,9 +122,9 @@ void AbstractDomain::handleHorizontalAxisRangeChanged(qreal min, qreal max) void AbstractDomain::blockRangeSignals(bool block) { - if(m_signalsBlocked!=block){ + if (m_signalsBlocked!=block) { m_signalsBlocked=block; - if(!block) { + if (!block) { emit rangeHorizontalChanged(m_minX,m_maxX); emit rangeVerticalChanged(m_minY,m_maxY); } @@ -165,14 +165,14 @@ qreal AbstractDomain::niceNumber(qreal x, bool ceiling) return q * z; } -bool AbstractDomain::attachAxis(QAbstractAxis* axis) +bool AbstractDomain::attachAxis(QAbstractAxis *axis) { - if(axis->orientation()==Qt::Vertical) { + if (axis->orientation() == Qt::Vertical) { QObject::connect(axis->d_ptr.data(), SIGNAL(rangeChanged(qreal,qreal)), this, SLOT(handleVerticalAxisRangeChanged(qreal,qreal))); QObject::connect(this, SIGNAL(rangeVerticalChanged(qreal,qreal)), axis->d_ptr.data(), SLOT(handleRangeChanged(qreal,qreal))); } - if(axis->orientation()==Qt::Horizontal) { + if (axis->orientation() == Qt::Horizontal) { QObject::connect(axis->d_ptr.data(), SIGNAL(rangeChanged(qreal,qreal)), this, SLOT(handleHorizontalAxisRangeChanged(qreal,qreal))); QObject::connect(this, SIGNAL(rangeHorizontalChanged(qreal,qreal)), axis->d_ptr.data(), SLOT(handleRangeChanged(qreal,qreal))); } @@ -180,14 +180,14 @@ bool AbstractDomain::attachAxis(QAbstractAxis* axis) return true; } -bool AbstractDomain::detachAxis(QAbstractAxis* axis) +bool AbstractDomain::detachAxis(QAbstractAxis *axis) { - if(axis->orientation()==Qt::Vertical) { + if (axis->orientation() == Qt::Vertical) { QObject::disconnect(axis->d_ptr.data(), SIGNAL(rangeChanged(qreal,qreal)), this, SLOT(handleVerticalAxisRangeChanged(qreal,qreal))); QObject::disconnect(this, SIGNAL(rangeVerticalChanged(qreal,qreal)), axis->d_ptr.data(), SLOT(handleRangeChanged(qreal,qreal))); } - if(axis->orientation()==Qt::Horizontal) { + if (axis->orientation() == Qt::Horizontal) { QObject::disconnect(axis->d_ptr.data(), SIGNAL(rangeChanged(qreal,qreal)), this, SLOT(handleHorizontalAxisRangeChanged(qreal,qreal))); QObject::disconnect(this, SIGNAL(rangeHorizontalChanged(qreal,qreal)), axis->d_ptr.data(), SLOT(handleRangeChanged(qreal,qreal))); } @@ -199,10 +199,10 @@ bool AbstractDomain::detachAxis(QAbstractAxis* axis) bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const AbstractDomain &domain1, const AbstractDomain &domain2) { - return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) && - qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) && - qFuzzyIsNull(domain1.m_minX - domain2.m_minX) && - qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); + return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) + && qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) + && qFuzzyIsNull(domain1.m_minX - domain2.m_minX) + && qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); } @@ -218,6 +218,17 @@ QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const AbstractDo return dbg.maybeSpace(); } +// This function adjusts min/max ranges to failsafe values if negative/zero values are attempted. +void AbstractDomain::adjustLogDomainRanges(qreal &min, qreal &max) +{ + if (min <= 0) { + min = 1.0; + if (max <= min) + max = min + 1.0; + } +} + + #include "moc_abstractdomain_p.cpp" QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/domain/abstractdomain_p.h b/src/domain/abstractdomain_p.h index 907cccff..cf7c2bac 100644 --- a/src/domain/abstractdomain_p.h +++ b/src/domain/abstractdomain_p.h @@ -42,12 +42,20 @@ class QTCOMMERCIALCHART_AUTOTEST_EXPORT AbstractDomain: public QObject { Q_OBJECT public: - enum DomainType { UndefinedDomain, XYDomain, XLogYDomain, LogXYDomain, LogXLogYDomain }; + enum DomainType { UndefinedDomain, + XYDomain, + XLogYDomain, + LogXYDomain, + LogXLogYDomain, + XYPolarDomain, + XLogYPolarDomain, + LogXYPolarDomain, + LogXLogYPolarDomain }; public: explicit AbstractDomain(QObject *object = 0); virtual ~AbstractDomain(); - void setSize(const QSizeF& size); + virtual void setSize(const QSizeF &size); QSizeF size() const; virtual DomainType type() = 0; @@ -82,10 +90,10 @@ public: virtual QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const = 0; virtual QPointF calculateDomainPoint(const QPointF &point) const = 0; - virtual QVector<QPointF> calculateGeometryPoints(const QList<QPointF>& vector) const = 0; + virtual QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const = 0; - virtual bool attachAxis(QAbstractAxis* axis); - virtual bool detachAxis(QAbstractAxis* axis); + virtual bool attachAxis(QAbstractAxis *axis); + virtual bool detachAxis(QAbstractAxis *axis); static void looseNiceNumbers(qreal &min, qreal &max, int &ticksCount); static qreal niceNumber(qreal x, bool ceiling); @@ -100,6 +108,8 @@ public Q_SLOTS: void handleHorizontalAxisRangeChanged(qreal min,qreal max); protected: + void adjustLogDomainRanges(qreal &min, qreal &max); + qreal m_minX; qreal m_maxX; qreal m_minY; diff --git a/src/domain/domain.pri b/src/domain/domain.pri index 1d5823d1..ab4fcfce 100644 --- a/src/domain/domain.pri +++ b/src/domain/domain.pri @@ -5,14 +5,24 @@ DEPENDPATH += $$PWD SOURCES += \ $$PWD/abstractdomain.cpp \ + $$PWD/polardomain.cpp \ $$PWD/xydomain.cpp \ + $$PWD/xypolardomain.cpp \ $$PWD/xlogydomain.cpp \ + $$PWD/xlogypolardomain.cpp \ $$PWD/logxydomain.cpp \ - $$PWD/logxlogydomain.cpp + $$PWD/logxypolardomain.cpp \ + $$PWD/logxlogydomain.cpp \ + $$PWD/logxlogypolardomain.cpp PRIVATE_HEADERS += \ $$PWD/abstractdomain_p.h \ + $$PWD/polardomain_p.h \ $$PWD/xydomain_p.h \ + $$PWD/xypolardomain_p.h \ $$PWD/xlogydomain_p.h \ + $$PWD/xlogypolardomain_p.h \ $$PWD/logxydomain_p.h \ - $$PWD/logxlogydomain_p.h + $$PWD/logxypolardomain_p.h \ + $$PWD/logxlogydomain_p.h \ + $$PWD/logxlogypolardomain_p.h diff --git a/src/domain/logxlogydomain.cpp b/src/domain/logxlogydomain.cpp index 7032d137..adb9bc1d 100644 --- a/src/domain/logxlogydomain.cpp +++ b/src/domain/logxlogydomain.cpp @@ -45,6 +45,9 @@ void LogXLogYDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) bool axisXChanged = false; bool axisYChanged = false; + adjustLogDomainRanges(minX, maxX); + adjustLogDomainRanges(minY, maxY); + if (!qFuzzyIsNull(m_minX - minX) || !qFuzzyIsNull(m_maxX - maxX)) { m_minX = minX; m_maxX = maxX; @@ -65,7 +68,7 @@ void LogXLogYDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) qreal logMaxY = log10(m_maxY) / log10(m_logBaseY); m_logLeftY = logMinY < logMaxY ? logMinY : logMaxY; m_logRightY = logMinY > logMaxY ? logMinY : logMaxY; - if(!m_signalsBlocked) + if (!m_signalsBlocked) emit rangeVerticalChanged(m_minY, m_maxY); } @@ -141,13 +144,13 @@ QPointF LogXLogYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) c ok = true; return QPointF(x, y); } else { - qWarning() << "Logarithm of negative value is undefined. Empty layout returned"; + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; ok = false; return QPointF(); } } -QVector<QPointF> LogXLogYDomain::calculateGeometryPoints(const QList<QPointF>& vector) const +QVector<QPointF> LogXLogYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const { const qreal deltaX = m_size.width() / qAbs(m_logRightX - m_logLeftX); const qreal deltaY = m_size.height() / qAbs(m_logRightY - m_logLeftY); @@ -162,7 +165,7 @@ QVector<QPointF> LogXLogYDomain::calculateGeometryPoints(const QList<QPointF>& v result[i].setX(x); result[i].setY(y); } else { - qWarning() << "Logarithm of negative value is undefined. Empty layout returned"; + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; return QVector<QPointF>(); } } @@ -178,17 +181,17 @@ QPointF LogXLogYDomain::calculateDomainPoint(const QPointF &point) const return QPointF(x, y); } -bool LogXLogYDomain::attachAxis(QAbstractAxis* axis) +bool LogXLogYDomain::attachAxis(QAbstractAxis *axis) { AbstractDomain::attachAxis(axis); QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); - if(logAxis && logAxis->orientation()==Qt::Vertical) { + if (logAxis && logAxis->orientation() == Qt::Vertical) { QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); handleVerticalAxisBaseChanged(logAxis->base()); } - if(logAxis && logAxis->orientation()==Qt::Horizontal) { + if (logAxis && logAxis->orientation() == Qt::Horizontal) { QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); handleHorizontalAxisBaseChanged(logAxis->base()); } @@ -196,15 +199,15 @@ bool LogXLogYDomain::attachAxis(QAbstractAxis* axis) return true; } -bool LogXLogYDomain::detachAxis(QAbstractAxis* axis) +bool LogXLogYDomain::detachAxis(QAbstractAxis *axis) { AbstractDomain::detachAxis(axis); QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); - if(logAxis && logAxis->orientation()==Qt::Vertical) + if (logAxis && logAxis->orientation() == Qt::Vertical) QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); - if(logAxis && logAxis->orientation()==Qt::Horizontal) + if (logAxis && logAxis->orientation() == Qt::Horizontal) QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); return true; @@ -234,10 +237,10 @@ void LogXLogYDomain::handleHorizontalAxisBaseChanged(qreal baseX) bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const LogXLogYDomain &domain1, const LogXLogYDomain &domain2) { - return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) && - qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) && - qFuzzyIsNull(domain1.m_minX - domain2.m_minX) && - qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); + return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) + && qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) + && qFuzzyIsNull(domain1.m_minX - domain2.m_minX) + && qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); } diff --git a/src/domain/logxlogydomain_p.h b/src/domain/logxlogydomain_p.h index 2d604163..014bf803 100644 --- a/src/domain/logxlogydomain_p.h +++ b/src/domain/logxlogydomain_p.h @@ -56,10 +56,10 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF>& vector) const; + QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; - bool attachAxis(QAbstractAxis* axis); - bool detachAxis(QAbstractAxis* axis); + bool attachAxis(QAbstractAxis *axis); + bool detachAxis(QAbstractAxis *axis); public Q_SLOTS: void handleVerticalAxisBaseChanged(qreal baseY); diff --git a/src/domain/logxlogypolardomain.cpp b/src/domain/logxlogypolardomain.cpp new file mode 100644 index 00000000..a193ce11 --- /dev/null +++ b/src/domain/logxlogypolardomain.cpp @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "logxlogypolardomain_p.h" +#include "qabstractaxis_p.h" +#include "qlogvalueaxis.h" +#include <qmath.h> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +LogXLogYPolarDomain::LogXLogYPolarDomain(QObject *parent) + : PolarDomain(parent), + m_logLeftX(0), + m_logRightX(1), + m_logBaseX(10), + m_logInnerY(0), + m_logOuterY(1), + m_logBaseY(10) +{ +} + +LogXLogYPolarDomain::~LogXLogYPolarDomain() +{ +} + +void LogXLogYPolarDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) +{ + bool axisXChanged = false; + bool axisYChanged = false; + + adjustLogDomainRanges(minX, maxX); + adjustLogDomainRanges(minY, maxY); + + if (!qFuzzyCompare(m_minX, minX) || !qFuzzyCompare(m_maxX, maxX)) { + m_minX = minX; + m_maxX = maxX; + axisXChanged = true; + qreal logMinX = log10(m_minX) / log10(m_logBaseX); + qreal logMaxX = log10(m_maxX) / log10(m_logBaseX); + m_logLeftX = logMinX < logMaxX ? logMinX : logMaxX; + m_logRightX = logMinX > logMaxX ? logMinX : logMaxX; + if (!m_signalsBlocked) + emit rangeHorizontalChanged(m_minX, m_maxX); + } + + if (!qFuzzyIsNull(m_minY - minY) || !qFuzzyIsNull(m_maxY - maxY)) { + m_minY = minY; + m_maxY = maxY; + axisYChanged = true; + qreal logMinY = log10(m_minY) / log10(m_logBaseY); + qreal logMaxY = log10(m_maxY) / log10(m_logBaseY); + m_logInnerY = logMinY < logMaxY ? logMinY : logMaxY; + m_logOuterY = logMinY > logMaxY ? logMinY : logMaxY; + if (!m_signalsBlocked) + emit rangeVerticalChanged(m_minY, m_maxY); + } + + if (axisXChanged || axisYChanged) + emit updated(); +} + +void LogXLogYPolarDomain::zoomIn(const QRectF &rect) +{ + qreal logLeftX = rect.left() * (m_logRightX - m_logLeftX) / m_size.width() + m_logLeftX; + qreal logRightX = rect.right() * (m_logRightX - m_logLeftX) / m_size.width() + m_logLeftX; + qreal leftX = qPow(m_logBaseX, logLeftX); + qreal rightX = qPow(m_logBaseX, logRightX); + qreal minX = leftX < rightX ? leftX : rightX; + qreal maxX = leftX > rightX ? leftX : rightX; + + qreal logLeftY = m_logOuterY - rect.bottom() * (m_logOuterY - m_logInnerY) / m_size.height(); + qreal logRightY = m_logOuterY - rect.top() * (m_logOuterY - m_logInnerY) / m_size.height(); + qreal leftY = qPow(m_logBaseY, logLeftY); + qreal rightY = qPow(m_logBaseY, logRightY); + qreal minY = leftY < rightY ? leftY : rightY; + qreal maxY = leftY > rightY ? leftY : rightY; + + setRange(minX, maxX, minY, maxY); +} + +void LogXLogYPolarDomain::zoomOut(const QRectF &rect) +{ + const qreal factorX = m_size.width() / rect.width(); + + qreal logLeftX = m_logLeftX + (m_logRightX - m_logLeftX) / 2.0 * (1.0 - factorX); + qreal logRIghtX = m_logLeftX + (m_logRightX - m_logLeftX) / 2.0 * (1.0 + factorX); + qreal leftX = qPow(m_logBaseX, logLeftX); + qreal rightX = qPow(m_logBaseX, logRIghtX); + qreal minX = leftX < rightX ? leftX : rightX; + qreal maxX = leftX > rightX ? leftX : rightX; + + const qreal factorY = m_size.height() / rect.height(); + qreal newLogMinY = m_logInnerY + (m_logOuterY - m_logInnerY) / 2.0 * (1.0 - factorY); + qreal newLogMaxY = m_logInnerY + (m_logOuterY - m_logInnerY) / 2.0 * (1.0 + factorY); + qreal leftY = qPow(m_logBaseY, newLogMinY); + qreal rightY = qPow(m_logBaseY, newLogMaxY); + qreal minY = leftY < rightY ? leftY : rightY; + qreal maxY = leftY > rightY ? leftY : rightY; + + setRange(minX, maxX, minY, maxY); +} + +void LogXLogYPolarDomain::move(qreal dx, qreal dy) +{ + qreal stepX = dx * (m_logRightX - m_logLeftX) / m_size.width(); + qreal leftX = qPow(m_logBaseX, m_logLeftX + stepX); + qreal rightX = qPow(m_logBaseX, m_logRightX + stepX); + qreal minX = leftX < rightX ? leftX : rightX; + qreal maxX = leftX > rightX ? leftX : rightX; + + qreal stepY = dy * (m_logOuterY - m_logInnerY) / m_radius; + qreal leftY = qPow(m_logBaseY, m_logInnerY + stepY); + qreal rightY = qPow(m_logBaseY, m_logOuterY + stepY); + qreal minY = leftY < rightY ? leftY : rightY; + qreal maxY = leftY > rightY ? leftY : rightY; + + setRange(minX, maxX, minY, maxY); +} + +qreal LogXLogYPolarDomain::toAngularCoordinate(qreal value, bool &ok) const +{ + qreal retVal; + if (value <= 0) { + ok = false; + retVal = 0.0; + } else { + ok = true; + const qreal tickSpan = 360.0 / qAbs(m_logRightX - m_logLeftX); + const qreal logValue = log10(value) / log10(m_logBaseX); + const qreal valueDelta = logValue - m_logLeftX; + + retVal = valueDelta * tickSpan; + } + return retVal; +} + +qreal LogXLogYPolarDomain::toRadialCoordinate(qreal value, bool &ok) const +{ + qreal retVal; + if (value <= 0) { + ok = false; + retVal = 0.0; + } else { + ok = true; + const qreal tickSpan = m_radius / qAbs(m_logOuterY - m_logInnerY); + const qreal logValue = log10(value) / log10(m_logBaseY); + const qreal valueDelta = logValue - m_logInnerY; + + retVal = valueDelta * tickSpan; + + if (retVal < 0.0) + retVal = 0.0; + } + return retVal; +} + +QPointF LogXLogYPolarDomain::calculateDomainPoint(const QPointF &point) const +{ + if (point == m_center) + return QPointF(0.0, m_minY); + + QLineF line(m_center, point); + qreal a = 90.0 - line.angle(); + if (a < 0.0) + a += 360.0; + + const qreal deltaX = 360.0 / qAbs(m_logRightX - m_logLeftX); + a = qPow(m_logBaseX, m_logLeftX + (a / deltaX)); + + const qreal deltaY = m_radius / qAbs(m_logOuterY - m_logInnerY); + qreal r = qPow(m_logBaseY, m_logInnerY + (line.length() / deltaY)); + + return QPointF(a, r); +} + +bool LogXLogYPolarDomain::attachAxis(QAbstractAxis *axis) +{ + AbstractDomain::attachAxis(axis); + QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); + + if (logAxis && logAxis->orientation() == Qt::Horizontal) { + QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); + handleHorizontalAxisBaseChanged(logAxis->base()); + } else if (logAxis && logAxis->orientation() == Qt::Vertical){ + QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); + handleVerticalAxisBaseChanged(logAxis->base()); + } + + return true; +} + +bool LogXLogYPolarDomain::detachAxis(QAbstractAxis *axis) +{ + AbstractDomain::detachAxis(axis); + QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); + + if (logAxis && logAxis->orientation() == Qt::Horizontal) + QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); + else if (logAxis && logAxis->orientation() == Qt::Vertical) + QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); + + return true; +} + +void LogXLogYPolarDomain::handleHorizontalAxisBaseChanged(qreal baseX) +{ + m_logBaseX = baseX; + qreal logMinX = log10(m_minX) / log10(m_logBaseX); + qreal logMaxX = log10(m_maxX) / log10(m_logBaseX); + m_logLeftX = logMinX < logMaxX ? logMinX : logMaxX; + m_logRightX = logMinX > logMaxX ? logMinX : logMaxX; + emit updated(); +} + +void LogXLogYPolarDomain::handleVerticalAxisBaseChanged(qreal baseY) +{ + m_logBaseY = baseY; + qreal logMinY = log10(m_minY) / log10(m_logBaseY); + qreal logMaxY = log10(m_maxY) / log10(m_logBaseY); + m_logInnerY = logMinY < logMaxY ? logMinY : logMaxY; + m_logOuterY = logMinY > logMaxY ? logMinY : logMaxY; + emit updated(); +} + +// operators + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const LogXLogYPolarDomain &domain1, const LogXLogYPolarDomain &domain2) +{ + return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) + && qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) + && qFuzzyIsNull(domain1.m_minX - domain2.m_minX) + && qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); +} + + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const LogXLogYPolarDomain &domain1, const LogXLogYPolarDomain &domain2) +{ + return !(domain1 == domain2); +} + + +QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const LogXLogYPolarDomain &domain) +{ + dbg.nospace() << "AbstractDomain(" << domain.m_minX << ',' << domain.m_maxX << ',' << domain.m_minY << ',' << domain.m_maxY << ')' << domain.m_size; + return dbg.maybeSpace(); +} + +#include "moc_logxlogypolardomain_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/domain/logxlogypolardomain_p.h b/src/domain/logxlogypolardomain_p.h new file mode 100644 index 00000000..106f58a6 --- /dev/null +++ b/src/domain/logxlogypolardomain_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef LOGXLOGYPOLARDOMAIN_H +#define LOGXLOGYPOLARDOMAIN_H +#include "polardomain_p.h" +#include <QRectF> +#include <QSizeF> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QTCOMMERCIALCHART_AUTOTEST_EXPORT LogXLogYPolarDomain: public PolarDomain +{ + Q_OBJECT +public: + explicit LogXLogYPolarDomain(QObject *object = 0); + virtual ~LogXLogYPolarDomain(); + + DomainType type() { return AbstractDomain::LogXLogYPolarDomain; } + + void setRange(qreal minX, qreal maxX, qreal minY, qreal maxY); + + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const LogXLogYPolarDomain &domain1, const LogXLogYPolarDomain &domain2); + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const LogXLogYPolarDomain &domain1, const LogXLogYPolarDomain &domain2); + friend QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const LogXLogYPolarDomain &domain); + + void zoomIn(const QRectF &rect); + void zoomOut(const QRectF &rect); + void move(qreal dx, qreal dy); + + QPointF calculateDomainPoint(const QPointF &point) const; + + bool attachAxis(QAbstractAxis *axis); + bool detachAxis(QAbstractAxis *axis); + +public Q_SLOTS: + void handleVerticalAxisBaseChanged(qreal baseY); + void handleHorizontalAxisBaseChanged(qreal baseX); + +protected: + qreal toAngularCoordinate(qreal value, bool &ok) const; + qreal toRadialCoordinate(qreal value, bool &ok) const; + +private: + qreal m_logLeftX; + qreal m_logRightX; + qreal m_logBaseX; + qreal m_logInnerY; + qreal m_logOuterY; + qreal m_logBaseY; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // LOGXLOGYPOLARDOMAIN_H diff --git a/src/domain/logxydomain.cpp b/src/domain/logxydomain.cpp index ffa739bf..da6e210a 100644 --- a/src/domain/logxydomain.cpp +++ b/src/domain/logxydomain.cpp @@ -42,6 +42,8 @@ void LogXYDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) bool axisXChanged = false; bool axisYChanged = false; + adjustLogDomainRanges(minX, maxX); + if (!qFuzzyCompare(m_minX, minX) || !qFuzzyCompare(m_maxX, maxX)) { m_minX = minX; m_maxX = maxX; @@ -58,7 +60,7 @@ void LogXYDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) m_minY = minY; m_maxY = maxY; axisYChanged = true; - if(!m_signalsBlocked) + if (!m_signalsBlocked) emit rangeVerticalChanged(m_minY, m_maxY); } @@ -137,13 +139,13 @@ QPointF LogXYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) cons ok = true; return QPointF(x, y); } else { - qWarning() << "Logarithm of negative value is undefined. Empty layout returned"; + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; ok = false; return QPointF(); } } -QVector<QPointF> LogXYDomain::calculateGeometryPoints(const QList<QPointF>& vector) const +QVector<QPointF> LogXYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const { const qreal deltaX = m_size.width() / (m_logRightX - m_logLeftX); const qreal deltaY = m_size.height() / (m_maxY - m_minY); @@ -158,7 +160,7 @@ QVector<QPointF> LogXYDomain::calculateGeometryPoints(const QList<QPointF>& vect result[i].setX(x); result[i].setY(y); } else { - qWarning() << "Logarithm of negative value is undefined. Empty layout returned"; + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; return QVector<QPointF>(); } } @@ -174,12 +176,12 @@ QPointF LogXYDomain::calculateDomainPoint(const QPointF &point) const return QPointF(x, y); } -bool LogXYDomain::attachAxis(QAbstractAxis* axis) +bool LogXYDomain::attachAxis(QAbstractAxis *axis) { AbstractDomain::attachAxis(axis); QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); - if(logAxis && logAxis->orientation()==Qt::Horizontal) { + if (logAxis && logAxis->orientation() == Qt::Horizontal) { QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); handleHorizontalAxisBaseChanged(logAxis->base()); } @@ -187,12 +189,12 @@ bool LogXYDomain::attachAxis(QAbstractAxis* axis) return true; } -bool LogXYDomain::detachAxis(QAbstractAxis* axis) +bool LogXYDomain::detachAxis(QAbstractAxis *axis) { AbstractDomain::detachAxis(axis); QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); - if(logAxis && logAxis->orientation()==Qt::Horizontal) + if (logAxis && logAxis->orientation() == Qt::Horizontal) QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); return true; @@ -212,10 +214,10 @@ void LogXYDomain::handleHorizontalAxisBaseChanged(qreal baseX) bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const LogXYDomain &domain1, const LogXYDomain &domain2) { - return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) && - qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) && - qFuzzyIsNull(domain1.m_minX - domain2.m_minX) && - qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); + return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) + && qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) + && qFuzzyIsNull(domain1.m_minX - domain2.m_minX) + && qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); } diff --git a/src/domain/logxydomain_p.h b/src/domain/logxydomain_p.h index cbc59e18..2f0e49ea 100644 --- a/src/domain/logxydomain_p.h +++ b/src/domain/logxydomain_p.h @@ -56,10 +56,10 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF>& vector) const; + QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; - bool attachAxis(QAbstractAxis* axis); - bool detachAxis(QAbstractAxis* axis); + bool attachAxis(QAbstractAxis *axis); + bool detachAxis(QAbstractAxis *axis); public Q_SLOTS: void handleHorizontalAxisBaseChanged(qreal baseX); diff --git a/src/domain/logxypolardomain.cpp b/src/domain/logxypolardomain.cpp new file mode 100644 index 00000000..b08f6a04 --- /dev/null +++ b/src/domain/logxypolardomain.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "logxypolardomain_p.h" +#include "qabstractaxis_p.h" +#include "qlogvalueaxis.h" +#include <qmath.h> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +LogXYPolarDomain::LogXYPolarDomain(QObject *parent) + : PolarDomain(parent), + m_logLeftX(0), + m_logRightX(1), + m_logBaseX(10) +{ +} + +LogXYPolarDomain::~LogXYPolarDomain() +{ +} + +void LogXYPolarDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) +{ + bool axisXChanged = false; + bool axisYChanged = false; + + adjustLogDomainRanges(minX, maxX); + + if (!qFuzzyCompare(m_minX, minX) || !qFuzzyCompare(m_maxX, maxX)) { + m_minX = minX; + m_maxX = maxX; + axisXChanged = true; + qreal logMinX = log10(m_minX) / log10(m_logBaseX); + qreal logMaxX = log10(m_maxX) / log10(m_logBaseX); + m_logLeftX = logMinX < logMaxX ? logMinX : logMaxX; + m_logRightX = logMinX > logMaxX ? logMinX : logMaxX; + if (!m_signalsBlocked) + emit rangeHorizontalChanged(m_minX, m_maxX); + } + + if (!qFuzzyIsNull(m_minY - minY) || !qFuzzyIsNull(m_maxY - maxY)) { + m_minY = minY; + m_maxY = maxY; + axisYChanged = true; + if (!m_signalsBlocked) + emit rangeVerticalChanged(m_minY, m_maxY); + } + + if (axisXChanged || axisYChanged) + emit updated(); +} + +void LogXYPolarDomain::zoomIn(const QRectF &rect) +{ + qreal logLeftX = rect.left() * (m_logRightX - m_logLeftX) / m_size.width() + m_logLeftX; + qreal logRightX = rect.right() * (m_logRightX - m_logLeftX) / m_size.width() + m_logLeftX; + qreal leftX = qPow(m_logBaseX, logLeftX); + qreal rightX = qPow(m_logBaseX, logRightX); + qreal minX = leftX < rightX ? leftX : rightX; + qreal maxX = leftX > rightX ? leftX : rightX; + + qreal dy = spanY() / m_size.height(); + qreal minY = m_minY; + qreal maxY = m_maxY; + + minY = maxY - dy * rect.bottom(); + maxY = maxY - dy * rect.top(); + + setRange(minX, maxX, minY, maxY); +} + +void LogXYPolarDomain::zoomOut(const QRectF &rect) +{ + const qreal factorX = m_size.width() / rect.width(); + + qreal logLeftX = m_logLeftX + (m_logRightX - m_logLeftX) / 2.0 * (1.0 - factorX); + qreal logRIghtX = m_logLeftX + (m_logRightX - m_logLeftX) / 2.0 * (1.0 + factorX); + qreal leftX = qPow(m_logBaseX, logLeftX); + qreal rightX = qPow(m_logBaseX, logRIghtX); + qreal minX = leftX < rightX ? leftX : rightX; + qreal maxX = leftX > rightX ? leftX : rightX; + + qreal dy = spanY() / rect.height(); + qreal minY = m_minY; + qreal maxY = m_maxY; + + maxY = minY + dy * rect.bottom(); + minY = maxY - dy * m_size.height(); + + setRange(minX, maxX, minY, maxY); +} + +void LogXYPolarDomain::move(qreal dx, qreal dy) +{ + qreal stepX = dx * (m_logRightX - m_logLeftX) / m_size.width(); + qreal leftX = qPow(m_logBaseX, m_logLeftX + stepX); + qreal rightX = qPow(m_logBaseX, m_logRightX + stepX); + qreal minX = leftX < rightX ? leftX : rightX; + qreal maxX = leftX > rightX ? leftX : rightX; + + qreal y = spanY() / m_radius; + qreal minY = m_minY; + qreal maxY = m_maxY; + + if (dy != 0) { + minY = minY + y * dy; + maxY = maxY + y * dy; + } + setRange(minX, maxX, minY, maxY); +} + +qreal LogXYPolarDomain::toAngularCoordinate(qreal value, bool &ok) const +{ + qreal retVal; + if (value <= 0) { + ok = false; + retVal = 0.0; + } else { + ok = true; + const qreal tickSpan = 360.0 / qAbs(m_logRightX - m_logLeftX); + const qreal logValue = log10(value) / log10(m_logBaseX); + const qreal valueDelta = logValue - m_logLeftX; + + retVal = valueDelta * tickSpan; + } + return retVal; +} + +qreal LogXYPolarDomain::toRadialCoordinate(qreal value, bool &ok) const +{ + ok = true; + if (value < m_minY) + value = m_minY; + + // Dont limit the max. The drawing should clip the stuff that goes out of the grid + qreal f = (value - m_minY) / (m_maxY - m_minY); + + return f * m_radius; +} + +QPointF LogXYPolarDomain::calculateDomainPoint(const QPointF &point) const +{ + if (point == m_center) + return QPointF(0.0, m_minY); + + QLineF line(m_center, point); + qreal a = 90.0 - line.angle(); + if (a < 0.0) + a += 360.0; + + const qreal deltaX = 360.0 / qAbs(m_logRightX - m_logLeftX); + a = qPow(m_logBaseX, m_logLeftX + (a / deltaX)); + + qreal r = m_minY + ((m_maxY - m_minY) * (line.length() / m_radius)); + + return QPointF(a, r); +} + +bool LogXYPolarDomain::attachAxis(QAbstractAxis *axis) +{ + AbstractDomain::attachAxis(axis); + QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); + + if (logAxis && logAxis->orientation() == Qt::Horizontal) { + QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); + handleHorizontalAxisBaseChanged(logAxis->base()); + } + + return true; +} + +bool LogXYPolarDomain::detachAxis(QAbstractAxis *axis) +{ + AbstractDomain::detachAxis(axis); + QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); + + if (logAxis && logAxis->orientation() == Qt::Horizontal) + QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleHorizontalAxisBaseChanged(qreal))); + + return true; +} + +void LogXYPolarDomain::handleHorizontalAxisBaseChanged(qreal baseX) +{ + m_logBaseX = baseX; + qreal logMinX = log10(m_minX) / log10(m_logBaseX); + qreal logMaxX = log10(m_maxX) / log10(m_logBaseX); + m_logLeftX = logMinX < logMaxX ? logMinX : logMaxX; + m_logRightX = logMinX > logMaxX ? logMinX : logMaxX; + emit updated(); +} + +// operators + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const LogXYPolarDomain &domain1, const LogXYPolarDomain &domain2) +{ + return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) + && qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) + && qFuzzyIsNull(domain1.m_minX - domain2.m_minX) + && qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); +} + + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const LogXYPolarDomain &domain1, const LogXYPolarDomain &domain2) +{ + return !(domain1 == domain2); +} + + +QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const LogXYPolarDomain &domain) +{ + dbg.nospace() << "AbstractDomain(" << domain.m_minX << ',' << domain.m_maxX << ',' << domain.m_minY << ',' << domain.m_maxY << ')' << domain.m_size; + return dbg.maybeSpace(); +} + +#include "moc_logxypolardomain_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/domain/logxypolardomain_p.h b/src/domain/logxypolardomain_p.h new file mode 100644 index 00000000..c7468ab9 --- /dev/null +++ b/src/domain/logxypolardomain_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef LOGXYPOLARDOMAIN_H +#define LOGXYPOLARDOMAIN_H +#include "polardomain_p.h" +#include <QRectF> +#include <QSizeF> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QTCOMMERCIALCHART_AUTOTEST_EXPORT LogXYPolarDomain: public PolarDomain +{ + Q_OBJECT +public: + explicit LogXYPolarDomain(QObject *object = 0); + virtual ~LogXYPolarDomain(); + + DomainType type() { return AbstractDomain::LogXYPolarDomain; } + + void setRange(qreal minX, qreal maxX, qreal minY, qreal maxY); + + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const LogXYPolarDomain &domain1, const LogXYPolarDomain &domain2); + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const LogXYPolarDomain &domain1, const LogXYPolarDomain &domain2); + friend QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const LogXYPolarDomain &domain); + + void zoomIn(const QRectF &rect); + void zoomOut(const QRectF &rect); + void move(qreal dx, qreal dy); + + QPointF calculateDomainPoint(const QPointF &point) const; + + bool attachAxis(QAbstractAxis *axis); + bool detachAxis(QAbstractAxis *axis); + +public Q_SLOTS: + void handleHorizontalAxisBaseChanged(qreal baseX); + +protected: + qreal toAngularCoordinate(qreal value, bool &ok) const; + qreal toRadialCoordinate(qreal value, bool &ok) const; + +private: + qreal m_logLeftX; + qreal m_logRightX; + qreal m_logBaseX; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // LOGXYPOLARDOMAIN_H diff --git a/src/domain/polardomain.cpp b/src/domain/polardomain.cpp new file mode 100644 index 00000000..d9a9d48f --- /dev/null +++ b/src/domain/polardomain.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polardomain_p.h" +#include "qabstractaxis_p.h" +#include <qmath.h> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +PolarDomain::PolarDomain(QObject *parent) + : AbstractDomain(parent) +{ +} + +PolarDomain::~PolarDomain() +{ +} + +void PolarDomain::setSize(const QSizeF &size) +{ + Q_ASSERT(size.width() == size.height()); + m_radius = size.height() / 2; + m_center = QPointF(m_radius, m_radius); + AbstractDomain::setSize(size); +} + +QPointF PolarDomain::calculateGeometryPoint(const QPointF &point, bool &ok) const +{ + qreal r; + qreal a = toAngularCoordinate(point.x(), ok); + if (ok) + r = toRadialCoordinate(point.y(), ok); + if (ok) { + return m_center + polarCoordinateToPoint(a, r); + } else { + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; + return QPointF(); + } +} + +QVector<QPointF> PolarDomain::calculateGeometryPoints(const QList<QPointF> &vector) const +{ + QVector<QPointF> result; + result.resize(vector.count()); + bool ok; + qreal r; + qreal a; + + for (int i = 0; i < vector.count(); ++i) { + a = toAngularCoordinate(vector[i].x(), ok); + if (ok) + r = toRadialCoordinate(vector[i].y(), ok); + if (ok) { + result[i] = m_center + polarCoordinateToPoint(a, r); + } else { + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; + return QVector<QPointF>(); + } + } + + return result; +} + +QPointF PolarDomain::polarCoordinateToPoint(qreal angularCoordinate, qreal radialCoordinate) const +{ + qreal dx = qSin(angularCoordinate * (M_PI / 180)) * radialCoordinate; + qreal dy = qCos(angularCoordinate * (M_PI / 180)) * radialCoordinate; + + return QPointF(dx, -dy); +} + +#include "moc_polardomain_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/domain/polardomain_p.h b/src/domain/polardomain_p.h new file mode 100644 index 00000000..81e921d5 --- /dev/null +++ b/src/domain/polardomain_p.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARDOMAIN_H +#define POLARDOMAIN_H +#include "abstractdomain_p.h" +#include <QRectF> +#include <QSizeF> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QTCOMMERCIALCHART_AUTOTEST_EXPORT PolarDomain: public AbstractDomain +{ + Q_OBJECT +public: + explicit PolarDomain(QObject *object = 0); + virtual ~PolarDomain(); + + void setSize(const QSizeF &size); + + QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; + QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; + + virtual qreal toAngularCoordinate(qreal value, bool &ok) const = 0; + virtual qreal toRadialCoordinate(qreal value, bool &ok) const = 0; + +protected: + QPointF polarCoordinateToPoint(qreal angularCoordinate, qreal radialCoordinate) const; + + QPointF m_center; + qreal m_radius; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARDOMAIN_H diff --git a/src/domain/xlogydomain.cpp b/src/domain/xlogydomain.cpp index 42e7edae..850eb99b 100644 --- a/src/domain/xlogydomain.cpp +++ b/src/domain/xlogydomain.cpp @@ -42,6 +42,8 @@ void XLogYDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) bool axisXChanged = false; bool axisYChanged = false; + adjustLogDomainRanges(minY, maxY); + if (!qFuzzyIsNull(m_minX - minX) || !qFuzzyIsNull(m_maxX - maxX)) { m_minX = minX; m_maxX = maxX; @@ -58,7 +60,7 @@ void XLogYDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) qreal logMaxY = log10(m_maxY) / log10(m_logBaseY); m_logLeftY = logMinY < logMaxY ? logMinY : logMaxY; m_logRightY = logMinY > logMaxY ? logMinY : logMaxY; - if(!m_signalsBlocked) + if (!m_signalsBlocked) emit rangeVerticalChanged(m_minY, m_maxY); } @@ -136,13 +138,13 @@ QPointF XLogYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) cons ok = true; return QPointF(x, y); } else { - qWarning() << "Logarithm of negative value is undefined. Empty layout returned"; + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; ok = false; return QPointF(); } } -QVector<QPointF> XLogYDomain::calculateGeometryPoints(const QList<QPointF>& vector) const +QVector<QPointF> XLogYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const { const qreal deltaX = m_size.width() / (m_maxX - m_minX); const qreal deltaY = m_size.height() / qAbs(m_logRightY - m_logLeftY); @@ -157,7 +159,7 @@ QVector<QPointF> XLogYDomain::calculateGeometryPoints(const QList<QPointF>& vect result[i].setX(x); result[i].setY(y); } else { - qWarning() << "Logarithm of negative value is undefined. Empty layout returned"; + qWarning() << "Logarithm of negative value is undefined. Empty layout returned."; return QVector<QPointF>(); } } @@ -173,22 +175,22 @@ QPointF XLogYDomain::calculateDomainPoint(const QPointF &point) const return QPointF(x, y); } -bool XLogYDomain::attachAxis(QAbstractAxis* axis) +bool XLogYDomain::attachAxis(QAbstractAxis *axis) { QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); - if(logAxis && logAxis->orientation()==Qt::Vertical){ + if (logAxis && logAxis->orientation() == Qt::Vertical) { QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); handleVerticalAxisBaseChanged(logAxis->base()); } return AbstractDomain::attachAxis(axis); } -bool XLogYDomain::detachAxis(QAbstractAxis* axis) +bool XLogYDomain::detachAxis(QAbstractAxis *axis) { QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); - if(logAxis && logAxis->orientation()==Qt::Vertical) + if (logAxis && logAxis->orientation() == Qt::Vertical) QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); return AbstractDomain::detachAxis(axis); @@ -208,10 +210,10 @@ void XLogYDomain::handleVerticalAxisBaseChanged(qreal baseY) bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const XLogYDomain &domain1, const XLogYDomain &domain2) { - return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) && - qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) && - qFuzzyIsNull(domain1.m_minX - domain2.m_minX) && - qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); + return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) + && qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) + && qFuzzyIsNull(domain1.m_minX - domain2.m_minX) + && qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); } diff --git a/src/domain/xlogydomain_p.h b/src/domain/xlogydomain_p.h index 88dd989f..68ac6f81 100644 --- a/src/domain/xlogydomain_p.h +++ b/src/domain/xlogydomain_p.h @@ -56,10 +56,10 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF>& vector) const; + QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; - bool attachAxis(QAbstractAxis* axis); - bool detachAxis(QAbstractAxis* axis); + bool attachAxis(QAbstractAxis *axis); + bool detachAxis(QAbstractAxis *axis); public Q_SLOTS: void handleVerticalAxisBaseChanged(qreal baseY); diff --git a/src/domain/xlogypolardomain.cpp b/src/domain/xlogypolardomain.cpp new file mode 100644 index 00000000..730a9dcd --- /dev/null +++ b/src/domain/xlogypolardomain.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "xlogypolardomain_p.h" +#include "qabstractaxis_p.h" +#include "qlogvalueaxis.h" +#include <qmath.h> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +XLogYPolarDomain::XLogYPolarDomain(QObject *parent) + : PolarDomain(parent), + m_logInnerY(0), + m_logOuterY(1), + m_logBaseY(10) +{ +} + +XLogYPolarDomain::~XLogYPolarDomain() +{ +} + +void XLogYPolarDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) +{ + bool axisXChanged = false; + bool axisYChanged = false; + + adjustLogDomainRanges(minY, maxY); + + if (!qFuzzyIsNull(m_minX - minX) || !qFuzzyIsNull(m_maxX - maxX)) { + m_minX = minX; + m_maxX = maxX; + axisXChanged = true; + if (!m_signalsBlocked) + emit rangeHorizontalChanged(m_minX, m_maxX); + } + + if (!qFuzzyIsNull(m_minY - minY) || !qFuzzyIsNull(m_maxY - maxY)) { + m_minY = minY; + m_maxY = maxY; + axisYChanged = true; + qreal logMinY = log10(m_minY) / log10(m_logBaseY); + qreal logMaxY = log10(m_maxY) / log10(m_logBaseY); + m_logInnerY = logMinY < logMaxY ? logMinY : logMaxY; + m_logOuterY = logMinY > logMaxY ? logMinY : logMaxY; + if (!m_signalsBlocked) + emit rangeVerticalChanged(m_minY, m_maxY); + } + + if (axisXChanged || axisYChanged) + emit updated(); +} + +void XLogYPolarDomain::zoomIn(const QRectF &rect) +{ + qreal dx = spanX() / m_size.width(); + qreal maxX = m_maxX; + qreal minX = m_minX; + + maxX = minX + dx * rect.right(); + minX = minX + dx * rect.left(); + + qreal logLeftY = m_logOuterY - rect.bottom() * (m_logOuterY - m_logInnerY) / m_size.height(); + qreal logRightY = m_logOuterY - rect.top() * (m_logOuterY - m_logInnerY) / m_size.height(); + qreal leftY = qPow(m_logBaseY, logLeftY); + qreal rightY = qPow(m_logBaseY, logRightY); + qreal minY = leftY < rightY ? leftY : rightY; + qreal maxY = leftY > rightY ? leftY : rightY; + + setRange(minX, maxX, minY, maxY); +} + +void XLogYPolarDomain::zoomOut(const QRectF &rect) +{ + qreal dx = spanX() / rect.width(); + qreal maxX = m_maxX; + qreal minX = m_minX; + + minX = maxX - dx * rect.right(); + maxX = minX + dx * m_size.width(); + + const qreal factorY = m_size.height() / rect.height(); + qreal newLogMinY = m_logInnerY + (m_logOuterY - m_logInnerY) / 2.0 * (1.0 - factorY); + qreal newLogMaxY = m_logInnerY + (m_logOuterY - m_logInnerY) / 2.0 * (1.0 + factorY); + qreal leftY = qPow(m_logBaseY, newLogMinY); + qreal rightY = qPow(m_logBaseY, newLogMaxY); + qreal minY = leftY < rightY ? leftY : rightY; + qreal maxY = leftY > rightY ? leftY : rightY; + + setRange(minX, maxX, minY, maxY); +} + +void XLogYPolarDomain::move(qreal dx, qreal dy) +{ + qreal x = spanX() / 360.0; + + qreal maxX = m_maxX; + qreal minX = m_minX; + + if (dx != 0) { + minX = minX + x * dx; + maxX = maxX + x * dx; + } + + qreal stepY = dy * (m_logOuterY - m_logInnerY) / m_radius; + qreal leftY = qPow(m_logBaseY, m_logInnerY + stepY); + qreal rightY = qPow(m_logBaseY, m_logOuterY + stepY); + qreal minY = leftY < rightY ? leftY : rightY; + qreal maxY = leftY > rightY ? leftY : rightY; + + setRange(minX, maxX, minY, maxY); +} + +qreal XLogYPolarDomain::toAngularCoordinate(qreal value, bool &ok) const +{ + ok = true; + qreal f = (value - m_minX) / (m_maxX - m_minX); + return f * 360.0; +} + +qreal XLogYPolarDomain::toRadialCoordinate(qreal value, bool &ok) const +{ + qreal retVal; + if (value <= 0) { + ok = false; + retVal = 0.0; + } else { + ok = true; + const qreal tickSpan = m_radius / qAbs(m_logOuterY - m_logInnerY); + const qreal logValue = log10(value) / log10(m_logBaseY); + const qreal valueDelta = logValue - m_logInnerY; + + retVal = valueDelta * tickSpan; + + if (retVal < 0.0) + retVal = 0.0; + } + return retVal; +} + +QPointF XLogYPolarDomain::calculateDomainPoint(const QPointF &point) const +{ + if (point == m_center) + return QPointF(0.0, m_minY); + + QLineF line(m_center, point); + qreal a = 90.0 - line.angle(); + if (a < 0.0) + a += 360.0; + a = ((a / 360.0) * (m_maxX - m_minX)) + m_minX; + + const qreal deltaY = m_radius / qAbs(m_logOuterY - m_logInnerY); + qreal r = qPow(m_logBaseY, m_logInnerY + (line.length() / deltaY)); + + return QPointF(a, r); +} + +bool XLogYPolarDomain::attachAxis(QAbstractAxis *axis) +{ + QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); + + if (logAxis && logAxis->orientation() == Qt::Vertical) { + QObject::connect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); + handleVerticalAxisBaseChanged(logAxis->base()); + } + return AbstractDomain::attachAxis(axis); +} + +bool XLogYPolarDomain::detachAxis(QAbstractAxis *axis) +{ + QLogValueAxis *logAxis = qobject_cast<QLogValueAxis *>(axis); + + if (logAxis && logAxis->orientation() == Qt::Vertical) + QObject::disconnect(logAxis, SIGNAL(baseChanged(qreal)), this, SLOT(handleVerticalAxisBaseChanged(qreal))); + + return AbstractDomain::detachAxis(axis); +} + +void XLogYPolarDomain::handleVerticalAxisBaseChanged(qreal baseY) +{ + m_logBaseY = baseY; + qreal logMinY = log10(m_minY) / log10(m_logBaseY); + qreal logMaxY = log10(m_maxY) / log10(m_logBaseY); + m_logInnerY = logMinY < logMaxY ? logMinY : logMaxY; + m_logOuterY = logMinY > logMaxY ? logMinY : logMaxY; + emit updated(); +} + +// operators + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const XLogYPolarDomain &domain1, const XLogYPolarDomain &domain2) +{ + return (qFuzzyIsNull(domain1.m_maxX - domain2.m_maxX) + && qFuzzyIsNull(domain1.m_maxY - domain2.m_maxY) + && qFuzzyIsNull(domain1.m_minX - domain2.m_minX) + && qFuzzyIsNull(domain1.m_minY - domain2.m_minY)); +} + + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const XLogYPolarDomain &domain1, const XLogYPolarDomain &domain2) +{ + return !(domain1 == domain2); +} + + +QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const XLogYPolarDomain &domain) +{ + dbg.nospace() << "AbstractDomain(" << domain.m_minX << ',' << domain.m_maxX << ',' << domain.m_minY << ',' << domain.m_maxY << ')' << domain.m_size; + return dbg.maybeSpace(); +} + +#include "moc_xlogypolardomain_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/domain/xlogypolardomain_p.h b/src/domain/xlogypolardomain_p.h new file mode 100644 index 00000000..3c3a45b7 --- /dev/null +++ b/src/domain/xlogypolardomain_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef XLOGYPOLARDOMAIN_H +#define XLOGYPOLARDOMAIN_H +#include "polardomain_p.h" +#include <QRectF> +#include <QSizeF> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QTCOMMERCIALCHART_AUTOTEST_EXPORT XLogYPolarDomain: public PolarDomain +{ + Q_OBJECT +public: + explicit XLogYPolarDomain(QObject *object = 0); + virtual ~XLogYPolarDomain(); + + DomainType type() { return AbstractDomain::XLogYPolarDomain; } + + void setRange(qreal minX, qreal maxX, qreal minY, qreal maxY); + + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const XLogYPolarDomain &domain1, const XLogYPolarDomain &domain2); + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const XLogYPolarDomain &domain1, const XLogYPolarDomain &domain2); + friend QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const XLogYPolarDomain &domain); + + void zoomIn(const QRectF &rect); + void zoomOut(const QRectF &rect); + void move(qreal dx, qreal dy); + + QPointF calculateDomainPoint(const QPointF &point) const; + + bool attachAxis(QAbstractAxis *axis); + bool detachAxis(QAbstractAxis *axis); + +public Q_SLOTS: + void handleVerticalAxisBaseChanged(qreal baseY); + +protected: + qreal toAngularCoordinate(qreal value, bool &ok) const; + qreal toRadialCoordinate(qreal value, bool &ok) const; + +private: + qreal m_logInnerY; + qreal m_logOuterY; + qreal m_logBaseY; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // XLOGYPOLARDOMAIN_H diff --git a/src/domain/xydomain.cpp b/src/domain/xydomain.cpp index 26f82a15..8ac1f0a0 100644 --- a/src/domain/xydomain.cpp +++ b/src/domain/xydomain.cpp @@ -126,7 +126,7 @@ QPointF XYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) const return QPointF(x, y); } -QVector<QPointF> XYDomain::calculateGeometryPoints(const QList<QPointF>& vector) const +QVector<QPointF> XYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const { const qreal deltaX = m_size.width() / (m_maxX - m_minX); const qreal deltaY = m_size.height() / (m_maxY - m_minY); @@ -156,10 +156,10 @@ QPointF XYDomain::calculateDomainPoint(const QPointF &point) const bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const XYDomain &domain1, const XYDomain &domain2) { - return (qFuzzyCompare(domain1.m_maxX, domain2.m_maxX) && - qFuzzyCompare(domain1.m_maxY, domain2.m_maxY) && - qFuzzyCompare(domain1.m_minX, domain2.m_minX) && - qFuzzyCompare(domain1.m_minY, domain2.m_minY)); + return (qFuzzyCompare(domain1.m_maxX, domain2.m_maxX) + && qFuzzyCompare(domain1.m_maxY, domain2.m_maxY) + && qFuzzyCompare(domain1.m_minX, domain2.m_minX) + && qFuzzyCompare(domain1.m_minY, domain2.m_minY)); } diff --git a/src/domain/xydomain_p.h b/src/domain/xydomain_p.h index 0a990388..657377b2 100644 --- a/src/domain/xydomain_p.h +++ b/src/domain/xydomain_p.h @@ -56,7 +56,7 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF>& vector) const; + QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/domain/xypolardomain.cpp b/src/domain/xypolardomain.cpp new file mode 100644 index 00000000..e9471293 --- /dev/null +++ b/src/domain/xypolardomain.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "xypolardomain_p.h" +#include "qabstractaxis_p.h" +#include <qmath.h> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +XYPolarDomain::XYPolarDomain(QObject *parent) + : PolarDomain(parent) +{ +} + +XYPolarDomain::~XYPolarDomain() +{ +} + +void XYPolarDomain::setRange(qreal minX, qreal maxX, qreal minY, qreal maxY) +{ + bool axisXChanged = false; + bool axisYChanged = false; + + if (!qFuzzyCompare(m_minX, minX) || !qFuzzyCompare(m_maxX, maxX)) { + m_minX = minX; + m_maxX = maxX; + axisXChanged = true; + if (!m_signalsBlocked) + emit rangeHorizontalChanged(m_minX, m_maxX); + } + + if (!qFuzzyCompare(m_minY, minY) || !qFuzzyCompare(m_maxY, maxY)) { + m_minY = minY; + m_maxY = maxY; + axisYChanged = true; + if (!m_signalsBlocked) + emit rangeVerticalChanged(m_minY, m_maxY); + } + + if (axisXChanged || axisYChanged) + emit updated(); +} + + +void XYPolarDomain::zoomIn(const QRectF &rect) +{ + qreal dx = spanX() / m_size.width(); + qreal dy = spanY() / m_size.height(); + + qreal maxX = m_maxX; + qreal minX = m_minX; + qreal minY = m_minY; + qreal maxY = m_maxY; + + maxX = minX + dx * rect.right(); + minX = minX + dx * rect.left(); + minY = maxY - dy * rect.bottom(); + maxY = maxY - dy * rect.top(); + + setRange(minX, maxX, minY, maxY); +} + +void XYPolarDomain::zoomOut(const QRectF &rect) +{ + qreal dx = spanX() / rect.width(); + qreal dy = spanY() / rect.height(); + + qreal maxX = m_maxX; + qreal minX = m_minX; + qreal minY = m_minY; + qreal maxY = m_maxY; + + minX = maxX - dx * rect.right(); + maxX = minX + dx * m_size.width(); + maxY = minY + dy * rect.bottom(); + minY = maxY - dy * m_size.height(); + + setRange(minX, maxX, minY, maxY); +} + +void XYPolarDomain::move(qreal dx, qreal dy) +{ + // One unit scrolls one degree angular and one pixel radial + qreal x = spanX() / 360.0; + qreal y = spanY() / m_radius; + + qreal maxX = m_maxX; + qreal minX = m_minX; + qreal minY = m_minY; + qreal maxY = m_maxY; + + if (dx != 0) { + minX = minX + x * dx; + maxX = maxX + x * dx; + } + if (dy != 0) { + minY = minY + y * dy; + maxY = maxY + y * dy; + } + setRange(minX, maxX, minY, maxY); +} + +QPointF XYPolarDomain::calculateDomainPoint(const QPointF &point) const +{ + if (point == m_center) + return QPointF(0.0, m_minX); + + QLineF line(m_center, point); + qreal a = 90.0 - line.angle(); + if (a < 0.0) + a += 360.0; + a = ((a / 360.0) * (m_maxX - m_minX)) + m_minX; + qreal r = m_minY + ((m_maxY - m_minY) * (line.length() / m_radius)); + return QPointF(a, r); +} + +qreal XYPolarDomain::toAngularCoordinate(qreal value, bool &ok) const +{ + ok = true; + qreal f = (value - m_minX) / (m_maxX - m_minX); + return f * 360.0; +} + +qreal XYPolarDomain::toRadialCoordinate(qreal value, bool &ok) const +{ + ok = true; + if (value < m_minY) + value = m_minY; + + // Dont limit the max. The drawing should clip the stuff that goes out of the grid + qreal f = (value - m_minY) / (m_maxY - m_minY); + + return f * m_radius; +} + +// operators + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const XYPolarDomain &domain1, const XYPolarDomain &domain2) +{ + return (qFuzzyCompare(domain1.m_maxX, domain2.m_maxX) + && qFuzzyCompare(domain1.m_maxY, domain2.m_maxY) + && qFuzzyCompare(domain1.m_minX, domain2.m_minX) + && qFuzzyCompare(domain1.m_minY, domain2.m_minY)); +} + + +bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const XYPolarDomain &domain1, const XYPolarDomain &domain2) +{ + return !(domain1 == domain2); +} + + +QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const XYPolarDomain &domain) +{ + dbg.nospace() << "AbstractDomain(" << domain.m_minX << ',' << domain.m_maxX << ',' << domain.m_minY << ',' << domain.m_maxY << ')' << domain.m_size; + return dbg.maybeSpace(); +} + +#include "moc_xypolardomain_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/domain/xypolardomain_p.h b/src/domain/xypolardomain_p.h new file mode 100644 index 00000000..662cc5ee --- /dev/null +++ b/src/domain/xypolardomain_p.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef XYPOLARDOMAIN_H +#define XYPOLARDOMAIN_H +#include "polardomain_p.h" +#include <QRectF> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QTCOMMERCIALCHART_AUTOTEST_EXPORT XYPolarDomain: public PolarDomain +{ + Q_OBJECT +public: + explicit XYPolarDomain(QObject *object = 0); + virtual ~XYPolarDomain(); + + DomainType type(){ return AbstractDomain::XYPolarDomain;} + + void setRange(qreal minX, qreal maxX, qreal minY, qreal maxY); + + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator== (const XYPolarDomain &Domain1, const XYPolarDomain &Domain2); + friend bool QTCOMMERCIALCHART_AUTOTEST_EXPORT operator!= (const XYPolarDomain &Domain1, const XYPolarDomain &Domain2); + friend QDebug QTCOMMERCIALCHART_AUTOTEST_EXPORT operator<<(QDebug dbg, const XYPolarDomain &AbstractDomain); + + void zoomIn(const QRectF &rect); + void zoomOut(const QRectF &rect); + void move(qreal dx, qreal dy); + + QPointF calculateDomainPoint(const QPointF &point) const; + +protected: + qreal toAngularCoordinate(qreal value, bool &ok) const; + qreal toRadialCoordinate(qreal value, bool &ok) const; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // XYPOLARDOMAIN_H diff --git a/src/layout/abstractchartlayout.cpp b/src/layout/abstractchartlayout.cpp new file mode 100644 index 00000000..092e8c0c --- /dev/null +++ b/src/layout/abstractchartlayout.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "abstractchartlayout_p.h" +#include "chartpresenter_p.h" +#include "qlegend_p.h" +#include "chartaxiselement_p.h" +#include "charttitle_p.h" +#include "chartbackground_p.h" +#include <QDebug> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +static const qreal golden_ratio = 0.4; + +AbstractChartLayout::AbstractChartLayout(ChartPresenter *presenter) + : m_presenter(presenter), + m_margins(20, 20, 20, 20), + m_minChartRect(0, 0, 200, 200) +{ +} + +AbstractChartLayout::~AbstractChartLayout() +{ +} + +void AbstractChartLayout::setGeometry(const QRectF &rect) +{ + if (!rect.isValid()) + return; + + QList<ChartAxisElement *> axes = m_presenter->axisItems(); + ChartTitle *title = m_presenter->titleElement(); + QLegend *legend = m_presenter->legend(); + ChartBackground *background = m_presenter->backgroundElement(); + + QRectF contentGeometry = calculateBackgroundGeometry(rect, background); + + contentGeometry = calculateContentGeometry(contentGeometry); + + if (title && title->isVisible()) + contentGeometry = calculateTitleGeometry(contentGeometry, title); + + if (legend->isAttachedToChart() && legend->isVisible()) + contentGeometry = calculateLegendGeometry(contentGeometry, legend); + + contentGeometry = calculateAxisGeometry(contentGeometry, axes); + + m_presenter->setGeometry(contentGeometry); + + QGraphicsLayout::setGeometry(rect); +} + +QRectF AbstractChartLayout::calculateContentGeometry(const QRectF &geometry) const +{ + return geometry.adjusted(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom()); +} + +QRectF AbstractChartLayout::calculateContentMinimum(const QRectF &minimum) const +{ + return minimum.adjusted(0, 0, m_margins.left() + m_margins.right(), m_margins.top() + m_margins.bottom()); +} + + +QRectF AbstractChartLayout::calculateBackgroundGeometry(const QRectF &geometry, ChartBackground *background) const +{ + qreal left; + qreal top; + qreal right; + qreal bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRectF backgroundGeometry = geometry.adjusted(left, top, -right, -bottom); + if (background) + background->setRect(backgroundGeometry); + return backgroundGeometry; +} + +QRectF AbstractChartLayout::calculateBackgroundMinimum(const QRectF &minimum) const +{ + qreal left; + qreal top; + qreal right; + qreal bottom; + getContentsMargins(&left, &top, &right, &bottom); + return minimum.adjusted(0, 0, left + right, top + bottom); +} + +QRectF AbstractChartLayout::calculateLegendGeometry(const QRectF &geometry, QLegend *legend) const +{ + QSizeF size = legend->effectiveSizeHint(Qt::PreferredSize, QSizeF(-1, -1)); + QRectF legendRect; + QRectF result; + + switch (legend->alignment()) { + case Qt::AlignTop: { + legendRect = QRectF(geometry.topLeft(), QSizeF(geometry.width(), size.height())); + result = geometry.adjusted(0, legendRect.height(), 0, 0); + break; + } + case Qt::AlignBottom: { + legendRect = QRectF(QPointF(geometry.left(), geometry.bottom() - size.height()), QSizeF(geometry.width(), size.height())); + result = geometry.adjusted(0, 0, 0, -legendRect.height()); + break; + } + case Qt::AlignLeft: { + qreal width = qMin(size.width(), geometry.width() * golden_ratio); + legendRect = QRectF(geometry.topLeft(), QSizeF(width, geometry.height())); + result = geometry.adjusted(width, 0, 0, 0); + break; + } + case Qt::AlignRight: { + qreal width = qMin(size.width(), geometry.width() * golden_ratio); + legendRect = QRectF(QPointF(geometry.right() - width, geometry.top()), QSizeF(width, geometry.height())); + result = geometry.adjusted(0, 0, -width, 0); + break; + } + default: { + legendRect = QRectF(0, 0, 0, 0); + result = geometry; + break; + } + } + + legend->setGeometry(legendRect); + + return result; +} + +QRectF AbstractChartLayout::calculateLegendMinimum(const QRectF &geometry, QLegend *legend) const +{ + QSizeF minSize = legend->effectiveSizeHint(Qt::MinimumSize, QSizeF(-1, -1)); + return geometry.adjusted(0, 0, minSize.width(), minSize.height()); +} + +QRectF AbstractChartLayout::calculateTitleGeometry(const QRectF &geometry, ChartTitle *title) const +{ + title->setGeometry(geometry); + QPointF center = geometry.center() - title->boundingRect().center(); + title->setPos(center.x(), title->pos().y()); + return geometry.adjusted(0, title->boundingRect().height()+1, 0, 0); +} + +QRectF AbstractChartLayout::calculateTitleMinimum(const QRectF &minimum, ChartTitle *title) const +{ + QSizeF min = title->sizeHint(Qt::MinimumSize); + return minimum.adjusted(0, 0, min.width(), min.height()); +} + +QSizeF AbstractChartLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_UNUSED(constraint); + if (which == Qt::MinimumSize) { + QList<ChartAxisElement *> axes = m_presenter->axisItems(); + ChartTitle *title = m_presenter->titleElement(); + QLegend *legend = m_presenter->legend(); + QRectF minimumRect(0, 0, 0, 0); + minimumRect = calculateBackgroundMinimum(minimumRect); + minimumRect = calculateContentMinimum(minimumRect); + minimumRect = calculateTitleMinimum(minimumRect, title); + minimumRect = calculateLegendMinimum(minimumRect, legend); + minimumRect = calculateAxisMinimum(minimumRect, axes); + return minimumRect.united(m_minChartRect).size().toSize(); + } + return QSize(-1, -1); +} + +void AbstractChartLayout::setMargins(const QMargins &margins) +{ + if (m_margins != margins) { + m_margins = margins; + updateGeometry(); + } +} + +QMargins AbstractChartLayout::margins() const +{ + return m_margins; +} + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/chartlayout_p.h b/src/layout/abstractchartlayout_p.h index 5b8d38a8..d28af3d2 100644 --- a/src/chartlayout_p.h +++ b/src/layout/abstractchartlayout_p.h @@ -27,51 +27,50 @@ // // We mean it. -#ifndef CHARTLAYOUT_H -#define CHARTLAYOUT_H +#ifndef ABSTRACTCHARTLAYOUT_H +#define ABSTRACTCHARTLAYOUT_H + #include <QGraphicsLayout> #include <QMargins> #include "qchartglobal.h" QTCOMMERCIALCHART_BEGIN_NAMESPACE -class ChartPresenter; class ChartTitle; +class ChartAxisElement; +class ChartPresenter; class QLegend; -class ChartAxis; class ChartBackground; -class ChartLayout : public QGraphicsLayout +class AbstractChartLayout : public QGraphicsLayout { public: + AbstractChartLayout(ChartPresenter *presenter); + virtual ~AbstractChartLayout(); - ChartLayout(ChartPresenter *presenter); - virtual ~ChartLayout(); + virtual void setMargins(const QMargins &margins); + virtual QMargins margins() const; + virtual void setGeometry(const QRectF &rect); - void setMargins(const QMargins &margins); - QMargins margins() const; +protected: + virtual QRectF calculateBackgroundGeometry(const QRectF &geometry, ChartBackground *background) const; + virtual QRectF calculateBackgroundMinimum(const QRectF &minimum) const; + virtual QRectF calculateContentGeometry(const QRectF &geometry) const; + virtual QRectF calculateContentMinimum(const QRectF &minimum) const; + virtual QRectF calculateTitleGeometry(const QRectF &geometry, ChartTitle *title) const; + virtual QRectF calculateTitleMinimum(const QRectF &minimum, ChartTitle *title) const; + virtual QRectF calculateLegendGeometry(const QRectF &geometry, QLegend *legend) const; + virtual QRectF calculateLegendMinimum(const QRectF &minimum, QLegend *legend) const; - void setGeometry(const QRectF &rect); + virtual QRectF calculateAxisGeometry(const QRectF &geometry, const QList<ChartAxisElement *>& axes) const = 0; + virtual QRectF calculateAxisMinimum(const QRectF &minimum, const QList<ChartAxisElement *>& axes) const = 0; -protected: + // from QGraphicsLayout QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; int count() const { return 0; } QGraphicsLayoutItem *itemAt(int) const { return 0; }; void removeAt(int) {}; -private: - QRectF calculateBackgroundGeometry(const QRectF &geometry, ChartBackground *background) const; - QRectF calculateContentGeometry(const QRectF &geometry) const; - QRectF calculateTitleGeometry(const QRectF &geometry, ChartTitle *title) const; - QRectF calculateLegendGeometry(const QRectF &geometry, QLegend *legend) const; - QRectF calculateAxisGeometry(const QRectF &geometry, const QList<ChartAxis *>& axes) const; - QRectF calculateBackgroundMinimum(const QRectF &minimum) const; - QRectF calculateContentMinimum(const QRectF &minimum) const; - QRectF calculateTitleMinimum(const QRectF &minimum, ChartTitle *title) const; - QRectF calculateAxisMinimum(const QRectF &minimum, const QList<ChartAxis *>& axes) const; - QRectF calculateLegendMinimum(const QRectF &minimum, QLegend *legend) const; - -private: ChartPresenter *m_presenter; QMargins m_margins; QRectF m_minChartRect; @@ -80,4 +79,4 @@ private: QTCOMMERCIALCHART_END_NAMESPACE -#endif +#endif // ABSTRACTCHARTLAYOUT_H diff --git a/src/chartlayout.cpp b/src/layout/cartesianchartlayout.cpp index bd885ba9..f5317752 100644 --- a/src/chartlayout.cpp +++ b/src/layout/cartesianchartlayout.cpp @@ -18,88 +18,25 @@ ** ****************************************************************************/ -#include "chartlayout_p.h" +#include "cartesianchartlayout_p.h" #include "chartpresenter_p.h" -#include "qlegend_p.h" -#include "chartaxis_p.h" -#include "charttitle_p.h" -#include "chartbackground_p.h" +#include "chartaxiselement_p.h" #include <QDebug> QTCOMMERCIALCHART_BEGIN_NAMESPACE static const qreal maxAxisPortion = 0.4; -ChartLayout::ChartLayout(ChartPresenter *presenter) - : m_presenter(presenter), - m_margins(20, 20, 20, 20), - m_minChartRect(0, 0, 200, 200) +CartesianChartLayout::CartesianChartLayout(ChartPresenter *presenter) + : AbstractChartLayout(presenter) { - -} - -ChartLayout::~ChartLayout() -{ - -} - -void ChartLayout::setGeometry(const QRectF &rect) -{ - if (!rect.isValid()) - return; - - QList<ChartAxis *> axes = m_presenter->axisItems(); - ChartTitle *title = m_presenter->titleElement(); - QLegend *legend = m_presenter->legend(); - ChartBackground *background = m_presenter->backgroundElement(); - - QRectF contentGeometry = calculateBackgroundGeometry(rect, background); - - contentGeometry = calculateContentGeometry(contentGeometry); - - if (title && title->isVisible()) - contentGeometry = calculateTitleGeometry(contentGeometry, title); - - if (legend->isAttachedToChart() && legend->isVisible()) - contentGeometry = calculateLegendGeometry(contentGeometry, legend); - - contentGeometry = calculateAxisGeometry(contentGeometry, axes); - - m_presenter->setGeometry(contentGeometry); - - QGraphicsLayout::setGeometry(rect); } -QRectF ChartLayout::calculateContentGeometry(const QRectF &geometry) const +CartesianChartLayout::~CartesianChartLayout() { - return geometry.adjusted(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom()); } -QRectF ChartLayout::calculateContentMinimum(const QRectF &minimum) const -{ - return minimum.adjusted(0, 0, m_margins.left() + m_margins.right(), m_margins.top() + m_margins.bottom()); -} - - -QRectF ChartLayout::calculateBackgroundGeometry(const QRectF &geometry, ChartBackground *background) const -{ - qreal left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); - QRectF backgroundGeometry = geometry.adjusted(left, top, -right, -bottom); - if (background) - background->setRect(backgroundGeometry); - return backgroundGeometry; -} - -QRectF ChartLayout::calculateBackgroundMinimum(const QRectF &minimum) const -{ - qreal left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); - return minimum.adjusted(0, 0, left + right, top + bottom); -} - - -QRectF ChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<ChartAxis *>& axes) const +QRectF CartesianChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<ChartAxisElement *> &axes) const { QSizeF left(0,0); QSizeF minLeft(0,0); @@ -115,16 +52,17 @@ QRectF ChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<Ch int topCount = 0; int bottomCount = 0; - foreach (ChartAxis *axis , axes) { + foreach (ChartAxisElement *axis , axes) { if (!axis->isVisible()) continue; + QSizeF size = axis->effectiveSizeHint(Qt::PreferredSize); //this is used to get single thick font size QSizeF minSize = axis->effectiveSizeHint(Qt::MinimumSize); - switch (axis->alignment()) { + switch (axis->axis()->alignment()) { case Qt::AlignLeft: left.setWidth(left.width()+size.width()); left.setHeight(qMax(left.height(),size.height())); @@ -163,7 +101,7 @@ QRectF ChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<Ch } } - int totalVerticalAxes = leftCount + rightCount; + qreal totalVerticalAxes = leftCount + rightCount; qreal leftSqueezeRatio = 1.0; qreal rightSqueezeRatio = 1.0; qreal vratio = 0; @@ -186,7 +124,7 @@ QRectF ChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<Ch } } - int totalHorizontalAxes = topCount + bottomCount; + qreal totalHorizontalAxes = topCount + bottomCount; qreal topSqueezeRatio = 1.0; qreal bottomSqueezeRatio = 1.0; qreal hratio = 0; @@ -225,15 +163,14 @@ QRectF ChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<Ch qreal topOffset = 0; qreal bottomOffset = 0; - foreach(ChartElement *axisElement , axes) { - ChartAxis* axis = qobject_cast<ChartAxis*>(axisElement); + foreach (ChartAxisElement *axis , axes) { if (!axis->isVisible()) continue; QSizeF size = axis->effectiveSizeHint(Qt::PreferredSize); - switch(axis->alignment()){ + switch (axis->axis()->alignment()){ case Qt::AlignLeft:{ qreal width = size.width(); if (leftSqueezeRatio < 1.0) @@ -271,21 +208,20 @@ QRectF ChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<Ch return chartRect; } -QRectF ChartLayout::calculateAxisMinimum(const QRectF &minimum, const QList<ChartAxis *>& axes) const +QRectF CartesianChartLayout::calculateAxisMinimum(const QRectF &minimum, const QList<ChartAxisElement *> &axes) const { QSizeF left; QSizeF right; QSizeF bottom; QSizeF top; - foreach (ChartAxis *axis, axes) { - + foreach (ChartAxisElement *axis, axes) { QSizeF size = axis->effectiveSizeHint(Qt::MinimumSize); if (!axis->isVisible()) continue; - switch (axis->alignment()) { + switch (axis->axis()->alignment()) { case Qt::AlignLeft: left.setWidth(left.width() + size.width()); left.setHeight(qMax(left.height() * 2, size.height())); @@ -307,96 +243,4 @@ QRectF ChartLayout::calculateAxisMinimum(const QRectF &minimum, const QList<Char return minimum.adjusted(0, 0, left.width() + right.width() + qMax(top.width(), bottom.width()), top.height() + bottom.height() + qMax(left.height(), right.height())); } -QRectF ChartLayout::calculateLegendGeometry(const QRectF &geometry, QLegend *legend) const -{ - QSizeF size = legend->effectiveSizeHint(Qt::PreferredSize, QSizeF(-1, -1)); - QRectF legendRect; - QRectF result; - - switch (legend->alignment()) { - case Qt::AlignTop: { - legendRect = QRectF(geometry.topLeft(), QSizeF(geometry.width(), size.height())); - result = geometry.adjusted(0, legendRect.height(), 0, 0); - break; - } - case Qt::AlignBottom: { - legendRect = QRectF(QPointF(geometry.left(), geometry.bottom() - size.height()), QSizeF(geometry.width(), size.height())); - result = geometry.adjusted(0, 0, 0, -legendRect.height()); - break; - } - case Qt::AlignLeft: { - qreal width = qMin(size.width(), geometry.width() * maxAxisPortion); - legendRect = QRectF(geometry.topLeft(), QSizeF(width, geometry.height())); - result = geometry.adjusted(width, 0, 0, 0); - break; - } - case Qt::AlignRight: { - qreal width = qMin(size.width(), geometry.width() * maxAxisPortion); - legendRect = QRectF(QPointF(geometry.right() - width, geometry.top()), QSizeF(width, geometry.height())); - result = geometry.adjusted(0, 0, -width, 0); - break; - } - default: { - legendRect = QRectF(0, 0, 0, 0); - result = geometry; - break; - } - } - - legend->setGeometry(legendRect); - - return result; -} - -QRectF ChartLayout::calculateLegendMinimum(const QRectF &geometry, QLegend *legend) const -{ - QSizeF minSize = legend->effectiveSizeHint(Qt::MinimumSize, QSizeF(-1, -1)); - return geometry.adjusted(0, 0, minSize.width(), minSize.height()); -} - -QRectF ChartLayout::calculateTitleGeometry(const QRectF &geometry, ChartTitle *title) const -{ - title->setGeometry(geometry); - QPointF center = geometry.center() - title->boundingRect().center(); - title->setPos(center.x(),title->pos().y()); - return geometry.adjusted(0,title->boundingRect().height()+1,0,0); -} - -QRectF ChartLayout::calculateTitleMinimum(const QRectF &minimum, ChartTitle *title) const -{ - QSizeF min = title->sizeHint(Qt::MinimumSize); - return minimum.adjusted(0, 0, min.width(), min.height()); -} - -QSizeF ChartLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const -{ - Q_UNUSED(constraint); - if (which == Qt::MinimumSize) { - QList<ChartAxis *> axes = m_presenter->axisItems(); - ChartTitle *title = m_presenter->titleElement(); - QLegend *legend = m_presenter->legend(); - QRectF minimumRect(0, 0, 0, 0); - minimumRect = calculateBackgroundMinimum(minimumRect); - minimumRect = calculateContentMinimum(minimumRect); - minimumRect = calculateTitleMinimum(minimumRect, title); - minimumRect = calculateLegendMinimum(minimumRect, legend); - minimumRect = calculateAxisMinimum(minimumRect, axes); - return minimumRect.united(m_minChartRect).size().toSize(); - } - return QSize(-1, -1); -} - -void ChartLayout::setMargins(const QMargins &margins) -{ - if (m_margins != margins) { - m_margins = margins; - updateGeometry(); - } -} - -QMargins ChartLayout::margins() const -{ - return m_margins; -} - QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/layout/cartesianchartlayout_p.h b/src/layout/cartesianchartlayout_p.h new file mode 100644 index 00000000..88d540b0 --- /dev/null +++ b/src/layout/cartesianchartlayout_p.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CARTESIANCHARTLAYOUT_H +#define CARTESIANCHARTLAYOUT_H + +#include "abstractchartlayout_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class CartesianChartLayout : public AbstractChartLayout +{ +public: + CartesianChartLayout(ChartPresenter *presenter); + virtual ~CartesianChartLayout(); + + // from AbstractChartLayout + QRectF calculateAxisMinimum(const QRectF &minimum, const QList<ChartAxisElement *> &axes) const; + QRectF calculateAxisGeometry(const QRectF &geometry, const QList<ChartAxisElement *> &axes) const; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // CARTESIANCHARTLAYOUT_H diff --git a/src/layout/layout.pri b/src/layout/layout.pri new file mode 100644 index 00000000..159eab13 --- /dev/null +++ b/src/layout/layout.pri @@ -0,0 +1,12 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +SOURCES += \ + $$PWD/abstractchartlayout.cpp \ + $$PWD/cartesianchartlayout.cpp \ + $$PWD/polarchartlayout.cpp + +PRIVATE_HEADERS += \ + $$PWD/abstractchartlayout_p.h \ + $$PWD/cartesianchartlayout_p.h \ + $$PWD/polarchartlayout_p.h diff --git a/src/layout/polarchartlayout.cpp b/src/layout/polarchartlayout.cpp new file mode 100644 index 00000000..ad48244a --- /dev/null +++ b/src/layout/polarchartlayout.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "polarchartlayout_p.h" +#include "chartpresenter_p.h" +#include "polarchartaxis_p.h" +#include <QDebug> +#include <QFontMetrics> + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +static const qreal golden_ratio = 0.4; + +PolarChartLayout::PolarChartLayout(ChartPresenter *presenter) + : AbstractChartLayout(presenter) +{ +} + +PolarChartLayout::~PolarChartLayout() +{ +} + +QRectF PolarChartLayout::calculateAxisGeometry(const QRectF &geometry, const QList<ChartAxisElement *> &axes) const +{ + // How to handle multiple angular/radial axes? + qreal axisRadius = geometry.height() / 2.0; + if (geometry.width() < geometry.height()) + axisRadius = geometry.width() / 2.0; + + int titleHeight = 0; + foreach (ChartAxisElement *chartAxis, axes) { + if (!chartAxis->isVisible()) + continue; + + PolarChartAxis *polarChartAxis = static_cast<PolarChartAxis *>(chartAxis); + qreal radius = polarChartAxis->preferredAxisRadius(geometry.size()); + if (radius < axisRadius) + axisRadius = radius; + + if (chartAxis->axis()->orientation() == Qt::Horizontal + && chartAxis->axis()->isTitleVisible() + && !chartAxis->axis()->titleText().isEmpty()) { + // If axis has angular title, adjust geometry down by the space title takes + QFontMetrics titleMetrics(chartAxis->axis()->titleFont()); + titleHeight = (titleMetrics.boundingRect(chartAxis->axis()->titleText()).height() / 2) + chartAxis->titlePadding(); + } + } + + QRectF axisRect; + axisRect.setSize(QSizeF(axisRadius * 2.0, axisRadius * 2.0)); + axisRect.moveCenter(geometry.center()); + axisRect.adjust(0, titleHeight, 0, titleHeight); + + foreach (ChartAxisElement *chartAxis, axes) + chartAxis->setGeometry(axisRect, QRectF()); + + return axisRect; +} + +QRectF PolarChartLayout::calculateAxisMinimum(const QRectF &minimum, const QList<ChartAxisElement *> &axes) const +{ + Q_UNUSED(axes); + return minimum; +} + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/layout/polarchartlayout_p.h b/src/layout/polarchartlayout_p.h new file mode 100644 index 00000000..e8d5db44 --- /dev/null +++ b/src/layout/polarchartlayout_p.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the QtCommercial Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef POLARCHARTLAYOUT_H +#define POLARCHARTLAYOUT_H + +#include "abstractchartlayout_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class PolarChartLayout : public AbstractChartLayout +{ +public: + PolarChartLayout(ChartPresenter *presenter); + virtual ~PolarChartLayout(); + + // from AbstractChartLayout + QRectF calculateAxisMinimum(const QRectF &minimum, const QList<ChartAxisElement *> &axes) const; + QRectF calculateAxisGeometry(const QRectF &geometry, const QList<ChartAxisElement *> &axes) const; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // POLARCHARTLAYOUT_H diff --git a/src/legend/legendlayout.cpp b/src/legend/legendlayout.cpp index e405c793..6d5f8328 100644 --- a/src/legend/legendlayout.cpp +++ b/src/legend/legendlayout.cpp @@ -21,7 +21,7 @@ #include "legendlayout_p.h" #include "chartpresenter_p.h" #include "qlegend_p.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include "qlegendmarker_p.h" #include "legendmarkeritem_p.h" diff --git a/src/legend/qlegend.cpp b/src/legend/qlegend.cpp index 8a34afe3..1920d258 100644 --- a/src/legend/qlegend.cpp +++ b/src/legend/qlegend.cpp @@ -25,7 +25,7 @@ #include "qchart_p.h" #include "legendlayout_p.h" #include "chartpresenter_p.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include "qlegendmarker.h" #include "qlegendmarker_p.h" #include "legendmarkeritem_p.h" diff --git a/src/linechart/linechartitem.cpp b/src/linechart/linechartitem.cpp index 1f10d53e..dd5f540d 100644 --- a/src/linechart/linechartitem.cpp +++ b/src/linechart/linechartitem.cpp @@ -22,7 +22,7 @@ #include "qlineseries.h" #include "qlineseries_p.h" #include "chartpresenter_p.h" -#include "abstractdomain_p.h" +#include "polardomain_p.h" #include <QPainter> #include <QGraphicsSceneMouseEvent> @@ -30,10 +30,11 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE const qreal mouseEventMinWidth(12); -LineChartItem::LineChartItem(QLineSeries *series,QGraphicsItem* item) +LineChartItem::LineChartItem(QLineSeries *series, QGraphicsItem *item) : XYChart(series,item), m_series(series), - m_pointsVisible(false) + m_pointsVisible(false), + m_chartType(QChart::ChartTypeUndefined) { setAcceptHoverEvents(true); setZValue(ChartPresenter::LineChartZValue); @@ -50,54 +51,235 @@ QRectF LineChartItem::boundingRect() const QPainterPath LineChartItem::shape() const { - return m_path; + return m_shapePath; } void LineChartItem::updateGeometry() { m_points = geometryPoints(); + const QVector<QPointF> &points = m_points; - if (m_points.size() == 0) { + if (points.size() == 0) { prepareGeometryChange(); - m_path = QPainterPath(); + m_fullPath = QPainterPath(); m_linePath = QPainterPath(); m_rect = QRect(); return; } - QPainterPath linePath(m_points.at(0)); + QPainterPath linePath; + QPainterPath fullPath; + // Use worst case scenario to determine required margin. + qreal margin = m_linePen.width() * 1.42; - if (m_pointsVisible) { + // Area series use component line series that aren't necessarily added to the chart themselves, + // so check if chart type is forced before trying to obtain it from the chart. + QChart::ChartType chartType = m_chartType; + if (chartType == QChart::ChartTypeUndefined) + chartType = m_series->chart()->chartType(); + // For polar charts, we need special handling for angular (horizontal) + // points that are off-grid. + if (chartType == QChart::ChartTypePolar) { + QPainterPath linePathLeft; + QPainterPath linePathRight; + QPainterPath *currentSegmentPath = 0; + QPainterPath *previousSegmentPath = 0; + qreal minX = domain()->minX(); + qreal maxX = domain()->maxX(); + qreal minY = domain()->minY(); + QPointF currentSeriesPoint = m_series->pointAt(0); + QPointF currentGeometryPoint = points.at(0); + QPointF previousGeometryPoint = points.at(0); int size = m_linePen.width(); - linePath.addEllipse(m_points.at(0), size, size); - linePath.moveTo(m_points.at(0)); - for (int i = 1; i < m_points.size(); i++) { - linePath.lineTo(m_points.at(i)); - linePath.addEllipse(m_points.at(i), size, size); - linePath.moveTo(m_points.at(i)); + bool pointOffGrid = false; + bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX); + + qreal domainRadius = domain()->size().height() / 2.0; + const QPointF centerPoint(domainRadius, domainRadius); + + if (!previousPointWasOffGrid) { + fullPath.moveTo(points.at(0)); + if (m_pointsVisible && currentSeriesPoint.y() >= minY) { + // Do not draw ellipses for points below minimum Y. + linePath.addEllipse(points.at(0), size, size); + fullPath.addEllipse(points.at(0), size, size); + linePath.moveTo(points.at(0)); + fullPath.moveTo(points.at(0)); + } } - } else { - for (int i = 1; i < m_points.size(); i++) - linePath.lineTo(m_points.at(i)); - } + qreal leftMarginLine = centerPoint.x() - margin; + qreal rightMarginLine = centerPoint.x() + margin; + qreal horizontal = centerPoint.y(); - m_linePath = linePath; + for (int i = 1; i < points.size(); i++) { + // Interpolating line fragments would be ugly when thick pen is used, + // so we work around it by utilizing three separate + // paths for line segments and clip those with custom regions at paint time. + // "Right" path contains segments that cross the axis line with visible point on the + // right side of the axis line, as well as segments that have one point within the margin + // on the right side of the axis line and another point on the right side of the chart. + // "Left" path contains points with similarly on the left side. + // "Full" path contains rest of the points. + // This doesn't yield perfect results always. E.g. when segment covers more than 90 + // degrees and both of the points are within the margin, one in the top half and one in the + // bottom half of the chart, the bottom one gets clipped incorrectly. + // However, this should be rare occurrence in any sensible chart. + currentSeriesPoint = m_series->pointAt(i); + currentGeometryPoint = points.at(i); + pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX); + + // Draw something unless both off-grid + if (!pointOffGrid || !previousPointWasOffGrid) { + QPointF intersectionPoint; + qreal y; + if (pointOffGrid != previousPointWasOffGrid) { + if (currentGeometryPoint.x() == previousGeometryPoint.x()) { + y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) / 2.0; + } else { + qreal ratio = (centerPoint.x() - currentGeometryPoint.x()) / (currentGeometryPoint.x() - previousGeometryPoint.x()); + y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) * ratio; + } + intersectionPoint = QPointF(centerPoint.x(), y); + } + + bool dummyOk; // We know points are ok, but this is needed + qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk); + qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->pointAt(i - 1).x(), dummyOk); + + if ((qAbs(currentAngle - previousAngle) > 180.0)) { + // If the angle between two points is over 180 degrees (half X range), + // any direct segment between them becomes meaningless. + // In this case two line segments are drawn instead, from previous + // point to the center and from center to current point. + if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine)) + && previousGeometryPoint.y() < horizontal) { + currentSegmentPath = &linePathRight; + } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine)) + && previousGeometryPoint.y() < horizontal) { + currentSegmentPath = &linePathLeft; + } else if (previousAngle > 0.0 && previousAngle < 360.0) { + currentSegmentPath = &linePath; + } else { + currentSegmentPath = 0; + } + + if (currentSegmentPath) { + if (previousSegmentPath != currentSegmentPath) + currentSegmentPath->moveTo(previousGeometryPoint); + if (previousPointWasOffGrid) + fullPath.moveTo(intersectionPoint); + + currentSegmentPath->lineTo(centerPoint); + fullPath.lineTo(centerPoint); + } + + previousSegmentPath = currentSegmentPath; + + if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine)) + && currentGeometryPoint.y() < horizontal) { + currentSegmentPath = &linePathRight; + } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &¤tGeometryPoint.x() > leftMarginLine)) + && currentGeometryPoint.y() < horizontal) { + currentSegmentPath = &linePathLeft; + } else if (currentAngle > 0.0 && currentAngle < 360.0) { + currentSegmentPath = &linePath; + } else { + currentSegmentPath = 0; + } + + if (currentSegmentPath) { + if (previousSegmentPath != currentSegmentPath) + currentSegmentPath->moveTo(centerPoint); + if (!previousSegmentPath) + fullPath.moveTo(centerPoint); + + currentSegmentPath->lineTo(currentGeometryPoint); + if (pointOffGrid) + fullPath.lineTo(intersectionPoint); + else + fullPath.lineTo(currentGeometryPoint); + } + } else { + if (previousAngle < 0.0 || currentAngle < 0.0 + || ((previousAngle <= 180.0 && currentAngle <= 180.0) + && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal) + || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) { + currentSegmentPath = &linePathRight; + } else if (previousAngle > 360.0 || currentAngle > 360.0 + || ((previousAngle > 180.0 && currentAngle > 180.0) + && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal) + || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) { + currentSegmentPath = &linePathLeft; + } else { + currentSegmentPath = &linePath; + } + + if (currentSegmentPath != previousSegmentPath) + currentSegmentPath->moveTo(previousGeometryPoint); + if (previousPointWasOffGrid) + fullPath.moveTo(intersectionPoint); + + if (pointOffGrid) + fullPath.lineTo(intersectionPoint); + else + fullPath.lineTo(currentGeometryPoint); + currentSegmentPath->lineTo(currentGeometryPoint); + } + } else { + currentSegmentPath = 0; + } + + previousPointWasOffGrid = pointOffGrid; + if (m_pointsVisible && !pointOffGrid && currentSeriesPoint.y() >= minY) { + linePath.addEllipse(points.at(i), size, size); + fullPath.addEllipse(points.at(i), size, size); + linePath.moveTo(points.at(i)); + fullPath.moveTo(points.at(i)); + } + previousSegmentPath = currentSegmentPath; + previousGeometryPoint = currentGeometryPoint; + } + m_linePathPolarRight = linePathRight; + m_linePathPolarLeft = linePathLeft; + // Note: This construction of m_fullpath is not perfect. The partial segments that are + // outside left/right clip regions at axis boundary still generate hover/click events, + // because shape doesn't get clipped. It doesn't seem possible to do sensibly. + } else { // not polar + linePath.moveTo(points.at(0)); + if (m_pointsVisible) { + int size = m_linePen.width(); + linePath.addEllipse(points.at(0), size, size); + linePath.moveTo(points.at(0)); + for (int i = 1; i < points.size(); i++) { + linePath.lineTo(points.at(i)); + linePath.addEllipse(points.at(i), size, size); + linePath.moveTo(points.at(i)); + } + } else { + for (int i = 1; i < points.size(); i++) + linePath.lineTo(points.at(i)); + } + fullPath = linePath; + } QPainterPathStroker stroker; // QPainter::drawLine does not respect join styles, for example BevelJoin becomes MiterJoin. // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and // multiply line width with square root of two when defining shape and bounding rectangle. - stroker.setWidth(m_linePen.width() * 1.42); + stroker.setWidth(margin); stroker.setJoinStyle(Qt::MiterJoin); stroker.setCapStyle(Qt::SquareCap); stroker.setMiterLimit(m_linePen.miterLimit()); prepareGeometryChange(); - m_path = stroker.createStroke(linePath); - m_rect = m_path.boundingRect(); + m_linePath = linePath; + m_fullPath = fullPath; + m_shapePath = stroker.createStroke(fullPath); + + m_rect = m_shapePath.boundingRect(); } void LineChartItem::handleUpdated() @@ -121,16 +303,35 @@ void LineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt Q_UNUSED(widget) Q_UNUSED(option) + QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); + painter->save(); painter->setPen(m_linePen); - painter->setClipRect(QRectF(QPointF(0,0),domain()->size())); + bool alwaysUsePath = false; + + if (m_series->chart()->chartType() == QChart::ChartTypePolar) { + qreal halfWidth = domain()->size().width() / 2.0; + QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height()); + QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height()); + QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse); + QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect())); + QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect())); + painter->setClipRegion(clipRegionLeft); + painter->drawPath(m_linePathPolarLeft); + painter->setClipRegion(clipRegionRight); + painter->drawPath(m_linePathPolarRight); + painter->setClipRegion(fullPolarClipRegion); + alwaysUsePath = true; // required for proper clipping + } else { + painter->setClipRect(clipRect); + } if (m_pointsVisible) { painter->setBrush(m_linePen.color()); painter->drawPath(m_linePath); } else { painter->setBrush(QBrush(Qt::NoBrush)); - if (m_linePen.style() != Qt::SolidLine) { + if (m_linePen.style() != Qt::SolidLine || alwaysUsePath) { // If pen style is not solid line, always fall back to path painting // to ensure proper continuity of the pattern painter->drawPath(m_linePath); diff --git a/src/linechart/linechartitem_p.h b/src/linechart/linechartitem_p.h index 6db4ccfb..2a728f3b 100644 --- a/src/linechart/linechartitem_p.h +++ b/src/linechart/linechartitem_p.h @@ -32,6 +32,7 @@ #include "qchartglobal.h" #include "xychart_p.h" +#include "qchart.h" #include <QPen> QTCOMMERCIALCHART_BEGIN_NAMESPACE @@ -44,7 +45,7 @@ class LineChartItem : public XYChart Q_OBJECT Q_INTERFACES(QGraphicsItem) public: - explicit LineChartItem(QLineSeries *series, QGraphicsItem* item = 0); + explicit LineChartItem(QLineSeries *series, QGraphicsItem *item = 0); ~LineChartItem() {} //from QGraphicsItem @@ -52,7 +53,7 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); QPainterPath shape() const; - QPainterPath path() const { return m_linePath; } + QPainterPath path() const { return m_fullPath; } public Q_SLOTS: void handleUpdated(); @@ -63,15 +64,21 @@ protected: void hoverEnterEvent(QGraphicsSceneHoverEvent *event); void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); void suppressPoints() { m_pointsVisible = false; } + void forceChartType(QChart::ChartType chartType) { m_chartType = chartType; } private: QLineSeries *m_series; - QPainterPath m_path; QPainterPath m_linePath; + QPainterPath m_linePathPolarRight; + QPainterPath m_linePathPolarLeft; + QPainterPath m_fullPath; + QPainterPath m_shapePath; + QVector<QPointF> m_points; QRectF m_rect; QPen m_linePen; bool m_pointsVisible; + QChart::ChartType m_chartType; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/qchart.cpp b/src/qchart.cpp index 8ab736ec..f2fd1b67 100644 --- a/src/qchart.cpp +++ b/src/qchart.cpp @@ -24,7 +24,7 @@ #include "qlegend_p.h" #include "chartbackground_p.h" #include "qabstractaxis.h" -#include "chartlayout_p.h" +#include "abstractchartlayout_p.h" #include "charttheme_p.h" #include "chartpresenter_p.h" #include "chartdataset_p.h" @@ -59,6 +59,16 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE */ /*! + \enum QChart::ChartType + + This enum describes the chart type. + + \value ChartTypeUndefined + \value ChartTypeCartesian + \value ChartTypePolar + */ + +/*! \class QChart \brief QtCommercial chart API. @@ -110,15 +120,34 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE */ /*! - Constructs a chart object which is a child of a\a parent. Parameter \a wFlags is passed to the QGraphicsWidget constructor. + \property QChart::chartType + Chart type indicates if the chart is a cartesian chart or a polar chart. + This property is set internally and is read only. + \sa QPolarChart + */ + +/*! + \internal + Constructs a chart object of \a type which is a child of a \a parent. + Parameter \a wFlags is passed to the QGraphicsWidget constructor. + This constructor is called only by subclasses. +*/ +QChart::QChart(QChart::ChartType type, QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QGraphicsWidget(parent, wFlags), + d_ptr(new QChartPrivate(this, type)) +{ + d_ptr->init(); +} + +/*! + Constructs a chart object which is a child of a \a parent. + Parameter \a wFlags is passed to the QGraphicsWidget constructor. */ QChart::QChart(QGraphicsItem *parent, Qt::WindowFlags wFlags) : QGraphicsWidget(parent, wFlags), - d_ptr(new QChartPrivate(this)) + d_ptr(new QChartPrivate(this, ChartTypeCartesian)) { - d_ptr->m_legend = new LegendScroller(this); - setTheme(QChart::ChartThemeLight); - setLayout(d_ptr->m_presenter->layout()); + d_ptr->init(); } /*! @@ -269,9 +298,12 @@ void QChart::zoomIn() /*! Zooms in the view to a maximum level at which \a rect is still fully visible. + \note This is not supported for polar charts. */ void QChart::zoomIn(const QRectF &rect) { + if (d_ptr->m_type == QChart::ChartTypePolar) + return; d_ptr->zoomIn(rect); } @@ -306,8 +338,8 @@ void QChart::zoom(qreal factor) } /*! - Returns the pointer to the x axis object of the chart asociated with the specified \a series - If no series is provided then pointer to currently visible axis is provided + Returns the pointer to the x axis object of the chart associated with the specified \a series. + If no series is provided then pointer to currently visible axis is provided. */ QAbstractAxis *QChart::axisX(QAbstractSeries *series) const { @@ -318,8 +350,8 @@ QAbstractAxis *QChart::axisX(QAbstractSeries *series) const } /*! - Returns the pointer to the y axis object of the chart asociated with the specified \a series - If no series is provided then pointer to currently visible axis is provided + Returns the pointer to the y axis object of the chart associated with the specified \a series. + If no series is provided then pointer to currently visible axis is provided. */ QAbstractAxis *QChart::axisY(QAbstractSeries *series) const { @@ -438,6 +470,11 @@ QMargins QChart::margins() const return d_ptr->m_presenter->layout()->margins(); } +QChart::ChartType QChart::chartType() const +{ + return d_ptr->m_type; +} + /*! Returns the the rect within which the drawing of the chart is done. It does not include the area defines by margins. @@ -471,6 +508,8 @@ QChart::AnimationOptions QChart::animationOptions() const /*! Scrolls the visible area of the chart by the distance defined in the \a dx and \a dy. + + For polar charts, \a dx indicates the angle along angular axis instead of distance. */ void QChart::scroll(qreal dx, qreal dy) { @@ -581,11 +620,12 @@ QPointF QChart::mapToPosition(const QPointF &value, QAbstractSeries *series) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -QChartPrivate::QChartPrivate(QChart *q): +QChartPrivate::QChartPrivate(QChart *q, QChart::ChartType type): q_ptr(q), + m_type(type), m_legend(0), m_dataset(new ChartDataSet(q)), - m_presenter(new ChartPresenter(q)), + m_presenter(new ChartPresenter(q, type)), m_themeManager(new ChartThemeManager(q)) { QObject::connect(m_dataset, SIGNAL(seriesAdded(QAbstractSeries*)), m_presenter, SLOT(handleSeriesAdded(QAbstractSeries*))); @@ -603,6 +643,13 @@ QChartPrivate::~QChartPrivate() } +void QChartPrivate::init() +{ + m_legend = new LegendScroller(q_ptr); + q_ptr->setTheme(QChart::ChartThemeLight); + q_ptr->setLayout(m_presenter->layout()); +} + void QChartPrivate::zoomIn(qreal factor) { QRectF rect = m_presenter->geometry(); diff --git a/src/qchart.h b/src/qchart.h index e18b127f..3f026732 100644 --- a/src/qchart.h +++ b/src/qchart.h @@ -45,10 +45,18 @@ class QTCOMMERCIALCHART_EXPORT QChart : public QGraphicsWidget Q_PROPERTY(QChart::AnimationOptions animationOptions READ animationOptions WRITE setAnimationOptions) Q_PROPERTY(QMargins minimumMargins READ minimumMargins WRITE setMinimumMargins) Q_PROPERTY(QMargins margins READ margins WRITE setMargins) + Q_PROPERTY(QChart::ChartType chartType READ chartType) Q_ENUMS(ChartTheme) Q_ENUMS(AnimationOption) + Q_ENUMS(ChartType) public: + enum ChartType { + ChartTypeUndefined = 0, + ChartTypeCartesian, + ChartTypePolar + }; + enum ChartTheme { ChartThemeLight = 0, ChartThemeBlueCerulean, @@ -84,7 +92,7 @@ public: QAbstractAxis *axisY(QAbstractSeries *series = 0) const; // ****************** - void addAxis(QAbstractAxis *axis,Qt::Alignment alignment); + void addAxis(QAbstractAxis *axis, Qt::Alignment alignment); void removeAxis(QAbstractAxis *axis); QList<QAbstractAxis*> axes(Qt::Orientations orientation = Qt::Horizontal|Qt::Vertical, QAbstractSeries *series = 0) const; @@ -133,7 +141,10 @@ public: QPointF mapToValue(const QPointF &position, QAbstractSeries *series = 0); QPointF mapToPosition(const QPointF &value, QAbstractSeries *series = 0); + ChartType chartType() const; + protected: + explicit QChart(QChart::ChartType type, QGraphicsItem *parent, Qt::WindowFlags wFlags); QScopedPointer<QChartPrivate> d_ptr; friend class QLegend; friend class DeclarativeChart; diff --git a/src/qchart_p.h b/src/qchart_p.h index f26a3600..e715bc63 100644 --- a/src/qchart_p.h +++ b/src/qchart_p.h @@ -31,10 +31,10 @@ #define QCHART_P_H #include "qchartglobal.h" +#include "qchart.h" QTCOMMERCIALCHART_BEGIN_NAMESPACE -class QChart; class ChartThemeManager; class ChartPresenter; class QLegend; @@ -44,14 +44,16 @@ class QChartPrivate { public: - QChartPrivate(QChart *q); + QChartPrivate(QChart *q, QChart::ChartType type); ~QChartPrivate(); QChart *q_ptr; QLegend *m_legend; ChartDataSet *m_dataset; ChartPresenter *m_presenter; ChartThemeManager *m_themeManager; + QChart::ChartType m_type; + void init(); void zoomIn(qreal factor); void zoomOut(qreal factor); void zoomIn(const QRectF &rect); diff --git a/src/qpolarchart.cpp b/src/qpolarchart.cpp new file mode 100644 index 00000000..1d93d35e --- /dev/null +++ b/src/qpolarchart.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpolarchart.h" +#include "qabstractaxis.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +/*! + \enum QPolarChart::PolarOrientation + + This type is used to signify the polar orientation of an axis. + + \value PolarOrientationRadial + \value PolarOrientationAngular +*/ + +/*! + \class QPolarChart + \brief QtCommercial chart API. + + QPolarChart is a specialization of QChart to show a polar chart. + + Polar charts support line, spline, area, and scatter series, and all axis types + supported by those series. + + \note When setting ticks to an angular QValueAxis, keep in mind that the first and last tick + are co-located at 0/360 degree angle. + + \note If the angular distance between two consecutive points in a series is more than 180 degrees, + any line connecting the two points becomes meaningless, so choose the axis ranges accordingly + when displaying line, spline, or area series. + + \note Polar charts do not support multiple axes of same orientation. + + \sa QChart + */ + +/*! + Constructs a polar chart as a child of a \a parent. + Parameter \a wFlags is passed to the QChart constructor. + */ +QPolarChart::QPolarChart(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QChart(QChart::ChartTypePolar, parent, wFlags) +{ +} + +/*! + Destroys the object and it's children, like series and axis objects added to it. + */ +QPolarChart::~QPolarChart() +{ +} + +/*! + Returns the axes added for the \a series with \a polarOrientation. If no series is provided, then any axis with the + specified polar orientation is returned. + \sa addAxis() + */ +QList<QAbstractAxis *> QPolarChart::axes(PolarOrientations polarOrientation, QAbstractSeries *series) const +{ + Qt::Orientations orientation(0); + if (polarOrientation.testFlag(PolarOrientationAngular)) + orientation |= Qt::Horizontal; + if (polarOrientation.testFlag(PolarOrientationRadial)) + orientation |= Qt::Vertical; + + return QChart::axes(orientation, series); +} + +/*! + This convenience method adds \a axis to the polar chart with \a polarOrientation. + The chart takes the ownership of the axis. + + \note Axes can be added to a polar chart also with QChart::addAxis() instead of this method. + The specified alignment determines the polar orientation: horizontal alignments indicate angular + axis and vertical alignments indicate radial axis. + \sa QChart::removeAxis(), QChart::createDefaultAxes(), QAbstractSeries::attachAxis(), QChart::addAxis() +*/ +void QPolarChart::addAxis(QAbstractAxis *axis, PolarOrientation polarOrientation) +{ + if (!axis || axis->type() == QAbstractAxis::AxisTypeBarCategory) { + qWarning("QAbstractAxis::AxisTypeBarCategory is not a supported axis type for polar charts."); + } else { + Qt::Alignment alignment = Qt::AlignLeft; + if (polarOrientation == PolarOrientationAngular) + alignment = Qt::AlignBottom; + QChart::addAxis(axis, alignment); + } +} + +/*! + Angular axes of a polar chart report horizontal orientation and radial axes report + vertical orientation. + This function is a convenience function for converting the orientation of an \a axis to + corresponding polar orientation. If the \a axis is NULL or not added to a polar chart, + the return value is meaningless. +*/ +QPolarChart::PolarOrientation QPolarChart::axisPolarOrientation(QAbstractAxis *axis) +{ + if (axis && axis->orientation() == Qt::Horizontal) + return PolarOrientationAngular; + else + return PolarOrientationRadial; +} + +#include "moc_qpolarchart.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/qpolarchart.h b/src/qpolarchart.h new file mode 100644 index 00000000..123eb578 --- /dev/null +++ b/src/qpolarchart.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Commercial Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPOLARCHART_H +#define QPOLARCHART_H + +#include "qchart.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QAbstractSeries; +class QAbstractAxis; + +class QTCOMMERCIALCHART_EXPORT QPolarChart : public QChart +{ + Q_OBJECT + Q_ENUMS(PolarOrientation) + Q_FLAGS(PolarOrientations) + +public: + enum PolarOrientation { + PolarOrientationRadial = 0x1, + PolarOrientationAngular = 0x2 + }; + Q_DECLARE_FLAGS(PolarOrientations, PolarOrientation) + +public: + explicit QPolarChart(QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0); + ~QPolarChart(); + + void addAxis(QAbstractAxis *axis, PolarOrientation polarOrientation); + + QList<QAbstractAxis*> axes(PolarOrientations polarOrientation = PolarOrientations(PolarOrientationRadial | PolarOrientationAngular), QAbstractSeries *series = 0) const; + + static PolarOrientation axisPolarOrientation(QAbstractAxis *axis); + +protected: + Q_DISABLE_COPY(QPolarChart) +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // QCHART_H diff --git a/src/scatterchart/scatterchartitem.cpp b/src/scatterchart/scatterchartitem.cpp index 93761ed1..fecbeb1b 100644 --- a/src/scatterchart/scatterchartitem.cpp +++ b/src/scatterchart/scatterchartitem.cpp @@ -23,6 +23,7 @@ #include "qscatterseries_p.h" #include "chartpresenter_p.h" #include "abstractdomain_p.h" +#include "qchart.h" #include <QPainter> #include <QGraphicsScene> #include <QDebug> @@ -30,7 +31,7 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -ScatterChartItem::ScatterChartItem(QScatterSeries *series, QGraphicsItem* item) +ScatterChartItem::ScatterChartItem(QScatterSeries *series, QGraphicsItem *item) : XYChart(series,item), m_series(series), m_items(this), @@ -93,12 +94,12 @@ void ScatterChartItem::deletePoints(int count) void ScatterChartItem::markerSelected(QGraphicsItem *marker) { - emit XYChart::clicked(domain()->calculateDomainPoint(m_markerMap[marker])); + emit XYChart::clicked(m_markerMap[marker]); } void ScatterChartItem::markerHovered(QGraphicsItem *marker, bool state) { - emit XYChart::hovered(domain()->calculateDomainPoint(m_markerMap[marker]), state); + emit XYChart::hovered(m_markerMap[marker], state); } void ScatterChartItem::updateGeometry() @@ -125,13 +126,16 @@ void ScatterChartItem::updateGeometry() QRectF clipRect(QPointF(0,0),domain()->size()); + QVector<bool> offGridStatus = offGridStatusVector(); + for (int i = 0; i < points.size(); i++) { QGraphicsItem *item = items.at(i); const QPointF &point = points.at(i); const QRectF &rect = item->boundingRect(); - m_markerMap[item] = point; + m_markerMap[item] = m_series->pointAt(i); item->setPos(point.x() - rect.width() / 2, point.y() - rect.height() / 2); - if (!m_visible || !clipRect.contains(point)) + + if (!m_visible || offGridStatus.at(i)) item->setVisible(false); else item->setVisible(true); diff --git a/src/scatterchart/scatterchartitem_p.h b/src/scatterchart/scatterchartitem_p.h index 6e8010bc..e00404a4 100644 --- a/src/scatterchart/scatterchartitem_p.h +++ b/src/scatterchart/scatterchartitem_p.h @@ -44,7 +44,7 @@ class ScatterChartItem : public XYChart Q_OBJECT Q_INTERFACES(QGraphicsItem) public: - explicit ScatterChartItem(QScatterSeries *series, QGraphicsItem* item = 0); + explicit ScatterChartItem(QScatterSeries *series, QGraphicsItem *item = 0); public: //from QGraphicsItem diff --git a/src/splinechart/splinechartitem.cpp b/src/splinechart/splinechartitem.cpp index 261a753f..4a46a724 100644 --- a/src/splinechart/splinechartitem.cpp +++ b/src/splinechart/splinechartitem.cpp @@ -22,13 +22,13 @@ #include "qsplineseries_p.h" #include "chartpresenter_p.h" #include "splineanimation_p.h" -#include "abstractdomain_p.h" +#include "polardomain_p.h" #include <QPainter> #include <QGraphicsSceneMouseEvent> QTCOMMERCIALCHART_BEGIN_NAMESPACE -SplineChartItem::SplineChartItem(QSplineSeries *series, QGraphicsItem* item) +SplineChartItem::SplineChartItem(QSplineSeries *series, QGraphicsItem *item) : XYChart(series,item), m_series(series), m_pointsVisible(false), @@ -49,8 +49,7 @@ QRectF SplineChartItem::boundingRect() const QPainterPath SplineChartItem::shape() const { - QPainterPathStroker stroker; - return stroker.createStroke(m_path); + return m_fullPath; } void SplineChartItem::setAnimation(SplineAnimation *animation) @@ -107,20 +106,179 @@ void SplineChartItem::updateGeometry() Q_ASSERT(points.count() * 2 - 2 == controlPoints.count()); - QPainterPath splinePath(points.at(0)); + QPainterPath splinePath; + QPainterPath fullPath; + // Use worst case scenario to determine required margin. + qreal margin = m_linePen.width() * 1.42; + + if (m_series->chart()->chartType() == QChart::ChartTypePolar) { + QPainterPath splinePathLeft; + QPainterPath splinePathRight; + QPainterPath *currentSegmentPath = 0; + QPainterPath *previousSegmentPath = 0; + qreal minX = domain()->minX(); + qreal maxX = domain()->maxX(); + qreal minY = domain()->minY(); + QPointF currentSeriesPoint = m_series->pointAt(0); + QPointF currentGeometryPoint = points.at(0); + QPointF previousGeometryPoint = points.at(0); + bool pointOffGrid = false; + bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX); + m_visiblePoints.clear(); + m_visiblePoints.reserve(points.size()); + + qreal domainRadius = domain()->size().height() / 2.0; + const QPointF centerPoint(domainRadius, domainRadius); + + if (!previousPointWasOffGrid) { + fullPath.moveTo(points.at(0)); + // Do not draw points for points below minimum Y. + if (m_pointsVisible && currentSeriesPoint.y() >= minY) + m_visiblePoints.append(currentGeometryPoint); + } + + qreal leftMarginLine = centerPoint.x() - margin; + qreal rightMarginLine = centerPoint.x() + margin; + qreal horizontal = centerPoint.y(); + + for (int i = 1; i < points.size(); i++) { + // Interpolating spline fragments accurately is not trivial, and would anyway be ugly + // when thick pen is used, so we work around it by utilizing three separate + // paths for spline segments and clip those with custom regions at paint time. + // "Right" path contains segments that cross the axis line with visible point on the + // right side of the axis line, as well as segments that have one point within the margin + // on the right side of the axis line and another point on the right side of the chart. + // "Left" path contains points with similarly on the left side. + // "Full" path contains rest of the points. + // This doesn't yield perfect results always. E.g. when segment covers more than 90 + // degrees and both of the points are within the margin, one in the top half and one in the + // bottom half of the chart, the bottom one gets clipped incorrectly. + // However, this should be rare occurrence in any sensible chart. + currentSeriesPoint = m_series->pointAt(i); + currentGeometryPoint = points.at(i); + pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX); + + // Draw something unless both off-grid + if (!pointOffGrid || !previousPointWasOffGrid) { + bool dummyOk; // We know points are ok, but this is needed + qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk); + qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->pointAt(i - 1).x(), dummyOk); + + if ((qAbs(currentAngle - previousAngle) > 180.0)) { + // If the angle between two points is over 180 degrees (half X range), + // any direct segment between them becomes meaningless. + // In this case two line segments are drawn instead, from previous + // point to the center and from center to current point. + if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine)) + && previousGeometryPoint.y() < horizontal) { + currentSegmentPath = &splinePathRight; + } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine)) + && previousGeometryPoint.y() < horizontal) { + currentSegmentPath = &splinePathLeft; + } else if (previousAngle > 0.0 && previousAngle < 360.0) { + currentSegmentPath = &splinePath; + } else { + currentSegmentPath = 0; + } + + if (currentSegmentPath) { + if (previousSegmentPath != currentSegmentPath) + currentSegmentPath->moveTo(previousGeometryPoint); + if (!previousSegmentPath) + fullPath.moveTo(previousGeometryPoint); + + currentSegmentPath->lineTo(centerPoint); + fullPath.lineTo(centerPoint); + } + + previousSegmentPath = currentSegmentPath; + + if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine)) + && currentGeometryPoint.y() < horizontal) { + currentSegmentPath = &splinePathRight; + } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &¤tGeometryPoint.x() > leftMarginLine)) + && currentGeometryPoint.y() < horizontal) { + currentSegmentPath = &splinePathLeft; + } else if (currentAngle > 0.0 && currentAngle < 360.0) { + currentSegmentPath = &splinePath; + } else { + currentSegmentPath = 0; + } + + if (currentSegmentPath) { + if (previousSegmentPath != currentSegmentPath) + currentSegmentPath->moveTo(centerPoint); + if (!previousSegmentPath) + fullPath.moveTo(centerPoint); + + currentSegmentPath->lineTo(currentGeometryPoint); + fullPath.lineTo(currentGeometryPoint); + } + } else { + QPointF cp1 = controlPoints[2 * (i - 1)]; + QPointF cp2 = controlPoints[(2 * i) - 1]; + + if (previousAngle < 0.0 || currentAngle < 0.0 + || ((previousAngle <= 180.0 && currentAngle <= 180.0) + && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal) + || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) { + currentSegmentPath = &splinePathRight; + } else if (previousAngle > 360.0 || currentAngle > 360.0 + || ((previousAngle > 180.0 && currentAngle > 180.0) + && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal) + || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) { + currentSegmentPath = &splinePathLeft; + } else { + currentSegmentPath = &splinePath; + } + + if (currentSegmentPath != previousSegmentPath) + currentSegmentPath->moveTo(previousGeometryPoint); + if (!previousSegmentPath) + fullPath.moveTo(previousGeometryPoint); + + fullPath.cubicTo(cp1, cp2, currentGeometryPoint); + currentSegmentPath->cubicTo(cp1, cp2, currentGeometryPoint); + } + } else { + currentSegmentPath = 0; + } + + previousPointWasOffGrid = pointOffGrid; + if (!pointOffGrid && m_pointsVisible && currentSeriesPoint.y() >= minY) + m_visiblePoints.append(currentGeometryPoint); + previousSegmentPath = currentSegmentPath; + previousGeometryPoint = currentGeometryPoint; + } - for (int i = 0; i < points.size() - 1; i++) { - const QPointF &point = points.at(i + 1); - splinePath.cubicTo(controlPoints[2 * i], controlPoints[2 * i + 1], point); + m_pathPolarRight = splinePathRight; + m_pathPolarLeft = splinePathLeft; + // Note: This construction of m_fullpath is not perfect. The partial segments that are + // outside left/right clip regions at axis boundary still generate hover/click events, + // because shape doesn't get clipped. It doesn't seem possible to do sensibly. + } else { // not polar + splinePath.moveTo(points.at(0)); + for (int i = 0; i < points.size() - 1; i++) { + const QPointF &point = points.at(i + 1); + splinePath.cubicTo(controlPoints[2 * i], controlPoints[2 * i + 1], point); + } + fullPath = splinePath; } + m_path = splinePath; + + QPainterPathStroker stroker; + // The full path is comprised of three separate paths. + // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and + // multiply line width with square root of two when defining shape and bounding rectangle. + stroker.setWidth(margin); + stroker.setJoinStyle(Qt::MiterJoin); + stroker.setCapStyle(Qt::SquareCap); + stroker.setMiterLimit(m_linePen.miterLimit()); prepareGeometryChange(); - // QPainterPathStroker stroker; - // stroker.setWidth(m_linePen.width() / 2.0); - // m_path = stroker.createStroke(splinePath); - m_path = splinePath; - m_rect = splinePath.boundingRect(); + m_fullPath = stroker.createStroke(fullPath); + m_rect = m_fullPath.boundingRect(); } /*! @@ -240,16 +398,38 @@ void SplineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o Q_UNUSED(widget) Q_UNUSED(option) + QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); + painter->save(); - painter->setClipRect(QRectF(QPointF(0,0),domain()->size())); painter->setPen(m_linePen); - // painter->setBrush(m_linePen.color()); + painter->setBrush(Qt::NoBrush); + + if (m_series->chart()->chartType() == QChart::ChartTypePolar) { + qreal halfWidth = domain()->size().width() / 2.0; + QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height()); + QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height()); + QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse); + QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect())); + QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect())); + painter->setClipRegion(clipRegionLeft); + painter->drawPath(m_pathPolarLeft); + painter->setClipRegion(clipRegionRight); + painter->drawPath(m_pathPolarRight); + painter->setClipRegion(fullPolarClipRegion); + } else { + painter->setClipRect(clipRect); + } painter->drawPath(m_path); + if (m_pointsVisible) { painter->setPen(m_pointPen); - painter->drawPoints(geometryPoints()); + if (m_series->chart()->chartType() == QChart::ChartTypePolar) + painter->drawPoints(m_visiblePoints); + else + painter->drawPoints(geometryPoints()); } + painter->restore(); } diff --git a/src/splinechart/splinechartitem_p.h b/src/splinechart/splinechartitem_p.h index a15b6c6a..37e1b52d 100644 --- a/src/splinechart/splinechartitem_p.h +++ b/src/splinechart/splinechartitem_p.h @@ -42,7 +42,7 @@ class SplineChartItem : public XYChart Q_OBJECT Q_INTERFACES(QGraphicsItem) public: - SplineChartItem(QSplineSeries *series, QGraphicsItem* item = 0); + SplineChartItem(QSplineSeries *series, QGraphicsItem *item = 0); //from QGraphicsItem QRectF boundingRect() const; @@ -70,11 +70,15 @@ protected: private: QSplineSeries *m_series; QPainterPath m_path; + QPainterPath m_pathPolarRight; + QPainterPath m_pathPolarLeft; + QPainterPath m_fullPath; QRectF m_rect; QPen m_linePen; QPen m_pointPen; bool m_pointsVisible; QVector<QPointF> m_controlPoints; + QVector<QPointF> m_visiblePoints; SplineAnimation *m_animation; friend class SplineAnimation; diff --git a/src/src.pro b/src/src.pro index bfb90ba0..a60a6cf2 100644 --- a/src/src.pro +++ b/src/src.pro @@ -39,8 +39,8 @@ SOURCES += \ $$PWD/chartelement.cpp \ $$PWD/chartitem.cpp \ $$PWD/scroller.cpp \ - $$PWD/chartlayout.cpp \ - $$PWD/charttitle.cpp + $$PWD/charttitle.cpp \ + $$PWD/qpolarchart.cpp PRIVATE_HEADERS += \ $$PWD/chartdataset_p.h \ $$PWD/chartitem_p.h \ @@ -53,7 +53,6 @@ PRIVATE_HEADERS += \ $$PWD/qchartview_p.h \ $$PWD/scroller_p.h \ $$PWD/qabstractseries_p.h \ - $$PWD/chartlayout_p.h \ $$PWD/charttitle_p.h \ $$PWD/charthelpers_p.h PUBLIC_HEADERS += \ @@ -61,7 +60,8 @@ PUBLIC_HEADERS += \ $$PWD/qchartglobal.h \ $$PWD/qabstractseries.h \ $$PWD/qchartview.h \ - $$PWD/chartsnamespace.h + $$PWD/chartsnamespace.h \ + $$PWD/qpolarchart.h include(animations/animations.pri) include(areachart/areachart.pri) @@ -75,6 +75,7 @@ include(scatterchart/scatter.pri) include(splinechart/splinechart.pri) include(themes/themes.pri) include(xychart/xychart.pri) +include(layout/layout.pri) HEADERS += $$PUBLIC_HEADERS HEADERS += $$PRIVATE_HEADERS diff --git a/src/xychart/qxyseries.cpp b/src/xychart/qxyseries.cpp index 3d5f7eec..285c078d 100644 --- a/src/xychart/qxyseries.cpp +++ b/src/xychart/qxyseries.cpp @@ -69,6 +69,18 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE */ /*! + \qmlproperty AbstractAxis XYSeries::axisAngular + The angular axis used for the series, drawn around the polar chart view. + \sa axisX +*/ + +/*! + \qmlproperty AbstractAxis XYSeries::axisRadial + The radial axis used for the series, drawn inside the polar chart view. + \sa axisY +*/ + +/*! \property QXYSeries::pointsVisible Controls if the data points are visible and should be drawn. */ @@ -354,6 +366,15 @@ QList<QPointF> QXYSeries::points() const } /*! + Returns point at \a index in internal points vector. +*/ +const QPointF &QXYSeries::pointAt(int index) const +{ + Q_D(const QXYSeries); + return d->m_points.at(index); +} + +/*! Returns number of data points within series. */ int QXYSeries::count() const diff --git a/src/xychart/qxyseries.h b/src/xychart/qxyseries.h index d0e6bb48..8eff380e 100644 --- a/src/xychart/qxyseries.h +++ b/src/xychart/qxyseries.h @@ -56,6 +56,7 @@ public: int count() const; QList<QPointF> points() const; + const QPointF &pointAt(int index) const; QXYSeries &operator << (const QPointF &point); QXYSeries &operator << (const QList<QPointF> &points); diff --git a/src/xychart/xychart.cpp b/src/xychart/xychart.cpp index ecadf676..3d442c5a 100644 --- a/src/xychart/xychart.cpp +++ b/src/xychart/xychart.cpp @@ -24,6 +24,7 @@ #include "chartpresenter_p.h" #include "abstractdomain_p.h" #include "qxymodelmapper.h" +#include "qabstractaxis_p.h" #include <QPainter> #include <QAbstractItemModel> @@ -32,7 +33,7 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE //TODO: optimize : remove points which are not visible -XYChart::XYChart(QXYSeries *series,QGraphicsItem* item): +XYChart::XYChart(QXYSeries *series, QGraphicsItem *item): ChartItem(series->d_func(),item), m_series(series), m_animation(0), @@ -46,7 +47,7 @@ XYChart::XYChart(QXYSeries *series,QGraphicsItem* item): QObject::connect(this, SIGNAL(hovered(QPointF,bool)), series, SIGNAL(hovered(QPointF,bool))); } -void XYChart::setGeometryPoints(const QVector<QPointF>& points) +void XYChart::setGeometryPoints(const QVector<QPointF> &points) { m_points = points; } @@ -61,6 +62,32 @@ void XYChart::setDirty(bool dirty) m_dirty = dirty; } +// Returns a vector with same size as geometryPoints vector, indicating +// the off grid status of points. +QVector<bool> XYChart::offGridStatusVector() +{ + qreal minX = domain()->minX(); + qreal maxX = domain()->maxX(); + qreal minY = domain()->minY(); + qreal maxY = domain()->maxY(); + + QVector<bool> returnVector; + returnVector.resize(m_points.size()); + + for (int i = 0; i < m_points.size(); i++) { + const QPointF &seriesPoint = m_series->pointAt(i); + if (seriesPoint.x() < minX + || seriesPoint.x() > maxX + || seriesPoint.y() < minY + || seriesPoint.y() > maxY) { + returnVector[i] = true; + } else { + returnVector[i] = false; + } + } + return returnVector; +} + void XYChart::updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoints, int index) { diff --git a/src/xychart/xychart_p.h b/src/xychart/xychart_p.h index cfdb296b..49c57bc3 100644 --- a/src/xychart/xychart_p.h +++ b/src/xychart/xychart_p.h @@ -45,10 +45,10 @@ class XYChart : public ChartItem { Q_OBJECT public: - explicit XYChart(QXYSeries *series,QGraphicsItem* item = 0); + explicit XYChart(QXYSeries *series,QGraphicsItem *item = 0); ~XYChart() {} - void setGeometryPoints(const QVector<QPointF>& points); + void setGeometryPoints(const QVector<QPointF> &points); QVector<QPointF> geometryPoints() const { return m_points; } void setAnimation(XYAnimation *animation); @@ -58,6 +58,9 @@ public: bool isDirty() const { return m_dirty; } void setDirty(bool dirty); + void getSeriesRanges(qreal &minX, qreal &maxX, qreal &minY, qreal &maxY); + QVector<bool> offGridStatusVector(); + public Q_SLOTS: void handlePointAdded(int index); void handlePointRemoved(int index); |