JKQTPlotter:

- NEW: improved plotting speed for line-graphs by a compression algorithm (see JKQTPGraphLinesCompressionMixin) that removes overlaying lines (e.g. in JKQTPXYLineGraph)
    - NEW: improved plotting speed for line-graphs by a clipping algorithm (applies to JKQTPXYLineGraph, JKQTPGraphErrorStyleMixin, JKQTPSpecialLineHorizontalGraph, JKQTPSpecialLineVerticalGraph and others)
    - NEW: improved plotting speed for scatter-graphs by not calling draw functions for symbols outside the plot window (e.g. in JKQTPXYLineGraph)
This commit is contained in:
jkriege2 2022-08-25 15:17:50 +02:00
parent 17b93ab580
commit 03031e3762
20 changed files with 719 additions and 174 deletions

View File

@ -463,7 +463,7 @@ LOOKUP_CACHE_SIZE = 0
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
NUM_PROC_THREADS = 1
NUM_PROC_THREADS = 2
#---------------------------------------------------------------------------
# Build related configuration options

View File

@ -33,7 +33,6 @@ This page lists several todos and wishes for future version of JKQTPlotter
<li>plot: axes with symlog (see http://dx.doi.org/10.1088/0957-0233/24/2/027001) and logit (https://de.m.wikipedia.org/wiki/Logit) scaling? </li>
<li>graphs: add candlestick charts (financial, see https://en.m.wikipedia.org/wiki/Candlestick_chart)</li>
<li>graphs: add OHLC charts (financial, see https://en.m.wikipedia.org/wiki/Open-high-low-close_chart)</li>
<li>graphs: explore/demonstrate/improve plotting speed for large dataset (e.g. only draw visible elements, use algorithm to draw fewer lines if they overlay ...)</li>
<li>plot: refactor print preview/export preview code </li>
<li></li>
<li></li>

View File

@ -27,10 +27,13 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
<li>IMPROVED: QT6-compatibility by removing deprecated warnings</li>
<li>NEW: JKQTPFilledCurveXGraph and JKQTPFilledCurveYGraph can now plot wiggle plots with different fill styles above and below the baseline (feature request <a href="https://github.com/jkriege2/JKQtPlotter/issues/68">#68 Wiggle Plots</a> from <a href="https://github.com/xichaoqiang">user:xichaoqiang</a> </li>
<li>NEW/BREAKING CHANGE: data tooltip can now also be shown when "just" moving the mouse (so far this was only possible when dragging the mouse with a button pressed). This also removes JKQtPlotter::getActMouseLeftAsToolTip() and adds JKQtPlotter::getActMouseMoveToolTip() instead! Also the default toolbars and context menus changed!</li>
<li>NEW: new "seaborn" style for plots</li>
<li>NEW: new "seaborn" style for plots, see \ref jkqtpplotter_styling </li>
<li>NEW/BREAKING CHANGE: changed JKQTPColorDerivationMode into a struct, which extends its capabilities above the previously available few enum-items</li>
<li>NEW: added debug-feature to show boxes around text in the plot</li>
<li>BREAKING: Print-Support can now be switched off with a CMAKE-option JKQtPlotter_BUILD_FORCE_NO_PRINTER_SUPPORT=ON. This also switches off PDF and SVG export, partly solves issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/81">#81</a>, many thanks to <a href="https://github.com/sufuk">user:sufuk</a> for contributing part of the code and supplying the idea! </li>
<li>NEW: improved plotting speed for line-graphs by a compression algorithm (see JKQTPGraphLinesCompressionMixin) that removes overlaying lines (e.g. in JKQTPXYLineGraph)</li>
<li>NEW: improved plotting speed for line-graphs by a clipping algorithm (applies to JKQTPXYLineGraph, JKQTPGraphErrorStyleMixin, JKQTPSpecialLineHorizontalGraph, JKQTPSpecialLineVerticalGraph and others)</li>
<li>NEW: improved plotting speed for scatter-graphs by not calling draw functions for symbols outside the plot window (e.g. in JKQTPXYLineGraph)</li>
</ul></li>
<li>JKQTMathText:<ul>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -98,7 +98,7 @@ int main(int argc, char* argv[])
// graph title is made from symbol+errorStylestyle, we use the LaTeX instruction \verb around the
// result of JKQTPErrorPlotstyle2String(), because it contains underscores that would otherwise
// lead to lower-case letter, which we don't want
graph->setTitle("\\verb{"+JKQTPErrorPlotstyle2String(errorStyles[errorID])+"}");
graph->setTitle("\\verb!"+JKQTPErrorPlotstyle2String(errorStyles[errorID])+"!");
// add the graph to the plot, so it is actually displayed
plot.addGraph(graph);

View File

@ -53,7 +53,7 @@ int main(int argc, char* argv[])
plot.addGraph(a=new JKQTPGeoArrow(&plot, 0.7, arr_y, 0.9, arr_y+0.05, decor, JKQTPNoDecorator)); a->setStyle(QColor("red"), 1);
plot.addGraph(a=new JKQTPGeoArrow(&plot, 1.0, arr_y, 1.3, arr_y+0.05, decor, JKQTPNoDecorator)); a->setStyle(QColor("red"), 2);
plot.addGraph(a=new JKQTPGeoArrow(&plot, 1.4, arr_y, 1.7, arr_y+0.05, decor, JKQTPNoDecorator)); a->setStyle(QColor("red"), 3);
plot.addGraph(new JKQTPGeoText(&plot, a->getX2()+0.05, a->getY2(), "\\verb{"+JKQTPLineDecoratorStyle2String(decor)+"}", 12, a->getLineColor()));
plot.addGraph(new JKQTPGeoText(&plot, a->getX2()+0.05, a->getY2(), "\\verb!"+JKQTPLineDecoratorStyle2String(decor)+"!", 12, a->getLineColor()));
arr_y+=arr_deltay;
}

View File

@ -59,7 +59,7 @@ int main(int argc, char* argv[])
// the graph is updated:
auto updateGraphFunctor=
[=]() {
parsedFunc->setTitle(QString("user function: \\verb{"+edit->text()+"}, p_1=%1, p_2=%2").arg(spinP1->value()).arg(spinP2->value()));
parsedFunc->setTitle(QString("user function: \\verb!"+edit->text()+"!, p_1=%1, p_2=%2").arg(spinP1->value()).arg(spinP2->value()));
parsedFunc->setFunction(edit->text());
parsedFunc->setParamsV(spinP1->value(), spinP2->value());
parsedFunc->setDisplaySamplePoints(check->isChecked());

View File

@ -6,11 +6,16 @@
#include "speedtestplot.h"
#include "jkqtplotter/graphs/jkqtpscatter.h"
#include "jkqtcommon_statistics_and_math/jkqtpstatisticstools.h"
SpeedTestPlot::SpeedTestPlot():
JKQTPlotter(), NDATA(500), dx(1.0/500.0*4.0*JKQTPSTATISTICS_PI), x0(0)
{
X.fill(0);
Y.fill(0);
Y2.fill(0);
// 1. optimize JKQTPlotter for speed (by switching off anti-aliasing)
getPlotter()->setUseAntiAliasingForGraphs(false);
getPlotter()->setUseAntiAliasingForSystem(false);
@ -18,12 +23,7 @@ SpeedTestPlot::SpeedTestPlot():
// 2. now we create data for a simple plot (a sine curve + random[-0.5,0.5])
for (size_t i=0; i<X.size(); i++) {
const double x=static_cast<double>(i)*dx;
X[i]=x0+x;
Y[i]=sin(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y2[i]=cos(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
}
updateDataSize(NDATA, false);
// 3. make data available to JKQTPlotter by adding it to the internal datastore.
// Here the data from the std::array's is not copied, but only the pointer to
@ -60,6 +60,7 @@ SpeedTestPlot::SpeedTestPlot():
p->getPlotter()->setUseAntiAliasingForGraphs(p->actAntiAliase->isChecked());
p->getPlotter()->setUseAntiAliasingForSystem(p->actAntiAliase->isChecked());
p->getPlotter()->setUseAntiAliasingForText(p->actAntiAliase->isChecked());
if (!p->actAnimation->isChecked()) p->redrawPlot();
}, this));
actTwoGraphs=new QAction(QObject::tr("2 Graphs"));
@ -67,6 +68,7 @@ SpeedTestPlot::SpeedTestPlot():
actTwoGraphs->setChecked(true);
connect(actTwoGraphs, &QAction::triggered, std::bind([](SpeedTestPlot* p, JKQTPXYLineGraph* g){
g->setVisible(p->actTwoGraphs->isChecked());
if (!p->actAnimation->isChecked()) p->redrawPlot();
}, this, graph2));
actFixedXAxis=new QAction(QObject::tr("Fixed X-Axis"));
@ -76,18 +78,20 @@ SpeedTestPlot::SpeedTestPlot():
actLines=new QAction(QObject::tr("Show Graph Lines"));
actLines->setCheckable(true);
actLines->setChecked(true);
connect(actLines, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2){
connect(actLines, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2,SpeedTestPlot* p){
g->setDrawLine(enabled);
g2->setDrawLine(enabled);
}, std::placeholders::_1, graph, graph2));
if (!p->actAnimation->isChecked()) p->redrawPlot();
}, std::placeholders::_1, graph, graph2,this));
actSymbols=new QAction(QObject::tr("Show Graph Symbols"));
actSymbols->setCheckable(true);
actSymbols->setChecked(true);
connect(actSymbols, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2){
connect(actSymbols, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2,SpeedTestPlot* p){
g->setSymbolType(enabled?JKQTPCross:JKQTPNoSymbol);
g2->setSymbolType(enabled?JKQTPCircle:JKQTPNoSymbol);
}, std::placeholders::_1, graph, graph2));
if (!p->actAnimation->isChecked()) p->redrawPlot();
}, std::placeholders::_1, graph, graph2,this));
menuSizes=new QMenu(QObject::tr("number of datapoints"), this);
QActionGroup* actGroup=new QActionGroup(menuSizes);
@ -103,12 +107,58 @@ SpeedTestPlot::SpeedTestPlot():
menuSizes->addAction(act);
}
actUseNonvisibleLineCompression=new QAction(QObject::tr("use NonvisibleLineCompression"));
actUseNonvisibleLineCompression->setCheckable(true);
actUseNonvisibleLineCompression->setChecked(true);
connect(actUseNonvisibleLineCompression, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2,SpeedTestPlot* p){
g->setUseNonvisibleLineCompression(enabled);
g2->setUseNonvisibleLineCompression(enabled);
if (!p->actAnimation->isChecked()) p->redrawPlot();
}, std::placeholders::_1, graph, graph2,this));
menuUseNonvisibleLineCompressionAgressiveness=new QMenu(QObject::tr("NonvisibleLineCompression level"), this);
actGroup=new QActionGroup(menuUseNonvisibleLineCompressionAgressiveness);
for (double a: {0.5, 0.8, 1.0, 1.5, 2.0, 5.0}) {
QAction* act=actGroup->addAction(QString::number(a));
act->setCheckable(true);
act->setChecked(a==1.0);
connect(act, &QAction::toggled, std::bind([](bool enabled,JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2,SpeedTestPlot* p, double a){
g->setNonvisibleLineCompressionAgressiveness(a);
g2->setNonvisibleLineCompressionAgressiveness(a);
if (!p->actAnimation->isChecked()) p->redrawPlot();
}, std::placeholders::_1, graph, graph2,this, a));
menuUseNonvisibleLineCompressionAgressiveness->addAction(act);
}
actStepAnimation=new QAction(QObject::tr("Next Animation Step"));
actStepAnimation->setCheckable(false);
actStepAnimation->setEnabled(false);
connect(actStepAnimation, &QAction::triggered, std::bind([](SpeedTestPlot* p){
p->plotNewData();
}, this));
actAnimation=new QAction(QObject::tr("Animation Active"));
actAnimation->setCheckable(true);
actAnimation->setChecked(true);
connect(actAnimation, &QAction::toggled, std::bind([](bool enabled, SpeedTestPlot* p, QAction* actStepAnimation){
if (enabled) {
p->plotNewData();
}
actStepAnimation->setEnabled(!enabled);
}, std::placeholders::_1, this, actStepAnimation));
addAction(actAntiAliase);
addAction(actFixedXAxis);
addAction(menuSizes->menuAction());
addAction(actTwoGraphs);
addAction(actLines);
addAction(actSymbols);
addAction(actUseNonvisibleLineCompression);
addAction(menuUseNonvisibleLineCompressionAgressiveness->menuAction());
addAction(actAnimation);
addAction(actStepAnimation);
// show plotter and make it a decent size
show();
@ -130,8 +180,8 @@ void SpeedTestPlot::plotNewData()
Y2[i]=Y2[i+1];
}
// add one new data point
Y[NDATA-1]=sin(X[NDATA-1]+x0)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y2[NDATA-1]=cos(X[NDATA-1]+x0)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y[NDATA-1]=sin(X[NDATA-1]+x0)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5+addOutlier(1.0/static_cast<double>(NDATA/5), 2.0);
Y2[NDATA-1]=cos(X[NDATA-1]+x0)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5+addOutlier(1.0/static_cast<double>(NDATA/7), 2.0);
} else {
// move old data to the left
for (size_t i=0; i<NDATA-1; i++) {
@ -141,8 +191,8 @@ void SpeedTestPlot::plotNewData()
}
// add one new data point
X[NDATA-1]=X[NDATA-2]+dx;
Y[NDATA-1]=sin(X[NDATA-1])+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y2[NDATA-1]=cos(X[NDATA-1])+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y[NDATA-1]=sin(X[NDATA-1])+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5+addOutlier(1.0/static_cast<double>(NDATA/5), 2.0);
Y2[NDATA-1]=cos(X[NDATA-1])+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5+addOutlier(1.0/static_cast<double>(NDATA/7), 2.0);
}
@ -155,12 +205,17 @@ void SpeedTestPlot::plotNewData()
const auto tlastalst=t_lastplot;
t_lastplot=std::chrono::system_clock::now();
const double delta_secs=static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(t_lastplot-tlastalst).count())/1000.0;
setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1.0/delta_secs,0,'f',2).arg(NDATA));
calctimes.push_back(delta_secs);
if (delta_secs<0.05) { while (calctimes.size()>60) calctimes.pop_front(); }
else if (delta_secs<0.1) { while (calctimes.size()>30) calctimes.pop_front(); }
else if (delta_secs<1) { while (calctimes.size()>10) calctimes.pop_front(); }
else { while (calctimes.size()>5) calctimes.pop_front(); }
setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %3 [this: %1] fps").arg(1.0/delta_secs,0,'f',2).arg(NDATA).arg(1.0/jkqtpstatAverage(calctimes.begin(), calctimes.end()),0,'f',2));
// enqueue call for next data value
QTimer::singleShot(1, this, SLOT(plotNewData()));
if (actAnimation->isChecked()) QTimer::singleShot(1, this, SLOT(plotNewData()));
}
void SpeedTestPlot::updateDataSize(size_t newSize)
void SpeedTestPlot::updateDataSize(size_t newSize, bool updatePlots)
{
NDATA=newSize;
dx=1.0/double(NDATA)*4.0*JKQTPSTATISTICS_PI;
@ -168,10 +223,11 @@ void SpeedTestPlot::updateDataSize(size_t newSize)
for (size_t i=0; i<X.size(); i++) {
const double x=static_cast<double>(i)*dx;
X[i]=x0+x;
Y[i]=sin(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y2[i]=cos(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y[i]=sin(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5+addOutlier(1.0/static_cast<double>(NDATA/5), 2.0);
Y2[i]=cos(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5+addOutlier(1.0/static_cast<double>(NDATA/7), 2.0);
}
if (updatePlots) {
// 3. make data available to JKQTPlotter by adding it to the internal datastore.
// Here the data from the std::array's is not copied, but only the pointer to
// the array is added to the datastore. therefore the datastore does not manage
@ -193,3 +249,10 @@ void SpeedTestPlot::updateDataSize(size_t newSize)
setX(X[0], X[NDATA]);
setY(-2,2);
}
}
double SpeedTestPlot::addOutlier(double prob, double height)
{
if (static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)<prob) return height*(2.0*static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-1.0);
else return 0;
}

View File

@ -2,6 +2,7 @@
#include <array>
#include <random>
#include <chrono>
#include <QList>
#include <QMenu>
#include <QActionGroup>
#include "jkqtplotter/jkqtplotter.h"
@ -22,15 +23,22 @@ class SpeedTestPlot: public JKQTPlotter {
QAction* actFixedXAxis;
QAction* actLines;
QAction* actSymbols;
QAction* actUseNonvisibleLineCompression;
QMenu* menuUseNonvisibleLineCompressionAgressiveness;
QAction* actUseClipping;
QAction* actAnimation;
QAction* actStepAnimation;
QMenu* menuSizes;
JKQTPXYLineGraph* graph;
JKQTPXYLineGraph* graph2;
QList<double> calctimes;
public:
SpeedTestPlot();
virtual ~SpeedTestPlot();
protected slots:
void plotNewData();
void updateDataSize(size_t newSize);
void updateDataSize(size_t newSize, bool updatePlots=true);
static double addOutlier(double prob, double height);
};

View File

@ -347,3 +347,137 @@ double JKQTPLineDecoratorStyleCalcDecoratorSize(double line_width, double decora
return decoratorSizeFactor*pow(line_width, 0.7);
}
QList<QPointF> JKQTPSimplifyPolyLines(const QList<QPointF> &lines_in, double maxDeltaXY)
{
if (lines_in.size()<=1) return QList<QPointF>();
if (lines_in.size()<=10) return lines_in;
QList<QPointF> l;
l.reserve(lines_in.size());
int groupStart=0;
int groupCount=1;
QRectF groupSize(lines_in[0], QSizeF(0,0));
auto writeGroup=[](QList<QPointF>& l, const int& groupStart, const int& groupCount, const QRectF& groupSize, const QList<QPointF>& lines_in, double maxDeltaXY) {
// group ends
if (groupCount>4) {
// we can optimize the group away
// we take the first and the last point and in between one at the top and one at the bottom of the group
// unless we have a group that is smaller than maxDeltaXY in both directions, the the first and last point suffice
const QPointF& x0=lines_in[groupStart];
l<<x0; // first point
if (groupSize.width()<=maxDeltaXY && groupSize.height()>maxDeltaXY) {
// x-group, i.e. small on x-axis
l<<QPointF(x0.x()+groupSize.width()/3.0, groupSize.bottom());
l<<QPointF(x0.x()+groupSize.width()*2.0/3.0, groupSize.top());
} else if (groupSize.width()>maxDeltaXY && groupSize.height()<=maxDeltaXY) {
// y-group, i.e. small on y-axis
l<<QPointF(groupSize.left(), x0.y()+groupSize.height()/3.0);
l<<QPointF(groupSize.right(), x0.y()+groupSize.height()*2.0/3.0);
}
l<<lines_in[groupStart+groupCount-1]; // last point
} else {
// small groups cannot be optimized away
// so we simply copy it to the output
for (int j=groupStart; j<groupStart+groupCount; j++) l<<lines_in[j];
}
};
for (int i=0; i<lines_in.size(); i++) {
const QPointF& x=lines_in[i];
QRectF newGroupSize=groupSize;
newGroupSize.setLeft(qMin(newGroupSize.left(), x.x()));
newGroupSize.setRight(qMax(newGroupSize.right(), x.x()));
newGroupSize.setTop(qMin(newGroupSize.top(), x.y()));
newGroupSize.setBottom(qMax(newGroupSize.bottom(), x.y()));
if (newGroupSize.width()<=maxDeltaXY || newGroupSize.height()<=maxDeltaXY) {
// points still in group, so extend the group
groupCount++;
groupSize=newGroupSize;
} else {
// group ends, because adding a new point would increase it's size too much
writeGroup(l, groupStart, groupCount, groupSize, lines_in, maxDeltaXY);
// start new group with current point
groupStart=i;
groupCount=1;
groupSize=QRectF(x, QSizeF(0,0));
}
}
writeGroup(l, groupStart, groupCount, groupSize, lines_in, maxDeltaXY);
//qDebug()<<"JKQTPSimplifyPolyLines("<<lines_in.size()<<", maxDeltaXY="<<maxDeltaXY<<") -> "<<l.size();
return l;
}
QList<QList<QPointF> > JKQTPClipPolyLine(const QList<QPointF> &polyline_in, const QRectF &clipRect)
{
QList<QList<QPointF>> l;
l<<polyline_in;
return JKQTPClipPolyLines(l, clipRect);
}
QList<QList<QPointF> > JKQTPClipPolyLines(const QList<QList<QPointF> > &polylines_in, const QRectF &clipRect)
{
const double xmin=qMin(clipRect.left(), clipRect.right());
const double xmax=qMax(clipRect.left(), clipRect.right());
const double ymin=qMin(clipRect.top(), clipRect.bottom());
const double ymax=qMax(clipRect.top(), clipRect.bottom());
QList<QList<QPointF>> out;
for (const QList<QPointF>& pl: polylines_in) {
if (pl.size()>1) {
if (out.size()==0 || out.last().size()>0) {
out<<QList<QPointF>();
}
for (int i=1; i<pl.size(); i++) {
const QLineF l(pl[i-1], pl[i]);
const QLineF lclipped=JKQTPClipLine(l, xmin, xmax, ymin, ymax);
//qDebug()<<" "<<l<<" --> "<<lclipped<<" clip: x="<<xmin<<".."<<xmax<<", y="<<ymin<<".."<<ymax;
// 0-length: line remove ==> dont's add anything, start new segment if necessary
// l=lclipped: no point was clipped: add second point, or if list empty both points (first line segment)
// only p1 clipped, i.e. second point clipped: add second point, end segmnt (i.e. start a new one)
// only p2 clipped, i.e. first point clipped: start new segment, add first point to it
// p1 and p2 clipped, i.e. segment clipped on both ends: add clipped line as separate segment
if (lclipped.length()==0) {
if (out.last().size()>0) {
out<<QList<QPointF>();
}
} else if (l==lclipped) {
if (out.last().size()==0) {
out.last()<<lclipped.p1();
} if (out.last().last()!=lclipped.p1()) {
out<<QList<QPointF>();
out.last()<<lclipped.p1();
}
out.last()<<lclipped.p2();
} else if (l.p1()==lclipped.p1() && l.p2()!=lclipped.p2()) {
if (out.last().size()==0) {
out.last()<<lclipped.p1();
} if (out.last().last()!=lclipped.p1()) {
out<<QList<QPointF>();
out.last()<<lclipped.p1();
}
out.last()<<lclipped.p2();
out<<QList<QPointF>();
} else if (l.p1()!=lclipped.p1() && l.p2()==lclipped.p2()) {
if (out.last().size()==0) {
out.last()<<lclipped.p1();
} if (out.last().last()!=lclipped.p1()) {
out<<QList<QPointF>();
out.last()<<lclipped.p1();
}
out.last()<<lclipped.p2();
} else if (l.p1()!=lclipped.p1() && l.p2()!=lclipped.p2()) {
if (out.last().size()>0) {
out<<QList<QPointF>();
}
out.last()<<lclipped.p1();
out.last()<<lclipped.p2();
out<<QList<QPointF>();
}
}
}
}
return out;
}

View File

@ -261,6 +261,154 @@ inline void JKQTPPlotSymbol(TPainter& painter, double x, double y, JKQTPGraphSym
JKQTCOMMON_LIB_EXPORT void JKQTPPlotSymbol(QPaintDevice& paintDevice, double x, double y, JKQTPGraphSymbols symbol, double size, double symbolLineWidth, QColor color, QColor fillColor);
/*! \brief clips the given line (\a x1 , \a y1 ) -- (\a x2 , \a y2 ) to the given rectangle \a xmin .. \a xmax and \a ymin ... \a ymax
\ingroup jkqtptools_drawing
\return the clipped line in \a x1 , \a y1 , \a x2 , \a y2 and \c true if the line is still to be drawn or \c false else
This function implements the algorithm descripbed in https://www.researchgate.net/publication/335018076_Another_Simple_but_Faster_Method_for_2D_Line_Clipping
i.e. in Pseudocode
\verbatim
// x1 , y1 , x2 , y2 , xmin , ymax , xmax , ymin //
if not ( x1<xmin and x2<xmin ) and not ( x1>xmax and x2>xmax ) then
if not ( y1<ymin and y2<ymin ) and not ( y1>ymax and y2>ymax ) then
x[1]= x1
y[1]= y1
x[2]= x2
y[2]= y2
i =1
repeat
if x[i] < xmin then
x[i] = xmin
y[i] = ( ( y2-y1 ) / ( x2-x1 ) ) * ( xmin-x1)+y1
elseif x[i] > xmax then
x[i] = xmax
y[i] = ( ( y2-y1 ) / ( x2-x1 ) ) * ( xmax-x1)+y1
endif
if y[i] < ymin then
y[i] = ymin
x[i] = ( ( x2-x1 ) / ( y2-y1 ) ) * ( ymin-y1)+x1
elseif y[i] > ymax then
y[i] = ymax
x[i] = ( ( x2-x1 ) / ( y2-y1 ) ) * ( ymax-y1)+x1
endif
i = i + 1
until i >2
if not ( x [1 ] < xmin and x [2 ] < xmin ) and not ( x [1 ] >xmax and x [2 ] >xmax ) then
drawLine ( x[1] , y[1] , x[2] , y[2] )
endif
endif
endif
\endverbatim
*/
inline bool JKQTPClipLine(double& x1 , double& y1 , double& x2 , double& y2 , double xmin , double xmax , double ymin, double ymax) {
if (! ( x1<xmin && x2<xmin ) && !( x1>xmax && x2>xmax )) {
if ( !( y1<ymin && y2<ymin ) && !( y1>ymax && y2>ymax ) ) {
double x[2]= {x1,x2};
double y[2]= {y1,y2};
for (int i=0; i<2; i++) {
if (x[i] < xmin) {
x[i] = xmin;
y[i] = ( ( y2-y1 ) / ( x2-x1 ) ) * ( xmin-x1)+y1;
} else if (x[i] > xmax) {
x[i] = xmax;
y[i] = ( ( y2-y1 ) / ( x2-x1 ) ) * ( xmax-x1)+y1;
}
if (y[i] < ymin) {
y[i] = ymin;
x[i] = ( ( x2-x1 ) / ( y2-y1 ) ) * ( ymin-y1)+x1;
} else if (y[i] > ymax) {
y[i] = ymax;
x[i] = ( ( x2-x1 ) / ( y2-y1 ) ) * ( ymax-y1)+x1;
}
}
if (! ( x[0] < xmin && x[1]< xmin ) && !( x[0] >xmax && x[1] >xmax )) {
x1=x[0];
y1=y[0];
x2=x[1];
y2=y[1];
return true;
}
}
}
return false;
}
/*! \brief clips the given line \a line to the given rectangle rectangle \a xmin .. \a xmax and \a ymin ... \a ymax
\ingroup jkqtptools_drawing
\return the clipped line or a line with 0-length, i.e. QLineF()
This function implements the algorithm descripbed in https://www.researchgate.net/publication/335018076_Another_Simple_but_Faster_Method_for_2D_Line_Clipping
*/
inline QLineF JKQTPClipLine(const QLineF& line, double xmin , double xmax , double ymin, double ymax) {
double x1=line.x1();
double y1=line.y1();
double x2=line.x2();
double y2=line.y2();
if (JKQTPClipLine(x1,y1,x2,y2,xmin,xmax,ymin,ymax)) {
return QLineF(x1,y1,x2,y2);
} else {
return QLineF();
}
}
/*! \brief clips the given line \a line to the given rectangle \a clipRect
\ingroup jkqtptools_drawing
\param line line to be clipped
\param clipRect rectangle to clip to
\return the clipped line or a line with 0-length, i.e. QLineF()
This function implements the algorithm descripbed in https://www.researchgate.net/publication/335018076_Another_Simple_but_Faster_Method_for_2D_Line_Clipping
*/
inline QLineF JKQTPClipLine(const QLineF& line, const QRectF& clipRect) {
const double xmin=qMin(clipRect.left(), clipRect.right());
const double xmax=qMax(clipRect.left(), clipRect.right());
const double ymin=qMin(clipRect.top(), clipRect.bottom());
const double ymax=qMax(clipRect.top(), clipRect.bottom());
double x1=line.x1();
double y1=line.y1();
double x2=line.x2();
double y2=line.y2();
if (JKQTPClipLine(x1,y1,x2,y2,xmin,xmax,ymin,ymax)) {
return QLineF(x1,y1,x2,y2);
} else {
return QLineF();
}
}
/*! \brief clips the given list of poly-lines \a polylines_in to the given rectangle \a clipRect
\ingroup jkqtptools_drawing
\param polylines_in list of poly-lines to be clipped
\param clipRect rectangle to clip to
\return a list of poly-lines representing the clipped lines. Note that some lines may be split further so the number of poly-lines in the output may actually be larger than the number of polylines in the input!
*/
JKQTCOMMON_LIB_EXPORT QList<QList<QPointF>> JKQTPClipPolyLines(const QList<QList<QPointF>> & polylines_in, const QRectF& clipRect);
/*! \brief clips the given poly-line \a polyline_in to the given rectangle \a clipRect
\ingroup jkqtptools_drawing
\param polyline_in poly-line to be clipped
\param clipRect rectangle to clip to
\return a list of poly-lines representing the clipped line.
*/
JKQTCOMMON_LIB_EXPORT QList<QList<QPointF>> JKQTPClipPolyLine(const QList<QPointF> & polyline_in, const QRectF& clipRect);
/*! \brief tries to reduce the complexity of the given poly-line \a lines_in, but keeping the appearance as if all lines were drawn
\ingroup jkqtptools_drawing
\param lines_in poly-line to be simplified
\param maxDeltaXY a group has to be either less wide or less high than this, typically equals the linewidth of the poly-line
\return a simplified version of lines_in
*/
JKQTCOMMON_LIB_EXPORT QList<QPointF> JKQTPSimplifyPolyLines(const QList<QPointF>& lines_in, double maxDeltaXY=1.0);
/*! \brief draw a tooltip, using the current brush and pen of the provided painter
\ingroup jkqtptools_drawing
@ -286,6 +434,7 @@ inline void JKQTPDrawTooltip(TPainter& painter, double x, double y, const QRectF
template <class TPainter>
inline void JKQTPPlotSymbol(TPainter& painter, double x, double y, JKQTPGraphSymbols symbol, double symbolSize, double symbolLineWidth, QColor color, QColor fillColor) {
if (symbol==JKQTPNoSymbol) return;
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
QPen p=painter.pen();
p.setColor(color);

View File

@ -23,7 +23,9 @@
#include "jkqtplotter/jkqtpbaseplotter.h"
#include <stdlib.h>
#include <QDebug>
#include <QMarginsF>
#include <iostream>
#include "jkqtcommon/jkqtpdrawingtools.h"
#include "jkqtplotter/jkqtptools.h"
#include "jkqtplotter/jkqtpimagetools.h"
#include "jkqtplotter/graphs/jkqtpimage.h"
@ -47,10 +49,10 @@ JKQTPXYLineGraph::JKQTPXYLineGraph(JKQTPlotter* parent):
}
JKQTPXYLineGraph::JKQTPXYLineGraph(JKQTBasePlotter* parent):
JKQTPXYGraph(parent)
JKQTPXYGraph(parent),
drawLine(true)
{
sortData=JKQTPXYGraph::Unsorted;
drawLine=true;
initLineStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Default);
initSymbolStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Default);
@ -74,6 +76,14 @@ void JKQTPXYLineGraph::draw(JKQTPEnhancedPainter& painter) {
const QPen p=getLinePen(painter, parent);
const QPen penSelection=getHighlightingLinePen(painter, parent);
const auto symType=getSymbolType();
const double xmin=transformX(parent->getXAxis()->getMin());
const double xmax=transformX(parent->getXAxis()->getMax());
const double ymin=transformY(parent->getYAxis()->getMin());
const double ymax=transformY(parent->getYAxis()->getMax());
const double symbolSize=parent->pt2px(painter, getSymbolSize());
const QMarginsF clipMargins=(symType==JKQTPNoSymbol)?QMarginsF(0,0,0,0):QMarginsF(symbolSize,symbolSize,symbolSize,symbolSize);
const QRectF cliprect=QRectF(qMin(xmin,xmax),qMin(ymin,ymax),fabs(xmax-xmin),fabs(ymax-ymin))+clipMargins;
int imax=0;
@ -81,8 +91,8 @@ void JKQTPXYLineGraph::draw(JKQTPEnhancedPainter& painter) {
if (getIndexRange(imin, imax)) {
std::vector<QPolygonF> vec_linesP;
vec_linesP.push_back(QPolygonF());
QList<QList<QPointF>> vec_linesP;
vec_linesP.push_back(QList<QPointF>());
intSortData();
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
@ -93,31 +103,41 @@ void JKQTPXYLineGraph::draw(JKQTPEnhancedPainter& painter) {
//qDebug()<<"JKQTPXYLineGraph::draw(): (xv, yv) = ( "<<xv<<", "<<yv<<" )";
if (JKQTPIsOKFloat(xv) && JKQTPIsOKFloat(yv) && JKQTPIsOKFloat(x) && JKQTPIsOKFloat(y)) {
if (isHighlighted() && getSymbolType()!=JKQTPNoSymbol) {
//if (isHighlighted() && getSymbolType()!=JKQTPNoSymbol) {
//JKQTPPlotSymbol(painter, x, y, JKQTPFilledCircle, parent->pt2px(painter, symbolSize*1.5), parent->pt2px(painter, symbolWidth*parent->getLineWidthMultiplier()), penSelection.color(), penSelection.color());
}
//}
if ((!parent->getXAxis()->isLogAxis() || xv>0.0) && (!parent->getYAxis()->isLogAxis() || yv>0.0) ) {
plotStyledSymbol(parent, painter, x, y);
if (symType!=JKQTPNoSymbol && cliprect.contains(x,y)) plotStyledSymbol(parent, painter, x, y);
if (drawLine) {
vec_linesP[vec_linesP.size()-1] << QPointF(x,y);
vec_linesP.last() << QPointF(x,y);
}
} else {
vec_linesP.push_back(QPolygonF());
if (drawLine) {
if (vec_linesP.size()==0 || vec_linesP.last().size()>0)
vec_linesP.push_back(QList<QPointF>());
}
}
}
}
//qDebug()<<"JKQTPXYLineGraph::draw(): "<<4<<" lines="<<lines.size();
//qDebug()<<"JKQTPXYLineGraph::draw(): "<<5<<" p="<<painter.pen();
for (auto &linesP : vec_linesP) {
if (drawLine) {
//qDebug()<<"JKQTPXYLineGraph::draw(): vec_linesP.size()=="<<vec_linesP.size();
const QList<QList<QPointF>> linesToDraw=JKQTPClipPolyLines(vec_linesP, cliprect);
//qDebug()<<"JKQTPXYLineGraph::draw(): linesToDraw.size()=="<<linesToDraw.size()<<", clip: x="<<xmin<<".."<<xmax<<", y="<<ymin<<".."<<ymax;
for (const auto &linesPFromV : linesToDraw) {
//qDebug()<<"JKQTPXYLineGraph::draw(): linesPFromV.size()=="<<linesPFromV.size()<<" useNonvisibleLineCompression="<<getUseNonvisibleLineCompression();
const QList<QPointF> linesP=getUseNonvisibleLineCompression()?JKQTPSimplifyPolyLines(linesPFromV, p.widthF()*getNonvisibleLineCompressionAgressiveness()):linesPFromV;
//qDebug()<<"JKQTPXYLineGraph::draw(): --> linesP.size()=="<<linesP.size();
if (linesP.size()>0) {
if (isHighlighted()) {
painter.setPen(penSelection);
//painter.drawLines(lines);
painter.drawPolyline(linesP);
painter.drawPolyline(linesP.data(), linesP.size());
}
painter.setPen(p);
//painter.drawLines(lines);
painter.drawPolyline(linesP);
painter.drawPolyline(linesP.data(), linesP.size());
}
}
}
//qDebug()<<"JKQTPXYLineGraph::draw(): "<<6;
@ -174,6 +194,7 @@ void JKQTPXYLineGraph::setColor(QColor c)
}
JKQTPXYLineErrorGraph::JKQTPXYLineErrorGraph(JKQTBasePlotter *parent):
JKQTPXYLineGraph(parent)
{

View File

@ -51,9 +51,14 @@ class JKQTPDatastore;
\image html plot_lineplots.png
\see \ref JKQTPlotterAdvancedLineAndFillStyling, \ref JKQTPlotterSimpleTest, \ref JKQTPlotterSymbolsAndStyles, jkqtpstatAddVKDE1D(), jkqtpstatAddVKDE1DAutoranged(), jkqtpstatAddHKDE1D(), jkqtpstatAddHKDE1DAutoranged()
\note This classes can (and does by default) apply a line-compression strategy that improves plotting speed
but reduces accuracy a bit. See JKQTPGraphLinesCompressionMixin for details.
\see \ref JKQTPlotterAdvancedLineAndFillStyling, \ref JKQTPlotterSimpleTest, \ref JKQTPlotterSymbolsAndStyles,
jkqtpstatAddVKDE1D(), jkqtpstatAddVKDE1DAutoranged(), jkqtpstatAddHKDE1D(), jkqtpstatAddHKDE1DAutoranged(),
JKQTPGraphLinesCompressionMixin
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPXYLineGraph: public JKQTPXYGraph, public JKQTPGraphLineStyleMixin, public JKQTPGraphSymbolStyleMixin {
class JKQTPLOTTER_LIB_EXPORT JKQTPXYLineGraph: public JKQTPXYGraph, public JKQTPGraphLineStyleMixin, public JKQTPGraphSymbolStyleMixin, public JKQTPGraphLinesCompressionMixin {
Q_OBJECT
public:
/** \brief class constructor */
@ -76,13 +81,14 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPXYLineGraph: public JKQTPXYGraph, public JKQTP
/** \brief set color of line and symbol */
void setColor(QColor c);
protected:
/** \brief indicates whether to draw a line or not */
bool drawLine;
};
@ -100,7 +106,9 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPXYLineGraph: public JKQTPXYGraph, public JKQTP
\image html paramscatterplot.png "Different Styles of Parametrized Scatter/Line Graphs"
\image html paramscatterplot_image_star.png "JKQTPXYParametrizedScatterGraph with symbols organized in a grid"
\note This classes is meant for cases where you want to change the color/size/... of single symbols, in dependence
of data. If you are looking for a simple scatter-plot without data-dependent properties, use JKQTPXYLineGraph
instead, which is faster.
\note For the size, line width and symbol type columns, you can also set a functor, which converts the column value (optionally based
also on the x- and y-location of the data point) into the local symbol size, symbol type or line width. Use the functions
@ -110,6 +118,8 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPXYLineGraph: public JKQTPXYGraph, public JKQTP
\image html JKQTPXYParametrizedScatterGraph_LinewidthFunctor.png
\image html JKQTPXYParametrizedScatterGraph_SymbolFunctor.png
\image html paramscatterplot_image_star.png "JKQTPXYParametrizedScatterGraph with symbols organized in a grid"
\see JKQTPXYParametrizedErrorScatterGraph, \ref JKQTPlotterParamScatter , \ref JKQTPlotterParamScatterImage, \ref JKQTPlotterParametricCurves
*/

View File

@ -128,6 +128,14 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
QPen ph=getHighlightingLinePen(painter, parent);
QPen np(Qt::NoPen);
QBrush b=getFillBrush(painter, parent);
const double xmin=transformX(parent->getXAxis()->getMin());
const double xmax=transformX(parent->getXAxis()->getMax());
const double ymin=transformY(parent->getYAxis()->getMin());
const double ymax=transformY(parent->getYAxis()->getMax());
const auto symType=getSymbolType();
const double symbolSize=parent->pt2px(painter, getSymbolSize());
const QMarginsF clipMargins=(symType==JKQTPNoSymbol)?QMarginsF(0,0,0,0):QMarginsF(symbolSize,symbolSize,symbolSize,symbolSize);
const QRectF cliprect=QRectF(qMin(xmin,xmax),qMin(ymin,ymax),fabs(xmax-xmin),fabs(ymax-ymin))+clipMargins;
int imax=0;
int imin=0;
@ -135,18 +143,17 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
if (getIndexRange(imin, imax)) {
QPainterPath pl, pf;
QVector<QPointF> ps;
QPolygonF pl, pf;
QList<QPointF> ps;
double xold=-1;
double yold=-1;
double y0=transformY(getBaseline());
if (parent->getYAxis()->isLogAxis()) {
y0=transformY(parent->getYAxis()->getMin());
if (getBaseline()>0 && getBaseline()>parent->getYAxis()->getMin()) y0=transformY(getBaseline());
else y0=transformY(parent->getYAxis()->getMin());
}
bool subsequentItem=false;
bool firstPoint=true;
intSortData();
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
@ -158,7 +165,7 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
const double y=transformY(yv);
if (JKQTPIsOKFloat(x) && JKQTPIsOKFloat(y)) {
ps.append(QPointF(x,y));
if (subsequentItem) {
if (!firstPoint) {
//double xl1=xold;
//double yl1=yold;
//double xl2=x;
@ -171,13 +178,13 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
// *--------|
// xold/yold
const double d=(x-xold);
pf.lineTo(xold+d/2.0, yold);
pf.lineTo(xold+d/2.0, y);
pf.lineTo(x, y);
pf<<QPointF(xold+d/2.0, yold);
pf<<QPointF(xold+d/2.0, y);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(xold+d/2.0, yold);
pl.lineTo(xold+d/2.0, y);
pl.lineTo(x, y);
pl<<QPointF(xold+d/2.0, yold);
pl<<QPointF(xold+d/2.0, y);
pl<<QPointF(x, y);
}
} else if (m_specialLineType==JKQTPStepLeft) {
// x/y
@ -185,11 +192,11 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
// |
// *
// xold/yold
pf.lineTo(xold, y);
pf.lineTo(x, y);
pf<<QPointF(xold, y);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(xold, y);
pl.lineTo(x, y);
pl<<QPointF(xold, y);
pl<<QPointF(x, y);
}
} else if (m_specialLineType==JKQTPStepRight) {
// x/y
@ -197,11 +204,11 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
// |
// *----------------|
// xold/yold
pf.lineTo(x, yold);
pf.lineTo(x, y);
pf<<QPointF(x, yold);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(x, yold);
pl.lineTo(x, y);
pl<<QPointF(x, yold);
pl<<QPointF(x, y);
}
} else if (m_specialLineType==JKQTPStepAverage) {
// x/y
@ -213,13 +220,13 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
// xold/yold
//const double d=(x-xold);
const double h=(y-yold);
pf.lineTo(xold, yold+h/2.0);
pf.lineTo(x, yold+h/2.0);
pf.lineTo(x,y);
pf<<QPointF(xold, yold+h/2.0);
pf<<QPointF(x, yold+h/2.0);
pf<<QPointF(x,y);
if (getDrawLine()) {
pl.lineTo(xold, yold+h/2.0);
pl.lineTo(x, yold+h/2.0);
pl.lineTo(x,y);
pl<<QPointF(xold, yold+h/2.0);
pl<<QPointF(x, yold+h/2.0);
pl<<QPointF(x,y);
}
} else if (m_specialLineType==JKQTPDirectLine) {
// x/y
@ -227,54 +234,64 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
// /----/
// *----/
// xold/yold
pf.lineTo(x, y);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(x, y);
pl<<QPointF(x, y);
}
}
//std::cout<<"line ("<<xl1<<", "<<yl1<<") -- ("<<xl2<<", "<<yl2<<")"<<std::endl;
} else {
if (getDrawLine()) pl.moveTo(x,y);
pf.moveTo(x, y0);
pf.lineTo(x, y);
if (getDrawLine()) pl<<QPointF(x,y);
pf<<QPointF(x, y0);
pf<<QPointF(x, y);
//xstart=x;
//ystart=y0;
}
xold=x;
yold=y;
subsequentItem=true;
firstPoint=false;
}
}
}
if (getFillCurve()) {
pf.lineTo(xold, y0);
pf.closeSubpath();
pf<<QPointF(xold, y0);
}
painter.save();
auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
if (getFillCurve()) {
painter.fillPath(pf, b);
painter.setBrush(b);
painter.setPen(Qt::NoPen);
painter.drawPolygon(pf.intersected(cliprect));
}
QList<QList<QPointF>> pl_fordrawing;
if (isHighlighted() || getDrawLine()) {
pl_fordrawing=JKQTPClipPolyLine(pl, cliprect);
}
if (isHighlighted()) {
painter.setBrush(QBrush(Qt::transparent));
painter.setBrush(Qt::NoBrush);
painter.setPen(ph);
painter.drawPath(pl);
for (const auto &lines : pl_fordrawing) {
painter.drawPolyline(lines);
}
}
if (getDrawLine()) {
painter.setBrush(QBrush(Qt::transparent));
painter.setBrush(Qt::NoBrush);
painter.setPen(p);
painter.drawPath(pl);
for (const auto &lines : pl_fordrawing) {
painter.drawPolyline(lines);
}
}
if (m_drawSymbols) {
painter.save();
auto __finalpaintsym=JKQTPFinally([&painter]() {painter.restore();});
for (auto& ppoint: ps) {
plotStyledSymbol(parent, painter, ppoint.x(), ppoint.y());
if (cliprect.contains(ppoint)) plotStyledSymbol(parent, painter, ppoint.x(), ppoint.y());
}
}
}
@ -317,6 +334,14 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
QPen ph=getHighlightingLinePen(painter, parent);
QPen np(Qt::NoPen);
QBrush b=getFillBrush(painter, parent);
const double xmin=transformX(parent->getXAxis()->getMin());
const double xmax=transformX(parent->getXAxis()->getMax());
const double ymin=transformY(parent->getYAxis()->getMin());
const double ymax=transformY(parent->getYAxis()->getMax());
const auto symType=getSymbolType();
const double symbolSize=parent->pt2px(painter, getSymbolSize());
const QMarginsF clipMargins=(symType==JKQTPNoSymbol)?QMarginsF(0,0,0,0):QMarginsF(symbolSize,symbolSize,symbolSize,symbolSize);
const QRectF cliprect=QRectF(qMin(xmin,xmax),qMin(ymin,ymax),fabs(xmax-xmin),fabs(ymax-ymin))+clipMargins;
int imax=0;
int imin=0;
@ -324,8 +349,8 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
if (getIndexRange(imin, imax)) {
QPainterPath pl, pf;
QVector<QPointF> ps;
QPolygonF pl, pf;
QList<QPointF> ps;
double xold=-1;
double yold=-1;
@ -354,27 +379,27 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
if (m_specialLineType==JKQTPStepCenter) {
double d=(y-yold);
pf.lineTo(xold, yold+d/2.0);
pf.lineTo(x, yold+d/2.0);
pf.lineTo(x, y);
pf<<QPointF(xold, yold+d/2.0);
pf<<QPointF(x, yold+d/2.0);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(xold, yold+d/2.0);
pl.lineTo(x, yold+d/2.0);
pl.lineTo(x, y);
pl<<QPointF(xold, yold+d/2.0);
pl<<QPointF(x, yold+d/2.0);
pl<<QPointF(x, y);
}
} else if (m_specialLineType==JKQTPStepLeft) {
pf.lineTo(x, yold);
pf.lineTo(x, y);
pf<<QPointF(x, yold);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(x, yold);
pl.lineTo(x, y);
pl<<QPointF(x, yold);
pl<<QPointF(x, y);
}
} else if (m_specialLineType==JKQTPStepRight) {
pf.lineTo(xold, y);
pf.lineTo(x, y);
pf<<QPointF(xold, y);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(xold, y);
pl.lineTo(x, y);
pl<<QPointF(xold, y);
pl<<QPointF(x, y);
}
} else if (m_specialLineType==JKQTPStepAverage) {
// x/y
@ -386,13 +411,13 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
// xold/yold
const double d=(x-xold);
//const double h=(y-yold);
pf.lineTo(xold+d/2.0, yold);
pf.lineTo(xold+d/2.0, y);
pf.lineTo(x,y);
pf<<QPointF(xold+d/2.0, yold);
pf<<QPointF(xold+d/2.0, y);
pf<<QPointF(x,y);
if (getDrawLine()) {
pl.lineTo(xold+d/2.0, yold);
pl.lineTo(xold+d/2.0, y);
pl.lineTo(x,y);
pl<<QPointF(xold+d/2.0, yold);
pl<<QPointF(xold+d/2.0, y);
pl<<QPointF(x,y);
}
} else if (m_specialLineType==JKQTPDirectLine) {
// x/y
@ -400,17 +425,17 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
// /----/
// *----/
// xold/yold
pf.lineTo(x, y);
pf<<QPointF(x, y);
if (getDrawLine()) {
pl.lineTo(x, y);
pl<<QPointF(x, y);
}
}
//std::cout<<"line ("<<xl1<<", "<<yl1<<") -- ("<<xl2<<", "<<yl2<<")"<<std::endl;
} else {
if (getDrawLine()) pl.moveTo(x,y);
pf.moveTo(x0, y);
pf.lineTo(x, y);
if (getDrawLine()) pl<<QPointF(x,y);
pf<<QPointF(x0, y);
pf<<QPointF(x, y);
}
xold=x;
yold=y;
@ -418,31 +443,41 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
}
}
}
pf.lineTo(x0, yold);
pf.closeSubpath();
pf<<QPointF(x0, yold);
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
if (getFillCurve()) {
painter.fillPath(pf, b);
painter.setBrush(b);
painter.setPen(Qt::NoPen);
painter.drawPolygon(pf.intersected(cliprect));
}
QList<QList<QPointF>> pl_fordrawing;
if (isHighlighted() || getDrawLine()) {
pl_fordrawing=JKQTPClipPolyLine(pl, cliprect);
}
if (isHighlighted()) {
painter.setBrush(QBrush(Qt::transparent));
painter.setBrush(Qt::NoBrush);
painter.setPen(ph);
painter.drawPath(pl);
for (const auto &lines : pl_fordrawing) {
painter.drawPolyline(lines);
}
}
if (getDrawLine()) {
painter.setBrush(QBrush(Qt::transparent));
painter.setBrush(Qt::NoBrush);
painter.setPen(p);
painter.drawPath(pl);
for (const auto &lines : pl_fordrawing) {
painter.drawPolyline(lines);
}
}
if (m_drawSymbols) {
painter.save();
auto __finalpaintsym=JKQTPFinally([&painter]() {painter.restore();});
for (auto& point: ps) {
plotStyledSymbol(parent, painter, point.x(), point.y());
for (auto& ppoint: ps) {
if (cliprect.contains(ppoint)) plotStyledSymbol(parent, painter, ppoint.x(), ppoint.y());
}
}
}

View File

@ -272,6 +272,13 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
if (!visX&&!visY) return;
//std::cout<<" JKQTPGraphErrors::intPlotXYErrorIndicators(p, "<<parent<<", "<<xColumn<<", "<<yColumn<<", "<<xErrorColumn<<", "<<yErrorColumn<<", ...)\n";
const double xmin=parentGraph->transformX(parent->getXAxis()->getMin());
const double xmax=parentGraph->transformX(parent->getXAxis()->getMax());
const double ymin=parentGraph->transformY(parent->getYAxis()->getMin());
const double ymax=parentGraph->transformY(parent->getYAxis()->getMax());
const QMarginsF clipMargins(50,50,50,50);
const QRectF cliprect=QRectF(qMin(xmin,xmax),qMin(ymin,ymax),fabs(xmax-xmin),fabs(ymax-ymin))+clipMargins;
QBrush b=getErrorFillBrush(painter, parent);
QPen p=getErrorLinePen(painter, parent);
QPen pr=getErrorLinePenForRects(painter, parent);
@ -394,28 +401,32 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
QPen pp=p;
if (!defaultErrorColor) pp.setColor(terrCol);
painter.setPen(pp);
QList<QLineF> elines;
if (x0ok&&x1ok&&xok&&yok) {
painter.drawLine(QLineF(x0, y, x1, y));
elines<<QLineF(x0, y, x1, y);
if (xErrorStyle==JKQTPErrorBars || xErrorStyle==JKQTPErrorBarsLines|| xErrorStyle==JKQTPErrorBarsPolygons) {
if (plotlowerbarx) painter.drawLine(QLineF(x0,y-ebs_px/2.0,x0,y+ebs_px/2.0));
if (plotupperbarx) painter.drawLine(QLineF(x1,y-ebs_px/2.0,x1,y+ebs_px/2.0));
if (plotlowerbarx) elines<<QLineF(x0,y-ebs_px/2.0,x0,y+ebs_px/2.0);
if (plotupperbarx) elines<<QLineF(x1,y-ebs_px/2.0,x1,y+ebs_px/2.0);
}
} else if (x0ok&&!x1ok&&xok&&yok) {
painter.drawLine(QLineF(x0, y, x, y));
elines<<QLineF(x0, y, x, y);
if (xErrorStyle==JKQTPErrorBars || xErrorStyle==JKQTPErrorBarsLines|| xErrorStyle==JKQTPErrorBarsPolygons) {
if (plotlowerbarx) painter.drawLine(QLineF(x0,y-ebs_px/2.0,x0,y+ebs_px/2.0));
if (plotlowerbarx) elines<<QLineF(x0,y-ebs_px/2.0,x0,y+ebs_px/2.0);
}
if (x0<x) painter.drawLine(QLineF(x,y,parentGraph->transformX(parent->getXMax()),y));
else painter.drawLine(QLineF(x,y,parentGraph->transformX(parent->getXMin()),y));
if (x0<x) elines<<QLineF(x,y,parentGraph->transformX(parent->getXMax()),y);
else elines<<QLineF(x,y,parentGraph->transformX(parent->getXMin()),y);
} else if (!x0ok&&x1ok&&xok&&yok) {
painter.drawLine(QLineF(x1, y, x, y));
elines<<QLineF(x1, y, x, y);
if (xErrorStyle==JKQTPErrorBars || xErrorStyle==JKQTPErrorBarsLines|| xErrorStyle==JKQTPErrorBarsPolygons) {
if (plotupperbarx) painter.drawLine(QLineF(x1,y-ebs_px/2.0,x1,y+ebs_px/2.0));
if (plotupperbarx) elines<<QLineF(x1,y-ebs_px/2.0,x1,y+ebs_px/2.0);
}
if (x1<x) painter.drawLine(QLineF(x,y,parentGraph->transformX(parent->getXMin()),y));
else painter.drawLine(QLineF(x,y,parentGraph->transformX(parent->getXMax()),y));
if (x1<x) elines<<QLineF(x,y,parentGraph->transformX(parent->getXMin()),y);
else elines<<QLineF(x,y,parentGraph->transformX(parent->getXMax()),y);
}
for (QLineF& l: elines) {
l=JKQTPClipLine(l, cliprect);
if (l.length()>0) painter.drawLine(l);
}
}
// y-errorbars
if ((yErrorColumn>=0 || yErrorColumnLower>=0) && (yErrorStyle==JKQTPErrorBars || yErrorStyle==JKQTPErrorBarsLines || yErrorStyle==JKQTPErrorBarsPolygons
@ -426,29 +437,32 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
QPen pp=p;
if (!defaultErrorColor) pp.setColor(terrCol);
painter.setPen(pp);
QList<QLineF> elines;
if (y0ok&&y1ok&&xok&&yok) {
painter.drawLine(QLineF(x, y0, x, y1));
elines<<QLineF(x, y0, x, y1);
if (yErrorStyle==JKQTPErrorBars || yErrorStyle==JKQTPErrorBarsLines || yErrorStyle==JKQTPErrorBarsPolygons) {
if (plotlowerbary) painter.drawLine(QLineF(x-ebs_px/2.0,y0,x+ebs_px/2.0,y0));
if (plotupperbary) painter.drawLine(QLineF(x-ebs_px/2.0,y1,x+ebs_px/2.0,y1));
if (plotlowerbary) elines<<QLineF(x-ebs_px/2.0,y0,x+ebs_px/2.0,y0);
if (plotupperbary) elines<<QLineF(x-ebs_px/2.0,y1,x+ebs_px/2.0,y1);
}
} else if (y0ok&&!y1ok&&xok&&yok) { // upper errorbar OK, lower errorbar NAN
painter.drawLine(QLineF(x, y0, x, y));
elines<<QLineF(x, y0, x, y);
if (yErrorStyle==JKQTPErrorBars || yErrorStyle==JKQTPErrorBarsLines || yErrorStyle==JKQTPErrorBarsPolygons) {
if (plotlowerbary) painter.drawLine(QLineF(x-ebs_px/2.0,y0,x+ebs_px/2.0,y0));
if (plotlowerbary) elines<<QLineF(x-ebs_px/2.0,y0,x+ebs_px/2.0,y0);
}
if (y0<y) painter.drawLine(QLineF(x,y,x,parentGraph->transformY(parent->getYMin())));
else painter.drawLine(QLineF(x,y,x,parentGraph->transformY(parent->getYMax()))); // inverted axis!
if (y0<y) elines<<QLineF(x,y,x,parentGraph->transformY(parent->getYMin()));
else elines<<QLineF(x,y,x,parentGraph->transformY(parent->getYMax())); // inverted axis!
} else if (!y0ok&&y1ok&&xok&&yok) {
painter.drawLine(QLineF(x, y1, x, y));
elines<<QLineF(x, y1, x, y);
if (yErrorStyle==JKQTPErrorBars || yErrorStyle==JKQTPErrorBarsLines || yErrorStyle==JKQTPErrorBarsPolygons) {
if (plotupperbary) painter.drawLine(QLineF(x-ebs_px/2.0,y1,x+ebs_px/2.0,y1));
if (plotupperbary) elines<<QLineF(x-ebs_px/2.0,y1,x+ebs_px/2.0,y1);
}
if (y1<y) painter.drawLine(QLineF(x,y,x,parentGraph->transformY(parent->getYMax())));
else painter.drawLine(QLineF(x,y,x,parentGraph->transformY(parent->getYMin())));
if (y1<y) elines<<QLineF(x,y,x,parentGraph->transformY(parent->getYMax()));
else elines<<QLineF(x,y,x,parentGraph->transformY(parent->getYMin()));
}
for (QLineF& l: elines) {
l=JKQTPClipLine(l, cliprect);
if (l.length()>0) painter.drawLine(l);
}
}
// error boxes
@ -465,8 +479,8 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
if (!defaultErrorColor) bb.setColor(terrFillCol);
painter.setBrush(bb);
QRectF errRect=QRectF(QPointF(x0,y0), QPointF(x1,y1));
if ((y0ok&&y1ok)||(x0ok&&x1ok)) {
const QRectF errRect=QRectF(QPointF(x0,y0), QPointF(x1,y1));
if (((y0ok&&y1ok)||(x0ok&&x1ok))&&cliprect.intersects(errRect)) {
if (yErrorStyle==JKQTPErrorEllipses || xErrorStyle==JKQTPErrorEllipses) painter.drawEllipse(errRect);
else painter.drawRect(errRect);
}
@ -487,10 +501,12 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
if (!defaultErrorColor) pp.setColor(terrCol);
painter.setPen(pp);
if (JKQTPIsOKFloat(xl1m)&&JKQTPIsOKFloat(yl1)&&JKQTPIsOKFloat(xl2m)&&JKQTPIsOKFloat(yl2)) {
painter.drawLine(QLineF(xl1m, yl1, xl2m, yl2));
const QLineF l=JKQTPClipLine(QLineF(xl1m, yl1, xl2m, yl2),cliprect);
if (l.length()>0) painter.drawLine(l);
}
if (JKQTPIsOKFloat(xl1p)&&JKQTPIsOKFloat(yl1)&&JKQTPIsOKFloat(xl2p)&&JKQTPIsOKFloat(yl2)) {
painter.drawLine(QLineF(xl1p, yl1, xl2p, yl2));
const QLineF l=JKQTPClipLine(QLineF(xl1p, yl1, xl2p, yl2),cliprect);
if (l.length()>0) painter.drawLine(l);
}
}
@ -509,10 +525,12 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
if (!defaultErrorColor) pp.setColor(terrCol);
painter.setPen(pp);
if (JKQTPIsOKFloat(xl1)&&JKQTPIsOKFloat(yl1m)&&JKQTPIsOKFloat(xl2)&&JKQTPIsOKFloat(yl2m)) {
painter.drawLine(QLineF(xl1, yl1m, xl2, yl2m));
const QLineF l=JKQTPClipLine(QLineF(xl1, yl1m, xl2, yl2m),cliprect);
if (l.length()>0) painter.drawLine(l);
}
if (JKQTPIsOKFloat(xl1)&&JKQTPIsOKFloat(yl1p)&&JKQTPIsOKFloat(xl2)&&JKQTPIsOKFloat(yl2p)) {
painter.drawLine(QLineF(xl1, yl1p, xl2, yl2p));
const QLineF l=JKQTPClipLine(QLineF(xl1, yl1p, xl2, yl2p),cliprect);
if (l.length()>0) painter.drawLine(l);
}
}
@ -541,7 +559,7 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
for (int i=polyXBottomPoints.size()-1; i>=0; i--) {
poly<<polyXBottomPoints[i];
}
painter.drawConvexPolygon(poly);
painter.drawConvexPolygon(poly.intersected(cliprect));
}
if ((polyYTopPoints.size()>0 || polyYBottomPoints.size()>0) && (yErrorStyle==JKQTPErrorPolygons || yErrorStyle==JKQTPErrorBarsPolygons || yErrorStyle==JKQTPErrorSimpleBarsPolygons)) {
@ -556,7 +574,8 @@ void JKQTPGraphErrorStyleMixin::intPlotXYErrorIndicators(JKQTPEnhancedPainter& p
for (int i=polyYBottomPoints.size()-1; i>=0; i--) {
poly<<polyYBottomPoints[i];
}
painter.drawConvexPolygon(poly);
painter.drawConvexPolygon(poly.intersected(cliprect));
}
//std::cout<<"end\n";

View File

@ -155,6 +155,38 @@ QPen JKQTPGraphLineStyleMixin::getLinePenForRects(JKQTPEnhancedPainter &painter,
JKQTPGraphLinesCompressionMixin::JKQTPGraphLinesCompressionMixin():
m_useNonvisibleLineCompression(true),
m_nonvisibleLineCompressionAgressiveness(1)
{
}
JKQTPGraphLinesCompressionMixin::~JKQTPGraphLinesCompressionMixin()
{
}
void JKQTPGraphLinesCompressionMixin::setUseNonvisibleLineCompression(bool _useNonvisibleLineCompression)
{
m_useNonvisibleLineCompression=_useNonvisibleLineCompression;
}
bool JKQTPGraphLinesCompressionMixin::getUseNonvisibleLineCompression() const
{
return m_useNonvisibleLineCompression;
}
void JKQTPGraphLinesCompressionMixin::setNonvisibleLineCompressionAgressiveness(double Agressiveness)
{
m_nonvisibleLineCompressionAgressiveness=Agressiveness;
}
double JKQTPGraphLinesCompressionMixin::getNonvisibleLineCompressionAgressiveness() const
{
return m_nonvisibleLineCompressionAgressiveness;
}

View File

@ -47,7 +47,9 @@ class JKQTPlotter; // forward
.
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPGraphLineStyleMixin {
#ifndef JKQTPLOTTER_WORKAROUND_QGADGET_BUG
Q_GADGET
#endif
public:
/** \brief class constructor */
JKQTPGraphLineStyleMixin();
@ -145,6 +147,76 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPGraphLineStyleMixin {
/*! \brief This Mix-In class provides setter/getter methods, storage and other facilities for a line-graph compression algorithm
\ingroup jkqtplotter_basegraphs_stylemixins
supported properties:
- activate compression (improves plotting speed, but decreases detail level)
- level of retained details
.
\image html JKQTPSimplifyPolyLines_agressive.png
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPGraphLinesCompressionMixin {
#ifndef JKQTPLOTTER_WORKAROUND_QGADGET_BUG
Q_GADGET
#endif
public:
/** \brief class constructor */
JKQTPGraphLinesCompressionMixin();
virtual ~JKQTPGraphLinesCompressionMixin();
/** \copydoc useNonvisibleLineCompression */
void setUseNonvisibleLineCompression(bool _useNonvisibleLineCompression);
/** \copydoc useNonvisibleLineCompression */
bool getUseNonvisibleLineCompression() const;
/** \copydoc useNonvisibleLineCompression */
void setNonvisibleLineCompressionAgressiveness(double Agressiveness);
/** \copydoc useNonvisibleLineCompression */
double getNonvisibleLineCompressionAgressiveness() const;
#ifndef JKQTPLOTTER_WORKAROUND_QGADGET_BUG
Q_PROPERTY(bool useNonvisibleLineCompression MEMBER useNonvisibleLineCompression READ getUseNonvisibleLineCompression WRITE setUseNonvisibleLineCompression)
Q_PROPERTY(double nonvisibleLineCompressionAgressiveness MEMBER nonvisibleLineCompressionAgressiveness READ getNonvisibleLineCompressionAgressiveness WRITE setNonvisibleLineCompressionAgressiveness)
#endif
private:
/** \brief use an optimization algorithm that tries to reduce the number of lines that
* overlap each other (i.e. for noisy data or a low zoom) and thus improves
* drawing speed
*
* When the property useNonvisibleLineCompression is activated (\c true ), the graph class
* uses the algorithm implemented in JKQTPSimplifyPolyLines() to simplify the task of plotting.
*
* \image html JKQTPSimplifyPolyLines.png
*
* \note This option is designed to not alter the plot representation significantly,
* but of course it may ...
*
* \see JKQTPSimplifyPolyLines() setUseNonvisibleLineCompression(), getUseNonvisibleLineCompression()
*/
bool m_useNonvisibleLineCompression;
/** \brief this sets the agressiveness of the option useNonvisibleLineCompression
*
* Basically the compressed groups will have a size of nonvisibleLineCompressionAgressiveness*pen.linewidth
*
* The default setting is \c 1.0 , larger settings will lead to better compression (and faster plotting), but less detailed
* plots, whereas smaller settings will increase the detail-level, but also increase plotting time.
*
* \image html JKQTPSimplifyPolyLines_agressive.png
*
* \note This option is designed to not alter the plot representation significantly,
* but of course it may ...
*
* \see JKQTPSimplifyPolyLines() setUseNonvisibleLineCompression(), getUseNonvisibleLineCompression()
*/
double m_nonvisibleLineCompressionAgressiveness;
protected:
};