/*------------------------------------------------------------------------------ -- This file is a part of the ColorMapChart API -- Copyright (C) 2016, Plasma Physics Laboratory - CNRS -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program; if not, write to the Free Software -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -------------------------------------------------------------------------------*/ /*-- Author : Hugo Winter -- Mail : hugo.winter@lpp.polytechnique.fr ----------------------------------------------------------------------------*/ #include "qcolormapseries.h" #include #include #include #include #include #include #include #include #include #include #include "qcolorbaraxis.h" QT_CHARTS_BEGIN_NAMESPACE /*! \internal Constructs empty series object which is a child of \a parent. When series object is added to QChart instance ownerships is transferred. */ QColorMapSeries::QColorMapSeries(QObject *parent) : QAbstractSeries(*new QColorMapSeriesPrivate(this), parent) { } /*! \internal Constructs empty series object which is a child of \a parent. When series object is added to QChart instance ownerships is transferred. */ QColorMapSeries::QColorMapSeries(QColorMapSeriesPrivate &d, QObject *parent) : QAbstractSeries(d, parent) { } /*! Destroys the object. Series added to QChart instances are owned by those, and are destroyed when QChart instances are destroyed. */ QColorMapSeries::~QColorMapSeries() { } /*! \fn virtual SeriesType QBoxPlotSeries::type() const \brief Returns type of series. \sa QAbstractSeries, SeriesType */ QAbstractSeries::SeriesType QColorMapSeries::type() const { return QAbstractSeries::SeriesTypeColorMap; } /*! Adds data part \a dataPart to the series.\n If \a copy is true, adds a copy of the data part instead. */ void QColorMapSeries::append(ColorMapDataPart* dataPart, bool copy) { Q_D(QColorMapSeries); if(copy) d->m_dataParts << new ColorMapDataPart(dataPart); else d->m_dataParts << dataPart; d->recomputeDataRange(); emit dataPartAdded(d->m_dataParts.count() - 1); } /*! This is an overloaded function.\n Adds data parts \a dataParts to the series.\n If \a copy is true, adds a copy of the data part instead. */ void QColorMapSeries::append(const QList &dataParts, bool copy) { foreach (ColorMapDataPart* dataPart , dataParts) append(dataPart,copy); } /*! Returns number of data parts within series. */ int QColorMapSeries::count() const { Q_D(const QColorMapSeries); return d->m_dataParts.count(); } /*! Stream operator for adding a data part \a point to the series. \sa append() */ QColorMapSeries &QColorMapSeries::operator <<(const ColorMapDataPart &dataPart) { append(new ColorMapDataPart(dataPart)); return *this; } /*! Stream operator for adding a vector of data parts \a dataParts to the series. \sa append() */ QColorMapSeries &QColorMapSeries::operator <<(const QList &dataParts) { append(dataParts); return *this; } /*! Returns a ColorMapData part containing a uniform grid of data points to be mapped in a ColorMapChart.\n The rectangle of data returned is determined by \a width and \height,\n from the position \a xpos , \a ypos (starting at the top left corner of the plot area).\n When there are more points than pixels, \a strategy is applied to determine which to choose. */ void QColorMapSeries::getUniformGrid(double minX, double maxX, double minY, double maxY, int width, int height, QVector &grid, QColorMapSeries::Strategy strategy) { Q_D(QColorMapSeries); d->getUniformGrid(minX, maxX,minY,maxY,width,height, grid, strategy); } //void QColorMapSeries::attachAxis(QAbstractAxis *axis) //{ // axis = static_cast(); // if(axis) // { // } // else // { // QAbstractSeries::attachAxis(axis); // } //} /*! 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 QColorMapSeries::setPen(const QPen &pen) { Q_D(QColorMapSeries); if (d->m_pen != pen) { //bool emitColorChanged = d->m_pen.color() != pen.color(); d->m_pen = pen; emit d->updated(); // if (emitColorChanged) // emit colorChanged(pen.color()); emit penChanged(pen); } } QPen QColorMapSeries::pen() const { Q_D(const QColorMapSeries); if (d->m_pen == QChartPrivate::defaultPen()) return QPen(); else 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 QColorMapSeries::setBrush(const QBrush &brush) { Q_D(QColorMapSeries); if (d->m_brush != brush) { d->m_brush = brush; emit d->updated(); } } QBrush QColorMapSeries::brush() const { Q_D(const QColorMapSeries); if (d->m_brush == QChartPrivate::defaultBrush()) return QBrush(); else return d->m_brush; } //void QColorMapSeries::setColor(const QColor &color) //{ // QPen p = pen(); // if (p.color() != color) // { // p.setColor(color); // setPen(p); // } //} //QColor QColorMapSeries::color() const //{ // return pen().color(); //} //void QColorMapSeries::setPointsVisible(bool visible) //{ // Q_D(QColorMapSeries); // if (d->m_pointsVisible != visible) // { // d->m_pointsVisible = visible; // emit d->updated(); // } //} //bool QColorMapSeries::pointsVisible() const //{ // Q_D(const QColorMapSeries); // return d->m_pointsVisible; //} //void QColorMapSeries::setPointLabelsFormat(const QString &format) //{ // Q_D(QColorMapSeries); // if (d->m_pointLabelsFormat != format) // { // d->m_pointLabelsFormat = format; // emit pointLabelsFormatChanged(format); // } //} //QString QColorMapSeries::pointLabelsFormat() const //{ // Q_D(const QColorMapSeries); // return d->m_pointLabelsFormat; //} //void QColorMapSeries::setPointLabelsVisible(bool visible) //{ // Q_D(QColorMapSeries); // if (d->m_pointLabelsVisible != visible) // { // d->m_pointLabelsVisible = visible; // emit pointLabelsVisibilityChanged(visible); // } //} //bool QColorMapSeries::pointLabelsVisible() const //{ // Q_D(const QColorMapSeries); // return d->m_pointLabelsVisible; //} //void QColorMapSeries::setPointLabelsFont(const QFont &font) //{ // Q_D(QColorMapSeries); // if (d->m_pointLabelsFont != font) { // d->m_pointLabelsFont = font; // emit pointLabelsFontChanged(font); // } //} //QFont QColorMapSeries::pointLabelsFont() const //{ // Q_D(const QColorMapSeries); // return d->m_pointLabelsFont; //} //void QColorMapSeries::setPointLabelsColor(const QColor &color) //{ // Q_D(QColorMapSeries); // if (d->m_pointLabelsColor != color) { // d->m_pointLabelsColor = color; // emit pointLabelsColorChanged(color); // } //} //QColor QColorMapSeries::pointLabelsColor() const //{ // Q_D(const QColorMapSeries); // if (d->m_pointLabelsColor == QChartPrivate::defaultPen().color()) // return QPen().color(); // else // return d->m_pointLabelsColor; //} //void QColorMapSeries::setPointLabelsClipping(bool enabled) //{ // Q_D(QColorMapSeries); // if (d->m_pointLabelsClipping != enabled) { // d->m_pointLabelsClipping = enabled; // emit pointLabelsClippingChanged(enabled); // } //} //bool QColorMapSeries::pointLabelsClipping() const //{ // Q_D(const QColorMapSeries); // return d->m_pointLabelsClipping; //} /*! Returns the minimum value of the series on the X axis. */ double QColorMapSeries::minX() { Q_D(QColorMapSeries); return d->m_minX; } /*! Returns the minimum value of the series on the Y axis. */ double QColorMapSeries::minY() { Q_D(QColorMapSeries); return d->m_minY; } /*! Returns the minimum value of the series on the Z axis. */ double QColorMapSeries::minZ() { Q_D(QColorMapSeries); return d->m_minZ; } /*! Returns the maximum value of the series on the X axis. */ double QColorMapSeries::maxX() { Q_D(QColorMapSeries); return d->m_maxX; } /*! Returns the maximum value of the series on the Y axis. */ double QColorMapSeries::maxY() { Q_D(QColorMapSeries); return d->m_maxY; } /*! Returns the maximum value of the series on the Z axis. */ double QColorMapSeries::maxZ() { Q_D(QColorMapSeries); return d->m_maxZ; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QColorMapSeriesPrivate::QColorMapSeriesPrivate(QColorMapSeries *q) : QAbstractSeriesPrivate(q), m_pen(QChartPrivate::defaultPen()), m_brush(QChartPrivate::defaultBrush()), m_pointsVisible(false), m_pointLabelsFormat(QLatin1String("@xPoint, @yPoint")), //TODO : change m_pointLabelsVisible(false), m_pointLabelsFont(QChartPrivate::defaultFont()), m_pointLabelsColor(QChartPrivate::defaultPen().color()), m_pointLabelsClipping(true) { } void QColorMapSeriesPrivate::initializeGraphics(QGraphicsItem* parent) { Q_Q(QColorMapSeries); ColorMapChart *colormap = new ColorMapChart(q,parent); m_item.reset(colormap); QAbstractSeriesPrivate::initializeGraphics(parent); } void QColorMapSeriesPrivate::initializeDomain() { domain()->setRange(m_minX, m_maxX, m_minY, m_maxY); } void QColorMapSeriesPrivate::initializeTheme(int index, ChartTheme* theme, bool forced) { // Q_Q(QColorMapSeries); // const QList gradients = theme->seriesGradients(); // const QList colors = theme->seriesColors(); // if (forced || QChartPrivate::defaultPen() == m_pen) { // QPen pen; // pen.setColor(ChartThemeManager::colorAt(gradients.at(index % gradients.size()), 0.0)); // pen.setWidthF(2); // q->setPen(pen); // } // if (forced || QChartPrivate::defaultBrush() == m_brush) { // QBrush brush(colors.at(index % colors.size())); // q->setBrush(brush); // } // if (forced || QChartPrivate::defaultPen().color() == m_pointLabelsColor) { // QColor color = theme->labelBrush().color(); // q->setPointLabelsColor(color); // } } QList QColorMapSeriesPrivate::createLegendMarkers(QLegend* legend) { Q_Q(QColorMapSeries); QList list; return list << new QColorMapLegendMarker(q,legend); } void QColorMapSeriesPrivate::initializeAxes() { } QAbstractAxis::AxisType QColorMapSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const { Q_UNUSED(orientation); return QAbstractAxis::AxisTypeValue; } QAbstractAxis* QColorMapSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const { Q_UNUSED(orientation); return new QValueAxis; } void QColorMapSeriesPrivate::getUniformGrid(double minX, double maxX, double minY, double maxY, int width, int height, QVector &grid, QColorMapSeries::Strategy strategy) { m_imageHeight = height; m_imageWidth = width; m_strategy = strategy; m_requestedMinX = minX; m_requestedMaxX = maxX; m_requestedMinY = minY; m_requestedMaxY = maxY; int index=0; std::generate(grid.begin(),grid.end(),[&index]{return index++;}); QFuture futureWatcher = QtConcurrent::map(grid,[this] (double &index) { return computePixelValue(index); }); futureWatcher.waitForFinished(); } double QColorMapSeriesPrivate::computePixelValue(double &index) { QVector cluster; cluster.reserve(m_imageHeight*m_imageWidth/1000); cluster.resize(0); double dx = (m_requestedMaxX - m_requestedMinX)/m_imageWidth; double dy = (m_requestedMaxY - m_requestedMinY)/m_imageHeight; int x = (int)index%m_imageWidth; int y = (int)index/m_imageWidth; this->buildCluster(x,y,dx,dy,cluster); if(m_strategy == QColorMapSeries::LastPixel) index = this->clusterStrategyLast(cluster); else if(m_strategy == QColorMapSeries::MeanPixel) index = this->clusterStrategyMean(cluster); else if(m_strategy == QColorMapSeries::MedianPixel) index = this->clusterStrategyMedian(cluster); return index;//not used } /*! Recompute the data range on X, Y and Z axes each time a ColorMapDataPart is added to the series.\n The minimum distance between 2 adjacent values on X and Y axis is added to the maximum on X and Y respectively\n in order to take account of the width of the last element. */ void QColorMapSeriesPrivate::recomputeDataRange() { //TODO : if non empty m_minX = m_dataParts.first()->timesSeries().first(); m_maxX = m_dataParts.last()->timesSeries().last(); m_minY = m_dataParts.last()->ySeries().last(); m_maxY = m_dataParts.last()->ySeries().first(); m_minZ = m_dataParts.first()->dataSeries().first(); m_maxZ = m_dataParts.last()->dataSeries().last(); double minDeltaX=m_maxX-m_minX; double minDeltaY=m_minY-m_maxY; foreach(ColorMapDataPart* datapart, m_dataParts) { m_minY = qMin(datapart->ySeries().first(),m_minY); m_maxY = qMax(datapart->ySeries().last(),m_maxY); for(int i=1;itimesSeries().size();i++) { double newDeltaX=datapart->timesSeries()[i]-datapart->timesSeries()[i-1]; minDeltaX=qMin(minDeltaX,newDeltaX); } for(int i=1;iySeries().size();i++) { double newDeltaY=datapart->ySeries()[i]-datapart->ySeries()[i-1]; minDeltaY=qMin(minDeltaY,newDeltaY); } for (int i = 0; i < datapart->dataSeries().count(); i++) { double z = datapart->dataSeries().at(i); m_minZ = qMin(m_minZ, z); m_maxZ = qMax(m_maxZ, z); } } m_maxX+=minDeltaX; m_maxY+=minDeltaY; } /*! Returns the last value (bottom right Z value) of a \a cluster of points passed as argument. */ double QColorMapSeriesPrivate::clusterStrategyLast(QVector& cluster) { if(!cluster.isEmpty()) return cluster.last().value(); return 0; } /*! Returns the mean value (mean Z value) of a \a cluster of points passed as argument. */ double QColorMapSeriesPrivate::clusterStrategyMean(QVector& cluster) { if(!cluster.isEmpty()) { double sum=0; int count =0; for(int i =0;i& cluster) { if(!cluster.isEmpty()) { QVector *copy = new QVector(); for(int i =0;iappend(Point3D(cluster.at(i)).value()); } if(!copy->isEmpty()) { std::sort(copy->begin(), copy->end()); if(copy->count() & 1) // odd { int middle = (copy->count()+1)/2; return copy->at(middle-1); } else //even { int middle = copy->count()/2; return (copy->at(middle-1)+copy->at(middle))/2; } } } return 0; } /*! Computes which data points correspond to the given position and returns the in a \a cluster. */ void QColorMapSeriesPrivate::buildCluster(int xpos, int ypos, double dx, double dy, QVector& cluster) { foreach(ColorMapDataPart *dataPart, m_dataParts) { QPair xRange = dataPart->getRange(dataPart->timesSeries(),m_requestedMinX+xpos*dx,m_requestedMinX+(xpos+1)*dx); QPair yRange = dataPart->getRange(dataPart->ySeries(),m_requestedMaxY-(ypos+1)*dy,m_requestedMaxY-ypos*dy); int timeSeriesSize = dataPart->timesSeries().size(); if(xRange.first != xRange.second && yRange.first != yRange.second) { for(int i =xRange.first+1;itimesSeries()[i]; for(int j=yRange.first+1;jySeries()[j]; qreal val=dataPart->dataSeries()[ i + (timeSeriesSize * j)]; cluster.append(Point3D(xval,yval,val)); } } } } } void QColorMapSeriesPrivate::initializeAnimations(QtCharts::QChart::AnimationOptions options, int duration, QEasingCurve &curve) { // ColorMapChart *item = static_cast(m_item.data()); // Q_ASSERT(item); // if (item->animation()) // item->animation()->stopAndDestroyLater(); // if (options.testFlag(QChart::SeriesAnimations)) // item->setAnimation(new XYAnimation(item, duration, curve)); // else // item->setAnimation(0); QAbstractSeriesPrivate::initializeAnimations(options, duration, curve); } #include "moc_qcolormapseries.cpp" #include "moc_qcolormapseries_p.cpp" QT_CHARTS_END_NAMESPACE