Draw huge amounts of data in NSView or something else?

I have hundreds of thousands of time series data points that I want to present to the user. My current solution is to display the data in PNG using a third-party library, then load that PNG into NSImage and display it as a scroll. This works great, except that:

  • NSImages over 32K pixels wide are not displayed properly
  • I want to quickly and easily scale data
  • Reading and writing from disk is stupid

My current attempt is to directly draw NSBezierPath before NSView . The view looks beautiful, but very, very slow, even if I draw only a limited subset of the points at a time. And every time I scroll, I have to draw again, which is also slow.

I am sure, as a relative Cocoa newbie, that I am missing some of the best ways to do this. What is the β€œright” way to do this?

+4
source share
6 answers

My current attempt is to directly draw NSBezierPaths in NSView. The view looks beautiful, but very, very slow, even if I draw only a limited subset of the points at a time. And every time I scroll, I have to redraw, which is also slow.

Before making any radical decisions, try these simpler steps:

  1. Clip. Use NSRectClip , passing the rectangle that you take as an argument to drawRect: This is a single line.
  2. Perform a simple rectangle test before filling in the paths. For each path, get its borders and use NSIntersectsRect to check if it is inside the rectangle.
  3. Do a little less simple testing of the rectangle before you outline. Similar to the previous step, except that you need to increase the rectangle (the one that you received as an argument) by the line width, since half the stroke will go beyond the path. For each path, get the width of the line and negate it ( delta = -[path lineWidth] - and yes, you must include this minus sign), and then pass the result as both arguments to NSInsetRect . Make sure you keep the original rectangle around, as different paths may have different line widths.

The idea is to simply draw less. Setting the clipping path (using NSRectClip ) will reduce the amount of glare resulting from drawing operations. Eliminating paths that completely extend beyond the drawing rectangle will save you probably a more expensive limitation for these paths.

And, of course, you must create a profile at every step to make sure that you have not done things slower. You might want to set some kind of frame rate counter.

One more thing: getting boundaries for each path can be expensive. (Again, a profile.) If so, you can cache the borders for each path, perhaps by using a parallel NSArray from NSValue or by wrapping the path and borders together in an object of your own device. Then you calculate the borders only once and just retrieve them at future runs of the drawings.

+6
source
+2
source

If you draw many bezier paths, OpenGL is probably the way to go. Apple documentation would be a good place to start. They have sample code for implementing the basic OpenGL rendering context in a cocoa window.

This will shift the burden of complex drawing tasks to the GPU and leave your application free to scroll as desired.

This page contains sample OpenGL code for drawing bezier curves.

+2
source

I would look at CATiledLayer (Leopard only). You can draw a huge amount of material on the tiled layer and, if necessary, display areas. For your case, you can set the CATiledLayer as the basis for your NSView, draw all Bezier paths in this layer, and then scroll and even zoom in and out. Core Animation levels are treated like OpenGL textures, so you should get very good performance. A vector drawing is cached in a layer and does not redraw when scrolling, as you find in standard NSView.

For example, Bill Dudney has posted some sample code on how to use CATiledLayer to display a massive PDF file.

+2
source

How is a dynamic data set?

If it is static enough and you want to "increase", then you should consider combining data into a scaled subset.

A permeable example: if you have 100,000 points (say moving along the X axis), then obviously in a 1000-pixel image you will inevitably plan 1000 points out of 100,000.

So, you can do it in advanced (1000 points, 10000 points, raw data of 100,000 points) and choose from the appropriate set based on the zoom level that you show.

As for which value to choose when the points overlap, you can do min, max, median, average, etc.

+2
source

Consider using a framework that efficiently processes this material, such as CorePlot

0
source

All Articles