Vue2 はバタフライを使用して有向非巡回グラフを描画するエクスペリエンスの概要

目次

1. 使用方法

1.1 キャンバス構成の設定方法 

1.2 Butterfly-Vue コンポーネントを初期化する方法

1.3 Butterfly-Vue コンポーネントのデータ構造

1.4 カスタム Node Vue コンポーネントの記述方法

2. ノードの位置をカスタマイズする

3. DAG を再描画したときにキャンバスのズームを元に戻す方法

4. ダグ再描画時にキャンバスのオフセットを元に戻す方法

5. ノードの可視性を制御する方法 Node

6. 再配置の実施方法


1. 使用方法

ノードの位置は、canvas.layout メソッドで計算できます。レイアウトの種類と、ノード間の水平方向の間隔と行間の垂直方向の間隔を定義するだけです。

1.1 キャンバス構成の設定方法 

canvasConfig: {
        disLinkable: false, // 可删除连线
        linkable: false, // 可连线
        draggable: true, // 可拖动
        zoomable: true, // 可放大
        moveable: true, // 可平移
        theme: {
          edge: {
            shapeType: 'AdvancedBezier',
            isExpandWidth: false,
          },
        },
        layout: {
          type: 'dagreLayout',
          options: {
            rankdir: 'TB',
            nodesep: 70,
            ranksep: 40,
            controlPoints: false,
          },
        },
 }

1.2 Butterfly-Vue コンポーネントを初期化する方法

Index.vueファイル内のテンプレートの記述方法を参照してください。

<template>
  <div class="canvas-box">
    <ButterflyVue ref="butterflyVue" :canvasData="renderData" :canvasConf="canvasConfig" @onLoaded="handleLoaded" />
  </div>
</template>

1.3 Butterfly-Vue コンポーネントのデータ構造

モックデータの書き方を参照してください。



import Node from '../Node.vue'

const renderData = {
    "nodes": [
        {
            "id": "200606",
            "label": "节点 1",
            "iconType": "icon-code",
            "status": "success",
            "render": Node
        },
        {
            "id": "200599",
            "label": "节点 2",
            "iconType": "icon-code",
            "status": "failed",
            "render": Node
        },
        {
            "id": "200609",
            "label": "节点 3",
            "iconType": "icon-code",
            "status": "init",
            "render": Node
        }
    ],
    "edges": [
        {
            "id": "0",
            "source": "200609",
            "target": "200606",
            "arrow": true,
            "type": "node"
        },
        {
            "id": "1",
            "source": "200609",
            "target": "200599",
            "arrow": true,
            "type": "node"
        },
        {
            "id": "2",
            "source": "200599",
            "target": "200606",
            "arrow": true,
            "type": "node"
        }
    ]
}

export default {
    renderData
}

1.4 カスタム Node Vue コンポーネントの記述方法

次のnode.vueファイルを参照してください。 

<template>
  <div
    :class="['dagre-node', hovered && 'hover']"
    :id="itemData.id"
    @mouseenter="onFocus"
    @mouseleave="onBlur"
  >
      <div :class="`icon-box ${itemData.className}`">
        <a-icon v-if="itemData.status === 'running'" class="iconfont" type="loading" />
        <i v-else :class="`iconfont ${itemData.iconType}`"></i>
      </div>
    <span class="text-box" :title="itemData.label">{
   
   { itemData.label }}</span>
  </div>
</template>

<script>

export default {
  props: {
    itemData: {
      type: Object,
      default: () => {},
    },
  },
  data() {
    return {
      hovered: false,
    }
  },
  mounted() {},
  methods: {
    onFocus() {
      this.hovered = true
    },
    onBlur() {
      this.hovered = false
    },
  },
}
</script>

<style lang="less" scoped>
.dagre-node {
  width: fit-content;

  .icon-box {
    width: 46px;
    height: 46px;
    display: flex;
    justify-content: center;
    align-items: center;
    line-height: 46px;
    box-sizing: content-box;
    margin: 0 auto;
    text-align: center;
    background: #438efd;
    border-radius: 50%;

    .iconfont {
      font-size: 28px;
      color: #fff;
    }
  }

  .text-box {
    height: 30px;
    width: 200px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    display: inline-block;
    padding: 0 10px;
    line-height: 30px;
    text-align: center;
    font-size: 14px;
  }
}

</style>

2. ノードの位置をカスタマイズする

事業背景:

ビジネスでは、ユーザーがモバイル ノードの位置をドラッグ アンド ドロップした後、ノードの位置をブラウザ キャッシュに保存し、新しく追加されたノードを自動的に計算する必要があります。

問題があります:

CanvasData が指定されている場合、直接定義されたノードの left および top 属性は無効であり、DAG のレンダリング後にノードを左および上の位置に移動しても効果はありません。

解決:

最初の一歩:

ノードの位置が移動されると、キャンバス イベントをリッスンし、現在のキャンバス内のすべてのノードの位置と ID をブラウザーのローカル キャッシュに書き込みます。コードは次のとおりです。

canvas.on('system.drag.end', (data) => {
    if(data.dragType === 'node:drag') {
        const graphData = _.cloneDeep(this.canvas.getDataMap().nodes.map((node) => {
            return {
              id: node.id,
              left: node.left,
              top: node.top
            }
        }))
        this.setLocalDagData(graphData)
    }
})

ステップ2:

ノードの位置を移動するコードを $nextTick に記述して、DAG グラフがロードされてレンダリングされていることを確認し、自動的に計算されたノードの位置でレイアウトした後、キャッシュに保存された左と上の位置に移動する必要があります。

次のコードを参照してください

watch: {
    data(newValue) {
    // 通过watch组件的props属性data的变化,实现重绘
    // 第一步,重新计算dag图的nodes和edges属性
      const _nodes = newValue.nodes.map((item) => {
        return {
          id: item.id.toString(),
          label: item.name,
          iconType: 'icon-code', // 或者 'icon-yinzi'
          factor_id: item.factor_id,
          schedule_id: item.schedule_id,
          status: item.status,
          render: item.render,
        }
      })
      const _edges = newValue.edges.map((item, index) => {
        return {
          id: index.toString(),
          source: item.source.toString(),
          target: item.dest.toString(),
          arrow: true,
          type: 'node',
        }
      })
      // 深拷贝nodes和edges,重新复制给renderData也就是butterfly-vue的数据canvasData属性
      // 只有这样,vue组件才能监听到canvasData属性的数据变化,引发重绘
      this.renderData = {
        nodes: _.cloneDeep(_nodes),
        edges: _.cloneDeep(_edges),
      }
      // 引用两次vue组件的redraw()方法,保证及时重绘更新
      this.$refs.butterflyVue.redraw()
      this.$nextTick(() => {
        this.$refs.butterflyVue.redraw()
        this.$nextTick(() => {
          // 在第二重绘redraw()完成之后,再移动节点到缓存里的位置
          this.canvas.nodes.forEach((node, index) => {
            const graphData = this.getLocalDagData()
            const target = graphData ? graphData.find((item) => { return item.id === node.id }) : null
            if(target) {
              this.canvas.nodes[index].moveTo(target.left, target.top)
            }
          })
        })
      })
    },
  },
methods: {
    getLocalDagData() {
      const dashboard_id = this.$route.params.id
      if(dashboard_id && localStorage.getItem(`dagData-${dashboard_id}`)) {
        return JSON.parse(localStorage.getItem(`dagData-${dashboard_id}`))
      }
      return null
    },
}

3. DAG を再描画したときにキャンバスのズームを元に戻す方法

事業背景:

データ更新は数秒ごとに発生する可能性があるため、DAG ダイアグラムには、タスクの実行ステータスのスケジュール レコードが表示されます。グラフを再描画するときは、ユーザー エクスペリエンスを最適化するために、キャンバス上のユーザーのズームを復元する必要があります。

実装のアイデア:

ここでは、butterfly-dag の 2 つの方法を使用する必要があります。公式ドキュメントを参照してください。

Canvas.getZoom()

機能: キャンバスのズームを取得します。

戻る

  • {float} - キャンバスのスケーリング (0 ~ 1)
getZoom = () => {}

Canvas.Zoom (スケール)

役割: キャンバスのズームを手動で設定します

パラメータ

  • {float} scale - 0 ~ 1 の間のスケーリング値
  • {function} callback - スケーリング後のコールバック
zoom = (scale) => {}

再描画前にキャンバスのズーム値をグローバル状態 this に保存します。再描画が完了したら、グローバル状態 this のキャンバスのズーム値を読み取り、キャンバスのズーム状態を復元します。次のコードを参照できます。

this.zoom = this.canvas.getZoom()
this.$refs.butterflyVue.redraw()
this.$nextTick(() => {
    this.canvas.zoom(this.zoom)
})

4. ダグ再描画時にキャンバスのオフセットを元に戻す方法

 事業背景:

データ更新は数秒ごとに発生する可能性があるため、DAG ダイアグラムにはタスクの実行ステータスのスケジュール記録が表示されます。チャートを再描画するときは、ユーザー エクスペリエンスを最適化するために、ユーザーのキャンバス上でのドラッグ アンド ドロップによって生成されたオフセットを復元する必要があります。この問題は拡張することもできます。つまり、キャンバスのオフセット、ズーム、移動後のノードの位置をブラウザーのキャッシュに保存したい場合は、ブラウザーのローカル キャッシュのナレッジ ポイントである localStorage を使用する必要があります。

実装のアイデア:

この問題の解決策は 3 番目の点と非常に似ており、butterfly-dag の 2 つの方法を使用する必要があります。公式ドキュメントを参照してください

Canvas.move (位置)

機能: キャンバスのオフセットを手動で設定します

パラメータ

  • {[x, y]} array - X、Y 座標
move = (postion) => {}

Canvas.getOffset()

機能: キャンバス全体の動きのオフセット値を取得

戻る

  • {[x, y]} - キャンバスのオフセット値
getOffset = () => {}

5. ノードの可視性を制御する方法 Node

事業背景:

グラフ内に十分な数のノードがある場合、すべてを表示すると冗長になったり、グラフの構造が乱雑になったり、ノードが小さすぎて区別できなくなったりします。そのため、ノードの状態に応じて一部のノードの表示/非表示を制御できるようにしたいと考えています。

解決:

ノードノードとエッジ情報を取得したら、すぐに条件を満たすノードをフィルタリングします。エッジも条件を満たすノードに接続されているエッジをフィルタリングする必要があることに注意してください。そうしないと、butterfly-vue は「ターゲット ノードまたはソース ノードが存在しません」というエラーを報告します

ノードの成功ステータスを制御する必要がある場合、グローバル変数この属性 HideSuccess を定義すると、ユーザーはその値を true/false の間で変更するように制御できます。データ ソース ノードと DAG グラフのエッジを取得した後、hideSuccess と node.status に従ってノードをフィルターし、フィルターされたノードにエッジのターゲット ノードとソース ノードが存在するかどうかを確認します。次に、それをデータに整理し、レンダリングのために Butterfly-Vue に渡します。

サンプルコード:

getSchedule(this.schedule_id).then((res) => {
    this.task_history = res
    const _nodes = this.task_history.nodes.filter((node) => {
        return !this.hideSuccess || node.status !== 'success'
    })
    const _edges = this.task_history.edges.filter((edge) => {
        const sourceNodeExist = !!_nodes.find((node) => { return node.id === edge.source })
        const destNodeExist = !!_nodes.find((node) => { return node.id === edge.dest })
        return sourceNodeExist && destNodeExist
    })
    this.dagData = {
        nodes: _nodes.map((node) => {
            node.render = Node
            node.factor_id = node.id
            node.schedule_id = this.task_history.id
            return node
        }),
        edges: _edges
    }
})

6. 再配置の実施方法

再レイアウトとは、キャンバスのズームやオフセット、ノードの位置の移動に対するユーザーの変更を削除することを意味し、直感的に次のコードを書きました。

initLayout() {
    this.canvas.move([0,0])
    this.canvas.zoom(1)
    // 重新计算节点位置,布局layout
    this.redraw(this.data)
}

質問:

上記のコードでは、キャンバスを初期状態にスケーリングする効果は得られません。

理由:

Butterfly-dag/src/canvas/baseCanvas.js にある Canvas.zoom (scale) のソースコードを読んだところ、このメソッドは第 2 引数としてコールバック関数も受け付けており、非同期で実行されることが分かりましたが、zoom メソッドを呼び出した直後に getZoom() メソッドを呼び出すと、ズーム値がまだ初期値の 1 に戻っていないことがわかります。

解決:

Canvas.zoom()とcanvas.move()の後にノード位置レイアウトを再計算するコードを記述し、$nextTick()のコールバック関数にラップします。

サンプルコード:

initLayout() {
    this.canvas.move([0,0])
    this.canvas.zoom(1)
    this.$nextTick(() => {
        // 待画布缩放和偏移量设置完,重新计算节点位置,布局layout
        this.redraw(this.data)
    })
}

 

おすすめ

転載: blog.csdn.net/valsedefleurs/article/details/130580071