chore: update trends line chart style

This commit is contained in:
CMK 2021-10-18 18:35:19 +08:00
parent 68a5c6d4fd
commit 0f55d80e20
3 changed files with 67 additions and 44 deletions

View File

@ -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 */,

View File

@ -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

View File

@ -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
}
}