Skip to content

Commit

Permalink
solved line view negative numbers, also when it crashed 0 or 1 elemen…
Browse files Browse the repository at this point in the history
…t data set
  • Loading branch information
AppPear committed Dec 27, 2019
1 parent 0d95dbd commit 03f9072
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 37 deletions.
52 changes: 38 additions & 14 deletions Sources/SwiftUICharts/LineChart/Legend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,37 @@ struct Legend: View {
@Binding var frame: CGRect
@Binding var hideHorizontalLines: Bool
@Environment(\.colorScheme) var colorScheme: ColorScheme
let padding:CGFloat = 3

var stepWidth: CGFloat {
if data.points.count < 2 {
return 0
}
return frame.size.width / CGFloat(data.points.count-1)
}
var stepHeight: CGFloat {
return frame.size.height / CGFloat(data.points.max()! + data.points.min()!)
if let min = data.points.min(), let max = data.points.max(), min != max {
if (min < 0){
return (frame.size.height-padding) / CGFloat(max - min)
}else{
return (frame.size.height-padding) / CGFloat(max + min)
}
}
return 0
}

var min: CGFloat {
return CGFloat(data.points.min() ?? 0)
}

var body: some View {
ZStack(alignment: .topLeading){
ForEach((0...4), id: \.self) { height in
HStack(alignment: .center){
Text("\(self.getYLegend()![height], specifier: "%.2f")").offset(x: 0, y: (self.frame.height-CGFloat(self.getYLegend()![height])*self.stepHeight)-(self.frame.height/2))
Text("\(self.getYLegendSafe(height: height), specifier: "%.2f")").offset(x: 0, y: self.getYposition(height: height) )
.foregroundColor(Colors.LegendText)
.font(.caption)
self.line(atHeight: CGFloat(self.getYLegend()![height]), width: self.frame.width)
self.line(atHeight: self.getYLegendSafe(height: height), width: self.frame.width)
.stroke(self.colorScheme == .dark ? Colors.LegendDarkColor : Colors.LegendColor, style: StrokeStyle(lineWidth: 1.5, lineCap: .round, dash: [5,height == 0 ? 0 : 10]))
.opacity((self.hideHorizontalLines && height != 0) ? 0 : 1)
.rotationEffect(.degrees(180), anchor: .center)
Expand All @@ -42,31 +57,40 @@ struct Legend: View {
}
}

func getYLegendSafe(height:Int)->CGFloat{
if let legend = getYLegend() {
return CGFloat(legend[height])
}
return 0
}

func getYposition(height: Int)-> CGFloat {
if let legend = getYLegend() {
return (self.frame.height-((CGFloat(legend[height]) - min)*self.stepHeight))-(self.frame.height/2)
}
return 0

}

func line(atHeight: CGFloat, width: CGFloat) -> Path {
var hLine = Path()
hLine.move(to: CGPoint(x:5, y: atHeight*stepHeight))
hLine.addLine(to: CGPoint(x: width, y: atHeight*stepHeight))
hLine.move(to: CGPoint(x:5, y: (atHeight-min)*stepHeight))
hLine.addLine(to: CGPoint(x: width, y: (atHeight-min)*stepHeight))
return hLine
}

func getYLegend() -> [Double]? {
guard let max = data.points.max() else { return nil }
guard let min = data.points.min() else { return nil }
if(min >= 0){
let upperBound = ((max/10)+1) * 10
let step = upperBound.rounded()/4

return [step * 0, step * 1, step * 2, step * 3, step * 4]
}

return nil
let step = Double(max - min)/4
return [min+step * 0, min+step * 1, min+step * 2, min+step * 3, min+step * 4]
}
}

struct Legend_Previews: PreviewProvider {
static var previews: some View {
GeometryReader{ geometry in
Legend(data: TestData.data, frame: .constant(geometry.frame(in: .local)), hideHorizontalLines: .constant(false))
Legend(data: ChartData(points: [0.2,0.4,1.4,4.5]), frame: .constant(geometry.frame(in: .local)), hideHorizontalLines: .constant(false))
}.frame(width: 320, height: 200)
}
}
58 changes: 35 additions & 23 deletions Sources/SwiftUICharts/LineChart/Line.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ struct Line: View {
@Binding var showIndicator: Bool
@State private var showFull: Bool = false
@State var showBackground: Bool = true

let padding:CGFloat = 3
var stepWidth: CGFloat {
if data.points.count < 2 {
return 0
}
return frame.size.width / CGFloat(data.points.count-1)
}
var stepHeight: CGFloat {
return frame.size.height / CGFloat(data.points.max()! + data.points.min()!)
if let min = data.points.min(), let max = data.points.max(), min != max {
if (min < 0){
return (frame.size.height-padding) / CGFloat(data.points.max()! - data.points.min()!)
}else{
return (frame.size.height-padding) / CGFloat(data.points.max()! + data.points.min()!)
}
}
return 0
}
var path: Path {
return Path.quadCurvedPathWithPoints(points: data.points, step: CGPoint(x: stepWidth, y: stepHeight))
Expand Down Expand Up @@ -67,24 +77,24 @@ struct Line: View {

extension CGPoint {
static func getMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
return CGPoint(
x: point1.x + (point2.x - point1.x) / 2,
y: point1.y + (point2.y - point1.y) / 2
)
return CGPoint(
x: point1.x + (point2.x - point1.x) / 2,
y: point1.y + (point2.y - point1.y) / 2
)
}

func dist(to: CGPoint) -> CGFloat {
return sqrt((pow(self.x - to.x, 2) + pow(self.y - to.y, 2)))
return sqrt((pow(self.x - to.x, 2) + pow(self.y - to.y, 2)))
}

static func midPointForPoints(p1:CGPoint, p2:CGPoint) -> CGPoint {
return CGPoint(x:(p1.x + p2.x) / 2,y: (p1.y + p2.y) / 2)
}

static func controlPointForPoints(p1:CGPoint, p2:CGPoint) -> CGPoint {
var controlPoint = CGPoint.midPointForPoints(p1:p1, p2:p2)
let diffY = abs(p2.y - controlPoint.y)

if (p1.y < p2.y){
controlPoint.y += diffY
} else if (p1.y > p2.y) {
Expand All @@ -94,16 +104,17 @@ extension CGPoint {
}
}
extension Path {
static func quadCurvedPathWithPoints(points:[Double], step:CGPoint) -> Path {
static func quadCurvedPathWithPoints(points:[Int], step:CGPoint) -> Path {
var path = Path()
var p1 = CGPoint(x: 0, y: CGFloat(points[0])*step.y)
path.move(to: p1)
if(points.count < 2){
path.addLine(to: CGPoint(x: step.x, y: step.y*CGFloat(points[1])))
if (points.count < 2){
return path
}
guard var offset = points.min() else { return path }
offset -= 3
var p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.move(to: p1)
for pointIndex in 1..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]))
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
let midPoint = CGPoint.midPointForPoints(p1: p1, p2: p2)
path.addQuadCurve(to: midPoint, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p1))
path.addQuadCurve(to: p2, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p2))
Expand All @@ -112,17 +123,18 @@ extension Path {
return path
}

static func quadClosedCurvedPathWithPoints(points:[Double], step:CGPoint) -> Path {
static func quadClosedCurvedPathWithPoints(points:[Int], step:CGPoint) -> Path {
var path = Path()
path.move(to: .zero)
var p1 = CGPoint(x: 0, y: CGFloat(points[0])*step.y)
path.addLine(to: p1)
if(points.count < 2){
path.addLine(to: CGPoint(x: step.x, y: step.y*CGFloat(points[1])))
if (points.count < 2){
return path
}
guard var offset = points.min() else { return path }
offset -= 3
path.move(to: .zero)
var p1 = CGPoint(x: 0, y: CGFloat(points[0]-offset)*step.y)
path.addLine(to: p1)
for pointIndex in 1..<points.count {
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]))
let p2 = CGPoint(x: step.x * CGFloat(pointIndex), y: step.y*CGFloat(points[pointIndex]-offset))
let midPoint = CGPoint.midPointForPoints(p1: p1, p2: p2)
path.addQuadCurve(to: midPoint, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p1))
path.addQuadCurve(to: p2, control: CGPoint.controlPointForPoints(p1: midPoint, p2: p2))
Expand Down Expand Up @@ -153,7 +165,7 @@ extension Path {
struct Line_Previews: PreviewProvider {
static var previews: some View {
GeometryReader{ geometry in
Line(data: TestData.data, frame: .constant(geometry.frame(in: .local)), touchLocation: .constant(CGPoint(x: 300, y: 12)), showIndicator: .constant(true))
Line(data: ChartData(points: [12,-230,10,54]), frame: .constant(geometry.frame(in: .local)), touchLocation: .constant(CGPoint(x: 100, y: 12)), showIndicator: .constant(true))
}.frame(width: 320, height: 160)
}
}

0 comments on commit 03f9072

Please sign in to comment.