美文网首页
股权穿透d3

股权穿透d3

作者: ShindouHiro | 来源:发表于2020-06-11 19:47 被阅读0次
<template>
    <div class="penetrate-chart">
        <div class="bt-group">
            <button class="save" @click="saveImg">保存</button>
            <button class="reset" @click="resetSvg">重置</button>
        </div>
    </div>
</template>
<!--股权穿透图-->
<script>
import * as d3 from 'd3'
// 过渡时间
const DURATION = 0
// 加减符号半径
const SYMBOLA_S_R = 9
// 公司
const COMPANY = 0
// 人
const PERSON = 1
export default {
    props: {},

    components: {},

    data() {
        return {
            layoutTree: '',
            diamonds: '',
            d3: d3,
            i: 0,
            hasChildNodeArr: [],
            originDiamonds: '',
            diagonalUp: '',
            diagonalDown: '',
            tree: { "name": "多多包", "children": [{ "name": "一卡通公司", "type": 0 }, { "name": "一卡通公司2", "type": 0, "children": [{ "name": "小公司", "type": 0, "children": [{ "name": "小小小", "type": 0, "children": [{ "type": 1, "name": "笑小下" }] }] }, { "type": 0, "name": "小公司2" }] }, { "name": "一卡通公司2333", "type": 0, "children": [{ "type": 0, "name": "小公司" }, { "type": 0, "name": "小公司2" }] }, { "type": 0, "name": "一卡通公司2222" }], "parents": [{ "name": "大公司", "type": 0, "children": [{ "name": "发发委", "type": 0, "money": "780万元", "children": [{ "type": 0, "money": "780万元", "name": "123" }] }, { "name": "123发发委", "money": "780万元", "type": 0, "children": [{ "money": "780万元", "type": 0, "name": "123" }] }] }, { "name": "多多网", "money": "780万元", "type": 0, "children": [{ "type": 0, "money": "780万元", "name": "发哈哈" }] }, { "name": "龙龙投资", "money": "780万元", "type": 0, "children": [{ "type": 1, "money": "780万元", "name": "王林" }, { "type": 1, "money": "780万元", "name": "张峰" }, { "type": 1, "money": "780万元", "name": "侯明" }] }] },
            rootUp: '',
            rootDown: '',
            svg: ''
        }
    },

    mounted() {
        this.init()
    },

    methods: {
        init() {
            let d3 = this.d3
            let svgW = document.body.clientWidth
            let svgH = 500
            // 方块形状
            this.diamonds = {
                w: 145,
                h: 68,
                intervalW: 200,
                intervalH: 150
            }
            // 源头对象
            this.originDiamonds = {
                w: 190
            }
            this.layoutTree = d3.tree().nodeSize([this.diamonds.intervalW, this.diamonds.intervalH]).separation(() => 1);
            // 主图
            this.svg = d3.select('#app').append('svg').attr('width', svgW).attr('height', svgH).attr('id', 'treesvg')
                .call(d3.zoom().scaleExtent([0, 5]).on('zoom', () => {
                    // 设置缩放位置以及平移初始位置
                    this.svg.attr('transform', d3.event.transform.translate(svgW / 2, svgH / 2));
                }))
                .attr('style', 'position: relative;z-index: 2;')
                .append('g').attr('id', 'g').attr('transform', 'translate(' + (svgW / 2) + ',' + (svgH / 2) + ')');
            let upTree = null
            let downTree = null
            // 拷贝树的数据
            Object.keys(this.tree).map(item => {
                if (item === 'parents') {
                    upTree = JSON.parse(JSON.stringify(this.tree))
                    upTree.children = this.tree[item]
                    upTree.parents = null
                } else if (item === 'children') {
                    downTree = JSON.parse(JSON.stringify(this.tree))
                    downTree.children = this.tree[item]
                    downTree.parents = null
                }
            })
            // hierarchy 返回新的结构 x0,y0初始化起点坐标
            this.rootUp = d3.hierarchy(upTree, d => d.children);
            this.rootUp.x0 = 0
            this.rootUp.y0 = 0

            this.rootDown = d3.hierarchy(downTree, d => d.children);
            this.rootDown.x0 = 0
            this.rootDown.y0 = 0;
            // 上 和 下 结构
            let treeArr = [
                {
                    data: this.rootUp,
                    type: 'up'
                },
                {
                    data: this.rootDown,
                    type: 'down'
                }
            ]
            treeArr.map(item => {
                item.data.children.forEach(this.collapse);
                this.update(item.data, item.type, item.data)
            })
        },

        /*
         *[update 函数描述], [click 函数描述]
         *  @param  {[Object]} source 第一次是初始源对象,后面是点击的对象
         *  @param  {[String]} showtype up表示向上 down表示向下
         *  @param  {[Object]} sourceTree 初始源对象
         */
        update(source, showtype, sourceTree) {
            let _this = this
            if (source.parents === null) {
                source.isOpen = !source.isOpen
            }
            let nodes
            if (showtype === 'up') {
                nodes = this.layoutTree(this.rootUp).descendants()
            } else {
                nodes = this.layoutTree(this.rootDown).descendants()
            }
            let links = nodes.slice(1);
            nodes.forEach(d => {
                d.y = d.depth * this.diamonds.intervalH;
            });

            let node = this.svg.selectAll('g.node' + showtype)
                .data(nodes, d => d.id || (d.id = showtype + ++this.i));

            let nodeEnter = node.enter().append('g')
                .attr('class', d => showtype === 'up' && !d.depth ? 'hide-node' : 'node' + showtype)
                .attr('transform', d => showtype === 'up' ? 'translate(' + d.x + ',' + -(d.y) + ')' : 'translate(' + d.x + ',' + d.y + ')')

            // 创建矩形
            nodeEnter.append('rect')
                .attr('type', d => d.id)
                .attr('width', d => d.depth ? this.diamonds.w : this.originDiamonds.w)
                .attr('height', d => d.depth ? (d.type === COMPANY ? this.diamonds.h : this.diamonds.h - 10) : 30)
                .attr('x', d => d.depth ? -this.diamonds.w / 2 : -this.originDiamonds.w / 2)
                .attr('y', d => d.depth ? showtype === 'up' ? -this.diamonds.h / 2 : 0 : -15)
                .attr('stroke', d => d.data.type === COMPANY || !d.depth ? '#FD7D00' : '#7A9EFF')
                .attr('stroke-width', 1)
                .attr('rx', 5)
                .attr('ry', 5)
                .style('fill', d => {
                    if (d.data.type === COMPANY || !d.depth) {
                        return d._children ? '#FFF1D7' : (d.depth ? '#fff' : '#FD7D00')
                    } else if (d.data.type === PERSON) {
                        return d._children ? '#fff' : (d.depth ? '#fff' : '#7A9EFF')
                    }
                });

            // 创建圆 加减
            nodeEnter.append('circle')
                .attr('type', d => d.id || (d.id = showtype + 'text' + ++this.i))
                .attr('r', (d) => d.depth ? (this.hasChildNodeArr.indexOf(d) === -1 ? 0 : SYMBOLA_S_R) : 0)
                .attr('cy', d => d.depth ? showtype === 'up' ? -(SYMBOLA_S_R + this.diamonds.h / 2) : this.diamonds.h : 0)
                .attr('cx', 0)
                .attr('fill', d => d.children ? '#fff' : '#FD7D00')
                .attr('stroke', d => d._children || d.children ? '#FD7D00' : '')
                .on('click', function (d) {
                    _this.click(d, showtype, sourceTree)
                    setTimeout(() => {
                        if (document.querySelector(`text[type="${d.id}"]`).innerHTML === '-') {
                            d.isOpen = false
                            this.innerHTML = '+'
                            this.setAttribute('fill', '#FD7D00')
                            document.querySelector(`text[type="${d.id}"]`).setAttribute('fill', '#fff')
                            document.querySelector(`rect[type="${d.id}"]`).setAttribute('style', 'fill:#FFF1D7')
                            document.querySelector(`text[type="${d.id}"]`).innerHTML = '+'
                        } else {
                            d.isOpen = true
                            this.setAttribute('fill', '#fff')
                            document.querySelector(`text[type="${d.id}"]`).setAttribute('fill', '#FD7D00')
                            document.querySelector(`rect[type="${d.id}"]`).setAttribute('style', 'fill:#fff')
                            document.querySelector(`text[type="${d.id}"]`).innerHTML = '-'
                        }
                    }, DURATION)
                });

            // 持股比例
            nodeEnter.append('g')
                .attr('transform', () => 'translate(0,0)')
                .append('text')
                .attr('class', d => !d.depth ? 'proportion-hide' : 'proportion')
                .attr('x', d => d.x > 0 ? (showtype === 'up' ? -30 : 30) : 30)
                .attr('y', showtype === 'up' ? this.diamonds.h : -20)
                .attr('text-anchor', 'middle')
                .attr('fill', d => d.data.type === COMPANY ? '#FD7D00' : '#7A9EFF')
                .text(d => '30%');

            // 公司名称
            // y轴 否表源头的字体距离
            nodeEnter.append('text')
                .attr('class', 'text-style-name')
                .attr('x', 0)
                .attr('y', showtype === 'up' ? -this.diamonds.h / 2 : 0)
                .attr('dy', d => d.depth ? (d.data.name.length > 9 ? '1.5em' : '2em') : '.3em')
                .attr('text-anchor', 'middle')
                .attr('fill', d => d.depth ? '#465166' : '#fff')
                .text(d => (d.data.name.length > 9) ? d.data.name.substr(0, 9) : d.data.name);

            // 名称过长 第二段
            nodeEnter.append('text')
                .attr('class', 'text-style-name')
                .attr('x', 0)
                .attr('y', showtype === 'up' ? -this.diamonds.h / 2 : 0)
                .attr('dy', d => d.depth ? '3em' : '.3em')
                .attr('text-anchor', 'middle')
                .attr('fill', d => d.depth ? '#465166' : '#fff')
                .text(d => d.data.name.substr(9, d.data.name.length));

            // 认缴金额
            nodeEnter.append('text')
                .attr('class', 'text-style-money')
                .attr('x', 0)
                .attr('y', showtype === 'up' ? -this.diamonds.h / 2 : 0)
                .attr('dy', d => d.data.name.substr(9, d.data.name.length).length ? '5em' : '4em')
                .attr('text-anchor', 'middle')
                .attr('fill', d => d.depth ? '#465166' : '#fff')
                .text(d => d.data.money);
            /*
            * 绘制箭头
            * @param  {string} markerUnits [设置为strokeWidth箭头会随着线的粗细发生变化]
            * @param {string} viewBox 坐标系的区域
            * @param {number} markerWidth,markerHeight 标识的大小
            * @param {string} orient 绘制方向,可设定为:auto(自动确认方向)和 角度值
            * @param {number} stroke-width 箭头宽度
            * @param {string} d 箭头的路径
            * @param {string} fill 箭头颜色
            * @param {string} id resolved0表示公司 resolved1表示个人
            * 直接用一个marker达不到两种颜色都展示的效果
            */
            nodeEnter.append('marker')
                .attr('id', showtype + 'resolved0')
                .attr('markerUnits', 'strokeWidth')
                .attr('markerUnits', 'userSpaceOnUse')
                .attr('viewBox', '0 -5 10 10')
                .attr('markerWidth', 12)
                .attr('markerHeight', 12)
                .attr('orient', '90')
                .attr('refX', () => showtype === 'up' ? '-5' : '15')
                .attr('stroke-width', 2)
                .attr('fill', 'red')
                .append('path')
                .attr('d', 'M0,-5L10,0L0,5')
                .attr('fill', '#FD7D00');

            nodeEnter.append('marker')
                .attr('id', showtype + 'resolved1')
                .attr('markerUnits', 'strokeWidth')
                .attr('markerUnits', 'userSpaceOnUse')
                .attr('viewBox', '0 -5 10 10')
                .attr('markerWidth', 12)
                .attr('markerHeight', 12)
                .attr('orient', '90')
                .attr('refX', () => showtype === 'up' ? '-5' : '15')
                .attr('stroke-width', 2)
                .attr('fill', 'red')
                .append('path')
                .attr('d', 'M0,-5L10,0L0,5')
                .attr('fill', '#7A9EFF');

            // 将节点转换到它们的新位置。
            let nodeUpdate = node.transition()
                .duration(DURATION)
                .attr('transform', d => showtype === 'up' ? 'translate(' + d.x + ',' + -(d.y) + ')' : 'translate(' + d.x + ',' + (d.y) + ')');

            // 代表是否展开的+-号,function this指向当前dom
            nodeEnter.append('svg:text')
                .attr('type', d => d.id || (d.id = showtype + 'text' + ++this.i))
                .on('click', function (d) {
                    _this.click(d, showtype, sourceTree)
                    setTimeout(() => {
                        if (this.innerHTML === '-') {
                            d.isOpen = false
                            this.innerHTML = '+'
                            this.setAttribute('fill', '#fff')
                            document.querySelector(`circle[type="${d.id}"]`).setAttribute('fill', '#FD7D00')
                            document.querySelector(`rect[type="${d.id}"]`).setAttribute('style', 'fill:#FFF1D7')
                        } else {
                            d.isOpen = true
                            this.innerHTML = '-'
                            this.setAttribute('fill', '#FD7D00')
                            document.querySelector(`circle[type="${d.id}"]`).setAttribute('fill', '#fff')
                            document.querySelector(`rect[type="${d.id}"]`).setAttribute('style', 'fill:#fff')
                        }
                    }, DURATION)
                })
                .attr('x', 0)
                .attr('dy', d => d.depth ? (showtype === 'up' ? -(SYMBOLA_S_R / 2 + this.diamonds.h / 2) : this.diamonds.h + 4) : 0)
                .attr('text-anchor', 'middle')
                .attr('fill', d => d._children ? '#fff' : '#FD7D00')
                .text(d => this.hasChildNodeArr.indexOf(d) !== -1 ? (source.depth && d.isOpen ? '-' : '+') : '');

            // 将退出节点转换到父节点的新位置.
            let nodeExit = node.exit().transition()
                .duration(DURATION)
                .attr('transform', () => showtype === 'up' ? 'translate(' + source.x + ',' + -(source.y) + ')' : 'translate(' + source.x + ',' + (parseInt(source.y)) + ')')
                .remove();

            nodeExit.select('rect')
                .attr('width', this.diamonds.w)
                .attr('height', this.diamonds.h)
                .attr('stroke', 'black')
                .attr('stroke-width', 1);

            // 修改线条
            let link = this.svg.selectAll('path.link' + showtype)
                .data(links, d => d.id);

            // 在父级前的位置画线。
            let linkEnter = link.enter().insert('path', 'g')
                .attr('class', 'link' + showtype)
                .attr('marker-start', d => `url(#${showtype}resolved${d.data.type})`)// 根据箭头标记的id号标记箭头
                .attr('stroke', d => d.data.type === COMPANY ? '#FD7D00' : '#7A9EFF')
                .style('fill-opacity', 1)
                .attr('d', () => {
                    let o = { x: source.x0, y: source.y0 };
                    return _this.diagonal(o, o, showtype)
                });

            let linkUpdate = linkEnter.merge(link);
            // 过渡更新位置.
            linkUpdate.transition()
                .duration(DURATION)
                .attr('d', d => _this.diagonal(d, d.parent, showtype));

            // 将退出节点转换到父节点的新位置
            link.exit().transition()
                .duration(DURATION)
                .attr('d', () => {
                    let o = {
                        x: source.x,
                        y: source.y
                    };
                    return _this.diagonal(o, o, showtype)
                }).remove();

            // 隐藏旧位置方面过渡.
            nodes.forEach(d => { d.x0 = d.x; d.y0 = d.y });
        },

        // 拷贝到_children 隐藏1排以后的树
        collapse(source) {
            if (source.children) {
                source._children = source.children;
                source._children.forEach(this.collapse);
                source.children = null;
                this.hasChildNodeArr.push(source);
            }
        },

        click(source, showType, sourceTree) {
            // 不是起点才能点
            if (source.depth) {
                if (source.children) {
                    source._children = source.children;
                    source.children = null;
                } else {
                    source.children = source._children;
                    source._children = null;
                }
                this.update(source, showType, sourceTree)
            }
        },

        diagonal(s, d, showtype) {
            let path
            if (showtype === 'up') {
                path = `M ${s.x} ${-s.y + 24}
        C${s.x} -${(s.y + d.y) * 0.45},
         ${s.x} -${(s.y + d.y) * 0.45},
          ${d.x} -${d.y}`;
            } else {
                path = `M ${s.x} ${s.y}
        C${s.x} ${(s.y + d.y) * 0.45},
         ${s.x} ${(s.y + d.y) * 0.45},
          ${d.x} ${d.y}`;
            }
            return path;
        },

        saveImg() {
            alert('保存')
        },

        resetSvg() {
            this.d3.select('#treesvg').remove()
            this.init()
        }
    }

}
</script>
<style lang="scss">
.penetrate-chart {
    .bt-group {
        position: fixed;
        z-index: 999;
        right: 15px;
        bottom: 15px;
        button {
            width: 88px;
            height: 32px;
            display: block;
            border-radius: 18px;
            font-size: 14px;
            font-family: PingFangSC-Medium;
            font-weight: 500;
            line-height: 20px;
        }
        .save {
            background: rgba(255, 168, 9, 1);
            color: rgba(255, 255, 255, 1);
        }
        .reset {
            margin-top: 8px;
            color: rgba(255, 168, 9, 1);
            background: white;
            border: 1px solid rgba(255, 168, 9, 1);
        }
    }
}
#treesvg {
    display: block;
    margin: auto;
    #g {
        .linkup,
        .linkdown {
            fill: none;
            stroke-width: 1px;
        }
        .text-style-name {
            font-size: 12px; /*no*/
            font-family: PingFangSC-Medium;
            font-weight: 500;
        }
        .text-style-money {
            font-size: 10px; /*no*/
            font-family: PingFangSC-Regular;
            font-weight: 400;
            color: rgba(70, 81, 102, 1);
        }
        .proportion {
            font-size: 10px;
            font-family: PingFangSC-Regular;
            font-weight: 400;
        }
    }
    .proportion-hide,
    .hide-node {
        display: none;
    }
}
</style>


相关文章

  • 股权穿透d3

  • d3.js 股权穿透

    背景 穿透图谱也出来了, 技术点 使用d3主要需要掌握svg,jquery和d3这几个知识点。本次使用的是 v3版...

  • 11月3日 我觉察我看到了什么 I 奇迹日志

    D3 第三天 今天,我觉察我在注视什么、留意什么。我是否比较有兴趣看到一起交通意外,还是享受看到阳光穿透窗户?我会...

  • python自动化:企查查股权穿透

    有位律师朋友,工作内容需要帮甲方公司整理上市文件,不断的向下获取公司的股东,直到自然人或者公司无股东则停止,给个图...

  • d3.js

    D3是什么? D3: 是Data-Driven Documents(数据驱动文档)的简称。 D3(或D3.js) ...

  • Vue D3 力导向图

    1. 安装 前端工程根目录下执行 yarn add d3 ,安装 d3 依赖包。安装的版本 "d3": "^5...

  • d3基础知识

    h5: Angular ts文件:import * as d3 from 'd3'; ngOnInit(): ...

  • 纳百川股权案例解析:作为老板,不可不知的投资骗局

    作者:纳百川股权 纳百川股权是一家专注于企业股权设计、股权激励、股权融资、股权研究、股权培训、股权专案服务的特色化...

  • Explore D3

    d3 v5 Tech Stack ? Node Webpack D3 Installation ? Compile...

  • 2018-09-14_D3.js

    Data Visualization with D3 D3: SVG中的jQurey 1. Add Documen...

网友评论

      本文标题:股权穿透d3

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