Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing massive amounts of data in NSView, or something else?

I have hundreds of thousands of points of time series data that I want to represent to the user. My current solution is to render said data to a PNG with a 3rd party library and then load that PNG into a NSImage and display it in a scroll view. This works great, except that:

  1. NSImages more than 32k pixels wide don't display properly
  2. I want to be able to zoom into the data quickly and easily
  3. Reading to and writing from disk is stupid

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.

I'm certain, as a relative Cocoa newbie, that I'm missing some better ways to do this. What's the "right" way to do it?

like image 586
Dan Avatar asked Dec 09 '22 22:12

Dan


2 Answers

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:

  1. Clip. Use NSRectClip, passing the rectangle that you take as the argument to drawRect:. This is a one-liner.
  2. Do simple rectangle testing before filling paths. For each path, get its bounds and use NSIntersectsRect to test whether it's inside the rectangle.
  3. 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 to NSInsetRect. 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 NSValues 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.

like image 191
Peter Hosey Avatar answered Dec 28 '22 23:12

Peter Hosey


The docs have a section on NSBezierPath performance.

like image 20
John M Avatar answered Dec 28 '22 22:12

John M