美文网首页
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