echarts 桑基图点击高亮整条链路相关笔记总结

请添加图片描述

点击高亮整条链路

思路: 从后台返回整条链路的数据, 并分别设置高亮与不高亮的节点和连线的样式

代码:

props: {
  emphasisInfo: {
    type: Object,
    default: () => ({}),
  },
},

watch: {
  nodes: {
    deep: true,
    handler() {
      this.$nextTick(() => {
        this.init()
      })
    },
  },
}  
  emphasisInfo: {
    deep: true,
    handler(val) {
  	  this.setEmphasis()
    },
  },
},
  
setEmphasis() {
  const { name } = this.emphasisInfo.eventData

  // 若与上一次点击的不是同一个节点, 或者点击前并非处于高亮状态
  if (this.emphaticName !== name) {
    this.isEmphatic = false
    this.emphaticName = name
  }

  // 若已处于高亮状态, 则恢复所有数据
  if (this.isEmphatic) {
    this.option.series = {
      ...this.option.series,
      data: this.nodes,
      links: this.links,
    }
    this.isEmphatic = false
    this.emphaticName = ''
    return
  }

  this.updateData()

  this.isEmphatic = true
},

updateData() {
  const nodeInfo = this.emphasisInfo.nodeInfo
  const emphaticNodeIds = nodeInfo.nodes.map((el) => el.nodeId)
  let nodes = _.cloneDeep(this.nodes)

  nodes = nodes.map((el) => {
    const empNodeStyle = {
      itemStyle: {
        color:
          el.valueFlag == 0
            ? this.getColorOpacity(el.color, '0.3')
            : el.color,
        borderColor:
          el.valueFlag == 0
            ? this.getColorOpacity(el.color, '0.3')
            : el.color,
      },
      label: {
        show: el.valueFlag !== 0,
      },
    }
    const notEmpNodeStyle = {
      itemStyle: {
        color: this.getColorOpacity(el.color, '0.3'),
        borderColor: this.getColorOpacity(el.color, '0.3'),
        borderWidth: el.valueFlag == 0 ? 10 : 0,
      },
      label: {
        show: false,
      },
    }

    return emphaticNodeIds.includes(el.nodeId)
      ? _.merge(el, empNodeStyle)
      : _.merge(el, notEmpNodeStyle)
  })

  const emphaticLinkIds = nodeInfo.links.map((el) => el.linkId)
  let links = _.cloneDeep(this.links)

  links = links.map((el) => {
    const notEmpLinkStyle = {
      lineStyle: {
        color: el.labelValue == 0 ? 'transparent' : '#999',
        opacity: 0.1,
      },
    }
    const emphaticLinkStyle = {
      lineStyle: {
        color: el.labelValue == 0 ? 'transparent' : 'source',
        opacity: 0.8,
      },
    }

    return emphaticLinkIds.includes(el.linkId)
      ? _.merge(el, emphaticLinkStyle)
      : _.merge(el, notEmpLinkStyle)
  })

  this.option.series = {
    ...this.option.series,
    data: nodes,
    links: links,
  }
},

getColorOpacity(color, opacity) {
  return color.replace(/(?<=,)[^,\)]*(?=\))/, opacity)
},

悬停高亮

series: {
	emphasis: {
	  focus: 'adjacency', // 实现鼠标悬停高亮显示整条链路
	  // disabled: true,
	},
}

点击节点后在节点下方显示搜索控件

思路: 通过节点点击事件回调方法里传入的事件参数获取节点位置信息, 来定位控件显示的位置

// 控件容器, 根据计算属性 changePos 来确定位置
<div :style="changePos">
...
</div>	

computed: {
  changePos() {
    return {
      position: 'absolute',
      top: this.selectDialog.y + 'px',
      left: this.selectDialog.x + 'px',
      color: '#fff',
      zIndex: '11',
    }
  },
},

// 节点点击回调方法
handleNodeClick(){
 // 显示表搜索组件
 if (this.showNodeSelect) {
   // 获取位置信息, 注意点击标题时, 通过 e.event.topTarget.parent.__hostTarget.shape 取得位置
   const positionInfo =
     e.event.target.shape || e.event.topTarget.parent.__hostTarget.shape
   this.setDialongXY(positionInfo)
 }	
}

setDialongXY(position) {
  this.selectDialog.x = position?.x + 44
  this.selectDialog.y = position?.y + posit
}

根据传入数据的顺序渲染

series: {
  type: 'sankey',
  layoutIterations: 0, // 为 0 时节点按照数据的顺序排列
}

将节点放置于指定列 (归类)

思路: echarts桑基图会根据连线关系, 将节点摆放至相应的列, 也就是说, 后一列的节点, 至少要有一根与前一列节点的连线存在. 但在实际的数据中, 我们想放置到某一列的节点并没有与前一列有连线关系, 此时可以创造一根 value 为 0 的连线, 并利用连线样式 lineStyle: el.dataNum == 0 ? { color: 'transparent' } : {}, , 将该连线隐藏起来, 以此来实现将节点放置在指定列的功能.

固定节点高度

思路: 统一设置相同的节点数值 value, 并在 tooltip 里显示真实的节点数值.

节点的高度是由其数值决定的, 而节点的数值是由所有连线数据汇总累加, 因此返回的数据中, 需要有一个字段 linkCount 表示有多少根线连接到该节点, value 的值就等于 固定数值/linkCount

代码:

  const links = linksList?.map((el) => ({
    source: el.startNodeId,
    target: el.endNodeId,
    sourceName: el.startNodeAlias,
    targetName: el.endNodeAlias,
    // 这里设置了一个固定的值为 500
    value:
      el.endNodeCount == 0 
        ? 500
        : 500 / el.endNodeCount,  
    labelValue: el.dataNum,
    linkCount: el.linkCount,
    linkId: el.id,
    lineStyle: el.dataNum == 0 ? { color: 'transparent' } : {},
  }))

设置节点的最小高度 (当数据为0时仍有高度)

series: {
    itemStyle: {
      borderWidth: 10, // 用边框宽度来保证在数据为0时仍有最小高度
    },
}

在节点之间添加分割线

在这里插入图片描述

利用富文本格式, 在 label.formatter 中加入

 label: {
   formatter: (params) => {
     const { nodeName, leftSplitLine, rightSplitLine } = params.data

     if (leftSplitLine || rightSplitLine) {
       return [
         '{line|------------------------------------------}',
         leftSplitLine
           ? `{leftText|${nodeName}}`
           : `{rightText|${nodeName}}`,
       ].join('\n')
     }

     return nodeName
   },
   rich: {
     leftText: {
       fontSize: '14px',
       padding: [0, 0, 18, 0],
       lineHeight: 24, //调整分割线和文字之间的间距
     },
     rightText: {
       fontSize: '14px',
       padding: [0, 0, 15, 0],
     },
     line: {
       color: '#17a9c5',
       fontWeight: 'bold',
       padding: [8, 0, 0, -60],
     },
   },
 },

桑基图配置代码

<template>
  <div class="sankey-chart">
    <v-chart
      ref="chart"
      :autoresize="true"
      :option="option"
      :update-options="{ notMerge: true }"
    >
    </v-chart>
    <!-- <div v-else class="no-data" /> -->
  </div>
</template>

<script>
import { data, links, tooltipContent } from './sankeyData'
export default {
  name: 'sankey-chart',
  props: {
    data: {
      type: Object,
      default: () => ({}),
    },
  },
  computed: {
    isNoData() {
      return _.isEmpty(this.data.seriesData)
    },
  },
  data() {
    return {
      option: {},
    }
  },
  watch: {
    data: {
      deep: true,
      handler() {
        this.$nextTick(() => {
          this.init()
        })
      },
    },
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      // if (_.isEmpty(this.data)) return
      this.initOption()
    },
    initOption() {
      this.option = {
        series: {
          type: 'sankey',
          draggable: false,
          emphasis: {
            focus: 'adjacency', // 实现鼠标悬停高亮显示整条链路
          },
          data,
          links,
        },
        tooltip: {
          trigger: 'item',
          triggerOn: 'mousemove',
          borderRadius: 4,
          padding: 6,
          borderWidth: 0,
          backgroundColor: '#000c2890',
          textStyle: {
            color: '#fff',
          },
          formatter: (params) => {
            if (params.dataType == 'edge') return
            console.log(params)
            const name = params.data.name
            const contentArr = tooltipContent[name]
            if (!contentArr) return
            let content = ''
            for (let item of contentArr) {
              content += `<div class="tooltip-item">
                    <div class="tooltip-item-label">${item[0]}:</div>
                    <div class="tooltip-item-value">${item[1]}</div>
                  </div>`
            }
            return `<div>${content}</div>`
          },
        },
      }
    },
  },
}
</script>

<style lang="less">
.sankey-chart {
  // canvas {
  //   cursor: default;
  // }
}

.tooltip-item {
  display: flex;
  &-label {
    width: 110px;
    text-align: right;
    margin-right: 5px;
  }
}
</style>

sankeyData.js (桑基图数据结构 )

const nodes = [
  { name: '协同XXX' },
  { name: 'XXX资源中心' },
  { name: '综合系统XX' },
  { name: '数字XXXX' },
  .....
]

const links = [
  { source: 'XXX系统', target: '人力资源', value: 35 },
  { source: 'XXX系统', target: '分析指标', value: 7 },
  { source: 'XXX系统', target: '市场营销', value: 39 },
  { source: 'XXX系统', target: '调度运行', value: 19 },
  { source: 'XXXX资源中心', target: '调度运行', value: 25 },
  { source: 'XXX资源中心', target: 'XXXX生产', value: 56 },
  { source: 'XXX系统', target: '综合管理', value: 5 },
  { source: '数字XXXX', target: '基建管理', value: 19 },
  { source: '数字XXXX', target: '调度运行', value: 15 },
 ....
}

export { nodes, links}

参考:
基于echarts桑基图改造的数据流向

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值