kirsan31 / winforms-datavisualization

System.Windows.Forms.DataVisualization provides charting for WinForms applications.
MIT License
47 stars 19 forks source link

X-Axis scroll on zoomed in chart area not scrolling with scrollbar thumb #31

Closed powertec-dan closed 1 year ago

powertec-dan commented 1 year ago

I've implemented a selection of a portion of a chart using mouse events. down -> move -> up as per some of the examples. Once I have the drag area I use that to zoom the X-Axis of the chart.

This all works perfectly. However, when I try and scroll the zoomed area with the thumb of the scrollbar, it doesn't work.

Clicking in the scrollbar area to either side of the thumb produces a scroll and clicking the arrow buttons of trhe scrollbar also work, just not the thumb,

Scrollbar thumb

In the image you can see the thumb registers the click, but refuses to budge to either side when dragged,

I can't find anything in the examples to influence the behaviour of this thumb and I'm at a bit of a loss. All of the examples scroll their zoomed chart areas ok.

Any idea what I am doing wrong?

Cheers, Dan

kirsan31 commented 1 year ago

It's strange... Can you provide a repro project to test this?

powertec-dan commented 1 year ago

Here's some code that reproduces the problem. I'm running this under dotnet 6 on a Windows 11 system

To select an area in the chart, press shift and left-button click and drag the mouse in the chart area. Then click and try and drag the scrollbar thumb

using System.Windows.Forms.DataVisualization.Charting;

namespace charttest;

public class Program : Form
{
    private const string DefaultKey = "Default";
    private Chart _chart = new();
    private double _zoomStartPoint = 0;
    private double _zoomEndPoint = 0;
    private bool _isZooming = false;

    public Program()
    {
        AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        ClientSize = new System.Drawing.Size(1024, 768);
        Text = "Chart Zoom Scrolling Test";

        ChartArea chartArea = new ChartArea(DefaultKey);
        chartArea.AxisX.LabelStyle.Format = "yyyy/MM/dd-HH:mm:ss";
        chartArea.AxisX.LabelStyle.Angle = 90;
        chartArea.AxisX.LabelAutoFitStyle = LabelAutoFitStyles.DecreaseFont | LabelAutoFitStyles.IncreaseFont | LabelAutoFitStyles.WordWrap;
        chartArea.CursorX.IsUserEnabled = true;
        chartArea.CursorX.IsUserSelectionEnabled = true;
        chartArea.CursorX.SelectionColor = System.Drawing.Color.LightBlue;
        chartArea.AxisX.ScaleView.Zoomable = true;
        chartArea.AxisX.ScrollBar.IsPositionedInside = true;
        _chart.ChartAreas.Add(chartArea);

        Legend legend = new(DefaultKey)
        {
            BackColor = Color.Transparent,
            BorderColor = Color.Black,
            BorderWidth = 2,
            BorderDashStyle = ChartDashStyle.Solid,
            ShadowOffset = 2
        };
        _chart.Legends.Add(legend);
        _chart.MouseDown += ChartMouseDown;
        _chart.MouseMove += ChartMouseMove;
        _chart.MouseUp += ChartMouseUp;
        _chart.AntiAliasing = AntiAliasingStyles.None;

        _chart.Location = new System.Drawing.Point(0, 0);
        _chart.Size = new Size(Width, Height);
        _chart.Dock = DockStyle.Fill;

        Controls.Add(_chart);

        List<DateTime> timeValues = new();
        List<int> fieldValues = new();

        Random rnd = new();
        DateTime currentTime = DateTime.Now - TimeSpan.FromHours(1);
        int currentValue = rnd.Next(-200, 201);
        for (int i = 0; i < 7200; i++)
        {
            timeValues.Add(currentTime);
            currentTime += TimeSpan.FromMilliseconds(500);

            fieldValues.Add(currentValue);
            int nextValue = rnd.Next(-10, 11);
            if (currentValue + nextValue > 200 || currentValue + nextValue < -200)
                nextValue *= -1;

            currentValue += nextValue;
        }

        Series series = new Series();
        series.ChartType = SeriesChartType.FastLine;
        series.Points.DataBindXY(timeValues, fieldValues);
        series.Legend = DefaultKey;
        series.LegendText = "Zoom Test";
        _chart.Series.Add(series);

    }

    private void ChartMouseDown(object? sender, MouseEventArgs e)
    {
        if (sender != _chart.ChartAreas[DefaultKey].AxisX.ScrollBar)
        {
            if (e.Button == MouseButtons.Left)
            {
                _zoomStartPoint = _chart.ChartAreas[DefaultKey].AxisX.PixelPositionToValue(e.X);
            }
        }
    }

    private void ChartMouseMove(object? sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left && (Control.ModifierKeys & Keys.Shift) != Keys.None)
        {
            _isZooming = true;
            _zoomEndPoint = _chart.ChartAreas[DefaultKey].AxisX.PixelPositionToValue(Math.Min(_chart.Size.Width, e.X));
            _zoomEndPoint = Math.Max(0, _zoomEndPoint);

            _chart.ChartAreas[DefaultKey].CursorX.SelectionStart = Math.Min(_zoomStartPoint, _zoomEndPoint);
            _chart.ChartAreas[DefaultKey].CursorX.SelectionEnd = Math.Max(_zoomStartPoint, _zoomEndPoint);
        }
    }

    private void ChartMouseUp(object? sender, MouseEventArgs e)
    {
        if (_isZooming)
        {
            _chart.ChartAreas[DefaultKey].CursorX.SelectionStart = double.NaN;
            _chart.ChartAreas[DefaultKey].CursorX.SelectionEnd = double.NaN;
            _zoomEndPoint = _chart.ChartAreas[DefaultKey].AxisX.PixelPositionToValue(Math.Min(_chart.Size.Width, e.X));
            _zoomEndPoint = Math.Max(0, _zoomEndPoint);
            SetZoom();
        }
    }

    private void SetZoom()
    {
        double viewStart = Math.Min(_zoomStartPoint, _zoomEndPoint);
        double viewEnd = Math.Max(_zoomStartPoint, _zoomEndPoint);
        _chart.ChartAreas[DefaultKey].AxisX.ScaleView.Zoom(viewStart, viewEnd);
        _isZooming = false;
    }

    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new Program());
    }    
}
kirsan31 commented 1 year ago

Properties are responsible for this scrolling are: chartArea.AxisX.ScaleView.SmallScrollMinSizeType and chartArea.AxisX.ScaleView.SmallScrollMinSize. And yes, msdn are very very confusing with description of them 😥 The default value for SmallScrollMinSize is 1. Because DateTime is OLE automation date in chart, your scroll step is one day, but your scroll area is much much smaller. So you need to set SmallScrollMinSizeType to something less than day or SmallScrollMinSize < 1 or use more flexible solution:

_chart.AxisViewChanged += _chart_AxisViewChanged;

private void _chart_AxisViewChanged(object? sender, ViewEventArgs e)
{
    var range = _chart.ChartAreas[0].AxisX.ScaleView.ViewMaximum - _chart.ChartAreas[0].AxisX.ScaleView.ViewMinimum;
    _chart.ChartAreas[0].AxisX.ScaleView.SmallScrollMinSize = range / 20;
}

P.s. you no need SetZoom() method - setting the cursor position will automatically zoom chart area.

powertec-dan commented 1 year ago

Awesome!

I used your suggested flexible solution and it works great.

Thanks heaps for the help. I really appreciate it :)