C# 3D 曲面图控件





5.00/5 (20投票s)
C# 中的 3D 曲面图控件
引言
本文介绍了一个C#控件,它将Z值的二维数组绘制成曲面图或散点图。该控件可用于WPF和WinForms应用程序。它专为实时数据显示而设计,即数据会不断更新。将控件添加到应用程序中非常简单。
显示高度可配置,包括:
- 每个轴上的可选标签
- 用户可以选择标签的颜色、大小和方向。
- 可选的Z轴条,显示每个Z轴值对应的颜色。
- 连接数据点的可选网格
- 用户可以选择网格颜色。
- 可选的曲面着色
- 可选的散点图显示
- 保持功能,即只显示每个X,Y点处的最大(或最小)Z值。
- 用户可选择的投影:3D、正交和鸟瞰
- 用户可选择的背景颜色
- 显示器侧面的可选框架
- 用户可以选择框架颜色。
- 一个允许用户配置显示的WPF视图。
- 用户可以使用键盘操作视图,包括缩放、旋转和移动。
- 用户可选择的透视。
该控件通过C# OpenTK库使用Open GL绘制形状和文本。OpenGL速度很快,因为它使用PC的显卡。性能当然取决于PC和显卡的性质。
设置作为实现IConfiguration
接口的类的实例传递给控件。我提供了一个基本实现。配置可以从注册表读取和写入。
该库包含一个WPF用户控件,可以添加到表单或对话框中,并允许用户配置显示设置,例如框架颜色和标签大小。设置包括缩放、Z轴缩放和透视。我还创建了一个简单的导出方法,供WinForms应用程序使用,用于显示配置视图。
有两个简单的演示应用程序,一个使用WPF,另一个使用WinForms。
示例图
下面显示了一个带有轴、标签和框架的简单曲面图
在上面,曲面着色是连续的,即平滑的。
以下示例的浮点轴由调用应用程序格式化
标签和轴标题等元素可以删除。在以下示例中,标签和轴标题未绘制
配色方案可以更改。在以下示例中,背景为黑色
在以下示例中,网格未绘制,并且网格着色是粗糙的而不是连续的
数据可以绘制为点
曲面可以显示为鸟瞰图
数据可以显示为正交投影(从侧面)
配置视图
该库包含一个视图,它作为WPF用户控件实现,允许用户调整显示设置
键盘控制
用户可以使用鼠标和键盘按以下方式操作视图
- 放大和缩小:按住鼠标左键并旋转鼠标滚轮。
- 移动图表:按住鼠标左键和Ctrl键,然后移动鼠标。
- 绕X、Y和Z轴旋转:按住鼠标左键并移动鼠标。
背景
您需要对使用C#编写Windows应用程序有很好的理解。WPF的基本知识有所帮助,但并非必不可少。您无需了解任何关于OpenGL的知识。
Using the Code
我提供了一个简单的演示应用程序,展示了如何使用该控件
曲面图控件嵌入在应用程序的主窗口中,如下所示
<WindowsFormsHost Grid.Row="0" Grid.RowSpan="2"
Grid.Column="1" Margin="0" Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<SurfacePlot:SurfacePlotControl x:Name="_surfacePlotControl"/>
</WindowsFormsHost>
主窗口在窗口加载后初始化控件
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);
}
主窗口视图模型类的IConfiguration
属性声明如下
private readonly Model.IConfiguration IConfiguration;
IConfiguration
接口定义了配置设置
public interface IConfiguration : IConfigurationSubject
{
// Signalled whenever a configuration setting is changed
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; }
}
观察者(包括曲面图控件)通过向ConfigurationChanged
事件添加处理程序来请求设置更改时的通知。应用程序还可以将设置序列化到存储介质和从存储介质反序列化。
IConfiguration
接口由Configuration
类实现。
IConfiguration = new OpenControls.Wpf.SurfacePlot.Model.Configuration();
通过调用曲面图控件上的SetData
方法来更新绘图。此方法必须在UI线程上调用,如下例所示
this.Dispatcher.Invoke(delegate
{
_surfacePlotControl.SetData(data, -50, 50, 21, -50, 50, 21, zMin, zMax, 21);
});
SetData
方法的参数如下
numberOfZLabels
类型 | 名称 | 含义 |
List<List<float>> | lineData | 要绘制的Z值 |
float | xMin | 最小X值 |
float | xMax | 最大X值 |
int | numberOfXLabels | X轴标签的数量 |
float | yMin | 最小Y值 |
float | yMax | 最大Y值 |
int | numberOfYLabels | Y轴标签的数量 |
float | zMin | 最小Z值 |
float | zMax | 最大Z值 |
float | numberOfZLabels | Z轴标签的数量 |
请注意,最小值和最大值定义了轴标签值。
存储配置
主窗口按如下方式加载配置
IConfiguration.Load(IConfigurationSerialiser);
并按如下方式保存配置
IConfiguration.Save(IConfigurationSerialiser);
IConfigurationSerialiser
参数是IConfigurationSerialiser
接口的实例
public interface IConfigurationSerialiser
{
void WriteEntry<T>(string key, T value);
T ReadEntry<T>(string key, T value);
}
该接口定义了将设置序列化到存储介质和从存储介质反序列化的方法。
IConfigurationSerialiser
接口由ConfigurationSerialiser
类实现
IConfigurationSerialiser = new SurfacePlot.Model.ConfigurationSerialiser();
ConfigurationSerialiser
类将设置序列化到注册表和从注册表反序列化。
当然,您可以实现自己的类并从IConfigurationSerialiser
接口派生。
标签格式
默认情况下,标签按以下示例格式化
public string XLabel(float x, int index) => x.ToString("G4");
因此,100
将显示为"100
",12.5
将显示为"12.5
"。
应用程序可以通过将ILabelFormatter
接口的实例传递给曲面图控件来覆盖默认标签格式。例如
_surfacePlotControl.ILabelFormatter = this;
在上面的示例中,this
指针是实现该接口的类的实例。
ILabelFormatter
接口定义如下
public interface ILabelFormatter
{
string XLabel(float x, int index);
string YLabel(float y, int index);
string ZLabel(float z);
}
WinForms:显示配置对话框
配置对话框供WinForms应用程序使用
以下代码显示对话框
OpenControls.Wpf.SurfacePlot.Exports.ShowConfigurationDialog(configuration);
其中configuration
参数是前面描述的Configuration
类的实例。
缺点
该控件不将散点图点渲染为球体或圆形。我尝试绘制多边形来近似球体,但这需要太多的处理,并使绘图速度降低到不可接受的程度。
关注点
OpenGL没有定义文本绘制方法。在初始化时,代码创建并保存字符的位图。要绘制字符,它使用位图的一部分作为填充纹理绘制一个正方形。这对于每个字符串中的每个字符重复。
OpenGL可以在直接和间接模式下使用。在间接模式下,要渲染的数据被复制到内部缓冲区,以便在绘制请求期间后续显示。在直接模式下,数据在绘制请求期间直接传递给Open GL。间接模式效率更高,速度更快。然而,在显示实时数据时,它几乎没有任何优势,因为缓冲区必须不断更新。曲面图控件在直接模式下使用OpenGL。
GitHub
该代码可在GitHub上作为OpenControls解决方案的一部分提供
历史
- 2021年2月19日:第一个版本
- 2021年2月21日:添加了一个函数,用于显示设置视图,供WinForms应用程序使用。
- 2021年2月23日:调整了投影参数,因为在绘制大量点时,远点太近了