ChartDirector 7.1 (.NET Edition)

Mega Chart Zoom/Scroll (Windows)




NOTE: For conciseness, some of the following descriptions only mention WinChartViewer. Those descriptions apply to WPFChartViewer as well.

This example demonstrates a zoomable and scrollable chart with huge datasets containing 3 x 10M data points.

The zooming, scrolling and track cursor part of the code is similar to Zooming and Scrolling with Track Line (1) (Windows), which you may refer to for more details. In the following explanation, we will focus on the code that handles the huge number of data points.

Source Code Listing

[Windows Forms - C# version] NetWinCharts\CSharpWinCharts\frmmegazoomscroll.cs
using System; using System.Windows.Forms; using System.Collections.Generic; using System.Threading.Tasks; using ChartDirector; namespace CSharpChartExplorer { public partial class FrmMegaZoomScroll : Form { // In this example, we plot 3 data series, each with 10,000,000 data points. // So the total is 30,000,000 data points. private const int sampleSize = 10000000; // Data arrays private double[] timeStamps; private double[] dataSeriesA; private double[] dataSeriesB; private double[] dataSeriesC; // Data Accelerator for handle large data sets private DataAccelerator fastData; // Will set to true at the end of initialization - prevents events from firing before the // controls are properly initialized. private bool hasFinishedInitialization = false; public FrmMegaZoomScroll() { InitializeComponent(); } // // From Load Event Handler // private void FrmMegaZoomScroll_Load(object sender, EventArgs e) { // Generate random data loadData(); // Display initial Message PieChart c = new PieChart(800, 400, 0xd0e0ff); c.addTitle(Chart.Center, "<*block,halign=left,maxwidth=500*>" + "<*font=Arial Bold,size=18,underline=2*>Mega Chart Zoom/Scroll with Track Line<*/font*>" + "<*br*><*br*>This example demonstrates a chart with huge amount of data. We limit" + "it to 3 lines, each with 10 million points, so that it uses less than 1G of RAM" + "(320M to store the data, 600M to plot the data and handle the GUI)." + "<*br*><*br*><*br*><*br*>Press the Plot Chart button to plot the chart."); winChartViewer1.Image = c.makeImage(); } // // Generate random data // private void loadData() { // To speed up random number generation, we use 3 threads to generate the random data // for the 3 data series. The current thread is used for generating the timestamps. List<Task> tasks = new List<Task>(); tasks.Add(Task.Factory.StartNew(() => { dataSeriesA = new RanSeries(109).getSeries2(sampleSize, 2500, -1, 1); })); tasks.Add(Task.Factory.StartNew(() => { dataSeriesB = new RanSeries(110).getSeries2(sampleSize, 2500, -1, 1); })); tasks.Add(Task.Factory.StartNew(() => { dataSeriesC = new RanSeries(111).getSeries2(sampleSize, 2500, -1, 1); })); timeStamps = new double[sampleSize]; for (int i = 0; i < sampleSize; ++i) timeStamps[i] = i; Task.WaitAll(tasks.ToArray()); } // // User clicks on the Plot Chart pushbutton // private void plotChartPB_Click(object sender, EventArgs e) { if (hasFinishedInitialization) return; List<Task> tasks = new List<Task>(); // Use DataAccerlerate the accelerate rendering. To speed up, we create two threads to // process two of the data series, and use the current thread to process the third series. fastData = new DataAccelerator(timeStamps, sampleSize); tasks.Add(Task.Factory.StartNew(() => { fastData.setDataSeries("mA", dataSeriesA); })); tasks.Add(Task.Factory.StartNew(() => { fastData.setDataSeries("mB", dataSeriesB); })); fastData.setDataSeries("mC", dataSeriesC); Task.WaitAll(tasks.ToArray()); // Initialize the WinChartViewer initChartViewer(winChartViewer1); hasFinishedInitialization = true; // Trigger the ViewPortChanged event to draw the chart winChartViewer1.updateViewPort(true, true); } // // Initialize the WinChartViewer // private void initChartViewer(WinChartViewer viewer) { // Set the full x range to be the duration of the data viewer.setFullRange("x", timeStamps[0], timeStamps[sampleSize - 1]); // Initialize the view port to show the latest 20% of the time range viewer.ViewPortWidth = 0.2; viewer.ViewPortLeft = 1 - viewer.ViewPortWidth; // Set the maximum zoom to 10 points viewer.ZoomInWidthLimit = 10.0 / timeStamps.Length; // Enable mouse wheel zooming by setting the zoom ratio to 1.1 per wheel event viewer.MouseWheelZoomRatio = 1.1; // Initially set the mouse usage to Drag to Scroll mode pointerPB.Checked = true; viewer.MouseUsage = WinChartMouseUsage.ScrollOnDrag; } // // Pointer (Drag to Scroll) button event handler // private void pointerPB_CheckedChanged(object sender, EventArgs e) { if (((RadioButton)sender).Checked) winChartViewer1.MouseUsage = WinChartMouseUsage.ScrollOnDrag; } // // Zoom In button event handler // private void zoomInPB_CheckedChanged(object sender, EventArgs e) { if (((RadioButton)sender).Checked) winChartViewer1.MouseUsage = WinChartMouseUsage.ZoomIn; } // // Zoom Out button event handler // private void zoomOutPB_CheckedChanged(object sender, EventArgs e) { if (((RadioButton)sender).Checked) winChartViewer1.MouseUsage = WinChartMouseUsage.ZoomOut; } // // The ViewPortChanged event handler. This event occurs if the user scrolls or zooms in // or out the chart by dragging or clicking on the chart. It can also be triggered by // calling WinChartViewer.updateViewPort. // private void winChartViewer1_ViewPortChanged(object sender, WinViewPortEventArgs e) { if (!hasFinishedInitialization) return; // In addition to updating the chart, we may also need to update other controls that // changes based on the view port. updateControls(winChartViewer1); // Update the chart if necessary if (e.NeedUpdateChart) drawChart(winChartViewer1); } // // Update controls when the view port changed // private void updateControls(WinChartViewer viewer) { // Update the scroll bar to reflect the view port position and width of the view port. hScrollBar1.Enabled = viewer.ViewPortWidth < 1; hScrollBar1.LargeChange = (int)Math.Ceiling(viewer.ViewPortWidth * (hScrollBar1.Maximum - hScrollBar1.Minimum)); hScrollBar1.SmallChange = (int)Math.Ceiling(hScrollBar1.LargeChange * 0.1); hScrollBar1.Value = (int)Math.Round(viewer.ViewPortLeft * (hScrollBar1.Maximum - hScrollBar1.Minimum)) + hScrollBar1.Minimum; } // // The scroll bar event handler // private void hScrollBar1_ValueChanged(object sender, EventArgs e) { // When the view port is changed (user drags on the chart to scroll), the scroll bar will get // updated. When the scroll bar changes (eg. user drags on the scroll bar), the view port will // get updated. This creates an infinite loop. To avoid this, the scroll bar can update the // view port only if the view port is not updating the scroll bar. if (hasFinishedInitialization && !winChartViewer1.IsInViewPortChangedEvent) { // Set the view port based on the scroll bar winChartViewer1.ViewPortLeft = ((double)(hScrollBar1.Value - hScrollBar1.Minimum)) / (hScrollBar1.Maximum - hScrollBar1.Minimum); // Trigger a view port changed event to update the chart winChartViewer1.updateViewPort(true, false); } } // // Draw the chart. // private void drawChart(WinChartViewer viewer) { // Get the start date and end date that are visible on the chart. double viewPortStartDate = viewer.getValueAtViewPort("x", viewer.ViewPortLeft); double viewPortEndDate = viewer.getValueAtViewPort("x", viewer.ViewPortRight); fastData.setSubsetRange(viewPortStartDate, viewPortEndDate); // // At this stage, we have extracted the visible data. We can use those data to plot the chart. // //================================================================================ // Configure overall chart appearance. //================================================================================ // Create an XYChart object of size 800 x 400 pixels XYChart c = new XYChart(800, 400); // Set the plotarea at (0, 0) with width 1 pixel less than chart width, and height 20 pixels // less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff) // as background. Set border to transparent and grid lines to white (ffffff). c.setPlotArea(0, 0, c.getWidth() - 1, c.getHeight() - 20, c.linearGradientColor(0, 0, 0, c.getHeight() - 20, 0xf0f6ff, 0xa0c0ff), -1, Chart.Transparent, 0xffffff, 0xffffff); // In our code, we can overdraw the line slightly, so we clip it to the plot area. c.setClipping(); // Add a legend box at the right side using horizontal layout. Use 10pt Arial Bold as font. Set // the background and border color to Transparent and use line style legend key. LegendBox b = c.addLegend(c.getWidth() - 1, 10, false, "Arial Bold", 10); b.setBackground(Chart.Transparent); b.setAlignment(Chart.Right); b.setLineStyleKey(); // Set the x and y axis stems to transparent and the label font to 10pt Arial c.xAxis().setColors(Chart.Transparent); c.yAxis().setColors(Chart.Transparent); c.xAxis().setLabelStyle("Arial", 10); c.yAxis().setLabelStyle("Arial", 10, 0x336699); // Configure the y-axis label to be inside the plot area and above the horizontal grid lines c.yAxis().setLabelGap(-1); c.yAxis().setMargin(20); c.yAxis().setLabelAlignment(1); // Configure the x-axis labels to be to the left of the vertical grid lines c.xAxis().setLabelAlignment(1); //================================================================================ // Add data to chart //================================================================================ // Add line layers using the DataAccelerator. Each layer only supports one accelerated // series, so we add 3 layers for the 3 data series. // Add a line layer for the lines, using a line width of 2 pixels LineLayer layer = c.addLineLayer(fastData, "mA", 0xff0000, "Alpha"); layer.setLineWidth(2); LineLayer layer2 = c.addLineLayer(fastData, "mB", 0x00cc00, "Beta"); layer2.setLineWidth(2); LineLayer layer3 = c.addLineLayer(fastData, "mC", 0x0000ff, "Gamma"); layer3.setLineWidth(2); //================================================================================ // Configure axis scale and labelling //================================================================================ // Set the x-axis as a date/time axis with the scale according to the view port x range. viewer.syncLinearAxisWithViewPort("x", c.xAxis()); // For the automatic axis labels, set the minimum spacing to 75/40 pixels for the x/y axis. c.xAxis().setTickDensity(75); c.yAxis().setTickDensity(40); // Set the auto-scale margin to 0.05, and the zero affinity to 0.2 c.yAxis().setAutoScale(0.05, 0.05, 0.2); //================================================================================ // Output the chart //================================================================================ // We need to update the track line too. If the mouse is moving on the chart (eg. if // the user drags the mouse on the chart to scroll it), the track line will be updated // in the MouseMovePlotArea event. Otherwise, we need to update the track line here. if ((!viewer.IsInMouseMoveEvent) && viewer.IsMouseOnPlotArea) trackLineLabel(c, viewer.PlotAreaMouseX); viewer.Chart = c; } // // Draw track cursor when mouse is moving over plotarea // private void winChartViewer1_MouseMovePlotArea(object sender, MouseEventArgs e) { if (!hasFinishedInitialization) return; WinChartViewer viewer = (WinChartViewer)sender; trackLineLabel((XYChart)viewer.Chart, viewer.PlotAreaMouseX); viewer.updateDisplay(); // Hide the track cursor when the mouse leaves the plot area viewer.removeDynamicLayer("MouseLeavePlotArea"); } // // Draw track line with data labels // private void trackLineLabel(XYChart c, int mouseX) { // Clear the current dynamic layer and get the DrawArea object to draw on it. DrawArea d = c.initDynamicLayer(); // The plot area object PlotArea plotArea = c.getPlotArea(); // Get the data x-value that is nearest to the mouse, and find its pixel coordinate. double xValue = c.getNearestXValue(mouseX); int xCoor = c.getXCoor(xValue); // Draw a vertical track line at the x-position d.vline(plotArea.getTopY(), plotArea.getBottomY(), xCoor, d.dashLineColor(0x000000, 0x0101)); // Draw a label on the x-axis to show the track line position. string xlabel = "<*font,bgColor=000000*> " + xValue + " <*/font*>"; TTFText t = d.text(xlabel, "Arial Bold", 10); // Restrict the x-pixel position of the label to make sure it stays inside the chart image. int xLabelPos = Math.Max(0, Math.Min(xCoor - t.getWidth() / 2, c.getWidth() - t.getWidth())); t.draw(xLabelPos, plotArea.getBottomY() + 2, 0xffffff); // Iterate through all layers to draw the data labels for (int i = 0; i < c.getLayerCount(); ++i) { Layer layer = c.getLayerByZ(i); // The data array index of the x-value int xIndex = layer.getXIndexOf(xValue); // Iterate through all the data sets in the layer for (int j = 0; j < layer.getDataSetCount(); ++j) { ChartDirector.DataSet dataSet = layer.getDataSetByZ(j); // Get the color and position of the data label int color = dataSet.getDataColor(); int yCoor = c.getYCoor(dataSet.getPosition(xIndex), dataSet.getUseYAxis()); // Draw a track dot with a label next to it for visible data points in the plot area if ((yCoor >= plotArea.getTopY()) && (yCoor <= plotArea.getBottomY()) && (color != Chart.Transparent)) { d.circle(xCoor, yCoor, 4, 4, color, color); string label = "<*font,bgColor=" + color.ToString("x") + "*> " + c.formatValue( dataSet.getValue(xIndex), "{value|P4}") + " <*/font*>"; t = d.text(label, "Arial Bold", 10); // Draw the label on the right side of the dot if the mouse is on the left side the // chart, and vice versa. This ensures the label will not go outside the chart image. if (xCoor <= (plotArea.getLeftX() + plotArea.getRightX()) / 2) t.draw(xCoor + 5, yCoor, 0xffffff, Chart.Left); else t.draw(xCoor - 5, yCoor, 0xffffff, Chart.Right); } } } } } }

[Windows Forms - VB Version] NetWinCharts\VBNetWinCharts\frmmegazoomscroll.vb
Imports ChartDirector Imports System.Collections.Generic Imports System.Threading.Tasks Public Class FrmMegaZoomScroll ' In this example, we plot 3 data series, each with 10, 0, 0 data points. ' So the total Is 30, 0, 0 data points. Private Const sampleSize As Integer = 10000000 ' Data arrays Dim timeStamps As Double() Dim dataSeriesA As Double() Dim dataSeriesB As Double() Dim dataSeriesC As Double() ' Data Accelerator for handle large data sets Dim fastData As DataAccelerator ' Will set to true at the end of initialization - prevents events from firing before the ' controls are properly initialized. Private hasFinishedInitialization As Boolean ' ' From Load Event Handler ' Private Sub FrmMegaZoomScroll_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles MyBase.Load ' Load the data loadData() ' Display initial Message Dim c As PieChart = New PieChart(800, 400, &HD0E0FF) c.addTitle(Chart.Center, "<*block,halign=left,maxwidth=500*>" & "<*font=Arial Bold,size=18,underline=2*>Mega Chart Zoom/Scroll with Track Line<*/font*>" & "<*br*><*br*>This example demonstrates a chart with huge amount of data. We limit" & "it to 3 lines, each with 10 million points, so that it uses less than 1G of RAM" & "(320M to store the data, 600M to plot the data and handle the GUI)." & "<*br*><*br*><*br*><*br*>Press the Plot Chart button to plot the chart.") winChartViewer1.Image = c.makeImage() End Sub ' ' Generate random data ' Private Sub loadData() ' To speed up random number generation, we use 3 threads to generate the random data ' for the 3 data series. The current thread Is used for generating the timestamps. Dim tasks As New List(Of Task) tasks.Add(Task.Factory.StartNew( Sub() dataSeriesA = New RanSeries(109).getSeries2(sampleSize, 2500, -1, 1) End Sub )) tasks.Add(Task.Factory.StartNew( Sub() dataSeriesB = New RanSeries(110).getSeries2(sampleSize, 2500, -1, 1) End Sub )) tasks.Add(Task.Factory.StartNew( Sub() dataSeriesC = New RanSeries(111).getSeries2(sampleSize, 2500, -1, 1) End Sub )) timeStamps = New Double(sampleSize) {} For i As Integer = 0 To sampleSize - 1 timeStamps(i) = i Next Task.WaitAll(tasks.ToArray()) End Sub ' ' User clicks on the Plot Chart pushbutton ' Private Sub plotChartPB_Click(sender As Object, e As EventArgs) Handles plotChartPB.Click If hasFinishedInitialization Then Exit Sub End If Dim tasks As New List(Of Task) ' Use DataAccerlerate the accelerate rendering. To speed up, we create two threads to ' process two of the data series, And use the current thread to process the third series. fastData = New DataAccelerator(timeStamps, sampleSize) tasks.Add(Task.Factory.StartNew( Sub() fastData.setDataSeries("mA", dataSeriesA) End Sub )) tasks.Add(Task.Factory.StartNew( Sub() fastData.setDataSeries("mB", dataSeriesB) End Sub )) fastData.setDataSeries("mC", dataSeriesC) Task.WaitAll(tasks.ToArray()) ' Initialize the WinChartViewer initChartViewer(winChartViewer1) hasFinishedInitialization = True ' Trigger the ViewPortChanged event to draw the chart winChartViewer1.updateViewPort(True, True) End Sub ' ' Initialize the WinChartViewer ' Private Sub initChartViewer(ByVal viewer As WinChartViewer) ' Set the full x range to be the duration of the data viewer.setFullRange("x", timeStamps(0), timeStamps(sampleSize - 1)) ' Initialize the view port to show the latest 20% of the time range viewer.ViewPortWidth = 0.2 viewer.ViewPortLeft = 1 - viewer.ViewPortWidth ' Set the maximum zoom to 10 points viewer.ZoomInWidthLimit = 10.0 / timeStamps.Length ' Enable mouse wheel zooming by setting the zoom ratio to 1.1 per wheel event viewer.MouseWheelZoomRatio = 1.1 ' Initially set the mouse usage to "Pointer" mode (Drag to Scroll mode) pointerPB.Checked = True viewer.MouseUsage = WinChartMouseUsage.ScrollOnDrag End Sub ' ' Pointer (Drag to Scroll) button event handler ' Private Sub pointerPB_CheckedChanged(ByVal sender As Object, ByVal e As EventArgs) _ Handles pointerPB.CheckedChanged If sender.Checked Then winChartViewer1.MouseUsage = WinChartMouseUsage.ScrollOnDrag End If End Sub ' ' Zoom In button event handler ' Private Sub zoomInPB_CheckedChanged(ByVal sender As Object, ByVal e As EventArgs) _ Handles zoomInPB.CheckedChanged If sender.Checked Then winChartViewer1.MouseUsage = WinChartMouseUsage.ZoomIn End If End Sub ' ' Zoom Out button event handler ' Private Sub zoomOutPB_CheckedChanged(ByVal sender As Object, ByVal e As EventArgs) _ Handles zoomOutPB.CheckedChanged If sender.Checked Then winChartViewer1.MouseUsage = WinChartMouseUsage.ZoomOut End If End Sub ' ' The ViewPortChanged event handler. This event occurs if the user scrolls or zooms in ' or out the chart by dragging or clicking on the chart. It can also be triggered by ' calling WinChartViewer.updateViewPort. ' Private Sub winChartViewer1_ViewPortChanged(ByVal sender As Object, ByVal e As WinViewPortEventArgs) Handles winChartViewer1.ViewPortChanged If Not hasFinishedInitialization Then Exit Sub End If ' In addition to updating the chart, we may also need to update other controls that ' changes based on the view port. updateControls(winChartViewer1) ' Update the chart if necessary If e.NeedUpdateChart Then drawChart(winChartViewer1) End If End Sub ' ' Update controls when the view port changed ' Private Sub updateControls(ByVal viewer As WinChartViewer) ' In this demo, we need to update the scroll bar to reflect the view port position and ' width of the view port. hScrollBar1.Enabled = viewer.ViewPortWidth < 1 hScrollBar1.LargeChange = Math.Ceiling(viewer.ViewPortWidth * (hScrollBar1.Maximum - hScrollBar1.Minimum)) hScrollBar1.SmallChange = Math.Ceiling(hScrollBar1.LargeChange * 0.1) hScrollBar1.Value = Math.Round(viewer.ViewPortLeft * (hScrollBar1.Maximum - hScrollBar1.Minimum)) + hScrollBar1.Minimum End Sub ' ' The scroll bar event handler ' Private Sub hScrollBar1_ValueChanged(ByVal sender As Object, ByVal e As EventArgs) _ Handles hScrollBar1.ValueChanged ' When the view port is changed (user drags on the chart to scroll), the scroll bar will get ' updated. When the scroll bar changes (eg. user drags on the scroll bar), the view port will ' get updated. This creates an infinite loop. To avoid this, the scroll bar can update the ' view port only if the view port is not updating the scroll bar. If hasFinishedInitialization And Not winChartViewer1.IsInViewPortChangedEvent Then ' Set the view port based on the scroll bar winChartViewer1.ViewPortLeft = (hScrollBar1.Value - hScrollBar1.Minimum) / (hScrollBar1.Maximum - hScrollBar1.Minimum) ' Trigger a view port changed event to update the chart winChartViewer1.updateViewPort(True, False) End If End Sub ' ' Draw the chart. ' Private Sub drawChart(ByVal viewer As WinChartViewer) ' Get the start date and end date that are visible on the chart. Dim viewPortStartDate As Double = viewer.getValueAtViewPort("x", viewer.ViewPortLeft) Dim viewPortEndDate As Double = viewer.getValueAtViewPort("x", viewer.ViewPortRight) fastData.setSubsetRange(viewPortStartDate, viewPortEndDate) ' ' At this stage, we have extracted the visible data. We can use those data to plot the chart. ' '================================================================================ ' Configure overall chart appearance. '================================================================================ ' Create an XYChart object of size 800 x 400 pixels Dim c As XYChart = New XYChart(800, 400) ' Set the plotarea at (0, 0) with width 1 pixel less than chart width, And height 20 pixels ' less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff) ' as background. Set border to transparent And grid lines to white (ffffff). c.setPlotArea(0, 0, c.getWidth() - 1, c.getHeight() - 20, c.linearGradientColor(0, 0, 0, c.getHeight() - 20, &HF0F6FF, &HA0C0FF), -1, Chart.Transparent, &HFFFFFF, &HFFFFFF) ' As the data can lie outside the plotarea in a zoomed chart, we need enable clipping. c.setClipping() ' Add a legend box at the right side using horizontal layout. Use 10pt Arial Bold as font. Set ' the background And border color to Transparent And use line style legend key. Dim b As LegendBox = c.addLegend(c.getWidth() - 1, 10, False, "Arial Bold", 10) b.setBackground(Chart.Transparent) b.setAlignment(Chart.Right) b.setLineStyleKey() ' Set the x And y axis stems to transparent And the label font to 10pt Arial c.xAxis().setColors(Chart.Transparent) c.yAxis().setColors(Chart.Transparent) c.xAxis().setLabelStyle("Arial", 10) c.yAxis().setLabelStyle("Arial", 10, &H336699) ' Configure the y-axis label to be inside the plot area And above the horizontal grid lines c.yAxis().setLabelGap(-1) c.yAxis().setMargin(20) c.yAxis().setLabelAlignment(1) ' Configure the x-axis labels to be to the left of the vertical grid lines c.xAxis().setLabelAlignment(1) '================================================================================ ' Add data to chart '================================================================================ ' Add line layers using the DataAccelerator. Each layer only supports one accelerated ' series, so we add 3 layers for the 3 data series. ' Add a line layer for the lines, using a line width of 2 pixels Dim layer As LineLayer = c.addLineLayer(fastData, "mA", &HFF0000, "Alpha") layer.setLineWidth(2) Dim layer2 As LineLayer = c.addLineLayer(fastData, "mB", &HCC00, "Beta") layer2.setLineWidth(2) Dim layer3 As LineLayer = c.addLineLayer(fastData, "mC", &HFF, "Gamma") layer3.setLineWidth(2) '================================================================================ ' Configure axis scale and labelling '================================================================================ ' Set the x-axis as a date/time axis with the scale according to the view port x range. viewer.syncLinearAxisWithViewPort("x", c.xAxis()) ' For the automatic axis labels, set the minimum spacing to 75/40 pixels for the x/y axis. c.xAxis().setTickDensity(75) c.yAxis().setTickDensity(40) ' Set the auto-scale margin to 0.05, And the zero affinity to 0.2 c.yAxis().setAutoScale(0.05, 0.05, 0.2) '================================================================================ ' Output the chart '================================================================================ ' We need to update the track line too. If the mouse Is moving on the chart (eg. if ' the user drags the mouse on the chart to scroll it), the track line will be updated ' in the MouseMovePlotArea event. Otherwise, we need to update the track line here. If (Not viewer.IsInMouseMoveEvent) AndAlso viewer.IsMouseOnPlotArea Then trackLineLabel(c, viewer.PlotAreaMouseX) End If viewer.Chart = c End Sub ' ' Draw track cursor when mouse is moving over plotarea ' Private Sub winChartViewer1_MouseMovePlotArea(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles winChartViewer1.MouseMovePlotArea If Not hasFinishedInitialization Then Exit Sub End If Dim viewer As WinChartViewer = sender trackLineLabel(viewer.Chart, viewer.PlotAreaMouseX) viewer.updateDisplay() ' Hide the track cursor when the mouse leaves the plot area viewer.removeDynamicLayer("MouseLeavePlotArea") End Sub ' ' Draw track line with data labels ' Private Sub trackLineLabel(ByVal c As XYChart, ByVal mouseX As Integer) ' Clear the current dynamic layer and get the DrawArea object to draw on it. Dim d As DrawArea = c.initDynamicLayer() ' The plot area object Dim plotArea As PlotArea = c.getPlotArea() ' Get the data x-value that is nearest to the mouse, and find its pixel coordinate. Dim xValue As Double = c.getNearestXValue(mouseX) Dim xCoor As Integer = c.getXCoor(xValue) ' Draw a vertical track line at the x-position d.vline(plotArea.getTopY(), plotArea.getBottomY(), xCoor, d.dashLineColor(&H0, &H101)) ' Draw a label on the x-axis to show the track line position. Dim xlabel As String = "<*font,bgColor=000000*> " & xValue & " <*/font*>" Dim t As TTFText = d.text(xlabel, "Arial Bold", 10) ' Restrict the x-pixel position of the label to make sure it stays inside the chart image. Dim xLabelPos As Integer = Math.Max(0, Math.Min(xCoor - t.getWidth() / 2, c.getWidth() - t.getWidth( ))) t.draw(xLabelPos, plotArea.getBottomY() + 2, &HFFFFFF) ' Iterate through all layers to draw the data labels For i As Integer = 0 To c.getLayerCount() - 1 Dim layer As Layer = c.getLayerByZ(i) ' The data array index of the x-value Dim xIndex As Integer = layer.getXIndexOf(xValue) ' Iterate through all the data sets in the layer For j As Integer = 0 To layer.getDataSetCount() - 1 Dim dataSet As ChartDirector.DataSet = layer.getDataSetByZ(j) ' Get the color and position of the data label Dim color As Integer = dataSet.getDataColor() Dim yCoor As Integer = c.getYCoor(dataSet.getPosition(xIndex), dataSet.getUseYAxis()) ' Draw a track dot with a label next to it for visible data points in the plot area If (yCoor >= plotArea.getTopY()) And (yCoor <= plotArea.getBottomY()) And (color <> Chart.Transparent) And (Not String.IsNullOrEmpty(dataSet.getDataName())) Then d.circle(xCoor, yCoor, 4, 4, color, color) Dim label As String = "<*font,bgColor=" & Hex(color) & "*> " & c.formatValue( dataSet.getValue(xIndex), "{value|P4}") & " <*/font*>" t = d.text(label, "Arial Bold", 10) ' Draw the label on the right side of the dot if the mouse is on the left side the chart, ' and vice versa. This ensures the label will not go outside the chart image. If xCoor <= (plotArea.getLeftX() + plotArea.getRightX()) / 2 Then t.draw(xCoor + 5, yCoor, &HFFFFFF, Chart.Left) Else t.draw(xCoor - 5, yCoor, &HFFFFFF, Chart.Right) End If End If Next Next End Sub End Class

[WPF - XAML] NetWPFCharts\CSharpWPFCharts\MegaZoomScrollWindow.xaml
<Window x:Class="CSharpWPFCharts.MegaZoomScrollWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:CSharpWPFCharts" mc:Ignorable="d" xmlns:ChartDirector="clr-namespace:ChartDirector;assembly=netchartdir" UseLayoutRounding="True" Title="Mega Chart Zoom/Scroll with Track Line" Loaded="Window_Loaded" SizeToContent="WidthAndHeight" ResizeMode="NoResize" > <StackPanel> <DockPanel DockPanel.Dock="Top" LastChildFill="False" Background="#FFE8E8E8"> <RadioButton DockPanel.Dock="Left" x:Name="pointerPB" Style="{StaticResource {x:Type ToggleButton}}" Checked="pointerPB_Checked" Margin="4" > <Image Source="/icons/scroll_icon.png" Width="20" Height="20" Margin="6"/> </RadioButton> <RadioButton DockPanel.Dock="Left" x:Name="zoomInPB" Style="{StaticResource {x:Type ToggleButton}}" Checked="zoomInPB_Checked" Margin="4" > <Image Source="/icons/zoomin_icon.png" Width="20" Height="20" Margin="6"/> </RadioButton> <RadioButton DockPanel.Dock="Left" x:Name="zoomOutPB" Style="{StaticResource {x:Type ToggleButton}}" Checked="zoomOutPB_Checked" Margin="4" > <Image Source="/icons/zoomout_icon.png" Width="20" Height="20" Margin="6"/> </RadioButton> <Button DockPanel.Dock="Right" x:Name="plotChartPB" Content="Plot Chart" Width="200" Margin="4" Click="plotChartPB_Click"/> </DockPanel> <ChartDirector:WPFChartViewer x:Name="WPFChartViewer1" Width="800" Height="400" MouseMovePlotArea="WPFChartViewer1_MouseMovePlotArea" ViewPortChanged="WPFChartViewer1_ViewPortChanged" /> <ScrollBar x:Name="hScrollBar1" Orientation="Horizontal" ValueChanged="hScrollBar1_ValueChanged" /> </StackPanel> </Window>

[WPF - C#] NetWPFCharts\CSharpWPFCharts\MegaZoomScrollWindow.xaml.cs
using System; using System.Windows; using System.Windows.Input; using System.Collections.Generic; using System.Threading.Tasks; using ChartDirector; namespace CSharpWPFCharts { /// <summary> /// Interaction logic for MegaZoomScrollWindow.xaml /// </summary> public partial class MegaZoomScrollWindow : Window { // In this example, we plot 3 data series, each with 10,000,000 data points. // So the total is 30,000,000 data points. private const int sampleSize = 10000000; // Data arrays private double[] timeStamps; private double[] dataSeriesA; private double[] dataSeriesB; private double[] dataSeriesC; // Data Accelerator for handle large data sets private DataAccelerator fastData; // Will set to true at the end of initialization - prevents events from firing before the // controls are properly initialized. private bool hasFinishedInitialization = false; public MegaZoomScrollWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { // Generate random data loadData(); // Display initial Message PieChart c = new PieChart(800, 400, 0xd0e0ff); c.addTitle(Chart.Center, "<*block,halign=left,maxwidth=500*>" + "<*font=Arial Bold,size=18,underline=2*>Mega Chart Zoom/Scroll with Track Line<*/font*>" + "<*br*><*br*>This example demonstrates a chart with huge amount of data. We limit" + "it to 3 lines, each with 10 million points, so that it uses less than 1G of RAM" + "(320M to store the data, 600M to plot the data and handle the GUI)." + "<*br*><*br*><*br*><*br*>Press the Plot Chart button to plot the chart."); WPFChartViewer1.Chart = c; } // // Generate random data // private void loadData() { // To speed up random number generation, we use 3 threads to generate the random data // for the 3 data series. The current thread is used for generating the timestamps. List<Task> tasks = new List<Task>(); tasks.Add(Task.Factory.StartNew(() => { dataSeriesA = new RanSeries(109).getSeries2(sampleSize, 2500, -1, 1); })); tasks.Add(Task.Factory.StartNew(() => { dataSeriesB = new RanSeries(110).getSeries2(sampleSize, 2500, -1, 1); })); tasks.Add(Task.Factory.StartNew(() => { dataSeriesC = new RanSeries(111).getSeries2(sampleSize, 2500, -1, 1); })); timeStamps = new double[sampleSize]; for (int i = 0; i < sampleSize; ++i) timeStamps[i] = i; Task.WaitAll(tasks.ToArray()); } private void plotChartPB_Click(object sender, RoutedEventArgs e) { if (hasFinishedInitialization) return; List<Task> tasks = new List<Task>(); // Use DataAccerlerate the accelerate rendering. To speed up, we create two threads to // process two of the data series, and use the current thread to process the third series. fastData = new DataAccelerator(timeStamps, sampleSize); tasks.Add(Task.Factory.StartNew(() => { fastData.setDataSeries("mA", dataSeriesA); })); tasks.Add(Task.Factory.StartNew(() => { fastData.setDataSeries("mB", dataSeriesB); })); fastData.setDataSeries("mC", dataSeriesC); Task.WaitAll(tasks.ToArray()); // Initialize the WinChartViewer initChartViewer(WPFChartViewer1); hasFinishedInitialization = true; // Trigger the ViewPortChanged event to draw the chart WPFChartViewer1.updateViewPort(true, true); } // // Initialize the WinChartViewer // private void initChartViewer(WPFChartViewer viewer) { // Set the full x range to be the duration of the data viewer.setFullRange("x", timeStamps[0], timeStamps[sampleSize - 1]); // Initialize the view port to show the latest 20% of the time range viewer.ViewPortWidth = 0.2; viewer.ViewPortLeft = 1 - viewer.ViewPortWidth; // Set the maximum zoom to 10 points viewer.ZoomInWidthLimit = 10.0 / timeStamps.Length; // Enable mouse wheel zooming by setting the zoom ratio to 1.1 per wheel event viewer.MouseWheelZoomRatio = 1.1; // Initially set the mouse usage to Drag to Scroll mode pointerPB.IsChecked = true; viewer.MouseUsage = WinChartMouseUsage.ScrollOnDrag; } // // Pointer (Drag to Scroll) button event handler // private void pointerPB_Checked(object sender, RoutedEventArgs e) { WPFChartViewer1.MouseUsage = WinChartMouseUsage.ScrollOnDrag; } // // Zoom In button event handler // private void zoomInPB_Checked(object sender, RoutedEventArgs e) { WPFChartViewer1.MouseUsage = WinChartMouseUsage.ZoomIn; } // // Zoom Out button event handler // private void zoomOutPB_Checked(object sender, RoutedEventArgs e) { WPFChartViewer1.MouseUsage = WinChartMouseUsage.ZoomOut; } // // The ViewPortChanged event handler. This event occurs if the user scrolls or zooms in // or out the chart by dragging or clicking on the chart. It can also be triggered by // calling WinChartViewer.updateViewPort. // private void WPFChartViewer1_ViewPortChanged(object sender, WPFViewPortEventArgs e) { if (!hasFinishedInitialization) return; // In addition to updating the chart, we may also need to update other controls that // changes based on the view port. updateControls(WPFChartViewer1); // Update the chart if necessary if (e.NeedUpdateChart) drawChart(WPFChartViewer1); } // // Update controls when the view port changed // private void updateControls(WPFChartViewer viewer) { // In this demo, we need to update the scroll bar to reflect the view port position and // width of the view port. hScrollBar1.IsEnabled = viewer.ViewPortWidth < 1; hScrollBar1.LargeChange = viewer.ViewPortWidth * (hScrollBar1.Maximum - hScrollBar1.Minimum); hScrollBar1.SmallChange = hScrollBar1.LargeChange * 0.1; hScrollBar1.ViewportSize = viewer.ViewPortWidth / Math.Max(1E-10, 1 - viewer.ViewPortWidth) * (hScrollBar1.Maximum - hScrollBar1.Minimum); hScrollBar1.Value = viewer.ViewPortLeft / Math.Max(1E-10, 1 - viewer.ViewPortWidth) * (hScrollBar1.Maximum - hScrollBar1.Minimum) + hScrollBar1.Minimum; } // // The scroll bar event handler // private void hScrollBar1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { var viewer = WPFChartViewer1; // When the view port is changed (user drags on the chart to scroll), the scroll bar will get // updated. When the scroll bar changes (eg. user drags on the scroll bar), the view port will // get updated. This creates an infinite loop. To avoid this, the scroll bar can update the // view port only if the view port is not updating the scroll bar. if (!viewer.IsInViewPortChangedEvent) { // Set the view port based on the scroll bar viewer.ViewPortLeft = (hScrollBar1.Value - hScrollBar1.Minimum) / (hScrollBar1.Maximum - hScrollBar1.Minimum) * (1 - viewer.ViewPortWidth); // Trigger a view port changed event to update the chart viewer.updateViewPort(true, false); } } // // Draw the chart. // private void drawChart(WPFChartViewer viewer) { // Get the start date and end date that are visible on the chart. double viewPortStartDate = viewer.getValueAtViewPort("x", viewer.ViewPortLeft); double viewPortEndDate = viewer.getValueAtViewPort("x", viewer.ViewPortRight); fastData.setSubsetRange(viewPortStartDate, viewPortEndDate); // // At this stage, we have extracted the visible data. We can use those data to plot the chart. // //================================================================================ // Configure overall chart appearance. //================================================================================ // Create an XYChart object of size 800 x 400 pixels XYChart c = new XYChart(800, 400); // Set the plotarea at (0, 0) with width 1 pixel less than chart width, and height 20 pixels // less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff) // as background. Set border to transparent and grid lines to white (ffffff). c.setPlotArea(0, 0, c.getWidth() - 1, c.getHeight() - 20, c.linearGradientColor(0, 0, 0, c.getHeight() - 20, 0xf0f6ff, 0xa0c0ff), -1, Chart.Transparent, 0xffffff, 0xffffff); // In our code, we can overdraw the line slightly, so we clip it to the plot area. c.setClipping(); // Add a legend box at the right side using horizontal layout. Use 10pt Arial Bold as font. Set // the background and border color to Transparent and use line style legend key. LegendBox b = c.addLegend(c.getWidth() - 1, 10, false, "Arial Bold", 10); b.setBackground(Chart.Transparent); b.setAlignment(Chart.Right); b.setLineStyleKey(); // Set the x and y axis stems to transparent and the label font to 10pt Arial c.xAxis().setColors(Chart.Transparent); c.yAxis().setColors(Chart.Transparent); c.xAxis().setLabelStyle("Arial", 10); c.yAxis().setLabelStyle("Arial", 10, 0x336699); // Configure the y-axis label to be inside the plot area and above the horizontal grid lines c.yAxis().setLabelGap(-1); c.yAxis().setMargin(20); c.yAxis().setLabelAlignment(1); // Configure the x-axis labels to be to the left of the vertical grid lines c.xAxis().setLabelAlignment(1); //================================================================================ // Add data to chart //================================================================================ // Add line layers using the DataAccelerator. Each layer only supports one accelerated // series, so we add 3 layers for the 3 data series. // Add a line layer for the lines, using a line width of 2 pixels LineLayer layer = c.addLineLayer(fastData, "mA", 0xff0000, "Alpha"); layer.setLineWidth(2); LineLayer layer2 = c.addLineLayer(fastData, "mB", 0x00cc00, "Beta"); layer2.setLineWidth(2); LineLayer layer3 = c.addLineLayer(fastData, "mC", 0x0000ff, "Gamma"); layer3.setLineWidth(2); //================================================================================ // Configure axis scale and labelling //================================================================================ // Set the x-axis as a date/time axis with the scale according to the view port x range. viewer.syncLinearAxisWithViewPort("x", c.xAxis()); // For the automatic axis labels, set the minimum spacing to 75/40 pixels for the x/y axis. c.xAxis().setTickDensity(75); c.yAxis().setTickDensity(40); // Set the auto-scale margin to 0.05, and the zero affinity to 0.2 c.yAxis().setAutoScale(0.05, 0.05, 0.2); //================================================================================ // Output the chart //================================================================================ // We need to update the track line too. If the mouse is moving on the chart (eg. if // the user drags the mouse on the chart to scroll it), the track line will be updated // in the MouseMovePlotArea event. Otherwise, we need to update the track line here. if ((!viewer.IsInMouseMoveEvent) && viewer.IsMouseOnPlotArea) trackLineLabel(c, viewer.PlotAreaMouseX); viewer.Chart = c; } // // Draw track cursor when mouse is moving over plotarea // private void WPFChartViewer1_MouseMovePlotArea(object sender, MouseEventArgs e) { if (!hasFinishedInitialization) return; WPFChartViewer viewer = (WPFChartViewer)sender; trackLineLabel((XYChart)viewer.Chart, viewer.PlotAreaMouseX); viewer.updateDisplay(); // Hide the track cursor when the mouse leaves the plot area viewer.removeDynamicLayer("MouseLeavePlotArea"); } // // Draw track line with data labels // private void trackLineLabel(XYChart c, int mouseX) { // Clear the current dynamic layer and get the DrawArea object to draw on it. DrawArea d = c.initDynamicLayer(); // The plot area object PlotArea plotArea = c.getPlotArea(); // Get the data x-value that is nearest to the mouse, and find its pixel coordinate. double xValue = c.getNearestXValue(mouseX); int xCoor = c.getXCoor(xValue); // Draw a vertical track line at the x-position d.vline(plotArea.getTopY(), plotArea.getBottomY(), xCoor, d.dashLineColor(0x000000, 0x0101)); // Draw a label on the x-axis to show the track line position. string xlabel = "<*font,bgColor=000000*> " + xValue + " <*/font*>"; TTFText t = d.text(xlabel, "Arial Bold", 10); // Restrict the x-pixel position of the label to make sure it stays inside the chart image. int xLabelPos = Math.Max(0, Math.Min(xCoor - t.getWidth() / 2, c.getWidth() - t.getWidth())); t.draw(xLabelPos, plotArea.getBottomY() + 2, 0xffffff); // Iterate through all layers to draw the data labels for (int i = 0; i < c.getLayerCount(); ++i) { Layer layer = c.getLayerByZ(i); // The data array index of the x-value int xIndex = layer.getXIndexOf(xValue); // Iterate through all the data sets in the layer for (int j = 0; j < layer.getDataSetCount(); ++j) { ChartDirector.DataSet dataSet = layer.getDataSetByZ(j); // Get the color and position of the data label int color = dataSet.getDataColor(); int yCoor = c.getYCoor(dataSet.getPosition(xIndex), dataSet.getUseYAxis()); // Draw a track dot with a label next to it for visible data points in the plot area if ((yCoor >= plotArea.getTopY()) && (yCoor <= plotArea.getBottomY()) && (color != Chart.Transparent)) { d.circle(xCoor, yCoor, 4, 4, color, color); string label = "<*font,bgColor=" + color.ToString("x") + "*> " + c.formatValue( dataSet.getValue(xIndex), "{value|P4}") + " <*/font*>"; t = d.text(label, "Arial Bold", 10); // Draw the label on the right side of the dot if the mouse is on the left side the // chart, and vice versa. This ensures the label will not go outside the chart image. if (xCoor <= (plotArea.getLeftX() + plotArea.getRightX()) / 2) t.draw(xCoor + 5, yCoor, 0xffffff, Chart.Left); else t.draw(xCoor - 5, yCoor, 0xffffff, Chart.Right); } } } } } }