SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, iOS Chart, Android Chart and JavaScript Chart Components

Answered
0
0

Is it possible to define custom extents such that when I call ChartSurface.ZoomExtents() it will zoom out to where I want instead of the default found by the chart?

I have a requirement that dictates the amount of padding around each axis. I use a custom ViewPortManager and override the OnCalculateNewYRange() and OnCalculateNewXRange() functions to set the range.

I use the ZoomState to decide whether to allow zoom to do its job versus my dictation of the range when the zoom has been to extents. This unfortunately shows an awkward bump between the 3 states.

  1. Zoomed
  2. Zoomed to extents
  3. Zoomed to my custom range

Between 2 and 3 there is a “bump” as the chart resizes. Imagine the user double clicks to ZoomExtents and the animation from zoomed goes out to extents and then suddenly does the same from extents to my custom range. It’s really awkward and I don’t want to lose the animation as it’s a very nice user experience instead of an abrupt switch from zoomed to extents.

So, it would be great to set the extents in my derived ViewPortManager such that a ZoomExtents zooms directly to my custom extents instead of the default.

Version
6.1.0.13075
  • You must to post comments
Best Answer
1
0

I finally came up with a solution. The only downside is that it doesn’t provide for an animated return from zoomed to extents. I have actually completely overridden the concept of SciChart defining the X and Y range for all the series being displayed. I have an enum that has two states. Zoomed and Extents. I added a property to my custom ViewPortManager which is an instance of that enum. When the user moves the mouse around the chart such that a zoom (or return to extents) is desired I am able to capture that event in a custom ZoomExtentsModifier.

<chart:ZoomOutModifier IsAnimated="True" />

In that derived class I am able to identify what the user is trying to do (zoom or not zoom) and set the value of the state in the ViewPortManager. This allows me to intercept the range to use in my ViewPortManager and define the X and Y ranges myself! Thanks to Andrew for leading me down the right path a couple weeks ago with customizing the ViewPortManager. Here is the solution below.

    class ZoomOutModifier : ZoomExtentsModifier
{
    double _x;
    double _y;
    MouseButtons _button = MouseButtons.None;

    /// <summary>
    /// I only want to zoom when the mouse is moved up and to the left
    /// </summary>
    /// <param name="e"></param>
    public override void OnModifierDoubleClick(ModifierMouseArgs e)
    {
        // do not zoom to extents on a double click
        e.Handled = true;
    }

    /// <summary>
    /// When the mouse button is clicked down we want to know what
    /// are the coordinates on the chart.  and we also want to know
    /// if this was the right or left mouse button that was clicked
    /// </summary>
    /// <param name="e"></param>
    public override void OnModifierMouseDown(ModifierMouseArgs e)
    {
        IsAnimated = true;

        base.OnModifierMouseDown(e);

        // where was the mouse button on the chart surface?            
        _x = e.MousePoint.X;
        _y = e.MousePoint.Y;

        // which button was clicked?  we're only going to zoom 
        // out on a left button click.
        _button = e.MouseButtons;
    }

    /// <summary>
    /// When the mouse button is up (has been released) we want to know
    /// if the coordinates are up and to the left of where the mouse down
    /// happened.  If this is true and the button is also the left
    /// button than we know we can remove any zoom and return to the
    /// charts default visible range.
    /// </summary>
    /// <param name="e"></param>
    public override void OnModifierMouseUp(ModifierMouseArgs e)
    {
        // get viewport from the parent surface
        AutoScrollingViewportManager viewport = ParentSurface.ViewportManager as AutoScrollingViewportManager;

        // are we left and up from where the mouse was clicked?
        if (MouseButtons.Left == _button &&
            e.MousePoint.X < _x &&
            e.MousePoint.Y < _y)
        {
            // set its state to be at our custom extents
            viewport.ViewState = ChartViewStateType.Extents;

            // despite telling the surface to animate a zoom to extents
            // the animation never happens; however, I do get the benefit of the
            // chart surface notifying the viewport that the X and Y ranges
            // are being drawn.
            ParentSurface.AnimateZoomExtents(new System.TimeSpan(0, 0, 0, 0, 500));

            e.Handled = true;
        }
        else
        {
            // set view port state to "zoomed" so the viewport manager
            // can accept the zoomed state of the chart surface as opposed to
            // overriding it.  I.e. let the chart manage zooming in.
            viewport.ViewState = ChartViewStateType.Zoomed;

            // tell the chart to continue the process of what happens to the 
            // surface when a ZoomExtentsModifier OnModifierMouseUp() event
            // is raised.  I.e. commit to the zoom.
            base.OnModifierMouseUp(e);
        }
    }
}

Now my custom ViewPortManager can handle when the chart is being re-drawn and use the value of my custom view state when either allowing the zoom to happen or to ignore it and “zoom” to my custom extents. In the later I’m not really zooming but instead am setting the visible range to what I want to see. This is relatively close to where the extents would be by default; however, they are more specifically defined by an application requirement where additional padding is shown on all axes based on the dynamic content of the series displayed. See below. This is an example that only shows overriding the X range but the code for the Y range is effectively identical.

public class AutoScrollingViewportManager : DefaultViewportManager
{
    public ChartViewStateType ViewState = ChartViewStateType.Extents;

    protected override IRange OnCalculateNewYRange(IAxis axis, RenderPassInfo renderPassInfo)
    {
        IRange result = null;

        // if the user has zoomed we aren't going to apply our rules       
        if (ViewState == ChartViewStateType.Zoomed)
            result = axis.VisibleRange.AsDoubleRange();

        // the user is not currently involved in sizing the Y axis, we're
        // going to get involved now and enforce our rules
        else if (ViewState == ChartViewStateType.Extents)
        {                               
                // this will allow me to access the measurement(s) on the chart which
                // will in turn allow me to define my min and max X values.
                MyChartViewModel model = axis.DataContext as MyChartViewModel;
                model.CalculateActualRange();

                // set the Y range
                result = new DoubleRange(model.LowestY, model.HighestY);                
        }

        // respond with how the chart range should be defined
        return result;
    }
}

I make reference to a model (MyChartViewModel) and a method CalculateActualRange(). Their relevance is that they are defining the boundaries that should be used given all the series that are currently bound to the chart surface.

  • Andrew Burnett-Thompson
    Nice! The animated zoom extents is surprisingly difficult, because the Y-range depends on the X-range, but the X-range is being animated, so what we do (internally) is calculate the X-ranges for all XAxes first, then calculate what the Y-ranges would be from those X-ranges, then animate the X, Y ranges for all axis to the desired level … not simple! Glad you got a solution and thanks for posting!
  • Chris Kirkman
    Of course, glad to share. You guys make such a great product, when I find ways I’ve been able to extend it for my requirements and I feel that can help others I’m happy to share.
  • You must to post comments
Showing 1 result
Your Answer

Please first to submit.