[Windows Forms - C# version] NetWinCharts\CSharpWinCharts\frmrealtimemultichart.cs
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using ChartDirector;
namespace CSharpChartExplorer
{
public partial class FrmRealTimeMultiChart : Form
{
//
// The height of each XYChart. The bottom chart has an extra height for the x-axis labels.
//
private const int chartHeight = 120;
private const int xAxisHeight = 25;
// The data arrays that store the realtime data. The data arrays are updated in realtime.
// In this demo, we store at most 10000 values.
private const int sampleSize = 10000;
private DateTime[] timeStamps = new DateTime[sampleSize];
private double[] dataSeriesA = new double[sampleSize];
private double[] dataSeriesB = new double[sampleSize];
private double[] dataSeriesC = new double[sampleSize];
// The index of the array position to which new data values are added.
private int currentIndex = 0;
// The full range is initialized to 60 seconds of data. It can be extended when more data
// are available.
private int initialFullRange = 60;
// The maximum zoom in is 10 seconds.
private int zoomInLimit = 10;
// In this demo, we use a data generator driven by a timer to generate realtime data. The
// nextDataTime is an internal variable used by the data generator to keep track of which
// values to generate next.
private DateTime nextDataTime = new DateTime(DateTime.Now.Ticks / 10000000 * 10000000);
public FrmRealTimeMultiChart()
{
InitializeComponent();
}
private void FrmRealTimeMultiChart_Load(object sender, EventArgs e)
{
// Initialize the WinChartViewer
initChartViewer(winChartViewer1);
// Data generation rate
dataRateTimer.Interval = 250;
// Chart update rate, which can be different from the data generation rate.
chartUpdateTimer.Interval = (int)samplePeriod.Value;
// Now can start the timers for data collection and chart update
dataRateTimer.Start();
chartUpdateTimer.Start();
}
//
// Initialize the WinChartViewer
//
private void initChartViewer(WinChartViewer viewer)
{
viewer.MouseWheelZoomRatio = 1.1;
// Initially set the mouse usage to "Pointer" mode (Drag to Scroll mode)
pointerPB.Checked = true;
}
//
// The data update routine. In this demo, it is invoked every 250ms to get new data.
//
private void dataRateTimer_Tick(object sender, EventArgs e)
{
do
{
//
// In this demo, we use some formulas to generate new values. In real applications,
// it may be replaced by some data acquisition code.
//
double p = nextDataTime.Ticks / 10000000.0 * 4;
double dataA = 20 + Math.Cos(p * 2.2) * 10 + 1 / (Math.Cos(p) * Math.Cos(p) + 0.01);
double dataB = 150 + 100 * Math.Sin(p / 27.7) * Math.Sin(p / 10.1);
double dataC = 150 + 100 * Math.Cos(p / 6.7) * Math.Cos(p / 11.9);
// In this demo, if the data arrays are full, the oldest 5% of data are discarded.
if (currentIndex >= timeStamps.Length)
{
currentIndex = sampleSize * 95 / 100 - 1;
for (int i = 0; i < currentIndex; ++i)
{
int srcIndex = i + sampleSize - currentIndex;
timeStamps[i] = timeStamps[srcIndex];
dataSeriesA[i] = dataSeriesA[srcIndex];
dataSeriesB[i] = dataSeriesB[srcIndex];
dataSeriesC[i] = dataSeriesC[srcIndex];
}
}
// Store the new values in the current index position, and increment the index.
timeStamps[currentIndex] = nextDataTime;
dataSeriesA[currentIndex] = dataA;
dataSeriesB[currentIndex] = dataB;
dataSeriesC[currentIndex] = dataC;
++currentIndex;
nextDataTime = nextDataTime.AddMilliseconds(dataRateTimer.Interval);
}
while (nextDataTime < DateTime.Now);
// We provide some visual feedback to the numbers generated, so you can see the
// values being generated.
valueA.Text = dataSeriesA[currentIndex - 1].ToString(".##");
valueB.Text = dataSeriesB[currentIndex - 1].ToString(".##");
valueC.Text = dataSeriesC[currentIndex - 1].ToString(".##");
}
//
// Update the chart and the viewport periodically
//
private void chartUpdateTimer_Tick(object sender, EventArgs e)
{
WinChartViewer viewer = winChartViewer1;
if (currentIndex > 0)
{
//
// As we added more data, we may need to update the full range.
//
DateTime startDate = timeStamps[0];
DateTime endDate = timeStamps[currentIndex - 1];
// Use the initialFullRange if this is sufficient.
double duration = endDate.Subtract(startDate).TotalSeconds;
if (duration < initialFullRange)
endDate = startDate.AddSeconds(initialFullRange);
// Update the full range to reflect the actual duration of the data. In this case,
// if the view port is viewing the latest data, we will scroll the view port as new
// data are added. If the view port is viewing historical data, we would keep the
// axis scale unchanged to keep the chart stable.
int updateType = Chart.ScrollWithMax;
if (viewer.ViewPortRight < 0.999)
updateType = Chart.KeepVisibleRange;
bool axisScaleHasChanged = viewer.updateFullRangeH("x", startDate, endDate, updateType);
// Set the zoom in limit as a ratio to the full range
viewer.ZoomInWidthLimit = zoomInLimit / (viewer.getValueAtViewPort("x", 1) -
viewer.getValueAtViewPort("x", 0));
// Trigger the viewPortChanged event to update the display if the axis scale has
// changed or if new data are added to the existing axis scale.
if (axisScaleHasChanged || (duration < initialFullRange))
viewer.updateViewPort(true, false);
}
}
//
// 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)
{
// 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)
drawMultiChart(winChartViewer1);
}
//
// Update other controls when the view port changed
//
private void updateControls(WinChartViewer viewer)
{
// Update the scroll bar to reflect the view port position and width.
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;
}
//
// Draw a single chart
//
XYChart drawXYChart(WinChartViewer viewer, double[] dataSeries, string name, int color,
Axis xAxisScale, bool xAxisVisible)
{
// Get the start date and end date that are visible on the chart.
DateTime viewPortStartDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortLeft));
DateTime viewPortEndDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortRight));
// Extract the part of the data arrays that are visible.
DateTime[] viewPortTimeStamps = null ;
double[] viewPortDataSeries = null;
if (currentIndex > 0)
{
// Get the array indexes that corresponds to the visible start and end dates
int startIndex = (int)Math.Floor(Chart.bSearch2(timeStamps, 0, currentIndex, viewPortStartDate));
int endIndex = (int)Math.Ceiling(Chart.bSearch2(timeStamps, 0, currentIndex, viewPortEndDate));
int noOfPoints = endIndex - startIndex + 1;
// Extract the visible data
viewPortTimeStamps = Chart.arraySlice(timeStamps, startIndex, noOfPoints);
viewPortDataSeries = Chart.arraySlice(dataSeries, startIndex, noOfPoints);
}
//
// At this stage, we have extracted the visible data. We can use those data to plot the chart.
//
//================================================================================
// Configure overall chart appearance.
//================================================================================
// Only the last chart has an x-axis
int extraHeght = xAxisVisible ? xAxisHeight : 0;
// Create an XYChart object of size 640 x 150 pixels (or 180 pixels for the last chart)
XYChart c = new XYChart(640, chartHeight + extraHeght);
// Set the plotarea at (55, 10) with width 80 pixels 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(55, 10, c.getWidth() - 85, c.getHeight() - 20 - extraHeght, c.linearGradientColor(0, 10, 0,
c.getHeight() - 20 - extraHeght, 0xf0f6ff, 0xa0c0ff), -1, Chart.Transparent, 0xffffff, 0xffffff);
// As the data can lie outside the plotarea in a zoomed chart, we need enable clipping.
c.setClipping();
// Add a legend box at (55, 5) using horizontal layout. Use 8pts Arial Bold as font. Set the
// background and border color to Transparent and use line style legend key.
LegendBox b = c.addLegend(55, 5, false, "Arial Bold", 10);
b.setBackground(Chart.Transparent);
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);
// Add axis title using 10pts Arial Bold Italic font
c.yAxis().setTitle(name, "Arial Bold", 10);
//================================================================================
// Add data to chart
//================================================================================
// Add a line layer with the given data, with a line width of 2 pixels.
LineLayer layer = c.addLineLayer();
layer.setLineWidth(2);
layer.setXData(viewPortTimeStamps);
layer.addDataSet(viewPortDataSeries, color, name);
//================================================================================
// Configure axis scale and labelling
//================================================================================
// For the automatic axis labels, set the minimum spacing to 30 pixels for the y axis.
c.yAxis().setTickDensity(30);
if (null != xAxisScale)
{
// If xAxisScale is given, then use it to synchronize with other charts.
c.xAxis().copyAxis(xAxisScale);
}
else if (currentIndex > 0)
{
// If xAxisScale is null, this is the first chart, and it needs to set up the axis scale.
c.xAxis().setDateScale(viewPortStartDate, viewPortEndDate);
// For the automatic axis labels, set the minimum spacing to 75 pixels for the x axis.
c.xAxis().setTickDensity(75);
//
// In this example, the axis range can change from a few seconds to thousands of seconds.
// We can need to define the axis label format for the various cases.
//
// If all ticks are minute algined, then we use "hh:nn" as the label format.
c.xAxis().setFormatCondition("align", 60);
c.xAxis().setLabelFormat("{value|hh:nn}");
// If all other cases, we use "hh:nn:ss" as the label format.
c.xAxis().setFormatCondition("else");
c.xAxis().setLabelFormat("{value|hh:nn:ss}");
// We make sure the tick increment must be at least 1 second.
c.xAxis().setMinTickInc(1);
}
// Hide the x-axis if it is not visible.
if (!xAxisVisible)
c.xAxis().setColors(Chart.Transparent, Chart.Transparent);
//================================================================================
// Output the chart
//================================================================================
return c;
}
//
// Draw the MultiChart
//
void drawMultiChart(WinChartViewer viewer)
{
// The MultiChart contains 3 charts. The x-axis is only visible on the last chart, so we only
// need to reserve space for 1 x-axis.
MultiChart m = new MultiChart(640, 30 + 3 * chartHeight + xAxisHeight);
m.addTitle("Real-Time MultiChart with Zoom/Scroll and Track Line", "Arial", 16);
// This first chart is responsible for setting up the x-axis scale.
m.addChart(0, 30, drawXYChart(viewer, dataSeriesA, "Alpha", 0xff0000, null, false));
Axis xAxisScale = ((XYChart)m.getChart(0)).xAxis();
// All other charts synchronize their x-axes with that of the first chart.
m.addChart(0, 30 + chartHeight, drawXYChart(viewer, dataSeriesB, "Beta", 0x008800,
xAxisScale, false));
// The last chart displays the x-axis.
m.addChart(0, 30 + chartHeight * 2, drawXYChart(viewer, dataSeriesC, "Gamma", 0x0000ff,
xAxisScale, true));
// We need to update the track line too. If the mouse is moving on the chart, the track line
// will be updated in MouseMovePlotArea. Otherwise, we need to update the track line here.
if (!viewer.IsInMouseMoveEvent)
drawMultiTrackLine(m, (null == viewer.Chart) ? m.getWidth() : viewer.PlotAreaMouseX);
// Set the combined plot area to be the bounding box of the plot areas of the 3 charts
m.setMainChart(m);
// Display the chart
viewer.Chart = m;
}
//
// Draw track cursor for MultiChart
//
void drawMultiTrackLine(MultiChart m, int mouseX)
{
// Obtain the dynamic layer of the MultiChart
DrawArea d = m.initDynamicLayer();
// Ask each XYChart to draw the track cursor on the dynamic layer
for (int i = 0; i < m.getChartCount(); ++i)
drawXYTrackLine(d, (XYChart)m.getChart(i), mouseX, i == m.getChartCount() - 1);
}
//
// Draw track line with data labels
//
void drawXYTrackLine(DrawArea d, XYChart c, int mouseX, bool hasXAxis)
{
// In a MultiChart, the XYChart is offsetted from the dynamic layer of the MultiChart
int offsetY = c.getAbsOffsetY();
// 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);
if (xCoor < plotArea.getLeftX())
return;
// Draw a vertical track line at the x-position
d.vline(plotArea.getTopY() + offsetY, plotArea.getBottomY() + offsetY, xCoor, 0x888888);
// Draw a label on the x-axis to show the track line position.
if (hasXAxis)
{
string xlabel = "<*font,bgColor=000000*> "
+ c.xAxis().getFormattedLabel(xValue, "hh:nn:ss.ff") + " <*/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() + 6 + offsetY, 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)
{
DataSet dataSet = layer.getDataSetByZ(j);
string dataSetName = dataSet.getDataName();
// 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) && !string.IsNullOrEmpty(dataSetName))
{
d.circle(xCoor, yCoor + offsetY, 4, 4, color, color);
string label = "<*font,bgColor=" + color.ToString("x") + "*> " +
c.formatValue(dataSet.getValue(xIndex), "{value|P4}") + " <*font*>";
TTFText 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 + 6, yCoor + offsetY, 0xffffff, Chart.Left);
else
t.draw(xCoor - 6, yCoor + offsetY, 0xffffff, Chart.Right);
}
}
}
}
//
// Updates the chartUpdateTimer interval if the user selects another interval.
//
private void samplePeriod_ValueChanged(object sender, EventArgs e)
{
chartUpdateTimer.Interval = (int)samplePeriod.Value;
}
//
// 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 (!winChartViewer1.IsInViewPortChangedEvent)
{
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 track cursor when mouse is moving over plotarea
//
private void winChartViewer1_MouseMovePlotArea(object sender, MouseEventArgs e)
{
WinChartViewer viewer = (WinChartViewer)sender;
drawMultiTrackLine((MultiChart)viewer.Chart, viewer.PlotAreaMouseX);
viewer.updateDisplay();
}
//
// 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;
}
//
// Save button event handler
//
private void savePB_Click(object sender, EventArgs e)
{
// The standard Save File dialog
SaveFileDialog fileDlg = new SaveFileDialog();
fileDlg.Filter = "PNG (*.png)|*.png|JPG (*.jpg)|*.jpg|GIF (*.gif)|*.gif|BMP (*.bmp)|*.bmp|" +
"SVG (*.svg)|*.svg|PDF (*.pdf)|*.pdf";
fileDlg.FileName = "chartdirector_demo";
if (fileDlg.ShowDialog() != DialogResult.OK)
return;
// Save the chart
if (null != winChartViewer1.Chart)
winChartViewer1.Chart.makeChart(fileDlg.FileName);
}
}
}
[Windows Forms - VB Version] NetWinCharts\VBNetWinCharts\frmrealtimemultichart.vb
Imports ChartDirector
Public Class FrmRealTimeMultiChart
'
' The height of each XYChart. The bottom chart has an extra height for the x-axis labels.
'
Private Const chartHeight As Integer = 120
Private Const xAxisHeight As Integer = 25
' The data arrays that store the visible data. The data arrays are updated in realtime. In
' this demo, we plot the last 240 samples.
Private Const sampleSize As Integer = 10000
Private dataSeriesA(sampleSize - 1) As Double
Private dataSeriesB(sampleSize - 1) As Double
Private dataSeriesC(sampleSize - 1) As Double
Private timeStamps(sampleSize - 1) As Date
' The index of the array position to which new data values are added.
Private currentIndex As Integer = 0
' The full range is initialized to 60 seconds of data. It can be extended when more data
' are available.
Private initialFullRange As Integer = 60
' The maximum zoom in is 10 seconds.
Private zoomInLimit As Integer = 10
' In this demo, we use a data generator driven by a timer to generate realtime data. The
' nextDataTime is an internal variable used by the data generator to keep track of which
' values to generate next.
Private nextDataTime As DateTime = New DateTime((Now.Ticks \ 10000000) * 10000000)
' Flag to indicated if initialization has been completed. Prevents events from firing before
' controls are properly initialized.
Private hasFinishedInitialization As Boolean
Private Sub FrmRealTimeZoomScroll_Load(ByVal sender As Object, ByVal e As EventArgs) _
Handles MyBase.Load
' Initialize the WinChartViewer
initChartViewer(winChartViewer1)
' Data generation rate
dataRateTimer.Interval = 250
' Chart update rate
chartUpdateTimer.Interval = CInt(samplePeriod.Value)
' Can handle events now
hasFinishedInitialization = True
' Now can start the timers for data collection and chart update
dataRateTimer.Start()
chartUpdateTimer.Start()
End Sub
'
' Initialize the WinChartViewer
'
Private Sub initChartViewer(ByVal viewer As WinChartViewer)
viewer.MouseWheelZoomRatio = 1.1
' Initially set the mouse usage to "Pointer" mode (Drag to Scroll mode)
pointerPB.Checked = True
End Sub
'/ <summary>
'/ The data generator - invoke once every 250ms
'/ </summary>
Private Sub dataRateTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles dataRateTimer.Tick
Do While nextDataTime < DateTime.Now
'
' In this demo, we use some formulas to generate new values. In real applications,
' it may be replaced by some data acquisition code.
'
Dim p As Double = nextDataTime.Ticks / 10000000.0 * 4
Dim dataA As Double = 20 + Math.Cos(p * 2.2) * 10 + 1 / (Math.Cos(p) * Math.Cos(p) + 0.01)
Dim dataB As Double = 150 + 100 * Math.Sin(p / 27.7) * Math.Sin(p / 10.1)
Dim dataC As Double = 150 + 100 * Math.Cos(p / 6.7) * Math.Cos(p / 11.9)
' In this demo, if the data arrays are full, the oldest 5% of data are discarded.
If currentIndex >= timeStamps.Length Then
currentIndex = Int(sampleSize * 95 / 100) - 1
For i As Integer = 0 To currentIndex - 1
Dim srcIndex As Integer = i + sampleSize - currentIndex
timeStamps(i) = timeStamps(srcIndex)
dataSeriesA(i) = dataSeriesA(srcIndex)
dataSeriesB(i) = dataSeriesB(srcIndex)
dataSeriesC(i) = dataSeriesC(srcIndex)
Next
End If
' Store the new values in the current index position, and increment the index.
timeStamps(currentIndex) = nextDataTime
dataSeriesA(currentIndex) = dataA
dataSeriesB(currentIndex) = dataB
dataSeriesC(currentIndex) = dataC
currentIndex += 1
' Update nextDataTime. This is needed by our data generator. In real applications,
' you may not need this variable or the associated do/while loop.
nextDataTime = nextDataTime.AddMilliseconds(dataRateTimer.Interval)
Loop
' We provide some visual feedback to the numbers generated, so you can see the
' values being generated.
valueA.Text = dataSeriesA(currentIndex - 1).ToString(".##")
valueB.Text = dataSeriesB(currentIndex - 1).ToString(".##")
valueC.Text = dataSeriesC(currentIndex - 1).ToString(".##")
End Sub
'
' Update the chart and the viewport periodically
'
Private Sub chartUpdateTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles chartUpdateTimer.Tick
Dim viewer As WinChartViewer = winChartViewer1
If currentIndex > 0 Then
'
' As we added more data, we may need to update the full range.
'
Dim startDate As DateTime = timeStamps(0)
Dim endDate As DateTime = timeStamps(currentIndex - 1)
' Use the initialFullRange if this is sufficient.
Dim duration As Double = endDate.Subtract(startDate).TotalSeconds
If duration < initialFullRange Then
endDate = startDate.AddSeconds(initialFullRange)
End If
' Update the full range to reflect the actual duration of the data. In this case,
' if the view port is viewing the latest data, we will scroll the view port as new
' data are added. If the view port is viewing historical data, we would keep the
' axis scale unchanged to keep the chart stable.
Dim updateType As Integer = Chart.ScrollWithMax
If viewer.ViewPortRight < 0.999 Then
updateType = Chart.KeepVisibleRange
End If
Dim axisScaleHasChanged As Boolean = viewer.updateFullRangeH("x", startDate, endDate, updateType)
' Set the zoom in limit as a ratio to the full range
viewer.ZoomInWidthLimit = zoomInLimit / (viewer.getValueAtViewPort("x", 1) -
viewer.getValueAtViewPort("x", 0))
' Trigger the viewPortChanged event to update the display if the axis scale has
' changed or if new data are added to the existing axis scale.
If axisScaleHasChanged Or duration < initialFullRange Then
viewer.updateViewPort(True, False)
End If
End If
End Sub
'
' The viewPortChanged event handler. In this example, it just updates the chart. If you
' have other controls to update, you may also put the update code here.
'
Private Sub winChartViewer1_ViewPortChanged(ByVal sender As Object, ByVal e As WinViewPortEventArgs) _
Handles winChartViewer1.ViewPortChanged
' 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
drawMultiChart(winChartViewer1)
End If
End Sub
'
' Update other controls when the view port changed
'
Private Sub updateControls(ByVal viewer As WinChartViewer)
' Update the scroll bar to reflect the view port position and width.
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
'
' Draw a single chart
'
Private Function drawXYChart(viewer As WinChartViewer, dataSeries() As Double, name As String,
color As Integer, xAxisScale As Axis, xAxisVisible As Boolean) As XYChart
' Get the start date And end date that are visible on the chart.
Dim viewPortStartDate As Date = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortLeft))
Dim viewPortEndDate As Date = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortRight))
' Extract the part of the data arrays that are visible.
Dim viewPortTimeStamps() As Date = Nothing
Dim viewPortDataSeries() As Double = Nothing
If currentIndex > 0 Then
' Get the array indexes that corresponds to the visible start And end dates
Dim startIndex As Integer = Math.Floor(Chart.bSearch2(timeStamps, 0, currentIndex, viewPortStartDate))
Dim endIndex As Integer = Math.Ceiling(Chart.bSearch2(timeStamps, 0, currentIndex, viewPortEndDate))
Dim noOfPoints As Integer = endIndex - startIndex + 1
' Extract the visible data
viewPortTimeStamps = Chart.arraySlice(timeStamps, startIndex, noOfPoints)
viewPortDataSeries = Chart.arraySlice(dataSeries, startIndex, noOfPoints)
End If
'
' At this stage, we have extracted the visible data. We can use those data to plot the chart.
'
'================================================================================
' Configure overall chart appearance.
'================================================================================
' Only the last chart has an x-axis
Dim extraHeght As Integer = IIf(xAxisVisible, xAxisHeight, 0)
' Create an XYChart object of size 640 x 150 pixels (Or 180 pixels for the last chart)
Dim c As XYChart = New XYChart(640, chartHeight + extraHeght)
' Set the plotarea at (55, 10) with width 80 pixels 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(55, 10, c.getWidth() - 85, c.getHeight() - 20 - extraHeght, c.linearGradientColor(0, 10, 0,
c.getHeight() - 20 - extraHeght, &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 (55, 5) using horizontal layout. Use 8pts Arial Bold as font. Set the
' background And border color to Transparent And use line style legend key.
Dim b As LegendBox = c.addLegend(55, 5, False, "Arial Bold", 10)
b.setBackground(Chart.Transparent)
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)
' Add axis title using 10pts Arial Bold Italic font
c.yAxis().setTitle(name, "Arial Bold", 10)
'================================================================================
' Add data to chart
'================================================================================
' Add a line layer with the given data, with a line width of 2 pixels.
Dim layer As LineLayer = c.addLineLayer()
layer.setLineWidth(2)
layer.setXData(viewPortTimeStamps)
layer.addDataSet(viewPortDataSeries, color, name)
'================================================================================
' Configure axis scale And labelling
'================================================================================
' For the automatic axis labels, set the minimum spacing to 30 pixels for the y axis.
c.yAxis().setTickDensity(30)
If Not IsNothing(xAxisScale) Then
' If xAxisScale Is given, then use it to synchronize with other charts.
c.xAxis().copyAxis(xAxisScale)
ElseIf currentIndex > 0 Then
' If xAxisScale Is null, this Is the first chart, And it needs to set up the axis scale.
c.xAxis().setDateScale(viewPortStartDate, viewPortEndDate)
' For the automatic axis labels, set the minimum spacing to 75 pixels for the x axis.
c.xAxis().setTickDensity(75)
'
' In this example, the axis range can change from a few seconds to thousands of seconds.
' We can need to define the axis label format for the various cases.
'
' If all ticks are minute algined, then we use "hh:nn" as the label format.
c.xAxis().setFormatCondition("align", 60)
c.xAxis().setLabelFormat("{value|hh:nn}")
' If all other cases, we use "hh:nn:ss" as the label format.
c.xAxis().setFormatCondition("else")
c.xAxis().setLabelFormat("{value|hh:nn:ss}")
' We make sure the tick increment must be at least 1 second.
c.xAxis().setMinTickInc(1)
End If
' Hide the x-axis if it Is Not visible.
If Not xAxisVisible Then
c.xAxis().setColors(Chart.Transparent, Chart.Transparent)
End If
'================================================================================
' Output the chart
'================================================================================
Return c
End Function
'
' Draw the MultiChart
'
Private Sub drawMultiChart(viewer As WinChartViewer)
' The MultiChart contains 3 charts. The x-axis Is only visible on the last chart, so we only
' need to reserve space for 1 x-axis.
Dim m As MultiChart = New MultiChart(640, 30 + 3 * chartHeight + xAxisHeight)
m.addTitle("Real-Time MultiChart with Zoom/Scroll and Track Line", "Arial", 16)
' This first chart Is responsible for setting up the x-axis scale.
m.addChart(0, 30, drawXYChart(viewer, dataSeriesA, "Alpha", &HFF0000, Nothing, False))
Dim xAxisScale As Axis = DirectCast(m.getChart(0), XYChart).xAxis()
' All other charts synchronize their x-axes with that of the first chart.
m.addChart(0, 30 + chartHeight, drawXYChart(viewer, dataSeriesB, "Beta", &H8800,
xAxisScale, False))
' The last chart displays the x-axis.
m.addChart(0, 30 + chartHeight * 2, drawXYChart(viewer, dataSeriesC, "Gamma", &HFF,
xAxisScale, True))
' We need to update the track line too. If the mouse Is moving on the chart, the track line
' will be updated in MouseMovePlotArea. Otherwise, we need to update the track line here.
If Not viewer.IsInMouseMoveEvent Then
drawMultiTrackLine(m, IIf(IsNothing(viewer.Chart), m.getWidth(), viewer.PlotAreaMouseX))
End If
' Set the combined plot area to be the bounding box of the plot areas of the 3 charts
m.setMainChart(m)
' Display the chart
viewer.Chart = m
End Sub
'
' Draw track cursor for MultiChart
'
Private Sub drawMultiTrackLine(m As MultiChart, mouseX As Integer)
' Obtain the dynamic layer of the MultiChart
Dim d As DrawArea = m.initDynamicLayer()
' Ask each XYChart to draw the track cursor on the dynamic layer
For i As Integer = 0 To m.getChartCount() - 1
drawXYTrackLine(d, m.getChart(i), mouseX, i = m.getChartCount() - 1)
Next
End Sub
'
' Draw track line with data labels
'
Private Sub drawXYTrackLine(d As DrawArea, c As XYChart, mouseX As Integer, hasXAxis As Boolean)
' In a MultiChart, the XYChart Is offsetted from the dynamic layer of the MultiChart
Dim offsetY As Integer = c.getAbsOffsetY()
' 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)
If xCoor < plotarea.getLeftX() Then
Exit Sub
End If
' Draw a vertical track line at the x-position
d.vline(plotArea.getTopY() + offsetY, plotArea.getBottomY() + offsetY, xCoor, &H888888)
' Draw a label on the x-axis to show the track line position.
If hasXAxis Then
Dim xlabel As String = "<*font,bgColor=000000*> " _
& c.xAxis().getFormattedLabel(xValue, "hh:nn:ss.ff") + " <*/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() + 6 + offsetY, &HFFFFFF)
End If
' 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 DataSet = Layer.getDataSetByZ(j)
Dim dataSetName As String = dataSet.getDataName()
' 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()) AndAlso (yCoor <= plotarea.getBottomY()) AndAlso (color <>
Chart.Transparent) AndAlso Not String.IsNullOrEmpty(dataSetName)) Then
d.circle(xCoor, yCoor + offsetY, 4, 4, color, color)
Dim label As String = "<*font,bgColor=" & color.ToString("x") & "*> " &
c.formatValue(dataSet.getValue(xIndex), "{value|P4}") & " <*font*>"
Dim t As TTFText = 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 + 6, yCoor + offsetY, &HFFFFFF, Chart.Left)
Else
t.draw(xCoor - 6, yCoor + offsetY, &HFFFFFF, Chart.Right)
End If
End If
Next
Next
End Sub
'
' Updates the chartUpdateTimer interval if the user selects another interval.
'
Private Sub samplePeriod_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles samplePeriod.ValueChanged
chartUpdateTimer.Interval = CInt(samplePeriod.Value)
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 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
Dim viewer As WinChartViewer = sender
drawMultiTrackLine(viewer.Chart, viewer.PlotAreaMouseX)
viewer.updateDisplay()
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
'
' Save button event handler
'
Private Sub savePB_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles savePB.Click
' The standard Save File dialog
Dim fileDlg As SaveFileDialog = New SaveFileDialog()
fileDlg.Filter = "PNG (*.png)|*.png|JPG (*.jpg)|*.jpg|GIF (*.gif)|*.gif|BMP (*.bmp)|*.bmp|" &
"SVG (*.svg)|*.svg|PDF (*.pdf)|*.pdf"
fileDlg.FileName = "chartdirector_demo"
If fileDlg.ShowDialog() <> DialogResult.OK Then
Return
End If
' Save the chart
If Not IsNothing(winChartViewer1.Chart) Then
winChartViewer1.Chart.makeChart(fileDlg.FileName)
End If
End Sub
End Class
[WPF - XAML] NetWPFCharts\CSharpWPFCharts\RealTimeMultiChartWindow.xaml
<Window x:Class="CSharpWPFCharts.RealTimeMultiChartWindow"
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="Real Time MultiChart with Zoom/Scroll and Track Line" SizeToContent="WidthAndHeight" ResizeMode="NoResize" Loaded="Window_Loaded"
>
<DockPanel>
<Label Content="Advanced Software Engineering" Height="25" DockPanel.Dock="Top" FontFamily="Arial" FontStyle="Italic" FontWeight="Bold"
FontSize="13" Background="#FF02098D" Foreground="#FFF4FF04" HorizontalContentAlignment="Right"/>
<StackPanel DockPanel.Dock="Left" Width="120" Background="#FFF0F0F0">
<RadioButton x:Name="pointerPB" Style="{StaticResource {x:Type ToggleButton}}" HorizontalContentAlignment="Left" Checked="pointerPB_Checked" >
<StackPanel Orientation="Horizontal" Margin="5">
<Image Source="/icons/scroll_icon.png" Height="16" />
<TextBlock Text="Pointer" Margin="6,0,0,0" />
</StackPanel>
</RadioButton>
<RadioButton x:Name="zoomInPB" Style="{StaticResource {x:Type ToggleButton}}" HorizontalContentAlignment="Left" Checked="zoomInPB_Checked" >
<StackPanel Orientation="Horizontal" Margin="5" >
<Image Source="/icons/zoomin_icon.png" Height="16" />
<TextBlock Text="Zoom In" Margin="6,0,0,0" />
</StackPanel>
</RadioButton>
<RadioButton x:Name="zoomOutPB" Style="{StaticResource {x:Type ToggleButton}}" HorizontalContentAlignment="Left" Checked="zoomOutPB_Checked">
<StackPanel Orientation="Horizontal" Margin="5" >
<Image Source="/icons/zoomout_icon.png" Height="16" />
<TextBlock Text="Zoom Out" Margin="6,0,0,0" />
</StackPanel>
</RadioButton>
<Button x:Name="savePB" Margin="0,32,0,0" HorizontalContentAlignment="Left" Click="savePB_Click">
<StackPanel Orientation="Horizontal" Margin="5" >
<Image Source="/icons/save_icon.png" Height="16" />
<TextBlock Text="Save" Margin="6,0,0,0" />
</StackPanel>
</Button>
<TextBlock Text="Updated Rate (ms)" Margin="5,30,0,0" FontWeight="Bold" />
<ComboBox x:Name="samplePeriod" Margin="3" SelectionChanged="samplePeriod_SelectionChanged">
<ComboBoxItem IsSelected="True">250</ComboBoxItem>
<ComboBoxItem>500</ComboBoxItem>
<ComboBoxItem>750</ComboBoxItem>
<ComboBoxItem>1000</ComboBoxItem>
<ComboBoxItem>1250</ComboBoxItem>
<ComboBoxItem>1500</ComboBoxItem>
<ComboBoxItem>1750</ComboBoxItem>
<ComboBoxItem>2000</ComboBoxItem>
</ComboBox>
<TextBlock Text="Simulated Machine" Margin="3,90,0,1" FontWeight="Bold" />
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Text="Alpha"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Text="Beta"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Text="Gamma"/>
<Label x:Name="valueA" Grid.Row="0" Grid.Column="1" Content=" " Margin="6,0,0,0" Padding="2" BorderThickness="1" BorderBrush="Gray"/>
<Label x:Name="valueB" Grid.Row="1" Grid.Column="1" Content=" " Margin="6,2,0,2" Padding="2" BorderThickness="1" BorderBrush="Gray"/>
<Label x:Name="valueC" Grid.Row="2" Grid.Column="1" Content=" " Margin="6,0,0,0" Padding="2" BorderThickness="1" BorderBrush="Gray"/>
</Grid>
</StackPanel>
<ChartDirector:WPFChartViewer x:Name="WPFChartViewer1" DockPanel.Dock="Top" Width="640" Height="415" Margin="5" ViewPortChanged="WPFChartViewer1_ViewPortChanged" MouseMovePlotArea="WPFChartViewer1_MouseMovePlotArea" />
<ScrollBar x:Name="hScrollBar1" DockPanel.Dock="Top" Orientation="Horizontal" ValueChanged="hScrollBar1_ValueChanged"/>
</DockPanel>
</Window>
[WPF - C#] NetWPFCharts\CSharpWPFCharts\RealTimeMultiChartWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using Microsoft.Win32;
using ChartDirector;
namespace CSharpWPFCharts
{
/// <summary>
/// Interaction logic for RealTimeMultiChartWindow.xaml
/// </summary>
public partial class RealTimeMultiChartWindow : Window
{
//
// The height of each XYChart. The bottom chart has an extra height for the x-axis labels.
//
private const int chartHeight = 120;
private const int xAxisHeight = 25;
// The data arrays that store the realtime data. The data arrays are updated in realtime.
// In this demo, we store at most 10000 values.
private const int sampleSize = 10000;
private DateTime[] timeStamps = new DateTime[sampleSize];
private double[] dataSeriesA = new double[sampleSize];
private double[] dataSeriesB = new double[sampleSize];
private double[] dataSeriesC = new double[sampleSize];
// The index of the array position to which new data values are added.
private int currentIndex = 0;
// The full range is initialized to 60 seconds of data. It can be extended when more data
// are available.
private int initialFullRange = 60;
// The maximum zoom in is 10 seconds.
private int zoomInLimit = 10;
// In this demo, we use a data generator driven by a timer to generate realtime data. The
// nextDataTime is an internal variable used by the data generator to keep track of which
// values to generate next.
private DispatcherTimer dataRateTimer = new DispatcherTimer(DispatcherPriority.Render);
private DateTime nextDataTime = new DateTime(DateTime.Now.Ticks / 10000000 * 10000000);
// Timer used to updated the chart
private DispatcherTimer chartUpdateTimer = new DispatcherTimer(DispatcherPriority.Render);
public RealTimeMultiChartWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Initialize the WinChartViewer
initChartViewer(WPFChartViewer1);
// Data generation rate = 250ms
dataRateTimer.Interval = new TimeSpan(0, 0, 0, 0, 250);
dataRateTimer.Tick += dataRateTimer_Tick;
// Chart update rate, which can be different from the data generation rate.
chartUpdateTimer.Interval = new TimeSpan(0, 0, 0, 0, int.Parse(samplePeriod.Text));
chartUpdateTimer.Tick += chartUpdateTimer_Tick;
// Now can start the timers for data collection and chart update
dataRateTimer.Start();
chartUpdateTimer.Start();
}
//
// Initialize the WinChartViewer
//
private void initChartViewer(WPFChartViewer viewer)
{
// Enable mouse wheel zooming
viewer.MouseWheelZoomRatio = 1.1;
// Initially set the mouse usage to "Pointer" mode (Drag to Scroll mode)
pointerPB.IsChecked = true;
}
//
// The data update routine. In this demo, it is invoked every 250ms to get new data.
//
private void dataRateTimer_Tick(object sender, EventArgs e)
{
do
{
//
// In this demo, we use some formulas to generate new values. In real applications,
// it may be replaced by some data acquisition code.
//
double p = nextDataTime.Ticks / 10000000.0 * 4;
double dataA = 20 + Math.Cos(p * 2.2) * 10 + 1 / (Math.Cos(p) * Math.Cos(p) + 0.01);
double dataB = 150 + 100 * Math.Sin(p / 27.7) * Math.Sin(p / 10.1);
double dataC = 150 + 100 * Math.Cos(p / 6.7) * Math.Cos(p / 11.9);
// In this demo, if the data arrays are full, the oldest 5% of data are discarded.
if (currentIndex >= timeStamps.Length)
{
currentIndex = sampleSize * 95 / 100 - 1;
for (int i = 0; i < currentIndex; ++i)
{
int srcIndex = i + sampleSize - currentIndex;
timeStamps[i] = timeStamps[srcIndex];
dataSeriesA[i] = dataSeriesA[srcIndex];
dataSeriesB[i] = dataSeriesB[srcIndex];
dataSeriesC[i] = dataSeriesC[srcIndex];
}
}
// Store the new values in the current index position, and increment the index.
timeStamps[currentIndex] = nextDataTime;
dataSeriesA[currentIndex] = dataA;
dataSeriesB[currentIndex] = dataB;
dataSeriesC[currentIndex] = dataC;
++currentIndex;
nextDataTime = nextDataTime.AddMilliseconds(dataRateTimer.Interval.TotalMilliseconds);
}
while (nextDataTime < DateTime.Now);
// We provide some visual feedback to the numbers generated, so you can see the
// values being generated.
valueA.Content = dataSeriesA[currentIndex - 1].ToString(".##");
valueB.Content = dataSeriesB[currentIndex - 1].ToString(".##");
valueC.Content = dataSeriesC[currentIndex - 1].ToString(".##");
}
//
// Update the chart and the viewport periodically
//
private void chartUpdateTimer_Tick(object sender, EventArgs e)
{
var viewer = WPFChartViewer1;
if (currentIndex > 0)
{
//
// As we added more data, we may need to update the full range.
//
DateTime startDate = timeStamps[0];
DateTime endDate = timeStamps[Math.Max(0, currentIndex - 1)];
// Use the initialFullRange if this is sufficient.
double duration = endDate.Subtract(startDate).TotalSeconds;
if (duration < initialFullRange)
endDate = startDate.AddSeconds(initialFullRange);
// Update the full range to reflect the actual duration of the data. In this case,
// if the view port is viewing the latest data, we will scroll the view port as new
// data are added. If the view port is viewing historical data, we would keep the
// axis scale unchanged to keep the chart stable.
int updateType = Chart.ScrollWithMax;
if (viewer.ViewPortRight < 0.999)
updateType = Chart.KeepVisibleRange;
bool axisScaleHasChanged = viewer.updateFullRangeH("x", startDate, endDate, updateType);
// Set the zoom in limit as a ratio to the full range
viewer.ZoomInWidthLimit = zoomInLimit / (viewer.getValueAtViewPort("x", 1) -
viewer.getValueAtViewPort("x", 0));
// Trigger the viewPortChanged event to update the display if the axis scale has
// changed or if new data are added to the existing axis scale.
if (axisScaleHasChanged || (duration < initialFullRange))
viewer.updateViewPort(true, false);
}
}
//
// 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)
{
var viewer = sender as WPFChartViewer;
// In addition to updating the chart, we may also need to update other controls that
// changes based on the view port.
updateControls(viewer);
// Update the chart if necessary
if (e.NeedUpdateChart)
drawMultiChart(viewer);
}
//
// Update other controls when the view port changed
//
private void updateControls(WPFChartViewer viewer)
{
// Update the scroll bar to reflect the view port position and width.
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;
}
//
// Draw a single chart
//
XYChart drawXYChart(WPFChartViewer viewer, double[] dataSeries, string name, int color,
Axis xAxisScale, bool xAxisVisible)
{
// Get the start date and end date that are visible on the chart.
DateTime viewPortStartDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortLeft));
DateTime viewPortEndDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.ViewPortRight));
// Extract the part of the data arrays that are visible.
DateTime[] viewPortTimeStamps = null;
double[] viewPortDataSeries = null;
if (currentIndex > 0)
{
// Get the array indexes that corresponds to the visible start and end dates
int startIndex = (int)Math.Floor(Chart.bSearch2(timeStamps, 0, currentIndex, viewPortStartDate));
int endIndex = (int)Math.Ceiling(Chart.bSearch2(timeStamps, 0, currentIndex, viewPortEndDate));
int noOfPoints = endIndex - startIndex + 1;
// Extract the visible data
viewPortTimeStamps = Chart.arraySlice(timeStamps, startIndex, noOfPoints);
viewPortDataSeries = Chart.arraySlice(dataSeries, startIndex, noOfPoints);
}
//
// At this stage, we have extracted the visible data. We can use those data to plot the chart.
//
//================================================================================
// Configure overall chart appearance.
//================================================================================
// Only the last chart has an x-axis
int extraHeght = xAxisVisible ? xAxisHeight : 0;
// Create an XYChart object of size 640 x 150 pixels (or 180 pixels for the last chart)
XYChart c = new XYChart(640, chartHeight + extraHeght);
// Set the plotarea at (55, 10) with width 80 pixels 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(55, 10, c.getWidth() - 85, c.getHeight() - 20 - extraHeght, c.linearGradientColor(0, 10, 0,
c.getHeight() - 20 - extraHeght, 0xf0f6ff, 0xa0c0ff), -1, Chart.Transparent, 0xffffff, 0xffffff);
// As the data can lie outside the plotarea in a zoomed chart, we need enable clipping.
c.setClipping();
// Add a legend box at (55, 5) using horizontal layout. Use 8pts Arial Bold as font. Set the
// background and border color to Transparent and use line style legend key.
LegendBox b = c.addLegend(55, 5, false, "Arial Bold", 10);
b.setBackground(Chart.Transparent);
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);
// Add axis title using 10pts Arial Bold Italic font
c.yAxis().setTitle(name, "Arial Bold", 10);
//================================================================================
// Add data to chart
//================================================================================
// Add a line layer with the given data, with a line width of 2 pixels.
LineLayer layer = c.addLineLayer();
layer.setLineWidth(2);
layer.setXData(viewPortTimeStamps);
layer.addDataSet(viewPortDataSeries, color, name);
//================================================================================
// Configure axis scale and labelling
//================================================================================
// For the automatic axis labels, set the minimum spacing to 30 pixels for the y axis.
c.yAxis().setTickDensity(30);
if (null != xAxisScale)
{
// If xAxisScale is given, then use it to synchronize with other charts.
c.xAxis().copyAxis(xAxisScale);
}
else if (currentIndex > 0)
{
// If xAxisScale is null, this is the first chart, and it needs to set up the axis scale.
c.xAxis().setDateScale(viewPortStartDate, viewPortEndDate);
// For the automatic axis labels, set the minimum spacing to 75 pixels for the x axis.
c.xAxis().setTickDensity(75);
//
// In this example, the axis range can change from a few seconds to thousands of seconds.
// We can need to define the axis label format for the various cases.
//
// If all ticks are minute algined, then we use "hh:nn" as the label format.
c.xAxis().setFormatCondition("align", 60);
c.xAxis().setLabelFormat("{value|hh:nn}");
// If all other cases, we use "hh:nn:ss" as the label format.
c.xAxis().setFormatCondition("else");
c.xAxis().setLabelFormat("{value|hh:nn:ss}");
// We make sure the tick increment must be at least 1 second.
c.xAxis().setMinTickInc(1);
}
// Hide the x-axis if it is not visible.
if (!xAxisVisible)
c.xAxis().setColors(Chart.Transparent, Chart.Transparent);
//================================================================================
// Output the chart
//================================================================================
return c;
}
//
// Draw the MultiChart
//
void drawMultiChart(WPFChartViewer viewer)
{
// The MultiChart contains 3 charts. The x-axis is only visible on the last chart, so we only
// need to reserve space for 1 x-axis.
MultiChart m = new MultiChart(640, 30 + 3 * chartHeight + xAxisHeight);
m.addTitle("Real-Time MultiChart with Zoom/Scroll and Track Line", "Arial", 16);
// This first chart is responsible for setting up the x-axis scale.
m.addChart(0, 30, drawXYChart(viewer, dataSeriesA, "Alpha", 0xff0000, null, false));
Axis xAxisScale = ((XYChart)m.getChart(0)).xAxis();
// All other charts synchronize their x-axes with that of the first chart.
m.addChart(0, 30 + chartHeight, drawXYChart(viewer, dataSeriesB, "Beta", 0x008800,
xAxisScale, false));
// The last chart displays the x-axis.
m.addChart(0, 30 + chartHeight * 2, drawXYChart(viewer, dataSeriesC, "Gamma", 0x0000ff,
xAxisScale, true));
// We need to update the track line too. If the mouse is moving on the chart, the track line
// will be updated in MouseMovePlotArea. Otherwise, we need to update the track line here.
if (!viewer.IsInMouseMoveEvent)
drawMultiTrackLine(m, (null == viewer.Chart) ? m.getWidth() : viewer.PlotAreaMouseX);
// Set the combined plot area to be the bounding box of the plot areas of the 3 charts
m.setMainChart(m);
// Display the chart
viewer.Chart = m;
}
//
// Draw track cursor for MultiChart
//
void drawMultiTrackLine(MultiChart m, int mouseX)
{
// Obtain the dynamic layer of the MultiChart
DrawArea d = m.initDynamicLayer();
// Ask each XYChart to draw the track cursor on the dynamic layer
for (int i = 0; i < m.getChartCount(); ++i)
drawXYTrackLine(d, (XYChart)m.getChart(i), mouseX, i == m.getChartCount() - 1);
}
//
// Draw track line with data labels
//
void drawXYTrackLine(DrawArea d, XYChart c, int mouseX, bool hasXAxis)
{
// In a MultiChart, the XYChart is offsetted from the dynamic layer of the MultiChart
int offsetY = c.getAbsOffsetY();
// 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);
if (xCoor < plotArea.getLeftX())
return;
// Draw a vertical track line at the x-position
d.vline(plotArea.getTopY() + offsetY, plotArea.getBottomY() + offsetY, xCoor, 0x888888);
// Draw a label on the x-axis to show the track line position.
if (hasXAxis)
{
string xlabel = "<*font,bgColor=000000*> "
+ c.xAxis().getFormattedLabel(xValue, "hh:nn:ss.ff") + " <*/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() + 6 + offsetY, 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)
{
DataSet dataSet = layer.getDataSetByZ(j);
string dataSetName = dataSet.getDataName();
// 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) && !string.IsNullOrEmpty(dataSetName))
{
d.circle(xCoor, yCoor + offsetY, 4, 4, color, color);
string label = "<*font,bgColor=" + color.ToString("x") + "*> " +
c.formatValue(dataSet.getValue(xIndex), "{value|P4}") + " <*font*>";
TTFText 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 + 6, yCoor + offsetY, 0xffffff, Chart.Left);
else
t.draw(xCoor - 6, yCoor + offsetY, 0xffffff, Chart.Right);
}
}
}
}
//
// Updates the chartUpdateTimer interval if the user selects another interval.
//
private void samplePeriod_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedText = (samplePeriod.SelectedValue as ComboBoxItem).Content as string;
if (!string.IsNullOrEmpty(selectedText))
chartUpdateTimer.Interval = new TimeSpan(0, 0, 0, 0, int.Parse(selectedText));
}
//
// 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 track cursor when mouse is moving over plotarea
//
private void WPFChartViewer1_MouseMovePlotArea(object sender, MouseEventArgs e)
{
var viewer = sender as WPFChartViewer;
drawMultiTrackLine((MultiChart)viewer.Chart, viewer.PlotAreaMouseX);
viewer.updateDisplay();
}
//
// 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;
}
//
// Save button event handler
//
private void savePB_Click(object sender, RoutedEventArgs e)
{
// The standard Save File dialog
SaveFileDialog fileDlg = new SaveFileDialog();
fileDlg.Filter = "PNG (*.png)|*.png|JPG (*.jpg)|*.jpg|GIF (*.gif)|*.gif|BMP (*.bmp)|*.bmp|" +
"SVG (*.svg)|*.svg|PDF (*.pdf)|*.pdf";
fileDlg.FileName = "chartdirector_demo";
var ret = fileDlg.ShowDialog(this);
if (!(ret.HasValue && ret.Value))
return;
// Save the chart
if (null != WPFChartViewer1.Chart)
WPFChartViewer1.Chart.makeChart(fileDlg.FileName);
}
}
}
© 2023 Advanced Software Engineering Limited. All rights reserved.