React与D3的结合

本文介绍如何在React项目中使用D3 V7版本开发数据关系图形界面,涉及拖拽、缩放、连线及箭头、节点添加删除、节点通信等功能。详细阐述了代码结构和各个关键函数的作用,包括DOM节点管理、力导向图模型构建、线条与箭头绘制、React组件渲染等,并探讨了开发过程中遇到的问题和解决方案。
摘要由CSDN通过智能技术生成

前言

前段时间公司有个需求要开发一个数据关系的界面,类似UML建模工具里面表之间关系的图形界面,目前用的前端框架是React、rxjs,图形界面这块定下来采用的是D3的最新版本V7,所以现在需要基于React框架下开发这个界面,前期查了一些相关资料,国内基于React、D3 V7版本结合开发的比较少,差不多都是V3、V4版本,V4版本国内还有中文翻译V4之后就停了,所以结合个人在当前的需求背景下以及使用过程中的碰到的一些问题记录下来,一方面供有需要人的可以借鉴下,一方面也是给自己做个总结。

用的D3版本v7.0.0,需要开发的功能:

1.拖拽、缩放功能

2.连线并带有箭头,线条有文字

3.能添加节点、删除结点

4.添加节点需计算位置,尽量保证不重叠

5.节点与节点之间需要通信更新数据

6.节点不同层级展示的背景颜色不一致

7.节点可折叠、展开

代码结构

import * as d3 from 'd3';
import * as React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';

// 节点高
const nodeHalfHeight = 300 / 2;
// 节点宽度
const nodeWidth = 240;
// 折叠之后的高度
const foldHeight = 85 / 2;
// 未选择表数据标识
const NO_DATA = 'NO_DATA';
// 获取随机ID
const getRandomId = () => Math.random().toString(32).slice(2);


// 记录当前操作折叠的nodeId
let nodeIds: Array<any> = [];

const D3DataModel = (props: any): React.ReactElement => {
  const refs = React.useRef(null);
  // 表数据
  const [d3NodeData, setD3NodeData] = React.useState(() => {
    // nodeId 用来构建连线以及生成表格区域的ID
    // level 用来根据层级绘画表格背景色
    // data_type 用来区分是否渲染无数据背景图片
    return [{ x: 10, y: 10, data_type: NO_DATA, nodeId: getRandomId(), level: 1 }];
  });
  // d3缩放范围
  const [transformInfo, setTransformInfo] = React.useState<any>(null);

  React.useEffect(() => {
    drawModel();
  }, [d3NodeData.length]);

  const getD3Data = (): any => {
      ...3.Demo数据
  };

  /**
   * 计算线条文字位置
   *
   * @param {*} data
   * @return {*}
   */
  const calcuLabelPoint = (data: any): number => {
      ...12.计算文字坐标
  };

  /**
   * 获取缩放对象
   *
   * @param {*} g
   * @return {*}
   */
  const d3ZoomObj = (g: any): any => {
      ...5.缩放
  };

  /**
   * 获取拖拽对象
   *
   * @param {*} simulation 力模型
   * @return {*}  {object}
   */
  const d3DragObj = (simulation: any): any => {
      ...6.拖拽
  };

  /**
   * 构建表格
   *
   * @param {*} g
   * @param {*} data
   * @param {*} drag
   * @return {*}
   */
  const buildTable = (g: any, data: any, drag: any): any => {
      ...7.构建表格节点
  };

  /**
   * 构建线条
   *
   * @param {*} g
   * @param {*} data
   * @return {*}  {*}
   */
  const buildLine = (g: any, data: any): any => {
      ...8.构建线条
  };

  /**
   * 构建线条文字
   *
   * @param {*} g
   * @param {*} data
   * @return {*}  {*}
   */
  const buildLineLabel = (g: any, data: any): any => {
      ...9.构建线条文字
  };

  /**
   * 构建箭头
   *
   * @param {*} g
   * @return {*}  {*}
   */
  const buildArrow = (g: any): any => {
      ...10.构建箭头
  };

  /**
   * 绘画
   *
   */
  const drawModel = () => {
      ...2.绘制函数
  };

  /**
   * 渲染数据表
   *
   * @param {*} props
   */
  const renderDataTable = (props: any) => {
      ...13.渲染React组件到图形中
  };

  return (
    <section className={'d3-dataModel-area'}>
      <div className={'popup-element'} />
      <div className={'d3-element'} ref={refs} />
    </section>
  );
};

export default D3DataModel;

代码拆解

1.DOM节点

这个DOM节点用于挂载ant组件TooltipSelect生成的DOM,因为我们当前这种方式节点内部元素DataTableComp中有使用到ant组件,导致D3重绘时ant生成的一些DOM节点没有清除,统一挂载到这个区域统一清除。

<div className={'popup-element'} />

D3绘制的图形节点全部在这个div中。

<div className={'d3-element'} ref={refs} />
<section className={'d3-dataModel-area'}>
      {/* ant组件弹框元素挂载节点 */}
      <div className={'popup-element'} />
      {/* d3绘制节点 */}
      <div className={'d3-element'} ref={refs} />
</section>

2.绘制函数

这个函数主要是整合其他函数,统一入口。

  React.useEffect(() => {
    drawModel();
  }, [d3NodeData.length]);

  /**
   * 绘画
   *
   */
  const drawModel = () => {
    const { edges } = getD3Data();
    // 先移除svg
    d3.selectAll('svg').remove();
    // 构建svg
    const svg = d3.select(refs.current).append('svg');
    // 构建容器g
    const g = svg.append('g').attr('transform', transformInfo);
    // 构建力模型,防止模型重叠
    const simulation = d3.forceSimulation(d3NodeData).force('collide', d3.forceCollide().radius(100));
    // 缩放
    const zoom = d3ZoomObj(g);
    // 获取拖拽对象
    const drag = d3DragObj(simulation);
    // 构建表格区节点
    const d3DataTable = buildTable(g, d3NodeData, drag);
    // 构建线条
    const line = buildLine(g, edges);
    // 连线名称
    const lineLabel = buildLineLabel(g, edges);
    // 绘制箭头
    const arrows = buildArrow(g);

    simulation.on('tick', () => {
      // 更新节点位置
      d3DataTable.attr('transform', (d) => {
        return d && 'translate(' + d.x + ',' + d.y + ')';
      });
      // 更新连线位置
      line.attr('d', (d: any) => {
        // 节点的x+节点宽度
        const M1 = d.source.x + nodeWidth;
        // 节点的y+节点的一半高度
        let pathStr = `M ${M1} ${d.source.y + nodeHalfHeight} L ${d.target.x} ${d.target.y + nodeHalfHeight}`;
        // 起点折叠
        if (nodeIds.includes(d.source.nodeId)) {
          pathStr = `M ${M1} ${d.source.y + foldHeight} L ${d.target.x} ${d.target.y + nodeHalfHeight}`;
        }
        //
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值