首页 > 其他分享 >d3.js 构建股权架构图并绘制双向节点树

d3.js 构建股权架构图并绘制双向节点树

时间:2024-09-14 12:24:57浏览次数:10  
标签:style const attr js width 架构图 d3 append name

效果:

代码:

StockStructureChart.js

import React, { useEffect, useRef } from "react"
import * as d3 from "d3"

const StockStructureChart = ({ upwardData, downwardData }) => {
  const ref = useRef()

  const width = 800
  const height = 500
  const boxWidth = 220
  const boxHeight = 55

  useEffect(() => {
    const chartRef = ref.current
    const svg = d3.select(chartRef)

    // 删除旧的元素
    svg.selectAll("*").remove()

    // 创建渲染的组
    const g = svg.append("g")

    drawTree(g, upwardData, 1) // 绘制向上的树(位于根节点上方)
    drawTree(g, downwardData, 0) // 绘制向下的树(位于根节点下方)

    // 初始化缩放和变换
    function initialTransform() {
      const bounds = g.node().getBBox()
      const dx = bounds.width,
        dy = bounds.height,
        x = bounds.x,
        y = bounds.y
      const scale = Math.min(width / dx, height / dy, 1.4) * 0.9
      const translate = [
        width / 2 - scale * (x + dx / 2),
        height / 2 - scale * (y + dy / 2),
      ]
      const initialTransform = d3.zoomIdentity
        .translate(translate[0], translate[1])
        .scale(scale)
      return initialTransform
    }
    const initialTransformTranslate = initialTransform()

    // 缩放行为
    const zoom = d3.zoom().on("zoom", (e) => {
      g.attr("transform", e.transform)
    })

    // 应用初始缩放
    svg.call(zoom).call(zoom.transform, initialTransformTranslate)
  }, [upwardData, downwardData])

  /**
   * 根据方向绘制树
   *
   * @param {*} g - 渲染树的组元素
   * @param {*} data - 用于绘制树的数据
   * @param {*} direction - 1 代表向上,0 代表向下
   */
  function drawTree(g, data, direction) {
    const tree = d3
      .tree()
      .nodeSize([boxWidth + 20, boxHeight + 20])
      .separation((a, b) => (a.parent === b.parent ? 1 : 1))

    const treeData = tree(d3.hierarchy(data))

    treeData.each((d) => {
      const spacing = d.depth * boxHeight * 4
      d.y = direction === 1 ? -spacing : spacing
      d.x = d.x + width / 2
    })

    const straightLine = (d) => {
      const sourceX = d.source.x
      const sourceY = d.source.y
      const targetX = d.target.x
      const targetY = d.target.y
      return `M${sourceX},${sourceY}
       V${(targetY - sourceY) / 2 + sourceY}
       H${targetX}
       V${targetY}`
    }

    // 箭头符号
    g.append("defs")
      .append("marker")
      .attr("id", "arrowDown")
      .attr("markerUnits", "userSpaceOnUse")
      .attr("viewBox", "-5 0 10 10")
      .attr("refX", 0)
      .attr("refY", boxHeight / 2 + 15) //箭头与节点的距离: boxHeight/2(15) + markerHeight(10) + delta(5)
      .attr("markerWidth", 10)
      .attr("markerHeight", 10)
      .attr("orient", "0")
      .attr("stroke-width", 1)
      .append("path")
      .attr("d", "M-5,0L5,0L0,10")
      .attr("fill", "#215af3")

    // 连接线
    g.selectAll(".link-" + direction)
      .data(treeData.links())
      .enter()
      .append("path")
      .attr("class", "link")
      .attr("fill", "none")
      .attr("stroke", "#215af3")
      .attr("stroke-width", 1)
      .attr("stroke-opacity", 0.6)
      .attr("marker-start", direction === 1 && "url(#arrowDown)")
      .attr("marker-end", direction === 0 && "url(#arrowDown)")
      .attr("d", straightLine)

    // 连接线上的文字
    g.selectAll(".link-text-" + direction)
      .data(treeData.links())
      .enter()
      .append("text")
      .attr("text-anchor", "middle")
      .attr("class", "link-text")
      .attr("fill", "black")
      .attr("font-size", "13px")
      .attr("x", (d) => d.target.x + 30)
      .attr("y", (d) => d.source.y / 4 + (d.target.y * 3) / 4)
      .attr("dy", "5")
      .text((d) => d.target.data.percent)

    // 节点
    const nodes = g
      .selectAll(".node-" + direction)
      .data(treeData.descendants())
      .enter()
      .append("g")
      .attr("class", "node")
      .attr("transform", (d) => `translate(${d.x},${d.y})`)

    // 节点框
    nodes
      .append("rect")
      .attr("width", boxWidth)
      .attr("height", boxHeight)
      .attr(
        "transform",
        "translate(" + -boxWidth / 2 + "," + -boxHeight / 2 + ")"
      )
      .style("stroke-width", "1")
      .style("stroke", (d) => (d.depth === 0 ? "#215af3" : "#215af3"))
      .style("fill", (d) => (d.depth === 0 ? "#215af3" : "white"))

    // 节点文本
    nodes
      .append("foreignObject")
      .attr("width", boxWidth)
      .attr("height", boxHeight)
      .attr("x", -boxWidth / 2)
      .attr("y", -boxHeight / 2)
      .attr("font-size", (d) => (d.data.name.length > 70 ? "10px" : "14px"))
      .append("xhtml:div")
      .style("color", (d) => (d.depth === 0 ? "white" : "black"))
      .style("display", "flex")
      .style("justify-content", "center")
      .style("align-items", "center")
      .style("text-align", "center")
      .style("line-height", "1.2")
      .style("word-break", "break-word")
      .style("width", "100%")
      .style("height", "100%")
      .style("padding", "5px")
      .html((d) => d.data.name)
  }

  return <svg ref={ref} width={width} height={height} />
}

export default StockStructureChart

App.js

import "./App.css"
import StockStructureChart from "./components/StockStructureChart"

function App() {
  const upwardData = {
    name: "Root",
    children: [
      {
        name: "Company A",
        percent: 50,
      },
      {
        name: "Company B",
        percent: 50,
      },
      {
        name: "Company C",
        percent: 50,
      },
    ],
  }

  const downwardData = {
    name: "Root",
    children: [
      {
        name: "Company L",
        percent: 50,
        children: [
          {
            name: "Company M",
            percent: 100,
          },
        ],
      },
      {
        name: "Company N",
        percent: 50,
      },
    ],
  }

  return (
    <div className="App">
      <h1>Hello React + D3 world!</h1>
      <StockStructureChart
        upwardData={upwardData}
        downwardData={downwardData}
      />
    </div>
  )
}

export default App

标签:style,const,attr,js,width,架构图,d3,append,name
From: https://www.cnblogs.com/lemos/p/18413726

相关文章

  • 083java jsp SSM Springboot体育球队比赛赛事报名系统小程序(源码+文档+运行视频+讲解
     项目技术:前端运行:微信开发者工具SSM+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可......
  • 084java jsp SSM Springboot大学生心理健康服务系统小程序(源码+文档+运行视频+讲解视
     项目技术:前端运行:微信开发者工具SSM+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可......
  • JST连接器:小型化电子连接的行业标准
    在现代电子设备中,连接器的选择对设备性能和可靠性具有重要影响。JST连接器作为全球广泛使用的一种连接器,以其小型化、高可靠性和多样化的产品系列,成为众多电子工程师的首选。本文将深入探讨JST连接器的类型及特点、应用领域及其在电子行业中的重要性。JST连接器的简介JST(JapanSold......
  • SolidJS-每日小知识(9/13)
    知识介绍在div容器中并列两个SVG元素->对div容器设置display:"flex"使用d3创建散点图使用d3的scaleLinear函数创建x轴和y轴的比例尺对d3的svg元素增加tooltip提示对svg元素增加zoom功能使用d3在svg中画线对d3中某个元素的attr属性使用函数表达式return值代码分析2......
  • JS中判断数据类型的四种方法
    前言近期回顾了JS的知识,重新梳理一下几种不同的判断数据类型的方式,对于不同的数据类型,可以用不同的判断方式,防止类型判断的不够精确。一.typeoftypeof可以用来判断number、string、boolean、undefined这四种简单数据类型,以及function这个引用类型(复杂数据类型)。具体写法如下:type......
  • package.json依赖包漏洞之tough-cookie原型污染漏洞
    背景有个安全扫描的流水线,扫描了我负责的项目之后,发现一些漏洞。需要说明的是,这个扫描只是针对package.json文件,扫的是依赖树,而不是项目源代码,也不是build打包后的代码。这些正是我们提升项目安全性的宝贵机会。让我们一起来看看这些发现,并学习如何将它们转化为我们的优势。 ......
  • Js基础之循环分支
    1.if语句单分支:除了0所有的数字都为真除了空字符串,所有的数字都为真if(0){console.log();//不执行,负数也执行,只要不为0就行}题目:单分支课堂案例1:用户输入高考成绩,如果分数大于700,则提示恭喜考入黑马程序员//1.用户输入letscore=+prompt('请输入成绩')//12、进......
  • Js基础之数组
    数组(Array)-----一种将一组数据存储在单个变量名下的优雅方式letarr=[]//arr是变量[]里是数组字面量1.基本使用1.声明语法//声明数组let数组名=[数据1,数据2,····,数据n]letarr=['小明','小刚','小红','小丽','小米']//使用数组console.log(arr)//同......
  • Js基础之数据类型
    1.基本数据类型number数字型string字符串型布尔型undefined未定义型null空类型引用数据类型object对象js弱数据类型的语言,只有当我们赋值了,才知道是什么数据类型letnum='pink'console.log(num)string类型letstr='pink'letstr="pink"letsr......
  • js之变量的 介绍
    用户输入的数据我们如何储存起来?1.变量就是个容器注意:变量不是数据本身,它们仅仅是一个用于存储数值的容器。可以理解为是一个个用来装东西的纸箱子。2.变量的基本使用一.声明变量:要想使用变量,首先需要创建变量(也称为声明变量或者定义变量)语法:let变量名声明变量......