WPF Sankey桑基图推荐

Sankey图可以分析能量数据流,网页形式的Sankey图库很多,而相应的WPF库却很少,这里推荐一个开源的WPF Sankey图:

GitHub - iou90/SankeyDiagram: A powerful and easy to use WPF library for drawing Sankey diagram

这个项目已经有好些年了,经过测试,功能还是比较完善的,下面对其部分功能进行修改(含一个Bug的屏蔽)。

加重节点颜色的显示     

原始的显示如下:

新的显示效果如下:

没错,就是添加了加重颜色的矩形显示,这里主要在SankeyStyleManager.cs中修改:

public void SetNodeBrush(SankeyNode node)
 {
     var brushCheck = diagram.NodeBrushes != null && diagram.NodeBrushes.Keys.Contains(node.Name);

     if (brushCheck)
     {
         node.Shape.Fill = diagram.NodeBrushes[node.Name].CloneCurrentValue();
     }
     else
     {
         if (diagram.UsePallette != SankeyPalette.None)
         {
             var solidBrush = defaultNodeLinksPalette[DefaultNodeLinksPaletteIndex].CloneCurrentValue() as SolidColorBrush;
             //设置边框颜色
             var fillColor = solidBrush.Color;
             fillColor.A = (byte)255;
             node.Shape.StrokeThickness = diagram.NodeThickness / 2;
             node.Shape.Stroke =new SolidColorBrush(fillColor);

             node.Shape.Fill = solidBrush;

             DefaultNodeLinksPaletteIndex++;

             if (DefaultNodeLinksPaletteIndex >= defaultNodeLinksPalette.Count)
             {
                 DefaultNodeLinksPaletteIndex = 0;
             }
         }
     }

     node.OriginalBrush = node.Shape.Fill.CloneCurrentValue();
 }

同时为了保证标签文字正确的偏移,在SankeyDiagramAssist.cs中修改CreateDiagram()

在if (diagram.SankeyFlowDirection == FlowDirection.TopToBottom){...}

   Else{}

将Canvas.SetRight(node.Label, node.Shape.Width);修改为

Canvas.SetRight(node.Label, diagram.NodeThickness);

将Canvas.SetLeft(node.Label, node.X + node.Shape.Width);修改为

Canvas.SetLeft(node.Label, node.X + diagram.NodeThickness);

调整颜色盘的颜色

Sankey图默认从颜色盘中获取颜色,并依次进行分配,这需要确保相应的属性设置:

UsePallette="NodesLinks"

在SankeyStyleManager.cs中的GetNodeLinksPalette(double opacity)可以预设各种颜色,例如:

return new List<Brush>()
{               
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#49B4FD")) { Opacity = opacity },//0095fb 蓝色
   
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF7D7D")) { Opacity = opacity },//ff0000 红色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#33C57C")) { Opacity = opacity },//60e8a4 绿色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EBB13C")) { Opacity = opacity },//ffa200 橙色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#12E0BC")) { Opacity = opacity },//00f2c8 浅绿色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#7373ff")) { Opacity = opacity }, //紫色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#91bc61")) { Opacity = opacity }, //青色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#dc89d9")) { Opacity = opacity }, //粉色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F2E510")) { Opacity = opacity },//fff100 黄色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#44c5f1")) { Opacity = opacity }, //浅蓝色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#19D904")) { Opacity = opacity },//85e91f 浅绿色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00b192")) { Opacity = opacity }, //蓝绿色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1cbe65")) { Opacity = opacity }, //1cbe65绿色
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#278bcc")) { Opacity = opacity },
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#954ab3")) { Opacity = opacity },
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#f3bc00")) { Opacity = opacity },
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#e47403")) { Opacity = opacity },//e47403
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EF5936")) { Opacity = opacity },//ce3e29
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#A1C5D3")) { Opacity = opacity },//d8dddf
    new SolidColorBrush((Color)ColorConverter.ConvertFromString("#ffb5ff")) { Opacity = opacity }
};

设置连接线为渐变色

原始图如下:

修改后如下:

在SankeyDiagramAssist.cs中的CreateNodesAndLinks(IEnumerable<SankeyData> datas)中修改

在shape.Tag = new SankeyLinkFinder(data.From, data.To);下面添加代码

//设置渐变色

var fromColor = (fromNode.Shape.Fill as SolidColorBrush).Color;

var toColor = (toNode.Shape.Fill as SolidColorBrush).Color;

fromColor.A = (int)(255 * 0.5); //这里的0.5为正常显示时的透明度,与颜色盘中的透明度一致

toColor.A = (int)(255 * 0.5); //这里的0.5为正常显示时的透明度,与颜色盘中的透明度一致

var grdBrush = new LinearGradientBrush(fromColor,toColor,0);

然后将shape.Fill = diagram.UsePallette != SankeyPalette.None ? fromNode.Shape.Fill.CloneCurrentValue()中的fromNode.Shape.Fill.CloneCurrentValue()修改为grdBrush即可。

调整连接线在弯曲位置变窄的效果

通过设置属性LinkCurvature即可,值在0-1之间,越大效果越明显,建议值为0.8

去掉对第三方库的引用

源库引用了第三方库的MeasureHepler.cs类,这里直接粘贴上源码

public class TextInfo
{
    public string Text { get; set; }

    public System.Windows.FlowDirection FlowDirection { get; set; }

    public Typeface Typeface { get; set; }

    public double FontSize { get; set; }

    public Brush Foreground { get; set; }

    public Thickness Margin { get; set; }
}

public static class MeasureHepler
{
    public static Size MeasureString(TextInfo info, CultureInfo culture)
    {
        if (info == null)
        {
            throw new ArgumentNullException("information");
        }

        var formattedText = new FormattedText(info.Text, culture, info.FlowDirection, info.Typeface, info.FontSize, info.Foreground, (new DpiScale(1, 1)).PixelsPerDip);
        return new Size(formattedText.Width + info.Margin.Left + info.Margin.Right, formattedText.Height + info.Margin.Top + info.Margin.Bottom);
    }

    public static Size MeasureString(string text, Style style, CultureInfo culture)
    {
        if (style == null || style.Setters == null)
        {
            throw new ArgumentNullException("style or style.Setters is null");
        }

        var flowDirection = System.Windows.FlowDirection.LeftToRight;
        FontFamily fontFamily = new FontFamily("Segoe UI");
        double emSize = 12.0;
        FontStyle style2 = FontStyles.Normal;
        FontWeight weight = FontWeights.Regular;
        FontStretch stretch = FontStretches.Normal;
        Thickness thickness = new Thickness(0.0);
        Brush foreground = new SolidColorBrush(Colors.Black);
        foreach (SetterBase setter2 in style.Setters)
        {
            Setter setter = (Setter)setter2;
            switch (setter.Property.Name)
            {
                case "FlowDirectoin":
                    flowDirection = (System.Windows.FlowDirection)setter.Value;
                    break;
                case "FontFamily":
                    fontFamily = (FontFamily)setter.Value;
                    break;
                case "FontSize":
                    emSize = (double)setter.Value;
                    break;
                case "FontStyle":
                    style2 = (FontStyle)setter.Value;
                    break;
                case "FontWeight":
                    weight = (FontWeight)setter.Value;
                    break;
                case "FontStretch":
                    stretch = (FontStretch)setter.Value;
                    break;
                case "Foreground":
                    foreground = (Brush)setter.Value;
                    break;
                case "Margin":
                    thickness = (Thickness)setter.Value;
                    break;
            }
        }

        var formattedText = new FormattedText(text, culture, flowDirection, new Typeface(fontFamily, style2, weight, stretch), emSize, foreground, (new DpiScale(1, 1)).PixelsPerDip);
        return new Size(formattedText.Width + thickness.Left + thickness.Right, formattedText.Height + thickness.Top + thickness.Bottom);
    }
}

当添加互为From/To的数据时,程序会进入死循环

在SankeyDiagramAssist.cs中的CreateNodesAndLinks(IEnumerable<SankeyData> datas)修改如下

var addedData=new List<SankeyData>();

 foreach (var data in datas)

 {

     if (addedData.Exists(n=>n.From==data.To && n.To==data.From))

         continue;

               

     if (!currentSliceNodes.Exists(n => n.Name == data.From))

     {

         currentSliceNodes.Add(CreateNode(data, data.From,true));

         addedData.Add(data);

     }

     if (!currentSliceNodes.Exists(n => n.Name == data.To))

     {

         currentSliceNodes.Add(CreateNode(data, data.To, false));

         addedData.Add(data);

     }

上面的代码通过添加addedData集合,检测如果存在互为From/To的数据时跳过

修改后最终的显示效果如下

选中节点时高亮显示所有相关的连接和加大文本字体

也可以只高亮显示单个连接和对应的文本字体加大

目前做了一些数据测试后发现,对于A->B->C->D...这种一层一层数据传递的,上述的库表现的很完美,但如果出现那种A->B,又出现A->C这种跨层次的数据传递,图形的避让和防重叠设计则表现得不怎样了,可能需要进一步优化。

  • 27
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值