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折线图的资料请关注其它相关文章!