WPF实现绘制折线图的示例代码

 更新时间:2024年04月17日 09:31:44   作者:WPF开发者  
这篇文章主要为大家详细介绍了如何使用WPF实现绘制简单的折线图,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

WPF 实现折线图

框架支持.NET4 至 .NET8

Visual Studio 2022;

实现代码

1)新增 ChartBase 代码如下:

1.绘制X轴:根据控件的宽度和数据的数量计算出图表的宽度,并在底部绘制X轴。

2.绘制Y轴虚线:绘制一系列垂直的短线来代表Y轴的虚线。

3.绘制Y轴数值文本:在Y轴虚线的旁边绘制对应的数值文本。

4.计算刻度值:根据数据的最大值和设定的行数来计算Y轴上每个刻度的值。

5.绘制Y轴线:在每个刻度值的位置绘制一条线来代表Y轴。

6.绘制Y轴数值文本:在每个刻度的位置绘制对应的数值文本。

xAxiHeight 设定 X 轴的高度

StartY 设定 Y 轴的起始位置

width 计算图表的宽度

IntervalY Y 轴的间隔初始化为0

x 当前 X 轴的位置

y 当前 Y 轴的位置加上画笔高度

drawingContext.DrawSnappedLinesBetweenPoints(myPen, myPen.Thickness, new Point(StartX, StartY), new Point(width, StartY)); 绘制X

drawingContext.DrawSnappedLinesBetweenPoints(myPen, myPen.Thickness, points.ToArray()); 绘制底部 X 轴的齿距

drawingContext.DrawText(formattedText, new Point(StartX - formattedText.Width - 10, yAxis - formattedText.Height / 2));绘制 Y 轴的数值

drawingContext.DrawSnappedLinesBetweenPoints(xAxisPen, xAxisPen.Thickness, points.ToArray()); 绘制 Y 轴上的线

public class ChartBase : Control
{
    public static readonly DependencyProperty DatasProperty =
        DependencyProperty.Register("Datas", typeof(IEnumerable<KeyValuePair<string, double>>),
            typeof(ChartBase), new UIPropertyMetadata(DatasChanged));

    static ChartBase()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ChartBase),
            new FrameworkPropertyMetadata(typeof(ChartBase)));
    }

    protected double Rows { get; } = 5;

    protected double Interval { get; } = 120;

    protected short ScaleFactor { get; private set; } = 80;

    protected Brush ChartFill { get; private set; }

    protected double StartX { get; private set; }

    protected double StartY { get; private set; }

    protected double MaxY { get; }

    protected double IntervalY { get; private set; }

    protected Brush NormalBrush => ControlsHelper.PrimaryNormalBrush;

    public IEnumerable<KeyValuePair<string, double>> Datas
    {
        get => (IEnumerable<KeyValuePair<string, double>>) GetValue(DatasProperty);
        set => SetValue(DatasProperty, value);
    }

    private static void DatasChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctrl = d as ChartBase;
        if (e.NewValue != null)
            ctrl.InvalidateVisual();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);
        if (Datas == null || Datas.Count() == 0)
            return;
        SnapsToDevicePixels = true;
        UseLayoutRounding = true;
        ChartFill = Application.Current.TryFindResource("WD.ChartFillSolidColorBrush") as Brush;
        var myPen = new Pen
        {
            Thickness = 1,
            Brush = ChartFill
        };
        myPen.Freeze();

        var xAxiHeight = 4;
        StartY = ActualHeight - (xAxiHeight + myPen.Thickness) - 20;
        var w = ActualWidth;
        StartX = 40;
        var width = Datas.Count() * Interval + StartX;
        IntervalY = 0;
        var x = StartX;
        var y = StartY + myPen.Thickness;

        drawingContext.DrawSnappedLinesBetweenPoints(myPen, myPen.Thickness, new Point(StartX, StartY),
            new Point(width, StartY));

        var points = new List<Point>();
        for (var i = 0; i < Datas.Count() + 1; i++)
        {
            points.Add(new Point(x, y));
            points.Add(new Point(x, y + xAxiHeight));
            x += Interval;
        }

        drawingContext.DrawSnappedLinesBetweenPoints(myPen, myPen.Thickness, points.ToArray());

        var formattedText = DrawingContextHelper.GetFormattedText(IntervalY.ToString(),
            ChartFill, FlowDirection.LeftToRight);
        drawingContext.DrawText(formattedText,
            new Point(StartX - formattedText.Width * 2, StartY - formattedText.Height / 2));

        var xAxisPen = new Pen
        {
            Thickness = 1,
            Brush = Application.Current.TryFindResource("WD.ChartXAxisSolidColorBrush") as Brush
        };
        xAxisPen.Freeze();
        var max = Convert.ToInt32(Datas.Max(kvp => kvp.Value));
        var min = Convert.ToInt32(Datas.Min(kvp => kvp.Value));
        ScaleFactor = Convert.ToInt16(StartY / Rows);
        var yAxis = StartY - ScaleFactor;
        points.Clear();
        var average = Convert.ToInt32(max / Rows);
        var result = Enumerable.Range(0, (Convert.ToInt32(max) - average) / average + 1)
            .Select(i => average + i * average);
        foreach (var item in result)
        {
            points.Add(new Point(StartX, yAxis));
            points.Add(new Point(width, yAxis));
            IntervalY = item;
            formattedText = DrawingContextHelper.GetFormattedText(IntervalY.ToString(),
                ChartFill, FlowDirection.LeftToRight);
            drawingContext.DrawText(formattedText,
                new Point(StartX - formattedText.Width - 10, yAxis - formattedText.Height / 2));
            yAxis -= ScaleFactor;
        }
        drawingContext.DrawSnappedLinesBetweenPoints(xAxisPen, xAxisPen.Thickness, points.ToArray());
    }
}

2)新增 ChartLine 代码如下:

  • 1.计算比例和位置:根据第一个数据点的值计算其在Y轴上的比例和位置。
  • 2.绘制数据点标签:遍历数据集中的每一个数据点,为每个数据点绘制其标签,并计算标签的绘制位置。
  • 3.绘制线条和椭圆:对于每个数据点,计算其在Y轴上的位置,并绘制从上一个数据点到当前数据点的线。同时,绘制一个椭圆来表示当前的数据点。
  • 4.更新位置和状态:更新起始点和X轴位置,为绘制下一个数据点做准备。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;

namespace WPFDevelopers.Controls
{
    public class ChartLine : ChartBase
    {
        private const double _size = 10;
        protected override void OnRender(DrawingContext drawingContext)
        {
            if (Datas == null || Datas.Count() == 0)
                return;
            base.OnRender(drawingContext);
            var x = StartX;
            var interval = Interval;
            var drawingPen = new Pen
            {
                Thickness = 1,
                Brush = NormalBrush
            };
            drawingPen.Freeze();
            var firstDataPoint = Datas.FirstOrDefault();
            if (firstDataPoint.Equals(default(KeyValuePair<string, double>)))
                return;
            double proportion = firstDataPoint.Value / IntervalY;
            double yPositionFromBottom = StartY - proportion * (ScaleFactor * Rows);
            var startPoint = new Point(x + Interval / 2, yPositionFromBottom);
            foreach (var item in Datas)
            {
                var formattedText = DrawingContextHelper.GetFormattedText(item.Key,
                   ChartFill, FlowDirection.LeftToRight);
                var point = new Point(x + interval / 2 - formattedText.Width / 2, StartY + 4);
                drawingContext.DrawText(formattedText, point);

                var y = StartY - (item.Value / IntervalY) * (ScaleFactor * Rows);
                var endPoint = new Point(x + Interval / 2, y);
                drawingContext.DrawLine(drawingPen, startPoint, endPoint);
                var ellipsePoint = new Point(endPoint.X - _size / 2, endPoint.Y - _size / 2);
                var rect = new Rect(ellipsePoint, new Size(_size, _size));
                var ellipseGeom = new EllipseGeometry(rect);
                drawingContext.DrawGeometry(drawingPen.Brush, drawingPen, ellipseGeom);
                startPoint = endPoint;
                x += interval;
            }
        }
    }
}

3)新增 ChartLineExample.xaml 示例代码如下:

<Grid Background="{DynamicResource WD.BackgroundSolidColorBrush}">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                <Border
                Height="500"
                Margin="30,0"
                Background="{DynamicResource WD.BackgroundSolidColorBrush}">
                    <wd:ChartLine Datas="{Binding Datas, RelativeSource={RelativeSource AncestorType=local:ChartLineExample}}" />
                </Border>
            </ScrollViewer>
            <Button
                Grid.Row="1"
                Width="200"
                VerticalAlignment="Bottom"
                Click="Button_Click"
                Content="刷新"
                Style="{StaticResource WD.PrimaryButton}" />
        </Grid>

3)新增 ChartLineExample.xaml.cs 示例代码如下:

public partial class ChartLineExample : UserControl
{
    public IEnumerable<KeyValuePair<string, double>> Datas
    {
        get { return (IEnumerable<KeyValuePair<string, double>>)GetValue(DatasProperty); }
        set { SetValue(DatasProperty, value); }
    }

    public static readonly DependencyProperty DatasProperty =
        DependencyProperty.Register("Datas", typeof(IEnumerable<KeyValuePair<string, double>>), typeof(ChartLineExample), new PropertyMetadata(null));

    private Dictionary<string, IEnumerable<KeyValuePair<string, double>>> keyValues = new Dictionary<string, IEnumerable<KeyValuePair<string, double>>>();
    private int _index = 0;
    public ChartLineExample()
    {
        InitializeComponent();
        var models1 = new[]
        {
            new KeyValuePair<string, double>("Mon", 120),
            new KeyValuePair<string, double>("Tue", 530),
            new KeyValuePair<string, double>("Wed", 1060),
            new KeyValuePair<string, double>("Thu", 140),
            new KeyValuePair<string, double>("Fri", 8000) ,
            new KeyValuePair<string, double>("Sat", 200) ,
            new KeyValuePair<string, double>("Sun", 300) ,
        };
        var models2 = new[]
        {
            new KeyValuePair<string, double>("(1)月", 120),
            new KeyValuePair<string, double>("(2)月", 170),
            new KeyValuePair<string, double>("(3)月", 30),
            new KeyValuePair<string, double>("(4)月", 200),
            new KeyValuePair<string, double>("(5)月", 100) ,
            new KeyValuePair<string, double>("(6)月", 180) ,
            new KeyValuePair<string, double>("(7)月", 90) ,
        };
        keyValues.Add("1", models1);
        keyValues.Add("2", models2);
        Datas = models1;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        _index++;
        if (_index >= keyValues.Count)
        {
            _index = 0;
        }
        Datas = keyValues.ToList()[_index].Value;
    }
}

效果图

以上就是WPF实现绘制折线图的示例代码的详细内容,更多关于WPF折线图的资料请关注脚本之家其它相关文章!

相关文章

  • C# PC版微信消息监听自动回复的实现方法

    C# PC版微信消息监听自动回复的实现方法

    这篇文章主要介绍了C# PC版微信消息监听自动回复的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • C#实现在匿名方法中捕获外部变量的方法

    C#实现在匿名方法中捕获外部变量的方法

    这篇文章主要介绍了C#实现在匿名方法中捕获外部变量的方法,本文直接给出代码实例,然后分析了代码中的一些知识点,需要的朋友可以参考下
    2015-03-03
  • 深入c# GDI+简单绘图的具体操作步骤(一)

    深入c# GDI+简单绘图的具体操作步骤(一)

    本篇文章是对GDI的基础知识进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C#中增强类功能的几种方式详解

    C#中增强类功能的几种方式详解

    这篇文章主要给大家介绍了关于C#中增强类功能的几种方式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2018-12-12
  • C#设置WinForm中DataGrid列的方法(列宽/列标题等)

    C#设置WinForm中DataGrid列的方法(列宽/列标题等)

    这篇文章主要介绍了C#设置WinForm中DataGrid列的方法,包括列宽、列标题等部分,并分析了其中相关的操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • C# Winform自动更新程序实例详解

    C# Winform自动更新程序实例详解

    这篇文章主要为大家详细介绍了C# Winform 自动更新程序实例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • 详解WPF中的对象资源

    详解WPF中的对象资源

    这篇文章主要介绍了WPF中对象资源的相关资料,帮助大家更好的理解和学习使用WPF,感兴趣的朋友可以了解下
    2021-04-04
  • C#实现语音播报功能

    C#实现语音播报功能

    这篇文章主要为大家详细介绍了C#实现语音播报功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C#多线程之Thread中Thread.IsAlive属性用法分析

    C#多线程之Thread中Thread.IsAlive属性用法分析

    这篇文章主要介绍了C#多线程之Thread中Thread.IsAlive属性用法,实例分析了C#判断线程可用状态的技巧,非常具有实用价值,需要的朋友可以参考下
    2015-04-04
  • C#中读写INI配置文件的方法

    C#中读写INI配置文件的方法

    这篇文章主要介绍了C#中读写INI配置文件的方法,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-07-07

最新评论