Blog

Back to Blog

Customizing that Crosshair

Posted on 19 Jul 2012 Written by Simon Withington

It’s been a while since we published this post, and both iOS and ShinobiCharts have moved on… but the good news is that we’ve got an updated version of the project on GitHub where you can browse, clone, or fork the project, or simply download the zip.

Here at Team Shinobi we love Formula One so I’ve prepped an F1-inspired app for us to play with today – go ahead and download the ShinobiF1 project. Fire up Xcode and drop in your ShinobiCharts framework (and licenseKey if required!). No framework? Get a trial here.

The ShinobiF1 app is a concept app, showing how the tyre-temperatures of two high-performance and slightly fictitious Shinobi race cars vary over an 18-second section of a race, using two different tyre compounds: intermediates and slicks.

What’s in a UIView?

By default, your chart’s crosshair tooltip (if enabled) is a modest little thing – showing a datapoint’s values as strings in a few UILabels. But not in this app – and not necessarily in yours either!

Go ahead and run the app, and tap-and-hold on the chart to bring up the crosshair. You’ll notice the tooltip is a bit bigger than it is normally and a bit more colourful too. It also contains another ShinobiChart?! (Inspired partly by one of our spotlight apps here).

 Crosshair1

Our tooltip here contains a ShinobiChart showing the temperatures of each tyre at the ‘targeted’ data point, rather than the average across the car as shown by the line on the chart. This is an example of how you can use your tooltip as a detail view for a master chart. By default, we just give you the data point information in the tooltip – however, the tooltip is a subclass of UIView and we can put whatever we want in there. That can be Charts, Grids, UIButtons, Tetris – anything you like. In order to do this, we need to subclass the tooltip: the SChartCrosshairTooltip class. So how do we do this?

A subclass of our tooltip

Let’s take a look inside the SChartCrosshairTooltip.h file (check out the Frameworks group in the Xcode project tree and find the Headers subfolder).

We can see that the standard crosshair has a UILabel, a style object with setter method, and two other methods:

  • The first method, setDataPoint:fromSeries:fromChart:, is called to provide the tooltip with datapoint, series, and chart information when the the crosshair first appears and/or moves. Using this information we can update the contents of the tooltip to display relevant information.
  • The second method, setPosition:onCanvas:   is called to provide the tooltip with information regarding the crosshair’s new position and the tooltip’s parent view. With this information, we can update the frame of the tooltip to move relative to the crosshair, should we wish to do so.

 Depending on which functionality we want to alter, we can override either or both of these methods in a subclass of SChartCrosshairTooltip – enter our SF1CrosshairTooltip class. Here, we override both methods, and add a few of our own for convenience. Let’s take a look at what we’ve done here. 

Charts inside charts

Since we already have a chart datasource object set up for our main chart, we’ll just use this  for our tooltip chart too. We’ll pass this as the argument into the initWithDatasource: method when we create the tooltip. I’ve tucked the chart setup code into the imaginatively-named setUpChart: method, called from initWithDatasource:. This way, the lifecycle of the chart aligns with that of the tooltip itself. I won’t dwell on the chart setup or the datasource – if you want to recap how to do this, check out our Quick Start Guide.

The following method provides our custom implementation of setDataPoint:fromSeries:fromChart: and tells the datasource on our tooltip which series (or car) is being targeted by the crosshair. The small tooltip chart is then reloaded to display the relevant information for tyre temperature. Finally, we use the label we inherit from the superclass to give the tooltip a title.

- (void)setDataPoint:(id<SChartData>)dataPoint fromSeries:(SChartSeries *)series fromChart:(ShinobiChart *)chart {
    [datasource setTooltipSeries: series];
    [tooltipChart reloadData];
    label.text = [NSString stringWithFormat:@"Car %@ at %@", [series title], [chart.xAxis stringForId: [dataPoint sChartXValue]]];
}

 Now, if we only override setDataPoint:fromSeries:fromChart:, we’ll be hiding half the data on display in our chart with our now larger tooltip – not ideal! Fortunately, setPosition:onCanvas: offers us the solution we need – we can set the frame of the tooltip ourselves to make room for our new super-size tooltip.

Thinking outside of the chart…

The following implementation of the setPosition:onCanvas: method in the SChartCrosshairTooltip.m parent class keeps the position of the tooltip aligned with the crosshair – the position of which is given by the SChartPoint passed in. In our custom implementation we restrict this movement side-to-side along the x-axis, fixing the y-position. We also move the tooltip outside of the chart entirely. Note that, by default, the chart clips it’s contents to its frame – so we need to set clipsToBounds property of the main chart to NO. I’ve done this in the viewDidLoad  method of the SF1ViewController where we set up the main chart.

- (void)setPosition:(SChartPoint)pos onCanvas:(SChartCanvas*)canvas {
    
    [self layoutContents];
    
    // Position tooltip
    CGRect tempFrame = self.frame;
    tempFrame.origin.x = pos.x - tempFrame.size.width/2.f;
    
    // Keep within horizontal bounds
    if (tempFrame.origin.x + tempFrame.size.width > canvas.glView.frame.origin.x + canvas.glView.bounds.size.width)  {
        tempFrame.origin.x = canvas.glView.frame.origin.x + canvas.glView.bounds.size.width - tempFrame.size.width;
    } else if (tempFrame.origin.x < 0.f) {
        tempFrame.origin.x = 0.f;
    }
    
    // Pin underneath chart
    tempFrame.origin.y = datasource.mainChart.frame.origin.y + datasource.mainChart.frame.size.height;
    
    self.frame = tempFrame;
}

The code that is responsible for tweaking the UILayer, configuring the content UIViews, and sizing the tooltip has been separated off into the layoutContents  method.

And there we have it – F1 performance chart style!

Hopefully, we’ve demostrated just how flexible the charts can be – and how simple some of these customizations are to implement. Have a play around with the example and we look forwarding to hearing about some of your innovations – screenshots always welcome here at ShinobiHQ.

Back to Blog