diff --git a/Source/Charts/Renderers/LineChartRenderer.swift b/Source/Charts/Renderers/LineChartRenderer.swift index 5c267e556f..aab5c393b4 100644 --- a/Source/Charts/Renderers/LineChartRenderer.swift +++ b/Source/Charts/Renderers/LineChartRenderer.swift @@ -354,17 +354,20 @@ open class LineChartRenderer: LineRadarRenderer _lineSegments[i] = _lineSegments[i].applying(valueToPixelMatrix) } - if (!viewPortHandler.isInBoundsRight(_lineSegments[0].x)) - { - break - } + // Determine the start and end coordinates of the line, and make sure they differ. + guard + let firstCoordinate = _lineSegments.first, + let lastCoordinate = _lineSegments.last, + firstCoordinate != lastCoordinate else { continue } - // make sure the lines don't do shitty things outside bounds - if !viewPortHandler.isInBoundsLeft(_lineSegments[1].x) - || (!viewPortHandler.isInBoundsTop(_lineSegments[0].y) && !viewPortHandler.isInBoundsBottom(_lineSegments[1].y)) - { - continue - } + // If both points lie left of viewport, skip stroking. + if !viewPortHandler.isInBoundsLeft(lastCoordinate.x) { continue } + + // If both points lie right of the viewport, break out early. + if !viewPortHandler.isInBoundsRight(firstCoordinate.x) { break } + + // Only stroke the line if it intersects with the viewport. + guard viewPortHandler.isIntersectingLine(from: firstCoordinate, to: lastCoordinate) else { continue } // get the color that is set for this line-segment context.setStrokeColor(dataSet.color(atIndex: j).cgColor) diff --git a/Source/Charts/Utils/ViewPortHandler.swift b/Source/Charts/Utils/ViewPortHandler.swift index 56e034b418..8916d068a8 100755 --- a/Source/Charts/Utils/ViewPortHandler.swift +++ b/Source/Charts/Utils/ViewPortHandler.swift @@ -416,6 +416,17 @@ open class ViewPortHandler: NSObject return isInBoundsTop(y) && isInBoundsBottom(y) } + /** + A method to check whether coordinate lies within the viewport. + + - Parameters: + - point: a coordinate. + */ + @objc open func isInBounds(point: CGPoint) -> Bool + { + return isInBounds(x: point.x, y: point.y) + } + @objc open func isInBounds(x: CGFloat, y: CGFloat) -> Bool { return isInBoundsX(x) && isInBoundsY(y) @@ -443,6 +454,52 @@ open class ViewPortHandler: NSObject return (_contentRect.origin.y + _contentRect.size.height) >= normalizedY } + /** + A method to check whether a line between two coordinates intersects with the view port by using a linear function. + + Linear function (calculus): `y = ax + b` + + Note: this method will not check for collision with the right edge of the view port, as we assume lines run from left + to right (e.g. `startPoint < endPoint`). + + - Parameters: + - startPoint: the start coordinate of the line. + - endPoint: the end coordinate of the line. + */ + @objc open func isIntersectingLine(from startPoint: CGPoint, to endPoint: CGPoint) -> Bool + { + // If start- and/or endpoint fall within the viewport, bail out early. + if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } + // check if x in bound when it's a vertical line + if startPoint.x == endPoint.x { return isInBoundsX(startPoint.x) } + + // Calculate the slope (`a`) of the line (e.g. `a = (y2 - y1) / (x2 - x1)`). + let a = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) + // Calculate the y-correction (`b`) of the line (e.g. `b = y1 - (a * x1)`). + let b = startPoint.y - (a * startPoint.x) + + // Check for colission with the left edge of the view port (e.g. `y = (a * minX) + b`). + // if a is 0, it's a horizontal line; checking b here is still valid, as b is `point.y` all the time + if isInBoundsY((a * contentRect.minX) + b) { return true } + + // Skip unnecessary check for collision with the right edge of the view port + // (e.g. `y = (a * maxX) + b`), as such a line will either begin inside the view port, + // or intersect the left, top or bottom edges of the view port. Leaving this logic here for clarity's sake: + // if isInBoundsY((a * contentRect.maxX) + b) { return true } + + // While slope `a` can theoretically never be `0`, we should protect against division by zero. + guard a != 0 else { return false } + + // Check for collision with the bottom edge of the view port (e.g. `x = (maxY - b) / a`). + if isInBoundsX((contentRect.maxY - b) / a) { return true } + + // Check for collision with the top edge of the view port (e.g. `x = (minY - b) / a`). + if isInBoundsX((contentRect.minY - b) / a) { return true } + + // This line does not intersect the view port. + return false + } + /// The current x-scale factor @objc open var scaleX: CGFloat {