canvas绘制拓扑图

canvas坐标系统默认为左上角为原点,横轴x轴,向右为正,纵轴为y轴,向下为正的窗口坐标系统,可以对坐标系统坐标变换,平移、缩放、旋转、自定义变换方式等。

mousemove、mouseup和mousedown等事件获取到的是默认窗口坐标系统的事件位置。在获取绘画内容的位置时,需要转换后的坐标系统和默认窗口坐标系统转换计算。

canvas对于画笔样式使用save,restore栈的方式记录。

拓补图支持适配

通过初始化一个_w和_h的值,_w = 375, _h = 318,_unit = 1。监听页面的大小canvas的大小跟随变动,计算_unit的值。

js 复制代码
let _unit = 1;
function updateCanvas() {
  if (!ctx) return;
  const _w = 375;
  const _h = 318;
  if (canvas_w / _w > canvas_h / _h) {
    _unit = canvas_h / _h;
  } else {
    _unit = canvas_w / _w;
  }
  ctx.clearRect(0, 0, canvas_w, canvas_h);
  ctx.scale(_unit, _unit);
 
  ctx && draw(ctx);
}
鼠标在节点上展示手的形状

监听mousemove事件,如果鼠标在节点上显示手状。通过事件e.clientX和e.clientY的值,与节点在坐标轴上的转换值对比。

js 复制代码
function bindEvent() {
  canvas.value!.addEventListener('mousemove',(e: any) => {
    let mouseX = e.clientX;
    let mousY = e.clientY;
    checkMousePos(mouseX, mousY);
  })
}
function checkMousePos(mouseX: number, mouseY: number) {
  for (let i = 0 ; i < imgList.length; i++) {
    let canvasBox = canvas.value!.getBoundingClientRect();
   
    let minx = (imgList[i].x - imgWidth / 2 ) * _unit + canvas_w / 2 + canvasBox.left;
    let maxx = (imgList[i].x  + imgWidth / 2) * _unit + canvas_w / 2 + canvasBox.left;
    let miny = (imgList[i].y - imgWidth / 2)  * _unit + canvas_h / 2 + canvasBox.top ;
    let maxy = (imgList[i].y  + imgWidth / 2) * _unit + canvas_h / 2  + canvasBox.top;
    
    if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) {
      canvas.value &&(canvas.value.style.cursor = 'pointer');
      break
    } else {
      canvas.value &&(canvas.value.style.cursor = 'default');
    }
  }
}
鼠标在节点展示tooltip

原理和节点展示手状相同。

js 复制代码
function checkMousePos(mouseX: number, mouseY: number) {
  for (let i = 0 ; i < imgList.length; i++) {
 
     //....
    
        if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) 
    {
     let x = imgList[i].x + (mouseX - minx);
     let y = imgList[i].y + (mouseY - miny);
      drawToolTip('测试',x, y);
      break
    }
  }
}

vue组件代码:

js 复制代码
    <template>
        <div class="canvas" v-show="true" ref="canvasBox">
          <canvas id="canvas" ref="canvas"></canvas>
        </div>
    </template>
ts 复制代码
    <script>
    import { h, onMounted, onUnmounted, ref } from 'vue';
    
    const canvas = ref<HTMLCanvasElement | null>(null);
    const canvasBox = ref<HTMLElement | null>(null);
    let ctx: CanvasRenderingContext2D | null;
    let canvas_w: number = 0,
      canvas_h: number = 0;
    const imgWidth = 36;
    const imgList: imgList[] = [];
    
    function initCanvas() {
      ctx = canvas.value!.getContext('2d');
      setTimeout(() => {
        resize();
      });

      window.addEventListener('resize', debounceResize);
    }
    const debounceResize = debounce(resize, 500)
    function resize() {
      canvas_w = canvasBox.value!.offsetWidth;
      canvas_h = canvasBox.value!.offsetHeight;
      canvas.value!.width = canvas_w;
      canvas.value!.height = canvas_h;

      updateCanvas();
    }
    let _unit = 1;
    function updateCanvas() {
      if (!ctx) return;
      const _w = 375;
      const _h = 318;
      if (canvas_w / _w > canvas_h / _h) {
        _unit = canvas_h / _h;
      } else {
        _unit = canvas_w / _w;
      }
      ctx.clearRect(0, 0, canvas_w, canvas_h);
      ctx.translate((canvas_w) / 2, (canvas_h) / 2);
      ctx.scale(_unit, _unit);

      ctx && draw(ctx);
      bindEvent();
    }
    function bindEvent() {
      canvas.value!.addEventListener('mousemove',(e: any) => {
        let mouseX = e.clientX;
        let mousY = e.clientY;
        checkMousePos(mouseX, mousY);
      })
    }
    function checkMousePos(mouseX: number, mouseY: number) {
      for (let i = 0 ; i < imgList.length; i++) {
        let canvasBox = canvas.value!.getBoundingClientRect();

        let minx = (imgList[i].x - imgWidth / 2 ) * _unit + canvas_w / 2 + canvasBox.left;
        let maxx = (imgList[i].x  + imgWidth / 2) * _unit + canvas_w / 2 + canvasBox.left;
        let miny = (imgList[i].y - imgWidth / 2)  * _unit + canvas_h / 2 + canvasBox.top ;
        let maxy = (imgList[i].y  + imgWidth / 2) * _unit + canvas_h / 2  + canvasBox.top;

        if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) {
          canvas.value &&(canvas.value.style.cursor = 'pointer');
          // showErrorDetails();
          let x = imgList[i].x + (mouseX - minx);
          let y = imgList[i].y + (mouseY - miny);
          drawToolTip('测试',x, y);
          break
        } else {
          canvas.value &&(canvas.value.style.cursor = 'default');
        }
      }
    }
    function drawToolTip(txtLoc:string, x:number, y:number) {
      if (!ctx) return;
        ctx.save();

        var padding = 3;
        var font = "16px arial";
        ctx.font = font;
        ctx.textBaseline = 'bottom';
        ctx.fillStyle = 'yellow';

        //绘制ToolTip背景
        var width = ctx.measureText(txtLoc).width;
        var height = parseInt(font, 10);
        ctx.fillRect(x, y-height, width+padding*2, height+padding*2);

        //绘制ToolTip文字
        ctx.fillStyle = '#000';
        ctx.fillText(txtLoc, x+padding, y+padding);

        ctx.restore();
    }
    // function showErrorDetails() {
    //   isShowErrorDetails.value = true;
    // }
    function draw(ctx: CanvasRenderingContext2D) {
      ctx.save();
      ctx.fillStyle = 'rgb(16, 16, 20)';
      ctx.arc(0, 0, imgWidth / 2 + 3, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
      imgList.forEach((el: imgList) => {
        ctx.save();
        loadImage(ctx, el);
        ctx.fillStyle = '#fff';
        ctx.font = '11px serif';
        ctx.fillText(
          el.name,
          el.x - ctx.measureText(el.name).width / 2,
          imgWidth / 2 + el.y + 16,
        );
        ctx.restore();
      });
    }
    function loadImage(ctx: CanvasRenderingContext2D, imageInfo: imgList) {
      const img = new Image();
      img.src = imageInfo.state ? imageInfo.src : imageInfo.src_error;
      img.onload = () => {
        ctx.drawImage(
          img,
          imageInfo.x - imgWidth / 2,
          imageInfo.y - imgWidth / 2,
          imgWidth,
          imgWidth,
        );

        ctx.save();
        ctx.beginPath();
        if (imageInfo.id !== 'network') {
          ctx.globalCompositeOperation = 'destination-over';
          ctx.strokeStyle = '#fff';

          if (imageInfo.state) {
            ctx.strokeStyle = '#1296DB';
          } else {
            ctx.strokeStyle = '#DF0B01';
          }
          ctx.lineWidth = 2;
          const info = { x: 0, y: 0 };
          if (imageInfo.x > 0) {
            info.x = -1;
          } else {
            info.x = 1;
          }

          if (imageInfo.y > 0) {
            info.y = -1;
          } else {
            info.y = 1;
          }

          ctx.moveTo(0, 0);
          ctx.lineTo(imageInfo.x, imageInfo.y + (info.y * imgWidth) / 2);
          ctx.stroke();
        }
        ctx.restore();
      };
    }

    onMounted(() => {
      initCanvas();
    });
    onUnmounted(() => {
      controller?.abort();
      window.removeEventListener('resize', resize);
    });
    </script>
相关推荐
氢灵子19 天前
Canvas 变换和离屏 Canvas 变换
前端·javascript·canvas
很甜的西瓜23 天前
typescript软渲染实现类似canvas的2d矢量图形引擎
前端·javascript·typescript·图形渲染·canvas
爱疯的iphone24 天前
保姆级Canvas入门指南!零基础也能让图形“动”起来 | 从画方块到炫酷粒子动画实战 🚀
canvas
全宝1 个月前
✏️Canvas实现环形文字
前端·javascript·canvas
yinshimoshen1 个月前
根据S-T教学分析法绘制图形-前端实现
前端·canvas
路很长OoO1 个月前
鸿蒙手写ECharts_手势惯性(条形统计图)
echarts·harmonyos·canvas
ncj3934379061 个月前
【第4章 图像与视频】4.6 结合剪辑区域来绘制图像
canvas
ncj3934379061 个月前
【第4章 图像与视频】4.5 操作图像的像素
canvas
ncj3934379061 个月前
【第1章 基础知识】1.8 在 Canvas 中使用 HTML 元素
canvas