美文网首页
d3.js 股权穿透

d3.js 股权穿透

作者: 二两毛豆 | 来源:发表于2022-06-17 09:59 被阅读0次

    背景

    穿透图谱也出来了,

    技术点

    使用d3主要需要掌握svg,jquery和d3这几个知识点。
    本次使用的是 v3版本的。

    案例

    company.png

    实现效果

    ct.png

    实现代码

    HTML

    <template>
     <div class="tree03">
        <div class="seeTree-page" id="app">
          <div id="box"></div>
        </div>
      </div>
    </template>
    

    JS

    <script>
    import d3 from "./js/d3";
    import $ from "./js/jquery-3.1.1.min";
    import ct01 from "./D3JSON/ct01.json";
    import checkApi from "@/api/check.js";
    
    export default {
      data() {
        return {};
      },
      created() {
        localStorage.setItem("ct", this.$route.query.entid);
      },
      mounted() {
        this.tree03();
      },
      computed: {},
      watch: {},
      methods: {
        tree03() {
          var rootName = ""; //根节点的名字
          var rootRectWidth = 0; //根节点rect的宽度
          var downwardLength = 0;
          var upwardLength = 0;
          var forUpward = true;
          var treeChart = function (d3Object) {
            this.d3 = d3Object;
            this.directions = ["upward", "downward"];
          };
    
          treeChart.prototype.drawChart = async function () {
            // First get tree data for both directions.
            this.treeData = {};
            var self = this;
            // d3.json('data.json', function(error, allData) {
            try {
              let params = {
                id: window.localStorage.ct,
              };
              // console.log(params, "paramsparams");
              // self.$toLoading.show();
              let res = await checkApi.atlasct(params);
              console.log(JSON.stringify(res), "穿透图谱");
              // console.log(allData, "原始数据");
              let allData = res.code == 0 && res.data;
              if(!allData) return;
              self.directions.forEach(function (direction) {
                self.treeData[direction] = allData[direction];
              });
              rootName = res.data.rootName;
              rootRectWidth = rootName.length * 15;
              //获得upward第一级节点的个数
              upwardLength = allData.upward.children.length;
              //获得downward第一级节点的个数
              downwardLength = allData.downward.children.length;
              self.graphTree(self.getTreeConfig());
    
              // self.$toLoading.hide();
            } catch (error) {
              // self.$toLoading.hide();
              console.log(error, "error");
            }
            // });
          };
    
          treeChart.prototype.getTreeConfig = function () {
            var width = document.body.clientWidth,
              height = document.body.clientHeight;
            var treeConfig = {
              margin: {
                top: 0,
                right: 20,
                bottom: -20,
                left: width / 5,
              },
            };
    
            treeConfig.chartWidth =
              $(window).width() - treeConfig.margin.right - treeConfig.margin.left;
            treeConfig.chartHeight =
              $(window).height() - treeConfig.margin.top - treeConfig.margin.bottom;
            treeConfig.centralHeight = treeConfig.chartHeight / 2; //中心坐标
            treeConfig.centralWidth = treeConfig.chartWidth / 3; //中心坐标
            treeConfig.linkLength = 120;
            treeConfig.duration = 500; //动画时间
            return treeConfig;
          };
    
          treeChart.prototype.graphTree = function (config) {
            var self = this;
            var d3 = this.d3;
            var linkLength = config.linkLength;
            var duration = config.duration;
            var hasChildNodeArr = [];
            var id = 0;
            // 曲线-----------start---
            // var diagonal = d3.svg.diagonal().source(function(d) {
            //      // console.log(d)
            //      return {
            //          "x": d.source.x,
            //          "y": d.source.name == 'origin' ? (forUpward ? d.source.y  :d.source.y+20 ) : (forUpward ? d.source.y-50: d.source.y+40)
            //      };
            //  })
            //  .target(function(d) {
            //      return {
            //          "x": d.target.x,
            //          // "y": d.target.y,
            //          "y": d.target.name == 'origin' ? (forUpward ? d.target.y  :d.target.y ) : (forUpward ? d.target.y: d.target.y-15)
            //      };
            //  })
            //  .projection(function(d) {
            //      return [d.x, d.y];
            //  });
            // 曲线----------end----
    
            var diagonal = function (obj) {
              //折线
              var s = obj.source;
              var t = obj.target;
              return (
                "M" +
                s.x +
                "," +
                s.y +
                "L" +
                s.x +
                "," +
                (s.y + (t.y - s.y) / 2) +
                "L" +
                t.x +
                "," +
                (s.y + (t.y - s.y) / 2) +
                "L" +
                t.x +
                "," +
                t.y
              );
            };
            // 缩放y
            var zoom = d3.behavior.zoom().scaleExtent([0.5, 2]).on("zoom", redraw);
            var svg = d3
              .select("#box")
              .append("svg")
              .attr("xmlns", "http://www.w3.org/2000/svg")
              .attr(
                "width",
                config.chartWidth + config.margin.right + config.margin.left
              )
              .attr(
                "height",
                config.chartHeight + config.margin.top + config.margin.bottom
              )
              .on("mousedown", disableRightClick)
              .call(zoom)
              .on("dblclick.zoom", null);
            var treeG = svg
              .append("g")
              .attr("class", "gbox")
              .attr(
                "transform",
                "translate(" +
                  config.margin.left +
                  "," +
                  config.margin.top +
                  ")scale(1)"
              );
    
            //箭头(下半部分)
            var markerDown = svg
              .append("marker")
              .attr("id", "resolvedDown")
              .attr("markerUnits", "strokeWidth") //设置为strokeWidth箭头会随着线的粗细发生变化
              .attr("markerUnits", "userSpaceOnUse")
              .attr("viewBox", "0 0 12 12") //坐标系的区域
              .attr("refX", 45) //箭头坐标
              .attr("refY", 6)
              .attr("markerWidth", 12) //标识的大小
              .attr("markerHeight", 12)
              .attr("orient", "90") //绘制方向,可设定为:auto(自动确认方向)和 角度值
              .attr("stroke-width", 2) //箭头宽度
              .append("path")
              .attr("d", "M2,2 L12,6 L2,10 L4,6 L2,2") //箭头的路径
              .attr("fill", "#128BED"); //箭头颜色
            //箭头(上半部分)
            var markerUp = svg
              .append("marker")
              .attr("id", "resolvedUp")
              .attr("markerUnits", "strokeWidth") //设置为strokeWidth箭头会随着线的粗细发生变化
              .attr("markerUnits", "userSpaceOnUse")
              .attr("viewBox", "0 0 12 12") //坐标系的区域
              .attr("refX", -45) //箭头坐标
              .attr("refY", 6)
              .attr("markerWidth", 12) //标识的大小
              .attr("markerHeight", 12)
              .attr("orient", "90") //绘制方向,可设定为:auto(自动确认方向)和 角度值
              .attr("stroke-width", 2) //箭头宽度
              .append("path")
              .attr("d", "M2,2 L12,6 L2,10 L4,6 L2,2") //箭头的路径
              .attr("fill", "#128BED"); //箭头颜色
    
            // Initialize the tree nodes and update chart.
            for (var d in this.directions) {
              var direction = this.directions[d];
              var data = self.treeData[direction];
              data.x0 = config.centralWidth;
              data.y0 = config.centralHeight;
              data.children.forEach(collapse);
              update(data, data, treeG);
            }
            function update(source, originalData, g) {
              // console.log(source, originalData, g, "update msg");
              var direction = originalData["direction"];
              forUpward = direction == "upward";
              var node_class = direction + "Node";
              var link_class = direction + "Link";
              var downwardSign = forUpward ? -1 : 1;
              var nodeColor = forUpward ? "#D6D6D6" : "#D6D6D6";
    
              var isExpand = false;
              var statusUp = true;
              var statusDown = true;
              var nodeSpace = 180;
              var tree = d3.layout.tree().sort(sortByDate).nodeSize([nodeSpace, 0]);
              var nodes = tree.nodes(originalData);
              var links = tree.links(nodes);
              var offsetX = -config.centralWidth;
              nodes.forEach(function (d) {
                d.y = downwardSign * (d.depth * linkLength) + config.centralHeight;
                d.x = d.x - offsetX;
                if (d.name == "origin") {
                  d.x = config.centralWidth;
                  d.y += downwardSign * 0; // 上下两树图根节点之间的距离
                }
              });
    
              // Update the node.
              var node = g.selectAll("g." + node_class).data(nodes, function (d) {
                return d.id || (d.id = ++id);
              });
              var nodeEnter = node
                .enter()
                .append("g")
                .attr("class", node_class)
                .attr("transform", function (d) {
                  return "translate(" + source.x0 + "," + source.y0 + ")";
                })
                .style("cursor", function (d) {
                  return d.name == "origin"
                    ? ""
                    : d.children || d._children
                    ? "pointer"
                    : "";
                })
                .on("click", click);
              //画矩形节点
              nodeEnter
                .append("svg:rect")
                .attr("x", function (d) {
                  return d.name == "origin" ? -(rootRectWidth / 2) : -70;
                })
                .attr("y", function (d) {
                  return d.name == "origin" ? -20 : forUpward ? -26 : -30;
                })
                .attr("width", function (d) {
                  return d.name == "origin" ? rootRectWidth : 140;
                })
                .attr("height", function (d) {
                  return d.name == "origin" ? 40 : 60;
                })
    
                .attr("rx", 2)
                .style("stroke", function (d) {
                  return d.name == "origin"
                    ? "rgb(18, 139, 237)"
                    : "rgb(18, 139, 237)";
                })
                .style("fill", function (d) {
                  return d.name == "origin" ? "#0080E3" : "#FFF"; //节点背景色
                });
              //画圆
              nodeEnter.append("circle").attr("r", 1e-6);
              nodeEnter
                .append("text")
                .attr("class", "linkname")
                .attr("x", function (d) {
                  return d.name == "origin" ? "0" : "-55";
                })
                .attr("dy", function (d) {
                  return d.name == "origin" ? ".35em" : forUpward ? "-10" : "-10";
                })
                .attr("text-anchor", function (d) {
                  return d.name == "origin" ? "middle" : "start";
                })
                .attr("fill", "#000")
                .text(function (d) {
                  if (d.name == "origin") {
                    // return ((forUpward) ? '根节点TOP' : '根节点Bottom');
                    return rootName;
                  }
                  if (d.repeated) {
                    return "[Recurring] " + d.name;
                  }
                  return d.name.length > 10 ? d.name.substr(0, 10) : d.name;
                })
                .style({
                  "fill-opacity": 1e-6,
                  fill: function (d) {
                    if (d.name == "origin") {
                      return "#fff";
                    }
                  },
                  "font-size": function (d) {
                    return d.name == "origin" ? 14 : 11;
                  },
                  cursor: "pointer",
                })
                .on("click", function () {});
              //添加文字
              nodeEnter
                .append("text")
                .attr("class", "linkname")
                .attr("x", "-55")
                .attr("dy", function (d) {
                  return d.name == "origin" ? ".35em" : forUpward ? "8" : "8";
                })
                .attr("text-anchor", function () {
                  return d.name == "origin" ? "middle" : "start";
                })
                .text(function (d) {
                  return d.name.substr(10, d.name.length);
                })
                .style({
                  fill: "#337ab7",
                  "font-size": function (d) {
                    return d.name == "origin" ? 14 : 11;
                  },
                  cursor: "pointer",
                })
                .on("click", function () {});
    
              nodeEnter
                .append("text")
                .attr("x", "-55")
                .attr("dy", function (d) {
                  return d.name == "origin" ? ".35em" : forUpward ? "25" : "23";
                })
                .attr("text-anchor", "start")
                .attr("class", "linkname")
                .style("fill", "red")
                .style("font-size", 10)
                .text(function (d) {
    
                  var str = d.name == "origin" ? "" : d.sign || "";
                  return str.length > 13 ? str.substr(0, 13) + ".." : str;
                });
              nodeEnter
                .append("text")
                .attr("x", "10")
                .attr("dy", function (d) {
                  return d.name == "origin" ? ".35em" : forUpward ? "48" : "-33";
                })
                .attr("text-anchor", "start")
                .attr("class", "linkname")
                .style("fill", "green")
                .style("font-size", 10)
                .text(function (d) {
                  return d.name == "origin" ? "" : d.ratio || "";
                });
    
              // Transition nodes to their new position.原有节点更新到新位置
              var nodeUpdate = node
                .transition()
                .duration(duration)
                .attr("transform", function (d) {
                  return "translate(" + d.x + "," + d.y + ")";
                });
              nodeUpdate
                .select("circle")
                .attr("r", function (d) {
                  return d.name == "origin"
                    ? 0
                    : hasChildNodeArr.indexOf(d) == -1
                    ? 0
                    : 6;
                })
                .attr("cy", function (d) {
                  return d.name == "origin" ? -20 : forUpward ? -38 : 39;
                })
                .style("fill", function (d) {
                  return hasChildNodeArr.indexOf(d) != -1 ? "#fff" : "";
                  // if (d._children || d.children) { return "#fff"; } else { return "rgba(0,0,0,0)"; }
                })
                .style("stroke", function (d) {
                  return hasChildNodeArr.indexOf(d) != -1
                    ? "rgb(18, 139, 237)"
                    : "";
                  // if (d._children || d.children) { return "#D6D6D6"; } else { return "rgba(0,0,0,0)"; }
                })
                .style("fill-opacity", function (d) {
                  if (d.children) {
                    return 0.35;
                  }
                })
                // Setting summary node style as class as mass style setting is
                // not compatible to circles.
                .style("stroke-width", function (d) {
                  if (d.repeated) {
                    return 5;
                  }
                });
              //代表是否展开的+-号
              nodeEnter
                .append("svg:text")
                .attr("class", "isExpand")
                .attr("x", "0")
                .attr("dy", function (d) {
                  return forUpward ? -35 : 42;
                })
                .attr("text-anchor", "middle")
                .style("fill", "rgb(18, 139, 237)")
                .text(function (d) {
                  if (d.name == "origin") {
                    return "";
                  }
                  return hasChildNodeArr.indexOf(d) != -1 ? "+" : "";
                  // if (d._children || d.children) {
                  //   return "+";
                  // }
                });
    
              nodeUpdate.select("text").style("fill-opacity", 1);
    
              //******************************************最终受益人 start******************************************//
              //提示框
              var tsk = nodeEnter
                .append("svg:rect")
                .attr("x", -60)
                .attr("y", function (d) {
                  return forUpward ? -86 : -68;
                })
                .attr("width", function (d) {
                  if (d.name == "origin") {
                    return 0;
                  } else {
                    return d.hasHumanholding ? 120 : 0; //如果有最终受益人
                  }
                })
                .attr("height", 20)
                .style("stroke", function (d) {
                  return "#1078AF";
                })
                .style("fill", function (d) {
                  return "#46A2D2";
                });
              //三角形
              nodeEnter
                .append("svg:path")
                .attr("fill", "#1078AF")
                .attr("d", function (d) {
                  if (d.name == "origin") {
                    return "";
                  } else {
                    return d.hasHumanholding
                      ? forUpward
                        ? "M-60 -66 L-40 -66 L-50 -52 Z"
                        : "M-60 -48 L-40 -48 L-50 -38 Z"
                      : ""; //如果有最终受益人
                  }
                });
    
              nodeEnter
                .append("svg:text")
                .attr("x", "-58")
                .attr("dy", function (d) {
                  return forUpward ? "-73" : "-55";
                })
                .attr("text-anchor", "start")
                .style("fill", "#fff")
                .style("font-size", 10)
                .text(function (d) {
                  var str =
                    "我是最终受益人".length > 6
                      ? "我是最终受益人".substr(0, 6) + ".."
                      : "我是最终受益人";
                  return d.hasHumanholding ? "最终受益人:" + str : ""; //如果有最终受益人
                });
              //******************************************最终受益人 end******************************************//
    
              var nodeExit = node
                .exit()
                .transition()
                .duration(duration)
                .attr("transform", function (d) {
                  return "translate(" + source.x + "," + source.y + ")";
                })
                .remove();
              nodeExit.select("circle").attr("r", 1e-6);
              nodeExit.select("text").style("fill-opacity", 1e-6);
    
              var link = g
                .selectAll("path." + link_class)
                .data(links, function (d) {
                  return d.target.id;
                });
    
              link
                .enter()
                .insert("path", "g")
                .attr("class", link_class)
                .attr("stroke", function (d) {
                  return "#D6D6D6";
                })
                .attr("fill", "none")
                .attr("stroke-width", 0.5)
                .attr("d", function (d) {
                  var o = {
                    x: source.x0,
                    y: source.y0,
                  };
                  return diagonal({
                    source: o,
                    target: o,
                  });
                })
                .attr("marker-end", function (d) {
                  return forUpward ? "url(#resolvedUp)" : "url(#resolvedDown)";
                }) //根据箭头标记的id号标记箭头;
                .attr("id", function (d, i) {
                  return "mypath" + i;
                });
              link.transition().duration(duration).attr("d", diagonal);
              link
                .exit()
                .transition()
                .duration(duration)
                .attr("d", function (d) {
                  var o = {
                    x: source.x,
                    y: source.y,
                  };
                  return diagonal({
                    source: o,
                    target: o,
                  });
                })
                .remove();
              nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
              });
    
              async function click(d) {
                let params = {
                  id: d.entid,
                  parameter: d.parameter,
                };
                // console.log(params, "穿透参数");
                let res = await checkApi.atlasct(params);
                let dataChildren = res.code == 0 && res.data;
                if (!dataChildren) return;
                console.log(dataChildren, "穿透反参");
                if (forUpward) {
                  if (d._children) {
                    // console.log(dataChildren,"股东--ok");
                    // if(!dataChildren.length) d.hasChildren = false;
                    d.children = dataChildren;
                    collapse(d);
                  } else {
                    // if(!dataChildren.length) d.hasChildren = false;
                    d.children = dataChildren;
                    // console.log("股东--no");
                    
                  }
                } else {
                  if (d._children) {
                    // console.log(dataChildren.length,"对外投资-ok");
                    // if(!dataChildren.length) d.hasChildren = false;
                    d.children = dataChildren;
                    collapse(d);
                  } else {
                    // if(!dataChildren.length) d.hasChildren = false;
                    d.children = dataChildren;
                    // console.log(dataChildren.length,"对外投资--no");
                  }
                }
                isExpand = !isExpand;
                if (isExpand) {
                  d3.select(this).select(".isExpand").text("-");
                } else {
                  d3.select(this).select(".isExpand").text("+");
                }
    
                if (d.name == "origin") {
                  return;
                }
                if (d.children) {
                  d._children = d.children;
                  d.children = null;
                } else {
                  d.children = d._children;
                  d._children = null;
                  // expand all if it's the first node
                  if (d.name == "origin") {
                    d.children.forEach(expand);
                  }
                }
                update(d, originalData, g);
              }
            }
    
            function expand(d) {
              if (d._children) {
                d.children = d._children;
                d.children.forEach(expand);
                d._children = null;
              }
            }
    
            function collapse(d) {
              // let isChildren = d.children && d.children.length;
              // console.log(isChildren,"isChildren")
              // console.log(d,"是否有子集")
              // if(isChildren){
              //   d.hasChildren = true
              // }else{
              //   d.hasChildren = false
              // }
              // console.log(d.hasChildren,"是否有子集")
              // console.log(hasChildNodeArr,"vhasChildNodeArr")
              // if (d.children && d.children.length != 0) {
              // 是否有子集
              if (d.hasChildren) {
                d._children = d.children;
                d._children.forEach(collapse);
                d.children = null;
                hasChildNodeArr.push(d);
              }
              // }
            }
    
            function redraw() {
              treeG.attr(
                "transform",
                "translate(" +
                  (d3.event.translate[0] + config.margin.left) +
                  "," +
                  (d3.event.translate[1] + config.margin.top) +
                  ")" +
                  " scale(" +
                  d3.event.scale * 1 +
                  ")"
              );
            }
    
            function disableRightClick() {
              // stop zoom
              if (d3.event.button == 2) {
                console.log("No right click allowed");
                d3.event.stopImmediatePropagation();
              }
            }
    
            function sortByDate(a, b) {
              var aNum = a.name.substr(a.name.lastIndexOf("(") + 1, 4);
              var bNum = b.name.substr(b.name.lastIndexOf("(") + 1, 4);
              return (
                d3.ascending(aNum, bNum) ||
                d3.ascending(a.name, b.name) ||
                d3.ascending(a.id, b.id)
              );
            }
          };
    
          var d3GenerationChart = new treeChart(d3);
          d3GenerationChart.drawChart();
        },
      },
    };
    </script>
    

    style

    .tree03 {
      background: #fff;
      touch-action: none;
      padding: 0;
      margin: 0;
      height: 100%;
      max-width: 100%;
      overflow: hidden;
      font-family: "PingFangSC-Regular", "PingFangSC-Light", "PingFang SC",
        sans-serif, "Microsoft YaHei";
    }
    

    总结

    写这个文档主要是为了记录一下,方便日后查看。
    持续更新。。。

    参考网站

    svg
    d3中文网站

    相关文章

      网友评论

          本文标题:d3.js 股权穿透

          本文链接:https://www.haomeiwen.com/subject/bcdjmrtx.html