Equal axis scale for x and y?

34 views
Skip to first unread message

Tyler Anderson

unread,
Dec 27, 2020, 6:42:01 AM12/27/20
to coreplot-discuss
Hello,

I am trying to do a pretty simple matching of data scales in the x and y axis, and I am not sure how exactly it should be implemented. The equivalent in matlab would be 'axis(square)', which should make the visible x axis units equivalent to the y axis units. In other words, the major and minor gridlines should trace out perfect squares to the user, and a line from (0,0) to (1,1) should be at a 45 degree angle. I would like to accomplish this while allowing pinch-to-zoom and variable graph width, so that if a user expands the window horizontally (on Mac), the x-axis shows more data points but does not change in scale.

How would I begin addressing this problem? Would this be a setting in the axisSet, plotSpace, x and y axes, or some combination? Would it require custom redrawing code for resizing the window? It feels like something that should already exist/ be pretty trivial but I am banging my head against a wall trying to figure it out.

Eric

unread,
Dec 27, 2020, 7:23:59 AM12/27/20
to coreplot-discuss
After setting up the graph, call -layoutIfNeeded on it to make sure all the graphical pieces are positioned correctly. You can then use the size of the plot area (in points) to scale the xRange and yRange of the plot space to keep the square aspect. Listen for the CPTLayerBoundsDidChangeNotification from the plot area and update the plot ranges as needed. This notification will be fired any time the plot area bounds change. Pinch zooming scales each axis proportionally so that shouldn't change the aspect ratio.

Eric

Tyler Anderson

unread,
Dec 27, 2020, 7:22:03 PM12/27/20
to coreplot-discuss
Thank you Eric, that seems to do the trick! For posterity, here is the simplest way I could figure to set the whole thing up in Swift (I'm not too familiar with the differences between CGFloat/Decimal/NSNumber, so if anyone has a better suggestion I'll welcome it. I dealt all in Decimal to avoid a bunch of conversions. Also,  I'm sure there is a better way to do the Notification observer)

First, in the top level class used to create the CPTGraph, I added the following variables to keep track of the plot area size (initialized to -1.0 so I can avoid running the resize callback on initialization):

var plotAreaSize: (Decimal, Decimal) = (-1.0, -1.0)
var newPlotAreaSize: (Decimal, Decimal) {
    if let pa = graph.plotAreaFrame.plotArea { 
        return (pa.widthDecimal, pa.heightDecimal)
    } else { return plotAreaSize }
}

Then I add a function to actually perform the resizing:
private func enforceEqualAxes(xScale: Decimal, yScale: Decimal) {
    let plotSpace = graph.defaultPlotSpace as! CPTXYPlotSpace
    let maxX = plotSpace.xRange.lengthDecimal*xScale
    let maxY = plotSpace.yRange.lengthDecimal*yScale
    let xLoc = plotSpace.xRange.locationDecimal
    let yLoc = plotSpace.yRange.locationDecimal

    if xScale == 1.0 && yScale == 1.0 { // on initialization, scale to fit the whole plot
        let maxSize = [maxX, maxY].max()! 
        plotSpace.xRange = CPTPlotRange(locationDecimal: xLoc, lengthDecimal: maxSize)
        plotSpace.yRange = CPTPlotRange(locationDecimal: yLoc, lengthDecimal: maxSize)
    }

    if xScale != 1.0 {
        plotSpace.xRange = CPTPlotRange(locationDecimal: xLoc, lengthDecimal: maxX)
    }

    if yScale != 1.0 {
        plotSpace.yRange = CPTPlotRange(locationDecimal: yLoc, lengthDecimal: maxY)
    }
}

Add a callback function to run the resizing when the plot area changes:
@objc func didResizePlotArea(notification: NSNotification) {
    guard let plotArea = notification.object as? CPTPlotArea else { return }
    if plotAreaSize.0 > 0 && plotAreaSize.1 > 0 { // Only run after initialization 
        enforceEqualAxes(xScale: newPlotAreaSize.0/plotAreaSize.0, yScale: newPlotAreaSize.1/plotAreaSize.1)
    }
    plotAreaSize = newPlotAreaSize
}

Finally, at the initialization of the graph, run the resizer the first time and add the notification watcher:
plotSpace.scale(toFitEntirePlots: graph.allPlots())
graph.layoutSublayers()
enforceEqualAxes(xScale: 1.0, yScale: 1.0)

guard let plotArea = graph.plotAreaFrame?.plotArea else { return }
NotificationCenter.default.addObserver(self, selector: #selector(self.didResizePlotArea), name: NSNotification.Name( CPTLayerNotification.boundsDidChange.rawValue), object: plotArea)

Tyler Anderson

unread,
Dec 27, 2020, 7:25:11 PM12/27/20
to coreplot-discuss
Also, just want to say I am really enjoying working with CorePlot and I hope to contribute in the future once I have spent some more time with the API (maybe helping to implement box plots/histograms)
Reply all
Reply to author
Forward
0 new messages