在 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 绘制图表的主要内容,如果未能解决你的问题,请参考以下文章

需要在WPF中绘制曲线图,请问使用哪种控件比较好

如何在 OxyPlot 图表上绘制 MULTIPLE LineSeries?

WPF绘制图表-LiveCharts

在WPF中 使用StreamGeometry提高性能。

动态数据显示 - WPF - 需要将文本添加到画布 - C#

WPF中的DataGridView绘制错误