From af3c727241778761e6b66c48df37930e167a2fdb Mon Sep 17 00:00:00 2001 From: Jeroen Wesbeek Date: Thu, 8 Aug 2019 18:27:31 +0200 Subject: [PATCH 1/6] Fixed #4099: Line renderer did not render lines if they coordinates fell outside of the viewport, even though they might intersect the viewport. --- .../Charts/Renderers/LineChartRenderer.swift | 23 ++++++------ Source/Charts/Utils/ViewPortHandler.swift | 36 +++++++++++++++++++ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/Source/Charts/Renderers/LineChartRenderer.swift b/Source/Charts/Renderers/LineChartRenderer.swift index 63930c19fb..031416e9a5 100644 --- a/Source/Charts/Renderers/LineChartRenderer.swift +++ b/Source/Charts/Renderers/LineChartRenderer.swift @@ -353,17 +353,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(firstCoordinate.x) && !viewPortHandler.isInBoundsLeft(lastCoordinate.x) { continue } + + // If both points lie right of the viewport, break out early. + if !viewPortHandler.isInBoundsRight(firstCoordinate.x) && !viewPortHandler.isInBoundsRight(lastCoordinate.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..2a168dd320 100755 --- a/Source/Charts/Utils/ViewPortHandler.swift +++ b/Source/Charts/Utils/ViewPortHandler.swift @@ -416,6 +416,11 @@ open class ViewPortHandler: NSObject return isInBoundsTop(y) && isInBoundsBottom(y) } + @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 +448,37 @@ 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. + + - 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 the start or endpoint fall within the viewport, bail out early. + if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } + + // Calculate the slope of the line. + let slope = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) + + // Check for colission with left edge of the view port. + if isInBoundsY((slope * (contentRect.minX - startPoint.x)) + startPoint.y) { return true } + + // Check for colission with right edge of the view port. + if isInBoundsY((slope * (contentRect.maxX - startPoint.x)) + startPoint.y) { return true } + + // Check for colission with top edge of the view port. + if isInBoundsX(((contentRect.minY - startPoint.y) / slope) + startPoint.x) { return true } + + // Check for colission with bottom edge of the viewport. + if isInBoundsX(((contentRect.maxY - startPoint.y) / slope) + startPoint.x) { return true } + + // This line does not intersect view the view port. + return false + } + /// The current x-scale factor @objc open var scaleX: CGFloat { From c1735be1bb97af35c62361aab838123f5541ed0d Mon Sep 17 00:00:00 2001 From: Jeroen Wesbeek Date: Thu, 8 Aug 2019 18:55:44 +0200 Subject: [PATCH 2/6] Updated inline documentation. --- Source/Charts/Utils/ViewPortHandler.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Source/Charts/Utils/ViewPortHandler.swift b/Source/Charts/Utils/ViewPortHandler.swift index 2a168dd320..d27bf4081d 100755 --- a/Source/Charts/Utils/ViewPortHandler.swift +++ b/Source/Charts/Utils/ViewPortHandler.swift @@ -416,6 +416,12 @@ 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) @@ -463,19 +469,19 @@ open class ViewPortHandler: NSObject // Calculate the slope of the line. let slope = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) - // Check for colission with left edge of the view port. + // Check for colission with the left edge of the view port. if isInBoundsY((slope * (contentRect.minX - startPoint.x)) + startPoint.y) { return true } - // Check for colission with right edge of the view port. + // Check for colission with the right edge of the view port. if isInBoundsY((slope * (contentRect.maxX - startPoint.x)) + startPoint.y) { return true } - // Check for colission with top edge of the view port. + // Check for colission with the top edge of the view port. if isInBoundsX(((contentRect.minY - startPoint.y) / slope) + startPoint.x) { return true } - // Check for colission with bottom edge of the viewport. + // Check for colission with the bottom edge of the viewport. if isInBoundsX(((contentRect.maxY - startPoint.y) / slope) + startPoint.x) { return true } - // This line does not intersect view the view port. + // This line does not intersect the view port. return false } From d7ad8c5bfb19e95ed22364549aad6b67edb4012e Mon Sep 17 00:00:00 2001 From: Jeroen Wesbeek Date: Thu, 26 Sep 2019 17:33:52 +0200 Subject: [PATCH 3/6] Implemented code review feedback and removed unnecessary checks for performance reasons. --- Source/Charts/Renderers/LineChartRenderer.swift | 4 ++-- Source/Charts/Utils/ViewPortHandler.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Charts/Renderers/LineChartRenderer.swift b/Source/Charts/Renderers/LineChartRenderer.swift index b498e372c0..aab5c393b4 100644 --- a/Source/Charts/Renderers/LineChartRenderer.swift +++ b/Source/Charts/Renderers/LineChartRenderer.swift @@ -361,10 +361,10 @@ open class LineChartRenderer: LineRadarRenderer firstCoordinate != lastCoordinate else { continue } // If both points lie left of viewport, skip stroking. - if !viewPortHandler.isInBoundsLeft(firstCoordinate.x) && !viewPortHandler.isInBoundsLeft(lastCoordinate.x) { continue } + if !viewPortHandler.isInBoundsLeft(lastCoordinate.x) { continue } // If both points lie right of the viewport, break out early. - if !viewPortHandler.isInBoundsRight(firstCoordinate.x) && !viewPortHandler.isInBoundsRight(lastCoordinate.x) { break } + 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 } diff --git a/Source/Charts/Utils/ViewPortHandler.swift b/Source/Charts/Utils/ViewPortHandler.swift index d27bf4081d..a879bf3453 100755 --- a/Source/Charts/Utils/ViewPortHandler.swift +++ b/Source/Charts/Utils/ViewPortHandler.swift @@ -466,7 +466,7 @@ open class ViewPortHandler: NSObject // If the start or endpoint fall within the viewport, bail out early. if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } - // Calculate the slope of the line. + // Calculate the slope of the line for use in a linear formula: `y = ax + b` (where `a` is `slope`). let slope = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) // Check for colission with the left edge of the view port. From ababe6f2078a9a794ca20e10f3bfcffa3a26e2bd Mon Sep 17 00:00:00 2001 From: Jeroen Wesbeek Date: Mon, 30 Sep 2019 13:35:25 +0200 Subject: [PATCH 4/6] Simplified and clarified the linear function to check for collisions with the left, top and bottom edges of the view port, and commented out the unecessary logic that checks for collision with the right edge of the view port. --- Source/Charts/Utils/ViewPortHandler.swift | 35 ++++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/Source/Charts/Utils/ViewPortHandler.swift b/Source/Charts/Utils/ViewPortHandler.swift index a879bf3453..4599d08814 100755 --- a/Source/Charts/Utils/ViewPortHandler.swift +++ b/Source/Charts/Utils/ViewPortHandler.swift @@ -455,7 +455,12 @@ open class ViewPortHandler: NSObject } /** - A method to check whether a line between two coordinates intersects with the view port. + A method to check whether a line between two coordinates intersects with the view port by using a linear function. + + Linear function: `y = ax + b` + + This method will not check colission 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. @@ -466,20 +471,28 @@ open class ViewPortHandler: NSObject // If the start or endpoint fall within the viewport, bail out early. if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } - // Calculate the slope of the line for use in a linear formula: `y = ax + b` (where `a` is `slope`). - let slope = (endPoint.y - startPoint.y) / (endPoint.x - 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 isInBoundsY((a * contentRect.minX) + b) { return true } - // Check for colission with the left edge of the view port. - if isInBoundsY((slope * (contentRect.minX - startPoint.x)) + startPoint.y) { return true } + // We can skip checking colission 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 in + // here for clarity sake: + // if isInBoundsY((a * contentRect.maxX) + b) { return true } - // Check for colission with the right edge of the view port. - if isInBoundsY((slope * (contentRect.maxX - startPoint.x)) + startPoint.y) { return true } + // While the slope `a` should never be `0`, we should protect against division by zero. + guard a != 0 else { return false } - // Check for colission with the top edge of the view port. - if isInBoundsX(((contentRect.minY - startPoint.y) / slope) + startPoint.x) { return true } + // Check for colission with the bottom edge of the view port (e.g. `x = (maxY - b) / a`). + if isInBoundsX((contentRect.maxY - b) / a) { return true } - // Check for colission with the bottom edge of the viewport. - if isInBoundsX(((contentRect.maxY - startPoint.y) / slope) + startPoint.x) { return true } + // Check for colission 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 From 73d1aad4755e100f7357b9680932d9c11ec07ce2 Mon Sep 17 00:00:00 2001 From: Jeroen Wesbeek Date: Mon, 30 Sep 2019 13:59:31 +0200 Subject: [PATCH 5/6] Updated in-line documentation. --- Source/Charts/Utils/ViewPortHandler.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/Charts/Utils/ViewPortHandler.swift b/Source/Charts/Utils/ViewPortHandler.swift index 4599d08814..4cf7daa25d 100755 --- a/Source/Charts/Utils/ViewPortHandler.swift +++ b/Source/Charts/Utils/ViewPortHandler.swift @@ -457,9 +457,9 @@ open class ViewPortHandler: NSObject /** A method to check whether a line between two coordinates intersects with the view port by using a linear function. - Linear function: `y = ax + b` + Linear function (calculus): `y = ax + b` - This method will not check colission with the right edge of the view port, as we assume lines run from left + 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: @@ -468,7 +468,7 @@ open class ViewPortHandler: NSObject */ @objc open func isIntersectingLine(from startPoint: CGPoint, to endPoint: CGPoint) -> Bool { - // If the start or endpoint fall within the viewport, bail out early. + // If start- and/or endpoint fall within the viewport, bail out early. if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } // Calculate the slope (`a`) of the line (e.g. `a = (y2 - y1) / (x2 - x1)`). @@ -479,19 +479,19 @@ open class ViewPortHandler: NSObject // Check for colission with the left edge of the view port (e.g. `y = (a * minX) + b`). if isInBoundsY((a * contentRect.minX) + b) { return true } - // We can skip checking colission with the right edge of the view port + // 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 in - // here for clarity sake: + // here for clarity's sake: // if isInBoundsY((a * contentRect.maxX) + b) { return true } - // While the slope `a` should never be `0`, we should protect against division by zero. + // While slope `a` can theoretically never be `0`, we should protect against division by zero. guard a != 0 else { return false } - // Check for colission with the bottom edge of the view port (e.g. `x = (maxY - b) / a`). + // 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 colission with the top edge of the view port (e.g. `x = (minY - b) / a`). + // 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. From a84b379feecf9040ff3abd309cfef912028a9cea Mon Sep 17 00:00:00 2001 From: Xuan Date: Tue, 8 Oct 2019 11:54:50 +0800 Subject: [PATCH 6/6] Update ViewPortHandler.swift add a check for vertical line and a few comments change --- Source/Charts/Utils/ViewPortHandler.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Charts/Utils/ViewPortHandler.swift b/Source/Charts/Utils/ViewPortHandler.swift index 4cf7daa25d..8916d068a8 100755 --- a/Source/Charts/Utils/ViewPortHandler.swift +++ b/Source/Charts/Utils/ViewPortHandler.swift @@ -470,6 +470,8 @@ open class ViewPortHandler: NSObject { // 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) @@ -477,12 +479,12 @@ open class ViewPortHandler: NSObject 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 in - // here for clarity's sake: + // 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.