阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 桑基图(Sankey)实现

桑基图(Sankey)实现

作者:陈川 阅读数:22411人阅读 分类: ECharts

桑基图的基本概念

桑基图是一种特定类型的流图,用于描述能量、物质或成本在不同节点间的流动情况。图中节点由带箭头的线连接,线条宽度与流量成比例。这种图表最早由爱尔兰工程师Matthew Henry Phineas Riall Sankey于1898年提出,用于展示蒸汽机的能量效率。

在数据可视化领域,桑基图特别适合展示:

  • 能源系统中的能量流动
  • 资金在不同部门间的转移
  • 网站用户在不同页面间的跳转路径
  • 产品生产过程中的物料流动

ECharts中的桑基图实现

ECharts提供了完善的桑基图支持,通过简单的配置即可创建复杂的流动关系图。基本实现需要定义nodes(节点)和links(连接)两个核心数据结构:

option = {
  series: [{
    type: 'sankey',
    layout: 'none',
    data: [{
      name: '节点1'
    }, {
      name: '节点2'
    }],
    links: [{
      source: '节点1',
      target: '节点2',
      value: 10
    }]
  }]
};

节点与连接配置

节点高级配置

节点支持丰富的样式定制,包括颜色、标签显示等:

data: [
  {
    name: '原料采购',
    itemStyle: {
      color: '#FF9F43'
    },
    label: {
      color: '#333',
      fontWeight: 'bold'
    }
  },
  {
    name: '生产加工',
    itemStyle: {
      color: '#28C76F'
    }
  }
]

连接线样式控制

连接线可以设置渐变色、透明度等视觉效果:

links: [
  {
    source: '原料采购',
    target: '生产加工',
    value: 100,
    lineStyle: {
      color: 'gradient',
      opacity: 0.7,
      curveness: 0.2
    }
  }
]

交互功能实现

ECharts桑基图支持丰富的交互功能:

series: [{
  type: 'sankey',
  // 启用拖拽
  draggable: true,
  // 节点点击事件
  emphasis: {
    focus: 'adjacency',
    itemStyle: {
      borderWidth: 3
    }
  },
  // 鼠标悬停样式
  blur: {
    itemStyle: {
      opacity: 0.1
    }
  }
}]

复杂数据示例

处理多层级数据时,需要合理组织节点关系:

const data = {
  nodes: [
    {name: '总预算'},
    {name: '市场部'},
    {name: '研发部'},
    {name: '线上广告'},
    {name: '线下活动'},
    {name: '产品开发'},
    {name: '技术研究'}
  ],
  links: [
    {source: '总预算', target: '市场部', value: 300},
    {source: '总预算', target: '研发部', value: 500},
    {source: '市场部', target: '线上广告', value: 200},
    {source: '市场部', target: '线下活动', value: 100},
    {source: '研发部', target: '产品开发', value: 350},
    {source: '研发部', target: '技术研究', value: 150}
  ]
};

样式深度定制

全局样式配置

series: [{
  type: 'sankey',
  nodeWidth: 20,
  nodeGap: 10,
  layoutIterations: 32,
  label: {
    position: 'right',
    fontSize: 12
  },
  lineStyle: {
    color: 'source',
    curveness: 0.5
  }
}]

响应式设计

结合ECharts的resize方法实现响应式:

window.addEventListener('resize', function() {
  myChart.resize();
});

// 容器样式
#chart-container {
  width: 100%;
  height: 600px;
  min-width: 300px;
}

动态数据更新

实现数据动态加载和更新:

function updateChart(newData) {
  const option = myChart.getOption();
  option.series[0].data = newData.nodes;
  option.series[0].links = newData.links;
  myChart.setOption(option);
}

// 模拟异步数据加载
setTimeout(() => {
  updateChart({
    nodes: [...],
    links: [...]
  });
}, 2000);

性能优化技巧

处理大规模数据时的优化方案:

series: [{
  type: 'sankey',
  // 减少布局迭代次数
  layoutIterations: 0,
  // 简化节点样式
  itemStyle: {
    borderWidth: 0
  },
  // 关闭动画
  animation: false,
  // 使用level配置控制显示层级
  levels: [{
    depth: 0,
    itemStyle: {
      color: '#F76258'
    }
  }, {
    depth: 1,
    itemStyle: {
      color: '#33B5E5'
    }
  }]
}]

实际应用案例

网站用户行为分析

const userFlowData = {
  nodes: [
    {name: '首页'},
    {name: '产品页'},
    {name: '购物车'},
    {name: '支付页'},
    {name: '注册页'},
    {name: '退出'}
  ],
  links: [
    {source: '首页', target: '产品页', value: 1560},
    {source: '产品页', target: '购物车', value: 870},
    {source: '购物车', target: '支付页', value: 490},
    {source: '支付页', target: '退出', value: 480},
    {source: '首页', target: '注册页', value: 320},
    {source: '注册页', target: '退出', value: 300}
  ]
};

能源流动分析

const energyData = {
  nodes: [
    {name: '煤炭', itemStyle: {color: '#333'}},
    {name: '天然气', itemStyle: {color: '#4E79A7'}},
    {name: '发电', itemStyle: {color: '#F28E2B'}},
    {name: '工业', itemStyle: {color: '#E15759'}},
    {name: '居民', itemStyle: {color: '#76B7B2'}}
  ],
  links: [
    {source: '煤炭', target: '发电', value: 50},
    {source: '天然气', target: '发电', value: 30},
    {source: '发电', target: '工业', value: 45},
    {source: '发电', target: '居民', value: 35}
  ]
};

高级交互功能

实现自定义tooltip和点击事件:

option = {
  tooltip: {
    trigger: 'item',
    formatter: function(params) {
      if(params.dataType === 'node') {
        return `${params.name}<br/>总流入: ${params.value}`;
      } else {
        return `${params.source} → ${params.target}<br/>流量: ${params.value}`;
      }
    }
  },
  series: [{
    type: 'sankey',
    // ...
  }]
};

myChart.on('click', function(params) {
  if(params.dataType === 'node') {
    console.log('点击节点:', params.name);
  } else if(params.dataType === 'edge') {
    console.log(`点击连接: ${params.source}→${params.target}`);
  }
});

与其他图表组合

桑基图与饼图组合展示:

option = {
  grid: [
    {left: '5%', top: '5%', width: '45%', height: '90%'},
    {right: '5%', top: '5%', width: '45%', height: '90%'}
  ],
  series: [
    {
      type: 'sankey',
      gridIndex: 0,
      // ...桑基图配置
    },
    {
      type: 'pie',
      gridIndex: 1,
      radius: ['30%', '70%'],
      data: [
        {value: 335, name: '直接访问'},
        {value: 310, name: '邮件营销'}
      ]
    }
  ]
};

数据格式转换

将原始数据转换为桑基图所需格式:

function convertToSankeyData(rawData) {
  const nodes = [];
  const links = [];
  const nodeMap = new Map();
  
  // 收集所有唯一节点
  rawData.forEach(item => {
    if(!nodeMap.has(item.source)) {
      nodeMap.set(item.source, {name: item.source});
    }
    if(!nodeMap.has(item.target)) {
      nodeMap.set(item.target, {name: item.target});
    }
  });
  
  // 转换为数组
  nodes.push(...nodeMap.values());
  
  // 创建连接
  links.push(...rawData.map(item => ({
    source: item.source,
    target: item.target,
    value: item.value
  })));
  
  return {nodes, links};
}

// 原始数据示例
const rawData = [
  {source: 'A', target: 'B', value: 10},
  {source: 'A', target: 'C', value: 20}
];

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌