Charts improvements

This commit is contained in:
Ilya Laktyushin
2020-03-21 03:19:05 +04:00
parent c5d39df2b3
commit d6f0a02fc7
59 changed files with 4448 additions and 4271 deletions

View File

@@ -245,8 +245,14 @@ class GeneralChartComponentController: ChartThemeContainer {
var labels: [LinesChartLabel] = []
for index in stride(from: chartsCollection.axisValues.count - 1, to: -1, by: -strideInterval).reversed() {
let date = chartsCollection.axisValues[index]
labels.append(LinesChartLabel(value: CGFloat(date.timeIntervalSince1970),
text: scaleType.dateFormatter.string(from: date)))
let timestamp = date.timeIntervalSince1970
if timestamp <= 24 {
labels.append(LinesChartLabel(value: CGFloat(timestamp),
text: "\(Int(timestamp)):00"))
} else {
labels.append(LinesChartLabel(value: CGFloat(timestamp),
text: scaleType.dateFormatter.string(from: date)))
}
}
prevoiusHorizontalStrideInterval = strideInterval
horizontalScalesRenderer.setup(labels: labels, animated: animated)
@@ -318,8 +324,9 @@ class GeneralChartComponentController: ChartThemeContainer {
tapAction: { [weak self] in
self?.zoomInOnDateClosure?(closestDate) },
hideAction: { [weak self] in
self?.setDetailsChartVisibleClosure?(false, true)
})
})
return viewModel
}

View File

@@ -157,8 +157,14 @@ public class BaseLinesChartController: BaseChartController {
var labels: [LinesChartLabel] = []
for index in stride(from: initialChartsCollection.axisValues.count - 1, to: -1, by: -strideInterval).reversed() {
let date = initialChartsCollection.axisValues[index]
labels.append(LinesChartLabel(value: CGFloat(date.timeIntervalSince1970),
text: scaleType.dateFormatter.string(from: date)))
let timestamp = date.timeIntervalSince1970
if timestamp <= 24 {
labels.append(LinesChartLabel(value: CGFloat(timestamp),
text: "\(Int(timestamp)):00"))
} else {
labels.append(LinesChartLabel(value: CGFloat(timestamp),
text: scaleType.dateFormatter.string(from: date)))
}
}
return (strideInterval, labels)
}
@@ -179,14 +185,14 @@ public class BaseLinesChartController: BaseChartController {
}
func verticalLimitsLabels(verticalRange: ClosedRange<CGFloat>) -> (ClosedRange<CGFloat>, [LinesChartLabel]) {
let ditance = verticalRange.distance
let distance = verticalRange.distance
let chartHeight = chartFrame().height
guard ditance > 0, chartHeight > 0 else { return (BaseConstants.defaultRange, []) }
guard distance > 0, chartHeight > 0 else { return (BaseConstants.defaultRange, []) }
let approximateNumberOfChartValues = (chartHeight / BaseConstants.minimumAxisYLabelsDistance)
var numberOfOffsetsPerItem = ditance / approximateNumberOfChartValues
var numberOfOffsetsPerItem = distance / approximateNumberOfChartValues
var multiplier: CGFloat = 1.0
while numberOfOffsetsPerItem > 10 {
numberOfOffsetsPerItem /= 10

View File

@@ -237,7 +237,7 @@ public class TwoAxisLinesChartController: BaseLinesChartController {
let chartHeight = chartFrame().height
let approximateNumberOfChartValues = (chartHeight / BaseConstants.minimumAxisYLabelsDistance)
var dividorsAndMultiplers: [(startValue: CGFloat, base: CGFloat, count: Int, maximumNumberOfDecimals: Int)] = graphControllers.enumerated().map { arg in
let dividorsAndMultiplers: [(startValue: CGFloat, base: CGFloat, count: Int, maximumNumberOfDecimals: Int)] = graphControllers.enumerated().map { arg in
let (index, controller) = arg
let verticalRange = LinesChartRenderer.LineData.verticalRange(lines: controller.chartLines,
calculatingRange: horizontalRange,

View File

@@ -138,7 +138,7 @@ class PercentChartComponentController: GeneralChartComponentController {
let values: [ChartDetailsViewModel.Value] = chartsCollection.chartValues.enumerated().map { arg in
let (index, component) = arg
return ChartDetailsViewModel.Value(prefix: PercentConstants.percentValueFormatter.string(from: component.values[pointIndex] / total * 100),
return ChartDetailsViewModel.Value(prefix: total > 0 ? PercentConstants.percentValueFormatter.string(from: component.values[pointIndex] / total * 100) : "0%",
title: component.name,
value: BaseConstants.detailsNumberFormatter.string(from: component.values[pointIndex]),
color: component.color,
@@ -151,16 +151,16 @@ class PercentChartComponentController: GeneralChartComponentController {
dateString = BaseConstants.headerMediumRangeFormatter.string(from: closestDate)
}
let viewModel = ChartDetailsViewModel(title: dateString,
showArrow: self.isZoomable && !self.isZoomed,
showArrow: total > 0 && self.isZoomable && !self.isZoomed,
showPrefixes: true,
values: values,
totalValue: nil,
tapAction: { [weak self] in
self?.hideDetailsView(animated: true)
self?.zoomInOnDateClosure?(closestDate) },
hideAction: { [weak self] in
self?.hideDetailsView(animated: true)
})
hideAction: { [weak self] in
self?.hideDetailsView(animated: true)
})
return viewModel
}

View File

@@ -95,9 +95,11 @@ public class PercentPieChartController: BaseChartController {
totalVerticalRange: BaseConstants.defaultRange)
switchToChart(chartsCollection: percentController.chartsCollection, isZoomed: false, animated: false)
TimeInterval.animationDurationMultipler = 0.00001
self.didTapZoomIn(date: Date(timeIntervalSinceReferenceDate: 603849600.0), animated: false)
TimeInterval.animationDurationMultipler = 1
if let lastDate = initialChartsCollection.axisValues.last {
TimeInterval.animationDurationMultipler = 0.00001
self.didTapZoomIn(date: lastDate, animated: false)
TimeInterval.animationDurationMultipler = 1.0
}
}
func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) {

View File

@@ -17,25 +17,25 @@ class BarsComponentController: GeneralChartComponentController {
let mainBarsRenderer: BarChartRenderer
let horizontalScalesRenderer: HorizontalScalesRenderer
let verticalScalesRenderer: VerticalScalesRenderer
let secondVerticalScalesRenderer: VerticalScalesRenderer?
let previewBarsChartRenderer: BarChartRenderer
private(set) var barsWidth: CGFloat = 1
private (set) var chartBars: BarChartRenderer.BarsData = .blank
private var step: Bool
init(isZoomed: Bool,
mainBarsRenderer: BarChartRenderer,
horizontalScalesRenderer: HorizontalScalesRenderer,
verticalScalesRenderer: VerticalScalesRenderer,
previewBarsChartRenderer: BarChartRenderer) {
previewBarsChartRenderer: BarChartRenderer,
step: Bool = false) {
self.mainBarsRenderer = mainBarsRenderer
self.horizontalScalesRenderer = horizontalScalesRenderer
self.verticalScalesRenderer = verticalScalesRenderer
self.previewBarsChartRenderer = previewBarsChartRenderer
self.secondVerticalScalesRenderer = VerticalScalesRenderer()
self.secondVerticalScalesRenderer?.isRightAligned = true
self.step = step
self.mainBarsRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel
self.previewBarsChartRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel
@@ -44,7 +44,7 @@ class BarsComponentController: GeneralChartComponentController {
}
override func initialize(chartsCollection: ChartsCollection, initialDate: Date, totalHorizontalRange _: ClosedRange<CGFloat>, totalVerticalRange _: ClosedRange<CGFloat>) {
let (width, chartBars, totalHorizontalRange, totalVerticalRange) = BarChartRenderer.BarsData.initialComponents(chartsCollection: chartsCollection)
let (width, chartBars, totalHorizontalRange, totalVerticalRange) = BarChartRenderer.BarsData.initialComponents(chartsCollection: chartsCollection, separate: self.step)
self.chartBars = chartBars
self.barsWidth = width
@@ -76,10 +76,10 @@ class BarsComponentController: GeneralChartComponentController {
mainBarsRenderer.bars = self.chartBars
previewBarsChartRenderer.bars = self.chartBars
previewBarsChartRenderer.setup(verticalRange: 0...117278, animated: animated)
previewBarsChartRenderer.setup(verticalRange: totalVerticalRange, animated: animated)
previewBarsChartRenderer.setup(horizontalRange: totalHorizontalRange, animated: animated)
setupMainChart(verticalRange: 0...117278, animated: animated)
setupMainChart(verticalRange: initialVerticalRange, animated: animated)
setupMainChart(horizontalRange: initialHorizontalRange, animated: animated)
updateChartVerticalRanges(horizontalRange: initialHorizontalRange, animated: animated)
@@ -119,16 +119,12 @@ class BarsComponentController: GeneralChartComponentController {
horizontalScalesRenderer.setVisible(visible, animated: animated)
verticalScalesRenderer.setVisible(visible, animated: animated)
previewBarsChartRenderer.setVisible(visible, animated: animated)
secondVerticalScalesRenderer?.setVisible(visible, animated: animated)
}
func setupMainChart(horizontalRange: ClosedRange<CGFloat>, animated: Bool) {
mainBarsRenderer.setup(horizontalRange: horizontalRange, animated: animated)
horizontalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated)
verticalScalesRenderer.setup(horizontalRange: horizontalRange, animated: animated)
secondVerticalScalesRenderer?.setup(horizontalRange: horizontalRange, animated: animated)
}
var visibleBars: BarChartRenderer.BarsData {
@@ -142,6 +138,7 @@ class BarsComponentController: GeneralChartComponentController {
func updateChartVerticalRanges(horizontalRange: ClosedRange<CGFloat>, animated: Bool) {
if let range = BarChartRenderer.BarsData.verticalRange(bars: visibleBars,
separate: self.step,
calculatingRange: horizontalRange,
addBounds: true) {
let (range, labels) = verticalLimitsLabels(verticalRange: range)
@@ -151,31 +148,19 @@ class BarsComponentController: GeneralChartComponentController {
verticalScalesRenderer.setVisible(true, animated: animated)
setupMainChart(verticalRange: range, animated: animated)
let (secondRange, secondLabels) = verticalLimitsLabels(verticalRange: range)
if secondVerticalScalesRenderer?.verticalRange.end != secondRange {
secondVerticalScalesRenderer?.setup(verticalLimitsLabels: secondLabels, animated: animated)
}
secondVerticalScalesRenderer?.setVisible(true, animated: animated)
} else {
verticalScalesRenderer.setVisible(false, animated: animated)
secondVerticalScalesRenderer?.setVisible(false, animated: animated)
}
if let range = BarChartRenderer.BarsData.verticalRange(bars: visibleBars) {
if let range = BarChartRenderer.BarsData.verticalRange(bars: visibleBars, separate: self.step) {
previewBarsChartRenderer.setup(verticalRange: range, animated: animated)
}
}
func setupMainChart(verticalRange: ClosedRange<CGFloat>, animated: Bool) {
var verticalRange = verticalRange
if verticalRange.upperBound > 2000 && verticalRange.upperBound < 10000 {
verticalRange = 0...117278
}
mainBarsRenderer.setup(verticalRange: verticalRange, animated: animated)
horizontalScalesRenderer.setup(verticalRange: verticalRange, animated: animated)
verticalScalesRenderer.setup(verticalRange: verticalRange, animated: animated)
secondVerticalScalesRenderer?.setup(verticalRange: verticalRange, animated: animated)
}
public override func updateChartsVisibility(visibility: [Bool], animated: Bool) {
@@ -198,12 +183,15 @@ class BarsComponentController: GeneralChartComponentController {
var viewModel = super.chartDetailsViewModel(closestDate: closestDate, pointIndex: pointIndex)
let visibleChartValues = self.visibleChartValues
let totalSumm: CGFloat = visibleChartValues.map { CGFloat($0.values[pointIndex]) }.reduce(0, +)
viewModel.totalValue = ChartDetailsViewModel.Value(prefix: nil,
title: "Total",
value: BaseConstants.detailsNumberFormatter.string(from: totalSumm),
color: .white,
visible: visibleChartValues.count > 1)
if !self.step {
viewModel.totalValue = ChartDetailsViewModel.Value(prefix: nil,
title: "Total",
value: BaseConstants.detailsNumberFormatter.string(from: totalSumm),
color: .white,
visible: visibleChartValues.count > 1)
} else {
viewModel.title = ""
}
return viewModel
}
@@ -235,10 +223,6 @@ class BarsComponentController: GeneralChartComponentController {
verticalScalesRenderer.horizontalLinesColor = theme.barChartStrongLinesColor
mainBarsRenderer.update(backgroundColor: theme.chartBackgroundColor, animated: false)
previewBarsChartRenderer.update(backgroundColor: theme.chartBackgroundColor, animated: false)
secondVerticalScalesRenderer?.labelsColor = theme.chartLabelsColor
secondVerticalScalesRenderer?.axisXColor = theme.barChartStrongLinesColor
secondVerticalScalesRenderer?.horizontalLinesColor = theme.barChartStrongLinesColor
}
override func updateChartRangeTitle(animated: Bool) {

View File

@@ -1,5 +1,5 @@
//
// DailyBarsChartController.swift
// StackedBarsChartController.swift
// GraphTest
//
// Created by Andrei Salavei on 4/7/19.
@@ -17,6 +17,12 @@ public class StepBarsChartController: BaseChartController {
let barsController: BarsComponentController
let zoomedBarsController: BarsComponentController
override public var isZoomable: Bool {
didSet {
barsController.isZoomable = self.isZoomable
}
}
override public init(chartsCollection: ChartsCollection) {
let horizontalScalesRenderer = HorizontalScalesRenderer()
let verticalScalesRenderer = VerticalScalesRenderer()
@@ -24,13 +30,13 @@ public class StepBarsChartController: BaseChartController {
mainBarsRenderer: BarChartRenderer(step: true),
horizontalScalesRenderer: horizontalScalesRenderer,
verticalScalesRenderer: verticalScalesRenderer,
previewBarsChartRenderer: BarChartRenderer())
previewBarsChartRenderer: BarChartRenderer(step: true), step: true)
zoomedBarsController = BarsComponentController(isZoomed: true,
mainBarsRenderer: BarChartRenderer(step: true),
mainBarsRenderer: BarChartRenderer(),
horizontalScalesRenderer: horizontalScalesRenderer,
verticalScalesRenderer: verticalScalesRenderer,
previewBarsChartRenderer: BarChartRenderer())
previewBarsChartRenderer: BarChartRenderer(), step: true)
super.init(chartsCollection: chartsCollection)
[barsController, zoomedBarsController].forEach { controller in
@@ -40,7 +46,7 @@ public class StepBarsChartController: BaseChartController {
self.didTapZoomIn(date: date)
}
controller.setChartTitleClosure = { [unowned self] (title, animated) in
self.setChartTitleClosure?(title, animated)
self.setChartTitleClosure?("", animated)
}
controller.setDetailsViewPositionClosure = { [unowned self] (position) in
self.setDetailsViewPositionClosure?(position)
@@ -60,12 +66,18 @@ public class StepBarsChartController: BaseChartController {
}
}
public var hourly: Bool = false
public convenience init(chartsCollection: ChartsCollection, hourly: Bool) {
self.init(chartsCollection: chartsCollection)
self.hourly = hourly
}
public override var mainChartRenderers: [ChartViewRenderer] {
return [barsController.mainBarsRenderer,
zoomedBarsController.mainBarsRenderer,
barsController.horizontalScalesRenderer,
barsController.verticalScalesRenderer,
barsController.secondVerticalScalesRenderer!
// performanceRenderer
]
}
@@ -90,57 +102,65 @@ public class StepBarsChartController: BaseChartController {
TimeInterval.setDefaultSuration(.osXDuration)
}
}
super.isZoomed = isZoomed
if isZoomed {
let toHorizontalRange = zoomedBarsController.initialHorizontalRange
let destinationHorizontalRange = (toHorizontalRange.lowerBound - barsController.barsWidth)...(toHorizontalRange.upperBound - barsController.barsWidth)
// let initialChartVerticalRange = lineProportionAnimationRange()
// let visibleVerticalRange = BarChartRenderer.BarsData.verticalRange(bars: zoomedBarsController.visibleBars,
// calculatingRange: zoomedBarsController.initialHorizontalRange) ?? BaseConstants.defaultRange
zoomedBarsController.mainBarsRenderer.setup(verticalRange: 0...117278, animated: false)
zoomedBarsController.setupMainChart(horizontalRange: barsController.currentHorizontalMainChartRange, animated: false)
let verticalVisibleRange = barsController.currentVerticalMainChartRange
let initialVerticalRange = verticalVisibleRange.lowerBound...(verticalVisibleRange.upperBound + verticalVisibleRange.distance * 10)
zoomedBarsController.mainBarsRenderer.setup(horizontalRange: barsController.currentHorizontalMainChartRange, animated: false)
zoomedBarsController.previewBarsChartRenderer.setup(horizontalRange: barsController.currentPreviewHorizontalRange, animated: false)
// zoomedBarsController.mainLinesRenderer.setup(verticalRange: initialChartVerticalRange, animated: false)
// zoomedBarsController.previewLinesChartRenderer.setup(verticalRange: initialChartVerticalRange, animated: false)
zoomedBarsController.mainBarsRenderer.setVisible(false, animated: false)
zoomedBarsController.previewBarsChartRenderer.setVisible(false, animated: false)
zoomedBarsController.mainBarsRenderer.setup(verticalRange: initialVerticalRange, animated: false)
zoomedBarsController.previewBarsChartRenderer.setup(verticalRange: initialVerticalRange, animated: false)
zoomedBarsController.mainBarsRenderer.setVisible(true, animated: false)
zoomedBarsController.previewBarsChartRenderer.setVisible(true, animated: false)
barsController.setupMainChart(horizontalRange: destinationHorizontalRange, animated: animated)
barsController.previewBarsChartRenderer.setup(horizontalRange: zoomedBarsController.totalHorizontalRange, animated: animated)
barsController.mainBarsRenderer.setVisible(false, animated: animated)
barsController.previewBarsChartRenderer.setVisible(false, animated: animated)
zoomedBarsController.willAppear(animated: animated)
barsController.willDisappear(animated: animated)
zoomedBarsController.updateChartsVisibility(visibility: zoomedBarsController.chartBars.components.map { _ in true }, animated: false)
zoomedBarsController.updateChartsVisibility(visibility: barsController.chartVisibility, animated: false)
zoomedBarsController.mainBarsRenderer.setup(verticalRange: zoomedBarsController.currentVerticalMainChartRange, animated: animated, timeFunction: .easeOut)
zoomedBarsController.previewBarsChartRenderer.setup(verticalRange: zoomedBarsController.currentPreviewVerticalRange, animated: animated, timeFunction: .easeOut)
} else {
if !zoomedBarsController.chartsCollection.isBlank {
barsController.hideDetailsView(animated: false)
barsController.chartVisibility = zoomedBarsController.chartVisibility
let visibleVerticalRange = BarChartRenderer.BarsData.verticalRange(bars: barsController.visibleBars,
separate: true,
calculatingRange: barsController.initialHorizontalRange) ?? BaseConstants.defaultRange
barsController.mainBarsRenderer.setup(verticalRange: visibleVerticalRange, animated: false)
let toHorizontalRange = barsController.initialHorizontalRange
// let destinationChartVerticalRange = lineProportionAnimationRange()
let verticalVisibleRange = barsController.initialVerticalRange
let targetVerticalRange = verticalVisibleRange.lowerBound...(verticalVisibleRange.upperBound + verticalVisibleRange.distance * 10)
zoomedBarsController.setupMainChart(horizontalRange: toHorizontalRange, animated: animated)
// zoomedBarsController.mainLinesRenderer.setup(verticalRange: destinationChartVerticalRange, animated: animated)
// zoomedBarsController.previewLinesChartRenderer.setup(verticalRange: destinationChartVerticalRange, animated: animated)
zoomedBarsController.mainBarsRenderer.setup(verticalRange: targetVerticalRange, animated: animated, timeFunction: .easeIn)
zoomedBarsController.previewBarsChartRenderer.setup(verticalRange: targetVerticalRange, animated: animated, timeFunction: .easeIn)
zoomedBarsController.previewBarsChartRenderer.setup(horizontalRange: barsController.totalHorizontalRange, animated: animated)
zoomedBarsController.mainBarsRenderer.setVisible(false, animated: animated)
zoomedBarsController.previewBarsChartRenderer.setVisible(false, animated: animated)
DispatchQueue.main.asyncAfter(deadline: .now() + .defaultDuration) { [weak self] in
self?.zoomedBarsController.mainBarsRenderer.setVisible(false, animated: false)
self?.zoomedBarsController.previewBarsChartRenderer.setVisible(false, animated: false)
}
}
barsController.willAppear(animated: animated)
zoomedBarsController.willDisappear(animated: animated)
if !zoomedBarsController.chartsCollection.isBlank {
barsController.updateChartsVisibility(visibility: zoomedBarsController.chartVisibility, animated: false)
}
}
self.setBackButtonVisibilityClosure?(isZoomed, animated)
self.refreshChartToolsClosure?(animated)
}
public override func updateChartsVisibility(visibility: [Bool], animated: Bool) {
@@ -166,13 +186,12 @@ public class StepBarsChartController: BaseChartController {
public override var actualChartsCollection: ChartsCollection {
let collection = isZoomed ? zoomedBarsController.chartsCollection : barsController.chartsCollection
if collection.isBlank {
return self.initialChartsCollection
}
return collection
}
public override func chartInteractionDidBegin(point: CGPoint) {
if isZoomed {
zoomedBarsController.chartInteractionDidBegin(point: point)
@@ -189,6 +208,10 @@ public class StepBarsChartController: BaseChartController {
}
}
public override var drawChartVisibity: Bool {
return true
}
public override var currentChartHorizontalRangeFraction: ClosedRange<CGFloat> {
if isZoomed {
return zoomedBarsController.currentChartHorizontalRangeFraction
@@ -221,15 +244,6 @@ public class StepBarsChartController: BaseChartController {
})
}
// func lineProportionAnimationRange() -> ClosedRange<CGFloat> {
// let visibleLines = self.barsController.chartVisibility.enumerated().compactMap { $0.element ? self.zoomedBarsController.chartLines[$0.offset] : nil }
// let linesRange = LinesChartRenderer.LineData.verticalRange(lines: visibleLines) ?? BaseConstants.defaultRange
// let barsRange = BarChartRenderer.BarsData.verticalRange(bars: self.barsController.visibleBars,
// calculatingRange: self.zoomedBarsController.totalHorizontalRange) ?? BaseConstants.defaultRange
// let range = 0...(linesRange.upperBound / barsRange.distance * self.barsController.currentVerticalMainChartRange.distance)
// return range
// }
public override func didTapZoomOut() {
cancelChartInteraction()
switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: true)
@@ -243,17 +257,10 @@ public class StepBarsChartController: BaseChartController {
}
}
override public func apply(theme: ChartTheme, animated: Bool) {
public override func apply(theme: ChartTheme, animated: Bool) {
super.apply(theme: theme, animated: animated)
zoomedBarsController.apply(theme: theme, animated: animated)
barsController.apply(theme: theme, animated: animated)
}
public override var drawChartVisibity: Bool {
return true
}
}
//TODO: Убрать Performance полоски сверзу чартов (Не забыть)
//TODO: Добавить ховеры на кнопки

View File

@@ -1,383 +0,0 @@
import Foundation
#if os(macOS)
import Cocoa
#else
import UIKit
#endif
public class StepBarsChartController2: BaseChartController {
class GraphController {
let mainBarsRenderer: BarChartRenderer
let verticalScalesRenderer = VerticalScalesRenderer()
let lineBulletsRenderer = LineBulletsRenderer()
let previewBarsRenderer: BarChartRenderer
var chartBars: BarChartRenderer.BarsData = .blank
var barsWidth: CGFloat = 1
var totalVerticalRange: ClosedRange<CGFloat> = BaseConstants.defaultRange
init(isZoomed: Bool,
mainBarsRenderer: BarChartRenderer,
previewBarsRenderer: BarChartRenderer) {
self.mainBarsRenderer = mainBarsRenderer
self.previewBarsRenderer = previewBarsRenderer
self.mainBarsRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel
self.previewBarsRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel
}
}
private var graphControllers: [GraphController] = []
private let horizontalScalesRenderer = HorizontalScalesRenderer()
private let verticalLineRenderer = VerticalLinesRenderer()
var chartVisibility: [Bool] = []
var zoomChartVisibility: [Bool] = []
private let initialChartCollection: ChartsCollection
var initialChartRange: ClosedRange<CGFloat> = BaseConstants.defaultRange
var zoomedChartRange: ClosedRange<CGFloat> = BaseConstants.defaultRange
var totalHorizontalRange: ClosedRange<CGFloat> = BaseConstants.defaultRange
var lastChartInteractionPoint: CGPoint = .zero
var isChartInteractionBegun: Bool = false
override public init(chartsCollection: ChartsCollection) {
self.initialChartCollection = chartsCollection
self.graphControllers = chartsCollection.chartValues.map { _ in GraphController(isZoomed: false, mainBarsRenderer: BarChartRenderer(step: true), previewBarsRenderer: BarChartRenderer(step: true))
}
super.init(chartsCollection: chartsCollection)
self.chartVisibility = Array(repeating: true, count: chartsCollection.chartValues.count)
self.zoomChartVisibility = self.chartVisibility
// self.graphControllers.map({ $0.barsController }).forEach { controller in
// controller.chartFrame = { [unowned self] in self.chartFrame() }
// controller.cartViewBounds = { [unowned self] in self.cartViewBounds() }
// controller.zoomInOnDateClosure = { [unowned self] date in
// self.didTapZoomIn(date: date)
// }
// controller.setChartTitleClosure = { [unowned self] (title, animated) in
// self.setChartTitleClosure?(title, animated)
// }
// controller.setDetailsViewPositionClosure = { [unowned self] (position) in
// self.setDetailsViewPositionClosure?(position)
// }
// controller.setDetailsChartVisibleClosure = { [unowned self] (visible, animated) in
// self.setDetailsChartVisibleClosure?(visible, animated)
// }
// controller.setDetailsViewModel = { [unowned self] (viewModel, animated) in
// self.setDetailsViewModel?(viewModel, animated)
// }
// controller.updatePreviewRangeClosure = { [unowned self] (fraction, animated) in
// self.chartRangeUpdatedClosure?(fraction, animated)
// }
// controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in
// self.setChartRangePagingEnabled(isEnabled: isEnabled, minimumSelectionSize: pageSize)
// }
// }
}
public override var mainChartRenderers: [ChartViewRenderer] {
var renderers: [ChartViewRenderer] = []
self.graphControllers.forEach { controller in
renderers.append(controller.mainBarsRenderer)
}
renderers.append(self.horizontalScalesRenderer)
self.graphControllers.forEach { controller in
renderers.append(controller.verticalScalesRenderer)
renderers.append(controller.lineBulletsRenderer)
}
renderers.append(self.verticalLineRenderer)
return renderers
}
public override var navigationRenderers: [ChartViewRenderer] {
return graphControllers.map { $0.previewBarsRenderer }
}
public override func initializeChart() {
if let first = initialChartCollection.axisValues.first?.timeIntervalSince1970,
let last = initialChartCollection.axisValues.last?.timeIntervalSince1970 {
initialChartRange = CGFloat(max(first, last - BaseConstants.defaultRangePresetLength))...CGFloat(last)
}
setupChartCollection(chartsCollection: initialChartCollection, animated: false, isZoomed: false)
}
public override func updateChartsVisibility(visibility: [Bool], animated: Bool) {
self.chartVisibility = visibility
self.zoomChartVisibility = visibility
let firstIndex = visibility.firstIndex(where: { $0 })
for (index, isVisible) in visibility.enumerated() {
let graph = graphControllers[index]
for graphIndex in graph.chartBars.components.indices {
graph.mainBarsRenderer.setComponentVisible(isVisible, at: graphIndex, animated: animated)
graph.previewBarsRenderer.setComponentVisible(isVisible, at: graphIndex, animated: animated)
graph.lineBulletsRenderer.setLineVisible(isVisible, at: graphIndex, animated: animated)
}
graph.verticalScalesRenderer.setVisible(isVisible, animated: animated)
if let firstIndex = firstIndex {
graph.verticalScalesRenderer.setHorizontalLinesVisible(index == firstIndex, animated: animated)
}
}
// updateVerticalLimitsAndRange(horizontalRange: currentHorizontalRange, animated: true)
if isChartInteractionBegun {
chartInteractionDidBegin(point: lastChartInteractionPoint)
}
}
private func findClosestDateTo(dateToFind: Date) -> (Date, Int)? {
guard self.initialChartCollection.axisValues.count > 0 else { return nil }
var closestDate = self.initialChartCollection.axisValues[0]
var minIndex = 0
for (index, date) in self.initialChartCollection.axisValues.enumerated() {
if abs(dateToFind.timeIntervalSince(date)) < abs(dateToFind.timeIntervalSince(closestDate)) {
closestDate = date
minIndex = index
}
}
return (closestDate, minIndex)
}
public override func chartInteractionDidBegin(point: CGPoint) {
let horizontalRange = currentHorizontalRange
let chartFrame = self.chartFrame()
guard chartFrame.width > 0 else { return }
let dateToFind = Date(timeIntervalSince1970: TimeInterval(horizontalRange.distance * point.x + horizontalRange.lowerBound))
guard let (closestDate, minIndex) = findClosestDateTo(dateToFind: dateToFind) else { return }
let chartInteractionWasBegin = isChartInteractionBegun
super.chartInteractionDidBegin(point: point)
// for graphController in graphControllers {
// graphController.lineBulletsRenderer.bullets = graphController.chartBars.components.map { component in
// LineBulletsRenderer.Bullet(coordinate: component.values[minIndex], color: component.color)
// }
// graphController.lineBulletsRenderer.isEnabled = true
// }
let chartValue: CGFloat = CGFloat(closestDate.timeIntervalSince1970)
let detailsViewPosition = (chartValue - horizontalRange.lowerBound) / horizontalRange.distance * chartFrame.width + chartFrame.minX
self.setDetailsViewModel?(chartDetailsViewModel(closestDate: closestDate, pointIndex: minIndex), chartInteractionWasBegin)
self.setDetailsChartVisibleClosure?(true, true)
self.setDetailsViewPositionClosure?(detailsViewPosition)
self.verticalLineRenderer.values = [chartValue]
}
// func chartDetailsViewModel(closestDate: Date, pointIndex: Int) -> ChartDetailsViewModel {
// var viewModel = super.chartDetailsViewModel(closestDate: closestDate, pointIndex: pointIndex)
// let visibleChartValues = self.visibleChartValues
// let totalSumm: CGFloat = visibleChartValues.map { CGFloat($0.values[pointIndex]) }.reduce(0, +)
//
// viewModel.totalValue = ChartDetailsViewModel.Value(prefix: nil,
// title: "Total",
// value: BaseConstants.detailsNumberFormatter.string(from: totalSumm),
// color: .white,
// visible: visibleChartValues.count > 1)
// return viewModel
// }
//
func chartDetailsViewModel(closestDate: Date, pointIndex: Int) -> ChartDetailsViewModel {
let values: [ChartDetailsViewModel.Value] = initialChartCollection.chartValues.enumerated().map { arg in
let (index, component) = arg
return ChartDetailsViewModel.Value(prefix: nil,
title: component.name,
value: BaseConstants.detailsNumberFormatter.string(from: NSNumber(value: component.values[pointIndex])) ?? "",
color: component.color,
visible: chartVisibility[index])
}
let dateString: String
if isZoomed {
dateString = BaseConstants.timeDateFormatter.string(from: closestDate)
} else {
dateString = BaseConstants.headerMediumRangeFormatter.string(from: closestDate)
}
let viewModel = ChartDetailsViewModel(title: dateString,
showArrow: self.isZoomable && !self.isZoomed,
showPrefixes: false,
values: values,
totalValue: nil,
tapAction: { [weak self] in },
hideAction: { [weak self] in
self?.setDetailsChartVisibleClosure?(false, true)
})
return viewModel
}
public override func chartInteractionDidEnd() {
self.isChartInteractionBegun = false
}
public override var currentHorizontalRange: ClosedRange<CGFloat> {
return graphControllers.first?.mainBarsRenderer.horizontalRange.end ?? BaseConstants.defaultRange
}
public override var currentChartHorizontalRangeFraction: ClosedRange<CGFloat> {
let lowerPercent = (currentHorizontalRange.lowerBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance
let upperPercent = (currentHorizontalRange.upperBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance
return lowerPercent...upperPercent
}
public override func cancelChartInteraction() {
super.cancelChartInteraction()
self.graphControllers.forEach { controller in
controller.lineBulletsRenderer.isEnabled = false
}
self.setDetailsChartVisibleClosure?(false, true)
self.verticalLineRenderer.values = []
}
func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) {
for (index, controller) in self.graphControllers.enumerated() {
let chart = chartsCollection.chartValues[index]
let points = chart.values.enumerated().map({ (arg) -> CGPoint in
return CGPoint(x: chartsCollection.axisValues[arg.offset].timeIntervalSince1970,
y: arg.element)
})
let (width, chartBars, totalHorizontalRange, totalVerticalRange) = BarChartRenderer.BarsData.initialComponents(chartsCollection: chartsCollection)
controller.chartBars = chartBars
controller.barsWidth = width
controller.verticalScalesRenderer.labelsColor = chart.color
controller.totalVerticalRange = totalVerticalRange
self.totalHorizontalRange = totalHorizontalRange
// controller.lineBulletsRenderer.bullets = chartBars.components.map { LineBulletsRenderer.Bullet(coordinate: $0.values.first ?? .zero,
// color: $0.color) }
controller.previewBarsRenderer.setup(horizontalRange: self.totalHorizontalRange, animated: animated)
controller.previewBarsRenderer.setup(verticalRange: controller.totalVerticalRange, animated: animated)
controller.mainBarsRenderer.bars = chartBars
controller.previewBarsRenderer.bars = chartBars
controller.verticalScalesRenderer.setHorizontalLinesVisible((index == 0), animated: animated)
controller.verticalScalesRenderer.isRightAligned = (index != 0)
}
let chartRange: ClosedRange<CGFloat>
if isZoomed {
chartRange = zoomedChartRange
} else {
chartRange = initialChartRange
}
// updateHorizontalLimits(horizontalRange: chartRange, animated: animated)
updateMainChartHorizontalRange(range: chartRange, animated: animated)
updateMainChartVerticalRange(range: chartRange, animated: animated)
// updateVerticalLimitsAndRange(horizontalRange: chartRange, animated: animated)
self.chartRangeUpdatedClosure?(currentChartHorizontalRangeFraction, animated)
}
// func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) {
// if animated {
// TimeInterval.setDefaultSuration(.expandAnimationDuration)
// DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) {
// TimeInterval.setDefaultSuration(.osXDuration)
// }
// }
//
// self.initialChartsCollection = chartsCollection
// self.isZoomed = isZoomed
//
// self.setBackButtonVisibilityClosure?(isZoomed, animated)
//
// self.graphControllers.forEach { controller in
// controller.barsController.willAppear(animated: animated)
// }
//
// self.refreshChartToolsClosure?(animated)
// }
public override func didTapZoomIn(date: Date) {
guard isZoomed == false else { return }
cancelChartInteraction()
self.getDetailsData?(date, { updatedCollection in
if let updatedCollection = updatedCollection {
self.initialChartRange = self.currentHorizontalRange
if let startDate = updatedCollection.axisValues.first,
let endDate = updatedCollection.axisValues.last {
self.zoomedChartRange = CGFloat(max(date.timeIntervalSince1970, startDate.timeIntervalSince1970))...CGFloat(min(date.timeIntervalSince1970 + .day - .hour, endDate.timeIntervalSince1970))
} else {
self.zoomedChartRange = CGFloat(date.timeIntervalSince1970)...CGFloat(date.timeIntervalSince1970 + .day - 1)
}
self.setupChartCollection(chartsCollection: updatedCollection, animated: true, isZoomed: true)
}
})
}
public override func didTapZoomOut() {
cancelChartInteraction()
self.setupChartCollection(chartsCollection: self.initialChartCollection, animated: true, isZoomed: false)
}
public override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool) {
cancelChartInteraction()
let horizontalRange = ClosedRange(uncheckedBounds:
(lower: totalHorizontalRange.lowerBound + rangeFraction.lowerBound * totalHorizontalRange.distance,
upper: totalHorizontalRange.lowerBound + rangeFraction.upperBound * totalHorizontalRange.distance))
zoomedChartRange = horizontalRange
// updateChartRangeTitle(animated: true)
updateMainChartHorizontalRange(range: horizontalRange, animated: false)
// updateHorizontalLimits(horizontalRange: horizontalRange, animated: true)
// updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true)
// barsController.chartRangeFractionDidUpdated(rangeFraction)
//
// let totalHorizontalRange = barsController.totalHorizontalRange
// let horizontalRange = ClosedRange(uncheckedBounds:
// (lower: totalHorizontalRange.lowerBound + rangeFraction.lowerBound * totalHorizontalRange.distance,
// upper: totalHorizontalRange.lowerBound + rangeFraction.upperBound * totalHorizontalRange.distance))
//
// updateMainChartHorizontalRange(range: horizontalRange, animated: false)
}
func updateMainChartHorizontalRange(range: ClosedRange<CGFloat>, animated: Bool) {
self.graphControllers.forEach { controller in
controller.mainBarsRenderer.setup(horizontalRange: range, animated: animated)
// controller.horizontalScalesRenderer.setup(horizontalRange: range, animated: animated)
controller.verticalScalesRenderer.setup(horizontalRange: range, animated: animated)
controller.lineBulletsRenderer.setup(horizontalRange: range, animated: animated)
}
self.horizontalScalesRenderer.setup(horizontalRange: range, animated: animated)
self.verticalLineRenderer.setup(horizontalRange: range, animated: animated)
}
func updateMainChartVerticalRange(range: ClosedRange<CGFloat>, animated: Bool) {
self.verticalLineRenderer.setup(verticalRange: range, animated: animated)
self.graphControllers.forEach { controller in
controller.lineBulletsRenderer.setup(verticalRange: range, animated: animated)
}
}
override public func apply(theme: ChartTheme, animated: Bool) {
super.apply(theme: theme, animated: animated)
self.graphControllers.forEach { controller in
controller.verticalScalesRenderer.horizontalLinesColor = theme.chartHelperLinesColor
controller.lineBulletsRenderer.setInnerColor(theme.chartBackgroundColor, animated: animated)
controller.verticalScalesRenderer.axisXColor = theme.chartStrongLinesColor
}
verticalLineRenderer.linesColor = theme.chartStrongLinesColor
}
public override var drawChartVisibity: Bool {
return true
}
}
//TODO: Убрать Performance полоски сверзу чартов (Не забыть)
//TODO: Добавить ховеры на кнопки

View File

@@ -0,0 +1,309 @@
import Foundation
#if os(macOS)
import Cocoa
#else
import UIKit
#endif
private enum Constants {
static let verticalBaseAnchors: [CGFloat] = [8, 5, 4, 2.5, 2, 1]
}
public class TwoAxisStepBarsChartController: BaseLinesChartController {
class GraphController {
let mainBarsRenderer = BarChartRenderer(step: true)
let verticalScalesRenderer = VerticalScalesRenderer()
let lineBulletsRenderer = LineBulletsRenderer()
let previewBarsRenderer = BarChartRenderer(step: true, lineWidth: 1.0)
var chartBars: BarChartRenderer.BarsData = .blank
var barsWidth: CGFloat = 1
var totalVerticalRange: ClosedRange<CGFloat> = BaseConstants.defaultRange
init() {
self.lineBulletsRenderer.isEnabled = false
self.mainBarsRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel
self.previewBarsRenderer.optimizationLevel = BaseConstants.barsChartOptimizationLevel
}
func updateMainChartVerticalRange(range: ClosedRange<CGFloat>, animated: Bool) {
mainBarsRenderer.setup(verticalRange: range, animated: animated)
verticalScalesRenderer.setup(verticalRange: range, animated: animated)
lineBulletsRenderer.setup(verticalRange: range, animated: animated)
}
}
private var graphControllers: [GraphController] = []
private let verticalLineRenderer = VerticalLinesRenderer()
private let horizontalScalesRenderer = HorizontalScalesRenderer()
var totalHorizontalRange: ClosedRange<CGFloat> = BaseConstants.defaultRange
private let initialChartCollection: ChartsCollection
private var prevoiusHorizontalStrideInterval: Int = 1
override public init(chartsCollection: ChartsCollection) {
self.initialChartCollection = chartsCollection
graphControllers = chartsCollection.chartValues.map { _ in GraphController() }
super.init(chartsCollection: chartsCollection)
self.zoomChartVisibility = chartVisibility
}
override func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) {
super.setupChartCollection(chartsCollection: chartsCollection, animated: animated, isZoomed: isZoomed)
for (index, controller) in self.graphControllers.enumerated() {
let chart = chartsCollection.chartValues[index]
let initialComponents = [BarChartRenderer.BarsData.Component(color: chart.color,
values: chart.values.map { CGFloat($0) })]
let (width, chartBars, totalHorizontalRange, totalVerticalRange) = BarChartRenderer.BarsData.initialComponents(chartsCollection: chartsCollection, separate: true, initialComponents: initialComponents)
controller.chartBars = chartBars
controller.verticalScalesRenderer.labelsColor = chart.color
controller.barsWidth = width
controller.totalVerticalRange = totalVerticalRange
self.totalHorizontalRange = totalHorizontalRange
var bullets: [LineBulletsRenderer.Bullet] = []
if let component = chartBars.components.first {
for i in 0 ..< chartBars.locations.count {
let location = chartBars.locations[i]
let value = component.values[i]
bullets.append(LineBulletsRenderer.Bullet(coordinate: CGPoint(x: location, y: value), color: component.color))
}
}
controller.lineBulletsRenderer.bullets = bullets
controller.previewBarsRenderer.setup(horizontalRange: self.totalHorizontalRange, animated: animated)
controller.previewBarsRenderer.setup(verticalRange: controller.totalVerticalRange, animated: animated)
controller.mainBarsRenderer.bars = chartBars
controller.previewBarsRenderer.bars = chartBars
controller.verticalScalesRenderer.setHorizontalLinesVisible((index == 0), animated: animated)
controller.verticalScalesRenderer.isRightAligned = (index != 0)
}
self.prevoiusHorizontalStrideInterval = -1
let chartRange: ClosedRange<CGFloat>
if isZoomed {
chartRange = zoomedChartRange
} else {
chartRange = initialChartRange
}
updateHorizontalLimits(horizontalRange: chartRange, animated: animated)
updateMainChartHorizontalRange(range: chartRange, animated: animated)
updateVerticalLimitsAndRange(horizontalRange: chartRange, animated: animated)
self.chartRangeUpdatedClosure?(currentChartHorizontalRangeFraction, animated)
}
public override func initializeChart() {
if let first = initialChartCollection.axisValues.first?.timeIntervalSince1970,
let last = initialChartCollection.axisValues.last?.timeIntervalSince1970 {
initialChartRange = CGFloat(max(first, last - BaseConstants.defaultRangePresetLength))...CGFloat(last)
}
setupChartCollection(chartsCollection: initialChartCollection, animated: false, isZoomed: false)
}
public override var mainChartRenderers: [ChartViewRenderer] {
return graphControllers.map { $0.mainBarsRenderer } +
graphControllers.flatMap { [$0.verticalScalesRenderer, $0.lineBulletsRenderer] } +
[horizontalScalesRenderer, verticalLineRenderer,
// performanceRenderer
]
}
public override var navigationRenderers: [ChartViewRenderer] {
return graphControllers.map { $0.previewBarsRenderer }
}
public override func updateChartsVisibility(visibility: [Bool], animated: Bool) {
chartVisibility = visibility
zoomChartVisibility = visibility
let firstIndex = visibility.firstIndex(where: { $0 })
for (index, isVisible) in visibility.enumerated() {
let graph = graphControllers[index]
graph.mainBarsRenderer.setVisible(isVisible, animated: animated)
graph.previewBarsRenderer.setVisible(isVisible, animated: animated)
graph.lineBulletsRenderer.setLineVisible(isVisible, at: 0, animated: animated)
graph.verticalScalesRenderer.setVisible(isVisible, animated: animated)
if let firstIndex = firstIndex {
graph.verticalScalesRenderer.setHorizontalLinesVisible(index == firstIndex, animated: animated)
}
}
updateVerticalLimitsAndRange(horizontalRange: currentHorizontalRange, animated: true)
if isChartInteractionBegun {
chartInteractionDidBegin(point: lastChartInteractionPoint)
}
}
public override func chartInteractionDidBegin(point: CGPoint) {
let horizontalRange = currentHorizontalRange
let chartFrame = self.chartFrame()
guard chartFrame.width > 0 else { return }
let dateToFind = Date(timeIntervalSince1970: TimeInterval(horizontalRange.distance * point.x + horizontalRange.lowerBound))
guard let (closestDate, minIndex) = findClosestDateTo(dateToFind: dateToFind) else { return }
let chartInteractionWasBegin = isChartInteractionBegun
super.chartInteractionDidBegin(point: point)
for graphController in graphControllers {
var bullets: [LineBulletsRenderer.Bullet] = []
if let component = graphController.chartBars.components.first {
let location = graphController.chartBars.locations[minIndex]
let value = component.values[minIndex]
bullets.append(LineBulletsRenderer.Bullet(coordinate: CGPoint(x: location, y: value), color: component.color))
}
graphController.lineBulletsRenderer.bullets = bullets
graphController.lineBulletsRenderer.isEnabled = true
}
let chartValue: CGFloat = CGFloat(closestDate.timeIntervalSince1970)
let detailsViewPosition = (chartValue - horizontalRange.lowerBound) / horizontalRange.distance * chartFrame.width + chartFrame.minX
self.setDetailsViewModel?(chartDetailsViewModel(closestDate: closestDate, pointIndex: minIndex), chartInteractionWasBegin)
self.setDetailsChartVisibleClosure?(true, true)
self.setDetailsViewPositionClosure?(detailsViewPosition)
self.verticalLineRenderer.values = [chartValue]
}
public override var currentChartHorizontalRangeFraction: ClosedRange<CGFloat> {
let lowerPercent = (currentHorizontalRange.lowerBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance
let upperPercent = (currentHorizontalRange.upperBound - totalHorizontalRange.lowerBound) / totalHorizontalRange.distance
return lowerPercent...upperPercent
}
public override var currentHorizontalRange: ClosedRange<CGFloat> {
return graphControllers.first?.mainBarsRenderer.horizontalRange.end ?? BaseConstants.defaultRange
}
public override func cancelChartInteraction() {
super.cancelChartInteraction()
for graphController in graphControllers {
graphController.lineBulletsRenderer.isEnabled = false
}
self.setDetailsChartVisibleClosure?(false, true)
self.verticalLineRenderer.values = []
}
public override func didTapZoomOut() {
cancelChartInteraction()
self.setupChartCollection(chartsCollection: initialChartCollection, animated: true, isZoomed: false)
}
public override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool = true) {
cancelChartInteraction()
let horizontalRange = ClosedRange(uncheckedBounds:
(lower: totalHorizontalRange.lowerBound + rangeFraction.lowerBound * totalHorizontalRange.distance,
upper: totalHorizontalRange.lowerBound + rangeFraction.upperBound * totalHorizontalRange.distance))
zoomedChartRange = horizontalRange
updateChartRangeTitle(animated: true)
updateMainChartHorizontalRange(range: horizontalRange, animated: false)
updateHorizontalLimits(horizontalRange: horizontalRange, animated: true)
updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true)
}
func updateMainChartHorizontalRange(range: ClosedRange<CGFloat>, animated: Bool) {
for controller in graphControllers {
controller.mainBarsRenderer.setup(horizontalRange: range, animated: animated)
controller.verticalScalesRenderer.setup(horizontalRange: range, animated: animated)
controller.lineBulletsRenderer.setup(horizontalRange: range, animated: animated)
}
horizontalScalesRenderer.setup(horizontalRange: range, animated: animated)
verticalLineRenderer.setup(horizontalRange: range, animated: animated)
}
func updateHorizontalLimits(horizontalRange: ClosedRange<CGFloat>, animated: Bool) {
if let (stride, labels) = horizontalLimitsLabels(horizontalRange: horizontalRange,
scaleType: isZoomed ? .minutes5 : .day,
prevoiusHorizontalStrideInterval: prevoiusHorizontalStrideInterval) {
self.horizontalScalesRenderer.setup(labels: labels, animated: animated)
self.prevoiusHorizontalStrideInterval = stride
}
}
func updateVerticalLimitsAndRange(horizontalRange: ClosedRange<CGFloat>, animated: Bool) {
let chartHeight = chartFrame().height
let approximateNumberOfChartValues = (chartHeight / BaseConstants.minimumAxisYLabelsDistance)
let dividorsAndMultiplers: [(startValue: CGFloat, base: CGFloat, count: Int, maximumNumberOfDecimals: Int)] = graphControllers.enumerated().map { arg in
let (index, controller) = arg
let verticalRange = BarChartRenderer.BarsData.verticalRange(bars: controller.chartBars, separate: true, calculatingRange: horizontalRange, addBounds: true) ?? controller.totalVerticalRange
var numberOfOffsetsPerItem = verticalRange.distance / approximateNumberOfChartValues
var multiplier: CGFloat = 1.0
while numberOfOffsetsPerItem > 10 {
numberOfOffsetsPerItem /= 10
multiplier *= 10
}
var dividor: CGFloat = 1.0
var maximumNumberOfDecimals = 2
while numberOfOffsetsPerItem < 1 {
numberOfOffsetsPerItem *= 10
dividor *= 10
maximumNumberOfDecimals += 1
}
let generalBase = Constants.verticalBaseAnchors.first { numberOfOffsetsPerItem > $0 } ?? BaseConstants.defaultVerticalBaseAnchor
let base = generalBase * multiplier / dividor
var verticalValue = (verticalRange.lowerBound / base).rounded(.down) * base
let startValue = verticalValue
var count = 0
if chartVisibility[index] {
while verticalValue < verticalRange.upperBound {
count += 1
verticalValue += base
}
}
return (startValue: startValue, base: base, count: count, maximumNumberOfDecimals: maximumNumberOfDecimals)
}
let totalCount = dividorsAndMultiplers.map { $0.count }.max() ?? 0
guard totalCount > 0 else { return }
let numberFormatter = BaseConstants.chartNumberFormatter
for (index, controller) in graphControllers.enumerated() {
let (startValue, base, _, maximumNumberOfDecimals) = dividorsAndMultiplers[index]
let updatedRange = startValue...(startValue + base * CGFloat(totalCount))
if controller.verticalScalesRenderer.verticalRange.end != updatedRange {
numberFormatter.maximumFractionDigits = maximumNumberOfDecimals
var verticalLabels: [LinesChartLabel] = []
for multipler in 0...(totalCount - 1) {
let verticalValue = startValue + base * CGFloat(multipler)
let text: String = numberFormatter.string(from: NSNumber(value: Double(verticalValue))) ?? ""
verticalLabels.append(LinesChartLabel(value: verticalValue, text: text))
}
controller.verticalScalesRenderer.setup(verticalLimitsLabels: verticalLabels, animated: animated)
controller.updateMainChartVerticalRange(range: updatedRange, animated: animated)
}
}
}
public override func apply(theme: ChartTheme, animated: Bool) {
horizontalScalesRenderer.labelsColor = theme.chartLabelsColor
verticalLineRenderer.linesColor = theme.chartStrongLinesColor
for controller in graphControllers {
controller.verticalScalesRenderer.horizontalLinesColor = theme.chartHelperLinesColor
controller.lineBulletsRenderer.setInnerColor(theme.chartBackgroundColor, animated: animated)
controller.verticalScalesRenderer.axisXColor = theme.chartStrongLinesColor
}
}
}