Antv G6 拖拽到已有节点上创建新节点,并避免节点重复

Antv G6 拖拽到已有节点上创建新节点,并避免节点重复

G6

使用 API

  • G6 坐标转换graph.getPointByClient(clientX, clientY)(x, y)
    可以将屏幕/页面坐标转换为渲染也就是画布坐标。

  • HTML 拖放 API:通过设置 HTMLdraggable 属性为 true,使元素可以被拖拽。这里可使用该 API 的 dragend 事件(当拖拽操作结束时触发),生成对应节点。

    例如,用户可使用鼠标选择可拖拽元素,将元素拖拽到可放置元素,并释放鼠标按钮以放置这些元素。拖拽操作期间,会有一个可拖拽元素的半透明快照跟随着鼠标指针。

  • G6 生成新节点graph.addItem(type, model, stack)
    新增元素(节点和边)。
    注意:将会直接使用 model 对象作为新增元素的数据模型,G6 内部可能会对其增加或修改一些必要的字段。若不希望原始参数被修改,建议在使用深拷贝后的 model

  • G6 查找节点graph.find(type, fn)
    根据具体规则查找单个元素。如果有符合规则的元素实例,则返回第一个匹配的元素实例,否则返回 undefined

    const findNode = graph.find('node', (node) => {
      return node.get('model').x === 100;
    });
    

步骤

  • 初始化 G6;
  • 拖拽按钮到已有节点上(:draggable="true");
  • 监听拖拽结束事件(@dragend="addNode(item.type, $event)");
  • 将页面坐标转换为拓扑区域坐标(this.graph.getPointByClient(e.x, e.y));
  • 获取被挂载的节点数据;
  • 获取新节点的坐标,并尽可能避免重复;
  • 生成新节点(this.graph.addItem('node', node); this.graph.addItem('edge', edge);

代码

<template>
  <div class="page">
    ...
    <!-- 图表区域 -->
    <div class="Graph-wrapper">
      <!-- 拖拽按钮 -->
      <div
        v-for="item in addNodeBtns"
        :key="item.type"
        class="item" 
       >
         <div 
           :class="['item-movement', item]"
           :draggable="true"
           @dragend="addNode(item.type, $event)"
         />
         <div>{{ item.name }}</div>
      </div>
      <!-- 拓扑图 -->
      <div id="graphContainer" style="width: 100%; height: 100%; position: absolute" />
    </div>
  </div>
</tempalte>

<script>
import G6 from "@antv/g6";

export default {
  name: 'Test',
  data() {
    return {
      ...
      addNodeBtns: [
        { type: 'a', name: 'testA' },
        { type: 'b', name: 'testB' },
      ],
    };
  },
  methods: {
    initGraph(data) {
      const container = document.getElementById('graphContainer');
      const graph = new G6.Graph({
	    container: 'graphContainer', // 图的 DOM 容器,可以传入该 DOM 的 id 或者直接传入容器的 HTML 节点对象。
	    width: container.clientWidth,
	    height: container.clientHeight,
	    animate: true, // 是否启用全局动画。
	    fitCenter: true, // 开启后,图将会被平移,图的中心将对齐到画布中心,但不缩放。优先级低于 fitView。
	    modes: {
	      default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
	    },
	    layout: {
          type: 'dagre', // 层次布局。
          rankdir: 'LR', // 可选,布局的方向, 从左至右布局(默认为图的中心)。
          align: 'DL', // 可选,节点对齐方式,对齐到左下角(默认为中间对齐)。
          nodesep: 20, // 可选,节点间距(px)。在 rankdir 为 'TB' 或 'BT' 时是节点的水平间距;在rankdir 为 'LR' 或 'RL' 时代表节点的竖直方向间距。
          ranksep: 50, // 可选,层间距(px)。在 rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。
          controlPoints: true, // 可选, 是否保留布局连线的控制点。默认值:false。
        },
	  });
	  graph.get('canvas').set('localRefresh', false); // 关掉局部渲染,防止在缩放和拖动中会出现轨迹的线条
	  graph.data(data);
	  graph.render();
	  ...
	  this.graph = graph;
	},
	addNode(type, e) {
	  // 将屏幕坐标转为拓扑区域坐标
	  const point = this.graph.getPointByClient(e.x, e.y);
	  // 获取挂载的节点
	  const currentMountedNode = this.graph.find('node', (node) => {
	    return (node.get('model').x >= point.x - 30 && node.get('model').x <= point.x + 30) && (node.get('model').y >= point.y - 30 && node.get('model').y <= point.y + 30);
	  });
	  if(!currentMountedNode?.get('model')){ return; };
	  ...
	  // 获取新节点的坐标
	  const coordinate = this.getNodeCoordinate(currentMountedNode.get('model').x + 150, currentMountedNode.get('model').y);
	  const node = {
	    id: uuid, // 建议使用 uuid,尽量避免重复。
	    label: type, // 元素的文本标签,有该字段时默认会渲染 label 。
	    type: type, // 元素的类型。
	    x: coordinate.x,
	    y: coordinate.y,
	    ...
	  };
	  // 生成从挂载节点到新节点的连线
	  const edge = {
	    source: currentMountedNode.get('model').id, 
	    target: node.id,
	    ...
	  };
	  this.graph.addItem('node', node);
	  this.graph.addItem('edge', edge);
	},
	// 尽可能不生成重复位置的节点
	getNodeCoordinate(x, y) {
	  let currentX = x;
	  let currentY = y;
	  // 获取当前节点位置附近是否已有节点,如果有,则重新生成坐标。
	  const node = this.findNodeByCoordinate(currentX, currentY);
	  if(!node?.get('model')){
	    return {x, y};
	  }
	  currentX = x + 70;
	  currentX = y + 50;
	  return this.getNodeCoordinate(currentX, currentY);
	},
	// 获取当前坐标附近的节点
	findNodeByCoordinate(x, y) {
	  const node = this.graph.find('node', (node) => {
	    return (node.get('model').x >= point.x - 50 && node.get('model').x <= point.x + 50) && (node.get('model').y >= point.y - 30 && node.get('model').y <= point.y + 30);
	  });
	  return node;
	},
  };
  ...
}
</script>

END

### STM32C8T6 单片机喇叭驱动示例代码 对于STM32C8T6单片机来说,要实现对喇叭的驱动通常会涉及到PWM信号生成以及DAC输出等功能来产生音频信号。下面是一个简单的例子,展示如何利用STM32CubeMX配置并编写一段用于播放简单音调的程序。 #### PWM方式生成方波作为基础频率源 ```c // 初始化TIM3定时器为PWM模式, 输出到PA6引脚上 __HAL_RCC_TIM3_CLK_ENABLE(); htim3.Instance = TIM3; htim3.Init.Prescaler = 79; // 设置预分频系数使得计数频率=APB1时钟/80 (假设系统主频72MHz) htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; // 自动重装载值决定周期长度 if (HAL_TIM_PWM_Init(&htim3) != HAL_OK){ Error_Handler(); } // 配置通道1(PA6)为PWM输出 TIM_OC_InitTypeDef sConfigOC; sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 500; // 初始占空比设置 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){ Error_Handler(); } ``` #### 使用DAC转换模拟电压给扬声器供电 为了获得更好的音质效果,还可以考虑使用内部DAC配合外部低通滤波电路来代替纯数字式的PWM方法: ```c // DAC初始化函数 static void MX_DAC_Init(void) { DAC_ChannelConfTypeDef sConfig; hdac.Instance = DAC; if (HAL_DAC_Init(&hdac) != HAL_OK) Error_Handler(); sConfig.DAC_Trigger = DAC_TRIGGER_NONE; sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK) Error_Handler(); } void Play_Tone(uint16_t frequencyHz,uint16_t durationMs) { uint32_t i,j; float t,freqScale,sampleValue; freqScale=(float)(SystemCoreClock)/(frequencyHz*2); // 计算每次改变幅度所需循环次数 for(i=0;i<durationMs*freqScale;i++) { sampleValue=sin(2*M_PI*(i%((int)freqScale))/freqScale)*127+128; __HAL_DAC_SET_VALUE(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,(uint32_t)sampleValue); for(j=0;j<(SystemCoreClock/(1000/frequencyHz));j++); } } ``` 上述代码片段展示了两种不同的方案来驱动喇叭发声,分别是通过PWM生成特定频率的声音[^1] 和借助DAC输出正弦波形以提高声音质量[^2] 。这两种技术都可以应用于基于STM32系列MCU的产品开发当中,在实际应用中可根据具体需求选择合适的方法。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值