Display order of chart legend


#1

I have SChartBarSeries with 3 series but the legend is always the opposite order of the bar series. So lets say I have 3 series 1, 2, 3. The legend always shows the series 3, 2, 1. How can I reverse the display order of the legend?


#2

Hi rches,

The legend displays the series in the order you provide them to your chart’s data source.

Using your example, if you are passing your series to your data source in the order 1, 2, 3. They should be displayed in the legend in the order 1, 2, 3.

It sounds like you aren’t seeing this behaviour.
What version of ShinobiCharts are you using?
This can be found in the Version.txt file in your Shinobi download.

Can you manipulate our ColumnSeries sample to replicate your issue and host it somewhere for us to download?

This would really help us identify the source of your issue.

Kind regards,
Andrew Polkinghorn


#3

Yes, I have made the edits to the ColumnSeries project to showcase the issue. Replace the current code in ViewController.swift with the following:

`//
// ViewController.swift
// ShinobiControls
//
// Copyright © 2014 Scott Logic. All rights reserved.
//

import UIKit
import ShinobiCharts

class ViewController: UIViewController, SChartDatasource {

// Grocery sales data array of dictionaries containing key value pairs matching grocery items to sales (in 1000s)
let salesData: [[String : Double]] = [
    ["Broccoli" : 5.65, "Carrots" : 12.6, "Mushrooms" : 8.4], // 2013 sales
    ["Broccoli" : 5.65, "Carrots" : 12.6, "Mushrooms" : 8.4], // 2014 sales
    ["Broccoli" : 5.65, "Carrots" : 12.6, "Mushrooms" : 8.4] // 2015 sales

]

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Add a margin between the view and chart
    let margin: CGFloat = UIDevice.current.userInterfaceIdiom == .phone ? 20 : 50
    
    // Create the chart
    let chart = ShinobiChart(frame: view.bounds.insetBy(dx: margin, dy: margin))
    chart.title = "Grocery Sales Figures"
    
    chart.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    
    chart.licenseKey = "" // TODO: Add your trial license key here
    
    // Create and add axes to chart
    let xAxis = SChartCategoryAxis()
    
    // Don't add any padding between series columns with same x value
    xAxis.style.interSeriesPadding = 0
    chart.xAxis = xAxis
    
    let yAxis = SChartNumberAxis()
    yAxis.title = "Sales (1000s)"
    yAxis.rangePaddingHigh = 1
    chart.yAxis = yAxis
    //chart.xAxis?.style.minorTickStyle.showLabels = true
    
    // Add chart to the view
    view.addSubview(chart)
    
    // This view controller will provide data to the chart
    chart.datasource = self
    
    // Show the legend on all devices
    chart.legend.isHidden = false
    chart.legend.placement = .outsidePlotArea
    chart.legend.position = .middleLeft

}

// MARK:- SChartDatasource Functions

// Two series of data (2013 and 2014 sales data)
func numberOfSeries(in chart: ShinobiChart) -> Int {
    return 3
}

// Create column series objects for both sets of data and assign relevant title
func sChart(_ chart: ShinobiChart, seriesAt index: Int) -> SChartSeries {
    let columnSeries = SChartBarSeries()
    if index == 0{
      columnSeries.title = "2013"
    }
    else if index == 1{
        columnSeries.title = "2014"
    }
    else if index == 2{
        columnSeries.title = "2015"
    }
    return columnSeries
}

// Number of datapoints in series is equivalent to number of grocery items for year at seriesIndex
func sChart(_ chart: ShinobiChart, numberOfDataPointsForSeriesAt seriesIndex: Int) -> Int {
    return salesData[seriesIndex].count
}

// Create data point objects
func sChart(_ chart: ShinobiChart, dataPointAt dataIndex: Int, forSeriesAt seriesIndex: Int) -> SChartData {
    let dataPoint = SChartDataPoint()
    
    // Get array of keys (grocery names) for the series
    let allGroceriesInYear = [String](salesData[seriesIndex].keys)
    
    let groceryForDataPoint = allGroceriesInYear[dataIndex]
    
    // Set data point x and y values to grocery title and sales data
    dataPoint.xValue = groceryForDataPoint
    dataPoint.yValue = salesData[seriesIndex][groceryForDataPoint]
    
    return dataPoint
}

}

`


#4

Hi rches,

This is intended behaviour due to the series order being drawn away from the chart origin.

In the case of a Bar chart, the data is drawn from bottom to top which is the reverse order of the way the legend orders all it’s items. The first series being at the top & the last at the bottom.

I have written an extension in Swift that should implement the behaviour you are after.

extension ViewController: SChartDelegate {
    
    func swapFrames(in views: [UIView]) {
        var frames = [CGRect]()
        for view in views {
            let frame = view.frame
            frames.append(frame)
        }
        frames = frames.reversed()
        for (index, frame) in frames.enumerated() {
            let symbol = views[index]
            symbol.frame = frame;
        }
    }
    
    func sChartRenderFinished(_ chart: ShinobiChart) {
        // Cast NSMutableArrays to Array<UIView>
        if let labels = NSArray(array: chart.legend.labels) as? [UIView],
            let symbols = NSArray(array: chart.legend.symbols) as? [UIView] {
            swapFrames(in: labels)
            swapFrames(in: symbols)
        }
    }
}

Make sure to set the delegate on your chart to self.

Let me know if you have any questions.

Kind regards,
Andrew Polkinghorn.


#5

Although its Shinobi’s intended behavior, I typically don’t see legends of charts represented this way. Might want to consider changing this at the framework level


#6

Hi rches,

I’m glad my solution works for you. :slight_smile:

Thanks for your feedback!

We appreciate all the feedback we get from our users.

I have forwarded this to our development team who will discuss this further.

Kind regards,
Andrew Polkinghorn.