Server Density Blog

Interesting devops tech stuff

Pssst… Server Density v2 is coming soon!

How to build stunning custom iOS graphs on iPhone and iPad

Written by Rob Elkin

Server monitoring graphs iPad

A couple of weeks ago, we released a big update to our iOS server monitoring app app, adding graph support on both iPhone + iPad. It’s an update that we’ve been working on for a while, as it brings the app in line with the web UI, whilst making our graphs even better. Here, I’ll explore the graphing library we chose and why; some of our design / UI considerations; and how we’ve implemented everything.

Graphing Libraries

We looked at a number of iOS graphing libraries:

If nothing else, this is a fantastic lesson in online marketing. Of those libraries, Core Plot and iOSPlot are open source, the other three libraries require a paid license. Shinobi Charts have developed the only website with more to it than a page of features or code examples, in our eyes it was instantly ahead. It extended that lead with promises of an easy setup and reasonable pricing compared to the other libraries available. Shinobi also has the best ongoing support (1 year of support included for the same base price as the others without); it is easily customisable; and has good documentation. Visit the last 3 sites for yourself and you’ll see why first impressions really do count for something!

The average cost for any of the 3 paid libraries is around $999, so why not just go with something open source and therefore free? It was certainly considered, but we ultimately decided that the price tag for Shinobi was worth it to receive the customisation we wanted, the documentation we needed, and the support that we (might have) required. Our business isn’t writing graphing libraries, and whilst it would have been great to contribute to open source projects like Core Plot and iOSPlot, it would have cost us more in time to setup and implement them, than simply paying for Shinobi.

Goals

Our main aim was to create an amazing way to access and interact with data visually on the iPad. We also wanted to add iPhone support as well, but the focus was to be on the iPad version. There were a couple of reasons for this:

To quote one of the internal discussions we had during planning:

iPhone

Accessing the graphs on the iPhone is more likely to be an emergency thing or a quick check of the recent status of a specific server/metric. This is because the screen is considerably smaller and hence, less useful. You’d be unlikely to want to go back over months.

  • Receive an alert and want to check what led up to it being triggered. This would involve checking the graph over a short time period e.g. the last hour or even the last 30 minutes.
  • Want to quickly check the state of a server over the last few hours or maybe last day or so.
  • Emphasis on speed of accessing the data for metrics vs customisability for range/metric/etc.

iPad

The iPad is much more useful for viewing historical data because of the larger screen. Quick access to common time periods would be useful, but a full time selection is also needed.

  • In a meeting discussing capacity planning so want to see stats over the last few weeks or months to look at the trending.
  • In a meeting discussing an outage so you want to see the stats leading up to a specific incident.
  • Want to compare metrics against each other (on a metric level e.g. comparing CPU usage and Apache req/s on the same graph from 1 server AND on a multiple server level e.g. comparing CPU on Server A vs Server B).

Design

We settled on a design quite early on, allowing us to make good progress. Daniele worked on the design for the current iteration of the app (since we updated it to a native version), and so naturally he designed this update.

One of the biggest features on the iOS version of the charts is the ability to view more than one chart at a time, an improvement we have planned for the web version of Server Density but haven’t finished yet. Now on iOS, you can drill down charts to just the metrics you want to see, and display them beside other metrics (e.g. compare your load average with your network traffic and disk IO over the same period of time). To enhance this ability to do comparisons, we added pan and zoom syncing to all the charts, so when you pan or zoom one chart, all the others follow suit, making the comparison of spikes incredibly powerful.

iPhone

As I mentioned, the iPhone version is for gaining quick access to information, and so we prioritised simplicity and a single track for this side of the app. A prime example of this is the way you can pick the dates to display graphs over. Unlike on the iPad, where you can choose any date and time, on the iPhone we only allow the choice of four static options, as shown below.

iPhone Time Selection

This allows users to quickly see what is happening without having to fiddle with small controls. We also provide some background feedback on the currently selected time range by modifying the time selector button.

Different states for the date range selector

iPad

On the iPad, we wanted to provide the best possible graphing experience. We included the ability to quickly switch between devices, kept the graph selector beside the graphs, and included a full featured date selector analogous to the date selector in the web version. We used the Tapku library for the calendar, and enhanced it to allow custom images to be used, as detailed in this pull request.

Selecting a date and time on the iPad

Comparing metrics on the iPad

Metrics Selection

We needed a way for users to be able to select not only the main metrics that they wanted to see (Network Traffic, Physical Memory) but also to turn on and off the sub metrics (eth0, Memory Used, etc). To do this, we created a nested tableview control, which we released on github a few months ago. We think this is really powerful for users to drill down to exactly what they want to see, and that is enhanced by the pan and zooming sync.

Developing with Shinobi Charts

Shinobi charts is everything you would expect from a modern framework, with good documentation, well structured relationships and classes, customisable interface elements, and the datasource and delegate callbacks you would expect. Its delegate and datasource structure should make it very easy to use for anyone remotely versed in using standard UIKit frameworks, e.g. UITableViews.

Data

All the data we use to populate our chart interfaces comes directly from our API, so if you need to do something similar for any platform you should be able to get everything you need in the same way the iOS app does.

Chart Class

This is the class we use for all the charts that we show in the app. It extends the ShinobiChart class, and allows us to add in our license key in the constructor, and set up a number of defaults that all the charts will use. This class deals with creating a new crosshair and legend on the chart, as well as setting the theme and modifying the axis’ to suit our needs. Here is a list of some useful things to note on the ShinobiChart class that we used to customise our charts:

Delegate methods

We use a controller for the interface you see when interacting with the charts, and each of the charts gets added to a UIScrollView that is part of that controller. When we create the chart, we set the delegate to be the controller that controls the view. This allows us to receive all of the important movement and change notifications, and take appropriate action. This controller is also the delegate for the scrollview, and for a pinch gesture recogniser we have added to the scrollview.

Breaking down some of the tricks we used:

Datasource methods

We had an existing app, which had a number of different data models for the different types of things we display (e.g. a Device model, Service model, Alert model). We decided that these models were best positioned to provide the data for the charts, so we implemented the SChartDatasource protocol and got to work. All the models extend off of a base called SDItem, so once we had added support to it, we got charting support for both Devices and Services. There are 4 basic delegate methods that should be implemented to get a chart up and running:

We implemented these methods, which then go back to a datasource singleton that is shared between all devices and services, to get the data for the chart. They are mostly self explanitory, however one thing to point out. seriesAtIndex: returns an SChartSeries, which you can customise by setting the style.lineColor and style.areaColor properties to get different colours of lines on your chart. We created a graph line colours object which would give us a concrete colour based on the sub-item key in the array, meaning that colours would always be the same for a given key, avoiding a colour change when a metric is added/removed.

Using a custom pinch gesture recogniser

ShinobiChart has a built in pinch recogniser, which will take care of all the zooming for you, and if you can get away with it, I would absolutely encourage you to use it. If however, you have the complex interactions we have, and/or want fine grained control, you can follow what we have done. It isn’t exactly the same as the Shinobi implementation, but it is very close and gave us the flexibility we wanted to zoom charts from anywhere in the scrollview.

We start off by adding the gesture recogniser to the scrollview:

UIPinchGestureRecognizer *pinchRecogniser = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchHappened:)];
[pinchRecogniser setDelegate:self];
[self.graphScrollView addGestureRecognizer:pinchRecogniser];

Then we take the callback we get from the pinch, and use the “scale” property:

- (void) pinchHappened: (UIPinchGestureRecognizer *)sender
{
    if(sender.state == UIGestureRecognizerStateChanged)
    {
        [self zoomAllChartsToLevel:sender.scale];
    }
}

Then, take each charts current zoom level and multiply it by the scale:

- (void)zoomAllChartsToLevel: (double)level
{
    Chart *tempChart = nil;
    for (NSString *aKey in charts)
    {
        tempChart = [charts objectForKey:aKey];
        if([tempChart isKindOfClass:[Chart class]])
        {
            [tempChart.xAxis setZoom:(tempChart.xAxis.zoom * level) fromPosition:nil withAnimation:YES];
        }
    }
}

Limitations of Shinobi

There isn’t much wrong with the Shinobi library, but here are a few things that irked me when I was working with it. These are things I’ve reported as bugs, and I believe are scheduled to be fixed.

tl;dr

If you want to do charting on iOS, and can afford the cost ($999), Shinobi Charts is the way to go.

Enjoy this post? You may also like Introducing Sockii: HTTP and WebSocket aggregator

  • http://www.facebook.com/JuanAGonzalezP Juan González

    This is incredibly helpful! Thanks for taking the time to document the experience and of course for sharing.

  • http://www.becomingapps.com Juan González

    Hi again Rob. There’s one thing that makes me particularly curious. How did you accomplish real-time charts using Shinobi? You mention at one point that you implemented sync and by your UI pics I’m assuming you also have the manual refresh but I don’t know if you did something more advanced to get the “real time”.

    I evaluated Shinobi for a reporting project a while ago but I discarded it because of the looks that it had at the time (compared to Fusion Charts) and the no-real time option (against the RT widgets). Now, after dealing with the HTML5 issues all over I’m thinking about going back to Shinobi.

    • http://www.serverdensity.com Rob Elkin

      The data coming from our API is in real time, so as soon as we receive it, it becomes available in the iOS charts. Once the data in the app is updated, the charts should be automatically updated. From memory, I believe we make a call to manually force a refresh when new data for the charts is received, which updates the UI.

      • http://www.becomingapps.com Juan González

        Interesting. When using FC Real Time Widgets I noticed my app demand on the iPad’s resources increasing, maybe 2x. Does the real time feature impacts in a negative way on your network traffic?

        I downloaded your app. David Mytton was so kind to set up a testing account on a public server but I couldn’t display the charts. Every time I select a metric (e.g. CPU) the app quits.

        I sent the reports and David told me they’ll look it in detail after the holidays.

        Anyway, great blog post, many interesting things that one can relate to and use.

        • https://blog.serverdensity.com David Mytton

          It would be a case of doing a network call behind the scenes. In most cases the data will have more points so it’ll draw those but we retrieve the full set every time. An optimisation could be to only call the API with a time period between now and when we last called it, so you get a differential and append that…but it’s probably a lot more work.

          As regards the crashing issue, this turned out to be a problem with the dev version of iOS 6.1. We’ll have this fixed before it’s released stable to users.