Print PDF

Managing Core Plot Graphs in iOS

This post gives an overview of how Core Plot graphs are managed in Niftybean's MyWorth personal net worth finance app for iPhone and iPod Touch.  Some familiarity with the Google Code core-plot project is assumed.  I cut my teeth on Core Plot with MyWorth and our use of the framework is not overly complex - the first version of this app displays either a 30 day graph or 12 month graph of the user's historical net worth.

Apart from the quality of the Core Plot libraries, other excellent factors for newcomers are the great documentation (especially the High Level Design Overview) and the high level of activity by Drew McCormack (the project's owner) on the coreplot-discuss group.  I would recommend anyone looking at adding graphs to Objective-C apps consider this project.


Core Plot Graphs

This is about as intuitive as things can get - the user chooses which graph to view using the segmented control.

Despite the simple appearance there's quite a bit of configuration, data manipulation and formatting to get these graphs rendering just right.


After trying a couple of approaches and realising this was more than just feeding an array of values to the graphing libraries, I came up with the following design which has worked out really well so far:


This is a cut down representation of classes and interrelationships to illustrate the main idea.  Classes coloured blue are from the MyWorth project.  Red classes are Core Plot and yellow are Foundation.

GraphsViewController

This is a straightforward UIViewController subclass which manages the navigation bar and controls.  It's main view is the required CPLayerHostingView.


In particular, it doesn't do anything as far as graph manipulation goes.  Since the graphs and their data have an independent lifecycle to that of this view controller this separation makes sense.

The only Core Plot class it's aware of is CPLayerHostingView.  It passes this to GraphManager's assignGraphType:toHostingView: method which results in the correct CPGraph being instantiated (if it hasn't already been) then assigned to hostedLayer property.

GraphManager

GraphManager manages both a CPGraph and a GraphDataHolder for the two 30 days and 12 months graphs.

Since it's a singleton it can cache graphs and data holders by retaining them (and releasing them when needed, say on low memory warnings or stale data).

The first time each graph is requested, the GraphDataHolder and CPGraph for that graph are created and retained.  If that graph is requested later the same objects are returned.

However data rendered on a graph may become stale.  When users are manipulating their balance sheet they can cause their net worth to change value.  All data is managed via Core Data - and I initially wrote the GraphManager to register for Core Data's NSManagedObjectContextDidSaveNotification notifications.  This was was pretty naive though - most of these notifications were for other core data save events and they caused the manager to waste cpu cycles by always iterating over updated objects looking for the ones it cared about.

I improved this by using a custom notification event only sent when the user's net worth changes.  This was much more efficient and detecting this condition only occurs in a couple of places in the codebase.

The GraphManager adopts two methods of the CPPlotDataSource protocol, numberOfRecordsForPlot:, and numberForPlot:field:recordIndex:.  These delegate down to similar methods on the active GraphDataHolder.

GraphDataHolder

GraphDataHolder is implemented as a class cluster which returns either GraphDataHolder30Days or GraphDataHolder12Months.

It might appear superfluous but by having these data holder implementations separated out there turned out out to be several benefits

  • Better encapsulation.  If this data & code weren't in the data holder it would have had to live in some other place, probably the manager (or worse, the view controller).
  • Data values and dates are both accessible by index.  (The custom number formatters make use of this to render date labels for index - they simply ask the data holder for the date at the index they are given.)
  • Concrete implementations know which formatter to provide for their x-axis values
  • Caching data is just a matter of retaining the data holder.
  • Adding more graphs into this pattern is really easy.
  • One unexpected benefit - it's really easy to render dummy data.  This came in handy both for screenshots, and also for testing how the graph renders with a different values (sparse data, no data, negative ranges etc.)

Formatters

The NSNumberFormatters shown are used to format the axis labels.  (The GraphManager assigns these to CPXYAxis labelFormatter property.)

GraphDataHolderDateFormatter requires both a date format string (used to create a private NSDateFormatter) and a GraphDataHolder.  Whenever it's stringForObjectValue: is invoked by the Core Plot framework, it retrieves the int value from the NSDecimalNumber passed as a parameter, fetches the Date to format from the data holder, and finally returns the formatted string.

  • NetWorthFormatter
    Formats the user's net worth in the y axis.  Ensures the monetary value is truncated and replaced with M or K for larger numbers.
  • GraphDataHolderDateFormatter
    The 30 day graph initializes this using pattern @"dd MMM".  The 12 month graph uses "MMM"

What I've written about here is a high level organization of how we pulled Core Plot together in MyWorth.  There are probably features in Core Plot's API which could have helped me even more, but I'm really happy that we were able to make some really effective graphs in a short amount of time using Core Plot.  The gains from organizing things well at a higher level really help keep a project manageable and keep productivity up and making change easy.  All these have turned out to be true for the way we used Core Plot with MyWorth.




 
Comments (1)
Thanks for the write-up
1 Monday, 25 October 2010 10:16
Victor Jalencas
Thanks Robert. This post gave me a couple of good ideas, as well as validated my approach to using Core Plot.

Add your comment

Your name:
Subject:
Comment:
  The word for verification. Lowercase letters only with no spaces.
Word verification:

View in the App Store



Niftybean Blog


Subscribe

Contact Us

Contact Us

Email us or reach us using details below

  • Address:
    PO Box 11463, Manners St Central, Wellington
  • Tel:
    (64)-4-478-2968


NZiPhone.com

iphonewzealand.co.nz

Search site

Login