mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Charts improvements
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: Добавить ховеры на кнопки
|
||||
|
||||
@@ -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: Добавить ховеры на кнопки
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user