d3.js 股权穿透

背景

穿透图谱也出来了,

技术点

使用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中文网站

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容