The docs have a section on NSBezierPath performance.
If you're drawing that many bezier paths, OpenGL is probably the way to go. Apple's documentation would be a good place to start. They have sample code for implementing a basic OpenGL rendering context in a cocoa window.
This would move the burden of the difficult drawing tasks onto the graphics processor, and leave your app free to scroll at will.
This page has sample OpenGL code for drawing bezier curves.
My current attempt is to directly draw NSBezierPaths to a NSView. The view renders beautifully, but very, very slowly, even if I only draw a limited subset of points at a time. And every time I scroll I have to re-draw which is also slow.
Before you undertake any drastic solutions, try these simpler steps:
- Clip. Use
NSRectClip
, passing the rectangle that you take as the argument todrawRect:
. This is a one-liner. - Do simple rectangle testing before filling paths. For each path, get its bounds and use
NSIntersectsRect
to test whether it's inside the rectangle. - Do slightly-less-simple rectangle testing before stroking paths. Similar to the previous step, except that you need to grow the rectangle (the one you received as your argument) by the line width, since half of the stroke will fall outside the path.
For each path, get the linewidth and negate it (
delta = -[path lineWidth]
— and yes, you must include that minus sign), then pass the result as both arguments toNSInsetRect
. Make sure you keep the original rectangle around, since different paths may have different linewidths.
The idea is, quite simply, to draw less. Setting the clipping path (using NSRectClip
) will reduce the amount of blitting that results from drawing operations. Excluding paths that fall completely outside the draw rectangle will save you the probably-more-expensive clipping for those paths.
And, of course, you should profile at each step to make sure you haven't made things slower. You may want to set up some sort of frame-rate counter.
One more thing: Getting the bounds for each path may be expensive. (Again, profile.) If so, you may want to cache the bounds for each path, perhaps using a parallel NSArray
of NSValue
s or by wrapping the path and bounds together in an object of your own devising. Then you only compute the bounds once and merely retrieve it on future drawing runs.
I'd look at CATiledLayer (Leopard-only) for this. You can draw a massive amount of stuff in the tiled layer and have it display areas as needed. For your case, you could set the CATiledLayer as the backing for your NSView, draw all your Bezier paths into that layer, and then scroll around and even zoom in and out on it. Core Animation layers are handled like OpenGL textures, so you should get very good performance from this. The vector drawing is cached in the layer and doesn't redraw as you scroll around, like you find on a standard NSView.
For an example, Bill Dudney has posted some sample code on how to use CATiledLayer to display a massive PDF file.
How dynamic is the data set?
If it's reasonably static, and you want to "zoom", then you should consider consolidating the data in to a scaled subset.
Contrived example: if you have 100,000 pts (say advancing on the X axis), then, clearly, in a 1000 pixel image, you're only going to be, inevitably, plotting 1000 pts of that 100,000.
So, you can do that in advanced (1000 pts, 10000 pts, raw data of 100000pts) and select from the appropriate set based on the "zoom" level that you're displaying.
As for what value to select when the points overlap, you can do min, max, median, average, etc.