diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 2afad3a03a..3208f3ef94 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -274,7 +274,7 @@ if(JKQtPlotter_BUILD_EXAMPLES) boxplot/JKQTPBoxplotVerticalGraph,JKQTPBoxplotHorizontalGraph/--iteratefunctorsteps--iteratefunctorsteps_suppressinitial--smallscreenshotplot second_axis/JKQTBasePlotter_addSecondaryYAxis,JKQTBasePlotter_addSecondaryXAxis graphlabels/JKQTPGLabelAwayFromXAxis,JKQTPGLabelAwayFromYAxis,JKQTPGLabelTowardsXAxis,JKQTPGLabelTowardsYAxis,JKQTPGLabelAboveData,JKQTPGLabelRightHandSide,JKQTPGLabelBelowData,JKQTPGLabelLeftHandSide,JKQTPGLSimpleBox,JKQTPGLSimpleBoxVertical,JKQTPGLSimpleBoxAndLine,JKQTPGLSimpleBoxAndLineVertical,JKQTPGLSimpleBoxAndLineONLYLABELS,JKQTPGLSimpleBoxAndLineONLYLABELSVertical/--iteratefunctorsteps--smallscreenshotplot - vectorfield/JKQTPVectorFieldGraph,JKQTPVectorFieldGraphAnchorBottom,JKQTPVectorFieldGraphAnchorMid,JKQTPVectorFieldGraphAnchorTip/--iteratefunctorsteps + vectorfield/JKQTPVectorFieldGraph,JKQTPVectorFieldGraphAnchorBottom,JKQTPVectorFieldGraphAnchorMid,JKQTPVectorFieldGraphAnchorTip,JKQTPVectorFieldGraphAutoscaleLength,JKQTPVectorFieldGraphLengthFromData,JKQTPVectorFieldGraphIgnoreLength/--iteratefunctorsteps ) diff --git a/doc/images/JKQTPVectorFieldGraph.png b/doc/images/JKQTPVectorFieldGraph.png index b7047fe7c6..f9f8b0eda0 100644 Binary files a/doc/images/JKQTPVectorFieldGraph.png and b/doc/images/JKQTPVectorFieldGraph.png differ diff --git a/doc/images/JKQTPVectorFieldGraphAnchorBottom.png b/doc/images/JKQTPVectorFieldGraphAnchorBottom.png index e03ea02574..e5e218d4f5 100644 Binary files a/doc/images/JKQTPVectorFieldGraphAnchorBottom.png and b/doc/images/JKQTPVectorFieldGraphAnchorBottom.png differ diff --git a/doc/images/JKQTPVectorFieldGraphAnchorMid.png b/doc/images/JKQTPVectorFieldGraphAnchorMid.png index 02f0cbacfe..f03e314c9a 100644 Binary files a/doc/images/JKQTPVectorFieldGraphAnchorMid.png and b/doc/images/JKQTPVectorFieldGraphAnchorMid.png differ diff --git a/doc/images/JKQTPVectorFieldGraphAnchorTip.png b/doc/images/JKQTPVectorFieldGraphAnchorTip.png index 9b548ca865..97d295f520 100644 Binary files a/doc/images/JKQTPVectorFieldGraphAnchorTip.png and b/doc/images/JKQTPVectorFieldGraphAnchorTip.png differ diff --git a/doc/images/JKQTPVectorFieldGraphAutoscaleLength.png b/doc/images/JKQTPVectorFieldGraphAutoscaleLength.png new file mode 100644 index 0000000000..e5e218d4f5 Binary files /dev/null and b/doc/images/JKQTPVectorFieldGraphAutoscaleLength.png differ diff --git a/doc/images/JKQTPVectorFieldGraphIgnoreLength.png b/doc/images/JKQTPVectorFieldGraphIgnoreLength.png new file mode 100644 index 0000000000..fd7713d0cc Binary files /dev/null and b/doc/images/JKQTPVectorFieldGraphIgnoreLength.png differ diff --git a/doc/images/JKQTPVectorFieldGraphLengthFromData.png b/doc/images/JKQTPVectorFieldGraphLengthFromData.png new file mode 100644 index 0000000000..ae8cd12119 Binary files /dev/null and b/doc/images/JKQTPVectorFieldGraphLengthFromData.png differ diff --git a/examples/vectorfield/README.md b/examples/vectorfield/README.md index 3668da664f..f8dff0d49c 100644 --- a/examples/vectorfield/README.md +++ b/examples/vectorfield/README.md @@ -11,17 +11,17 @@ Here is a short summary of the important parts of the code: JKQTPDatastore* ds=plot.getDatastore(); // 2. make up some arbitrary data to be used for plotting - // this generates a 2D grid of x/y-coordinates and then calculates dx=cos(y) and dy=sin(x) + // this generates a 2D grid of x/y-coordinates and then calculates dx=cos(y)*sqrt(x/3.0) and dy=sin(x)*sqrt(x/3.0) const auto columnXY=ds->addLinearGridColumns(NX, 0, 6, NY, -3, 3,"x","y"); - const auto columnDX=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sin(y); }); - const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x); }); + const auto columnDX=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sin(y)*sqrt(x/3.0); }); + const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x)*sqrt(x/3.0); }); // 3. create JKQTPVectorFieldGraph to display the data: JKQTPVectorFieldGraph* graph1=new JKQTPVectorFieldGraph(&plot); graph1->setXYColumns(columnXY); graph1->setDxColumn(columnDX); graph1->setDyColumn(columnDY); - graph1->setTitle(QObject::tr("$\\vec{f}(x,y)=\\bigl[\\sin(y), \\cos(x)\\bigr]^\\mathrm{T}$")); + graph1->setTitle(QObject::tr("$\\vec{f}(x,y)=\\bigl[\\sin(y)\\cdot\\sqrt{x/3}, \\cos(x)\\cdot\\sqrt{x/3}\\bigr]^\\mathrm{T}$")); // 4. add the graphs to the plot, so it is actually displayed plot.addGraph(graph1); @@ -33,3 +33,23 @@ The result looks like this: ![vectorfield](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/vectorfield.png) +By default, the length of the drawn vector is determined from the actual length in the data via an autoscaling algorithm that is supposed to prevent the vectors from overlapping. +But you can modify this behaviour by adding a line + +```.cpp + graph1->setVectorLengthMode(JKQTPVectorFieldGraph::LengthFromData); +``` + +which will use the given lengths directly (only scaled by an optional factor defined by JKQTPVectorFieldGraph::setLengthScaleFactor() ). The result then looks like this: + +![vectorfield](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/doc/images/JKQTPVectorFieldGraphLengthFromData.png) + +Alternatively you can also set + +```.cpp + graph1->setVectorLengthMode(JKQTPVectorFieldGraph::IgnoreLength); +``` + +which will draw all vectors with the same length. You can scale this length by setting JKQTPVectorFieldGraph::setAutoscaleLengthFactor() ). The result then looks like this: + +![vectorfield](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/doc/images/JKQTPVectorFieldGraphIgnoreLength.png) diff --git a/examples/vectorfield/vectorfield.cpp b/examples/vectorfield/vectorfield.cpp index 55db17cfbc..4ef7f1990f 100644 --- a/examples/vectorfield/vectorfield.cpp +++ b/examples/vectorfield/vectorfield.cpp @@ -32,10 +32,10 @@ int main(int argc, char* argv[]) // 2. make up some arbitrary data to be used for plotting - // this generates a 2D grid of x/y-coordinates and then calculates dx=cos(y) and dy=sin(x) + // this generates a 2D grid of x/y-coordinates and then calculates dx=cos(y)*sqrt(x/3.0) and dy=sin(x)*sqrt(x/3.0) const auto columnXY=ds->addLinearGridColumns(NX, 0, 6, NY, -3, 3,"x","y"); - const auto columnDX=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sin(y); }); - const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x); }); + const auto columnDX=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sin(y)*sqrt(x/3.0); }); + const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x)*sqrt(x/3.0); }); // 3. create JKQTPVectorFieldGraph to display the data: @@ -43,7 +43,7 @@ int main(int argc, char* argv[]) graph1->setXYColumns(columnXY); graph1->setDxColumn(columnDX); graph1->setDyColumn(columnDY); - graph1->setTitle(QObject::tr("$\\vec{f}(x,y)=\\bigl[\\sin(y), \\cos(x)\\bigr]^\\mathrm{T}$")); + graph1->setTitle(QObject::tr("$\\vec{f}(x,y)=\\bigl[\\sin(y)\\cdot\\sqrt{x/3}, \\cos(x)\\cdot\\sqrt{x/3}\\bigr]^\\mathrm{T}$")); // 4. add the graphs to the plot, so it is actually displayed plot.addGraph(graph1); @@ -78,7 +78,23 @@ int main(int argc, char* argv[]) app.addExportStepFunctor([&](){ graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorTip); - plot.redrawPlot(); + plot.redrawPlot(); + }); + + app.addExportStepFunctor([&](){ + graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorBottom); + graph1->setVectorLengthMode(JKQTPVectorFieldGraph::AutoscaleLength); + plot.redrawPlot(); + }); + app.addExportStepFunctor([&](){ + graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorBottom); + graph1->setVectorLengthMode(JKQTPVectorFieldGraph::LengthFromData); + plot.redrawPlot(); + }); + app.addExportStepFunctor([&](){ + graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorBottom); + graph1->setVectorLengthMode(JKQTPVectorFieldGraph::IgnoreLength); + plot.redrawPlot(); }); return app.exec(); diff --git a/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp b/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp index 88e0cd3554..f37f3e1b72 100644 --- a/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp +++ b/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp @@ -25,6 +25,7 @@ #include #include #include "jkqtcommon/jkqtpdrawingtools.h" +#include "jkqtmath/jkqtpstatisticstools.h" #include "jkqtplotter/jkqtptools.h" #define SmallestGreaterZeroCompare_xvsgz() if ((xvsgz>10.0*DBL_MIN)&&((smallestGreaterZero<10.0*DBL_MIN) || (xvsgz lengths; + lengths.reserve(imax-imin); for (int iii=imin; iiiget(static_cast(xColumn),static_cast(i)); const double yv=datastore->get(static_cast(yColumn),static_cast(i)); const QPointF vecv=getVectorDxDy(i); if (JKQTPIsOKFloat(xv) && JKQTPIsOKFloat(yv) && JKQTPIsOKFloat(vecv)) { - avgVecLength+=sqrt(jkqtp_sqr(vecv.x())+jkqtp_sqr(vecv.y())); + const double l=sqrt(jkqtp_sqr(vecv.x())+jkqtp_sqr(vecv.y())); + lengths<get(static_cast(yColumn),static_cast(i)); const double x=transformX(xv); const double y=transformY(yv); - const QPointF vecv=getVectorDxDy(i); + const QPointF vecv=[&]() { + QPointF vec=getVectorDxDy(i); + if (m_vectorLengthMode==IgnoreLength) { + const double veclen=sqrt(jkqtp_sqr(vec.x())+jkqtp_sqr(vec.y())); + if (qFuzzyIsNull(veclen)) vec=QPointF(0,0); + else vec/=veclen; // normalize vector + } + return vec; + }(); const QLineF l=[&]() { switch (m_anchorPoint) { case AnchorBottom: return QLineF(x,y,transformX(xv+scale*vecv.x()),transformY(yv+scale*vecv.y())); @@ -119,7 +134,7 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter) } return QLineF(JKQTP_NAN,JKQTP_NAN,JKQTP_NAN,JKQTP_NAN); }(); - if (JKQTPIsOKFloat(l)) { + if (JKQTPIsOKFloat(l) && l.length()>0) { JKQTPPlotDecoratedLine(painter,l, getTailDecoratorStyle(), calcTailDecoratorSize(p.widthF()), getHeadDecoratorStyle(), calcHeadDecoratorSize(p.widthF())); } } @@ -144,14 +159,14 @@ QColor JKQTPVectorFieldGraph::getKeyLabelColor() const return getLineColor(); } -bool JKQTPVectorFieldGraph::getAutoscaleLength() const +JKQTPVectorFieldGraph::VectorLengthMode JKQTPVectorFieldGraph::getVectorLengthMode() const { - return m_autoscaleLength; + return m_vectorLengthMode; } -void JKQTPVectorFieldGraph::setAutoscaleLength(bool newAutoscaleLength) +void JKQTPVectorFieldGraph::setVectorLengthMode(VectorLengthMode newMode) { - m_autoscaleLength = newAutoscaleLength; + m_vectorLengthMode = newMode; } double JKQTPVectorFieldGraph::getAutoscaleLengthFactor() const diff --git a/lib/jkqtplotter/graphs/jkqtpvectorfield.h b/lib/jkqtplotter/graphs/jkqtpvectorfield.h index 8a5837135f..bac312ca0e 100644 --- a/lib/jkqtplotter/graphs/jkqtpvectorfield.h +++ b/lib/jkqtplotter/graphs/jkqtpvectorfield.h @@ -43,7 +43,7 @@ class JKQTPDatastore; corresponding to their magnitude. \ingroup jkqtplotter_vectorfieldgraphs - \note This type of plot is sometimes also refered to as quiver plot (e.g. in Matlab or matplotlib) + \note This type of plot is sometimes also refered to as quiver plot (e.g. in Matlab or matplotlib) \image html JKQTPVectorFieldGraph.png @@ -54,35 +54,62 @@ class JKQTPDatastore; JKQTPDatastore* ds=plot.getDatastore(); // 2. make up some arbitrary data to be used for plotting - // this generates a 2D grid of x/y-coordinates and then calculates dx=cos(y) and dy=sin(x) + // this generates a 2D grid of x/y-coordinates and then calculates dx=cos(y)*sqrt(x/3.0) and dy=sin(x)*sqrt(x/3.0) const auto columnXY=ds->addLinearGridColumns(NX, 0, 6, NY, -3, 3,"x","y"); - const auto columnDX=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sin(y); }); - const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x); }); + const auto columnDX=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sin(y)*sqrt(x/3.0); }); + const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x)*sqrt(x/3.0); }); // 3. create JKQTPVectorFieldGraph to display the data: JKQTPVectorFieldGraph* graph1=new JKQTPVectorFieldGraph(&plot); graph1->setXYColumns(columnXY); graph1->setDxColumn(columnDX); graph1->setDyColumn(columnDY); - graph1->setTitle(QObject::tr("$\\vec{f}(x,y)=\\bigl[\\sin(y), \\cos(x)\\bigr]^\\mathrm{T}$")); + graph1->setTitle(QObject::tr("$\\vec{f}(x,y)=\\bigl[\\sin(y)\\cdot\\sqrt{x/3}, \\cos(x)\\cdot\\sqrt{x/3}\\bigr]^\\mathrm{T}$")); // 4. add the graphs to the plot, so it is actually displayed plot.addGraph(graph1); \endcode + You have several options to influence the way the vectors are drawn: + 1. You can change the tip shape (and actually also the tail) of the vector by using the methods from + JKQTPGraphDecoratedLineStyleMixin, e.g. use JKQTPGraphDecoratedLineStyleMixin::setHeadDecoratorStyle() + to set another shape for the vector's tip, or modify JKQTPGraphDecoratedLineStyleMixin::setHeadDecoratorSizeFactor() + to modify the size of the vector's head. the vector line width, color etz. can be modified by the + methods from JKQTPGraphLineStyleMixin, like JKQTPGraphLineStyleMixin::setLineColor() or + JKQTPGraphLineStyleMixin::setLineWidth(). + 2. By default the length of the drawn vectors corresponds to the actual length of the vector data, + but is modified by an autoscaling algorithm that should prevent them from overlapping. + This behaviour can be changed by setVectorLengthMode() with the different options described in + VectorLengthMode. + 3. By default, vector start at \c (x,y). But you can also make them be centered around or + point to \c (x,y) . This can be set by setAnchorPoint(). + . + \see \ref JKQTPlotterVectorFieldExample , JKQTPGraphDecoratedLineStyleMixin , JKQTPXYAndVectorGraph */ class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph, public JKQTPGraphDecoratedLineStyleMixin { Q_OBJECT public: + /** \brief indicates the position of the point \c (x,y) relative to the vector */ enum VectorAnchorPoint { - AnchorBottom, //!< \brief this is the default: the vector starts at (x,y) \image html JKQTPVectorFieldGraphAnchorBottom.png - AnchorMid, //!< \brief the vector's mid is at (x,y) \image html JKQTPVectorFieldGraphAnchorMid.png - AnchorTip //!< \brief the vector ends at (x,y) \image html JKQTPVectorFieldGraphAnchorTip.png + AnchorBottom, //!< \brief this is the default: the vector starts at \c (x,y) \image html JKQTPVectorFieldGraphAnchorBottom.png + AnchorMid, //!< \brief the vector's mid is at \c (x,y) \image html JKQTPVectorFieldGraphAnchorMid.png + AnchorTip //!< \brief the vector ends at \c (x,y) \image html JKQTPVectorFieldGraphAnchorTip.png }; Q_ENUM(VectorAnchorPoint) + /** \brief indicates how the drawn vector's length is calculated from the data + * + * \see documentation of m_vectorLengthMode for details + */ + enum VectorLengthMode { + AutoscaleLength, //!< \brief this is the default: vector lengths are autoscaled, so they don't overlap (in first approximation) \image html JKQTPVectorFieldGraphAnchorBottom.png + LengthFromData, //!< \brief the vector's length is determined by the data directly \image html JKQTPVectorFieldGraphAnchorMid.png + IgnoreLength //!< \brief all vectors have the same length \image html JKQTPVectorFieldGraphAnchorTip.png + }; + Q_ENUM(VectorLengthMode) + /** \brief class constructor */ explicit JKQTPVectorFieldGraph(JKQTBasePlotter* parent=nullptr); /** \brief class constructor */ @@ -95,10 +122,10 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph /** \brief returns the color to be used for the key label */ virtual QColor getKeyLabelColor() const override; - /** \copydoc m_autoscaleLength */ - bool getAutoscaleLength() const; - /** \copydoc m_autoscaleLength */ - void setAutoscaleLength(bool newAutoscaleLength); + /** \copydoc m_vectorLengthMode */ + VectorLengthMode getVectorLengthMode() const; + /** \copydoc m_vectorLengthMode */ + void setVectorLengthMode(VectorLengthMode newMode); /** \copydoc m_autoscaleLengthFactor */ double getAutoscaleLengthFactor() const; @@ -116,31 +143,38 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph /** \copydoc m_anchorPoint */ void setAnchorPoint(VectorAnchorPoint newAnchorPoint); - Q_PROPERTY(bool autoscaleLength READ getAutoscaleLength WRITE setAutoscaleLength ) + Q_PROPERTY(VectorLengthMode vectorLengthMode READ getVectorLengthMode WRITE setVectorLengthMode ) Q_PROPERTY(bool autoscaleLengthFactor READ getAutoscaleLengthFactor WRITE setAutoscaleLengthFactor ) Q_PROPERTY(double lengthScaleFactor READ getLengthScaleFactor WRITE setLengthScaleFactor ) Q_PROPERTY(VectorAnchorPoint anchorPoint READ getAnchorPoint WRITE setAnchorPoint ) protected: private: - /** \brief enables or disables the autoscaling of vector lengths + /** \brief indicates how the length of the drawn vectors are determined from the data * - * If disabled (\c false ) the vector is drawn from \c (x,y) to \c (x+dx,y+dy)*m_lengthScaleFactor , - * otherweise to \c (x+dx,y+dy)*autoscale*m_autoscaleLengthFactor . - * The autoscaled length is calculated by a siple algorithm that uses the average vector length in the data: - * \c autoscale=plotwidth/VectorPerWidth/avgVectorLength . + * Several modes are possible: + * - If \c == LengthFromData the vector is drawn from \c (x,y) to \c (x+dx,y+dy)*m_lengthScaleFactor. + * \image html JKQTPVectorFieldGraphAutoscaleLength.png + * - If \c == AutoscaleLength the vector is drawn to \c (x+dx,y+dy)*autoscale*m_autoscaleLengthFactor . + * The autoscaled length is calculated by a simple algorithm that uses the 90% quantile of vector length in the data \c q90VectorLength : + * \c autoscale=plotwidth/VectorPerWidth/q90VectorLength . + * \image html JKQTPVectorFieldGraphLengthFromData.png + * - If \c == IgnoreLength all vectors are drawn with the same length, which is determined from \c autoscale*m_autoscaleLengthFactor + * where \c autoscale is defined as above. + * \image html JKQTPVectorFieldGraphIgnoreLength.png + * . * - * \see m_autoscaleFactor, m_autoscaleLengthFactor, setAutoscaleLength(), getAutoscaleLength() + * \see VectorLengthMode, setVectorLengthMode(), getVectorLengthMode(), m_autoscaleFactor, m_autoscaleLengthFactor */ - bool m_autoscaleLength; - /** \brief a scaling factor that can be used to modify the result of the autoscaling algorithm (m_autoscaleLength \c ==true) + VectorLengthMode m_vectorLengthMode; + /** \brief a scaling factor that can be used to modify the result of the autoscaling algorithm (m_vectorLengthMode \c ==AutoscaleLength) * * The vector length is further scaled by this value. - * \see m_autoscaleLength, setAutoscaleFactor(), getAutoscaleFactor() + * \see m_vectorLengthMode, setAutoscaleFactor(), getAutoscaleFactor() */ double m_autoscaleLengthFactor; - /** \brief if m_autoscaleLength \c ==false, this is the scale-factor used to calculate the vector length + /** \brief if m_vectorLengthMode \c ==false, this is the scale-factor used to calculate the vector length * - * \see setLengthScaleFactor(), getLengthScaleFactor(), m_autoscaleLength + * \see setLengthScaleFactor(), getLengthScaleFactor(), m_vectorLengthMode */ double m_lengthScaleFactor; /** \brief defines where the vector is anchored diff --git a/screenshots/vectorfield.png b/screenshots/vectorfield.png index 9b46c60d0b..848ab063f2 100644 Binary files a/screenshots/vectorfield.png and b/screenshots/vectorfield.png differ diff --git a/screenshots/vectorfield_small.png b/screenshots/vectorfield_small.png index 9375870081..081cc89b2b 100644 Binary files a/screenshots/vectorfield_small.png and b/screenshots/vectorfield_small.png differ