Pepf
Pepf

Reputation: 94

Dynamically scaling canvas in WPF by setting in- and outpoints

I'm working on plotting program in WPF using the canvas element. What I want to achieve is a scrollbar with draggable endpoints. Example of these kinds of scrollbars are in the After Effects video editing software by Adobe.

Basic functionality of such a scrollbar is that it is able to scroll trough content that is bigger then it's container, but both the left and right endpoint can be dragged to dynamically change the scale of the content.

I have implemented a similar scrollbar in the plotting program; users should be able to drag around the in and outpoint (Rectangles in a canvas), and the plot canvas should respond to this by scaling to the desired range. Information I need for this:

Link to current screenshot

With this information I have created a MatrixTransform, using the ScaleAt() method to scale the plot canvas inside the container so that it matches the in and outpoints in the scrollbar below. For this I used the following code. resetTransform gets called FPS times a second to keep up with the incoming data and XMAX and YMAX are updated elsewhere to reflect this.

public void resetTransform(Boolean useSlider = false)
{
    //Add transformgroup to plot
    double yscale = plot.Height / view.YMAX; //YMAX is maximum plot value received
    double xscale = plot.Width / view.XMAX; //XMAX is total ammount of plotted points
    Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
    if (useSlider)
    {
        double maxVal = zoomBar.ActualWidth - outPoint.Width;
        double outP = Canvas.GetLeft(outPoint); //points position relative to the scrollbar
        double inP = Canvas.GetLeft(inPoint);
        double center = (((outP + inP) / 2) / maxVal) * plot.ActualWidth;
        double delta = (outP-inP);
        double factor = (maxVal/delta) * xscale;
        double mappedinP = (inP / maxVal) * view.XMAX;

        double anchorOut = (outP / maxVal) * view.XMAX;
        double anchorIn = (inP / maxVal) * view.XMAX;

        m.ScaleAt(factor, -yscale,center,0); //scale around the center point, 
        m.Translate(0, plot.Height); //to compensate the flipped graph, move it back down
    }
    scale = new ScaleTransform(m.M11, m.M22, 0, 0); //save scale factors in a scaletransform for reference
    signals.scaleSignalStrokes(scale); //Scale the plotlines to compensate for canvas scaling
    MatrixTransform matrixTrans = new MatrixTransform(m); //Create matrixtransform
    plot.RenderTransform = matrixTrans; //Apply to canvas
}

Expectation: Everything should work and the plotted graph would scale nicely when the amount of plotpoints grows over time. Reality: The graph scales when moving the points around, but it is not representative; Moreover, the more plot points are added, the more the whole canvas shifts to the right and the less control I seem to have over the transformation. The algorithm as it is now is probably to wrong approach to get the result I need, but I have spent quite some time thinking how to do this right.

Update

I have uploaded a video to give a clearer picture on the interaction. In the video you can clearly see the canvas shifting to the right. Screencapture video

How should I scale the canvas (plot) to fit within two boundaries?

Upvotes: 1

Views: 1514

Answers (1)

Pepf
Pepf

Reputation: 94

So, after some struggling, I found the right algorithm to solve this problem. I will post the adjusted version of the resetTransform function below:

    //Reset graph transform
    public void resetTransform(Boolean useSlider = false)
    {
        double yscale = plot.Height / view.YMAX; //YMAX is maximum plot value received
        double xscale = plot.Width / view.XMAX; //XMAX is total ammount of plotted points
        Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
        if (useSlider)
        {
            double maxVal = zoomBar.ActualWidth - outPoint.Width;
            double outP = Canvas.GetLeft(outPoint); //points position relative to the scrollbar
            double inP = Canvas.GetLeft(inPoint);
            double delta = (outP-inP);
            double factor = (maxVal/delta) * xscale;

            anchorOut = (outP / maxVal) * view.XMAX; //Define anchorpoint coordinates
            anchorIn = (inP / maxVal) * view.XMAX;
            double center = (anchorOut +anchorIn)/2; //Define centerpoint

            m.Translate(-anchorIn, 0); //Move graph to inpoint
            m.ScaleAt(factor, -yscale,0,0); //scale around the inpoint, with a factor so that outpoint is plot.Height(=600px) further away
            m.Translate(0, plot.Height); //to compensate the flipped graph, move it back down
        }
            scale = new ScaleTransform(m.M11, m.M22, 0, 0); //save scale factors in a scaletransform for reference
            signals.scaleSignalStrokes(scale); //Scale the plotlines to compensate for canvas scaling
            MatrixTransform matrixTrans = new MatrixTransform(m); //Create matrixtransform
            plot.RenderTransform = matrixTrans; //Apply to canvas
    }

So rather than scaling around the centre point, I should first translate the image and then scale around the origin of the canvas with a factor. This means the other side of the canvas is exactly plot.Height pixels away (with some added scaling)

Everything seems to work fine now, but because I am using custom controls (draggable Rectangles in a canvas) I notice these rectangles do not always fire the mousse events.

Since it is out of the scope of this question, I've described the issue further in this post

Upvotes: 1

Related Questions