replace JKQTPVectorFieldGraph::setAutoscaleLengthFactor() by an enum-property JKQTPVectorFieldGraph::setVectorLengthMode() and added a mode that ignores the length completely, the vectorfield example was also modified to better show this new property

This commit is contained in:
jkriege2 2024-02-09 13:48:41 +01:00
parent 1ff97e1d0a
commit 627c329ab1
14 changed files with 133 additions and 48 deletions

View File

@ -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
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -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)

View File

@ -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();

View File

@ -25,6 +25,7 @@
#include <QDebug>
#include <QDateTime>
#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<smallestGreaterZero))) smallestGreaterZero=xvsgz;
@ -33,9 +34,9 @@
JKQTPVectorFieldGraph::JKQTPVectorFieldGraph(JKQTBasePlotter *parent):
JKQTPXYAndVectorGraph(parent),
m_autoscaleLength(true),
m_autoscaleLengthFactor(0.9),
m_lengthScaleFactor(1),
m_vectorLengthMode(AutoscaleLength),
m_autoscaleLengthFactor(0.8),
m_lengthScaleFactor(1.0),
m_anchorPoint(AnchorBottom)
{
initDecoratedLineStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Default);
@ -72,17 +73,21 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
double scale=1;
if (getIndexRange(imin, imax)) {
// first determine (auto-scale) factor
if (m_autoscaleLength) {
if (m_vectorLengthMode==AutoscaleLength || m_vectorLengthMode==IgnoreLength) {
double avgVecLength=0;
double NDatapoints=0;
double xmin=0, xmax=0,ymin=0,ymax=0;
QVector<double> lengths;
lengths.reserve(imax-imin);
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
const double xv=datastore->get(static_cast<size_t>(xColumn),static_cast<size_t>(i));
const double yv=datastore->get(static_cast<size_t>(yColumn),static_cast<size_t>(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<<l;
avgVecLength+=l;
if (NDatapoints==0) {
xmin=xmax=xv;
ymin=ymax=yv;
@ -95,10 +100,12 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
NDatapoints++;
}
}
avgVecLength/=NDatapoints;
//avgVecLength/=NDatapoints;
avgVecLength=jkqtpstatQuantile(lengths.begin(), lengths.end(), 0.9);
const double plotsize=qMax(fabs(xmax-xmin),fabs(ymax-ymin));
const double aproxNPerSide=sqrt(NDatapoints);
scale=plotsize/aproxNPerSide/avgVecLength*m_autoscaleLengthFactor;
if (m_vectorLengthMode==IgnoreLength) scale=plotsize/aproxNPerSide*m_autoscaleLengthFactor; // we can assume that the elngths of all vectors have been normalized beforehand
else scale=plotsize/aproxNPerSide/avgVecLength*m_autoscaleLengthFactor;
} else {
scale=m_lengthScaleFactor;
}
@ -110,7 +117,15 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
const double yv=datastore->get(static_cast<size_t>(yColumn),static_cast<size_t>(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

View File

@ -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 <b>quiver plot</b> (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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 15 KiB