PowerPlotPercolation logo

Mixed chart animation

This is a more powerful animation that involves a bar chart and a line chart. The user has a UISegmentedControl to select between two different scenarios that are represented graphically in the chart — a “bad case” and a “good case”.

This page describes the class “MixedAnimation” in the PowerPlot demo app.

First, we need to set up the chart. With the available factory methods, this is straightforward:

    // We have two data sets: project cost and net profits.
    self.current = 0;
    self.projectCost = [DemoData projectCostBad];
    self.totalProfit = [DemoData totalProfitBad];
    
    // Create and configure the bar chart with the project cost.
    WSChart *barChart = [WSChart barPlotWithFrame:[self.chart frame]
                                             data:self.projectCost
                                            style:kChartBarPlain
                                      colorScheme:kColorLight];
    WSPlotAxis *axis = [barChart firstPlotAxis];
    [barChart scaleAllAxisYD:NARangeMake(-50, 200)];
    [barChart setAllAxisLocationYD:0.];
    [axis setTicksXDWithData:self.projectCost];
    [axis autoTicksYD];
    [axis.ticksX setTickLabelsWithStyle:NSNumberFormatterNoStyle];
    [axis.ticksY setTickLabelsWithStyle:NSNumberFormatterNoStyle];
    WSPlotBar *barPlot = [barChart firstPlotOfClass:[WSPlotBar class]];
    WSBarProperties *barDefault = (WSBarProperties *)[barPlot propDefault];
    [barDefault setOutlineColor:[UIColor darkGrayColor]];
    [barDefault setBarColor:[UIColor darkGrayColor]];
    [barDefault setBarColor2:[UIColor redColor]];
    [barDefault setStyle:kBarGradient];
    
    // Add the line chart with the net profits.
    [barChart generateControllerWithData:self.totalProfit
                               plotClass:[WSPlotData class]
                                   frame:[self.chart frame]];
    WSPlotData *linePlot = [barChart firstPlotOfClass:[WSPlotData class]];
    linePlot.propDefault.symbolStyle = kSymbolNone;
    linePlot.lineWidth = 3;
    
    [self.chart addPlotsFromChart:barChart];

The default case is the “bad scenario”. When the user triggers the segmented control, we need to update the scenario accordingly:

- (void)toggleSwitch:(UISegmentedControl *)sender
{
    switch ([sender selectedSegmentIndex]) {
        case 0:
            // Bad scenario.
            [self badScenario];
            break;
            
        case 1:
            // Good scenario.
            [self goodScenario];
            break;
            
        default:
            break;
    }
}

A particularly important point is the handling of user interactions: it is not possible to start an animation while another one is running. So we need to handle the case when the user decides to trigger the switch while the animation is still running. There are two ways to handle this case: i) abort the animation using WSChart's method abortAnimation or ii) prevent the user from toggling the switch while the animation is running. The latter is generally considered bad style on iOS. It is, however, a little more difficult to implement and thus we choose this harder case for illustration. The alternative is left as an exercise:

- (void)goodScenario
{
    if (self.chart.animationTimer != nil) {
        [self.aSwitch setSelectedSegmentIndex:self.current];
        return;
    } else {
        [self.chart dataAnimateWithDuration:duration
                                 animations:^{
                                     [[self.chart plotAtIndex:0] setDataD:[DemoData projectCostGood]];
                                     [[self.chart plotAtIndex:2] setDataD:[DemoData totalProfitGood]];
                                 }];
        self.current = 1;
    }
}

- (void)badScenario
{
    if (self.chart.animationTimer != nil) {
        [self.aSwitch setSelectedSegmentIndex:self.current];
    } else {
        [self.chart dataAnimateWithDuration:duration
                                 animations:^{
                                     [[self.chart plotAtIndex:0] setDataD:[DemoData projectCostBad]];
                                     [[self.chart plotAtIndex:2] setDataD:[DemoData totalProfitBad]];
                                 }];
        self.current = 0;
    }
}