摘要:本文演示了如何在WPF中使用LiveCharts.WPF创建双Y轴折线图控件,并集成到WinForms应用中。通过UcChart.xaml定义了包含左右双Y轴和动态X轴的图表布局,支持数据绑定和样式自定义。UcChart.xaml.cs实现了数据模型和属性变更通知,提供SetTitle方法动态设置轴标题。该控件具有缩放和拖拽功能,左右系列分别以蓝红两色显示,适用于需要展示多维度数据的场景。
WPF代码
UcChart.xaml代码
<UserControl x:Class="WpfChartControl.UcChart"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfChartControl"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid Background="White" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<lvc:CartesianChart x:Name="chart" Zoom="Y" Pan="Y">
<!-- 定义双 Y 轴 -->
<lvc:CartesianChart.AxisY>
<lvc:Axis x:Name="YLeftAxis" Title="{Binding LeftTitle}" Foreground="Blue" MaxValue="{Binding YLeftMax}"/>
<lvc:Axis x:Name="YRightAxis" Title="{Binding RightTitle}" Position="RightTop" Foreground="Red" MaxValue="{Binding YRightMax}">
<lvc:Axis.Separator>
<lvc:Separator></lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisY>
<!-- 定义 X 轴为动态分类轴 -->
<lvc:CartesianChart.AxisX>
<lvc:Axis x:Name="XAxis" Title="{Binding BottomTitle}" Labels="{Binding XLabels}" MaxValue="{Binding XAxisMax}">
<lvc:Axis.Separator>
<lvc:Separator StrokeThickness="1" Stroke="#DDDDDD"/>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
<!-- 定义两个数据系列 -->
<lvc:CartesianChart.Series>
<lvc:LineSeries
Title="{Binding LeftTitle}"
Values="{Binding LeftValues}"
ScalesYAt="0"
LineSmoothness="0"
Fill="Transparent"
StrokeThickness="2"
Stroke="Blue"/>
<lvc:LineSeries
Title="{Binding RightTitle}"
Values="{Binding RightValues}"
ScalesYAt="1"
LineSmoothness="0"
Fill="Transparent"
StrokeThickness="2"
Stroke="Red"/>
</lvc:CartesianChart.Series>
</lvc:CartesianChart>
</Grid>
</UserControl>
UcChart.xaml.cs代码
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using LiveCharts;
using LiveCharts.Wpf;
namespace WpfChartControl
{
/// <summary>
/// UcChart.xaml 的交互逻辑
/// </summary>
public partial class UcChart : UserControl, INotifyPropertyChanged
{
// 动态数据集合
public ChartValues<double> LeftValues { get; set; } = new ChartValues<double>();
public ChartValues<double> RightValues { get; set; } = new ChartValues<double>();
// 动态 X 轴标签
public ObservableCollection<string> XLabels { get; set; } = new ObservableCollection<string>();
public string _LeftTitle { get; set; }
public string _RightTitle { get; set; }
public string BottomTitle { get; set; }
public string LeftTitle
{
get => _LeftTitle;
set
{
_LeftTitle = value;
OnPropertyChanged();
}
}
public string RightTitle
{
get => _RightTitle;
set
{
_RightTitle = value;
OnPropertyChanged();
}
}
public UcChart()
{
InitializeComponent();
this.DataContext = this;
}
public void SetTitle(string leftTitle, string rightTitle, string bottomTitle)
{
LeftTitle = leftTitle;
RightTitle = rightTitle;
BottomTitle = bottomTitle;
}
public void SetTitle(SeriesType seriesType, string title)
{
switch (seriesType)
{
case SeriesType.Left:
LeftTitle = title;
break;
case SeriesType.Right:
RightTitle = title;
break;
case SeriesType.Bottom:
BottomTitle = title;
break;
default:
break;
}
}
public void ChangeTitle(SeriesType seriesType, string title)
{
switch (seriesType)
{
case SeriesType.Left:
LeftTitle = title;
break;
case SeriesType.Right:
RightTitle = title;
break;
case SeriesType.Bottom:
BottomTitle = title;
break;
default:
break;
}
}
/// <summary>
/// Clear清理数据 NaN使曲线图居中
/// </summary>
/// <param name="seriesType"></param>
public void ClearData(SeriesType seriesType)
{
switch (seriesType)
{
case SeriesType.Left:
LeftValues.Clear();
YLeftAxis.MaxValue = double.NaN;//每次添加数据后,将代码恢复居中显示
YLeftAxis.MinValue = double.NaN;
break;
case SeriesType.Right:
RightValues.Clear();
YRightAxis.MaxValue = double.NaN;
YRightAxis.MinValue = double.NaN;
break;
case SeriesType.Bottom:
XLabels.Clear();
break;
default:
break;
}
}
// 外部调用方法:动态更新双轴数据和 X 轴标签
public void UpdateData(double leftVal, double rightVal, string xLabel, int offset)
{
LeftValues.Add(leftVal);
if (LeftValues.Count > 100)
{
LeftValues.RemoveAt(0);
}
RightValues.Add(rightVal);
if (RightValues.Count > 100)
{
RightValues.RemoveAt(0);
}
XAxis.MaxValue = XLabels.Count + offset;
XLabels.Add(xLabel);
if (XLabels.Count > 100)
{
XLabels.RemoveAt(0);
}
}
public void UpdateLeftData(double data)
{
LeftValues.Add(data);
//if (LeftValues.Max() > 0)
//{
// YLeftAxis.MaxValue = LeftValues.Max() * 1.1;
//}
//else
//{
// YLeftAxis.MaxValue = LeftValues.Max() * 0.9;
//}
}
public void UpdateRightData(double data)
{
RightValues.Add(data);
//if (RightValues.Max() > 0)
//{
// YRightAxis.MaxValue = RightValues.Max() * 1.1;
//}
//else
//{
// YRightAxis.MaxValue = RightValues.Max() * 0.9;
//}
}
public void UpdateBottomData(string data, int offset)
{
XLabels.Add(data);
XAxis.MaxValue = XLabels.Count + offset;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName=null)
{
PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(propertyName));
}
}
public enum SeriesType : int
{
/// <summary>
/// 左轴
/// </summary>
Left = 0,
/// <summary>
/// 右轴
/// </summary>
Right = 1,
/// <summary>
/// 底轴
/// </summary>
Bottom = 2
}
}
Winform代码
LIvecharts控件dll和WPF制作的dll都要引入
代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
using LiveCharts.Wpf;
using LiveCharts;
using WpfChartControl;
namespace WpfChartWinform
{
public partial class Form1 : Form
{
private UcChart _wpfChart;
private readonly System.Windows.Forms.Timer _timer = new System.Windows.Forms.Timer();
private readonly Random _random = new Random();
private int _timeCounter = 0;
ElementHost host = new ElementHost();
public Form1()
{
InitializeComponent();
InitializeChart();
_wpfChart.SetTitle("左","右","底");
InitializeTimer();
}
// 初始化 WPF 控件
private void InitializeChart()
{
_wpfChart = new UcChart();
host = new ElementHost
{
Dock = DockStyle.Fill,
Child = _wpfChart
};
Controls.Add(host);
}
// 定时器驱动动态数据
private void InitializeTimer()
{
_timer.Interval = 10; // 1秒更新一次
_timer.Tick += (sender, e) =>
{
// 生成数据
double leftValue = _random.Next(0, 100);
double rightValue = _random.Next(100, 200);
string xLabel = DateTime.Now.AddSeconds(_timeCounter++).ToString("ss.fff");
this.Invoke(new Action(() =>
{
// 更新图表
_wpfChart.UpdateData(leftValue, rightValue, xLabel, 0);
}));
};
_timer.Start();
//double[] douy1 = { 1, 3, 5, 7, 9 };
////double[] douy2 = { 2, 4, 6, 8, 10 };
//double[] douy2 = { 0.0000000000099972, 0.0000000000100067, 0.0000000000100178, 0.0000000000099946, 0.0000000000099931 };
//string[] doux = { "1", "2", "3", "4", "5" };
//Task.Factory.StartNew(() => {
// for (int i=0;i<doux.Length;i++)
// {
// Thread.Sleep(100);
// this.Invoke(new Action(() =>
// {
// // 更新图表
// _wpfChart.UpdateBottomData(doux[i],0);
// }));
// }
// for (int i = 0; i < doux.Length; i++)
// {
// Thread.Sleep(100);
// this.Invoke(new Action(() =>
// {
// // 更新图表
// _wpfChart.UpdateLeftData(douy1[i]);
// }));
// }
// for (int i = 0; i < doux.Length; i++)
// {
// Thread.Sleep(100);
// this.Invoke(new Action(() =>
// {
// // 更新图表
// _wpfChart.UpdateRightData(douy2[i]);
// }));
// }
//});
}
private void Form1_Resize(object sender, EventArgs e)
{
host.Size = this.ClientSize;
}
}
}