几个图表控件关于热力图显示的调研笔记

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; }
    }
}

最终效果

image

试用总结

作为一个古老的控件,但是单单热力图这个功能,实现得相当靠谱。
生成热力图的时候,会同时要求提供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; }
    }
}

试用总结

间隔均匀的热力图也没有啥问题。遇到间隔不均匀的数据,会出现中间的空白,不会像上一个控件一样,自动去补齐。
image

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值间隔均匀的图像。应该是符合预期的。
image

下面是特意把最后一个点的Y轴从40改成100的情况,看起来跟40的貌似没啥区别,但这是不符合预期的。其实是应该像InteractiveDataDisplay.WPF显示的那样才对。
image

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值的数值进去的。其实这也是热力图比较常规的使用方式。
不均匀的是不是显示成散点图会更合理一点?

posted @ 2024-12-06 17:45  wzwyc  阅读(9)  评论(0编辑  收藏  举报