This article describes a 3D surface plot control that can display real time data in C# WinForms and WPF applications. The library includes a WPF configuration view, and classes to serialise the configuration to and from the registry. Adding the control to an application is very straight forward.
Introduction
This article describes a C# control that plots a 2D array of Z values as a surface or a scatter plot. The control can be used in WPF and WinForms applications. It is designed for the real time display of data, i.e., data that is constantly being updated. Adding the control to an application is very straight forward.
The display is highly configurable including:
- Optional labels on each axis
- The user can select the colour, size and orientation of the labels.
- An optional Z bar which displays the colour corresponding to each Z axis value.
- Optional grid connecting the data points
- The user can select the grid colour.
- Optional surface shading
- Optional scatter plot display
- A hold feature, whereby only the maximum (or minimum) Z values at each X,Y point are shown.
- User selectable projections: 3D, orthographic and bird's eye
- User selectable background colour
- Optional frame around the sides of the display
- The user can select the frame colour.
- A WPF view that allows the user to configure the display.
- The user can manipulate the view using the keyboard including zoom, rotate and move.
- User selectable perspective.
The control shapes and text using Open GL, via the C# OpenTK library. OpenGL is fast as it uses the PC's graphics card. Performance will of course depend on the nature of the PC and the graphics card.
The settings are passed to the control as an instance of a class derived from the IConfiguration
interface. I have provided a basic implementation. The configuration can be read from and written to the registry.
The library includes a WPF user control that can be added to a form or a dialog and which allows the user to configure the display settings, such as the frame colour and the label size. The settings include zoom, Z axis scaling and perspective. I have also created a simple export method for use by WinForms applications that displays the configuration view.
There are two simple demonstration applications, one using WPF and the other using WinForms.
Example Plots
The following shows a simple surface plot with axes, labels and frame:
In the above, the surface shading is continuous, i.e., smooth.
The following example has floating point axes formatted by the calling application:
Elements such as labels and axes titles can be removed. In the following example, the labels and axes titles are not drawn:
The colour scheme can be changed. In the following example, the background is black:
In the following example, the grid mesh is not drawn, and the grid shading is coarse rather than continuous:
Data can be plotted as points:
The surface can be shown in a bird's eye view:
And data can be shown as an orthographic projection (from the side):
Configuration View
The library includes a view, implemented as a WPF user control, that allows the user to adjust the display settings:
Keyboard Controls
The user can manipulate the view using the mouse and keyboard as follows:
- Zoom in and out: Hold the left mouse button down, and rotate the mouse wheel.
- Moves the plot: Hold the left mouse button and control key down, and move the mouse.
- Rotate about the X, Y and Z axes: Hold the left mouse button down, and move the mouse.
Background
You will need a good understanding of writing Windows applications in C#. A basic knowledge of WPF is helpful, but not essential. You do not need to know anything about OpenGL.
Using the Code
I have provided a simple demonstration application which shows how to use the control:
The surface plot control is embedded in the application's main window as follows:
<WindowsFormsHost Grid.Row="0" Grid.RowSpan="2"
Grid.Column="1" Margin="0" Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<SurfacePlot:SurfacePlotControl x:Name="_surfacePlotControl"/>
</WindowsFormsHost>
The main window initializes the control after the window has been loaded:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
OpenControls.Wpf.SurfacePlotterDemo.ViewModel.MainViewModel mainViewModel =
new OpenControls.Wpf.SurfacePlotterDemo.ViewModel.MainViewModel();
DataContext = mainViewModel;
mainViewModel.Load();
_configurationControl.DataContext =
new OpenControls.Wpf.SurfacePlot.ViewModel.ConfigurationControlViewModel
(mainViewModel.IConfiguration);
_surfacePlotControl.Initialise(mainViewModel.IConfiguration);
}
The IConfiguration
property of the main window view model class is declared as follows:
private readonly Model.IConfiguration IConfiguration;
The IConfiguration
interface defines the configuration settings:
public interface IConfiguration : IConfigurationSubject
{
event ConfigurationChangedEventHandler ConfigurationChanged;
void Load(IConfigurationSerialiser configurationSerialiser);
void Save(IConfigurationSerialiser configurationSerialiser);
int Zoom { get; set; }
int MaximumZoom { get; }
int MinimumZoom { get; }
double ZScale { get; set; }
string BackgroundColour { get; set; }
bool ShowAxes { get; set; }
bool ShowAxesTitles { get; set; }
bool ShowZBar { get; set; }
bool ShowFrame { get; set; }
string FrameColour { get; set; }
bool ShowLabels { get; set; }
string LabelColour { get; set; }
int LabelFontSize { get; set; }
int LabelAngleInDegrees { get; set; }
bool TransparentLabelBackground { get; set; }
XYLabelPosition XYLabelPosition { get; set; }
float Perspective { get; set; }
ViewProjection ViewProjection { get; set; }
ShadingMethod ShadingMethod { get; set; }
bool ShowGrid { get; set; }
string GridColour { get; set; }
bool ShowScatterPlot { get; set; }
bool ShowShading { get; set; }
ShadingAlgorithm ShadingAlgorithm { get; set; }
short BlueLevel { get; set; }
short RedLevel { get; set; }
bool Hold { get; set; }
bool HoldMaximum { get; set; }
}
Observers (including the surface plot control) request a notification when a setting changes by adding a handler to the ConfigurationChanged
event. The application may also serialize settings to and from a storage medium.
The IConfiguration
interface is implemented by the Configuration
class.
IConfiguration = new OpenControls.Wpf.SurfacePlot.Model.Configuration();
The plot is updated by calling the SetData
method on the surface plot control. This method must be called on a UI thread as per the following example:
this.Dispatcher.Invoke(delegate
{
_surfacePlotControl.SetData(data, -50, 50, 21, -50, 50, 21, zMin, zMax, 21);
});
The arguments to the SetData
method are as follows:
numberOfZLabels
Type | Name | Meaning |
List<List<float>> | lineData | The Z values to plot |
float | xMin | The minimum X value |
float | xMax | The maximum X value |
int | numberOfXLabels | The number of X axis labels |
float | yMin | The minimum Y value |
float | yMax | The maximum Y value |
int | numberOfYLabels | The number of Y axis labels |
float | zMin | The minimum Z value |
float | zMax | The maximum Z value |
float | numberOfZLabels | The number of Z axis labels |
Note that the minimum and maximum values define the axes label values.
Storing the Configuration
The main window loads the configuration as follows:
IConfiguration.Load(IConfigurationSerialiser);
And it saves the configuration as follows:
IConfiguration.Save(IConfigurationSerialiser);
The IConfigurationSerialiser
argument is an instance of the IConfigurationSerialiser
interface:
public interface IConfigurationSerialiser
{
void WriteEntry<T>(string key, T value);
T ReadEntry<T>(string key, T value);
}
The interface defines methods to serialize settings to and from a storage medium.
The IConfigurationSerialiser
interface is implemented by the ConfigurationSerialiser
class:
IConfigurationSerialiser = new SurfacePlot.Model.ConfigurationSerialiser();
The ConfigurationSerialiser
class serializes settings to and from the registry.
You can, of course, implement your own class and derive it from the IConfigurationSerialiser
interface.
Label Format
By default, the labels are formatted as per the following example:
public string XLabel(float x, int index) => x.ToString("G4");
Thus 100
would appear as "100
" and 12.5
as "12.5
".
The application may override the default label format by passing an instance of the ILabelFormatter
interface to the surface plot control. For example:
_surfacePlotControl.ILabelFormatter = this;
In the above example, the this
pointer is an instance of a class that implements the interface.
The ILabelFormatter
interface is defined as follows:
public interface ILabelFormatter
{
string XLabel(float x, int index);
string YLabel(float y, int index);
string ZLabel(float z);
}
WinForms: Displaying the Configuration Dialog
The configuration dialog is for use by WinForms applications:
The following code displays the dialog:
OpenControls.Wpf.SurfacePlot.Exports.ShowConfigurationDialog(configuration);
Where the configuration
argument is an instance of the Configuration
class described earlier.
Shortcomings
The control does not render the scatter plot points as spheres or circles. I tried drawing polygons to approximate spheres, but it required too much processing, and slowed down the drawing to an unacceptable degree.
Points of Interest
OpenGL does not define text drawing methods. On initialization, the code creates and saves a bitmap of characters. To draw a character, it draws a square using a section from the bitmap as the fill texture. This is repeated for each character in each string.
OpenGL can be used in direct and indirect modes. In indirect mode, the data to be rendered is copied to internal buffers for subsequent display during a paint request. In direct mode, the data is passed directly to Open GL during the paint request. Indirect mode is more efficient and faster. However, it has little if any advantage when displaying real time data, as the buffers have to be constantly updated. The surface plot control uses OpenGL in direct mode.
GitHub
The code is available on GitHub as part of the OpenControls solution:
History
- 19th February, 2021: First version
- 21st February, 2021: Added a function to display the settings view, for use by WinForms applications,
- 23rd February, 2021: Adjusted the projection parameters as the far point was too close when plotting a large number of points