Skip to content

Instantly share code, notes, and snippets.

@ryanoff
Last active June 5, 2020 23:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanoff/ca6b55176e5314dccb94e0924562239a to your computer and use it in GitHub Desktop.
Save ryanoff/ca6b55176e5314dccb94e0924562239a to your computer and use it in GitHub Desktop.
SwiftUI Sparkling Line Chart
//
// SparkLineView.swift
// SwiftUI Examples
//
//
import SwiftUI
struct LineChartView: View {
@State private var width: CGFloat = 250.0
@State private var height: CGFloat = 150.0
var points: [CGPoint] = [
CGPoint(x: 1, y: 4),
CGPoint(x: 2, y: 5),
CGPoint(x: 5, y: 7),
CGPoint(x: 8, y: 16),
CGPoint(x: 12, y: 8),
CGPoint(x: 60, y: 3),
CGPoint(x: 16, y: 6),
CGPoint(x: 26, y: 11),
CGPoint(x: 30, y: 0),
CGPoint(x: 32, y: 7),
CGPoint(x: 35, y: 13),
CGPoint(x: 40, y: 23),
CGPoint(x: 64, y: 15)
]
var body: some View {
VStack {
Text("Sparkline Line Chart")
Spacer()
Sparkline(points: points, width: width, height: height, box: false)
.stroke(Color.green, style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
.frame(width: self.width, height: self.height/4)
.padding(.vertical)
Spacer()
Group {
HStack {
Text("Width").font(.footnote).foregroundColor(Color.gray).frame(maxWidth: 50)
Text("\(Int(width))").font(.headline)
Slider(value: $width, in: 1...300, step: 1)
.padding([.horizontal, .bottom])
}
HStack {
Text("Height").font(.footnote).foregroundColor(Color.gray).frame(maxWidth: 50)
Text("\(Int(height))").font(.headline)
Slider(value: $height, in: 1...350, step: 1)
.padding([.horizontal, .bottom])
}
}
Spacer()
} //VStack
}
}
// MARK: - Sparkline
/// Sparkline Shape must be passed a set of points where the x coordinate is always unique.
struct Sparkline: Shape {
var points: [CGPoint]
var box: Bool = false
func path(in rect: CGRect) -> Path {
var path = Path()
// Sort points so that x values are in order from lowest to highest
let sPoints = points.sorted { $0.x < $1.x }
// Get the highest X and Y values
let maxYCoord = sPoints.map {$0.y}.max() ?? 1
let maxXCoord = sPoints.map {$0.x}.max() ?? 1
// Create a scale factor to resize the chart based on maximum values
let xScale: CGFloat = rect.maxX / CGFloat(maxXCoord)
let yScale: CGFloat = rect.maxY / CGFloat(maxYCoord)
// Draw the first point
path.move(to: CGPoint(x: rect.minX, y: rect.maxY - (CGFloat(sPoints[0].y) * yScale)) )
// Draw the remaining points and paths after dropping the first already used point
for item in sPoints.dropFirst() {
path.addLine(to: CGPoint(x: rect.minX + (item.x * xScale), y: rect.maxY - (item.y * yScale) ))
}
// Optionally draws a bounding box of the maximum coordinates
if box {
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint( x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint( x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
}
return path
}
}
struct LineChartView_Previews: PreviewProvider {
static var previews: some View {
LineChartView()
}
}
@ryanoff
Copy link
Author

ryanoff commented Jun 5, 2020

Screen Shot 2020-06-05 at 3 25 05 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment