NEW: Base class JKQTPXYAndVectorGraph for graphs like vector fields, e.g. (x,y,dx,dy) or (x,y,angle,length)

NEW: ... and an actual implementation JKQTPVectorFieldGraph (+example)
This commit is contained in:
jkriege2 2024-02-08 21:52:30 +01:00
parent a178a1f6d3
commit e2057ae27a
24 changed files with 763 additions and 5 deletions

View File

@ -245,6 +245,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
multithreaded/multithreaded/--mdfile=${CMAKE_CURRENT_LIST_DIR}/../examples/multithreaded/README.md
#"multithreaded_complex[multithreaded]/multithreaded_complex/--mdfile=${CMAKE_CURRENT_LIST_DIR}/../examples/multithreaded/README.md --complexlabel"
graphlabels/graphlabels,graphlabels_hor
vectorfield
)
@ -273,6 +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
)

View File

@ -94,6 +94,9 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
<tr><td> \image html violinplot_small.png
<td> \subpage JKQTPlotterViolinplotGraphs
<td> `JKQTPViolinplotVerticalElement`, `JKQTPViolinplotHorizontalElement`, ...
<tr><td> \image html vectorfield_small.png
<td> \subpage JKQTPlotterVectorFieldExample
<td> `JKQTPVectorFieldGraph`
</table>
@ -114,7 +117,7 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
<td> \subpage JKQTPlotterGeometricCoordinateAxis0
<td> `JKQTPCoordinateAxisStyle::drawMode0`, `JKQTPGeoPolygon`, `JKQTPGeoEllipse`
<tr><td> \image html JKQTPGLSimpleBoxAndLineONLYLABELS_small.png
<td> \subpage JKQTPXYGraphLabels
<td> \subpage JKQTPlotterGraphLabelsExample
<td> `JKQTPXYGraphLabels`
</table>

View File

@ -75,13 +75,17 @@
rank=same;
JKQTPXYYGraph [URL="\ref JKQTPXYYGraph"]
noteJKQTPXYYGraph [shape="note", color="lightyellow",style=filled,fontsize=8,label="base class for all\ngraphs based on\n (x, y1, y2) data tripels"];
noteJKQTPXYYGraph [shape="note", color="lightyellow",style=filled,fontsize=8,label="base class for all\ngraphs based on\n (x, y1, y2) data triples"];
JKQTPXYYGraph -> noteJKQTPXYYGraph [style=dashed,arrowhead=none];
JKQTPXXYGraph [URL="\ref JKQTPXXYGraph"]
noteJKQTPXXYGraph [shape="note", color="lightyellow",style=filled,fontsize=8,label="base class for all\ngraphs based on\n (x1, x2, y) data tripels"];
noteJKQTPXXYGraph [shape="note", color="lightyellow",style=filled,fontsize=8,label="base class for all\ngraphs based on\n (x1, x2, y) data triples"];
JKQTPXXYGraph -> noteJKQTPXXYGraph [style=dashed,arrowhead=none];
JKQTPXYAndVectorGraph [URL="\ref JKQTPXYAndVectorGraph"]
noteJJKQTPXYAndVectorGraph [shape="note", color="lightyellow",style=filled,fontsize=8,label="base class for all\nvector field graphs based on\n (x, y, dx, dy) data quadruples"];
JKQTPXYAndVectorGraph -> noteJJKQTPXYAndVectorGraph [style=dashed,arrowhead=none];
JKQTPXYBaselineGraph [URL="\ref JKQTPXYBaselineGraph"]
noteJJKQTPXYBaselineGraph [shape="note", color="lightyellow",style=filled,fontsize=8,label="base class for all\ngraphs based on\n (x,y) data pairs and a baseline"];
JKQTPXYBaselineGraph -> noteJJKQTPXYBaselineGraph [style=dashed,arrowhead=none];

View File

@ -285,6 +285,21 @@ Examples:
<td> JKQTPPeakStreamGraph
</table>
\defgroup jkqtplotter_vectorfieldgraphs Vector Fields
\ingroup jkqtplotter_concretegraphs
This group assembles graphs that represent vector fields (i.e. sets of quadruples (x,y,dx,dy) or (x,y,angle,lengthh) in a plot:
<table>
<tr>
<th> Screenshot
<th> Classes
<tr>
<td>\image html vectorfield_small.png
<td> JKQTPVectorFieldGraph
</table>
\defgroup jkqtplotter_imagelots Matrix/Image Plotting
\ingroup jkqtplotter_concretegraphs

View File

@ -20,7 +20,6 @@ This page lists several todos and wishes for future version of JKQTPlotter
<li>graphs: gant-chart as simplified vector field with (x,y1,y2) or (x1,x2,y), or (x,y,dx), (x,y,dy) ... different head/tail style</li>
<li>graphs: matrix plots with boxes, labels, ...</li>
<li>graphs: matrix plots with symbols: symbol-type, color, size should be parametric, cf. scatter plots</li>
<li>graphs: vector field graph (arrows), variant (x,y,dx,dy), (x,y,alpha,length), (x1,y1,x2,y2) ... different head/tail styles</li>
<li>graphs: waterfall charts (see https://en.wikipedia.org/wiki/Waterfall_chart)</li>
<li>graphs: parametrized variant of JKQTPSingleColumnSymbolsGraph (beeswarm plots etz.) where each symbol may have a color given by a second column</li>
<li>graphs: option for JKQTPSingleColumnSymbolsGraph (beeswarm plots etz.) to have a distribution only on one side of the center (left or right)</li>

View File

@ -128,6 +128,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
<li>NEW: allow linear-gradient(), currentcolor, ... in brush definitions of style.ini-file ... and using it is cyberpunk and dark styles</li>
<li>NEW: style simple_noaxes.ini</li>
<li>NEW: JKQTPXYGraphLabels which can draw a label next to each datapoint in the given x/y-dataset. The labels can be x-/y- or x&y-coordinates or custom, then defined by a user-supplied functor (+example \ref JKQTPlotterGraphLabelsExample)</li>
<li>NEW: Base class JKQTPXYAndVectorGraph for graphs like vector fields, e.g. (x,y,dx,dy) or (x,y,angle,length) and an actual implementation JKQTPVectorFieldGraph (+example \ref JKQTPlotterVectorFieldExample)</li>
</ul></li>
<li>JKQTMathText:<ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -107,6 +107,7 @@ if (JKQtPlotter_BUILD_LIB_JKQTPLOTTER)
add_subdirectory(symbols_and_styles)
add_subdirectory(ui)
add_subdirectory(user_interaction)
add_subdirectory(vectorfield)
add_subdirectory(violinplot)
add_subdirectory(wiggleplots)
endif()

View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.23)
set(EXAMPLE_NAME vectorfield)
set(EXENAME jkqtptest_${EXAMPLE_NAME})
message( STATUS ".. Building Example ${EXAMPLE_NAME}" )
add_executable(${EXENAME} WIN32 ${EXAMPLE_NAME}.cpp)
target_link_libraries(${EXENAME} JKQTPExampleToolsLib)
target_include_directories(${EXENAME} PRIVATE ../../lib)
target_link_libraries(${EXENAME} ${jkqtplotter_namespace}JKQTPlotter${jkqtplotter_LIBNAME_VERSION_PART})
# precomiled headers to speed up compilation
if (JKQtPlotter_BUILD_WITH_PRECOMPILED_HEADERS)
target_precompile_headers(${EXENAME} REUSE_FROM jkqtptest_simpletest)
endif (JKQtPlotter_BUILD_WITH_PRECOMPILED_HEADERS)
# Installation
install(TARGETS ${EXENAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
#Installation of Qt DLLs on Windows
jkqtplotter_deployqt(${EXENAME})

View File

@ -0,0 +1,35 @@
# Example (JKQTPlotter): Vector Field Plot Example {#JKQTPlotterVectorFieldExample}
This project (see [`vectorfield`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/vectorfield) demonstrates the use of JKQTPXYvectorfield to add labels to the datapoints of a graph.
The source code of the main application is (see [`vectorfield.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/vectorfield/vectorfield.cpp).
Here is a short summary of the important parts of the code:
```.cpp
// 1. setup a plotter window and get a pointer to the internal datastore (for convenience)
JKQTPlotter plot;
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)
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); });
// 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}$"));
// 4. add the graphs to the plot, so it is actually displayed
plot.addGraph(graph1);
```
The result looks like this:
![vectorfield](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/vectorfield.png)

View File

@ -0,0 +1,85 @@
/** \example vectorfield.cpp
* Display a vector field
*
* \ref JKQTPlotterVectorFieldExample
*/
#include "jkqtpexampleapplication.h"
#include <QApplication>
#include "jkqtplotter/jkqtplotter.h"
#include "jkqtplotter/graphs/jkqtpvectorfield.h"
#include "jkqtplotter/graphs/jkqtpscatter.h"
#include "jkqtpexampleapplication.h"
#define NX 9
#define NY 9
int main(int argc, char* argv[])
{
JKQTPAppSettingController highDPIController(argc,argv);
JKQTPExampleApplication app(argc, argv);
// 1. setup a plotter window and get a pointer to the internal datastore (for convenience)
JKQTPlotter plot;
plot.getPlotter()->setUseAntiAliasingForGraphs(true); // nicer (but slower) plotting
plot.getPlotter()->setUseAntiAliasingForSystem(true); // nicer (but slower) plotting
plot.getPlotter()->setUseAntiAliasingForText(true); // nicer (but slower) text rendering
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)
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); });
// 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}$"));
// 4. add the graphs to the plot, so it is actually displayed
plot.addGraph(graph1);
// 5. scale the plot so the graph is contained
plot.getPlotter()->setAxisAspectRatio(1);
plot.getPlotter()->setAspectRatio(1);
plot.getPlotter()->setMaintainAxisAspectRatio(true);
plot.getPlotter()->setMaintainAspectRatio(true);
plot.zoomToFit();
// show plotter and make it a decent size
plot.setWindowTitle("JKQTPVectorFieldGraph example");
plot.show();
plot.resize(400/plot.devicePixelRatioF(),430/plot.devicePixelRatioF());
app.addExportStepFunctor([&](){
JKQTPXYScatterGraph* g2=new JKQTPXYScatterGraph(&plot);
g2->setXYColumns(columnXY);
g2->setTitle("anchor points");
g2->setSymbolSize(5);
g2->setSymbolType(JKQTPFilledCircle);
plot.addGraph(g2);
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorMid);
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorTip);
plot.redrawPlot();
});
return app.exec();
}

View File

@ -27,6 +27,8 @@
#include <limits>
#include <QPoint>
#include <QPointF>
#include <QLineF>
#include <QRectF>
#include <vector>
#include <QString>
#include <functional>
@ -485,6 +487,18 @@ inline bool JKQTPIsOKFloat(T v) {
return std::isfinite(v)&&(!std::isinf(v))&&(!std::isnan(v));
}
inline bool JKQTPIsOKFloat(const QPointF& v) {
return JKQTPIsOKFloat<qreal>(v.x()) && JKQTPIsOKFloat<qreal>(v.y());
}
inline bool JKQTPIsOKFloat(const QLineF& v) {
return JKQTPIsOKFloat<qreal>(v.x1()) && JKQTPIsOKFloat<qreal>(v.x2()) && JKQTPIsOKFloat<qreal>(v.y1()) && JKQTPIsOKFloat<qreal>(v.y2());
}
inline bool JKQTPIsOKFloat(const QRectF& v) {
return JKQTPIsOKFloat<qreal>(v.x()) && JKQTPIsOKFloat<qreal>(v.x()) && JKQTPIsOKFloat<qreal>(v.width()) && JKQTPIsOKFloat<qreal>(v.height());
}
/** \brief evaluates a gaussian propability density function
* \ingroup jkqtptools_math_basic
*

View File

@ -59,6 +59,7 @@ isEmpty(JKQTP_PLOTTER_PRI_INCLUDED) {
$$PWD/jkqtplotter/graphs/jkqtplines.h \
$$PWD/jkqtplotter/graphs/jkqtpgraphlabelstylemixin.h \
$$PWD/jkqtplotter/graphs/jkqtpgraphlabels.h \
$$PWD/jkqtplotter/graphs/jkqtpvectorfield.h \
$$PWD/jkqtplotter/gui/jkqtpcomboboxes.h \
$$PWD/jkqtplotter/gui/jkqtpenhancedspinboxes.h \
$$PWD/jkqtplotter/gui/jkqtpenhancedtableview.h \
@ -115,6 +116,7 @@ isEmpty(JKQTP_PLOTTER_PRI_INCLUDED) {
$$PWD/jkqtplotter/graphs/jkqtplines.cpp \
$$PWD/jkqtplotter/graphs/jkqtpgraphlabelstylemixin.cpp \
$$PWD/jkqtplotter/graphs/jkqtpgraphlabels.cpp \
$$PWD/jkqtplotter/graphs/jkqtpvectorfield.cpp \
$$PWD/jkqtplotter/gui/jkqtpcomboboxes.cpp \
$$PWD/jkqtplotter/gui/jkqtpenhancedspinboxes.cpp \
$$PWD/jkqtplotter/gui/jkqtpenhancedtableview.cpp \

View File

@ -33,6 +33,7 @@ target_sources(${lib_name} PRIVATE
jkqtplines.cpp
jkqtpgraphlabelstylemixin.cpp
jkqtpgraphlabels.cpp
jkqtpvectorfield.cpp
)
# ... and add headers
target_sources(${lib_name} PUBLIC FILE_SET HEADERS TYPE HEADERS
@ -68,4 +69,5 @@ target_sources(${lib_name} PUBLIC FILE_SET HEADERS TYPE HEADERS
jkqtplines.h
jkqtpgraphlabelstylemixin.h
jkqtpgraphlabels.h
jkqtpvectorfield.h
)

View File

@ -0,0 +1,174 @@
/*
Copyright (c) 2008-2024 Jan W. Krieger (<jan@jkrieger.de>)
This software is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License (LGPL) as published by
the Free Software Foundation, either version 2.1 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 Lesser General Public License (LGPL) for more details.
You should have received a copy of the GNU Lesser General Public License (LGPL)
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "jkqtplotter/graphs/jkqtpvectorfield.h"
#include "jkqtplotter/jkqtpbaseplotter.h"
#include "jkqtplotter/jkqtplotter.h"
#include <QDebug>
#include <QDateTime>
#include "jkqtcommon/jkqtpdrawingtools.h"
#include "jkqtplotter/jkqtptools.h"
#define SmallestGreaterZeroCompare_xvsgz() if ((xvsgz>10.0*DBL_MIN)&&((smallestGreaterZero<10.0*DBL_MIN) || (xvsgz<smallestGreaterZero))) smallestGreaterZero=xvsgz;
JKQTPVectorFieldGraph::JKQTPVectorFieldGraph(JKQTBasePlotter *parent):
JKQTPXYAndVectorGraph(parent),
m_autoscaleLength(true),
m_lengthScaleFactor(1),
m_anchorPoint(AnchorBottom)
{
initDecoratedLineStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Default);
setTailDecoratorStyle(JKQTPNoDecorator);
setHeadDecoratorStyle(JKQTPFilledArrow);
setHeadDecoratorSizeFactor(getHeadDecoratorSizeFactor()/1.6);
setTailDecoratorSizeFactor(getTailDecoratorSizeFactor()/1.6);
}
JKQTPVectorFieldGraph::JKQTPVectorFieldGraph(JKQTPlotter *parent):
JKQTPVectorFieldGraph(parent->getPlotter())
{
}
void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
{
#ifdef JKQTBP_AUTOTIMER
JKQTPAutoOutputTimer jkaaot("JKQTPVectorFieldGraph::draw");
#endif
if (parent==nullptr) return;
const JKQTPDatastore* datastore=parent->getDatastore();
if (datastore==nullptr) return;
drawErrorsBefore(painter);
{
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
const QPen p=getLinePen(painter, parent);
painter.setPen(p);
painter.setBrush(p.color());
int imax=0;
int imin=0;
double scale=1;
if (getIndexRange(imin, imax)) {
// first determine (auto-scale) factor
if (m_autoscaleLength) {
double avgVecLength=0;
double NDatapoints=0;
double xmin=0, xmax=0,ymin=0,ymax=0;
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()));
if (NDatapoints==0) {
xmin=xmax=xv;
ymin=ymax=yv;
} else {
xmin=qMin(xmin,xv);
xmax=qMax(xmax,xv);
ymin=qMin(ymin,yv);
ymax=qMax(ymax,yv);
}
NDatapoints++;
}
}
avgVecLength/=NDatapoints;
const double plotsize=qMax(fabs(xmax-xmin),fabs(ymax-ymin));
const double aproxNPerSide=sqrt(NDatapoints);
scale=plotsize/aproxNPerSide/avgVecLength;
} else {
scale=m_lengthScaleFactor;
}
// now draw
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 double x=transformX(xv);
const double y=transformY(yv);
const QPointF vecv=getVectorDxDy(i);
const QLineF l=[&]() {
switch (m_anchorPoint) {
case AnchorBottom: return QLineF(x,y,transformX(xv+scale*vecv.x()),transformY(yv+scale*vecv.y()));
case AnchorMid: return QLineF(transformX(xv-0.5*scale*vecv.x()),transformY(yv-0.5*scale*vecv.y()),transformX(xv+0.5*scale*vecv.x()),transformY(yv+0.5*scale*vecv.y()));
case AnchorTip: return QLineF(transformX(xv-scale*vecv.x()),transformY(yv-scale*vecv.y()), x,y);
}
return QLineF(JKQTP_NAN,JKQTP_NAN,JKQTP_NAN,JKQTP_NAN);
}();
if (JKQTPIsOKFloat(l)) {
JKQTPPlotDecoratedLine(painter,l, getTailDecoratorStyle(), calcTailDecoratorSize(p.widthF()), getHeadDecoratorStyle(), calcHeadDecoratorSize(p.widthF()));
}
}
}
}
drawErrorsAfter(painter);
}
void JKQTPVectorFieldGraph::drawKeyMarker(JKQTPEnhancedPainter &painter, const QRectF &rect)
{
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
const QPen p=getKeyLinePen(painter, rect, parent);
painter.setPen(p);
painter.setBrush(p.color());
const QLineF l(rect.left(), rect.bottom(), rect.right(), rect.top());
JKQTPPlotDecoratedLine(painter,l, getTailDecoratorStyle(), calcTailDecoratorSize(p.widthF()), getHeadDecoratorStyle(), calcHeadDecoratorSize(p.widthF()));
}
QColor JKQTPVectorFieldGraph::getKeyLabelColor() const
{
return getLineColor();
}
bool JKQTPVectorFieldGraph::getAutoscaleLength() const
{
return m_autoscaleLength;
}
void JKQTPVectorFieldGraph::setAutoscaleLength(bool newAutoscaleLength)
{
m_autoscaleLength = newAutoscaleLength;
}
double JKQTPVectorFieldGraph::getLengthScaleFactor() const
{
return m_lengthScaleFactor;
}
void JKQTPVectorFieldGraph::setLengthScaleFactor(double newLengthScaleFactor)
{
m_lengthScaleFactor = newLengthScaleFactor;
}
JKQTPVectorFieldGraph::VectorAnchorPoint JKQTPVectorFieldGraph::getAnchorPoint() const
{
return m_anchorPoint;
}
void JKQTPVectorFieldGraph::setAnchorPoint(VectorAnchorPoint newAnchorPoint)
{
m_anchorPoint = newAnchorPoint;
}

View File

@ -0,0 +1,133 @@
/*
Copyright (c) 2008-2024 Jan W. Krieger (<jan@jkrieger.de>)
This software is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License (LGPL) as published by
the Free Software Foundation, either version 2.1 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 Lesser General Public License (LGPL) for more details.
You should have received a copy of the GNU Lesser General Public License (LGPL)
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef jkqtpvectorfield_H
#define jkqtpvectorfield_H
#include <QString>
#include <QPainter>
#include <QPair>
#include <functional>
#include "jkqtplotter/jkqtptools.h"
#include "jkqtplotter/jkqtplotter_imexport.h"
#include "jkqtplotter/jkqtpgraphsbase.h"
#include "jkqtplotter/jkqtpgraphsbasestylingmixins.h"
// forward declarations
class JKQTBasePlotter;
class JKQTPlotter;
class JKQTPCoordinateAxis;
class JKQTPDatastore;
/*! \brief This graph plots a vector field, i.e. a set of vectors (dx,dy) or (angle,length) at positions (x,y).
This type of plot is sometimes also refered to as quicver plot (e.g. in Matlab or matplotlib)
\ingroup jkqtplotter_vectorfieldgraphs
\image html JKQTPVectorFieldGraph.png
To achieve this, use code like this:
\code
// 1. setup a plotter window and get a pointer to the internal datastore (for convenience)
JKQTPlotter plot;
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)
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); });
// 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}$"));
// 4. add the graphs to the plot, so it is actually displayed
plot.addGraph(graph1);
\endcode
\see \ref JKQTPlotterVectorFieldExample , JKQTPGraphDecoratedLineStyleMixin , JKQTPXYAndVectorGraph
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph, public JKQTPGraphDecoratedLineStyleMixin {
Q_OBJECT
public:
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
};
Q_ENUM(VectorAnchorPoint)
/** \brief class constructor */
explicit JKQTPVectorFieldGraph(JKQTBasePlotter* parent=nullptr);
/** \brief class constructor */
JKQTPVectorFieldGraph(JKQTPlotter* parent);
/** \brief plots the graph to the plotter object specified as parent */
virtual void draw(JKQTPEnhancedPainter& painter) override;
/** \brief plots a key marker inside the specified rectangle \a rect */
virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, const QRectF& rect) override;
/** \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_lengthScaleFactor */
double getLengthScaleFactor() const;
/** \copydoc m_lengthScaleFactor */
void setLengthScaleFactor(double newLengthScaleFactor);
/** \copydoc m_anchorPoint */
VectorAnchorPoint getAnchorPoint() const;
/** \copydoc m_anchorPoint */
void setAnchorPoint(VectorAnchorPoint newAnchorPoint);
Q_PROPERTY(bool autoscaleLength READ getAutoscaleLength WRITE setAutoscaleLength )
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
*
* If disabled (\c false ) the vector is drawn from (x,y) to (x+dx*m_lengthScaleFactor,y+dy*m_lengthScaleFactor),
* otherweise to (x+dx*autoscale,y+dy*autoscale)
*/
bool m_autoscaleLength;
/** \brief if m_autoscaleLength \c ==false, this is the scale-factor used to calculate the vector length */
double m_lengthScaleFactor;
/** \brief defines where the vector is anchored */
VectorAnchorPoint m_anchorPoint;
};
#endif // jkqtpvectorfield_H

View File

@ -1330,6 +1330,9 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPDatastore{
inline size_t addColumnCalculatedFromColumn(size_t otherColumnX, size_t otherColumnY, const std::function<double(double,double)>& f, const QString& name=QString("")) {
return addCalculatedColumnFromColumn(otherColumnX, otherColumnY, f, name);
}
inline size_t addCalculatedColumnFromColumn(const std::pair<size_t, size_t>& otherColumn, const std::function<double(double,double)>& f, const QString& name=QString("")) {
return addCalculatedColumnFromColumn(otherColumn.first, otherColumn.second, f, name);
}
/** \brief returns the number of (logical) columns currently managed by the datastore */
inline size_t getColumnCount() const { return static_cast<size_t>(columns.size()); }

View File

@ -1164,3 +1164,145 @@ JKQTPPlotAnnotationElement::~JKQTPPlotAnnotationElement()
{
}
JKQTPXYAndVectorGraph::JKQTPXYAndVectorGraph(JKQTBasePlotter *parent):
JKQTPXYGraph(parent),
vectorDataLayout(DefaultVectorDataLayout),
dxColumn(-1), dyColumn(-1),
angleColumn(-1), lengthColumn(-1)
{
}
bool JKQTPXYAndVectorGraph::getXMinMax(double &minx, double &maxx, double &smallestGreaterZero)
{
return JKQTPXYGraph::getXMinMax(minx,maxx,smallestGreaterZero);
}
bool JKQTPXYAndVectorGraph::getYMinMax(double &miny, double &maxy, double &smallestGreaterZero)
{
return JKQTPXYGraph::getYMinMax(miny,maxy,smallestGreaterZero);
}
bool JKQTPXYAndVectorGraph::usesColumn(int column) const
{
if (vectorDataLayout==DeltaXDeltaYLayout) {
return (column==dxColumn) || (column==dyColumn) || JKQTPXYGraph::usesColumn(column);
} else {
return (column==angleColumn) || (column==lengthColumn) || JKQTPXYGraph::usesColumn(column);
}
}
int JKQTPXYAndVectorGraph::getDxColumn() const
{
return dxColumn;
}
int JKQTPXYAndVectorGraph::getDyColumn() const
{
return dyColumn;
}
int JKQTPXYAndVectorGraph::getAngleColumn() const
{
return angleColumn;
}
int JKQTPXYAndVectorGraph::getLengthColumn() const
{
return lengthColumn;
}
JKQTPXYAndVectorGraph::VectorDataLayout JKQTPXYAndVectorGraph::getVectorDataLayout() const
{
return vectorDataLayout;
}
double JKQTPXYAndVectorGraph::hitTest(const QPointF &posSystem, QPointF *closestSpotSystem, QString *label, HitTestMode mode) const
{
return JKQTPXYGraph::hitTest(posSystem, closestSpotSystem, label, mode);
}
void JKQTPXYAndVectorGraph::setDxColumn(int col)
{
dxColumn=col;
vectorDataLayout=DeltaXDeltaYLayout;
}
void JKQTPXYAndVectorGraph::setDyColumn(int col)
{
dyColumn=col;
vectorDataLayout=DeltaXDeltaYLayout;
}
void JKQTPXYAndVectorGraph::setDxDyColumn(int colDx, int colDy)
{
dxColumn=colDx;
dyColumn=colDy;
vectorDataLayout=DeltaXDeltaYLayout;
}
void JKQTPXYAndVectorGraph::setAngleColumn(int col)
{
angleColumn=col;
vectorDataLayout=AngleAndLengthLayout;
}
void JKQTPXYAndVectorGraph::setAngleAndLengthColumn(int colAngle, int colLength)
{
angleColumn=colAngle;
lengthColumn=colLength;
vectorDataLayout=AngleAndLengthLayout;
}
void JKQTPXYAndVectorGraph::setLengthColumn(int col)
{
lengthColumn=col;
vectorDataLayout=AngleAndLengthLayout;
}
QPointF JKQTPXYAndVectorGraph::getVectorDxDy(int i) const
{
const JKQTPDatastore* datastore=parent->getDatastore();
if (datastore) {
switch(vectorDataLayout) {
case DeltaXDeltaYLayout: {
const double dx=datastore->get(static_cast<size_t>(dxColumn),static_cast<size_t>(i));
const double dy=datastore->get(static_cast<size_t>(dyColumn),static_cast<size_t>(i));
return QPointF(dx,dy);
} break;
case AngleAndLengthLayout: {
const double a=datastore->get(static_cast<size_t>(angleColumn),static_cast<size_t>(i));
const double l=(lengthColumn<0) ? 1.0 : datastore->get(static_cast<size_t>(lengthColumn),static_cast<size_t>(i));
return QPointF(l*cos(a),l*sin(a));
} break;
}
}
return QPointF(JKQTP_NAN, JKQTP_NAN);
}
bool JKQTPXYAndVectorGraph::getIndexRange(int &imin, int &imax) const
{
bool ok=JKQTPXYGraph::getIndexRange(imin, imax);
if (ok) {
if (vectorDataLayout==DeltaXDeltaYLayout) {
if (parent==nullptr) return false;
if (dxColumn<0||dyColumn<0) return false;
const JKQTPDatastore* datastore=parent->getDatastore();
const int rowsDX=static_cast<int>(datastore->getRows(static_cast<size_t>(dxColumn)));
const int rowsDY=static_cast<int>(datastore->getRows(static_cast<size_t>(dyColumn)));
imax=qMin<int>(imax, qMin<int>(rowsDX,rowsDY));
} else if (vectorDataLayout==AngleAndLengthLayout) {
if (parent==nullptr) return false;
if (angleColumn<0 && lengthColumn<0) return false;
if (angleColumn<0) return false;
const JKQTPDatastore* datastore=parent->getDatastore();
const int rowsA=static_cast<int>(datastore->getRows(static_cast<size_t>(angleColumn)));
const int rowsL=(lengthColumn>=0)?static_cast<int>(datastore->getRows(static_cast<size_t>(lengthColumn))):0;
imax=qMin<int>(imax, qMin<int>(rowsA,rowsL));
}
}
return ok;
}

View File

@ -957,5 +957,123 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPSingleColumnGraph: public JKQTPGraph {
/** \brief This virtual JKQTPGraph descendent extends JKQTPXYGraph with two additional columns that encode for a vector starting at (x,y), i.e. either two distances along the x- and y-axis (\f$ \Delta x, \Delta y \f$), or a rotation angle \f$ \alpha \f$ and a vector length \f$ \l \f$ .
* \ingroup jkqtplotter_basegraphs
*
* \see
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPXYAndVectorGraph: public JKQTPXYGraph {
Q_OBJECT
public:
/** \brief values from this enum indicates how to interpret the data columns provided to this graph */
enum VectorDataLayout {
DeltaXDeltaYLayout,
AngleAndLengthLayout,
DefaultVectorDataLayout=DeltaXDeltaYLayout,
};
Q_ENUM(VectorDataLayout)
/** \brief class constructor */
JKQTPXYAndVectorGraph(JKQTBasePlotter* parent=nullptr);
/** \copydoc JKQTPPlotElement::getXMinMax() */
virtual bool getXMinMax(double& minx, double& maxx, double& smallestGreaterZero) override;
/** \copydoc JKQTPPlotElement::getYMinMax() */
virtual bool getYMinMax(double& miny, double& maxy, double& smallestGreaterZero) override;
/** \copydoc JKQTPGraph::usesColumn() */
virtual bool usesColumn(int column) const override;
/** \copydoc dxColumn */
int getDxColumn() const;
/** \copydoc dyColumn */
int getDyColumn() const;
/** \copydoc angleColumn */
int getAngleColumn() const;
/** \copydoc lengthColumn */
int getLengthColumn() const;
/** \copydoc vectorDataLayout */
VectorDataLayout getVectorDataLayout() const;
/** \copydoc JKQTPXYGraph::hitTest() */
virtual double hitTest(const QPointF &posSystem, QPointF* closestSpotSystem=nullptr, QString* label=nullptr, HitTestMode mode=HitTestXY) const override;
Q_PROPERTY(VectorDataLayout vectorDataLayout READ getVectorDataLayout)
Q_PROPERTY(int dxColumn READ getDxColumn WRITE setDxColumn)
Q_PROPERTY(int dyColumn READ getDyColumn WRITE setDyColumn)
Q_PROPERTY(int angleColumn READ getAngleColumn WRITE setAngleColumn)
Q_PROPERTY(int lengthColumn READ getLengthColumn WRITE setLengthColumn)
public Q_SLOTS:
/** \copydoc dxColumn */
void setDxColumn(int col);
/** \copydoc dyColumn */
void setDyColumn(int col) ;
/** \brief det dxColumn and dyColumn column at the same time! ALso ensures that vectorDataLayout is set accordingly.
*
* \see dxColumn, dyColumn
*/
void setDxDyColumn(int colDx, int colDy) ;
/** \copydoc angleColumn */
void setAngleColumn(int col) ;
/** \brief det angleColumn and lengthColumn column at the same time! ALso ensures that vectorDataLayout is set accordingly.
*
* \see angleColumn, lengthColumn
*/
void setAngleAndLengthColumn(int colAngle, int colLength) ;
/** \copydoc lengthColumn */
void setLengthColumn(int col) ;
protected:
/** \brief this function interprets vectorDataLayout together with (dxColumn, dyColumn) or (angleColumn, lengthColumn) or ... and returns the \a i -th vectors \f$ \Delta x, \Delta y \f$ */
QPointF getVectorDxDy(int i) const;
/** \brief indicates, which column pairs to use (dxColumn, dyColumn), (angleColumn, lengthColumn), ... */
VectorDataLayout vectorDataLayout;
/** \brief the column that contains the delta along the x-axis.
*
* \note Note that this column is only used, when vectorDataLayout is set accordingly to DeltaXDeltaYLayout!
* Also note that Setter-functions (e.g. setDxColumn() ) will ensure that vectorDataLayout is set accordingly.
*
* \see setDxColumn(), setDyColumn(), setDxDyColumn(), getDxColumn(), getDyColumn()
*/
int dxColumn;
/** \brief the column that contains the delta along the y-axis.
*
* \note Note that this column is only used, when vectorDataLayout is set accordingly to DeltaXDeltaYLayout!
* Also note that Setter-functions (e.g. setDyColumn() ) will ensure that vectorDataLayout is set accordingly.
*
* \see setDxColumn(), setDyColumn(), setDxDyColumn(), getDxColumn(), getDyColumn()
*/
int dyColumn;
/** \brief the column that contains the rotation angle [in radian]
*
* An angle of 0 means a right-pointing vector and angle is measured count-clockwise,
* so angle \f$ \alpha \f$ and length \f$ l \f$ can be converted to \f$ \Delta x, \Delta y \f$ via:
* \f[ \Delta x = l\cdot \cos\alpha \f]
* \f[ \Delta y = l\cdot \sin\alpha \f]
* Note that these calculations are performed in the coordinate-axis-space, NOT in screen pixel space!
*
* \note Note that this column is only used, when vectorDataLayout is set accordingly to AngleAndLengthLayout!
* Also note that Setter-functions (e.g. setAngleColumn() ) will ensure that vectorDataLayout is set accordingly.
*
* \see setAngleColumn(), setLengthColumn(), getAngleColumn(), getLengthColumn()
*/
int angleColumn;
/** \brief the column that contains the vector length
*
* \copydetails angleColumn
*/
int lengthColumn;
/** \brief determines the range of row indexes available in the data columns of this graph
*
* \param[out] imin first usable row-index
* \param[out] imax last usable row-index
* \return \c true on success and \c false if the information is not available
*/
virtual bool getIndexRange(int &imin, int &imax) const override;
};
#endif // JKQTPGRAPHSBASE_H

BIN
screenshots/vectorfield.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB