Search code examples
iosswiftxcodeswiftcharts

Swiftchars - bars chart overlap after specific number of points


  • How can I specify min space between bars in bar chart using SwiftChars library ?

    • Here is my code sample for using SwiftCharts to display bars chart:
    // xAxis
    
     let labelSettings = ChartLabelSettings(font: .systemFont(ofSize: 13.0), fontColor: .aiduBlue)
    
     var xValues = [ChartAxisValue]()
     xValues.append(ChartAxisValueString(order: -1))
     for (index, point) in barsDataSource.enumerated() {
         let dateString = DateFormatter.shortDayOfWeekDayOnTwoLinesFormatter.string(from: point.departureDate)
         xValues.append(ChartAxisValueString(dateString, order: index, labelSettings: labelSettings))
     }
     xValues.append(ChartAxisValueString(order: xValues.count - 1))
     let xModel = ChartAxisModel(axisValues: xValues,
                                 lineColor: .aiduSkyBlue,
                                 axisTitleLabel: ChartAxisLabel(text: "", settings: labelSettings))
    
     // yAxis
    
     let yAxisMinSpaceBetweenPoints: CGFloat = 10.0
     let yAxisTopBottomMargin = (((barsMaxPrice - barsMinPrice) / Double(barsDataSource.count)) + Double(yAxisMinSpaceBetweenPoints)) + 10
     let yAxisGenerator = ChartAxisValuesGeneratorNice(minValue: barsMinPrice - yAxisTopBottomMargin,
                                                       maxValue: barsMaxPrice + yAxisTopBottomMargin,
                                                       preferredDividers: 1,
                                                       minSpace: yAxisMinSpaceBetweenPoints,
                                                       maxTextSize: CGFloat.greatestFiniteMagnitude,
                                                       multiplierUpdateMode: .nice)
     let yLabelsGenerator = ChartAxisLabelsGeneratorFunc {scalar in
         return ChartAxisLabel(text: "", settings: ChartLabelSettings())
     }
     let yModel = ChartAxisModel(lineColor: UIColor.white.withAlphaComponent(0),
                                 firstModelValue: barsMinPrice,
                                 lastModelValue: barsMaxPrice,
                                 axisTitleLabels: [],
                                 axisValuesGenerator: yAxisGenerator,
                                 labelsGenerator: yLabelsGenerator)
    
     // Char Bars layer
    
     let frame = chartFrame(containerView.bounds)
     let coordsSpace = ChartCoordsSpaceLeftBottomSingleAxis(chartSettings: chartSettings,
                                                            chartFrame: frame,
                                                            xModel: xModel,
                                                            yModel: yModel)
     let (xAxisLayer, yAxisLayer, innerFrame) = (coordsSpace.xAxisLayer, coordsSpace.yAxisLayer, coordsSpace.chartInnerFrame)
    
     let barsModels: [ChartBarModel] = barsDataSource.enumerated().flatMap { index, item in
         [
             ChartBarModel(constant: ChartAxisValueInt(index),
                           axisValue1: ChartAxisValueDouble(0),
                           axisValue2: ChartAxisValueDouble(item.price),
                           bgColor: barColor(price: item.price, minPrice: barsMinPrice))
         ]
     }
    
     let chartBarSettings = ChartBarViewSettings(animDuration: 0,
                                                 animDelay: 0,
                                                 cornerRadius: 0,
                                                 roundedCorners: .allCorners,
                                                 selectionViewUpdater: nil,
                                                 delayInit: false)
    
     let chartBarsLayer =  ChartBarsLayer(xAxis: xAxisLayer.axis,
                                          yAxis: yAxisLayer.axis,
                                          bars: barsModels,
                                          horizontal: false,
                                          barWidth: 40,
                                          settings: chartBarSettings,
                                          mode: .translate,
                                          tapHandler: { [weak self] (tappedBar) in
                                             self?.chartBarTapHandler(tappedBar: tappedBar)
         }, viewGenerator: { [weak self] (p1, p2, barWidth, color, settings, model, index) -> ChartPointViewBar in
             var barBGColor = color
             if let s = self, let lastTappedBarModel = s.lastTappedBarModel {
                 let currentBarModel = s.dataSource[index]
                 barBGColor = currentBarModel.departureDate == lastTappedBarModel.departureDate && currentBarModel.duration == lastTappedBarModel.duration ? s.barSelectionBgColor : color
             }
             let view = ChartPointViewBar(p1: p1, p2: p2, width: barWidth, bgColor: barBGColor, settings: settings)
             return view
     })
    
     // Price labels layer
    
     let labelToBarSpace: Double = 20
     let labelChartPoints = barsModels.map { bar in
         ChartPoint(x: bar.constant, y: bar.axisValue2.copy(bar.axisValue2.scalar + labelToBarSpace))
     }
    
     let priceLabelsLayer = ChartPointsViewsLayer(xAxis: xAxisLayer.axis,
                                                  yAxis: yAxisLayer.axis,
                                                  chartPoints: labelChartPoints,
                                                  viewGenerator: {(chartPointModel, layer, chart) -> UIView? in
                                                     let label = HandlingLabel()
                                                     label.text = PriceFormatter.string(fromPrice: Float(chartPointModel.chartPoint.y.scalar - labelToBarSpace))
                                                     label.font = .boldSystemFont(ofSize: 10.0)
                                                     label.textColor = UIColor.aiduBlue
                                                     label.sizeToFit()
                                                     let pos = chartPointModel.chartPoint.y.scalar > 0
                                                     label.center = CGPoint(x: chartPointModel.screenLoc.x, y: pos ? innerFrame.origin.y : innerFrame.origin.y + innerFrame.size.height)
                                                     label.alpha = 0
                                                     label.isUserInteractionEnabled = false
                                                     label.movedToSuperViewHandler = {[weak label] in
                                                         label?.alpha = 1
                                                         label?.center.y = chartPointModel.screenLoc.y
                                                     }
                                                     return label
     }, displayDelay: 0, mode: .translate)
    
     return Chart(
         frame: frame,
         innerFrame: innerFrame,
         settings: chartSettings,
         layers: [
             xAxisLayer,
             yAxisLayer,
             chartBarsLayer,
             priceLabelsLayer
         ]
     )
    
    private var chartSettings: ChartSettings {
         var chartSettings = ChartSettings()
         chartSettings.leading = 2
         chartSettings.top = 2
         chartSettings.trailing = 2
         chartSettings.bottom = 2
         chartSettings.axisStrokeWidth = 0.4
         chartSettings.spacingBetweenAxesX = 2
         chartSettings.spacingBetweenAxesY = 2
         chartSettings.labelsSpacing = 10
         chartSettings.labelsToAxisSpacingY = 0
         chartSettings.spacingBetweenAxesY = 0
         chartSettings.axisTitleLabelsToLabelsSpacing = 0
         chartSettings.zoomPan.panEnabled = true
         chartSettings.zoomPan.zoomEnabled = false
         chartSettings.zoomPan.maxZoomX = 3
         chartSettings.zoomPan.minZoomX = 3
         chartSettings.zoomPan.minZoomY = 1
         chartSettings.zoomPan.maxZoomY = 1
         return chartSettings
     }
    
    
    • When data source contains around (10 for ex) points, chart working without overlapping, but without fixed space between bars if you changed (increase/decrease) data source: without overlapping

    • When data source contains around (35 for ex) points, bars overlapped: overlapping

SwiftCharts version 0.6.5 Swift version 5


Solution

  • Finally I found solution:

    I had to set the minZoomX and maxZoomX (in chart settings) with fixed value which the number of pages for the chart (or number of scrollable area than the original width). why x ? because I want chart to scroll horizontally.

    here is code:

            let barWidth: CGFloat = 30.0
            let spaceBetweenBars: CGFloat = 80.0
    
            let zoomXValue = CGFloat(barsDataSource.count) / (UIScreen.main.bounds.width / (barWidth + spaceBetweenBars))
            chartSettings.zoomPan.minZoomX = zoomXValue
            chartSettings.zoomPan.maxZoomX = zoomXValue