惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Fortinet All Blogs
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Secure Thoughts
美团技术团队
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
月光博客
月光博客
T
Tor Project blog
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
I
Intezer
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
GbyAI
GbyAI
罗磊的独立博客
V
V2EX
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
A
About on SuperTechFans
Scott Helme
Scott Helme
Vercel News
Vercel News
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
Recent Announcements
Recent Announcements
Hacker News: Ask HN
Hacker News: Ask HN
C
CERT Recently Published Vulnerability Notes
G
Google Developers Blog
B
Blog
博客园 - 叶小钗
WordPress大学
WordPress大学
博客园 - 聂微东
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
IT之家
IT之家
C
Cybersecurity and Infrastructure Security Agency CISA
P
Palo Alto Networks Blog
小众软件
小众软件
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
AWS News Blog
AWS News Blog

博客园 - Lemo_wd

flutter —— iOS 的构建与分发 flutter —— 理解 iOS 的依赖管理 wireguard 入门 k3s 基础 —— 将 traefik 替换为 ingress-nginx Pr 入门 为什么 TCP 是3次握手4次挥手? 给 ssh 私钥文件配置统一口令 滚动部署零碎笔记1 Clickhouse 物化视图与投影 Clickhouse 主键索引 Clickhouse 核心概念 ClickHouse 案例学习 —— 使用 ClickHouse 探索 GitHub 统计信息 linux Tun/Tap 虚拟网卡 Clickhouse 的 kafka Engine 集成 Clickhouse 表引擎 —— MergeTree 局域网中的设备间的流量转发 d3.js 构建股权架构图并绘制双向节点树 final cut pro 入门 flutter 的滚动控制 —— 滚动类组件的内部与整体滚动
d3.js 构建股权架构图并绘制股权百分比
Lemo_wd · 2024-06-04 · via 博客园 - Lemo_wd

效果:

代码:

StockStructureChart.js

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

const StockStructureChart = ({ data }) => {
  const ref = useRef()

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

  useEffect(() => {
    const chartRef = ref.current
    //移除旧图形
    d3.select("svg g").remove()

    const svg = d3.select(chartRef).append("g")

    const tree = d3
      .tree()
      .nodeSize([boxWidth + 20, boxHeight + 20])
      .separation(function (a, b) {
        return a.parent === b.parent ? 1 : 1
      })
    const root = d3.hierarchy(data)
    const treeData = tree(root)

    //节点树垂直居中处理
    //注1:root.height 为节点树的深度, 这里是估算的树的总高度
    const nodeHeight = boxHeight * 4
    const totalTreeHeight = root.height * nodeHeight
    const center = height / 2
    const offset = center - totalTreeHeight / 2
    //注2:调整 y 坐标(注:左上角为坐标原点),使图形垂直居中
    treeData.each((d) => {
      d.y = height - d.depth * nodeHeight - offset
      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}`
    }

    //节点连接线的箭头符号
    svg
      .append("defs")
      .append("marker")
      .attr("id", "arrowDown")
      .attr("markerUnits", "userSpaceOnUse")
      .attr("viewBox", "-5 0 10 10")
      .attr("refX", 0)
      .attr("refY", boxHeight / 2 + 13) //箭头与节点的距离: boxHeight/2(15) + markerHeight(10) + delta(3)
      .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")

    //绘制连接线
    svg
      .selectAll(".link")
      .data(treeData.links())
      .enter()
      .append("path")
      .attr("class", "link")
      .attr("fill", "none")
      .attr("stroke", "#555")
      .attr("stroke-width", 1)
      .attr("marker-start", "url(#arrowDown)")
      .attr("d", straightLine)

    //连接线上的文字
    svg
      .selectAll(".link-text")
      .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 = svg
      .selectAll(".node")
      .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", "steelblue")
      .style("fill", "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", "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)

    // 默认缩放
    function initialTransform() {
      const bounds = d3.select("svg 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) => {
      d3.select("svg g").attr("transform", e.transform)
    })

    //初始化缩放
    d3.select(chartRef)
      .call(zoom)
      .call(zoom.transform, initialTransformTranslate)
  }, [data])

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

export default StockStructureChart

App.js

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

function App() {
  const data = {
    name: "Root",
    children: [
      {
        name: "Company A",
        percent: 50,
        children: [
          { name: "Subsidiary A1", percent: 100 },
          { name: "Subsidiary A2", percent: 200,
            children: [
              { name: "Subsidiary A2.1", percent: 50 },
              { name: "Subsidiary A2.2", percent: 100 },
            ],
           },
        ],
      },
      {
        name: "Company B",
        percent: 50,
        children: [{ name: "Subsidiary B1", percent: 350 }],
      },
      {
        name: "Company C",
        percent: 50,
        children: [{ name: "Subsidiary C1", percent: 150 }],
      },
      {
        name: "Company C",
        percent: 50,
        children: [{ name: "Subsidiary C1", percent: 150 }],
      },
      {
        name: "Company C",
        percent: 50,
        children: [{ name: "Subsidiary C1", percent: 150 }],
      },
    ],
  }

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

export default App