使用dagre-d3画节点图,流程图

发布于:2025-08-02 ⋅ 阅读:(12) ⋅ 点赞:(0)

d3和dagre-d3版本

 "dependencies": {
    "d3": "^7.9.0",
    "dagre-d3": "^0.6.4",
  }
<template>
  <div>
    <div class="top-box">ssss</div>
    <div>
      <svg ref="svgRef" width="100%" height="600" class="svg-box"></svg>
      <!-- Tooltip 元素 -->
      <div ref="tooltipRef" class="dagre-tooltip"></div>
    </div>
  </div>
</template>

<script setup lang="ts">
import * as d3 from 'd3';
import * as dagreD3 from 'dagre-d3';
import { graphlib } from 'dagre-d3';
import { ref, onMounted } from 'vue';
const svgRef = ref(null);
const tooltipRef = ref();
// 定义图形配置类型
interface GraphConfig {
  rankdir?: 'TB' | 'LR' | 'BT' | 'RL';
  marginx?: number;
  marginy?: number;
  width?: number;
  height?: number;
  // 其他可能的属性
}
onMounted(() => {
  renderGraph();
});
const renderGraph = () => {
  // var g = new dagreD3.graphlib.Graph()
  var g = new graphlib.Graph({
    compound: true, //分组时候必须设置
  })
    .setGraph({})
    .setDefaultEdgeLabel(function () {
      return {};
    });

  // Here we're setting nodeclass, which is used by our custom drawNodes function
  // below.
  g.setNode('A', {
    label: '<span style="color:red">A</span>',
    labelType: 'html',
    width: 20,
    height: 20,
    shape: 'house',
    class: 'type-TOP',
    labelStyle: 'fill:#000,font-weight: bold',
    style: 'fill: #afa',
    title: '啊啊啊啊啊啊',
  });
  g.setNode('B', {
    label: 'B',
    class: 'circle',
    shape: 'circle',
    style: ' stroke: #333; fill: #fff;  stroke-width: 1.5px;',
  });
  g.setNode('C', { label: 'C', class: 'diamond', shape: 'diamond' });
  g.setNode('D', { label: 'D', class: 'ellipse', shape: 'ellipse' });
  g.setNode('E', { label: 'E', class: 'last-node', shape: 'rect' });
  g.setNode('F', {
    label: 'F',
    shape: 'rect',
    width: 50,
    height: 50,
    rx: 10, // 圆角半径
    ry: 20,
  });
  g.setNode('G', { label: 'G', class: '' });
  g.setNode('H', { label: 'H', class: 'last-node' });
  g.setNode('I', { label: 'I', class: '' });
  g.setNode('J', { label: 'J', class: '' });
  g.setNode('K', { label: 'K', class: 'last-node' });
  g.setNode('L', { label: 'L', class: '' });
  g.setNode('M', { label: 'M', class: 'last-node' });
  g.setNode('N', { label: 'N', class: '' });
  g.setNode('O', { label: 'O', class: 'last-node' });

  g.nodes().forEach(function (v) {
    var node = g.node(v);
    node.rx = node.ry = 5;
  });

  // Set up edges, no special attributes.
  g.setEdge('A', 'B', {
    label: 'a to b',
    labelStyle: 'font-style: italic; text-decoration: underline;',
    style: 'stroke: #f66; stroke-width: 1px; stroke-dasharray: 5, 5;',
    arrowheadStyle: 'fill: #f66',
    arrowheadClass: 'arrowhead',
    curve: d3.curveBasis,
    arrowhead: 'vee', //"normal", "vee", "undirected"
  });
  g.setNode('group', {
    label: 'Group',
    clusterLabelPos: 'top',
    style: 'fill: #d3d7e8',
  });
  g.setNode('top_group', {
    label: 'Top Group',
    clusterLabelPos: 'top',
    style: 'fill: #ffd47f',
  });
  g.setNode('bottom_group', {
    label: 'Bottom Group',
    style: 'fill: #5f9488',
    clusterLabelPos: 'bottom',
  });
  g.setParent('top_group', 'group');
  g.setParent('bottom_group', 'group');
  g.setParent('B', 'top_group');
  g.setParent('C', 'bottom_group');
  g.setParent('F', 'bottom_group');
  g.setParent('N', 'bottom_group');

  g.setEdge('B', 'C', { curve: d3.curveBasis });
  g.setEdge('C', 'D');
  g.setEdge('D', 'E');

  g.setEdge('B', 'F');
  g.setEdge('F', 'G');
  g.setEdge('G', 'H');

  g.setEdge('F', 'I');
  g.setEdge('I', 'J');
  g.setEdge('J', 'K');
  g.setEdge('I', 'L');
  g.setEdge('L', 'M');

  g.setEdge('B', 'N');
  g.setEdge('N', 'O');
  // 4. 渲染设置
  const svg = d3.select(svgRef.value);
  const inner = svg.append('g');
  const render = new dagreD3.render();

  // 自定义形状
  render.shapes().house = function (parent, bbox, node) {
    var w = bbox.width,
      h = bbox.height,
      points = [
        { x: 0, y: 0 },
        { x: w, y: 0 },
        { x: w, y: -h },
        { x: w / 2, y: (-h * 3) / 2 },
        { x: 0, y: -h },
      ];
    const shapeSvg = parent
      .insert('polygon', ':first-child')
      .attr(
        'points',
        points
          .map(function (d) {
            return d.x + ',' + d.y;
          })
          .join(' ')
      )
      .attr('transform', 'translate(' + -w / 2 + ',' + (h * 3) / 4 + ')');

    node.intersect = function (point) {
      return dagreD3.intersect.polygon(node, points, point);
    };

    return shapeSvg;
  };
  // 5. 执行渲染
  render(inner, g as any);
  // 6. 添加缩放和平移
  const zoom = d3
    .zoom()
    .scaleExtent([0.5, 3])
    .on('zoom', (event) => {
      inner.attr('transform', event.transform);
    }) as any;

  svg.call(zoom);

  // 7. 自动居中
  const graphHeight = (g.graph() as GraphConfig)?.height || 0;
  const graphWidth = (g.graph() as GraphConfig)?.width || 0;
  const initialScale = 0.8;
  const centerX = (svg.node().clientWidth - graphWidth * initialScale) / 2;
  const centerY = (svg.node().clientHeight - graphHeight * initialScale) / 2;
  svg.call(
    zoom.transform,
    d3.zoomIdentity.translate(centerX, centerY).scale(initialScale)
  );
  //显示tootip
  const tooltip = d3.select(tooltipRef.value);
  const svgNode = svg.node() as any;
  const left = svgNode.getBoundingClientRect().left;
  const top = svgNode.getBoundingClientRect().top;
  const transform = d3.zoomTransform(svgNode);
  inner
    .selectAll('g.node')
    .on('mouseover', (event: any, nodeId: string) => {
      const node = g.node(nodeId);
      console.log(node.x, left);
      if (node.title) {
        tooltip
          .html(node.title)
          .style('opacity', 1)
          .style('left', `${node.x * transform.k + transform.x + left + 10}px`)
          .style('top', `${node.y * transform.k + transform.y + top + 10}px`);
      }
    })
    .on('mouseout', () => {
      tooltip.style('opacity', 0);
    });
  // .on('mousemove', (event: MouseEvent) => {
  //   tooltip
  //     .style('left', `${event.pageX + 10}px`)
  //     .style('top', `${event.pageY + 10}px`);
  // });
};
</script>

<style>
.svg-box {
  border: 1px solid #f00;
}
/* This sets the color for "TK" nodes to a light blue green. */
g.last-node > rect {
  fill: #00ffd0;
}

text {
  font-weight: 300;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 14px;
}

.node rect,
.node circle,
.node polygon,
.node ellipse {
  stroke: #999;
  fill: #fff;
  stroke-width: 1.5px;
}

.edgePath path {
  stroke: #333;
  stroke-width: 1.5px;
}
.arrowhead {
  stroke: blue;
  fill: blue;
  stroke-width: 1.5px;
}
.dagre-tooltip {
  position: fixed;
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  border-radius: 4px;
  pointer-events: none; /* 防止遮挡鼠标事件 */
  opacity: 0;
  transition: opacity 0.2s;
  font-size: 14px;
  z-index: 100;
}
.top-box {
  height: 200px;
}
</style>


网站公告

今日签到

点亮在社区的每一天
去签到