[简易版]有向无环图(DAG)前端可视化

首发简书, 此为合并整理版.代码链接放在文末最近公司需要做一个内部使用的机器学习平台,其中有一部分需求可以抽象为有向无环图,一边踩坑一边把研发过程记录了一下(其实是搜不到高耦合业务的成品轮子
摘要由CSDN通过智能技术生成

首发简书, 此为合并整理版.代码链接放在文末

最近公司需要做一个内部使用的机器学习平台,其中有一部分需求可以抽象为有向无环图,一边踩坑一边把研发过程记录了一下(其实是搜不到高耦合业务的成品轮子?),如果有类似需求,不妨泡杯枸杞,慢慢读完此篇.

教程实现的内容有:

模型节点的拖动, 建立关系(连线)

模型节点外部操作(节点的增删,前端实现的DAG环检测)

模型整图的平面移动(全图放缩,选框,全屏等)

关于前端可视化的技术选型.

初接需求, 考虑使用svg与canvas实现此内容,综合来看:

名称 svg canvas
图像质量 矢量图随意缩放 位图,缩放失真
事件驱动 基于dom元素,绑定事件easy 脚本驱动,事件配置不灵活
性能 同上,故渲染元素过多会造成卡顿 性能极高,更有离屏canvas未来趋势
适用场景 交互行为较多量级较少图像 超多重复元素的渲染
学习成本 相对简单 上手有一定成本

故,整体选用svg,且目前市面上基于svg实现的成品有很多, 比如墨刀,processon,noflo,和阿里系的诸多平台,在部分场景下的表现相当优秀(当然也方便随时扒开代码学习写法啦~)

书接前文,切回正题

一、节点的实现

对应节点代码(初版)为

{
    name: "name1",
    description: "description1",
    id: 1,
    parentNode: 0,
    childNode: 2,
    imgContent: "",
    parentDetails: {
      a: "",
      b: ""
    },
    linkTo: [{ id: 2 }, { id: 3 }],
    translate: {
      left: 100,
      top: 20
    }
  }
复制代码

后期(教程step5后)优化为:

{
    name: "name2",
    id: 2,
    imgContent: "",
    pos_x: 300,
    pos_y: 400,
    type: 'constant',
    in_ports: [0, 1, 2, 3, 4],
    out_ports: [0, 1, 2, 3, 4]
 }
复制代码

请忽略灵魂绘图师的抽象,一切基于数据驱动,模型节点只需要仿照上图与后端研发交互即可.

二、模型节点连线的实现

<path
  class="connector"
  v-for="(each, n) in item.linkTo" :key="n"
  :d="computedLink(i, each, n)">
</path>
复制代码

基于vue实现所以直接用了:d 动态计算贝塞尔曲线,思路是利用出入节点的id计算起始位置,对曲线公式进行赋值 点击->关于贝塞尔曲线可参考https://brucewar.gitbooks.io/svg-tutorial/15.SVG-path%E5%85%83%E7%B4%A0.html

三、节点拖拽的实现

dragPre(e, i) {
      // 准备拖动节点
      this.setInitRect(); // 初始化画板坐标
      this.currentEvent = "dragPane"; // 修正行为
      this.choice.index = i;
      this.setDragFramePosition(e);
    },
复制代码

初始化画板的原因: 由于元素在窗口的位置并非固定,每次需要初始坐标, 方便计算相对位移量.

<g
        :transform="`translate(${dragFrame.posX}, ${dragFrame.posY})`"
        class="dragFrame">
          <foreignObject width="180" height="30" >
            <body xmlns="http://www.w3.org/1999/xhtml">
              <div
              v-show="currentEvent === 'dragPane'"
              class="dragFrameArea">
              </div>
            </body>
          </foreignObject>
        </g>
复制代码

mousedown时获取拖拽元素的下标,修正坐标

dragIng(e) {
      if (this.currentEvent === "dragPane") {
        this.setDragFramePosition(e);
        // 模拟框随动
      }
    },
setDragFramePosition(e) {
      const x = e.x - this.initPos.left; // 修正拖动元素坐标
      const y = e.y - this.initPos.top;
      this.dragFrame = { posX: x - 90, posY: y - 15 };
    }
复制代码

拖动时给模拟拖动的元素赋值位置

dragEnd(e) {
      // 拖动结束
      if (this.currentEvent === "dragPane") {
        this.dragFrame = { dragFrame: false, posX: 0, posY: 0 };
        this.setPanePosition(e); // 设定拖动后的位置
      }
      this.currentEvent = null; // 清空事件行为
    },
setPanePosition(e) {
      const x = e.x - this.initPos.left - 90;
      const y = e.y - this.initPos.top - 15;
      const i = this.choice.index;
      this.DataAll[i].translate = { left: x, top: y };
    },
复制代码

拖动结束把新的位置赋值给对应元素,当然在实际项目中, 每次变更需要跟后台交互这些数据, 不需要前端模拟数据变更的,直接请求整张图的接口重新渲染就好了,更easy

四、节点连线拖拽的实现

和上一步类似,我们也是通过监听mousedown mousemove 与 mouseup这些事件.来实现节点间连线的拖拽效果.唯一难点在于计算起始的位置.

<g>
          <path
          class="connector"
          :d="dragLinkPath()"
          ></path>
        </g>
复制代码

首先来个path

setInitRect() {
      let { le
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的C++ DAG类的构建: ```c++ #include <iostream> #include <vector> using namespace std; class DAG { private: vector<vector<int>> adjList; // 存储图的邻接表 vector<int> inDegree; // 存储每个节点的入度 public: // 构造函数 DAG(int numNodes) { adjList.resize(numNodes); inDegree.resize(numNodes, 0); } // 添加一条有向边 void addEdge(int u, int v) { adjList[u].push_back(v); inDegree[v]++; } // 拓扑排序 vector<int> topologicalSort() { vector<int> result; vector<bool> visited(adjList.size(), false); // 找到所有入度为0的节点 for (int i = 0; i < adjList.size(); i++) { if (inDegree[i] == 0 && !visited[i]) { visited[i] = true; result.push_back(i); // 更新相邻节点的入度 for (int j = 0; j < adjList[i].size(); j++) { int neighbor = adjList[i][j]; inDegree[neighbor]--; } // 重置i,从头开始找入度为0的节点 i = -1; } } return result; } }; int main() { DAG dag(6); dag.addEdge(0, 1); dag.addEdge(1, 2); dag.addEdge(1, 3); dag.addEdge(2, 4); dag.addEdge(3, 4); dag.addEdge(4, 5); vector<int> result = dag.topologicalSort(); for (int i = 0; i < result.size(); i++) { cout << result[i] << " "; } return 0; } ``` 在上面的代码中,我们定义了一个DAG类来表示有向无环图,它包含两个成员变量:邻接表 `adjList` 和每个节点的入度 `inDegree`。我们可以使用 `addEdge` 方法向图中添加一条有向边,并使用 `topologicalSort` 方法执行拓扑排序来得到一个可行的执行顺序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值