dlrandy / note-issues

2 stars 0 forks source link

echarts #149

Open dlrandy opened 1 year ago

dlrandy commented 1 year ago
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Editable Organization Chart</title>
    <!-- Import ECharts library -->
    <script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
  </head>
  <body>
    <div id="chart" style="width: 800px; height: 600px;"></div>

    <script>
      // Sample data for the organization chart
      var data = {
        name: "CEO",
        children: [
          {
            name: "Manager 1",
            children: [{ name: "Employee 1" }, { name: "Employee 2" }]
          },
          {
            name: "Manager 2",
            children: [{ name: "Employee 1" }, { name: "Employee 3" }]
          }
        ]
      };

      // Create the options object for ECharts
      var options = {
        series: [
          {
            type: "tree",
            data: [data],
            top: "10%",
            left: "20%",
            bottom: "10%",
            right: "20%",
            symbol: "emptyCircle",
            symbolSize: 7,
            initialTreeDepth: 2,
            label: {
              position: "bottom",
              verticalAlign: "middle",
              align: "center"
            },
            leaves: {
              label: {
                position: "top",
                verticalAlign: "middle",
                align: "center"
              }
            },
            expandAndCollapse: true,
            animationDuration: 550,
            animationDurationUpdate: 750,
            layout: "orthogonal" // Set the layout to orthogonal
          }
        ],
        lineStyle: {
          curveness: 0.5, // Adjust the curveness of the lines
          type: "solid" // Set the line type to dotted for broken lines
        }
      };

      // Create the organization chart using ECharts
      var chart = echarts.init(document.getElementById("chart"));
      chart.setOption(options);

      // Add event listener for node click
      chart.on("click", function (params) {
        if (params.data.name !== "CEO") {
          var newName = prompt("Enter a new name for " + params.data.name);
          if (newName) {
            params.data.name = newName;
            chart.setOption(options);
          }
        }
      });
    </script>
  </body>
</html>
dlrandy commented 1 year ago
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Organization Structure Chart</title>
    <style>
      .node rect {
        fill: lightblue;
        stroke: steelblue;
      }

      .node text {
        font-size: 14px;
        text-anchor: middle;
        fill: black;
      }

      .node-image {
        width: 40px;
        height: 40px;
        border-radius: 50%;
      }
    </style>
  </head>
  <body>
    <svg id="chart"></svg>

    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script>
      const width = 800;
      const height = 600;
      const margin = { top: 20, right: 20, bottom: 20, left: 20 };
      const nodeWidth = 180;
      const nodeHeight = 100;
      let i = 0;

      const data = {
        name: "Company",
        children: [
          {
            name: "CEO",
            avatar: "https://example.com/avatar1.png",
            role: "Chief Executive Officer",
            children: [
              {
                name: "CFO",
                avatar: "https://example.com/avatar2.png",
                role: "Chief Financial Officer",
                children: [
                  {
                    name: "Finance Manager",
                    avatar: "https://example.com/avatar3.png",
                    role: "Finance Manager"
                  },
                  {
                    name: "Accounting Manager",
                    avatar: "https://example.com/avatar4.png",
                    role: "Accounting Manager"
                  }
                ]
              },
              {
                name: "CTO",
                avatar: "https://example.com/avatar5.png",
                role: "Chief Technology Officer",
                children: [
                  {
                    name: "Engineering Manager",
                    avatar: "https://example.com/avatar6.png",
                    role: "Engineering Manager"
                  },
                  {
                    name: "Product Manager",
                    avatar: "https://example.com/avatar7.png",
                    role: "Product Manager"
                  }
                ]
              }
            ]
          },
          {
            name: "COO",
            avatar: "https://example.com/avatar8.png",
            role: "Chief Operating Officer",
            children: [
              {
                name: "Operations Manager",
                avatar: "https://example.com/avatar9.png",
                role: "Operations Manager",
                children: [
                  {
                    name: "Logistics Manager",
                    avatar: "https://example.com/avatar10.png",
                    role: "Logistics Manager"
                  },
                  {
                    name: "Procurement Manager",
                    avatar: "https://example.com/avatar11.png",
                    role: "Procurement Manager"
                  }
                ]
              },
              {
                name: "HR Manager",
                avatar: "https://example.com/avatar12.png",
                role: "HR Manager",
                children: [
                  {
                    name: "Recruitment Manager",
                    avatar: "https://example.com/avatar13.png",
                    role: "Recruitment Manager"
                  },
                  {
                    name: "Employee Relations Manager",
                    avatar: "https://example.com/avatar14.png",
                    role: "Employee Relations Manager"
                  }
                ]
              }
            ]
          }
        ]
      };

      const svg = d3
        .select("#chart")
        .attr("width", width)
        .attr("height", height);

      const treeLayout = d3
        .tree()
        .size([
          width - margin.left - margin.right,
          height - margin.top - margin.bottom
        ]);

      const root = d3.hierarchy(data);
      root.x0 = width / 2;
      root.y0 = 0;

      treeLayout(root);

      function update(source) {
        const nodes = root.descendants().reverse();
        const links = root.links();

        const node = svg
          .selectAll("g.node")
          .data(nodes, (d) => d.id || (d.id = ++i));

        const nodeEnter = node
          .enter()
          .append("g")
          .attr("class", "node")
          .attr(
            "transform",
            (d) => `translate(${d.y + margin.left},${d.x + margin.top})`
          )
          .on("dblclick", editNodeName);

        nodeEnter
          .append("rect")
          .attr("width", nodeWidth)
          .attr("height", nodeHeight)
          .attr("rx", 10)
          .attr("ry", 10);

        nodeEnter
          .append("image")
          .attr("class", "node-image")
          .attr("x", nodeWidth / 2 - 20)
          .attr("y", nodeHeight / 2 - 30)
          .attr("xlink:href", (d) => d.data.avatar)
          .attr("width", 40)
          .attr("height", 40);

        nodeEnter
          .append("text")
          .attr("x", nodeWidth / 2)
          .attr("y", nodeHeight / 2 - 10)
          .attr("dy", ".35em")
          .text((d) => d.data.name);

        nodeEnter
          .append("text")
          .attr("x", nodeWidth / 2)
          .attr("y", nodeHeight / 2 + 10)
          .attr("dy", ".35em")
          .text((d) => d.data.role);

        const nodeUpdate = node
          .merge(nodeEnter)
          .transition()
          .duration(500)
          .attr(
            "transform",
            (d) => `translate(${d.y + margin.left},${d.x + margin.top})`
          );

        const link = svg.selectAll("path.link").data(links, (d) => d.target.id);

        const linkEnter = link
          .enter()
          .insert("path", "g")
          .attr("class", "link")
          .attr("d", (d) => {
            const o = { x: source.x0, y: source.y0 };
            return diagonal({ source: o, target: o });
          });

        link.merge(linkEnter).transition().duration(500).attr("d", diagonal);

        nodes.forEach((d) => {
          d.x0 = d.x;
          d.y0 = d.y;
        });
      }

      const diagonal = d3
        .linkVertical()
        .x((d) => d.y + margin.left + nodeWidth / 2)
        .y((d) => d.x + margin.top + nodeHeight / 2);

      function editNodeName(event, d) {
        event.stopPropagation();
        const currentNode = d.data;
        const currentName = currentNode.name ? currentNode.name : "";
        const newText = prompt("Enter new name:", currentName);
        if (newText !== null) {
          currentNode.name = newText;
          update(d);
        }
      }

      update(root);
    </script>
  </body>
</html>
dlrandy commented 1 year ago

import { Graph, Cell, Node, Dom } from '@antv/x6'
import dagre from 'dagre'
import insertCss from 'insert-css'

// 定义样式
insertCss(`
  .x6-cell {
    cursor: default;
  }
  .x6-node .btn {
    cursor: pointer;
  }
`)

// 自定义节点
Graph.registerNode(
  'org-node',
  {
    width: 260,
    height: 88,
    markup: [
      {
        tagName: 'rect',
        attrs: {
          class: 'card',
        },
      },
      {
        tagName: 'image',
        attrs: {
          class: 'image',
        },
      },
      {
        tagName: 'text',
        attrs: {
          class: 'rank',
        },
      },
      {
        tagName: 'text',
        attrs: {
          class: 'name',
        },
      },
      {
        tagName: 'g',
        attrs: {
          class: 'btn addup ',
        },
        children: [
          {
            tagName: 'circle',
            attrs: {
              class: 'addup ',
            },
          },
          {
            tagName: 'text',
            attrs: {
              class: 'addup',
            },
          },
        ],
      },
      {
        tagName: 'g',
        attrs: {
          class: 'btn add ',
        },
        children: [
          {
            tagName: 'circle',
            attrs: {
              class: 'add ',
            },
          },
          {
            tagName: 'text',
            attrs: {
              class: 'add',
            },
          },
        ],
      },
      {
        tagName: 'g',
        attrs: {
          class: 'btn del',
        },
        children: [
          {
            tagName: 'circle',
            attrs: {
              class: 'del',
            },
          },
          {
            tagName: 'text',
            attrs: {
              class: 'del',
            },
          },
        ],
      },
    ],
    attrs: {
      '.card': {
        rx: 10,
        ry: 10,
        refWidth: '100%',
        refHeight: '100%',
        fill: '#5F95FF',
        stroke: '#5F95FF',
        strokeWidth: 1,
        pointerEvents: 'visiblePainted',
      },
      '.image': {
        x: 16,
        y: 16,
        width: 56,
        height: 56,
        opacity: 0.7,
      },
      '.rank': {
        refX: 0.95,
        refY: 0.5,
        fill: '#fff',
        fontFamily: 'Courier New',
        fontSize: 13,
        textAnchor: 'end',
        textVerticalAnchor: 'middle',
      },
      '.name': {
        refX: 0.95,
        refY: 0.7,
        fill: '#fff',
        fontFamily: 'Arial',
        fontSize: 14,
        fontWeight: '600',
        textAnchor: 'end',
      },
      '.btn.add': {
        refDx: -16,
        refY: 16,
        event: 'node:add',
      },
      '.btn.addup': {
        refDx: -72,
        refY: 16,
        event: 'node:add',
      },
      '.btn.del': {
        refDx: -44,
        refY: 16,
        event: 'node:delete',
      },
      '.btn > circle': {
        r: 10,
        fill: 'transparent',
        stroke: '#fff',
        strokeWidth: 1,
      },
      '.btn.add > text': {
        fontSize: 20,
        fontWeight: 800,
        fill: '#fff',
        x: -5.5,
        y: 7,
        fontFamily: 'Times New Roman',
        text: '+',
      },
      '.btn.addup > text': {
        fontSize: 20,
        fontWeight: 800,
        fill: '#fff',
        x: -5.5,
        y: 7,
        fontFamily: 'Times New Roman',
        text: '∧',
      },
      '.btn.del > text': {
        fontSize: 28,
        fontWeight: 500,
        fill: '#fff',
        x: -4.5,
        y: 6,
        fontFamily: 'Times New Roman',
        text: '-',
      },
    },
  },
  true,
)

// 自定义边
Graph.registerEdge(
  'org-edge',
  {
    zIndex: -1,
    attrs: {
      line: {
        strokeWidth: 2,
        stroke: '#A2B1C3',
        sourceMarker: null,
        targetMarker: null,
      },
    },
  },
  true,
)

const male =
  'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ'
const female =
  'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*f6hhT75YjkIAAAAAAAAAAAAAARQnAQ'
// 布局方向
const dir = 'TB' // LR RL TB BT

// 创建画布
const graph = new Graph({
  container: document.getElementById('container')!,
  scroller: true,
  interacting: false,
})

// 监听自定义事件
function setup() {
  graph.on('node:add', ({ e, node }) => {
    console.log(e, node);
    e.stopPropagation()
    const member = createNode(
      'Employee',
      'New Employee',
      Math.random() < 0.5 ? male : female,
    )
    if(e.currentTarget.classList.contains('addup')){
graph.addCell([member, createEdge(member, node)])
    }else{

    graph.addCell([member, createEdge(node, member)])
    }
    layout()
  })

  graph.on('node:delete', ({ e, node }) => {
    e.stopPropagation()
    graph.removeCell(node)
    layout()
  })
}

// 自动布局
function layout() {
  const nodes = graph.getNodes()
  const edges = graph.getEdges()
  const g = new dagre.graphlib.Graph()
  g.setGraph({ rankdir: dir, nodesep: 16, ranksep: 16 })
  g.setDefaultEdgeLabel(() => ({}))

  const width = 260
  const height = 90
  nodes.forEach((node) => {
    g.setNode(node.id, { width, height })
  })

  edges.forEach((edge) => {
    const source = edge.getSource()
    const target = edge.getTarget()
    g.setEdge(source.cell, target.cell)
  })

  dagre.layout(g)

  g.nodes().forEach((id) => {
    const node = graph.getCellById(id) as Node
    if (node) {
      const pos = g.node(id)
      node.position(pos.x, pos.y)
    }
  })

  edges.forEach((edge) => {
    const source = edge.getSourceNode()!
    const target = edge.getTargetNode()!
    const sourceBBox = source.getBBox()
    const targetBBox = target.getBBox()

    if ((dir === 'LR' || dir === 'RL') && sourceBBox.y !== targetBBox.y) {
      const gap =
        dir === 'LR'
          ? targetBBox.x - sourceBBox.x - sourceBBox.width
          : -sourceBBox.x + targetBBox.x + targetBBox.width
      const fix = dir === 'LR' ? sourceBBox.width : 0
      const x = sourceBBox.x + fix + gap / 2
      edge.setVertices([
        { x, y: sourceBBox.center.y },
        { x, y: targetBBox.center.y },
      ])
    } else if (
      (dir === 'TB' || dir === 'BT') &&
      sourceBBox.x !== targetBBox.x
    ) {
      const gap =
        dir === 'TB'
          ? targetBBox.y - sourceBBox.y - sourceBBox.height
          : -sourceBBox.y + targetBBox.y + targetBBox.height
      const fix = dir === 'TB' ? sourceBBox.height : 0
      const y = sourceBBox.y + fix + gap / 2
      edge.setVertices([
        { x: sourceBBox.center.x, y },
        { x: targetBBox.center.x, y },
      ])
    } else {
      edge.setVertices([])
    }
  })
}

function createNode(rank: string, name: string, image: string) {
  return graph.createNode({
    shape: 'org-node',
    attrs: {
      '.image': { xlinkHref: image },
      '.rank': {
        text: Dom.breakText(rank, { width: 160, height: 45 }),
      },
      '.name': {
        text: Dom.breakText(name, { width: 160, height: 45 }),
      },
    },
  })
}

function createEdge(source: Cell, target: Cell) {
  return graph.createEdge({
    shape: 'org-edge',
    source: { cell: source.id },
    target: { cell: target.id },
  })
}

const nodes = [
  createNode('Founder & Chairman', 'Pierre Omidyar', male),
  createNode('President & CEO', 'Margaret C. Whitman', female),
  createNode('President, PayPal', 'Scott Thompson', male),
  createNode('President, Ebay Global Marketplaces', 'Devin Wenig', male),
  createNode('Senior Vice President Human Resources', 'Jeffrey S. Skoll', male),
  createNode('Senior Vice President Controller', 'Steven P. Westly', male),
]

const edges = [
  createEdge(nodes[0], nodes[1]),
  createEdge(nodes[1], nodes[2]),
  createEdge(nodes[1], nodes[3]),
  createEdge(nodes[1], nodes[4]),
  createEdge(nodes[1], nodes[5]),
]

graph.resetCells([...nodes, ...edges])
layout()
graph.zoomTo(0.8)
graph.centerContent()
setup()