forked from zelo72/mastodon-ios
chore: update trends line chart style
This commit is contained in:
parent
68a5c6d4fd
commit
0f55d80e20
|
@ -348,6 +348,7 @@
|
|||
DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */; };
|
||||
DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */; };
|
||||
DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71C7CA271D5A0300BE3819 /* LineChartView.swift */; };
|
||||
DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */; };
|
||||
DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD2B25F86A5100512AE1 /* AvatarStackContainerButton.swift */; };
|
||||
DB71FD3625F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD3525F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift */; };
|
||||
DB71FD3C25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD3B25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift */; };
|
||||
|
@ -1143,6 +1144,7 @@
|
|||
DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewController.swift; sourceTree = "<group>"; };
|
||||
DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTopChevronView.swift; sourceTree = "<group>"; };
|
||||
DB71C7CA271D5A0300BE3819 /* LineChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartView.swift; sourceTree = "<group>"; };
|
||||
DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurveAlgorithm.swift; sourceTree = "<group>"; };
|
||||
DB71FD2B25F86A5100512AE1 /* AvatarStackContainerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarStackContainerButton.swift; sourceTree = "<group>"; };
|
||||
DB71FD3525F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistMemo.swift"; sourceTree = "<group>"; };
|
||||
DB71FD3B25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistCache.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1752,6 +1754,7 @@
|
|||
2D5A3D0125CF8640002347D6 /* Vender */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */,
|
||||
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */,
|
||||
DB51D170262832380062B7A1 /* BlurHashDecode.swift */,
|
||||
DB51D171262832380062B7A1 /* BlurHashEncode.swift */,
|
||||
|
@ -4234,6 +4237,7 @@
|
|||
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */,
|
||||
DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */,
|
||||
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */,
|
||||
DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */,
|
||||
DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */,
|
||||
DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */,
|
||||
DBAC6499267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift in Sources */,
|
||||
|
|
|
@ -66,61 +66,33 @@ extension LineChartView {
|
|||
gradientLayer.isHidden = false
|
||||
|
||||
// Draw smooth chart
|
||||
// use vDSP scale the data with line interpolation method
|
||||
var data = data.map { Float($0) }
|
||||
// duplicate first and last value to prevent interpolation at edge data
|
||||
data.insert(data[0], at: 0)
|
||||
if let last = data.last {
|
||||
data.append(last)
|
||||
}
|
||||
|
||||
let n = vDSP_Length(128)
|
||||
let stride = vDSP_Stride(1)
|
||||
|
||||
// generate fine control with smoothing (simd_smoothstep(_:_:_:))
|
||||
let denominator = Float(n) / Float(data.count - 1)
|
||||
let control: [Float] = (0...n).map {
|
||||
let x = Float($0) / denominator
|
||||
return floor(x) + simd_smoothstep(0, 1, simd_fract(x))
|
||||
}
|
||||
|
||||
var points = [Float](repeating: 0, count: Int(n))
|
||||
vDSP_vlint(data,
|
||||
control, stride,
|
||||
&points, stride,
|
||||
n,
|
||||
vDSP_Length(data.count))
|
||||
|
||||
guard let maxDataPoint = data.max() else {
|
||||
return
|
||||
}
|
||||
func calculateY(for point: Float, in frame: CGRect) -> CGFloat {
|
||||
func calculateY(for point: CGFloat, in frame: CGRect) -> CGFloat {
|
||||
guard maxDataPoint > 0 else { return .zero }
|
||||
return (1 - CGFloat(point / maxDataPoint)) * frame.height
|
||||
return (1 - point / maxDataPoint) * frame.height
|
||||
}
|
||||
|
||||
let segmentCount = points.count - 1
|
||||
let segmentCount = data.count - 1
|
||||
let segmentWidth = bounds.width / CGFloat(segmentCount)
|
||||
|
||||
let linePath = UIBezierPath()
|
||||
let points: [CGPoint] = {
|
||||
var points: [CGPoint] = []
|
||||
var x: CGFloat = 0
|
||||
for value in data {
|
||||
let point = CGPoint(x: x, y: calculateY(for: value, in: bounds))
|
||||
points.append(point)
|
||||
x += segmentWidth
|
||||
}
|
||||
return points
|
||||
}()
|
||||
|
||||
guard let linePath = CurveAlgorithm.shared.createCurvedPath(points) else { return }
|
||||
let dotPath = UIBezierPath()
|
||||
|
||||
// move to first data point
|
||||
var x: CGFloat = 0
|
||||
let y = calculateY(for: points[0], in: bounds)
|
||||
linePath.move(to: CGPoint(x: x, y: y))
|
||||
for point in points.dropFirst() {
|
||||
x += segmentWidth
|
||||
linePath.addLine(to: CGPoint(
|
||||
x: x,
|
||||
y: calculateY(for: point, in: bounds)
|
||||
))
|
||||
}
|
||||
|
||||
if let last = points.last {
|
||||
let y = calculateY(for: last, in: bounds)
|
||||
let center = CGPoint(x: bounds.maxX, y: y)
|
||||
dotPath.addArc(withCenter: center, radius: 3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
|
||||
dotPath.addArc(withCenter: last, radius: 3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
|
||||
}
|
||||
|
||||
// this not works
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// CurveAlgorithm.swift
|
||||
//
|
||||
// Ref: https://github.com/nhatminh12369/LineChart/blob/master/LineChart/CurveAlgorithm.swift
|
||||
|
||||
import UIKit
|
||||
|
||||
struct CurvedSegment {
|
||||
var controlPoint1: CGPoint
|
||||
var controlPoint2: CGPoint
|
||||
}
|
||||
|
||||
class CurveAlgorithm {
|
||||
static let shared = CurveAlgorithm()
|
||||
|
||||
private func controlPointsFrom(points: [CGPoint]) -> [CurvedSegment] {
|
||||
var result: [CurvedSegment] = []
|
||||
|
||||
let delta: CGFloat = 0.4
|
||||
|
||||
// only use horizontal control point
|
||||
for i in 1..<points.count {
|
||||
let A = points[i-1]
|
||||
let B = points[i]
|
||||
let controlPoint1 = CGPoint(x: A.x + delta*(B.x-A.x), y: A.y)
|
||||
let controlPoint2 = CGPoint(x: B.x - delta*(B.x-A.x), y: B.y)
|
||||
let curvedSegment = CurvedSegment(controlPoint1: controlPoint1, controlPoint2: controlPoint2)
|
||||
result.append(curvedSegment)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Create a curved bezier path that connects all points in the dataset
|
||||
func createCurvedPath(_ dataPoints: [CGPoint]) -> UIBezierPath? {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: dataPoints[0])
|
||||
|
||||
var curveSegments: [CurvedSegment] = []
|
||||
curveSegments = controlPointsFrom(points: dataPoints)
|
||||
|
||||
for i in 1..<dataPoints.count {
|
||||
path.addCurve(to: dataPoints[i], controlPoint1: curveSegments[i-1].controlPoint1, controlPoint2: curveSegments[i-1].controlPoint2)
|
||||
}
|
||||
return path
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue