vue项目中基于D3.js实现桑基图功能

前端实现数据可视化的方案有很多种,以前都是使用百度的echarts,使用起来很方便,直接按照特定的数据格式输入,就能实现相应的效果,虽然使用方便,但是缺点就是无法自定义一些事件操作,可自由发挥的功能很少,对于一些特殊要求的需求就无能为力了,而d3.js很好地解决了这个问题;

1、安装d3

npm install d3 --save

2、安装d3-sankey

npm install d3-sankey --save

3、引入对应的模块

import * as d3 from 'd3'
import * as d3Sankey from 'd3-sankey'

4、桑基图核心代码实现

methods: {
        initHandle() {
            let data = [
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Child",
                    Class: "Second Class",
                    value: 67
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Adult",
                    Class: "Crew",
                    value: 270
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Adult",
                    Class: "Crew",
                    value: 170
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Child",
                    Class: "Crew",
                    value: 610
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Adult",
                    Class: "Second Class",
                    value: 870
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Adult",
                    Class: "Crew",
                    value: 1670
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Child",
                    Class: "Crew",
                    value: 6370
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Adult",
                    Class: "Crew",
                    value: 10
                },
                {
                    Survived: "Perished",
                    Sex: "Male",
                    Age: "Adult",
                    Class: "Crew",
                    value: 370
                }
            ]
            
            data['columns'] = [
                "Survived",
                "Sex",
                "Age",
                "Class",
                "value"
            ]

            const keys = data.columns.slice(0, -1)
            
            const sankey = d3Sankey.sankey()
                .nodeSort(null)
                .linkSort(null)
                .nodeWidth(4)
                .nodePadding(20)
                .extent([[0, 5], [this.width, this.height - 5]])

            function graph() {
                let index = -1;
                const nodes = [];
                const nodeByKey = new Map;
                const indexByKey = new Map;
                const links = [];

                for (const k of keys) {
                    for (const d of data) {
                        const key = JSON.stringify([k, d[k]]);
                        if (nodeByKey.has(key)) continue;
                        const node = {name: d[k]};
                        nodes.push(node);
                        nodeByKey.set(key, node);
                        indexByKey.set(key, ++index);
                    }
                }

                for (let i = 1; i < keys.length; ++i) {
                    const a = keys[i - 1];
                    const b = keys[i];
                    const prefix = keys.slice(0, i + 1);
                    const linkByKey = new Map;
                    for (const d of data) {
                        const names = prefix.map(k => d[k]);
                        const key = JSON.stringify(names);
                        const value = d.value || 1;
                        let link = linkByKey.get(key);
                        if (link) { link.value += value; continue; }
                        link = {
                            source: indexByKey.get(JSON.stringify([a, d[a]])),
                            target: indexByKey.get(JSON.stringify([b, d[b]])),
                            names,
                            value
                        };
                        links.push(link);
                        linkByKey.set(key, link);
                    }
                }

                return {nodes, links};
            }

            const color = d3.scaleOrdinal(["Perished"], ["#da4f81"]).unknown("#ccc")

            const svg = d3.select("#box")
                .append('svg')
                .attr("width", this.width)
                .attr("height", this.height);

            const {nodes, links} = sankey({
                nodes: graph().nodes.map(d => Object.assign({}, d)),
                links: graph().links.map(d => Object.assign({}, d))
            });

            svg.append("g")
                .selectAll("rect")
                .data(nodes)
                .join("rect")
                .attr("x", d => d.x0)
                .attr("y", d => d.y0)
                .attr("height", d => d.y1 - d.y0)
                .attr("width", d => d.x1 - d.x0)
                .append("title")
                .text(d => `${d.name}\n${d.value.toLocaleString()}`);

            svg.append("g")
                .attr("fill", "none")
                .selectAll("g")
                .data(links)
                .join("path")
                .attr("d", d3Sankey.sankeyLinkHorizontal())
                .attr("stroke", d => color(d.names[0]))
                .attr("stroke-width", d => d.width)
                .style("mix-blend-mode", "multiply")
                .append("title")
                .text(d => `${d.names.join(" → ")}\n${d.value.toLocaleString()}`);

            svg.append("g")
                .style("font", "10px sans-serif")
                .selectAll("text")
                .data(nodes)
                .join("text")
                .attr("x", d => d.x0 < this.width / 2 ? d.x1 + 6 : d.x0 - 6)
                .attr("y", d => (d.y1 + d.y0) / 2)
                .attr("dy", "0.35em")
                .attr("text-anchor", d => d.x0 < this.width / 2 ? "start" : "end")
                .text(d => d.name)
                .append("tspan")
                .attr("fill-opacity", 0.7)
                .text(d => ` ${d.value.toLocaleString()}`);

            return svg.node();
        }
    }

其中的data数据暂时为固定数据,实际开发中可以调取后台接口获取。

html部分为:

<template>
    <div class="app-container">
        <div id="box"></div>
    </div>
</template>

5、预览效果

 后续可封装为通用组件,只需传入数据即可实现效果~~~

猜你喜欢

转载自www.cnblogs.com/zjknb/p/12933298.html