Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lineChartView.zoom() - how to obtain center x/y coordinates #4434

Closed
lienbacher opened this issue Aug 2, 2020 · 4 comments
Closed

lineChartView.zoom() - how to obtain center x/y coordinates #4434

lienbacher opened this issue Aug 2, 2020 · 4 comments

Comments

@lienbacher
Copy link

Hello!
I am trying to implement an x-axis zoom-slider on a mac os app. The zooming itself is no issue at all, it zooms just fine. I am however struggling to keep the current viewport centered during the zoom operation, especially when the scale of the viewpoint has already been altered by for instance a pinch zoom with the touchpad. I have managed to keep the x-axis centered on the viewport, by just calculating the center of highestVisibleX and lowestVisibleX and call moveViewToX(center), however there are no such functions for the y axis.

I am 100% sure there is a simple way to gather those x/y coordinates so I can just pass to the zoom() function and keep the view centered while I change the x-scale. I have read the documentation a few times and still fail to come up with a working solution. I'd be incredibly thankful if somebody would be able to help me along. 🙏

@bivant
Copy link
Contributor

bivant commented Aug 3, 2020

Hello @lienbacher
This is the code for the lowest/highest functions:

    /// The lowest x-index (value on the x-axis) that is still visible on he chart.
    open var lowestVisibleX: Double
    {
        var pt = CGPoint(
            x: viewPortHandler.contentLeft,
            y: viewPortHandler.contentBottom)
        
        getTransformer(forAxis: .left).pixelToValues(&pt)
        
        return max(xAxis._axisMinimum, Double(pt.x))
    }
    
    /// The highest x-index (value on the x-axis) that is still visible on the chart.
    open var highestVisibleX: Double
    {
        var pt = CGPoint(
            x: viewPortHandler.contentRight,
            y: viewPortHandler.contentBottom)
        
        getTransformer(forAxis: .left).pixelToValues(&pt)

        return min(xAxis._axisMaximum, Double(pt.x))
    }

Why do not compute the values you need yourself? Seems like all the data is available.

@objc open var viewPortHandler: ViewPortHandler!
open func getTransformer(forAxis axis: YAxis.AxisDependency) -> Transformer

Also there is a contentCenter property in the 'ViewPortHandler', so you can calc center right a way without min/max.
an example, not checked/tested

    let transformer = chartView.getTransformer(forAxis: .left)
...
    func visibleCenter(of viewPortHandler: ViewPortHandler, transformer: Transformer) -> CGPoint
    {
        var pt = viewPortHandler.contentCenter
        
        transformer.pixelToValues(&pt)

        return pt //+ add checks for the min/max for the x/y if you need
    }

@lienbacher
Copy link
Author

lienbacher commented Aug 3, 2020

Thanks for your detailed reply @bivant!

Why do not compute the values you need yourself? Seems like all the data is available.

Well I am trying, but I fail to find/understand the documentation. I understand from the documentation that the zoom() function expects x/y to be data values, but I have not found any documentation that would bring me anywhere close to how helpful your post was.

edit: I have finally managed to understand what's going on with the snippets you provided. I was after all using the wrong overload of the zoom function without the axis dependency that would accept pixel values. Interestingly enough the function accepting pixel values would not yield the expected result when passing viewPortHandler.contentCenter as values, but transforming it according to your suggestion, using an axis dependency and actual scale values instead of relative scale values did the trick!

@objc func chartXZoomChanged(notification: Notification) {
        let transformer = lineChartView.getTransformer(forAxis: .left)
        var centerPt = lineChartView.viewPortHandler.contentCenter
        transformer.pixelToValues(&centerPt)
        lineChartView.zoom(scaleX: zoomValue, scaleY: lineChartView.scaleY, xValue: Double(centerPt.x), yValue: Double(centerPt.y), axis: .left)
}

Thank you very much! I can finally sleep tight again 🙏

@bivant
Copy link
Contributor

bivant commented Aug 3, 2020

Hello @lienbacher
x: CGFloat(center), y: centerPt.y- looks like a typo - I see no center variable in the function

I understand from the documentation that the zoom() function expects x/y to be data values

Seems like your assumption is wrong:

    /// Zooms in or out by the given scale factor. x and y are the coordinates
    /// (in pixels) of the zoom center.
    ///
    /// - Parameters:
    ///   - scaleX: if < 1 --> zoom out, if > 1 --> zoom in
    ///   - scaleY: if < 1 --> zoom out, if > 1 --> zoom in
    ///   - x:
    ///   - y:
    @objc open func zoom(
        scaleX: CGFloat,
               scaleY: CGFloat,
               x: CGFloat,
               y: CGFloat)
    {

Well, since pinch zoom works as you need maybe the best way is to check it's implementation:

                if canZoomMoreX || canZoomMoreY
                {
                    var location = recognizer.location(in: self)
                    location.x = location.x - _viewPortHandler.offsetLeft
                    
                    if isTouchInverted()
                    {
                        location.y = -(location.y - _viewPortHandler.offsetTop)
                    }
                    else
                    {
                        location.y = -(_viewPortHandler.chartHeight - location.y - _viewPortHandler.offsetBottom)
                    }
                    
                    let scaleX = canZoomMoreX ? recognizer.nsuiScale : 1.0
                    let scaleY = canZoomMoreY ? recognizer.nsuiScale : 1.0
                    
                    var matrix = CGAffineTransform(translationX: location.x, y: location.y)
                    matrix = matrix.scaledBy(x: scaleX, y: scaleY)
                    matrix = matrix.translatedBy(x: -location.x, y: -location.y)
                    
                    matrix = _viewPortHandler.touchMatrix.concatenating(matrix)
                    
                    _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true)
                    
                    if delegate !== nil
                    {
                        delegate?.chartScaled?(self, scaleX: scaleX, scaleY: scaleY)
                    }
                }

I hope that using the center of the viewPort you should achieve the result you need. The difference here, as I see, is the matrix logic.


Glad you overcome this issue. Good luck :)
P.S. Do not forget to close the issue, there are too many of them already.

@lienbacher
Copy link
Author

Ahh this reply came way faster than I expected, I got it down shortly after I posted my reply and thought I would be fast enough with an edit. I was actually using the wrong overload of zoom. There is another one:

/// Zooms in or out by the given scale factor.
    /// x and y are the values (**not pixels**) of the zoom center.
    ///
    /// - Parameters:
    ///   - scaleX: if < 1 --> zoom out, if > 1 --> zoom in
    ///   - scaleY: if < 1 --> zoom out, if > 1 --> zoom in
    ///   - xValue:
    ///   - yValue:
    ///   - axis:
    @objc open func zoom(
        scaleX: CGFloat,
               scaleY: CGFloat,
               xValue: Double,
               yValue: Double,
               axis: 

For future readers: the revision history has my old post still in there!

And thanks for that additional snippet, that also helps a lot understanding!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants