/**************************************************************************** ** ** Copyright (C) 2012 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 "qxyseries.h" #include "qxyseries_p.h" #include "domain_p.h" #include "legendmarker_p.h" #include QTCOMMERCIALCHART_BEGIN_NAMESPACE /*! \class QXYSeries \brief The QXYSeries class is a base class for line, spline and scatter series. */ /*! \fn QPen QXYSeries::pen() const \brief Returns pen used to draw points for series. \sa setPen() */ /*! \fn QBrush QXYSeries::brush() const \brief Returns brush used to draw points for series. \sa setBrush() */ /*! \fn void QXYSeries::clicked(const QPointF& point) \brief Signal is emitted when user clicks the \a point on chart. */ /*! \fn void QXYSeries::selected() The signal is emitted if the user selects/deselects the XY series. The logic for maintaining selections should be implemented by the user of QXYSeries API. */ /*! \fn void QXYSeriesPrivate::pointReplaced(int index) \brief \internal \a index */ /*! \fn void QXYSeriesPrivate::pointAdded(int index) \brief \internal \a index */ /*! \fn void QXYSeriesPrivate::pointRemoved(int index) \brief \internal \a index */ /*! \fn void QXYSeriesPrivate::updated() \brief \internal */ /*! \fn int QXYSeries::mapFirst() const Returns the index of the model's item that is used as a first one for the series. \sa mapCount() */ /*! \fn int QXYSeries::mapCount() const Returns the number of the items that are taken from the model. If -1 it means all the items of the model following the first one are used. \sa mapFirst() */ /*! \internal Constructs empty series object which is a child of \a parent. When series object is added to QChartView or QChart instance ownerships is transferred. */ QXYSeries::QXYSeries(QXYSeriesPrivate &d,QObject *parent):QSeries(d,parent) { } /*! Destroys the object. Series added to QChartView or QChart instances are owned by those, and are deleted when mentioned object are destroyed. */ QXYSeries::~QXYSeries() { } /*! Adds data point \a x \a y to the series. Points are connected with lines on the chart. */ void QXYSeries::append(qreal x,qreal y) { Q_D(QXYSeries); Q_ASSERT(d->m_x.size() == d->m_y.size()); d->m_x<m_y<pointAdded(d->m_x.size()-1); } /*! This is an overloaded function. Adds data \a point to the series. Points are connected with lines on the chart. */ void QXYSeries::append(const QPointF &point) { append(point.x(),point.y()); } /*! This is an overloaded function. Adds list of data \a points to the series. Points are connected with lines on the chart. */ void QXYSeries::append(const QList points) { foreach(const QPointF& point , points) { append(point.x(),point.y()); } } /*! Modifies \a y value for given \a x a value. */ void QXYSeries::replace(qreal x,qreal y) { Q_D(QXYSeries); int index = d->m_x.indexOf(x); d->m_x[index] = x; d->m_y[index] = y; emit d->pointReplaced(index); } /*! This is an overloaded function. Replaces current y value of for given \a point x value with \a point y value. */ void QXYSeries::replace(const QPointF &point) { replace(point.x(),point.y()); } /*! Removes first \a x value and related y value. */ void QXYSeries::remove(qreal x) { Q_D(QXYSeries); int index = d->m_x.indexOf(x); if (index == -1) return; d->m_x.remove(index); d->m_y.remove(index); emit d->pointRemoved(index); } /*! Removes current \a x and \a y value. */ void QXYSeries::remove(qreal x,qreal y) { Q_D(QXYSeries); int index =-1; do { index = d->m_x.indexOf(x,index+1); } while (index !=-1 && d->m_y.at(index)!=y); if (index==-1) return; d->m_x.remove(index); d->m_y.remove(index); emit d->pointRemoved(index); } /*! Removes current \a point x value. Note \a point y value is ignored. */ void QXYSeries::remove(const QPointF &point) { remove(point.x(),point.y()); } /*! Removes all data points from the series. */ void QXYSeries::removeAll() { Q_D(QXYSeries); d->m_x.clear(); d->m_y.clear(); } /*! \internal \a pos */ qreal QXYSeries::x(int pos) const { Q_D(const QXYSeries); if (d->m_model) { if (d->m_mapOrientation == Qt::Vertical) // consecutive data is read from model's column return d->m_model->data(d->m_model->index(pos + d->m_mapFirst, d->m_mapX), Qt::DisplayRole).toDouble(); else // consecutive data is read from model's row return d->m_model->data(d->m_model->index(d->m_mapX, pos + d->m_mapFirst), Qt::DisplayRole).toDouble(); } else { // model is not specified, return the data from series' internal data store return d->m_x.at(pos); } } /*! \internal \a pos */ qreal QXYSeries::y(int pos) const { Q_D(const QXYSeries); if (d->m_model) { if (d->m_mapOrientation == Qt::Vertical) // consecutive data is read from model's column return d->m_model->data(d->m_model->index(pos + d->m_mapFirst, d->m_mapY), Qt::DisplayRole).toDouble(); else // consecutive data is read from model's row return d->m_model->data(d->m_model->index(d->m_mapY, pos + d->m_mapFirst), Qt::DisplayRole).toDouble(); } else { // model is not specified, return the data from series' internal data store return d->m_y.at(pos); } } /*! Returns number of data points within series. */ int QXYSeries::count() const { Q_D(const QXYSeries); Q_ASSERT(d->m_x.size() == d->m_y.size()); if (d->m_model) { if (d->m_mapOrientation == Qt::Vertical) { // data is in a column. Return the number of mapped items if the model's column have enough items // or the number of items that can be mapped if (d->m_mapLimited) return qMin(d->m_mapCount, qMax(d->m_model->rowCount() - d->m_mapFirst, 0)); else return qMax(d->m_model->rowCount() - d->m_mapFirst, 0); } else { // data is in a row. Return the number of mapped items if the model's row have enough items // or the number of items that can be mapped if (d->m_mapLimited) return qMin(d->m_mapCount, qMax(d->m_model->columnCount() - d->m_mapFirst, 0)); else return qMax(d->m_model->columnCount() - d->m_mapFirst, 0); } } // model is not specified, return the number of points in the series internal data store return d->m_x.size(); } /*! Returns the data points of the series. */ QList QXYSeries::data() { Q_D(QXYSeries); QList data; for (int i(0); i < d->m_x.count() && i < d->m_y.count(); i++) data.append(QPointF(d->m_x.at(i), d->m_y.at(i))); return data; } /*! Sets \a pen used for drawing points on the chart. If the pen is not defined, the pen from chart theme is used. \sa QChart::setTheme() */ void QXYSeries::setPen(const QPen &pen) { Q_D(QXYSeries); if (d->m_pen!=pen) { d->m_pen = pen; emit d->updated(); } } QPen QXYSeries::pen() const { Q_D(const QXYSeries); return d->m_pen; } /*! Sets \a brush used for drawing points on the chart. If the brush is not defined, brush from chart theme setting is used. \sa QChart::setTheme() */ void QXYSeries::setBrush(const QBrush &brush) { Q_D(QXYSeries); if (d->m_brush!=brush) { d->m_brush = brush; emit d->updated(); } } QBrush QXYSeries::brush() const { Q_D(const QXYSeries); return d->m_brush; } /*! Sets if data points are \a visible and should be drawn on line. */ void QXYSeries::setPointsVisible(bool visible) { Q_D(QXYSeries); if (d->m_pointsVisible != visible){ d->m_pointsVisible = visible; emit d->updated(); } } /*! Returns true if drawing the data points of the series is enabled. */ bool QXYSeries::pointsVisible() const { Q_D(const QXYSeries); return d->m_pointsVisible; } /*! Stream operator for adding a data \a point to the series. \sa append() */ QXYSeries& QXYSeries::operator<< (const QPointF &point) { append(point); return *this; } /*! Stream operator for adding a list of \a points to the series. \sa append() */ QXYSeries& QXYSeries::operator<< (const QList points) { append(points); return *this; } /*! \internal */ void QXYSeries::modelUpdated(QModelIndex topLeft, QModelIndex bottomRight) { Q_UNUSED(bottomRight) Q_D(QXYSeries); if (d->m_mapOrientation == Qt::Vertical) { if (topLeft.row() >= d->m_mapFirst && (!d->m_mapLimited || topLeft.row() < d->m_mapFirst + d->m_mapCount)) emit d->pointReplaced(topLeft.row() - d->m_mapFirst); } else { if (topLeft.column() >= d->m_mapFirst && (!d->m_mapLimited || topLeft.column() < d->m_mapFirst + d->m_mapCount)) emit d->pointReplaced(topLeft.column() - d->m_mapFirst); } } /*! \internal */ void QXYSeries::modelDataAboutToBeAdded(QModelIndex parent, int start, int end) { Q_UNUSED(parent) // Q_UNUSED(end) Q_D(QXYSeries); if (d->m_mapLimited) { if (start >= d->m_mapFirst + d->m_mapCount) { // the added data is below mapped area // therefore it has no relevance return; } else { // the added data is in the mapped area or before it and update is needed // check how many mapped items there is currently (before new items are added) // if the number of items currently is equal the m_mapCount then some needs to be removed from xychartitem // internal storage before new ones can be added int itemsToRemove = qMin(count() - qMax(start - d->m_mapFirst, 0), end - start + 1); if (d->m_mapCount == count()) { for (int i = 0; i < itemsToRemove; i++) emit d->pointRemoved(qMin(end, count()) - i); } } } else { // map is not limited (it includes all the items starting from m_mapFirst till the end of model) // nothing to do // emit pointAdded(qMax(start - m_mapFirst, 0)); } } /*! \internal */ void QXYSeries::modelDataAdded(QModelIndex parent, int start, int end) { Q_UNUSED(parent) // Q_UNUSED(end) Q_D(QXYSeries); if (d->m_mapLimited) { if (start >= d->m_mapFirst + d->m_mapCount) { // the added data is below mapped area // therefore it has no relevance return; } else { // the added data is in the mapped area or before it // update needed if (count() > 0) { int toBeAdded = qMin(d->m_mapCount - (start - d->m_mapFirst), end - start + 1); for (int i = 0; i < toBeAdded; i++) if (start + i >= d->m_mapFirst) emit d->pointAdded(start + i); } } } else { // map is not limited (it included all the items starting from m_mapFirst till the end of model) for (int i = 0; i < end - start + 1; i++) emit d->pointAdded(start + i); } } /*! \internal */ void QXYSeries::modelDataAboutToBeRemoved(QModelIndex parent, int start, int end) { Q_UNUSED(parent) // Q_UNUSED(end) Q_D(QXYSeries); if (d->m_mapLimited) { if (start >= d->m_mapFirst + d->m_mapCount) { // the removed data is below mapped area // therefore it has no relevance return; } else { // the removed data is in the mapped area or before it // update needed // check how many items need to be removed from the xychartitem storage // the number equals the number of items that are removed and that lay before // or in the mapped area. Items that lay beyond the map do not count // the max is the current number of items in storage (count()) int itemsToRemove = qMin(count(), qMin(end, d->m_mapFirst + d->m_mapCount - 1) - start + 1); for (int i = 0; i < itemsToRemove; i++) emit d->pointRemoved(start); } } else { // map is not limited (it included all the items starting from m_mapFirst till the end of model) for (int i = 0; i < end - start + 1; i++) emit d->pointRemoved(start); } } /*! \internal */ void QXYSeries::modelDataRemoved(QModelIndex parent, int start, int end) { Q_UNUSED(parent) Q_UNUSED(end) Q_D(QXYSeries); // how many items there were before data was removed // int oldCount = count() - 1; if (d->m_mapLimited) { if (start >= d->m_mapFirst + d->m_mapCount) { // the removed data is below mapped area // therefore it has no relevance return; } else { // if the current items count in the whole model is bigger than the index of the last item // that was removed than it means there are some extra items available int removedItemsCount = qMin(count(), qMin(end, d->m_mapFirst + d->m_mapCount - 1) - start + 1); int extraItemsAvailable = 0; if (d->m_mapOrientation == Qt::Vertical) { extraItemsAvailable = qMax(d->m_model->rowCount() + (end - start + 1) - qMax(end + 1, d->m_mapFirst + d->m_mapCount), 0); } else { extraItemsAvailable = qMax(d->m_model->columnCount() + (end - start + 1) - qMax(end + 1, d->m_mapFirst + d->m_mapCount), 0); } // if there are excess items available (below the mapped area) use them to repopulate mapped area int toBeAdded = qMin(extraItemsAvailable, removedItemsCount); for (int k = 0; k < toBeAdded; k++) emit d->pointAdded(d->m_mapFirst + d->m_mapCount - removedItemsCount + k); } } else { // data was removed from XYSeries interal storage. Nothing more to do } } /*! \fn bool QXYSeries::setModel(QAbstractItemModel *model) Sets the \a model to be used as a data source \sa setModelMapping(), setModelMappingRange() */ bool QXYSeries::setModel(QAbstractItemModel *model) { Q_D(QXYSeries); // disconnect signals from old model if (d->m_model) { QObject::disconnect(d->m_model, 0, this, 0); d->m_mapX = -1; d->m_mapY = -1; d->m_mapFirst = 0; d->m_mapCount = 0; d->m_mapLimited = false; d->m_mapOrientation = Qt::Vertical; } // set new model if (model) { d->m_model = model; return true; } else { d->m_model = 0; return false; } } /*! \fn bool QXYSeries::setModelMapping(int modelX, int modelY, Qt::Orientation orientation) Sets the \a modelX to be used as a data source for x coordinate and \a modelY to be used as a data source for y coordinate. The \a orientation parameter specifies whether the data is in columns or in rows. \sa setModel(), setModelMappingRange() */ void QXYSeries::setModelMapping(int modelX, int modelY, Qt::Orientation orientation) { Q_D(QXYSeries); if (d->m_model == 0) return; d->m_mapX = modelX; d->m_mapY = modelY; d->m_mapFirst = 0; d->m_mapOrientation = orientation; if (d->m_mapOrientation == Qt::Vertical) { connect(d->m_model,SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(modelUpdated(QModelIndex,QModelIndex))); connect(d->m_model,SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(modelDataAboutToBeAdded(QModelIndex,int,int))); connect(d->m_model,SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(modelDataAdded(QModelIndex,int,int))); connect(d->m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(modelDataAboutToBeRemoved(QModelIndex,int,int))); connect(d->m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(modelDataRemoved(QModelIndex,int,int))); } else { connect(d->m_model,SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(modelUpdated(QModelIndex,QModelIndex))); connect(d->m_model,SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(modelDataAboutToBeAdded(QModelIndex,int,int))); connect(d->m_model,SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(modelDataAdded(QModelIndex,int,int))); connect(d->m_model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(modelDataAboutToBeRemoved(QModelIndex,int,int))); connect(d->m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(modelDataRemoved(QModelIndex,int,int))); } } /*! \fn bool QXYSeries::setModelMappingRange(int first, int count) Allows limiting the model mapping. Parameter \a first specifies which element of the model should be used as a first one of the series. Parameter \a count specifies how many elements should be mapped. If count is not specified (defaults to -1) then all the items following \a first item in a model are used. \sa setModel(), setModelMapping() */ void QXYSeries::setModelMappingRange(int first, int count) { Q_D(QXYSeries); d->m_mapFirst = first; if (count == 0) { d->m_mapLimited = false; } else { d->m_mapCount = count; d->m_mapLimited = true; } } int QXYSeries::mapFirst() const { Q_D(const QXYSeries); return d->m_mapFirst; } int QXYSeries::mapCount() const { Q_D(const QXYSeries); return d->m_mapCount; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QXYSeriesPrivate::QXYSeriesPrivate(QXYSeries *q): QSeriesPrivate(q), m_mapX(-1), m_mapY(-1), m_mapFirst(0), m_mapCount(0), m_mapLimited(false), m_mapOrientation( Qt::Vertical), m_pointsVisible(false) { } void QXYSeriesPrivate::scaleDomain(Domain& domain) { qreal minX(domain.minX()); qreal minY(domain.minY()); qreal maxX(domain.maxX()); qreal maxY(domain.maxY()); int tickXCount(domain.tickXCount()); int tickYCount(domain.tickYCount()); Q_Q(QXYSeries); for (int i = 0; i < q->count(); i++) { qreal x = q->x(i); qreal y = q->y(i); minX = qMin(minX, x); minY = qMin(minY, y); maxX = qMax(maxX, x); maxY = qMax(maxY, y); } domain.setRangeX(minX,maxX,tickXCount); domain.setRangeY(minY,maxY,tickYCount); } QList QXYSeriesPrivate::createLegendMarker(QLegend* legend) { Q_Q(QXYSeries); QList list; return list << new XYLegendMarker(q,legend); } #include "moc_qxyseries.cpp" #include "moc_qxyseries_p.cpp" QTCOMMERCIALCHART_END_NAMESPACE