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 */; };
|
DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */; };
|
||||||
DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */; };
|
DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */; };
|
||||||
DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71C7CA271D5A0300BE3819 /* LineChartView.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 */; };
|
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 */; };
|
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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
DB71FD3B25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistCache.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1752,6 +1754,7 @@
|
||||||
2D5A3D0125CF8640002347D6 /* Vender */ = {
|
2D5A3D0125CF8640002347D6 /* Vender */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */,
|
||||||
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */,
|
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */,
|
||||||
DB51D170262832380062B7A1 /* BlurHashDecode.swift */,
|
DB51D170262832380062B7A1 /* BlurHashDecode.swift */,
|
||||||
DB51D171262832380062B7A1 /* BlurHashEncode.swift */,
|
DB51D171262832380062B7A1 /* BlurHashEncode.swift */,
|
||||||
|
@ -4234,6 +4237,7 @@
|
||||||
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */,
|
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */,
|
||||||
DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */,
|
DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */,
|
||||||
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */,
|
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */,
|
||||||
|
DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */,
|
||||||
DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */,
|
DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */,
|
||||||
DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */,
|
DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */,
|
||||||
DBAC6499267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift in Sources */,
|
DBAC6499267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift in Sources */,
|
||||||
|
|
|
@ -66,61 +66,33 @@ extension LineChartView {
|
||||||
gradientLayer.isHidden = false
|
gradientLayer.isHidden = false
|
||||||
|
|
||||||
// Draw smooth chart
|
// 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 {
|
guard let maxDataPoint = data.max() else {
|
||||||
return
|
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 }
|
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 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()
|
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 {
|
if let last = points.last {
|
||||||
let y = calculateY(for: last, in: bounds)
|
dotPath.addArc(withCenter: last, radius: 3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
|
||||||
let center = CGPoint(x: bounds.maxX, y: y)
|
|
||||||
dotPath.addArc(withCenter: center, radius: 3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this not works
|
// 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