在 C# 中正确使用 WPF 绘制图表
Posted
技术标签:
【中文标题】在 C# 中正确使用 WPF 绘制图表【英文标题】:Draw Graph Diagram with WPF in C# Properly 【发布时间】:2022-01-17 01:53:18 【问题描述】:这段代码如何生成与其他代码类似的图表?我已经发布了我的整个代码,如果有人是这方面的专家,请分享您的意见。我不完全理解在 WPF 中绘图!我仍在阅读该主题。
我的代码:
private void GenerateGraphBtn_Click(object sender, RoutedEventArgs e)
// Setting up the bounds of the graph
const double margin = 30;
double xmin = margin;
double xmax = (DrawGraphArea.Width / 2);
double ymin = margin;
double ymax = (DrawGraphArea.Height / 2);
const double step = 12;
// ##########################################################
// Make the X axis.
GeometryGroup xaxis_geom = new GeometryGroup();
// Creates the long Horisontal Line
xaxis_geom.Children.Add(new LineGeometry(
new Point(0, ymax), new Point(DrawGraphArea.Width / 2, ymax)));
// Adds all the mini lines on the horisontal axis (bottom)
for (double x = xmin + step;
x <= (DrawGraphArea.Width / 2) - step; x += step)
xaxis_geom.Children.Add(new LineGeometry(
new Point(x, ymax - margin / 2),
new Point(x, ymax + margin / 2)));
/* Adds all the lines that were created in the above code to the graph.
* Stroke thickness = line thickness
* Stroke = line colour
* Data = the geometry you are adding (the points that you have created in the above code)
*/
Path xaxis_path = new Path();
xaxis_path.StrokeThickness = 2;
xaxis_path.Stroke = Brushes.DarkRed;
xaxis_path.Data = xaxis_geom;
DrawGraphArea.Children.Add(xaxis_path);
// ##########################################################
// Creates the Y axis.
GeometryGroup yaxis_geom = new GeometryGroup();
// Adds the long Vertical Line
yaxis_geom.Children.Add(new LineGeometry(
new Point(xmin, 0), new Point(xmin, DrawGraphArea.Height / 2)));
// Adds the mini lines on the vertical axis (Left)
for (double y = step; y <= (DrawGraphArea.Height / 2) - step; y += step)
yaxis_geom.Children.Add(new LineGeometry(
new Point(xmin - margin / 2, y),
new Point(xmin + margin / 2, y)));
// Adds them all to the graph.
Path yaxis_path = new Path();
yaxis_path.StrokeThickness = 1;
yaxis_path.Stroke = Brushes.Black;
yaxis_path.Data = yaxis_geom;
DrawGraphArea.Children.Add(yaxis_path);
// ##########################################################
// This creates the brushes colours
Random rand = new Random();
// This will then go along and create all the colour
for (int data_set = 0; data_set < 1; data_set++)
int last_y = rand.Next((int)ymin, (int)ymax);
// This is where you add the points to the graph
// Little bit confusing as it is adding 3 lines at once
PointCollection points = new PointCollection();
for (double x = xmin; x <= xmax; x += step)
last_y = rand.Next(last_y - 10, last_y + 10);
if (last_y < ymin) last_y = (int)ymin;
if (last_y > ymax) last_y = (int)ymax;
points.Add(new Point(x, last_y));
// Adds the lines that connect the points
Polyline polyline = new Polyline();
polyline.StrokeThickness = 2;
polyline.Stroke = Brushes.Red;
polyline.Points = points;
// Add the line / points
DrawGraphArea.Children.Add(polyline);
当前结果:
期望的结果:
【问题讨论】:
嗨,John,我无法帮助重构代码,但如果您以前没有见过,这里有一个开源图形存储库。你可以到达这里。我希望它能指导你。 github.com/ScottPlot/ScottPlot @saklanmaz,我已经看到很多第三方准备好使用这些东西的库。但是,我不能使用它,禁止使用它。我很抱歉。不过,这里没有什么要Refactor,我只是想知道怎么做,因为我不完全理解。谢谢。 几个指针,如果你有想要的结果,为什么不提供与之配套的数据集呢?如果您想知道为什么轴标记的长度超过轴间隔长度的两倍,那是因为margin
是代码中使用的 step
值的两倍以上。
【参考方案1】:
您的解决方法是正确的,我使用您的代码作为指导。
所以,这里的第一个是提供您想要的结果的代码,
这将转到 MainWindow xaml:
<Window x:Class="WpfApp2.MainWindow"
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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="20" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Button" HorizontalAlignment="Left" Click="GenerateGraphBtn_Click" />
<Canvas Grid.Row="2" x:Name="GrpahArea" Margin="40" />
</Grid>
</Window>
这会进入 MainWindow 的代码隐藏
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApp2
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
private Point[] GenerateGraphpoints(int minimumX, int maximumX, int minimumY, int maximumY)
var result = new List<Point>();
var rnd = new Random();
for (var x = minimumX; x < maximumX; x++)
result.Add(new Point(x, rnd.Next(minimumY, maximumY)));
return result.ToArray();
private void PlotXAxes(Panel drawArea, int minimumX, int maximumX, int step, int miniLineExtent)
var geometryGroup = new GeometryGroup();
var axePositionY = drawArea.ActualHeight;
geometryGroup.Children.Add(new LineGeometry(new Point(0, axePositionY), new Point(drawArea.ActualWidth, axePositionY)));
var factorX = drawArea.ActualWidth / (maximumX - minimumX);
for (var i = minimumX; i < maximumX; i++)
if (i % step == 0)
geometryGroup.Children.Add(new LineGeometry(
new Point(i * factorX, axePositionY - miniLineExtent),
new Point(i * factorX, axePositionY + miniLineExtent)));
drawArea.Children.Add(new Label
Margin = new Thickness(i*factorX, axePositionY + miniLineExtent, 0, 0),
Content = i,
Foreground = Brushes.Black
);
var path = new Path
StrokeThickness = 2,
Stroke = Brushes.DarkRed,
Data = geometryGroup
;
drawArea.Children.Add(path);
private void PlotYAxes(Panel drawArea, int minimumY, int maximumY, int step, int miniLineExtent)
var labelSize = new Size(30, 26);
var geometryGroup = new GeometryGroup();
var axePositionX = 0;
geometryGroup.Children.Add(new LineGeometry(new Point(axePositionX, 0), new Point(axePositionX, drawArea.ActualHeight)));
var factorY = drawArea.ActualHeight / (maximumY - minimumY);
for (var i = minimumY; i < maximumY; i++)
if (i % step == 0)
geometryGroup.Children.Add(new LineGeometry(
new Point(axePositionX - miniLineExtent, (maximumY-i) * factorY),
new Point(axePositionX + miniLineExtent, (maximumY-i) * factorY)));
drawArea.Children.Add(new Label
Width = labelSize.Width,
Height = labelSize.Height,
Margin = new Thickness(
axePositionX - miniLineExtent - labelSize.Width,
(maximumY - i) * factorY - (labelSize.Height / 2), 0, 0),
Content = i,
Foreground = Brushes.Black
);
var path = new Path
StrokeThickness = 2,
Stroke = Brushes.DarkRed,
Data = geometryGroup
;
drawArea.Children.Add(path);
private void PlotGraph(Point[] points, Panel drawArea, int minimumX, int maximumX, int minimumY, int maximumY)
var factorX = drawArea.ActualWidth / (maximumX-minimumX);
var factorY = drawArea.ActualHeight / (maximumY-minimumY);
for (var i = 0; i < points.Length; i++)
points[i].X *= factorX;
//points[i].Y *= factorY;
points[i].Y = (maximumY - points[i].Y) * factorY; //since zero of Y should be in bottom we "swap" the Y value.
Polyline polyline = new Polyline();
polyline.StrokeThickness = 2;
polyline.Stroke = Brushes.Blue;
polyline.Points = new PointCollection(points);
drawArea.Children.Add(polyline);
private void GenerateGraphBtn_Click(object sender, RoutedEventArgs e)
//Step 1: define the data limits and generate the data, this has nothing to do with the view.
const int xmin = 0, xmax = 12;
const int ymin = 0, ymax = 800;
var graphPoints = GenerateGraphPoints(xmin, xmax, ymin, ymax);
//Step 2: Draw yAxes
PlotYAxes(GrpahArea, ymin, ymax, 100, 2);
//Step 3: Draw xAxes
PlotXAxes(GrpahArea, xmin, xmax, 1, 2);
//Step 4: Plot Grpah
var plotPoints = new Point[graphPoints.Length]; //graphPoints is our model, we usually wish to keep the original value of model.
graphPoints.CopyTo(plotPoints,0); //plotPoints is what we going to scale and adjust to the view.
PlotGraph(plotPoints, GrpahArea, xmin, xmax, ymin, ymax);
现在到我回答的第二部分,构建图形组件并不是那么简单的任务,主要是因为图形通常被开发为许多应用程序的通用,所以允许的定制量需要很大,这就是其他人的原因正确地将您引导到一个现成的图书馆。
另外,我在答案中为您提供的代码不是 MVVM 样式,它与您提供的代码非常接近。这是因为以 MVVM 样式编写将需要创建一组视图模型类并将它们绑定到视图,考虑到自定义,与您要求解决的问题相比,这是相当大的努力。
但我希望提供的代码可以帮助您做任何您想做的事情。
更新,添加标签:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApp2
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
private Point[] GenerateGraphPoints(int minimumX, int maximumX, int minimumY, int maximumY)
var result = new List<Point>();
var rnd = new Random();
for (var x = minimumX; x < maximumX; x++)
result.Add(new Point(x, rnd.Next(minimumY, maximumY)));
return result.ToArray();
private void PlotXAxes(Panel drawArea, int minimumX, int maximumX, int step, int miniLineExtent, string title)
var geometryGroup = new GeometryGroup();
var axePositionY = drawArea.ActualHeight;
geometryGroup.Children.Add(new LineGeometry(new Point(0, axePositionY), new Point(drawArea.ActualWidth, axePositionY)));
var factorX = drawArea.ActualWidth / (maximumX - minimumX);
for (var i = minimumX; i < maximumX; i++)
if (i % step == 0)
geometryGroup.Children.Add(new LineGeometry(
new Point(i * factorX, axePositionY - miniLineExtent),
new Point(i * factorX, axePositionY + miniLineExtent)));
drawArea.Children.Add(new Label
Margin = new Thickness(i*factorX, axePositionY + miniLineExtent, 0, 0),
Content = i,
Foreground = Brushes.Black
);
var path = new Path
StrokeThickness = 2,
Stroke = Brushes.DarkRed,
Data = geometryGroup
;
drawArea.Children.Add(path);
var titleLabel = new Label
Margin = new Thickness(drawArea.ActualWidth - 50, drawArea.ActualHeight - 20, 0, 0),
Content = title
;
drawArea.Children.Add(titleLabel);
private void PlotYAxes(Panel drawArea, int minimumY, int maximumY, int step, int miniLineExtent, string title)
var labelSize = new Size(30, 26);
var geometryGroup = new GeometryGroup();
var axePositionX = 0;
geometryGroup.Children.Add(new LineGeometry(new Point(axePositionX, 0), new Point(axePositionX, drawArea.ActualHeight)));
var factorY = drawArea.ActualHeight / (maximumY - minimumY);
for (var i = minimumY; i < maximumY; i++)
if (i % step == 0)
geometryGroup.Children.Add(new LineGeometry(
new Point(axePositionX - miniLineExtent, (maximumY-i) * factorY),
new Point(axePositionX + miniLineExtent, (maximumY-i) * factorY)));
drawArea.Children.Add(new Label
Width = labelSize.Width,
Height = labelSize.Height,
Margin = new Thickness(
axePositionX - miniLineExtent - labelSize.Width,
(maximumY - i) * factorY - (labelSize.Height / 2), 0, 0),
Content = i,
Foreground = Brushes.Black
);
var path = new Path
StrokeThickness = 2,
Stroke = Brushes.DarkRed,
Data = geometryGroup
;
drawArea.Children.Add(path);
var titleLabel = new Label
Margin = new Thickness(0, 0, 0, 0),
Content = title
;
drawArea.Children.Add(titleLabel);
private void PlotGraph(Point[] points, Panel drawArea, int minimumX, int maximumX, int minimumY, int maximumY)
var factorX = drawArea.ActualWidth / (maximumX-minimumX);
var factorY = drawArea.ActualHeight / (maximumY-minimumY);
for (var i = 0; i < points.Length; i++)
points[i].X *= factorX;
//points[i].Y *= factorY;
points[i].Y = (maximumY - points[i].Y) * factorY; //since zero of Y should be in bottom we "swap" the Y value.
Polyline polyline = new Polyline();
polyline.StrokeThickness = 2;
polyline.Stroke = Brushes.Blue;
polyline.Points = new PointCollection(points);
drawArea.Children.Add(polyline);
private void GenerateGraphBtn_Click(object sender, RoutedEventArgs e)
//Step 1: define the data limits and generate the data, this has nothing to do with the view.
const int xmin = 0, xmax = 12;
const int ymin = 0, ymax = 800;
var graphPoints = GenerateGraphPoints(xmin, xmax, ymin, ymax);
GrpahArea.Children.Add(new Label
Content = "Profits over months",
Margin = new Thickness(GrpahArea.ActualWidth / 2 - 100, -50, 0, 0)
);
//Step 2: Draw yAxes
PlotYAxes(GrpahArea, ymin, ymax, 100, 2, "Profits");
//Step 3: Draw xAxes
PlotXAxes(GrpahArea, xmin, xmax, 1, 2, "Months");
//Step 4: Plot Grpah
var plotPoints = new Point[graphPoints.Length]; //graphPoints is our model, we usually wish to keep the original value of model.
graphPoints.CopyTo(plotPoints,0); //plotPoints is what we going to scale and adjust to the view.
PlotGraph(plotPoints, GrpahArea, xmin, xmax, ymin, ymax);
【讨论】:
我会尽快回复您! 不需要这符合 MVVM。不用担心。我对此表示感谢。非常感谢。您将如何添加标题标签?只要让我知道,我会给你 50 分 + 答案。 @JohnSmith 您的意思是整个图表的标题?像标题? @JohnSmith 您可以通过添加标签从 XAML 或从代码中执行此操作 - 您喜欢什么? @JohnSmith 将坐标轴的标题和标题添加到答案中。以上是关于在 C# 中正确使用 WPF 绘制图表的主要内容,如果未能解决你的问题,请参考以下文章
如何在 OxyPlot 图表上绘制 MULTIPLE LineSeries?