ivnsch / SwiftCharts

Easy to use and highly customizable charts library for iOS
Apache License 2.0
2.53k stars 410 forks source link

How to add description in front of horizontal bars #375

Closed vitormeds closed 5 years ago

vitormeds commented 5 years ago

Hi, I'm trying to add a description in front of the horizontal bars graph, but I'm not getting the expected result, I followed the Bars +/- example, but it's only displaying the description and not displaying the graph, how can I display the description on the front of each bar of the graph as in the image below:

image

Here is my code:

`class ViewController: UIViewController {

fileprivate var chart: Chart?

let sideSelectorHeight: CGFloat = 50

fileprivate func barsChart(horizontal: Bool) -> Chart {
    let tuplesXY = [(2, 8), (4, 9)]

    func reverseTuples(_ tuples: [(Int, Int)]) -> [(Int, Int)] {
        return tuples.map{($0.1, $0.0)}
    }

    //Se for horizontal inverte a visualizacao do grafico e a posicao dos valores
    let chartPoints = (horizontal ? reverseTuples(tuplesXY) : tuplesXY).map{ChartPoint(x: ChartAxisValueInt($0.0), y: ChartAxisValueInt($0.1))}

    //fonte das labels do grafico
    let labelSettings = ChartLabelSettings(font: UIFont.systemFont(ofSize: 12))

    //seta as labels dos graficos
    let generator = ChartAxisGeneratorMultiplier(2)
    let labelsGenerator = ChartAxisLabelsGeneratorFunc {scalar in
        return ChartAxisLabel(text: "\(scalar)", settings: labelSettings)
    }

    let xGenerator = ChartAxisGeneratorMultiplier(2)

    //seta os titulos na horizontal e vertical dos graficos e seta o valor maximo e minimo do grafico
    let xModel = ChartAxisModel(firstModelValue: 0, lastModelValue: 10, axisTitleLabels: [ChartAxisLabel(text: "Axis title", settings: labelSettings)], axisValuesGenerator: xGenerator, labelsGenerator: labelsGenerator)
    let yModel = ChartAxisModel(firstModelValue: 0, lastModelValue: 10, axisTitleLabels: [ChartAxisLabel(text: "Axis title", settings: labelSettings.defaultVertical())], axisValuesGenerator: generator, labelsGenerator: labelsGenerator)

    //seta as barras no grafico
    let barViewGenerator = {(chartPointModel: ChartPointLayerModel, layer: ChartPointsViewsLayer, chart: Chart) -> UIView? in
        let bottomLeft = layer.modelLocToScreenLoc(x: 0, y: 0)

        //seta a largura da barra do grafico
        let barWidth: CGFloat = 30

        let settings = ChartBarViewSettings(animDuration: 0.5)

        let (p1, p2): (CGPoint, CGPoint) = {
            if horizontal {
                return (CGPoint(x: bottomLeft.x, y: chartPointModel.screenLoc.y), CGPoint(x: chartPointModel.screenLoc.x, y: chartPointModel.screenLoc.y))
            } else {
                return (CGPoint(x: chartPointModel.screenLoc.x, y: bottomLeft.y), CGPoint(x: chartPointModel.screenLoc.x, y: chartPointModel.screenLoc.y))
            }
        }()
        return ChartPointViewBar(p1: p1, p2: p2, width: barWidth, bgColor: UIColor.blue.withAlphaComponent(0.6), settings: settings)
    }

    let frame = ExamplesDefaults.chartFrame(view.bounds)
    let chartFrame = chart?.frame ?? CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: frame.size.height - sideSelectorHeight)

    let chartSettings = ExamplesDefaults.chartSettingsWithPanZoom

    let coordsSpace = ChartCoordsSpaceLeftBottomSingleAxis(chartSettings: chartSettings, chartFrame: chartFrame, xModel: xModel, yModel: yModel)
    let (xAxisLayer, yAxisLayer, innerFrame) = (coordsSpace.xAxisLayer, coordsSpace.yAxisLayer, coordsSpace.chartInnerFrame)

    let chartPointsLayer = ChartPointsViewsLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: chartPoints, viewGenerator: barViewGenerator)

    let settings = ChartGuideLinesDottedLayerSettings(linesColor: UIColor.black, linesWidth: ExamplesDefaults.guidelinesWidth)
    let guidelinesLayer = ChartGuideLinesDottedLayer(xAxisLayer: xAxisLayer, yAxisLayer: yAxisLayer, settings: settings)

    let labelToBarSpace: Double = 3 // domain units
    let labelChartPoints = chartPoints
    let formatter = NumberFormatter()
    formatter.maximumFractionDigits = 2
    let labelsLayer = ChartPointsViewsLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: chartPoints, viewGenerator: {(chartPointModel, layer, chart) -> UIView? in
        let label = HandlingLabel()

        let pos = chartPointModel.chartPoint.y.scalar > 0

        label.text = "1"
        label.font = ExamplesDefaults.labelFont
        label.sizeToFit()
        label.center = CGPoint(x: chartPointModel.screenLoc.x, y: pos ? innerFrame.origin.y : innerFrame.origin.y + innerFrame.size.height)
        label.alpha = 0

        label.movedToSuperViewHandler = {[weak label] in
            UIView.animate(withDuration: 0.3, animations: {
                label?.alpha = 1
                label?.center.y = chartPointModel.screenLoc.y
            })
        }

        return label

    }, displayDelay: 0.5, mode: .translate) // show after bars animation

    return Chart(
        frame: chartFrame,
        innerFrame: innerFrame,
        settings: chartSettings,
        layers: [
            xAxisLayer,
            yAxisLayer,
            guidelinesLayer,
            labelsLayer
        ]
    )
}

fileprivate func showChart(horizontal: Bool) {
    self.chart?.clearView()

    let chart = barsChart(horizontal: horizontal)
    view.addSubview(chart.view)
    self.chart = chart
}

override func viewDidLoad() {
    view.backgroundColor = UIColor.white
    var bar = BarsExample()
    showChart(horizontal: true)
    if let chart = chart {
        let sideSelector = DirSelector(frame: CGRect(x: 0, y: chart.frame.origin.y + chart.frame.size.height, width: view.frame.size.width, height: sideSelectorHeight), controller: bar)
        view.addSubview(sideSelector)
    }
}

class DirSelector: UIView {

    let horizontal: UIButton
    let vertical: UIButton

    weak var controller: BarsExample?

    fileprivate let buttonDirs: [UIButton : Bool]

    init(frame: CGRect, controller: BarsExample) {

        self.controller = controller

        horizontal = UIButton()
        horizontal.setTitle("Horizontal", for: UIControl.State())
        vertical = UIButton()
        vertical.setTitle("Vertical", for: UIControl.State())

        buttonDirs = [horizontal : true, vertical : false]

        super.init(frame: frame)

        addSubview(horizontal)
        addSubview(vertical)

        for button in [horizontal, vertical] {
            button.titleLabel?.font = ExamplesDefaults.fontWithSize(14)
            button.setTitleColor(UIColor.blue, for: UIControl.State())
            button.addTarget(self, action: #selector(DirSelector.buttonTapped(_:)), for: .touchUpInside)
        }
    }

    @objc func buttonTapped(_ sender: UIButton) {
        let horizontal = sender == self.horizontal ? true : false
        controller?.showChart(horizontal: horizontal)
    }

    override func didMoveToSuperview() {
        let views = [horizontal, vertical]
        for v in views {
            v.translatesAutoresizingMaskIntoConstraints = false
        }

        let namedViews = views.enumerated().map{index, view in
            ("v\(index)", view)
        }

        var viewsDict = Dictionary<String, UIView>()
        for namedView in namedViews {
            viewsDict[namedView.0] = namedView.1
        }

        let buttonsSpace: CGFloat = Env.iPad ? 20 : 10

        let hConstraintStr = namedViews.reduce("H:|") {str, tuple in
            "\(str)-(\(buttonsSpace))-[\(tuple.0)]"
        }

        let vConstraits = namedViews.flatMap {NSLayoutConstraint.constraints(withVisualFormat: "V:|[\($0.0)]", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDict)}

        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: hConstraintStr, options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDict)
            + vConstraits)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

}

`

vitormeds commented 5 years ago

I discovered, to add a description. I need to create a separate layer for a description.

Exemple:

` let labelsLayer = ChartPointsViewsLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: chartPoints, viewGenerator: {(chartPointModel, layer, chart) -> UIView? in let label = HandlingLabel()

        let pos = chartPointModel.chartPoint.y.scalar > 0

        label.text = "1"
        label.font = ExamplesDefaults.labelFont
        label.sizeToFit()
        label.center = CGPoint(x: chartPointModel.screenLoc.x + 10 , y: pos ? innerFrame.origin.y : innerFrame.origin.y + innerFrame.size.height)
        label.alpha = 0

        label.movedToSuperViewHandler = {[weak label] in
            UIView.animate(withDuration: 0.3, animations: {
                label?.alpha = 1
                label?.center.y = chartPointModel.screenLoc.y
            })
        }
        return label

    }, displayDelay: 0.5, mode: .translate) // show after bars animation

    return Chart(
        frame: chartFrame,
        innerFrame: innerFrame,
        settings: chartSettings,
        layers: [
            xAxisLayer,
            yAxisLayer,
            guidelinesLayer,
            chartPointsLayer,
            labelsLayer
        ]
    )`