什么是桑吉图
桑基图是一种展示数据流动的利器,先来了解一下它的概念
再看一下在echars官网的实例
对应的代码:
option = {
series: {
type: 'sankey',
layout: 'none',
focusNodeAdjacency: 'allEdges',
data: [{
name: 'a'
}, {
name: 'b'
}, {
name: 'a1'
}, {
name: 'a2'
}, {
name: 'b1'
}, {
name: 'c'
}],
links: [{
source: 'a',
target: 'a1',
value: 5
}, {
source: 'a',
target: 'a2',
value: 3
}, {
source: 'b',
target: 'b1',
value: 8
}, {
source: 'a',
target: 'b1',
value: 3
}, {
source: 'b1',
target: 'a1',
value: 1
}, {
source: 'b1',
target: 'c',
value: 2
}]
}
};
从代码可以看出,echars的桑基图主要有两个基本概念:
- 节点 (nodes):表示所有有关系的节点
- 连接 (links): 源节点至目标节点之间的关系,每个连接包括三个元素:
- source: 源节点
- target: 目标节点
- value: 数据
桑吉图的作用:
可用于数据从一系列节点到另一系列节点流入流出的可视化。例如: 图书章节,资金流向,渠道分析,用户行为分析。
开始实践
需求场景
做一个app用户行为分析,点击某个节点或支流流向,能后点亮整个链路。
实践效果
代码实现
初步配置达到预览效果,可以将以下代码复制到echarts的编辑器中查看效果
option = {
series: {
type: 'sankey',
layout: 'none',
focusNodeAdjacency: 'allEdges',
data: [{
name: '进入',
}, {
name: '首页'
}, {
name: '个人中心'
}, {
name: '购物车'
},
{
name: '商品'
},
{
name: '订单'
}],
links: [{
source: '进入',
target: '首页',
value: 5,
},
{
source: '进入',
target: '个人中心',
value: 3,
},
{
source: '个人中心',
target: '购物车',
value: 8,
},
{
source: '购物车',
target: '商品',
value: 8,
},
{
source: '个人中心',
target: '订单',
value: 9,
},]
}
};
实现能点击某个节点或支流流向,能后点亮整个链路。
myChart.showLoading()
let option
let links=[]
$.get(ROOT_PATH + '/data/asset/data/energy.json', function (data) {
myChart.hideLoading()
let node = [
{
name: '进入',
},
{
name: '首页',
},
{
name: '个人中心',
},
{
name: '购物车',
},
{
name: '商品',
},
{
name: '订单',
},
]
links = [
{
source: '进入',
target: '首页',
value: 5,
},
{
source: '进入',
target: '个人中心',
value: 3,
},
{
source: '个人中心',
target: '购物车',
value: 8,
},
{
source: '购物车',
target: '商品',
value: 8,
},
{
source: '个人中心',
target: '订单',
value: 9,
},
]
option = {
title: {
text: 'Sankey Diagram',
},
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
},
series: [
{
type: 'sankey',
data: node,
links: links,
// focusNodeAdjacency: 'allEdges',
itemStyle: {
borderWidth: 1,
borderColor: '#aaa',
},
lineStyle: {
color: 'source',
curveness: 0.5,
},
},
],
}
getLastLevelNodes()
findFirstNode()
myChart.setOption(option)
})
let currentItemObj
let rootLinks = []
let leftNodesArr = []
let rightNodesArr = []
let lastLevelNodesArr = []
let headsArr = [];
function findFirstNode(){
//当前source 不在其他target中
let headNodesArr = [];
links.forEach((link,index,arr) => {
if(arr.every((i)=>{
return link.source !=i.target
})){
headNodesArr.push(link.source)
}
})
headsArr = Array.from(new Set(headNodesArr))
console.log('headsArr',headsArr)
}
function isLastLevelNode(node) {
let sourceArr = []
let targetArr = []
option.series[0].links.forEach((link) => {
if (node.name === link.source) {
sourceArr.push(link)
} else if (node.name === link.target) {
targetArr.push(link)
}
})
return targetArr.length > 0 && sourceArr.length === 0
}
// 拿到所有末级节点数组
function getLastLevelNodes() {
lastLevelNodesArr = []
option.series[0].data.forEach((node) => {
if (isLastLevelNode(node)) {
lastLevelNodesArr.push(node)
}
})
}
function findLeftLink(obj) {
console.log('findLeftLink obj', obj)
if (!Object.prototype.toString.call(obj) === '[object Object]') return
leftNodesArr.push(obj)
if(headsArr.includes(obj.source)){
return ''
}
let nextObj = option.series[0].links.find((item) => item.target == obj.source)
console.log('findLeftLink nextObj', nextObj)
return nextObj && findLeftLink(nextObj)
}
function findRightLink(obj) {
if (!obj) {
return
}
rightNodesArr.push(obj)
option.series[0].links
.filter((item) => item.source == obj.target)
.map((i) => findRightLink(i))
}
myChart.on('click', function (params) {
console.log('params:', params)
console.log('links:', option.series[0].links)
option.series[0].links.forEach((item) => {
if (item.hasOwnProperty('value')) {
delete item.lineStyle
}
})
let currentItem = params.data
leftNodesArr = []
rightNodesArr = []
rootLinks = []
if (!currentItem.hasOwnProperty('value')) {
// 点击的节点
console.log('currentItem:点击的节点', currentItem)
currentItemObj = option.series[0].links.find(
(o) => o.target == currentItem.name
)
if (currentItemObj) {
findLeftLink(currentItemObj)
// 如果不是是末级节点
if (!lastLevelNodesArr.find((v) => v.name === currentItem.name)) {
findRightLink(currentItemObj)
}
} else {
// 如果是1级节点
option.series[0].links.forEach((link) => {
if (link.source == currentItem.name) {
console.log('nn link:', link)
findRightLink(link)
}
})
}
console.log('currentItem:点击的节点', currentItemObj)
} else {
// 点击的线
console.log('currentItem: 点击的线', currentItem)
currentItemObj = currentItem
findLeftLink(currentItemObj)
findRightLink(currentItemObj)
}
console.log('左边的节点leftNodesArr', leftNodesArr)
console.log('右边的节点rightNodesArr', rightNodesArr)
// let matchedNodes = Array.from(new Set(leftNodesArr.concat(rightNodesArr)))
// console.log('matchedNodes', matchedNodes)
if (!currentItem.hasOwnProperty('value')) {
//第一个是不是根节点
if (rightNodesArr.length > 1 && !headsArr.includes(rightNodesArr[1].source)) {
rightNodesArr = rightNodesArr.slice(1)
}
}
console.log('右边的节点rightNodesArr', rightNodesArr)
option.series[0].links.forEach((item) => {
rightNodesArr.forEach((v) => {
if (item.target === v.target) {
item.lineStyle = {
color: 'red',
}
}
})
})
myChart.setOption(option)
})
运用的知识点
- 函数递归
- 数组去重
- echats操作
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!