ASE Home Page Products Download Purchase Support About ASE
ChartDirector Support
Forum HomeForum Home   SearchSearch

Message ListMessage List     Post MessagePost Message

  Using WinChartViewer.syncLinearAxisWithViewPort for "y" id syncs to first chart loaded
Posted by NorthEast on Dec-06-2022 03:30
Hello, I am trying to implement a winform that has two buttons that each loads its own chart. Each chart needs to have zooming functionality that allows for zooming onto a region without data, and a resetzoom function to revert to the original chart as loaded by the button.

Following the examples in the documentation for "simple line chart" and "simple bar chart", I have mapped button 1 to the line chart and button 2 to the bar chart, and have zooming and a resetzoom function working for each chart individually.

The problem I am running into is when I try to first load one chart, and then load the other without restarting the program. When the second is loaded, the x axis is correctly matches with the new chart but the y-axis matches the old chart.

The way I update the axes ranges when the viewport changes is with the "syncLinearAxisWithViewPort" (or syncDate if needed) method. If I comment out the sync method call for the y axis, then loading one after the other works as intended, but then zooming works as it does in the "simple zooming and scrolling" example where if you zoom in on a region without data, it will instead zoom in on the data between the bounding x values of the zooming region.

Any insight into why syncing the y axis syncs to the yaxis of the first chart would be very helpful, thank you.

Below is my code:
public partial class Plot2D : Form {

        public Plot2D()
        {
            InitializeComponent();
            winChart.ZoomDirection = WinChartDirection.HorizontalVertical;
        }

        private void ExitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void winChart_MouseDown_test(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left) {
                winChart.MouseUsage = WinChartMouseUsage.ZoomIn;
            }
        }

        private void winChart_MouseClick_test(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right) {
                ResetZoom();
            }
        }

        private void winChart_MouseUp_test(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left) {
                winChart.MouseUsage = WinChartMouseUsage.Default;
                winChart.Cursor = Cursors.Default;
            }
        }

        private void winChart_ViewPortChanged_test(object sender, WinViewPortEventArgs e)
        {
            if (chart1) {
                if (e.NeedUpdateChart) {
                    drawChart(winChart);
                }

            }

            if (!chart1) {
                if (e.NeedUpdateChart) {
                    drawBarChart(winChart);
                }
            }

            if (e.NeedUpdateImageMap) {
                updateImageMap(winChart);
            }
        }

        private void ResetZoom()
        {
            winChart.ViewPortLeft = 0.0;
            winChart.ViewPortTop = 0.0;
            winChart.ViewPortWidth = 1.0;
            winChart.ViewPortHeight = 1.0;
            if (chart1) {
                drawChart(winChart);
            }

            if (!chart1) {
                drawBarChart(winChart);
            }
        }

        private void drawChart(WinChartViewer viewer)
        {
            var viewPortStartDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortLeft));
            var viewPortEndDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortRight));

            int startIndex = (int)Math.Floor(Chart.bSearch(labels, viewPortStartDate));
            int endIndex = (int)Math.Ceiling(Chart.bSearch(labels, viewPortEndDate));
            int noOfPoints = endIndex - startIndex + 1;

            //Extract Data array that are visible
            DateTime[] viewPortLabels = Chart.arraySlice(labels, startIndex, noOfPoints);
            double[] viewPortData = Chart.arraySlice(data, startIndex, noOfPoints);

            var width = winChart.Width;
            var height = winChart.Height;

            XYChart chart = new XYChart(width, height);

            chart.setDefaultFonts("Consolas");
            var chartTitle = chart.addTitle("Title");
            var xAxisTitle = chart.xAxis().setTitle("X axis");
            var yAxisTitle = chart.yAxis().setTitle("Y axis");

            chart.setPlotArea(yAxisTitle.getWidth(), chartTitle.getHeight(), width - yAxisTitle.getWidth(), height - xAxisTitle.getHeight());

            LineLayer layer = chart.addLineLayer(viewPortData.ToArray());
            layer.setLineWidth(2);

            layer.setFastLineMode();

            layer.setXData(viewPortLabels);
            layer.addDataSet(viewPortData, 0xff0000);

            viewer.syncDateAxisWithViewPort("x", chart.xAxis());
            viewer.syncLinearAxisWithViewPort("y", chart.yAxis());

            chart.xAxis().setTickDensity(25);

            winChart.Chart = chart;
        }

        private void updateImageMap(WinChartViewer viewer)
        {
            if (winChart.ImageMap == null) {
                winChart.ImageMap = winChart.Chart.getHTMLImageMap("");
            }
        }

        private double[] data;
        private DateTime[] labels;

        private void LoadData()
        {
            var rand = new Random();
            var a = 5;
            List<double> d = new List<double>();
            List<DateTime> l = new List<DateTime>();
            for (var dt = DateTime.Today; dt < DateTime.Today.AddDays(10); dt = dt.AddDays(1)) {
                d.Add(a);
                l.Add(dt);
                a += rand.Next() % 2;
            }

            data = d.ToArray();
            labels = l.ToArray();
        }

        private void initChartViewer(WinChartViewer viewer)
        {
            viewer.setFullRange("x", labels[0], labels[labels.Length - 1]);
        }
        //TimeSeries Chart
        private void LoadButton_Click(object sender, EventArgs e)
        {
            chart1 = true;
            LoadData();

            initChartViewer(winChart);
            ResetZoom();

            winChart.updateViewPort(true,true);
        }

        private bool chart1;
        //Bar Chart
        private void Load2_Click(object sender, EventArgs e)
        {
            chart1= false;
            LoadBarData();

            initChartViewer(winChart);
            ResetZoom();

            winChart.updateViewPort(true, true);
        }


        private void LoadBarData()
        {
            double[] d = { 85, 156, 179.5, 211, 123 };
            DateTime[] lab = {DateTime.Today.AddDays(-110), DateTime.Today.AddDays(-111), DateTime.Today.AddDays(-112), DateTime.Today.AddDays(-113), DateTime.Today.AddDays(-114)};


            data = d.ToArray();
            labels = lab;
        }



        private void drawBarChart(WinChartViewer viewer)
        {
            var viewPortStartDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortLeft));
            var viewPortEndDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortRight));

            int startIndex = (int)Math.Floor(Chart.bSearch(labels, viewPortStartDate));
            int endIndex = (int)Math.Ceiling(Chart.bSearch(labels, viewPortEndDate));
            int noOfPoints = endIndex - startIndex + 1;

            //Extract Data array that are visible
            DateTime[] viewPortLabels = Chart.arraySlice(labels, startIndex, noOfPoints);
            double[] viewPortData = Chart.arraySlice(data, startIndex, noOfPoints);

            var width = winChart.Width;
            var height = winChart.Height;

            XYChart chart = new XYChart(width, height);

            chart.setDefaultFonts("Consolas");
            var chartTitle = chart.addTitle("Title");
            var xAxisTitle = chart.xAxis().setTitle("X axis");
            var yAxisTitle = chart.yAxis().setTitle("Y axis");

            chart.setPlotArea(yAxisTitle.getWidth(), chartTitle.getHeight(), width - yAxisTitle.getWidth(), height - xAxisTitle.getHeight());

            BarLayer layer = chart.addBarLayer(viewPortData.ToArray());

            layer.setXData(viewPortLabels);
            layer.addDataSet(viewPortData, 0xff0000);

            viewer.syncDateAxisWithViewPort("x", chart.xAxis());
            viewer.syncLinearAxisWithViewPort("y", chart.yAxis());

            chart.xAxis().setTickDensity(25);

            winChart.Chart = chart;
        }

  Re: Using WinChartViewer.syncLinearAxisWithViewPort for "y" id syncs to first chart loaded
Posted by Peter Kwan on Dec-06-2022 13:38
Hi NorthEast,

When you set a chart to the WinChartViewer using "winChart.Chart = chart;", ChartDirector would not know if it is a completely new chart using different data, or a continuation of the previous chart. By default, it will keep the existing viewport configuration unchanged.

For your case, the x-axis does change, probably because your code change it with the line:

viewer.setFullRange("x", labels[0], labels[labels.Length - 1]);

Your code does not set the full range of "y". In this case, ChartDirector will keep using the existing range. If there is no existing range (the chart is the first chart), it will auto-learn the y range from the first chart.

To solve the problem, you can use:

viewer.setFullRange("y", 0, 0);

The above sets the y full range to an invalid range, so ChartDirector will ignore it and auto-learn the y-axis range from the next chart.

Best Regards
Peter Kwan

  Re: Using WinChartViewer.syncLinearAxisWithViewPort for "y" id syncs to first chart loaded
Posted by NorthEast on Dec-08-2022 23:08
Thank you for the response! What you suggested works, but if you don't mind explaining a little further, I also tried changing the setFullRange for the x axis to 0,0 as well since it would be simpler if I didn't have to feed it the min and max values every time, but the method doesn't automatically learn the correct x scale like it does for the y axis. Does it have to do with being dateTimes?

Specifically I end up seeing an x axis that has far too small a range (I believe it is only showing the first data point) when I use "viewer.setFullRange("x",0,0) instead of what I have above.

Thanks again!

  Re: Using WinChartViewer.syncLinearAxisWithViewPort for "y" id syncs to first chart loaded
Posted by Peter Kwan on Dec-09-2022 03:46
Hi NorthEast,

If you do not set the x range, ChartDirector will auto-learn it by using the data in the first chart. However, in the sample code, the x range is used before the first chart is drawn. The code is at the first two lines of the drawChart function:

var viewPortStartDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortLeft));
var viewPortEndDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortRight));

The code is used to select the data that you need to provide to ChartDirector. As the x range is not set, the selected data are invalid. So the first chart has invalid data, and ChartDirector auto-learns the invalid x range.

You can completely remove the selection code and just plot the labels and data (instead of
viewPortLabels and viewPortData), and it will work. The XY Zooming and Scrolling sample code is using this method:

https://www.advsofteng.com/doc/cdnet.htm#xyzoomscroll.htm

You may wonder why the sample code "select" the data if we can just pass all the data to ChartDirector to let it plot only the part that is within the viewport. There are two reasons:

(a) If there are lots of data (eg. 10 million data points), even if only a few data points are visible in the viewport, ChartDirector must still examine all of them to determine which ones need to be plotted. However, if the data are sorted in the x direction, we can use binary search to very quickly select the range to be plotted, almost at zero cost. But ChartDirector cannot know if the data are sorted before examining all of them. Only your code can know.

(b) In many applications, the first chart does not contain all the data. For example, in a financial chart, the code may initially loads only the last 6 months of stock price, even though the full range can be much longer. So ChartDirector cannot learn the full range from the chart and cannot set the scrollbar properly.

In our "XY Zooming and Scrolling", both the x and y data are not sorted, and the first chart is the full data, so we can auto-learning both axes.

In my opinion, if the x data are sorted, the cost of calling setFullRange is almost zero, as the full range is just the first item to the last item. You can put the setFullRange in the first line of the drawChart code if you like (provided you do not have any other code that needs to use the full range earlier).

Best Regards
Peter Kwan