//########################################################################## //# # //# CLOUDCOMPARE # //# # //# 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; version 2 or later of the License. # //# # //# 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. # //# # //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) # //# # //########################################################################## #ifndef CC_QCUSTOMPLOT_HEADER #define CC_QCUSTOMPLOT_HEADER //QCustomPlot #ifdef _MSC_VER //To get rid of the really annoying warnings about unsafe methods #pragma warning( push ) #pragma warning( disable : 4996 ) #endif #include #ifdef _MSC_VER #pragma warning( pop ) #endif //System #include /*********************************/ /*** Custom QCustomPlot wigets ***/ /*********************************/ //! QCustomPlot: vertical bar with text along side class QCPBarsWithText : public QCPBars { Q_OBJECT public: QCPBarsWithText(QCPAxis* keyAxis, QCPAxis* valueAxis) : QCPBars(keyAxis,valueAxis), m_textOnTheLeft(false) {} void setText(QString text) { m_text = QStringList(text); } void appendText(QString text) { m_text.append(text); } void setTextAlignment(bool left) { m_textOnTheLeft = left; } protected: QStringList m_text; bool m_textOnTheLeft; // reimplemented virtual draw method virtual void draw(QCPPainter *painter) { if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } //switch to standard display QCPBars::draw(painter); int fontHeight = painter->fontMetrics().height(); if (!data()->isEmpty()) { double& key = data()->begin()->key; double& value = data()->begin()->value; QPointF P = coordsToPixels(key, value); //apply a small shift int margin = 5; //in pixels if (m_textOnTheLeft) margin = -margin; P.setX(P.x() + margin); //we draw at the 'base' line P.setY(P.y() + fontHeight); for (int i=0; ifontMetrics().width(m_text[i])); painter->drawText(Pstart,m_text[i]); P.setY(P.y() + fontHeight); } } } }; //! QCustomPlot: colored histogram class QCPColoredBars : public QCPBars { Q_OBJECT public: class QCPColoredBarData : public QCPBarsData { public: QCPColoredBarData() : color(Qt::blue) {} QColor color; }; typedef QMap QCPColoredBarDataMap; QCPColoredBars(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPBars(keyAxis,valueAxis) {} void setData(const QVector &key, const QVector &value) { //no colors? we switch to the standard QCPBars object m_coloredData.clear(); QCPBars::setData(key,value); } void setData(const QVector &key, const QVector &value, const QVector& colors) { Q_ASSERT(colors.size() == key.size()); data()->clear(); //we duplicate the structures so that other stuff in QCPBarData works! int n = qMin(key.size(), value.size()); for (int i=0; i i) newData.color = colors[i]; m_coloredData.insertMulti(newData.key, newData); QCPBars::addData(newData.key,newData.value); } } inline QRect rect() const { return clipRect(); } // reimplemented virtual methods: virtual void clearData() { QCPBars::data().clear(); m_coloredData.clear(); } protected: // reimplemented virtual draw method virtual void draw(QCPPainter *painter) { //no colors? if (m_coloredData.empty()) { //switch to standard display QCPBars::draw(painter); } if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } QCPColoredBarDataMap::const_iterator it; for (it = m_coloredData.constBegin(); it != m_coloredData.constEnd(); ++it) { // skip bar if not visible in key axis range: if (it.key()+mWidth*0.5 < mKeyAxis.data()->range().lower || it.key()-mWidth*0.5 > mKeyAxis.data()->range().upper) continue; QRectF barRect = getBarRect(it.key(), it.value().value); // draw bar fill: if (brush().style() != Qt::NoBrush && brush().color().alpha() != 0) { QBrush theBrush = brush(); theBrush.setColor(it.value().color); applyFillAntialiasingHint(painter); painter->setPen(Qt::NoPen); painter->setBrush(theBrush); painter->drawRect(barRect); } // draw bar line: if (pen().style() != Qt::NoPen && pen().color().alpha() != 0) { QPen thePen = pen(); thePen.setColor(it.value().color); applyDefaultAntialiasingHint(painter); painter->setPen(thePen); painter->setBrush(Qt::NoBrush); painter->drawPolyline(barRect); } } } QCPColoredBarDataMap m_coloredData; }; //! QCustomPlot: selectable cursor interface class QCPSelectableCursor : public QCPAbstractPlottable { Q_OBJECT public: //! Default constructor explicit QCPSelectableCursor(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable(keyAxis, valueAxis) , mCurrentVal(0) , mMinVal(0) , mMaxVal(0) , mLastPos(-1,-1) , mLastRadius(0) {} //! Returns whether the item is "selectable" when the mouse is clicked at a given position inline virtual bool isSelectable(QPoint click) const { if (mLastPos.x() < 0 || mLastPos.y() < 0) return false; QPoint d = mLastPos - click; return (d.x()*d.x() + d.y()*d.y() <= mLastRadius*mLastRadius); } // getters inline double currentVal() const { return mCurrentVal; } inline double minVal() const { return mMinVal; } inline double maxVal() const { return mMaxVal; } inline void range(double& minVal, double& maxVal) const { minVal = mMinVal; maxVal = mMaxVal; } // setters inline void setCurrentVal(double val) { mCurrentVal = std::max(std::min(val,mMaxVal),mMinVal); } inline void setRange(double minVal, double maxVal) { mMinVal = minVal; mMaxVal = maxVal; } //! Converts a pixel value (X) to the equivalent key inline double pixelToKey(int pixX) const { return keyAxis() ? keyAxis()->pixelToCoord(pixX) : 0; } //! Converts a pixel value (Y) to the equivalent value inline double pixelToValue(int pixY) const { return valueAxis() ? valueAxis()->pixelToCoord(pixY) : 0; } // reimplemented virtual methods: virtual void clearData() {} double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=0) const override { return -1; } //we don't use the QCP internal selection mechanism! protected: // reimplemented virtual methods: void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override {} QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override { foundRange = false; return QCPRange(); } QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override { foundRange = false; return QCPRange(); } // property members: double mCurrentVal; double mMinVal,mMaxVal; QPoint mLastPos; int mLastRadius; }; //! QCustomPlot: greyed areas class QCPHiddenArea : public QCPSelectableCursor { Q_OBJECT public: explicit QCPHiddenArea(bool leftSide, QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPSelectableCursor(keyAxis, valueAxis) , mLeftSide(leftSide) { mPen = QPen(QColor(80, 80, 80),Qt::SolidLine); // dark grey mPen.setWidth(2); setPen( mPen ); mBrush = QBrush(Qt::white,Qt::SolidPattern); // white setBrush( mBrush ); } protected: // reimplemented virtual methods: virtual void draw(QCPPainter *painter) { if (!keyAxis()) return; QRect rect = clipRect(); double currentPosd = keyAxis()->coordToPixel(mCurrentVal); if (mLeftSide) { int x2 = static_cast(ceil(currentPosd)); //assert(x2 >= rect.x()); if (x2 < rect.x()) return; rect.setWidth(x2 - rect.x()); } else { int x1 = static_cast(floor(currentPosd)); //assert(x1 >= rect.x()); if (x1 < rect.x()) return; int newWidth = rect.width() - (x1 - rect.x()); rect.setX(x1); rect.setWidth(newWidth); } // draw greyed rect if ( (mLeftSide && mCurrentVal > mMinVal) || (!mLeftSide && mCurrentVal < mMaxVal) ) { applyFillAntialiasingHint(painter); painter->setPen(Qt::NoPen); painter->setBrush(QBrush(QColor(128, 128, 128, 128),Qt::SolidPattern)); // semi-transparent grey painter->drawPolygon(rect); } //draw circle (handle) if (pen().style() != Qt::NoPen && pen().color().alpha() != 0) { //circle QPoint C(mLeftSide ? rect.x()+rect.width() : rect.x(), rect.y()+rect.height()/2); int r = rect.height() / 10; painter->setPen(pen()); painter->setBrush(brush()); painter->drawEllipse(C,r,r); painter->setPen(QPen(QColor(128, 128, 128, 128),Qt::SolidLine)); // semi-transparent grey painter->drawLine(C+QPoint(0,r),C-QPoint(0,r)); //save last circle position mLastPos = C; mLastRadius = r; } else { //no circle mLastPos = QPoint(-1,-1); mLastRadius = 0; } } //! Whether the cursor is displayed on the left side or not bool mLeftSide; }; //! QCustomPlot: small arrows at the bottom class QCPArrow : public QCPSelectableCursor { Q_OBJECT public: explicit QCPArrow(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPSelectableCursor(keyAxis, valueAxis) { mPen.setColor(QColor(128, 128, 0)); // dark yellow mPen.setStyle(Qt::SolidLine); mPen.setWidth(2); setPen( mPen ); mBrush.setColor(QColor(255, 255, 0, 196)); // semi-transparent yellow mBrush.setStyle(Qt::SolidPattern); setBrush( mBrush ); } //! Sets triangle 'inside' color void setColor(int r, int g, int b) { mBrush.setColor(QColor(r, g, b, 196)); // semi-transparent color setBrush( mBrush ); } protected: // reimplemented virtual methods: virtual void draw(QCPPainter *painter) { if (!keyAxis()) return; QRect rect = clipRect(); int currentPos = static_cast(keyAxis()->coordToPixel(mCurrentVal)); int r = rect.height() / 10; // draw dashed line { QPen pen(QColor(128, 128, 128, 128),Qt::DashLine); pen.setWidth(1); painter->setPen(pen); // semi-transparent grey painter->drawLine(QPoint(currentPos,rect.y()+2*r), QPoint(currentPos,rect.y()+rect.height())); } //draw triangle(handle) if (pen().style() != Qt::NoPen && pen().color().alpha() != 0) { //QPoint O(currentPos,rect.y() + rect.height() - r); //QPoint T[3] = { O - QPoint(0,r), O + QPoint(r,r), O + QPoint(-r,r) }; QPoint O(currentPos,rect.y() + r); QPoint T[3] = { O + QPoint(0,r), O - QPoint(r,r), O - QPoint(-r,r) }; painter->setPen(pen()); painter->setBrush(brush()); painter->drawPolygon(T,3); //save last circle position mLastPos = O; mLastRadius = r; } else { //no circle mLastPos = QPoint(-1,-1); mLastRadius = 0; } } }; #endif //CC_QCUSTOMPLOT_HEADER