You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JSCharting组织结构图:实现节点拖拽修改parent属性方案

如何为JSCharting组织结构图节点添加拖拽事件调整层级归属

要实现拖拽节点调整层级的功能,我们可以通过原生JavaScript监听鼠标事件,结合JSCharting的API修改节点parent属性并重新渲染图表。以下是改造后的完整代码及关键步骤说明:

完整实现代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <title>可拖拽调整层级的组织结构图 | JSCharting</title>
  <meta http-equiv="content-type" content="text-html; charset=utf-8" />

  <script src="https://code.jscharting.com/latest/jscharting.js"></script>
  <script type="text/javascript" src="https://code.jscharting.com/latest/modules/types.js"></script>
  <script type="text/javascript" src="https://code.jscharting.com/latest/modules/toolbar.js"></script>

  <style>
    .chartDiv {
      margin: 8px auto;
      padding: 15px;
      border-radius: 10px;
    }
    /* 拖拽状态样式 */
    .dragging-node {
      opacity: 0.6;
      cursor: move;
    }
    .target-node {
      outline: 2px solid #2196F3;
    }
  </style>
</head>
<body>
  <div id="chartDiv1" class="chartDiv" style="max-width: 700px;height: 400px;"></div>

  <script type="text/javascript">
    var chart = JSC.chart('chartDiv1', {
      debug: true,
      type: 'organizational',
      defaultAnnotation: { padding: [5, 10], margin: [15, 5] },
      defaultTooltip_enabled: false,
      defaultSeries: {
        color: '#dde1e8',
        defaultPoint: {
          label_maxWidth: 90,
          connectorLine: {
            radius: [0, 5],
            color: '#424242',
            width: 1,
            caps: { end: { type: 'arrow', size: 6 } }
          },
          // 绑定节点鼠标按下事件
          events_mousedown: startDrag
        }
      },
      series: [
        {
          points: [
            { x: '项目经理', id: 'PM' },
            { x: '副项目经理', id: 'DPM', parent: 'PM' },
            { x: '系统工程部', id: 'SE', parent: 'DPM' },
            { x: '独立测试组', id: 'ITG', parent: 'DPM' },
            { x: '项目技术负责人', id: 'PTL', parent: 'DPM' },
            { x: '质量保证部', id: 'QA', parent: 'DPM' },
            { x: '配置管理员', id: 'CM', parent: 'DPM' },

            { x: '软件子项目经理1', id: 'SM1', parent: 'PTL' },
            { x: '团队1', id: 'team11', parent: 'SM1' },
            { x: '团队2', id: 'team12', parent: 'SM1' },
            { x: '团队3', id: 'team13', parent: 'SM1' },

            { x: '软件子项目经理2', id: 'SM2', parent: 'PTL' },
            { x: '团队1', id: 'team21', parent: 'SM2' },
            { x: '团队2', id: 'team22', parent: 'SM2' },

            { x: '软件子项目经理3', id: 'SM3', parent: 'PTL' },
            { x: '团队1', id: 'team31', parent: 'SM3' },
            { x: '团队2', id: 'team32', parent: 'SM3' }
          ]
        }
      ],
      toolbar: {
        defaultItem: {
          margin: 5,
          events_click: orientChart
        },
        items: {
          Left_icon: 'system/default/zoom/arrow-left',
          Right_icon: 'system/default/zoom/arrow-right',
          Down_icon: 'system/default/zoom/arrow-down',
          Up_icon: 'system/default/zoom/arrow-up'
        }
      }
    });

    // 拖拽状态变量
    let draggedNode = null;
    let targetNode = null;

    // 启动拖拽
    function startDrag(e) {
      draggedNode = this;
      draggedNode.annotation.element.classList.add('dragging-node');
      // 绑定全局鼠标事件
      document.addEventListener('mousemove', onDrag);
      document.addEventListener('mouseup', endDrag);
      e.preventDefault();
    }

    // 拖拽过程
    function onDrag(e) {
      // 清除之前的目标节点样式
      if (targetNode) {
        targetNode.annotation.element.classList.remove('target-node');
      }
      // 获取鼠标位置对应的节点
      targetNode = chart.get('point', e.clientX, e.clientY);
      // 标记有效目标节点(排除自身)
      if (targetNode && targetNode.id !== draggedNode.id) {
        targetNode.annotation.element.classList.add('target-node');
      } else {
        targetNode = null;
      }
    }

    // 结束拖拽
    function endDrag() {
      // 清除拖拽样式
      if (draggedNode) {
        draggedNode.annotation.element.classList.remove('dragging-node');
      }
      // 更新层级(防止循环归属)
      if (targetNode && draggedNode && !isDescendant(targetNode, draggedNode)) {
        draggedNode.options({ parent: targetNode.id });
        chart.update();
      }
      // 移除全局事件监听
      document.removeEventListener('mousemove', onDrag);
      document.removeEventListener('mouseup', endDrag);
      // 重置状态
      draggedNode = null;
      targetNode = null;
    }

    // 检查节点是否为目标节点的后代,避免循环层级
    function isDescendant(child, parent) {
      let current = child;
      while (current.parent) {
        if (current.parent.id === parent.id) {
          return true;
        }
        current = current.parent;
      }
      return false;
    }

    // 调整图表方向
    function orientChart(direction) {
      var isVertical = /up|down/.test(direction.toLowerCase());
      chart.options({
        type: 'organizational ' + direction,
        defaultPoint_annotation: {
          syncWidth: !isVertical,
          syncHeight: isVertical,
          margin: isVertical ? [15, 5] : [5, 15]
        }
      });
    }
  </script>
</body>
</html>

关键实现说明

  1. 拖拽事件绑定

    • 在defaultPoint配置中添加events_mousedown: startDrag,为所有节点绑定鼠标按下事件,触发拖拽启动逻辑。
    • 通过全局监听mousemove和mouseup事件,实现跨节点的拖拽跟踪。
  2. 目标节点检测

    • 使用chart.get('point', e.clientX, e.clientY)方法,根据鼠标实时位置获取对应节点。
    • 为拖拽节点和目标节点添加CSS样式,提升交互视觉反馈。
  3. 层级更新与防循环

    • 拖拽结束时修改拖拽节点的parent属性为目标节点ID,调用chart.update()重新渲染图表。
    • 新增isDescendant函数,避免将父节点拖到子节点下造成循环层级结构。
  4. 交互体验优化

    • 添加拖拽半透明效果和目标节点高亮边框,清晰展示拖拽状态。
    • 阻止鼠标按下默认事件,避免节点文字被选中影响拖拽体验。

内容的提问来源于stack exchange,提问作者RG Servers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 07:27:03