InteractiveDataDisplay.WPF
這是微軟出的一個開源的曲線圖控制元件,目前已經沒有更新了,而且只支援.NET Framework,不支援.NET Core平臺。
安裝
Install-Package InteractiveDataDisplay.WPF
前臺程式碼
<Window
x:Class="HeatmapGraphDemo.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:d3="clr-namespace:InteractiveDataDisplay.WPF;assembly=InteractiveDataDisplay.WPF"
xmlns:local="clr-namespace:HeatmapGraphDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<d3:Chart>
<d3:Chart.Title>
<TextBlock Margin="0,5,0,5" FontSize="18">Heatmap sample</TextBlock>
</d3:Chart.Title>
<!--<d3:HeatmapGraph x:Name="heatmap" Palette="Yellow,Blue,Orange" />-->
<d3:HeatmapGraph x:Name="heatmap" />
</d3:Chart>
<Button
Grid.Row="1"
Click="Button_Click"
Content="載入" />
</Grid>
</Window>
後臺程式碼
using MiniExcelLibs;
using System.Diagnostics;
using System.Linq;
using System.Windows;
namespace HeatmapGraphDemo
{
/// <summary>
/// MainWindow.xaml 的互動邏輯
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var items = MiniExcel.Query<DataInfo>("test2.xlsx");
items = items.OrderBy(s => s.Longitude).ThenBy(s => s.Latitude).ToList();
var x = items.Select(s => s.Longitude).Distinct().ToArray();
var y = items.Select(s => s.Latitude).Distinct().ToArray();
var values = items.Select(s => s.AccessCycle).ToArray();
Debug.Assert(x.Length * y.Length == values.Length);
var f = new double[x.Length, y.Length];
for (int i = 0; i < x.Length; i++)
{
for (int j = 0; j < y.Length; j++)
{
f[i, j] = values[i * y.Length + j];
}
}
heatmap.Plot(f, x, y);
}
}
public class DataInfo
{
public double Longitude { get; set; }
public double Latitude { get; set; }
public double AccessCycle { get; set; }
}
}
最終效果
試用總結
作為一個古老的控制元件,但是單單熱力圖這個功能,實現得相當靠譜。
生成熱力圖的時候,會同時要求提供X,Y,Z三個引數組。而且X,Y不要求均勻間隔,如果之間的間隔不均勻的時候,會自動進行插值。
生成的熱力圖最終圖片有點像一張平滑處理過的圖片。
LiveCharts2
也是一個開源的圖表控制元件。LiveCharts2目前還在RC版本,沒有正式釋出。支援的平臺很多,支援.NET FRAMEWORK和.NET CORE,除了支援WPF,也支援Avalonia這些平臺,能夠用來做跨平臺的專案。
安裝
因為還不是正式版,需要跟上具體的版本號安裝。
Install-Package LiveChartsCore.SkiaSharpView.WPF -Version 2.0.0-rc4.5
前臺介面
<Window
x:Class="LiveChartDemo.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:local="clr-namespace:LiveChartDemo"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<!--<lvc:CartesianChart
Series="{Binding Series}"
XAxes="{Binding XAxes}"
YAxes="{Binding YAxes}" />-->
<lvc:CartesianChart Series="{Binding Series}" />
</Grid>
</Window>
後臺程式碼
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using MiniExcelLibs;
using SkiaSharp;
namespace LiveChartDemo
{
public class MainWindowViewModel2
{
public ISeries[] Series { get; set; } = [
new HeatSeries<WeightedPoint>
{
HeatMap = [
SKColors.Blue.AsLvcColor(), // the first element is the "coldest"
SKColors.Red.AsLvcColor() // the last element is the "hottest"
]
}
];
//public ICartesianAxis[] XAxes { get; set; } = [
// new Axis
//{
// Labels = ["Charles", "Richard", "Ana", "Mari"]
//}
//];
//public ICartesianAxis[] YAxes { get; set; } = [
// new Axis
//{
// Labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
//}
//];
public MainWindowViewModel2()
{
var items = MiniExcel.Query<DataInfo>("test2.xlsx").ToList();
var ser = Series.First();
var list = new List<WeightedPoint>();
foreach (var item in items)
{
list.Add(new WeightedPoint(item.Longitude, item.Latitude, item.AccessCycle));
}
ser.Values = list;
}
}
public class DataInfo
{
public double Longitude { get; set; }
public double Latitude { get; set; }
public double AccessCycle { get; set; }
}
}
試用總結
間隔均勻的熱力圖也沒有啥問題。遇到間隔不均勻的資料,會出現中間的空白,不會像上一個控制元件一樣,自動去補齊。
OxyPlot
也是開源的圖表控制元件,目前也差不多是我們團隊裡面使用最多的圖表控制元件。跟LiveChart2一樣,支援.NET FRAMEWORK和.NET CORE,支援WPF和Avalonia這些平臺,可以用來做跨平臺開發專案。效能也不錯。
安裝
Install-Package OxyPlot.SkiaSharp.Wpf
前臺程式碼
<Window
x:Class="OxyPlotDemo.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:local="clr-namespace:OxyPlotDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:oxy="http://oxyplot.org/skiawpf"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<oxy:PlotView x:Name="plot" />
</Grid>
</Window>
後臺程式碼
using MiniExcelLibs;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System.Diagnostics;
using System.Windows;
namespace OxyPlotDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
plot.Model = GetPlotModel();
}
private PlotModel GetPlotModel()
{
var items = MiniExcel.Query<DataInfo>("test2.xlsx").ToList();
items = items.OrderBy(s => s.Longitude).ThenBy(s => s.Latitude).ToList();
var xvalues = items.Select(s => s.Longitude).Distinct().ToArray();
var yvalues = items.Select(s => s.Latitude).Distinct().ToArray();
var zvalues = items.Select(s => s.AccessCycle).ToArray();
var min = zvalues.Min();
var max = zvalues.Max();
var len = max - min;
Debug.Assert(xvalues.Length * yvalues.Length == zvalues.Length);
var model = new PlotModel { Title = "Heatmap" };
// Color axis (the X and Y axes are generated automatically)
model.Axes.Add(new LinearColorAxis
{
Palette = OxyPalettes.Rainbow(100)
});
//// generate 2d normal distribution
var data = new double[xvalues.Length, yvalues.Length];
for (int x = 0; x < xvalues.Length; ++x)
{
for (int y = 0; y < yvalues.Length; ++y)
{
data[y, x] = zvalues[y * xvalues.Length + x];
}
}
var heatMapSeries = new HeatMapSeries
{
X0 = xvalues.Min(),
X1 = xvalues.Max(),
Y0 = yvalues.Min(),
Y1 = yvalues.Max(),
Interpolate = true,
RenderMethod = HeatMapRenderMethod.Bitmap,
Data = data
};
model.Series.Add(heatMapSeries);
return model;
}
}
public class DataInfo
{
public double Longitude { get; set; }
public double Latitude { get; set; }
public double AccessCycle { get; set; }
}
}
試用總結
常規的熱力圖顯示也沒有問題。但是OxyPlot預設每個點之間的X和Y的間隔是均勻的。它的X軸和Y軸的區間是透過最大值和最小值去控制的,然後傳進去的點,它會預設當作最大值和最小值之間均分的,所以如果點是不均勻的,最終顯示的圖會有期望的圖有差異。
下面這張圖是X和Y值間隔均勻的影像。應該是符合預期的。
下面是特意把最後一個點的Y軸從40改成100的情況,看起來跟40的貌似沒啥區別,但這是不符合預期的。其實是應該像InteractiveDataDisplay.WPF顯示的那樣才對。
ScottPlot
這個控制元件貌似也有不少人使用,也聽到不少人推薦。但我們團隊用得不多,主要因為不熟,官方的示例和文件感覺有點偏簡單,一些我們平常經常用的功能,或者開發過程中遇到的一些問題不好解決,然後本身可用的控制元件較多,所以這個控制元件就用得比較少。
安裝
Install-Package ScottPlot.WPF
前臺程式碼
<Window
x:Class="ScottPlotDemo.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:local="clr-namespace:ScottPlotDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpf="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<wpf:WpfPlot Name="plot" />
<Button
Grid.Row="1"
Click="Button_Click"
Content="載入" />
</Grid>
</Window>
後臺程式碼
using MiniExcelLibs;
using ScottPlot;
using System.Diagnostics;
using System.Windows;
namespace ScottPlotDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var items = MiniExcel.Query<DataInfo>("test.xlsx").ToList();
items = items.OrderBy(s => s.Longitude).ThenBy(s => s.Latitude).ToList();
var x = items.Select(s => s.Longitude).Distinct().ToArray();
var y = items.Select(s => s.Latitude).Distinct().ToArray();
var values = items.Select(s => s.AccessCycle).ToArray();
var min = values.Min();
var max = values.Max();
var len = max - min;
Debug.Assert(x.Length * y.Length == values.Length);
var f = new Coordinates3d[x.Length, y.Length];
for (int i = 0; i < x.Length; i++)
{
for (int j = 0; j < y.Length; j++)
{
var info = f[i, j];
var data = items[i * y.Length + j];
info.X = data.Longitude;
info.Y = data.Latitude;
info.Z = data.AccessCycle;
}
}
//double[,] f2 = SampleData.MonaLisa();
var map = plot.Plot.Add.Heatmap(f);
map.Colormap = new ScottPlot.Colormaps.Turbo();
plot.Plot.Add.ColorBar(map);
plot.Plot.Axes.AutoScale();
plot.Refresh();
}
}
public class DataInfo
{
public double Longitude { get; set; }
public double Latitude { get; set; }
public double AccessCycle { get; set; }
}
}
試用總結
熱力圖是畫出來了,但是顯示的圖片完全一個顏色的,沒有深淺的對比,嘗試了不少方法,貌似都沒折騰出來。
提供了蒙娜麗莎的測試資料,貌似可以顯示出來,但是自己的資料顯示就一個色的。
最後的總結
熱力圖,大部分的圖表控制元件都是預設X和Y值間隔均勻的情況,並且大部分的圖表控制元件,傳點的時候,都是隻傳Z值的數值進去的。其實這也是熱力圖比較常規的使用方式。
不均勻的是不是顯示成散點圖會更合理一點?