antvis/G6

force2如何让节点保持不动 #4651

CodingJzy posted onGitHub

问题描述

接口返回的数据不变的,但是force渲染后效果不太好,我们是定时获取数据、渲染。导致每次定时获取到数据后会有个过度动画一样,一闪一闪的,不过force的节点位置是不会改变。 使用force2之后,渲染数据后不会闪一下,但是节点位置会变。

force效果

image

force2效果(可以看到刷新后,节点名称变了。而force是不变的)

image image

重现链接

1

重现步骤

<script lang="ts" setup>
import G6, { Graph } from '@antv/g6'
import { computed, onMounted, ref, watch } from 'vue'
import { useMonitorXdrStore } from '@/store/monitor/xdr'
import { storeToRefs } from 'pinia'
import { StatusHandler } from '@/tools/systemDashboard'
import { Substr } from '@/tools/tools'
import { monitorXdrServicesResp } from '@/types/monitor/xdr'

const { xdrServiceData } = storeToRefs(useMonitorXdrStore())
onMounted(() => {
  setTimeout(() => {
    initG6()
  }, 1000)
})

const emits = defineEmits(['nodeClick'])

const graphData = computed(() => {
  const nodes = xdrServiceData.value?.node.map((item) => {
    return {
      id: item.service_name,
      label: Substr(item.service_name, 10),
      type: 'node',
      version: item.version,
      business: item.namespace,
      commit_hash: item.commit_hash,
      style: {
        fill: StatusHandler(item.status).color,
        stroke: StatusHandler(item.status).color,
        // lineWidth: 2,
        // radius: 5,
      },
    }
  })

  const edges = xdrServiceData.value?.edge.map((item) => {
    return {
      source: item.source,
      target: item.target,
      type: 'circle-running',
      style: {
        stroke: StatusHandler(item.status).color,
        lineWidth: 2,
        endArrow: {
          path: 'M 0,0 L 8,4 L 8,-4 Z',
          fill: StatusHandler(item.status).color,
        },
        // curveOffset: 30,
      },
    }
  })
  return {
    nodes,
    edges,
  }
})

const graph = ref<Graph>()
const tooltipHandler = (obj: any) => {
  let base = `
<h4>APP信息:</h4>
APP名称:  <span style='color: red'>${obj.node}</span></br>`
  if (obj.business) {
    return (
      base + `业务线:  <span style='color: red'>${obj.business}</span></br>`
    )
  }
  if (obj.commit_hash) {
    return (
      base +
      `commitHash:  <span style='color: red'>${obj.commit_hash}</span></br>`
    )
  }
  if (obj.version) {
    return base + `版本:  <span style='color: red'>${obj.version}</span></br>`
  }

  return base
}
const initG6 = () => {
  if (xdrServiceData.value?.node.length == 0) {
    return
  }

  const tooltip = new G6.Tooltip({
    offsetX: 10,
    offsetY: 20,
    getContent(e: any) {
      const outDiv = document.createElement('div')
      outDiv.style.width = '200px'
      outDiv.innerHTML = tooltipHandler({
        node: e.item.getModel().id,
        business: e.item._cfg.model.business,
        commit_hash: e.item._cfg.model.commit_hash,
        version: e.item._cfg.model.version,
      })
      return outDiv
    },
    itemTypes: ['node'],
  })
  graph.value = new G6.Graph({
    plugins: [tooltip],
    container: 'mountNode',
    width: 1660,
    height: window.innerHeight - 260,
    // fitView: true, // 画布自适应
    fitCenter: true, // 画布居中
    // linkCenter:true ,
    defaultEdge: {
      style: {
        radius: 10,
        offset: 10,
        lineWidth: 2,
        stroke: 'steelblue',
      },
      type: 'circle-running',
    },
    defaultNode: {
      style: { fill: '#A9D18E', stroke: '#000', lineWidth: 1, radius: 5 },
      type: 'circle', // 圆形
      size: 50, // node 大小
      labelCfg: {
        //   position: 'bottom',
        //   offset: 5,
        style: {
          fontSize: 10,
        },
      },
    },
    // https://g6.antv.antgroup.com/manual/middle/states/mode
    modes: {
      // default: ['drag-node'],
      default: ['drag-canvas'],
    },
    // animate:false,
    layout: {
      type: 'force2',
      preventOverlap: true,
      linkDistance: 150,
      strictRadial: true,
    },
    //  https://g6.antv.antgroup.com/manual/middle/states/state#%E9%85%8D%E7%BD%AE-state-%E6%A0%B7%E5%BC%8F
    nodeStateStyles: {
      // 二值状态 hover 为 true 时的样式
      hover: {
        fill: '#FFF',
        stroke: '#40A9FF',
        labelCfg: {
          style: {
            fontSize: 100,
          },
        },
      },
    },

    // edgeStateStyles: {
    //   // 二值状态 hover 为 true 时的样式
    //   hover: {
    //     fill: '#40A9FF',
    //   },
    //
    // },
  })
  const lineDash = [4, 2, 1, 2]
  G6.registerEdge(
    'circle-running',
    {
      afterDraw(cfg: any, group: any) {
        // get the first shape in the group, it is the edge's path here=
        const shape = group.get('children')[0]
        let index = 0
        // Define the animation
        shape.animate(
          () => {
            index++
            if (index > 9) {
              index = 0
            }
            return {
              lineDash,
              lineDashOffset: -index,
            }
          },
          {
            repeat: true, // plugins executes the animation repeatly
            duration: 3000, // the duration for executing once
          },
        )
      },
    },
    'cubic', // extend the built-in edge 'cubic'
  )
  graph.value.on('node:dragstart', function (e) {
    graph.value?.layout()
    refreshDragedNodePosition(e)
  })
  graph.value.on('node:drag', function (e) {
    const forceLayout = graph.value?.get('layoutController').layoutMethods[0]
    forceLayout.execute()
    refreshDragedNodePosition(e)
  })
  graph.value.on('node:dragend', function (e: any) {
    e.item.get('model').fx = null
    e.item.get('model').fy = null
  })
  graph.value.on('node:click', async (evt: any) => {
    emits('nodeClick', evt.item._cfg.id)
  })
  // console.log(graphData)
  graph.value.data(graphData)
  graph.value.render()

  // this.setItemState2Offline("fc-gateway")
  // setItemState('fc-gateway', '内存占用高')
}

function refreshDragedNodePosition(e: any) {
  const model = e.item.get('model')
  model.fx = e.x
  model.fy = e.y
}
watch(
  () => graphData.value,
  (neVal) => {
    if (neVal) {
      graph.value?.read(graphData)
    }
  },
)
</script>

<template>
  <div>
    <div
      v-if="(xdrServiceData as monitorXdrServicesResp)?.node.length >0"
      id="mountNode"
    />
    <a-empty v-else style="width: 100%" />
  </div>
</template>

<style scoped></style>

预期行为

1

平台

  • 操作系统: [macOS, Windows, Linux, React Native ...]
  • 网页浏览器: [Google Chrome, Safari, Firefox]
  • G6 版本: [4.5.1 ... ]

屏幕截图或视频(可选)

1

补充说明(可选)

No response


节点名称咋会变呢,你是说节点位置变了?接口回来的节点顺序有变化吗?给个在线复现 demo 看看?

posted by Yanyan-Wang almost 2 years ago

节点名称咋会变呢,你是说节点位置变了?接口回来的节点顺序有变化吗?给个在线复现 demo 看看?

我也不知道呀,因为我们的节点都是有名字的,所以变了,但是force1就不会,

posted by CodingJzy almost 2 years ago

不过也奇怪,我在你们那个示例平台把下面的代码运行,然后每次运行,节点位置都是不动的,但是放到项目中,就会变 我在渲染后console打印了那个node的数据,发现里边的x|y轴都变化了。

import G6 from '@antv/g6';

const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
  container: 'container',
  width,
  height,
      fitView: true,  // 画布自适应
    fitCenter: true, // 画布居中
  defaultEdge: {
      style: {
        radius: 10,
        offset: 10,
        lineWidth: 2,
        stroke: 'steelblue',
      },
      type: 'circle-running',
    },
    defaultNode: {
      style: {fill: '#A9D18E', stroke: '#000', lineWidth: 1, radius: 5},
      type: 'circle', // 圆形
      // size: [48],  // node 大小
      labelCfg: {
        //   position: 'bottom',
        //   offset: 5,
        style: {
          fontSize: 10,
        },
      },
    },
  layout: {
    type: 'force2',
    preventOverlap: true,
    linkDistance: 150,
     strictRadial: true,
  },
  modes: {
    default: ['drag-canvas'],
  },
});

const data ={
  nodes: [
    {
      "id": "redis",
      "label": "redis"
    },
    {
      "id": "mongo"
    },
    {
      "id": "fc-tic",
      "server_id": "MacBook-Air.local",
      "label": "MacBook-Air.local",
      "namespace": "fc-tic",
      "status": 3
    },
    {
      "id": "fc-system"
    },
    {
      "id": "fc-statistics"
    },
    {
      "id": "node1",
      "label": "node1"
    },
    {
      "id": "node2",
      "label": "node2"
    },
    {
      "id": "node3",
      "label": "node3"
    },
    {
      "id": "node4",
      "label": "node4"
    },
    {
      "id": "node5",
      "label": "node5"
    },
    {
      "id": "node6",
      "label": "node6"
    },
    {
      "id": "node7",
      "label": "node7"
    },
    {
      "id": "node8",
      "label": "node8"
    },
    {
      "id": "node9",
      "label": "node9"
    },
    {
      "id": "node10",
      "label": "node10"
    },
    {
      "id": "fc-route"
    },
    {
      "id": "fc-robot-weak-cloud"
    },
    {
      "id": "fc-robot-permeate-cloud"
    },
    {
      "id": "fc-robot-asset-cloud"
    },
    {
      "id": "fc-report"
    },
    {
      "id": "fc-public"
    },
    {
      "id": "fc-plug-manage"
    },
    {
      "id": "fc-openapi"
    },
    {
      "id": "fc-intrusion"
    },
    {
      "id": "fc-gateway",
      "server_id": "fc-gateway-b95bbdbc5-wtm2r",
      "status": 1,
      "size": 50
    },
    {
      "id": "fc-auth"
    },
    {
      "id": "fc-asset"
    }
  ],
  edges: [
    {
      "source": "fc-tic",
      "target": "go_panic",
      "status": 1
    },
    {
      "source": "fc-tic",
      "target": "mongo",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-tic",
      "target": "redis",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-tic",
      "target": "web",
      "status": 3
    },
    {
      "source": "fc-gateway",
      "target": "fc-plug-manage",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "node1"
    },
    {
      "source": "fc-gateway",
      "target": "node2"
    },
    {
      "source": "fc-gateway",
      "target": "node3"
    },
    {
      "source": "fc-gateway",
      "target": "node4"
    },
    {
      "source": "fc-gateway",
      "target": "node5"
    },
    {
      "source": "fc-gateway",
      "target": "node6"
    },
    {
      "source": "fc-gateway",
      "target": "node7"
    },
    {
      "source": "fc-gateway",
      "target": "node8"
    },
    {
      "source": "fc-gateway",
      "target": "node9"
    },
    {
      "source": "fc-gateway",
      "target": "node10"
    },
    {
      "source": "fc-gateway",
      "target": "fc-statistics",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-route",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-report",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-public",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-openapi",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-system",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-asset",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-auth",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-robot-asset-cloud",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-robot-weak-cloud",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-intrusion",
      "status": 1,
      "target_is_service": true
    },
    {
      "source": "fc-gateway",
      "target": "fc-robot-permeate-cloud",
      "status": 1,
      "target_is_service": true
    }
  ]
}


    const nodes = data.nodes;
    // randomize the node size
    nodes.forEach((node) => {
      node.size = 50;
    });
    graph.data({
      nodes,
      edges: data.edges
    });
    graph.render();

    graph.on('node:dragstart', function (e) {
      graph.layout();
      refreshDragedNodePosition(e);
    });
    graph.on('node:drag', function (e) {
      const forceLayout = graph.get('layoutController').layoutMethods[0];
      forceLayout.execute();
      refreshDragedNodePosition(e);
    });
    graph.on('node:dragend', function (e) {
      e.item.get('model').fx = null;
      e.item.get('model').fy = null;
    });


if (typeof window !== 'undefined')
  window.onresize = () => {
    if (!graph || graph.get('destroyed')) return;
    if (!container || !container.scrollWidth || !container.scrollHeight) return;
    graph.changeSize(container.scrollWidth, container.scrollHeight);
  };

function refreshDragedNodePosition(e) {
  const model = e.item.get('model');
  model.fx = e.x;
  model.fy = e.y;
}
posted by CodingJzy almost 2 years ago

Yanyan-Wang

会不会是watch的原因? image

posted by CodingJzy almost 2 years ago

给 force2 配个 preset 看看,preset: { type: 'concentric' }

https://g6.antv.antgroup.com/api/graph-layout/force2#layoutcfgpreset

posted by Yanyan-Wang almost 2 years ago

This issue has been closed because it has been outdate for a long time. Please open a new issue if you still need help.

这个 issue 已经被关闭,因为 它已经过期很久了。 如果你仍然需要帮助,请创建一个新的 issue。

posted by github-actions[bot] 10 months ago

Fund this Issue

$0.00
Funded

Pull requests