当前位置:   article > 正文

基于D3.js的企业关联图谱——风控可视化_d3.js做企业图谱 react

d3.js做企业图谱 react

说明

  • 项目背景

    这个是一个金融建模比赛的题目,本人和队友拿了一等奖(感谢慷慨的金主爸爸)。该题目分析企业自身的主要数据,企业与企业、企业与个人、个人与个人之间的关系数据,建设企业的关联关系图谱,识别对公客户的关联关系,并找出关联关系中的风险点。

  • 代码说明
    以下代码将是我GitHub中的其中一个页面,主要目的是展示企业之间的担保关系,其他关系的代码可以以此进行推演。主要框架是D3.js

  • 开源地址(有用的话记得follow一下并点个star哦)
    https://github.com/lebron-li/Visualization-of-risk-control-based-on-Enterprise-Association-Graph.git

  • 答辩ppt
    很多效果和使用说明在PPT中
    链接:https://pan.baidu.com/s/1Sl8EpcqTrQhiUrZH90wHzw
    提取码:1984
    复制这段内容后打开百度网盘手机App,操作更方便哦

效果展示

粗细
在这里插入图片描述
其实还有很多特性没写出来,有兴趣的可以clone去跑一跑

主要代码块

  • 绑定body中的svg,并初始化simulation
//svg的元素g是用来组合对象的容器。添加到g元素上的变换会应用到其所有的子元素上。添加到g元素的属性会被其所有的子元素继承。
var svg = d3.select(".svg1");
//为指定的类型调用每个注册的回调,将指定的参数传递给回调,并将其作为 this 上下文
        svg.call(d3.zoom() //对zoom这个事件进行链式调用绑定事件
            .scaleExtent([0.1, 8])//使用scaleExtent来控制zoom的缩小和扩大范围
            .on("zoom", function () {//在svg绑定zoom事件后,监听zoom事件,然后将缩放结果返回给绑定的g,直接给g绑定zoom会出现抖动情况,体验很差
                $("g").attr("transform", d3.event.transform)
            })
        );
		//无论屏幕大小是多少,每次关联图谱的初始化中心都能在一个合适的位置
        var svgCenterWidth=document.documentElement.clientWidth/3;
        var svgCenterHeight=document.documentElement.clientHeight/2;
		//利用d3.forcesimulation()定义关系图
		//包括设置边link,排斥电荷charge,关系图中心点,表示如何展示
        var simulation = d3.forceSimulation()
            .force("link", d3.forceLink().id(function (d) {
                return d.id;
            }))
            //使用默认参数创建新的多实体力
            //API可参考https://github.com/d3/d3-force/blob/v2.1.1/README.md#forceManyBody
            .force("charge", d3.forceManyBody())
            //使用指定的x和y坐标创建新的定心力
			//API可参考https://github.com/d3/d3-force/blob/v2.1.1/README.md#forceCenter
            .force("center", d3.forceCenter(svgCenterWidth, svgCenterHeight));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

对于forceSimulation的优化可参考D3.js 力导向图的显示优化

  • 数据驱动部分
var graph;
        d3.json("../../res/json/guarantee_json/demo.json", function (error, data) {
            if (error) throw error;
            graph = data;
			//g用于绘制所有边,selectALL选中所有的line,并绑定数据data(graph.links)
			//enter().append("line")添加元素
            var link = svg.append("g").attr("class", "links")
                .selectAll("line").data(graph.links)//数据驱动文档
                .enter().append("line")
                .attr("stroke-width", function (d) {
                	//这里设置边的粗细,d.amount是数据中一次担保关系的担保金额
                	//如果担保金额越大,边就越粗
                	//为了展示效果,调用了math.sqrt进行了两次开方
                    return Math.sqrt(Math.sqrt(d.amount))/20;
                })
			//把担保金额显示在边上
            var edgesText = svg.append("g")
                .selectAll('.linetext')
                .data(graph.links)
                .enter() // 对所有的links,可能是null元素
                .append('text') 
                .attr('class',"edgesTexts")
                .text((d) => {
                    return d.amount;
                })
                .attr("font-size", function (d) {
                    return 5;
                })
                .attr('fill','#f2f2f2')
                .attr('opacity','0.5')

			//由于是有向图,所以这里设置箭头属性
			//marker元素定义了在特定的<path>元素、<line>元素、<polyline>元素或者<polygon>元素上绘制的箭头或者多边标记图形
            var marker=	svg.append("marker")
                .attr("id", "resolved")
                //userSpaceOnUse指定markerWidth和markerUnits属性以及<marker>元素的内容表示当前用户坐标系中引用标记的图形对象的值
                //即通过标记、标记开始、标记中间、,或标记结束属性
                .attr("markerUnits","userSpaceOnUse")
                .attr("viewBox", "0 -5 10 10")
                .attr("refX",26)
                .attr("refY", 0)
                .attr("markerWidth", 5)
                .attr("markerHeight", 6)
                .attr("orient", "auto")
                .attr("stroke-width",2)
                .append("path")
                .attr("d", "M0,-5L10,0L0,5")
                .attr('fill','#4e88af');
			// <circle> SVG 元素是一个SVG的基本形状,用来创建圆,基于一个圆心和一个半径。
            var node = svg.append("g").attr("class", "nodes")
            	selectAll("circle")选中所有的圆并绑定数据
                .selectAll("circle").data(graph.nodes)
                .enter().append("circle").attr("r", function (d) {
                    return d.size/4+2;
                }).attr("fill", function (d) {
                    return colors[d.group];
                })
                .attr("stroke", 'none')
                .attr("name", function (d) {
                    return d.id;
                }).call(
                    d3.drag()
                        .on("start", dragstarted)
                        .on("drag", dragged)
                        .on("end", dragended));
			//给每一个node设置texts,在texts模式下使用
            var text = svg.append("g").attr("class", "texts")
                .selectAll("text").data(graph.nodes)
                .enter().append("text")
                //text的大小和每个节点的size正相关
                //在之前的数据分析中,size和该节点的风险传播能力呈正相关
                .attr("font-size", function (d) {
                    return d.size/4+2;
                }).attr("fill", function (d) {
                    return colors[d.group];
                }).attr("name", function (d) {
                    return d.id;
                }).text(function (d) {
                    return d.id;
                }).attr("text-anchor", 'middle')
                //当拖动开始绑定dragstarted函数
                //拖动进行时绑定dragstarted函数
                //拖动结束绑定dragended函数
                .call(
                    d3.drag()
                        .on("start", dragstarted)
                        .on("drag", dragged)
                        .on("end", dragended)
                );
			
            node.append("title").text(function (d) {
                return d.id;
            });
            //ticked数据初始化,生成每个结点的位置
            //按指定的迭代次数手动逐步执行模拟,并返回模拟。
            //对于每个迭代,它将当前alpha增加(alphaTarget-alpha)×alphaDecay;然后调用每个注册的力,传递新的alpha;然后将每个节点的速度减少
            simulation
            //如果指定了节点,则将模拟的节点设置为指定的对象数组
            //必要时初始化其位置和速度,然后重新初始化任何绑定力
                .nodes(graph.nodes)
            //ticked是listener,
            //If listener is specified, sets the event listener for the specified typenames and returns this simulation. 
                .on("tick", ticked);
            //力的强度与链接节点的距离和目标距离之间的差成比例,类似于弹簧力
            simulation.force("link")
                .links(graph.links)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

如果对以上代码还不好理解的同学,可以翻阅d3-force的官方文档

  • ticked()函数部分
    ticked() 函数确定link的起始点x,y坐标。确定node中心点,文本通过translate平移变化
function ticked() {
                link
                    .attr("x1", function (d) {
                        return d.source.x;
                    })
                    .attr("y1", function (d) {
                        return d.source.y;
                    })
                    .attr("x2", function (d) {
                        return d.target.x;
                    })
                    .attr("y2", function (d) {
                        return d.target.y;
                    })
                    .attr("marker-end", "url(#resolved)");

                edgesText.attr('x', function (d) {
                    return (d.source.x + d.target.x) / 2-1;
                })
                edgesText.attr('y', function (d) { return (d.source.y + d.target.y) / 2 })

                node
                    .attr("cx", function (d) {
                        return d.x;
                    })
                    .attr("cy", function (d) {
                        return d.y;
                    });

                text.attr('transform', function (d) {
                    return 'translate(' + d.x + ',' + (d.y + d.size / 2) + ')';
                });
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

  • 鼠标按住nodes或texts的拖动部分
var dragging = false;
        //开始拖动并更新相应的node
        //alpha大致类似于模拟退火中的温度。当模拟“冷却”时,它会随着时间的推移而减少。
        function dragstarted(d) {
            if (!d3.event.active) simulation.alphaTarget(0.1).restart();
            d.fx = d.x;
            d.fy = d.y;
            dragging = true;
        }
        //拖动进行中
        function dragged(d) {
            d.fx = d3.event.x;
            d.fy = d3.event.y;
        }
        //拖动结束
        //alphaTarget的值从0-1,控制nodes拖拽后返回的速度
        function dragended(d) {
            if (!d3.event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
            dragging = false;
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

  • 节点显示效果切换,实现圆点和text的相互切换
//点击circle的时候显示的是圆圈,点击text的时候显示结点中文信息
//我在css中将nodes和text默认显示设为隐藏,当类设置为active时就为显示
       $('.mode span').click(function (event){
            $('.mode span').removeClass('active');
            $(this).addClass('active');
            if ($(this).text()=='Circles'){
                $('.texts text').hide();
                $('.nodes circle').show();
            }else{
                $('.texts text').show();
                $('.nodes circle').hide();
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  • 当鼠标悬浮到某一个node或者text上时,出现的事件
$('.svg1').on('mouseenter','.nodes circle', function (event){
            var name =$(this).attr('name');
            //每一个子图都会有一个id,标记为Gid
            //ctx_name为该节点的介绍或者说是详情
            var Gid_name = $(this)['context'].__data__.Gid;
            var ctx_name= $(this)['context'].__data__.ctx;
            //当鼠标hover到某一个节点的时候,右上角会出现节点详情,在源代码body中为info类
            //标题的填充色赋给了右上角的节点详情文字
            $('.info h5').css('color', $(this).attr('fill')).text('id: '+name);
            $('.info h4').css('color', $(this).attr('fill')).text('type: '+ctx_name);
            $('.info p').remove();
            //!dragging可以防止在拖动的过程中hover到其他nodes或texts
            if (!dragging) {
                d3.select(".svg1 .nodes").selectAll('circle').attr('class', function (d) {
                    if (d.id == name) {
                        return '';
                    }
                    //只有是和hover的那个node或text同一个子图的才会保留
                    for (var i = 0; i < graph.links.length; i++) {
                        if (graph.links[i]['source'].Gid == Gid_name && graph.links[i]['target'].Gid == d.Gid) {
                            return '';
                        }
                        if (graph.links[i]['target'].Gid == Gid_name && graph.links[i]['source'].Gid == d.Gid) {
                            return '';
                        }
                    }
                    //否则给一个‘inactive’类将其隐藏,‘inactive’在css文件中有预设
                    return 'inactive';
                });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

  • 鼠标离开
        $('.svg1').on('mouseleave', '.nodes circle', function(event) {
            if (!dragging) {
                d3.select('.svg1 .nodes').selectAll('circle').attr('class', '');
                d3.select('.svg1 .links').selectAll('line').attr('class', '');
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  • 搜索节点功能
$('.search1 input').keyup(function(event) {
            if ($(this).val() == '') {
                d3.select('.svg1 .texts').selectAll('text').attr('class', '');
                d3.select('.svg1 .nodes').selectAll('circle').attr('class', '');
                d3.select('.svg1 .links').selectAll('line').attr('class', '');
            } else {
                var name = $(this).val();
                d3.select('.svg1 .nodes').selectAll('circle').attr('class', function(d) {
                    if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
                        return '';
                    } else {
                        return 'inactive';
                    }
                });
                d3.select('.svg1 .texts').selectAll('text').attr('class', function(d) {
                    if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
                        return '';
                    } else {
                        return 'inactive';
                    }
                });
                d3.select(".svg1 .links").selectAll('line').attr('class', function(d) {
                    return 'inactive';
                });
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/563009
推荐阅读
相关标签
  

闽ICP备14008679号