diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
index 581818fd9c2..5825a6a1deb 100644
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -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
)
diff --git a/doc/dox/examples_and_tutorials.dox b/doc/dox/examples_and_tutorials.dox
index 7319c8323c2..0153e751b00 100644
--- a/doc/dox/examples_and_tutorials.dox
+++ b/doc/dox/examples_and_tutorials.dox
@@ -97,6 +97,9 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
+ \image html paramvectorfield_small.png
+ | JKQTPParametrizedVectorFieldGraph
diff --git a/doc/dox/whatsnew.dox b/doc/dox/whatsnew.dox
index 25054c50881..2cd007ef0c1 100644
--- a/doc/dox/whatsnew.dox
+++ b/doc/dox/whatsnew.dox
@@ -128,7 +128,9 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
NEW: allow linear-gradient(), currentcolor, ... in brush definitions of style.ini-file ... and using it is cyberpunk and dark styles
NEW: style simple_noaxes.ini
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)
- 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)
+ NEW: Base class JKQTPXYAndVectorGraph for graphs like vector fields, e.g. (x,y,dx,dy) or (x,y,angle,length)
+ NEW: JKQTPVectorFieldGraph for drawing "simple" vector fields/quiver plots (+example \ref JKQTPlotterVectorFieldExample)
+ NEW: JKQTPParametrizedVectorFieldGraph for drawing color-coded vector fields/quiver plots (+example \ref JKQTPParametrizedVectorFieldGraphExample)
JKQTMathText:
diff --git a/doc/images/JKQTPParametrizedVectorFieldGraph.png b/doc/images/JKQTPParametrizedVectorFieldGraph.png
new file mode 100644
index 00000000000..99f1417308b
Binary files /dev/null and b/doc/images/JKQTPParametrizedVectorFieldGraph.png differ
diff --git a/doc/images/JKQTPParametrizedVectorFieldGraphColorFromAngle.png b/doc/images/JKQTPParametrizedVectorFieldGraphColorFromAngle.png
new file mode 100644
index 00000000000..e5c1bdcd6f9
Binary files /dev/null and b/doc/images/JKQTPParametrizedVectorFieldGraphColorFromAngle.png differ
diff --git a/doc/images/JKQTPParametrizedVectorFieldGraphColorFromMagnitude.png b/doc/images/JKQTPParametrizedVectorFieldGraphColorFromMagnitude.png
new file mode 100644
index 00000000000..517c5ea512a
Binary files /dev/null and b/doc/images/JKQTPParametrizedVectorFieldGraphColorFromMagnitude.png differ
diff --git a/doc/images/JKQTPParametrizedVectorFieldGraphDefaultColor.png b/doc/images/JKQTPParametrizedVectorFieldGraphDefaultColor.png
new file mode 100644
index 00000000000..ffbffc177b3
Binary files /dev/null and b/doc/images/JKQTPParametrizedVectorFieldGraphDefaultColor.png differ
diff --git a/doc/images/JKQTPVectorFieldGraphIgnoreLength.png b/doc/images/JKQTPVectorFieldGraphIgnoreLength.png
index fd7713d0ccd..ffd8b330027 100644
Binary files a/doc/images/JKQTPVectorFieldGraphIgnoreLength.png and b/doc/images/JKQTPVectorFieldGraphIgnoreLength.png differ
diff --git a/doc/images/JKQTPVectorFieldGraphIgnoreLengthAutoscaleLineWidthFromLength.png b/doc/images/JKQTPVectorFieldGraphIgnoreLengthAutoscaleLineWidthFromLength.png
index cf7b00401bb..99be29d60cf 100644
Binary files a/doc/images/JKQTPVectorFieldGraphIgnoreLengthAutoscaleLineWidthFromLength.png and b/doc/images/JKQTPVectorFieldGraphIgnoreLengthAutoscaleLineWidthFromLength.png differ
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 14118fe5e75..a63575ccc65 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -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)
diff --git a/examples/paramvectorfield/CMakeLists.txt b/examples/paramvectorfield/CMakeLists.txt
new file mode 100644
index 00000000000..d1a11dd0bf6
--- /dev/null
+++ b/examples/paramvectorfield/CMakeLists.txt
@@ -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})
diff --git a/examples/paramvectorfield/README.md b/examples/paramvectorfield/README.md
new file mode 100644
index 00000000000..ddb96eeac1d
--- /dev/null
+++ b/examples/paramvectorfield/README.md
@@ -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)
+
+
diff --git a/examples/paramvectorfield/paramvectorfield.cpp b/examples/paramvectorfield/paramvectorfield.cpp
new file mode 100644
index 00000000000..d6ed84127ac
--- /dev/null
+++ b/examples/paramvectorfield/paramvectorfield.cpp
@@ -0,0 +1,86 @@
+/** \example vectorfield.cpp
+ * Display a vector field
+ *
+ * \ref JKQTPlotterVectorFieldExample
+ */
+
+#include "jkqtpexampleapplication.h"
+#include
+#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();
+}
diff --git a/examples/vectorfield/README.md b/examples/vectorfield/README.md
index 1424d11232a..2f1ba73e052 100644
--- a/examples/vectorfield/README.md
+++ b/examples/vectorfield/README.md
@@ -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).
diff --git a/examples/vectorfield/vectorfield.cpp b/examples/vectorfield/vectorfield.cpp
index 7a44f5ba622..040b29c6f19 100644
--- a/examples/vectorfield/vectorfield.cpp
+++ b/examples/vectorfield/vectorfield.cpp
@@ -73,7 +73,7 @@ int main(int argc, char* argv[])
});
app.addExportStepFunctor([&](){
graph1->setAnchorPoint(JKQTPVectorFieldGraph::AnchorMid);
- plot.redrawPlot();
+ plot.redrawPlot();
});
app.addExportStepFunctor([&](){
diff --git a/lib/jkqtplotter/graphs/jkqtpbarchartbase.h b/lib/jkqtplotter/graphs/jkqtpbarchartbase.h
index 4aa16352fad..f85ba1fcbbf 100644
--- a/lib/jkqtplotter/graphs/jkqtpbarchartbase.h
+++ b/lib/jkqtplotter/graphs/jkqtpbarchartbase.h
@@ -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;
diff --git a/lib/jkqtplotter/graphs/jkqtpboxplot.h b/lib/jkqtplotter/graphs/jkqtpboxplot.h
index 915f07e11e8..ba0733ffddc 100644
--- a/lib/jkqtplotter/graphs/jkqtpboxplot.h
+++ b/lib/jkqtplotter/graphs/jkqtpboxplot.h
@@ -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() */
diff --git a/lib/jkqtplotter/graphs/jkqtpscatter.cpp b/lib/jkqtplotter/graphs/jkqtpscatter.cpp
index 6e6103939b8..5fa582d6e4d 100644
--- a/lib/jkqtplotter/graphs/jkqtpscatter.cpp
+++ b/lib/jkqtplotter/graphs/jkqtpscatter.cpp
@@ -270,6 +270,7 @@ JKQTPXYParametrizedScatterGraph::JKQTPXYParametrizedScatterGraph(JKQTBasePlotter
symbolFillDerivationMode=JKQTPColorDerivationMode::JKQTPFFCMLighterColor;
if (parent) {
symbolFillDerivationMode=parent->getCurrentPlotterStyle().graphsStyle.defaultGraphStyle.fillColorDerivationMode;
+ palette=parent->getCurrentPlotterStyle().graphsStyle.defaultPalette;
}
clearSizeColumnFunctor();
diff --git a/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp b/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp
index d0126afbdc2..52536e8c06c 100644
--- a/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp
+++ b/lib/jkqtplotter/graphs/jkqtpvectorfield.cpp
@@ -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 lengths;
+ std::vector lengths;
lengths.reserve(imax-imin);
for (int iii=imin; iiiget(static_cast(yColumn),static_cast(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<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(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; iiiget(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(__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);
+ }
+
+}
diff --git a/lib/jkqtplotter/graphs/jkqtpvectorfield.h b/lib/jkqtplotter/graphs/jkqtpvectorfield.h
index d6d00541019..e7d6ad834b1 100644
--- a/lib/jkqtplotter/graphs/jkqtpvectorfield.h
+++ b/lib/jkqtplotter/graphs/jkqtpvectorfield.h
@@ -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 quiver plot (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
diff --git a/lib/jkqtplotter/jkqtpgraphsbase.h b/lib/jkqtplotter/jkqtpgraphsbase.h
index a777d7c0fe6..156310dc866 100644
--- a/lib/jkqtplotter/jkqtpgraphsbase.h
+++ b/lib/jkqtplotter/jkqtpgraphsbase.h
@@ -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;
diff --git a/screenshots/paramvectorfield.png b/screenshots/paramvectorfield.png
new file mode 100644
index 00000000000..92181809e5f
Binary files /dev/null and b/screenshots/paramvectorfield.png differ
diff --git a/screenshots/paramvectorfield_small.png b/screenshots/paramvectorfield_small.png
new file mode 100644
index 00000000000..fd88d084d21
Binary files /dev/null and b/screenshots/paramvectorfield_small.png differ
|