mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-03 19:33:49 +00:00
Organic Maps sources as of 02.04.2025 (fad26bbf22ac3da75e01e62aa01e5c8e11861005)
To expand with full Organic Maps and Maps.ME commits history run: git remote add om-historic [om-historic.git repo url] git fetch --tags om-historic git replace squashed-history historic-commits
This commit is contained in:
50
iphone/Chart/Chart/ChartData/ChartData.swift
Normal file
50
iphone/Chart/Chart/ChartData/ChartData.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
import UIKit
|
||||
|
||||
public enum ChartType {
|
||||
case regular
|
||||
case percentage
|
||||
}
|
||||
|
||||
public enum ChartLineType: String {
|
||||
case line
|
||||
case lineArea
|
||||
}
|
||||
|
||||
public protocol ChartFormatter {
|
||||
func xAxisString(from value: Double) -> String
|
||||
func yAxisString(from value: Double) -> String
|
||||
func yAxisLowerBound(from value: CGFloat) -> CGFloat
|
||||
func yAxisUpperBound(from value: CGFloat) -> CGFloat
|
||||
func yAxisSteps(lowerBound: CGFloat, upperBound: CGFloat) -> [CGFloat]
|
||||
}
|
||||
|
||||
public protocol ChartData {
|
||||
var xAxisValues: [Double] { get }
|
||||
var lines: [ChartLine] { get }
|
||||
var type: ChartType { get }
|
||||
}
|
||||
|
||||
public protocol ChartLine {
|
||||
var values: [ChartValue] { get }
|
||||
var color: UIColor { get }
|
||||
var type: ChartLineType { get }
|
||||
}
|
||||
|
||||
public struct ChartValue {
|
||||
let x: CGFloat
|
||||
let y: CGFloat
|
||||
|
||||
public init(xValues: CGFloat, y: CGFloat) {
|
||||
self.x = xValues
|
||||
self.y = y
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == ChartValue {
|
||||
var maxDistance: CGFloat { return map { $0.x }.max() ?? 0 }
|
||||
|
||||
func altitude(at relativeDistance: CGFloat) -> CGFloat {
|
||||
guard let distance = last?.x else { return 0 }
|
||||
return first { $0.x >= distance * relativeDistance }?.y ?? 0
|
||||
}
|
||||
}
|
||||
131
iphone/Chart/Chart/ChartData/ChartPathBuilder.swift
Normal file
131
iphone/Chart/Chart/ChartData/ChartPathBuilder.swift
Normal file
@@ -0,0 +1,131 @@
|
||||
import UIKit
|
||||
|
||||
protocol ChartPathBuilder {
|
||||
func build(_ lines: [ChartPresentationLine])
|
||||
func makeLinePath(line: ChartPresentationLine) -> UIBezierPath
|
||||
func makePercentLinePath(line: ChartPresentationLine, bottomLine: ChartPresentationLine?) -> UIBezierPath
|
||||
}
|
||||
|
||||
extension ChartPathBuilder {
|
||||
func makeLinePreviewPath(line: ChartPresentationLine) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
let values = line.values
|
||||
let xScale = CGFloat(values.count) / values.maxDistance
|
||||
for i in 0..<values.count {
|
||||
let x = values[i].x * xScale
|
||||
let y = values[i].y - line.minY
|
||||
if i == 0 {
|
||||
path.move(to: CGPoint(x: x, y: y))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func makeLinePath(line: ChartPresentationLine) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
let values = line.values
|
||||
let xScale = CGFloat(values.count) / values.maxDistance
|
||||
for i in 0..<values.count {
|
||||
let x = values[i].x * xScale
|
||||
let y = values[i].y - line.minY
|
||||
if i == 0 {
|
||||
path.move(to: CGPoint(x: x, y: y))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func makePercentLinePreviewPath(line: ChartPresentationLine, bottomLine: ChartPresentationLine?) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: 0, y: 0))
|
||||
let aggregatedValues = line.aggregatedValues
|
||||
let xScale = CGFloat(aggregatedValues.count) / aggregatedValues.maxDistance
|
||||
for i in 0..<aggregatedValues.count {
|
||||
let x = aggregatedValues[i].x * xScale
|
||||
let y = aggregatedValues[i].y - CGFloat(line.minY)
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
path.addLine(to: CGPoint(x: aggregatedValues.maxDistance, y: 0))
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
|
||||
func makePercentLinePath(line: ChartPresentationLine, bottomLine: ChartPresentationLine?) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: 0, y: 0))
|
||||
let aggregatedValues = line.aggregatedValues
|
||||
let xScale = CGFloat(aggregatedValues.count) / aggregatedValues.maxDistance
|
||||
for i in 0..<aggregatedValues.count {
|
||||
let x = aggregatedValues[i].x * xScale
|
||||
let y = aggregatedValues[i].y - CGFloat(line.minY)
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
path.addLine(to: CGPoint(x: aggregatedValues.maxDistance, y: 0))
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
final class DefaultChartPathBuilder {
|
||||
private let builders: [ChartType: ChartPathBuilder] = [
|
||||
.regular : LinePathBuilder(),
|
||||
.percentage : PercentagePathBuilder()
|
||||
]
|
||||
|
||||
func build(_ lines: [ChartPresentationLine], type: ChartType) {
|
||||
builders[type]?.build(lines)
|
||||
}
|
||||
}
|
||||
|
||||
final class LinePathBuilder: ChartPathBuilder {
|
||||
func build(_ lines: [ChartPresentationLine]) {
|
||||
lines.forEach {
|
||||
$0.aggregatedValues = $0.values
|
||||
if $0.type == .lineArea {
|
||||
$0.minY = 0
|
||||
for val in $0.values {
|
||||
$0.maxY = max(val.y, $0.maxY)
|
||||
}
|
||||
$0.path = makePercentLinePath(line: $0, bottomLine: nil)
|
||||
$0.previewPath = makePercentLinePreviewPath(line: $0, bottomLine: nil)
|
||||
} else {
|
||||
for val in $0.values {
|
||||
$0.minY = min(val.y, $0.minY)
|
||||
$0.maxY = max(val.y, $0.maxY)
|
||||
}
|
||||
$0.path = makeLinePath(line: $0)
|
||||
$0.previewPath = makeLinePreviewPath(line: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class PercentagePathBuilder: ChartPathBuilder {
|
||||
func build(_ lines: [ChartPresentationLine]) {
|
||||
lines.forEach {
|
||||
$0.minY = 0
|
||||
$0.maxY = CGFloat(Int.min)
|
||||
}
|
||||
|
||||
for i in 0..<lines[0].values.count {
|
||||
let sum = CGFloat(lines.reduce(0) { (r, l) in r + l.values[i].y })
|
||||
var aggrPercentage: CGFloat = 0
|
||||
lines.forEach {
|
||||
aggrPercentage += CGFloat($0.values[i].y) / sum * 100
|
||||
$0.aggregatedValues.append(ChartValue(xValues: lines[0].values[i].x, y: aggrPercentage))
|
||||
$0.maxY = max(round(aggrPercentage), CGFloat($0.maxY))
|
||||
}
|
||||
}
|
||||
|
||||
var prevLine: ChartPresentationLine? = nil
|
||||
lines.forEach {
|
||||
$0.path = makePercentLinePath(line: $0, bottomLine: prevLine)
|
||||
$0.previewPath = makePercentLinePreviewPath(line: $0, bottomLine: prevLine)
|
||||
prevLine = $0
|
||||
}
|
||||
}
|
||||
}
|
||||
56
iphone/Chart/Chart/ChartData/ChartPresentationData.swift
Normal file
56
iphone/Chart/Chart/ChartData/ChartPresentationData.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
import UIKit
|
||||
|
||||
public class ChartPresentationData {
|
||||
private let chartData: ChartData
|
||||
private var presentationLines: [ChartPresentationLine]
|
||||
private let pathBuilder = DefaultChartPathBuilder()
|
||||
|
||||
let formatter: ChartFormatter
|
||||
var linesCount: Int { chartData.lines.count }
|
||||
var pointsCount: Int { chartData.xAxisValues.count }
|
||||
var xAxisValues: [Double] { chartData.xAxisValues }
|
||||
var type: ChartType { chartData.type }
|
||||
var labels: [String]
|
||||
var lower = CGFloat(Int.max)
|
||||
var upper = CGFloat(Int.min)
|
||||
|
||||
public init(_ chartData: ChartData, formatter: ChartFormatter) {
|
||||
self.chartData = chartData
|
||||
self.formatter = formatter
|
||||
self.presentationLines = chartData.lines.map { ChartPresentationLine($0) }
|
||||
self.labels = chartData.xAxisValues.map { formatter.xAxisString(from: $0) }
|
||||
recalculateBounds()
|
||||
}
|
||||
|
||||
func labelAt(_ point: CGFloat) -> String {
|
||||
formatter.xAxisString(from: xAxisValueAt(point))
|
||||
}
|
||||
|
||||
func xAxisValueAt(_ point: CGFloat) -> Double {
|
||||
let distance = chartData.xAxisValues.last!
|
||||
let p1 = floor(point)
|
||||
let p2 = ceil(point)
|
||||
let v1 = p1 / CGFloat(pointsCount) * distance
|
||||
let v2 = p2 / CGFloat(pointsCount) * distance
|
||||
return v1 + (v2 - v1) * Double(point.truncatingRemainder(dividingBy: 1))
|
||||
}
|
||||
|
||||
func lineAt(_ index: Int) -> ChartPresentationLine {
|
||||
presentationLines[index]
|
||||
}
|
||||
|
||||
private func recalculateBounds() {
|
||||
presentationLines.forEach { $0.aggregatedValues = [] }
|
||||
pathBuilder.build(presentationLines, type: type)
|
||||
|
||||
var l = CGFloat(Int.max)
|
||||
var u = CGFloat(Int.min)
|
||||
presentationLines.forEach {
|
||||
l = min($0.minY, l)
|
||||
u = max($0.maxY, u)
|
||||
}
|
||||
lower = l
|
||||
upper = u
|
||||
}
|
||||
}
|
||||
|
||||
19
iphone/Chart/Chart/ChartData/ChartPresentationLine.swift
Normal file
19
iphone/Chart/Chart/ChartData/ChartPresentationLine.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
import UIKit
|
||||
|
||||
final class ChartPresentationLine {
|
||||
private let chartLine: ChartLine
|
||||
|
||||
var aggregatedValues: [ChartValue] = []
|
||||
var minY: CGFloat = CGFloat(Int.max)
|
||||
var maxY: CGFloat = CGFloat(Int.min)
|
||||
var path = UIBezierPath()
|
||||
var previewPath = UIBezierPath()
|
||||
|
||||
var values: [ChartValue] { chartLine.values }
|
||||
var color: UIColor { chartLine.color }
|
||||
var type: ChartLineType { chartLine.type }
|
||||
|
||||
init(_ chartLine: ChartLine) {
|
||||
self.chartLine = chartLine
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user