NEW add JKQTPParametrizedVectorFieldGraph, which draws color-coded vector fields (color from length, angle, or user-supplied data column) + example

This commit is contained in:
jkriege2 2024-02-10 00:26:45 +01:00
parent 24526aa6a5
commit 25fcb2a74a
24 changed files with 595 additions and 25 deletions

View File

@ -246,6 +246,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
#"multithreaded_complex[multithreaded]/multithreaded_complex/--mdfile=${CMAKE_CURRENT_LIST_DIR}/../examples/multithreaded/README.md --complexlabel"
graphlabels/graphlabels,graphlabels_hor
vectorfield
paramvectorfield
)
@ -275,6 +276,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
second_axis/JKQTBasePlotter_addSecondaryYAxis,JKQTBasePlotter_addSecondaryXAxis
graphlabels/JKQTPGLabelAwayFromXAxis,JKQTPGLabelAwayFromYAxis,JKQTPGLabelTowardsXAxis,JKQTPGLabelTowardsYAxis,JKQTPGLabelAboveData,JKQTPGLabelRightHandSide,JKQTPGLabelBelowData,JKQTPGLabelLeftHandSide,JKQTPGLabelCenteredOnData,JKQTPGLabelCenteredOnDataVertical,JKQTPGLSimpleBox,JKQTPGLSimpleBoxVertical,JKQTPGLSimpleBoxAndLine,JKQTPGLSimpleBoxAndLineVertical,JKQTPGLSimpleBoxAndLineONLYLABELS,JKQTPGLSimpleBoxAndLineONLYLABELSVertical/--iteratefunctorsteps--smallscreenshotplot
vectorfield/JKQTPVectorFieldGraph,JKQTPVectorFieldGraphAnchorBottom,JKQTPVectorFieldGraphAnchorMid,JKQTPVectorFieldGraphAnchorTip,JKQTPVectorFieldGraphAutoscaleLength,JKQTPVectorFieldGraphLengthFromData,JKQTPVectorFieldGraphIgnoreLength,JKQTPVectorFieldGraphIgnoreLengthAutoscaleLineWidthFromLength,JKQTPVectorFieldGraphAutoscaleLengthAutoscaleLineWidthFromLength/--iteratefunctorsteps
paramvectorfield/JKQTPParametrizedVectorFieldGraph,JKQTPParametrizedVectorFieldGraphColorFromMagnitude,JKQTPParametrizedVectorFieldGraphColorFromAngle,JKQTPParametrizedVectorFieldGraphDefaultColor/--iteratefunctorsteps
)

View File

@ -97,6 +97,9 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
<tr><td> \image html vectorfield_small.png
<td> \subpage JKQTPlotterVectorFieldExample
<td> `JKQTPVectorFieldGraph`
<tr><td> \image html paramvectorfield_small.png
<td> \subpage JKQTPParametrizedVectorFieldGraphExample
<td> `JKQTPParametrizedVectorFieldGraph`
</table>

View File

@ -297,6 +297,9 @@ This group assembles graphs that represent vector fields (i.e. sets of quadruple
<tr>
<td>\image html vectorfield_small.png
<td> JKQTPVectorFieldGraph
<tr>
<td>\image html paramvectorfield_small.png
<td> JKQTPParametrizedVectorFieldGraph
</table>

View File

@ -128,7 +128,9 @@ 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>
<li>NEW: Base class JKQTPXYAndVectorGraph for graphs like vector fields, e.g. (x,y,dx,dy) or (x,y,angle,length) </li>
<li>NEW: JKQTPVectorFieldGraph for drawing "simple" vector fields/quiver plots (+example \ref JKQTPlotterVectorFieldExample)</li>
<li>NEW: JKQTPParametrizedVectorFieldGraph for drawing color-coded vector fields/quiver plots (+example \ref JKQTPParametrizedVectorFieldGraphExample)</li>
</ul></li>
<li>JKQTMathText:<ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -91,6 +91,7 @@ if (JKQtPlotter_BUILD_LIB_JKQTPLOTTER)
add_subdirectory(paramscatterplot_image)
add_subdirectory(paramscatterplot_customsymbol)
add_subdirectory(parsedfunctionplot)
add_subdirectory(paramvectorfield)
add_subdirectory(rgbimageplot)
add_subdirectory(rgbimageplot_cimg)
add_subdirectory(rgbimageplot_opencv)

View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.23)
set(EXAMPLE_NAME paramvectorfield)
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,63 @@
# Example (JKQTPlotter): Vector Field Plot Example {#JKQTPParametrizedVectorFieldGraphExample}
This project (see [`paramvectorfield`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/paramvectorfield) demonstrates the use of JKQTPParametrizedVectorFieldGraph to visualize a vector field with additional information encoded in the color of the vectors.
The source code of the main application is (see [`paramvectorfield.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/paramvectorfield/paramvectorfield.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)*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)*sqrt(x/3.0); });
const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x)*sqrt(x/3.0); });
// now we also calulate a column that encodes some other information that can be color-coded
const auto columnC=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sqrt(fabs(y)); });
// 3. create JKQTPVectorFieldGraph to display the data:
JKQTPParametrizedVectorFieldGraph* graph1=new JKQTPParametrizedVectorFieldGraph(&plot);
graph1->setXYColumns(columnXY);
graph1->setDxColumn(columnDX);
graph1->setDyColumn(columnDY);
graph1->setColorColumn(columnC);
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);
```
The result looks like this:
![paramvectorfield](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/paramvectorfield.png)
By default, the color of the drawn vector is determined from the color column provided to the graph object.
But you can also choose to not provide a color column and instead set
```.cpp
graph1->setVectorColorMode(JKQTPParametrizedVectorFieldGraph::ColorFromMagnitude);
```
Now the color encodes the actual length (or magnitude) of the vectors:
![paramvectorfield](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/doc/images/JKQTPParametrizedVectorFieldGraphColorFromMagnitude.png)
Alternatively
```.cpp
graph1->setVectorColorMode(JKQTPParametrizedVectorFieldGraph::ColorFromAngle);
```
will color-encode the rotation angle (in radians, 3 o'clock is 0rad) of the vectors:
![paramvectorfield](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/doc/images/JKQTPParametrizedVectorFieldGraphColorFromAngle.png)

View File

@ -0,0 +1,86 @@
/** \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)*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)*sqrt(x/3.0); });
const auto columnDY=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return cos(x)*sqrt(x/3.0); });
// now we also calulate a column that encodes some other information that can be color-coded
const auto columnC=ds->addCalculatedColumnFromColumn(columnXY.first, columnXY.second, [](double x,double y) { return sqrt(fabs(y)); });
// 3. create JKQTPVectorFieldGraph to display the data:
JKQTPParametrizedVectorFieldGraph* graph1=new JKQTPParametrizedVectorFieldGraph(&plot);
graph1->setXYColumns(columnXY);
graph1->setDxColumn(columnDX);
graph1->setDyColumn(columnDY);
graph1->setColorColumn(columnC);
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);
// 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([&](){
graph1->setVectorColorMode(JKQTPParametrizedVectorFieldGraph::ColorFromMagnitude);
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graph1->setVectorColorMode(JKQTPParametrizedVectorFieldGraph::ColorFromAngle);
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graph1->setVectorColorMode(JKQTPParametrizedVectorFieldGraph::DefaultColor);
plot.redrawPlot();
});
return app.exec();
}

View File

@ -1,5 +1,5 @@
# 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.
This project (see [`vectorfield`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/vectorfield) demonstrates the use of JKQTPVectorFieldGraph to visualize a vector field.
The source code of the main application is (see [`vectorfield.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/vectorfield/vectorfield.cpp).

View File

@ -73,7 +73,7 @@ int main(int argc, char* argv[])
});
app.addExportStepFunctor([&](){
graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorMid);
plot.redrawPlot();
plot.redrawPlot();
});
app.addExportStepFunctor([&](){

View File

@ -123,9 +123,9 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarGraphBase: public JKQTPXYBaselineGraph, pub
/** \brief class constructor */
JKQTPBarGraphBase(JKQTPlotter* parent);
/** \brief plots a key marker inside the specified rectangle \a rect */
/** \copydoc JKQTPXYBaselineGraph::drawKeyMarker() */
virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, const QRectF& rect) override;
/** \brief returns the color to be used for the key label */
/** \copydoc JKQTPXYBaselineGraph::getKeyLabelColor() */
virtual QColor getKeyLabelColor() const override;

View File

@ -111,7 +111,7 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBoxplotVerticalGraph: public JKQTPBoxplotGraph
/** \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 */
/** \copydoc JKQTPBoxplotGraphBase::drawKeyMarker() */
virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, const QRectF& rect) override;
/** \copydoc JKQTPPlotElement::getXMinMax() */

View File

@ -270,6 +270,7 @@ JKQTPXYParametrizedScatterGraph::JKQTPXYParametrizedScatterGraph(JKQTBasePlotter
symbolFillDerivationMode=JKQTPColorDerivationMode::JKQTPFFCMLighterColor;
if (parent) {
symbolFillDerivationMode=parent->getCurrentPlotterStyle().graphsStyle.defaultGraphStyle.fillColorDerivationMode;
palette=parent->getCurrentPlotterStyle().graphsStyle.defaultPalette;
}
clearSizeColumnFunctor();

View File

@ -39,7 +39,9 @@ JKQTPVectorFieldGraph::JKQTPVectorFieldGraph(JKQTBasePlotter *parent):
m_lengthScaleFactor(1.0),
m_anchorPoint(AnchorBottom),
m_vectorLineWidthMode(DefaultVectorLineWidth),
m_minLineWidth(0.001)
m_minLineWidth(0.001),
m_minVecLen(0),
m_maxVecLen(0)
{
initDecoratedLineStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Default);
setTailDecoratorStyle(JKQTPNoDecorator);
@ -75,13 +77,13 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
int imin=0;
double scale=1;
if (getIndexRange(imin, imax)) {
double minVecLen=0, maxVecLen=0;
m_minVecLen=m_maxVecLen=0;
// first determine (auto-scale) factor
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;
std::vector<double> lengths;
lengths.reserve(imax-imin);
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
@ -89,20 +91,20 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
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)) {
const double l=sqrt(jkqtp_sqr(vecv.x())+jkqtp_sqr(vecv.y()));
lengths<<l;
const double l=getVectorMagnitude(vecv);
lengths.push_back(l);
avgVecLength+=l;
if (NDatapoints==0) {
xmin=xmax=xv;
ymin=ymax=yv;
minVecLen=maxVecLen=l;
m_minVecLen=m_maxVecLen=l;
} else {
xmin=qMin(xmin,xv);
xmax=qMax(xmax,xv);
ymin=qMin(ymin,yv);
ymax=qMax(ymax,yv);
minVecLen=qMin(minVecLen,l);
maxVecLen=qMax(maxVecLen,l);
m_minVecLen=qMin(m_minVecLen,l);
m_maxVecLen=qMax(m_maxVecLen,l);
}
NDatapoints++;
}
@ -127,7 +129,7 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
const QPointF vec_orig=getVectorDxDy(i);
const QPointF vecv=[&](QPointF vec) {
if (m_vectorLengthMode==IgnoreLength) {
const double veclen=sqrt(jkqtp_sqr(vec.x())+jkqtp_sqr(vec.y()));
const double veclen=getVectorMagnitude(vecv);
if (qFuzzyIsNull(veclen)) vec=QPointF(0,0);
else vec/=veclen; // normalize vector
}
@ -143,14 +145,15 @@ void JKQTPVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
}();
if (JKQTPIsOKFloat(l) && l.length()>0) {
double actualLW=p.widthF();
QPen plw=p;
if (m_vectorLineWidthMode==AutoscaleLineWidthFromLength) {
const double vec_origlen=sqrt(jkqtp_sqr(vec_orig.x())+jkqtp_sqr(vec_orig.y()));
QPen plw=p;
plw.setWidthF(m_minLineWidth+(vec_origlen-minVecLen)/(maxVecLen-minVecLen)*(lw-m_minLineWidth));
painter.setPen(plw);
const double vec_origlen=getVectorMagnitude(vec_orig);
plw.setWidthF(m_minLineWidth+(vec_origlen-m_minVecLen)/(m_maxVecLen-m_minVecLen)*(lw-m_minLineWidth));
actualLW=plw.widthF();
}
plw.setColor(getLocalVectorColor(i,xv,yv,vec_orig.x(),vec_orig.y()));
painter.setPen(plw);
painter.setBrush(plw.color());
JKQTPPlotDecoratedLine(painter,l, getTailDecoratorStyle(), calcTailDecoratorSize(actualLW), getHeadDecoratorStyle(), calcHeadDecoratorSize(actualLW));
}
}
@ -234,3 +237,235 @@ double JKQTPVectorFieldGraph::getMinLineWIdth() const
{
return m_minLineWidth;
}
QColor JKQTPVectorFieldGraph::getLocalVectorColor(int /*i*/, double /*x*/, double /*y*/, double /*dx*/, double /*dy*/) const
{
return getLineColor();
}
JKQTPParametrizedVectorFieldGraph::JKQTPParametrizedVectorFieldGraph(JKQTBasePlotter *parent):
JKQTPVectorFieldGraph(parent),
JKQTPColorPaletteStyleAndToolsMixin(parent),
m_colorColumn(-1),
m_colorColumnContainsRGB(false),
m_vectorColorMode(ColorFromMagnitude)
{
palette=JKQTPMathImageMATLAB;
if (parent) {
palette=parent->getCurrentPlotterStyle().graphsStyle.defaultPalette;
}
}
JKQTPParametrizedVectorFieldGraph::JKQTPParametrizedVectorFieldGraph(JKQTPlotter *parent):
JKQTPParametrizedVectorFieldGraph(parent->getPlotter())
{
}
void JKQTPParametrizedVectorFieldGraph::drawKeyMarker(JKQTPEnhancedPainter &painter, const QRectF &rect)
{
if (m_vectorColorMode==DefaultColor) {
JKQTPVectorFieldGraph::drawKeyMarker(painter,rect);
} else {
QColor color1=getKeyLabelColor();
QColor color2=getKeyLabelColor();
if (m_colorColumn>=0) {
if (m_colorColumnContainsRGB) {
color1=QColor("red");
color2=QColor("blue");
} else {
QImage img;
double colorval[]={0,1};
JKQTPImageTools::array2image<double>(colorval, 2, 1, img, getColorPalette(), double(0.0), double(1.0));
color1=img.pixel(0,0);
color2=img.pixel(1,0);
}
}
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
QPen p=getKeyLinePen(painter, rect, parent);
p.setColor(color1);
painter.setPen(p);
painter.setBrush(p.color());
const QLineF l(rect.left(), rect.bottom(), rect.right(), (rect.top()+rect.bottom())/2.0);
JKQTPPlotDecoratedLine(painter,l, getTailDecoratorStyle(), calcTailDecoratorSize(p.widthF()), getHeadDecoratorStyle(), calcHeadDecoratorSize(p.widthF()));
p=getKeyLinePen(painter, rect, parent);
p.setColor(color2);
painter.setPen(p);
painter.setBrush(p.color());
const QLineF l2(rect.left(), (rect.top()+rect.bottom())/2.0, rect.right(), rect.top());
JKQTPPlotDecoratedLine(painter,l2, getTailDecoratorStyle(), calcTailDecoratorSize(p.widthF()), getHeadDecoratorStyle(), calcHeadDecoratorSize(p.widthF()));
}
}
void JKQTPParametrizedVectorFieldGraph::draw(JKQTPEnhancedPainter &painter)
{
cbGetDataMinMax(m_intColMin, m_intColMax);
JKQTPVectorFieldGraph::draw(painter);
}
void JKQTPParametrizedVectorFieldGraph::setParent(JKQTBasePlotter *parent)
{
JKQTPVectorFieldGraph::setParent(parent);
cbSetParent(parent);
}
void JKQTPParametrizedVectorFieldGraph::getOutsideSize(JKQTPEnhancedPainter &painter, int &leftSpace, int &rightSpace, int &topSpace, int &bottomSpace)
{
JKQTPVectorFieldGraph::getOutsideSize(painter, leftSpace, rightSpace, topSpace, bottomSpace);
if (showColorBar&& m_colorColumn>=0 && !m_colorColumnContainsRGB) cbGetOutsideSize(painter, leftSpace, rightSpace, topSpace, bottomSpace);
}
void JKQTPParametrizedVectorFieldGraph::drawOutside(JKQTPEnhancedPainter &painter, QRect leftSpace, QRect rightSpace, QRect topSpace, QRect bottomSpace)
{
JKQTPVectorFieldGraph::drawOutside(painter, leftSpace, rightSpace, topSpace, bottomSpace);
if (showColorBar&& m_colorColumn>=0 && !m_colorColumnContainsRGB) cbDrawOutside(painter, leftSpace, rightSpace, topSpace, bottomSpace);
}
void JKQTPParametrizedVectorFieldGraph::cbGetDataMinMax(double &dmin, double &dmax)
{
dmin=dmax=0;
if (autoImageRange) {
if (parent==nullptr) return;
JKQTPDatastore* datastore=parent->getDatastore();
if (datastore==nullptr) return;
int imin=0, imax=0;
if (getIndexRange(imin, imax)) {
if (m_vectorColorMode==ColorFromCustomColumn) {
if (m_colorColumn<0) return;
bool first=true;
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
const double xv=datastore->get(m_colorColumn,i);
if (JKQTPIsOKFloat(xv)) {
if (first) {
dmin=dmax=xv;
first=false;
} else {
dmin=qMin(xv, dmin);
dmax=qMax(xv, dmax);
}
}
}
} else if (m_vectorColorMode==ColorFromMagnitude) {
bool first=true;
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
const double vecLen=getVectorMagnitude(i);
if (JKQTPIsOKFloat(vecLen)) {
if (first) {
dmin=dmax=vecLen;
first=false;
} else {
dmin=qMin(vecLen, dmin);
dmax=qMax(vecLen, dmax);
}
}
}
} else if (m_vectorColorMode==ColorFromAngle) {
bool first=true;
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
const double vecAngle=getVectorAngle(i);
if (JKQTPIsOKFloat(vecAngle)) {
if (first) {
dmin=dmax=vecAngle;
first=false;
} else {
dmin=qMin(vecAngle, dmin);
dmax=qMax(vecAngle, dmax);
}
}
}
}
}
} else {
dmin=imageMin;
dmax=imageMax;
}
}
bool JKQTPParametrizedVectorFieldGraph::usesColumn(int c) const
{
return (c==m_colorColumn) || JKQTPVectorFieldGraph::usesColumn(c);
}
void JKQTPParametrizedVectorFieldGraph::setColorColumn(int __value)
{
m_colorColumn = __value;
if (__value<0) m_vectorColorMode=ColorFromMagnitude;
else m_vectorColorMode=ColorFromCustomColumn;
}
int JKQTPParametrizedVectorFieldGraph::getColorColumn() const
{
return m_colorColumn;
}
void JKQTPParametrizedVectorFieldGraph::setColorColumn(size_t __value)
{
m_colorColumn = static_cast<int>(__value);
m_vectorColorMode=ColorFromCustomColumn;
}
void JKQTPParametrizedVectorFieldGraph::setColorColumnContainsRGB(bool __value)
{
m_colorColumnContainsRGB=__value;
}
bool JKQTPParametrizedVectorFieldGraph::getColorColumnContainsRGB() const
{
return m_colorColumnContainsRGB;
}
void JKQTPParametrizedVectorFieldGraph::setVectorColorMode(VectorColorMode __value)
{
m_vectorColorMode=__value;
}
JKQTPParametrizedVectorFieldGraph::VectorColorMode JKQTPParametrizedVectorFieldGraph::getVectorColorMode() const
{
return m_vectorColorMode;
}
QColor JKQTPParametrizedVectorFieldGraph::getLocalVectorColor(int i, double x, double y, double dx, double dy) const
{
if (parent==nullptr) return getLineColor();
const JKQTPDatastore* datastore=parent->getDatastore();
if (datastore==nullptr) return getLineColor();
if (m_colorColumn<0 && m_vectorColorMode==ColorFromCustomColumn) return getLineColor();
if (m_colorColumn>=0 && m_vectorColorMode==ColorFromCustomColumn && (i<0 || i>=(int64_t)datastore->getRows(m_colorColumn))) return getLineColor();
double colValue=0;
double colMin=m_intColMin;
double colMax=m_intColMax;
switch(m_vectorColorMode) {
case ColorFromCustomColumn:
colValue=datastore->get(m_colorColumn,i);
if (m_intColMin==m_intColMax) {
colMin=0;
colMax=datastore->getRows(m_colorColumn)-1;
}
break;
case ColorFromMagnitude:
colValue=getVectorMagnitude(QPointF(dx,dy));
break;
case ColorFromAngle:
colValue=getVectorAngle(QPointF(dx,dy));
break;
case DefaultColor:
return getLineColor();
}
if (m_colorColumnContainsRGB && m_vectorColorMode==ColorFromCustomColumn) {
return QRgb(round(colValue));
} else {
QImage img;
JKQTPImageTools::array2image(&colValue, 1, 1, img, palette, colMin, colMax);
return img.pixel(0,0);
}
}

View File

@ -90,7 +90,7 @@ class JKQTPDatastore;
\image html JKQTPVectorFieldGraphIgnoreLengthAutoscaleLineWidthFromLength.png
.
\see \ref JKQTPlotterVectorFieldExample , JKQTPGraphDecoratedLineStyleMixin , JKQTPXYAndVectorGraph
\see \ref JKQTPlotterVectorFieldExample , JKQTPGraphDecoratedLineStyleMixin , JKQTPXYAndVectorGraph and JKQTPParametrizedVectorFieldGraph for a user-colored alternative
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph, public JKQTPGraphDecoratedLineStyleMixin {
@ -129,11 +129,11 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph
/** \brief class constructor */
JKQTPVectorFieldGraph(JKQTPlotter* parent);
/** \brief plots the graph to the plotter object specified as parent */
/** \copydoc JKQTPXYAndVectorGraph::draw() */
virtual void draw(JKQTPEnhancedPainter& painter) override;
/** \brief plots a key marker inside the specified rectangle \a rect */
/** \copydoc JKQTPXYAndVectorGraph::drawKeyMarker() */
virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, const QRectF& rect) override;
/** \brief returns the color to be used for the key label */
/** \copydoc JKQTPXYAndVectorGraph::getKeyLabelColor() */
virtual QColor getKeyLabelColor() const override;
/** \copydoc m_vectorLengthMode */
@ -174,6 +174,17 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph
Q_PROPERTY(double minLineWidth READ getMinLineWIdth WRITE setMinLineWidth )
Q_PROPERTY(VectorLineWidthMode vectorLineWidthMode READ getVectorLineWidthMode WRITE setVectorLineWidthMode )
protected:
/** \brief internal color functor, a customization point for derived classes
*
* Overwrite this in a derived class to be able to color each vector differently */
virtual QColor getLocalVectorColor(int i,double x,double y,double dx,double dy) const;
/** \brief acess to internally calculated (before draw() actually draws) minimum vector length
*/
inline double getMinVecLen() const { return m_minVecLen; };
/** \brief acess to internally calculated (before draw() actually draws) maximum vector length
*/
inline double getMaxVecLen() const { return m_maxVecLen; };
private:
/** \brief indicates how the length of the drawn vectors are determined from the data
*
@ -221,9 +232,131 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPVectorFieldGraph: public JKQTPXYAndVectorGraph
*/
double m_minLineWidth;
/** \brief internally calculated (before draw() actually draws) minimum vector length
* \internal
*/
double m_minVecLen;
/** \brief internally calculated (before draw() actually draws) maximum vector length
* \internal
*/
double m_maxVecLen;
};
/*! \brief This graph plots a vector field, i.e. a set of vectors (dx,dy) or (angle,length) at positions (x,y).
This class is an extension of JKQTPVectorFieldGraph and additionally supports setting the line-color
from an additional data column.
\ingroup jkqtplotter_vectorfieldgraphs
\note This type of plot is sometimes also refered to as <b>quiver plot</b> (e.g. in Matlab or matplotlib)
\image html JKQTPParametrizedVectorFieldGraph.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)*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)*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)\\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
Use setVectorColorMode() if you don't want to use a custom column, but just use the vector's magnitude/length or
rotation angle as parameter for the color.
\see \ref JKQTPParametrizedVectorFieldGraphExample , JKQTPVectorFieldGraph , JKQTPColorPaletteStyleAndToolsMixin
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPParametrizedVectorFieldGraph: public JKQTPVectorFieldGraph, public JKQTPColorPaletteStyleAndToolsMixin {
Q_OBJECT
public:
/** \brief determines how the color of the vector is determined */
enum VectorColorMode {
DefaultColor, //!< \brief no color-coding, just use getLineColor() \image html JKQTPParametrizedVectorFieldGraphDefaultColor.png
ColorFromMagnitude, //!< \brief color-coding by vector magnitude/length \image html JKQTPParametrizedVectorFieldGraphColorFromMagnitude.png
ColorFromAngle, //!< \brief color-coding by vector angle \image html JKQTPParametrizedVectorFieldGraphColorFromAngle.png
ColorFromCustomColumn //!< \brief color-coding from getColorColumn() column \image html JKQTPParametrizedVectorFieldGraph.png
};
Q_ENUM(VectorColorMode)
/** \brief class constructor */
explicit JKQTPParametrizedVectorFieldGraph(JKQTBasePlotter* parent=nullptr);
/** \brief class constructor */
JKQTPParametrizedVectorFieldGraph(JKQTPlotter* parent);
/** \copydoc JKQTPVectorFieldGraph::drawKeyMarker() */
virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, const QRectF& rect) override;
/** \copydoc JKQTPVectorFieldGraph::draw() */
virtual void draw(JKQTPEnhancedPainter& painter) override;
/** \copydoc JKQTPGraph::setParent() */
virtual void setParent(JKQTBasePlotter* parent) override;
/** \copydoc JKQTPGraph::getOutsideSize() */
virtual void getOutsideSize(JKQTPEnhancedPainter& painter, int& leftSpace, int& rightSpace, int& topSpace, int& bottomSpace) override;
/** \copydoc JKQTPGraph::drawOutside() */
virtual void drawOutside(JKQTPEnhancedPainter& painter, QRect leftSpace, QRect rightSpace, QRect topSpace, QRect bottomSpace) override;
/** \brief determine min/max data value of the image */
virtual void cbGetDataMinMax(double& imin, double& imax) override;
/** \copydoc JKQTPGraph::usesColumn() */
virtual bool usesColumn(int c) const override;
/** \copydoc colorColumn */
void setColorColumn(int __value);
/** \copydoc colorColumn */
int getColorColumn() const;
/** \copydoc colorColumn */
void setColorColumn (size_t __value);
/** \copydoc colorColumnContainsRGB */
void setColorColumnContainsRGB(bool __value);
/** \copydoc colorColumnContainsRGB */
bool getColorColumnContainsRGB() const;
/** \copydoc m_vectorColorMode */
void setVectorColorMode(VectorColorMode __value);
/** \copydoc m_vectorColorMode */
VectorColorMode getVectorColorMode() const;
protected:
/** \copdydoc JKQTPVectorFieldGraph::getLocalVectorColor() */
virtual QColor getLocalVectorColor(int i,double x,double y,double dx,double dy) const override;
private:
/** \brief this column contains the symbol color
*
* \see setColorColumn(), getColorColumn()
*/
int m_colorColumn;
/** \brief if this is true, the value in the colorColumn is converted to an integer, representing a color in ARGB format (as in QRgb)
*
* \see setColorColumnContainsRGB(), getColorColumnContainsRGB()
*/
bool m_colorColumnContainsRGB;
/** \brief internally used to store the range of the color column
* \internal
*/
double m_intColMin;
/** \brief internally used to store the range of the color column
* \internal
*/
double m_intColMax;
/** \brief indicates how color is determined from data (either from the vector or from m_colorColumn) */
VectorColorMode m_vectorColorMode;
};
#endif // jkqtpvectorfield_H

View File

@ -960,7 +960,7 @@ 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 JKQTPVectorFieldGraph
* \see JKQTPVectorFieldGraph, JKQTPParametrizedVectorFieldGraph
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPXYAndVectorGraph: public JKQTPXYGraph {
Q_OBJECT
@ -1025,6 +1025,22 @@ public Q_SLOTS:
/** \copydoc lengthColumn */
void setLengthColumn(int col) ;
protected:
/** \brief calculates the magnitude/length of a vector \a v */
static inline double getVectorMagnitude(const QPointF& v) {
return sqrt(jkqtp_sqr(v.x())+jkqtp_sqr(v.y()));
}
inline double getVectorMagnitude(int i) const {
return getVectorMagnitude(getVectorDxDy(i));
}
/** \brief calculates the rotation angle (3 o'clock is 0) in radians \f$ [0...2\pi] \f$ of a vector \a v */
static inline double getVectorAngle(const QPointF& v) {
double colValue=atan2(v.y(),v.x());
if (colValue<0) colValue=2.0*JKQTPSTATISTICS_PI+colValue;
return colValue;
}
inline double getVectorAngle(int i) const {
return getVectorAngle(getVectorDxDy(i));
}
/** \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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB