diff --git a/doc/images/areachart.png b/doc/images/areachart.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2928994c4b86ae4827c846780fa09468669aca GIT binary patch literal 23070 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

%DGpr;B(pQ{O~?9J%XA>cXa`gPG<{+-#K|Yy0R} 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 literal 0 Hc$@

    +
  • QAreaSeries
  • QBarSeries
  • QBarSet
  • QChart
  • diff --git a/doc/src/example-areachart.qdoc b/doc/src/example-areachart.qdoc new file mode 100644 index 0000000..dc6a31e --- /dev/null +++ b/doc/src/example-areachart.qdoc @@ -0,0 +1,30 @@ +/*! + \example example/areachart + \title AreaChart Example + \subtitle + + The example shows how to create simple area chart. + + \image areachart.png + + To create area charts, we need two QLineSeries instances. They are going to define upper and lower boundary of the area. + + \snippet ../example/areachart/main.cpp 1 + + We add data to both series, we use stream operator. + + \snippet ../example/areachart/main.cpp 2 + + Now we create QAreaSeries instance using two line series objects. We set the custom gradient fill and width of the outline. + + \snippet ../example/areachart/main.cpp 3 + + In the end we create QChartView instance, set title, set anti-aliasing and add area series. We also set the specific range for axes. + + \snippet ../example/areachart/main.cpp 4 + + Chart is ready to be shown. + + \snippet ../example/areachart/main.cpp 5 + +*/ \ No newline at end of file diff --git a/doc/src/examples.qdoc b/doc/src/examples.qdoc index 437ae3d..6b3550e 100644 --- a/doc/src/examples.qdoc +++ b/doc/src/examples.qdoc @@ -13,6 +13,7 @@
      +
    • Area Chart example
    • Bar Chart example
    • Stacked Bar Chart example
    • Percent Bar Chart example
    • diff --git a/doc/src/index.qdoc b/doc/src/index.qdoc index d3a7a8c..8f7f98d 100644 --- a/doc/src/index.qdoc +++ b/doc/src/index.qdoc @@ -16,21 +16,22 @@ - + + - + - - + +
      linechartbarchartareachart
      barchart percentbarcchartstackedbarchart
      stackedbarchart linechartpiechart
      scatterchart themechart
      barchartbarchartpiechart
      diff --git a/example/areachart/areachart.pro b/example/areachart/areachart.pro new file mode 100644 index 0000000..f9b33f6 --- /dev/null +++ b/example/areachart/areachart.pro @@ -0,0 +1,8 @@ +!include( ../example.pri ) { + error( "Couldn't find the example.pri file!" ) +} +TARGET = areachart +SOURCES += main.cpp + + + diff --git a/example/areachart/main.cpp b/example/areachart/main.cpp new file mode 100644 index 0000000..616dcfa --- /dev/null +++ b/example/areachart/main.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +QTCOMMERCIALCHART_USE_NAMESPACE + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + +//![1] + + QLineSeries* series0 = new QLineSeries(); + QLineSeries* series1 = new QLineSeries(); + +//![1] + +//![2] + *series0 << QPointF(1, 5) << QPointF(3, 7) << QPointF(7, 6) << QPointF(9, 7) << QPointF(12,6) << QPointF(16,7) << QPointF(18,5); + *series1 << QPointF(1, 3) << QPointF(3, 4) << QPointF(7, 3) << QPointF(8, 2) << QPointF(12,3) << QPointF(16,4) << QPointF(18,3); +//![2] +//![3] + + QAreaSeries* series = new QAreaSeries(series0,series1); + QPen pen(0x059605); + pen.setWidth(3); + series->setPen(pen); + + QLinearGradient gradient(QPointF(0, 0), QPointF(0, 1)); + gradient.setColorAt(0.0,0x3cc63c); + gradient.setColorAt(1.0, 0x26f626); + gradient.setCoordinateMode(QGradient::ObjectBoundingMode); + series->setBrush(gradient); + +//![3] +//![4] + QMainWindow window; + QChartView* chartView = new QChartView(&window); + + chartView->setChartTitle("Basic area chart example"); + chartView->setRenderHint(QPainter::Antialiasing); + + chartView->addSeries(series); + chartView->axisX()->setRange(0,20); + chartView->axisY()->setRange(0,10); +//![4] +//![5] + window.setCentralWidget(chartView); + window.resize(400, 300); + window.show(); +//![5] + + return a.exec(); +} diff --git a/example/example.pro b/example/example.pro index 027ce5b..2a725df 100644 --- a/example/example.pro +++ b/example/example.pro @@ -15,4 +15,5 @@ SUBDIRS += linechart \ presenterchart \ chartview \ scatterinteractions \ - splinechart + splinechart \ + areachart diff --git a/src/areachart/areachart.pri b/src/areachart/areachart.pri new file mode 100644 index 0000000..1ac078e --- /dev/null +++ b/src/areachart/areachart.pri @@ -0,0 +1,14 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +SOURCES += \ + #$$PWD/areachartanimationitem.cpp \ + $$PWD/areachartitem.cpp \ + $$PWD/qareaseries.cpp + +PRIVATE_HEADERS += \ + $$PWD/areachartitem_p.h \ +# $$PWD/linechartanimationitem_p.h + +PUBLIC_HEADERS += \ + $$PWD/qareaseries.h \ No newline at end of file diff --git a/src/areachart/areachartanimationitem.cpp b/src/areachart/areachartanimationitem.cpp new file mode 100644 index 0000000..0a07e80 --- /dev/null +++ b/src/areachart/areachartanimationitem.cpp @@ -0,0 +1,63 @@ +#include "linechartanimationitem_p.h" +#include "linechartitem_p.h" +#include + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +const static int duration = 500; + + +LineChartAnimationItem::LineChartAnimationItem(ChartPresenter* presenter, QLineSeries* series,QGraphicsItem *parent): +LineChartItem(presenter,series,parent) +{ + +} + +LineChartAnimationItem::~LineChartAnimationItem() +{ +} + +void LineChartAnimationItem::addPoints(const QVector& points) +{ + m_data=points; + clearView(); + QPropertyAnimation *animation = new QPropertyAnimation(this, "a_addPoints", parent()); + animation->setDuration(duration); + //animation->setEasingCurve(QEasingCurve::InOutBack); + animation->setKeyValueAt(0.0, 0); + animation->setKeyValueAt(1.0, m_data.size()); + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + +void LineChartAnimationItem::setPoint(int index,const QPointF& point) +{ + AnimationHelper* helper = new AnimationHelper(this,index); + QPropertyAnimation *animation = new QPropertyAnimation(helper, "point", parent()); + animation->setDuration(duration); + //animation->setEasingCurve(QEasingCurve::InOutBack); + animation->setKeyValueAt(0.0, points().value(index)); + animation->setKeyValueAt(1.0, point); + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + +void LineChartAnimationItem::aw_addPoints(int points) +{ + int index = count(); + for(int i = index;i< points ;i++){ + LineChartItem::addPoint(m_data.at(i)); + } + updateGeometry(); + update(); +} + +void LineChartAnimationItem::aw_setPoint(int index,const QPointF& point) +{ + LineChartItem::setPoint(index,point); + updateGeometry(); + update(); +} + + +#include "moc_linechartanimationitem_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/areachart/areachartanimationitem_p.h b/src/areachart/areachartanimationitem_p.h new file mode 100644 index 0000000..163df48 --- /dev/null +++ b/src/areachart/areachartanimationitem_p.h @@ -0,0 +1,55 @@ +#ifndef LINECHARTANIMATIONITEM_P_H_ +#define LINECHARTANIMATIONITEM_P_H_ + +#include "qchartglobal.h" +#include "linechartitem_p.h" +#include "domain_p.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class LineChartItem; + +class LineChartAnimationItem : public LineChartItem { + Q_OBJECT + Q_PROPERTY(int a_addPoints READ ar_addPoints WRITE aw_addPoints); + // Q_PROPERTY(QPointF a_setPoint READ ar_setPoint WRITE aw_setPoint); +public: + LineChartAnimationItem(ChartPresenter* presenter, QLineSeries *series, QGraphicsItem *parent = 0); + virtual ~LineChartAnimationItem(); + + void addPoints(const QVector& points); + void setPoint(int index,const QPointF& point); + //void removePoint(const QPointF& point){}; + //void setPoint(const QPointF& oldPoint, const QPointF& newPoint){}; + + int ar_addPoints() const { return m_addPoints;} + void aw_addPoints(int points); + const QPointF& ar_setPoint() const { return m_setPoint;} + void aw_setPoint(int index,const QPointF& point); + +private: + QVector m_data; + Domain m_domain; + int m_addPoints; + QPointF m_setPoint; + int m_setPoint_index; +}; + +class AnimationHelper: public QObject +{ + Q_OBJECT + Q_PROPERTY(QPointF point READ point WRITE setPoint); +public: + AnimationHelper(LineChartAnimationItem* item,int index):m_item(item),m_index(index){}; + void setPoint(const QPointF& point){ + m_item->aw_setPoint(m_index,point); + } + QPointF point(){return m_point;} + QPointF m_point; + LineChartAnimationItem* m_item; + int m_index; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif diff --git a/src/areachart/areachartitem.cpp b/src/areachart/areachartitem.cpp new file mode 100644 index 0000000..7a2df4b --- /dev/null +++ b/src/areachart/areachartitem.cpp @@ -0,0 +1,116 @@ +#include "areachartitem_p.h" +#include "qareaseries.h" +#include "qlineseries.h" +#include "chartpresenter_p.h" +#include + + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +//TODO: optimize : remove points which are not visible + +AreaChartItem::AreaChartItem(ChartPresenter* presenter, QAreaSeries* areaSeries,QGraphicsItem *parent):ChartItem(parent), +m_presenter(presenter), +m_series(areaSeries), +m_upper(0), +m_lower(0) +{ + //m_items.setZValue(ChartPresenter::LineChartZValue); + m_upper = new AreaBoundItem(this,presenter,m_series->upperSeries()); + if(m_series->lowerSeries()){ + m_lower = new AreaBoundItem(this,presenter,m_series->lowerSeries()); + } + setZValue(ChartPresenter::LineChartZValue); + + QObject::connect(presenter,SIGNAL(geometryChanged(const QRectF&)),this,SLOT(handleGeometryChanged(const QRectF&))); + QObject::connect(areaSeries,SIGNAL(updated()),this,SLOT(handleUpdated())); + + handleUpdated(); +} + +AreaChartItem::~AreaChartItem() +{ + delete m_upper; + delete m_lower; +}; + +QRectF AreaChartItem::boundingRect() const +{ + return m_rect; +} + +QPainterPath AreaChartItem::shape() const +{ + return m_path; +} + +void AreaChartItem::setPen(const QPen& pen) +{ + m_pen = pen; +} + +void AreaChartItem::setBrush(const QBrush& brush) +{ + m_brush = brush; +} + +void AreaChartItem::updatePath() +{ + QPainterPath path; + + path.connectPath(m_upper->shape()); + if(m_lower){ + path.connectPath(m_lower->shape().toReversed()); + } + else{ + QPointF first = path.pointAtPercent(0); + QPointF last = path.pointAtPercent(1); + path.lineTo(last.x(),m_clipRect.bottom()); + path.lineTo(first.x(),m_clipRect.bottom()); + } + path.closeSubpath(); + prepareGeometryChange(); + m_path=path; + m_rect=path.boundingRect(); + update(); +} + +void AreaChartItem::handleUpdated() +{ + setPen(m_series->pen()); + setBrush(m_series->brush()); + update(); +} + +void AreaChartItem::handleDomainChanged(const Domain& domain) +{ + m_upper->handleDomainChanged(domain); + if(m_lower) + m_lower->handleDomainChanged(domain); +} + +void AreaChartItem::handleGeometryChanged(const QRectF& rect) +{ + m_clipRect=rect.translated(-rect.topLeft()); + setPos(rect.topLeft()); + m_upper->handleGeometryChanged(rect); + if(m_lower) + m_lower->handleGeometryChanged(rect); +} +//painter + +void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget); + Q_UNUSED(option); + painter->save(); + painter->setPen(m_pen); + painter->setBrush(m_brush); + painter->setClipRect(m_clipRect); + painter->drawPath(m_path); + painter->restore(); +} + +#include "moc_areachartitem_p.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/areachart/areachartitem_p.h b/src/areachart/areachartitem_p.h new file mode 100644 index 0000000..d2464b9 --- /dev/null +++ b/src/areachart/areachartitem_p.h @@ -0,0 +1,67 @@ +#ifndef AREACHARTITEM_H +#define AREACHARTITEM_H + +#include "qchartglobal.h" +#include "linechartitem_p.h" +#include + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class ChartPresenter; +class QAreaSeries; +class AreaChartItem; + +class AreaChartItem : public QObject ,public ChartItem +{ + Q_OBJECT +public: + AreaChartItem(ChartPresenter* presenter, QAreaSeries* areaSeries, QGraphicsItem *parent = 0); + ~ AreaChartItem(); + + //from QGraphicsItem + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QPainterPath shape() const; + + void setPen(const QPen& pen); + void setBrush(const QBrush& brush); + void setPointsVisible(bool visible); + void updatePath(); +public slots: + void handleUpdated(); + void handleDomainChanged(const Domain& domain); + void handleGeometryChanged(const QRectF& size); + +private: + ChartPresenter* m_presenter; + QPainterPath m_path; + QAreaSeries* m_series; + LineChartItem* m_upper; + LineChartItem* m_lower; + QRectF m_rect; + QRectF m_clipRect; + QPen m_pen; + QBrush m_brush; +}; + +class AreaBoundItem : public LineChartItem +{ +public: + AreaBoundItem(AreaChartItem* item,ChartPresenter* presenter, QLineSeries* lineSeries):LineChartItem(presenter,lineSeries), + m_item(item){}; + + ~AreaBoundItem(){}; + + void applyGeometry(QVector& points){ + LineChartItem::applyGeometry(points); + m_item->updatePath(); + } + +private: + AreaChartItem* m_item; + +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif diff --git a/src/areachart/qareaseries.cpp b/src/areachart/qareaseries.cpp new file mode 100644 index 0000000..5b6ce2e --- /dev/null +++ b/src/areachart/qareaseries.cpp @@ -0,0 +1,115 @@ +#include "qareaseries.h" +#include "qlineseries.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +/*! + \class QAreaSeries + \brief The QAreaSeries class is used for making area charts. + + \mainclass + + An area chart is used to show quantitative data. It is based on line chart, in the way that area between axis and the line + is emphasized with color. Since the area chart is based on line chart, QAreaSeries constructor needs QLineSeries instance, + which defines "upper" boundary of the area. "Lower" boundary is defined by default by axis X. Instead of axis X "lower" boundary can be specified by other line. + In that case QAreaSeries should be initiated with two QLineSerie instances. Please note terms "upper" and "lower" boundary can be misleading in cases + where "lower" boundary had bigger values than the "upper" one, however the main point that area between these two boundary lines will be filled. + + \image areachart.png + + Creating basic area chart is simple: + \code + QLineSeries* lineSeries = new QLineSeries(); + series->add(0, 6); + series->add(2, 4); + QAreaSeries* areaSeries = new QAreaSeries(lineSeries); + ... + chartView->addSeries(areaSeries); + \endcode +*/ + +/*! + \fn virtual QSeriesType QAreaSeries::type() const + \brief Returns type of series. + \sa QSeries, QSeriesType +*/ + +/*! + \fn QLineSeries* QAreaSeries::upperSeries() const + \brief Returns upperSeries used to define one of area boundaries. +*/ + +/*! + \fn QLineSeries* QAreaSeries::lowerSeries() const + \brief Returns lowerSeries used to define one of area boundaries. Note if QAreaSeries where counstucted wihtout a\ lowerSeries + this function return Null pointer. +*/ + +/*! + \fn QPen QAreaSeries::pen() const + \brief Returns the pen used to draw line for this series. + \sa setPen() +*/ + +/*! + \fn QPen QAreaSeries::brush() const + \brief Returns the brush used to draw line for this series. + \sa setBrush() +*/ + +/*! + \fn bool QAreaSeries::pointsVisible() const + \brief Returns if the points are drawn for this series. + \sa setPointsVisible() +*/ + +/*! + \fn void QAreaSeries::updated() + \brief \internal +*/ + +/*! + Constructs area series object which is a child of \a upperSeries. Area will be spanned between \a + upperSeries line and \a lowerSeries line. If no \a lowerSeries is passed to constructor, area is specified by axis x (y=0) instead. + When series object is added to QChartView or QChart instance ownerships is transfered. +*/ +QAreaSeries::QAreaSeries(QLineSeries* upperSeries,QLineSeries* lowerSeries):QSeries(upperSeries), +m_upperSeries(upperSeries), +m_lowerSeries(lowerSeries), +m_pointsVisible(false) +{ +} +/*! + Destroys the object. Series added to QChartView or QChart instances are owned by those, + and are deleted when mentioned object are destroyed. +*/ +QAreaSeries::~QAreaSeries() +{ +} + +/*! + Sets \a pen used for drawing area outline. +*/ +void QAreaSeries::setPen(const QPen& pen) +{ + m_pen=pen; +} + +/*! + Sets \a brush used for filling the area. +*/ +void QAreaSeries::setBrush(const QBrush& brush) +{ + m_brush=brush; +} +/*! + Sets if data points are \a visible and should be drawn on line. +*/ +void QAreaSeries::setPointsVisible(bool visible) +{ + m_pointsVisible=visible; +} + +#include "moc_qareaseries.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/areachart/qareaseries.h b/src/areachart/qareaseries.h new file mode 100644 index 0000000..d7f8cff --- /dev/null +++ b/src/areachart/qareaseries.h @@ -0,0 +1,48 @@ +#ifndef QAREASERIES_H_ +#define QAREASERIES_H_ + +#include "qchartglobal.h" +#include "qseries.h" +#include +#include +#include + +QTCOMMERCIALCHART_BEGIN_NAMESPACE +class QLineSeries; + +class QTCOMMERCIALCHART_EXPORT QAreaSeries : public QSeries +{ + Q_OBJECT +public: + QAreaSeries(QLineSeries* upperSeries,QLineSeries* lowerSeries=0); + virtual ~QAreaSeries(); + +public: // from QChartSeries + virtual QSeriesType type() const { return QSeries::SeriesTypeArea;} + + QLineSeries* upperSeries() const { return m_upperSeries;} + QLineSeries* lowerSeries() const { return m_lowerSeries;} + + void setPen(const QPen& pen); + QPen pen() const { return m_pen;} + + void setBrush(const QBrush& brush); + QBrush brush() const { return m_brush;} + + void setPointsVisible(bool visible); + bool pointsVisible() const {return m_pointsVisible;} + +signals: + void updated(); + +private: + QBrush m_brush; + QPen m_pen; + QLineSeries* m_upperSeries; + QLineSeries* m_lowerSeries; + bool m_pointsVisible; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif diff --git a/src/chartdataset.cpp b/src/chartdataset.cpp index 896de3d..d5441ca 100644 --- a/src/chartdataset.cpp +++ b/src/chartdataset.cpp @@ -2,6 +2,7 @@ #include "qchartaxis.h" //series #include "qlineseries.h" +#include "qareaseries.h" #include "qbarseries.h" #include "qstackedbarseries.h" #include "qpercentbarseries.h" @@ -38,13 +39,13 @@ void ChartDataSet::addSeries(QSeries* series, QChartAxis *axisY) { // TODO: we should check the series not already added - series->setParent(this); // take ownership + series->setParent(this);// take ownership clearDomains(); if(axisY==0) axisY = m_axisY; - axisY->setParent(this); // take ownership + axisY->setParent(this);// take ownership - QList seriesList = m_seriesMap.values(axisY); + QList seriesList = m_seriesMap.values(axisY); QList domainList = m_domainMap.values(axisY); @@ -58,12 +59,12 @@ void ChartDataSet::addSeries(QSeries* series, QChartAxis *axisY) { case QSeries::SeriesTypeLine: { - QLineSeries* xyseries = static_cast(series); + QLineSeries* lineSeries = static_cast(series); - for (int i = 0; i < xyseries->count(); i++) + for (int i = 0; i < lineSeries->count(); i++) { - qreal x = xyseries->x(i); - qreal y = xyseries->y(i); + qreal x = lineSeries->x(i); + qreal y = lineSeries->y(i); domain.m_minX = qMin(domain.m_minX,x); domain.m_minY = qMin(domain.m_minY,y); domain.m_maxX = qMax(domain.m_maxX,x); @@ -71,6 +72,45 @@ void ChartDataSet::addSeries(QSeries* series, QChartAxis *axisY) } break; } + case QSeries::SeriesTypeArea: { + + QAreaSeries* areaSeries = static_cast(series); + + QLineSeries* upperSeries = areaSeries->upperSeries(); + QLineSeries* lowerSeries = areaSeries->lowerSeries(); + + for (int i = 0; i < upperSeries->count(); i++) + { + qreal x = upperSeries->x(i); + qreal y = upperSeries->y(i); + domain.m_minX = qMin(domain.m_minX,x); + domain.m_minY = qMin(domain.m_minY,y); + domain.m_maxX = qMax(domain.m_maxX,x); + domain.m_maxY = qMax(domain.m_maxY,y); + } + if(lowerSeries){ + for (int i = 0; i < lowerSeries->count(); i++) + { + qreal x = lowerSeries->x(i); + qreal y = lowerSeries->y(i); + domain.m_minX = qMin(domain.m_minX,x); + domain.m_minY = qMin(domain.m_minY,y); + domain.m_maxX = qMax(domain.m_maxX,x); + domain.m_maxY = qMax(domain.m_maxY,y); + }} + break; + } + case QSeries::SeriesTypeBar: { + qDebug() << "QChartSeries::SeriesTypeBar"; + QBarSeries* barSeries = static_cast(series); + qreal x = barSeries->categoryCount(); + qreal y = barSeries->max(); + domain.m_minX = qMin(domain.m_minX,x); + domain.m_minY = qMin(domain.m_minY,y); + domain.m_maxX = qMax(domain.m_maxX,x); + domain.m_maxY = qMax(domain.m_maxY,y); + break; + } case QSeries::SeriesTypeBar: { qDebug() << "QChartSeries::SeriesTypeBar"; QBarSeries* barSeries = static_cast(series); @@ -173,11 +213,11 @@ void ChartDataSet::removeSeries(QSeries* series) { QList keys = m_seriesMap.uniqueKeys(); foreach(QChartAxis* axis , keys) { - if(m_seriesMap.contains(axis,series)){ + if(m_seriesMap.contains(axis,series)) { emit seriesRemoved(series); m_seriesMap.remove(axis,series); //remove axis if no longer there - if(!m_seriesMap.contains(axis)){ + if(!m_seriesMap.contains(axis)) { emit axisRemoved(axis); m_domainMap.remove(axis); if(axis != m_axisY) @@ -194,7 +234,7 @@ void ChartDataSet::removeAllSeries() QList keys = m_seriesMap.uniqueKeys(); foreach(QChartAxis* axis , keys) { QList seriesList = m_seriesMap.values(axis); - for(int i =0 ; i < seriesList.size();i++ ) + for(int i =0; i < seriesList.size();i++ ) { emit seriesRemoved(seriesList.at(i)); delete(seriesList.at(i)); @@ -280,7 +320,6 @@ void ChartDataSet::setDomain(int index,QChartAxis* axis) emit axisRangeChanged(axisX(),labels); } - void ChartDataSet::clearDomains(int toIndex) { Q_ASSERT(toIndex>=0); @@ -294,10 +333,10 @@ void ChartDataSet::clearDomains(int toIndex) QList domains = m_domainMap.values(key); m_domainMap.remove(key); int i = domains.size() - toIndex - 1; - while(i--){ + while(i--) { domains.removeFirst(); } - for(int j=domains.size()-1; j>=0 ;j--) + for(int j=domains.size()-1; j>=0;j--) m_domainMap.insert(key,domains.at(j)); } } @@ -313,7 +352,7 @@ void ChartDataSet::addDomain(const QRectF& rect, const QRectF& viewport) Domain domain; - foreach (QChartAxis* axis , domainList){ + foreach (QChartAxis* axis , domainList) { domain = m_domainMap.value(axis).subDomain(rect,viewport.width(),viewport.height()); m_domainMap.insert(axis,domain); } @@ -328,7 +367,7 @@ QChartAxis* ChartDataSet::axisY(QSeries* series) const QList keys = m_seriesMap.uniqueKeys(); foreach(QChartAxis* axis , keys) { - if(m_seriesMap.contains(axis,series)){ + if(m_seriesMap.contains(axis,series)) { return axis; } } @@ -343,19 +382,19 @@ QStringList ChartDataSet::createLabels(QChartAxis* axis,qreal min, qreal max) int ticks = axis->ticksCount()-1; - for(int i=0; i<= ticks; i++){ + for(int i=0; i<= ticks; i++) { qreal value = min + (i * (max - min)/ ticks); QString label = axis->axisTickLabel(value); - if(label.isEmpty()){ + if(label.isEmpty()) { labels << QString::number(value); - }else{ + } + else { labels << label; } } return labels; } - void ChartDataSet::handleRangeChanged(QChartAxis* axis) { qreal min = axis->min(); @@ -399,16 +438,16 @@ void ChartDataSet::handleRangeChanged(QChartAxis* axis) setDomain(m_domainIndex,axis); } - } void ChartDataSet::handleTickChanged(QChartAxis* axis) { - if(axis==axisX()){ + if(axis==axisX()) { Domain domain = m_domainMap.value(axisY()); QStringList labels = createLabels(axis,domain.m_minX,domain.m_maxX); emit axisRangeChanged(axis,labels); - }else{ + } + else { Domain domain = m_domainMap.value(axis); QStringList labels = createLabels(axis,domain.m_minY,domain.m_maxY); emit axisRangeChanged(axis,labels); diff --git a/src/chartpresenter.cpp b/src/chartpresenter.cpp index d9ac176..a4d76e2 100644 --- a/src/chartpresenter.cpp +++ b/src/chartpresenter.cpp @@ -8,12 +8,14 @@ #include "qstackedbarseries.h" #include "qpercentbarseries.h" #include "qlineseries.h" +#include "qareaseries.h" #include "qpieseries.h" #include "qscatterseries.h" #include "qsplineseries.h" //items #include "axisitem_p.h" #include "axisanimationitem_p.h" +#include "areachartitem_p.h" #include "barpresenter_p.h" #include "stackedbarpresenter_p.h" #include "percentbarpresenter_p.h" @@ -111,6 +113,7 @@ void ChartPresenter::handleSeriesAdded(QSeries* series) switch(series->type()) { case QSeries::SeriesTypeLine: { + QLineSeries* lineSeries = static_cast(series); LineChartItem* item; if(m_options.testFlag(QChart::SeriesAnimations)){ @@ -119,14 +122,24 @@ void ChartPresenter::handleSeriesAdded(QSeries* series) item = new LineChartItem(this,lineSeries,m_chart); } m_chartTheme->decorate(item,lineSeries,m_chartItems.count()); - QObject::connect(this,SIGNAL(geometryChanged(const QRectF&)),item,SLOT(handleGeometryChanged(const QRectF&))); - QObject::connect(lineSeries,SIGNAL(pointReplaced(int)),item,SLOT(handlePointReplaced(int))); - QObject::connect(lineSeries,SIGNAL(pointAdded(int)),item,SLOT(handlePointAdded(int))); - QObject::connect(lineSeries,SIGNAL(pointRemoved(int)),item,SLOT(handlePointRemoved(int))); - QObject::connect(lineSeries,SIGNAL(updated()),item,SLOT(handleUpdated())); m_chartItems.insert(series,item); if(m_rect.isValid()) item->handleGeometryChanged(m_rect); - item->handleUpdated(); + break; + } + + case QSeries::SeriesTypeArea: { + + QAreaSeries* areaSeries = static_cast(series); + AreaChartItem* item; + if(m_options.testFlag(QChart::SeriesAnimations)) { + item = new AreaChartItem(this,areaSeries,m_chart); + } + else { + item = new AreaChartItem(this,areaSeries,m_chart); + } + m_chartTheme->decorate(item,areaSeries,m_chartItems.count()); + m_chartItems.insert(series,item); + if(m_rect.isValid()) item->handleGeometryChanged(m_rect); break; } diff --git a/src/charttheme.cpp b/src/charttheme.cpp index ba15e5f..744bf41 100644 --- a/src/charttheme.cpp +++ b/src/charttheme.cpp @@ -9,6 +9,7 @@ #include "qstackedbarseries.h" #include "qpercentbarseries.h" #include "qlineseries.h" +#include "qareaseries.h" #include "qscatterseries.h" #include "qpieseries.h" #include "qpieslice.h" @@ -20,6 +21,7 @@ #include "stackedbarpresenter_p.h" #include "percentbarpresenter_p.h" #include "linechartitem_p.h" +#include "areachartitem_p.h" #include "scatterpresenter_p.h" #include "piepresenter_p.h" #include "splinepresenter_p.h" @@ -61,7 +63,7 @@ ChartTheme* ChartTheme::createTheme(QChart::ChartTheme theme) { switch(theme) { case QChart::ChartThemeDefault: - return new ChartThemeIcy(); + return new ChartTheme(); case QChart::ChartThemeVanilla: return new ChartThemeVanilla(); case QChart::ChartThemeIcy: @@ -92,6 +94,12 @@ void ChartTheme::decorate(ChartItem* item, QSeries* series,int count) decorate(i,s,count); break; } + case QSeries::SeriesTypeArea: { + QAreaSeries* s = static_cast(series); + AreaChartItem* i = static_cast(item); + decorate(i,s,count); + break; + } case QSeries::SeriesTypeBar: { QBarSeries* b = static_cast(series); BarPresenter* i = static_cast(item); @@ -131,6 +139,28 @@ void ChartTheme::decorate(ChartItem* item, QSeries* series,int count) } +void ChartTheme::decorate(AreaChartItem* item, QAreaSeries* series,int count) +{ + QPen pen; + QBrush brush; + + if(pen != series->pen()){ + item->setPen(series->pen()); + }else{ + pen.setColor(m_seriesColor.at(count%m_seriesColor.size())); + pen.setWidthF(2); + item->setPen(pen); + } + + if(brush != series->brush()){ + item->setBrush(series->brush()); + }else{ + QBrush brush(m_seriesColor.at(count%m_seriesColor.size())); + item->setBrush(brush); + } +} + + void ChartTheme::decorate(LineChartItem* item, QLineSeries* series,int count) { QPen pen; diff --git a/src/charttheme_p.h b/src/charttheme_p.h index 5ef15f1..225411d 100644 --- a/src/charttheme_p.h +++ b/src/charttheme_p.h @@ -23,6 +23,8 @@ class PiePresenter; class QPieSeries; class SplinePresenter; class QSplineSeries; +class AreaChartItem; +class QAreaSeries; class ChartTheme { @@ -36,7 +38,8 @@ public: void decorate(BarPresenter* item, QBarSeries* series,int count); void decorate(StackedBarPresenter* item, QStackedBarSeries* series,int count); void decorate(PercentBarPresenter* item, QPercentBarSeries* series,int count); - void decorate(LineChartItem* item, QLineSeries*, int count); + void decorate(LineChartItem* item, QLineSeries* series,int count); + void decorate(AreaChartItem* item, QAreaSeries* series,int count); void decorate(ScatterPresenter* presenter, QScatterSeries* series, int count); void decorate(PiePresenter* item, QPieSeries* series, int count); void decorate(QChartAxis* axis,AxisItem* item); diff --git a/src/linechart/linechartitem.cpp b/src/linechart/linechartitem.cpp index 484fb20..16fa31d 100644 --- a/src/linechart/linechartitem.cpp +++ b/src/linechart/linechartitem.cpp @@ -15,6 +15,12 @@ m_items(this) { //m_items.setZValue(ChartPresenter::LineChartZValue); setZValue(ChartPresenter::LineChartZValue); + + QObject::connect(presenter,SIGNAL(geometryChanged(const QRectF&)),this,SLOT(handleGeometryChanged(const QRectF&))); + QObject::connect(series,SIGNAL(pointReplaced(int)),this,SLOT(handlePointReplaced(int))); + QObject::connect(series,SIGNAL(pointAdded(int)),this,SLOT(handlePointAdded(int))); + QObject::connect(series,SIGNAL(pointRemoved(int)),this,SLOT(handlePointRemoved(int))); + QObject::connect(series,SIGNAL(updated()),this,SLOT(handleUpdated())); } QRectF LineChartItem::boundingRect() const diff --git a/src/src.pro b/src/src.pro index e637ede..ecf2bd1 100644 --- a/src/src.pro +++ b/src/src.pro @@ -29,6 +29,7 @@ PUBLIC_HEADERS += qchart.h \ qseries.h \ qchartview.h include(linechart/linechart.pri) +include(areachart/areachart.pri) include(barchart/barchart.pri) include(piechart/piechart.pri) include(scatterseries/scatter.pri)