PathFlatteningIterator() - after read manual still not sure what it does and how to use it


#1

Hello,
I have vector with about 5000 float members. And I want to draw it with Path.
But it’s lot of data for one Path to stroke it on 600x400 window. So I was looking for some easy to use approximation method. And I found method PathFlatteningIterator(). But it doesn’t help me. My computer still lagging.
But I am not sure if I use that method in proper way.
My code looks like that (in simplification):

float vectorSize = myVector.size();
float winWidth = getWidth();

myPath.startNewSubPath(0.0f, somePointY);

for(int i = 0; i < vectorSize; ++i)
{
       myPath.lineTo((float)i * winWidth / vectorSize, myVector[i]);
}

myPathFlatteningIterator(myPath, AffineTransform(), Path::defaultToleranceForMeasurement);

Then in the paint(Graphics& g) method I just draw it with:
g.strokePath(myPath, PathStrokeType(2));

In the manual they are talking:

Use one of these to iterate through a Path object

But I have no idea how to iterate through a Path?


#2

The documentation for the class says:

Flattens a Path object into a series of straight-line sections

…but your path is already created using many calls to juce::Path::lineTo() so it’s already straight line sections. The PathFlatteningIterator is for converting paths that have bezier curves (such as loading a path from an SVG) to using many straight lines only.

If you need an approximation method you’ll have to do it before you start constructing your path. A basic example would be only taking every other point from your data, which would cut it down to 2500 points already. The approximation method really depends on what your data is, but either way the goal is to reduce the number of times you actually call juce::Path::lineTo().

The lag also might depend on how often you’re repainting. If the data doesn’t change much you can reduce how often you redraw the path (I’m assuming you’re repainting the path based on a timer?).


#3

Yes I painting with timer, my timer is set to startTimer(100). Ok I can make slower. But still have no idea how to approximate my graph/Path.
I think I can take each point and just do Path.lineTo(x1+(x2-x1)/2, y1+(y2-Y1)/2)
But it doesn’t work for me. My horizontal scale is logarythmic. So even for 5000 points, first 1/3 part of my window has very few points (very sparse) and they are very densely from about half of my window. And my vertical scale is for freq magnitude.


#4

Take a look at how @daniel draws his FFT in his Frequalizer:

he basically iterates over each pixel in his window, converts that pixel’s X coord to a frequency value, then passes that frequency value to the filter to get the magnitude, which becomes the Y coordinate. With the X and Y values, he can just p.lineTo(x, y); for every (or every other) x in his window.


#5

You need to lower your point count by using average values on the dense side of the graph. Path drawing has weak performance for spectrum-type drawing with lots of scanline intersections. What helps is splitting the path in multiple horizontal sections or drawing the spectrum 90 degrees rotated and then rotate the result image back. As you don’t need a filled path, but a stroked one… drawing lots of single lines using drawLine is much faster than a path of lines as it mostly avoids the costly scanline intersection calculation. That way maybe you could get away even with the 5000.


#6

Great tip, I will try it, thanks


#7

It may help to use Path::preallocateSpace() to save resizing your Path object every time you add a new line.

From the documentation:

“The actual value to pass is a bit tricky to calculate because the space required depends on what you’re adding - e.g. each lineTo() or startNewSubPath() will require 3 coords (x, y and a type marker).”

So for 5000 points, you’ll want to preallocate 15000:
myPath.preallocateSpace(15000);

https://docs.juce.com/master/classPath.html#aead99ae2d7abf0063362eac7a8e9e776