vue canvas绘制信令图,动态显示标题、宽度、高度

需求:

1、 根据后端返回的数据,动态绘制出信令图
2、根据 dataStatus 返回值: 0 和 1, 判断 文字内容的颜色,0:#000,1:red
3.、根据 lineType 返回值: 0 和 1,  判断 箭头线的显示 是实线、虚线
4、根据返回的文字内容的换行符:“\r\n” 自动换行 (这步比较难,得计算高度)
最后的效果图大概是这样的:

 一、标题的动态获取


1-1、如果后端给你返回的标题是随机顺序的,这里需要根据全部标题数组做一下排序。
// 全部标题数组:
titletypeArr: ['ATP', 'MT', 'BTS', 'BSC', 'MSC', 'RBC'],


// 后端返回的随机标题数组: 
resultTitle: ['MT', 'ATP' ]


// 处理方法
this.typeArr = this.titletypeArr.filter(item => this.resultTitle.indexOf(item) !== -1)

 二、canvas绘制初始化

    // 初始化加载
    initData() {
      var mycanvas = document.getElementById("myCanvas");
      this.canvas = mycanvas;
      var context = mycanvas.getContext("2d");
      // 动态设置宽高一定要在 myCanvas 节点添加之后
      document.getElementById("myCanvas").width = this.typeArr.length * 320 - 120;
      document.getElementById("myCanvas").style.background = "#fff";
      const minHeight = window.innerHeight - 180;
      if (this.xlArr.length > 0) {
        document.getElementById("myCanvas").height =
          30 * this.xlArr.length + 80 < minHeight
            ? minHeight
            : 30 * this.xlArr.length + 80;
      } else {
        document.getElementById("myCanvas").height = minHeight;
      }
      var height = this.paddingTop + 62; // 初始值
      this.xlArr.map((v, i) => {
        const k = this.typeArr.indexOf(v.startDataDir);
        const j = this.typeArr.indexOf(v.endDataDir);
        context.font = '13px "微软雅黑"'; // 设置字体
        // 时间文字
        context.fillStyle = '#000' // 时间颜色
        context.fillText(v.recTime.split(' ')[1], 40, height);

        // 箭头
        this.paintArr(
          v,
          [this.gapX * k + this.paddingLeft, height],
          [this.gapX * j + this.paddingLeft, height],
          k < j ? "right" : "left",
          context
        );

        var maxWidth = 260; // 最大宽度,超过这个宽度会自动换行
        var words = v.showInfo.split("\r\n");

        // 文字自动换行
        this.wrapText(
          v,
          context,
          words,
          this.gapX * (k < j ? k : j) + this.paddingLeft,
          height - 10,
          maxWidth,
          this.lineHeight
        );

        if (i < this.xlArr.length - 1) {
          let nextWords = this.xlArr[i + 1].showInfo.split("\r\n");
          height += (this.lineHeight * (words.length + nextWords.length)) / 2 + 30;
        } else {
          height += this.lineHeight * words.length + 30;
        }
        // console.log(height, "height")
      })
      // 画虚线以及标题
      this.typeArr.map((v, i) => {
        this.paintText(context, v, i);
        setTimeout(() => {
          this.drawDashed(context, i);
        }, 300)
      })

      // document.getElementById('container').onscroll = (e) => {
      //   // console.log('e:', e.target)
      //   this.left = e.target.scrollLeft
      // }
      // 屏蔽所有页面 右键菜单
      // document.oncontextmenu = (event) => {
      //   event.returnValue = false
      // }
      // 屏蔽当前页面 右键菜单
      // document.getElementById('container').oncontextmenu = (event) => {
      //   event.returnValue = false
      // }
    }

三、绘制箭头

    // 箭头
    paintArr(item, s, t, direction, ctx) {
      ctx.beginPath()
      ctx.lineWidth = 1
      if (item.dataStatus == 1) {
        ctx.strokeStyle = 'red'
      } else {
        ctx.strokeStyle = '#000' // 箭头线的颜色
      }

      if (item.lineType === 1) {
        ctx.setLineDash([5, 2]); // 虚线
      }
      ctx.moveTo(s[0], s[1])
      ctx.lineTo(t[0], t[1])
      ctx.stroke()
      ctx.closePath()

      ctx.beginPath()
      if (direction === 'right') {
        ctx.moveTo(t[0] - 10, t[1] + 3)
        ctx.lineTo(t[0], t[1])
        ctx.lineTo(t[0] - 10, t[1] - 3)
      } else {
        ctx.moveTo(t[0] + 10, t[1] - 3)
        ctx.lineTo(t[0], t[1])
        ctx.lineTo(t[0] + 10, t[1] + 3)
      }
      // ctx.closePath()
      ctx.stroke()
      // ctx.fill()
    },

四、绘制 标题列的虚线

    // 标题列的虚线
    drawDashed(ctx, i) {
      ctx.beginPath()
      ctx.lineWidth = 1
      ctx.strokeStyle = '#696969' // '#FF8080'//虚线的颜色
      ctx.setLineDash([5, 2])
      ctx.moveTo(320 * i + this.paddingLeft, this.paddingTop + 40);
      ctx.lineTo(320 * i + this.paddingLeft, 400 * this.typeArr.length);
      ctx.fill()
      ctx.stroke()
      ctx.closePath()
    },

五、文字自动换行 遇到换行符换行

    // 文字自动换行 遇到换行符换行,并且超出最大宽度换行,只计算了最多显示7行的情况,超出7行得再计算
    wrapText(item, context, words, x, y, maxWidth, lineHeight) {
      // console.log(words, "words")
      let originY = y;
      let len = words.length;
      let rectWidth = 0;

      for (var n = 0; n < len; n++) {
        // 不超出一行
        var testWidth = context.measureText(words[n]).width;
        if (testWidth < maxWidth) {
          if (rectWidth < testWidth) {
            rectWidth = testWidth;
          }
        }
      }
      // 在上面循环计算出文字实际最宽的位置,画出背景色遮挡箭头
      // 画背景色遮盖箭头, 背景色自己调,跟画布统一就行
      context.fillStyle = "#fff"; // 背景颜色
      context.fillRect(
        x + this.gapX / 2 - rectWidth / 2 - 4,
        originY,
        rectWidth + 6,
        lineHeight
      ); // 填充黄色背景

      for (var n = 0; n < len; n++) {
        // 不超出一行
        var testWidth = context.measureText(words[n]).width;
        if (testWidth < maxWidth) {
          // console.log(words[n], 1);
          let currentY = y;
          if (len === 1) {
            currentY = y + 14;
          } else if (len === 2) {
            currentY = y + 2;
          } else if (len === 3) {
            currentY = y - 6;
          } else if (len === 4) {
            currentY = y - 18;
          } else if (len === 5) {
            currentY = y - 28;
          } else if (len === 6) {
            currentY = y - 38;
          } else if (len === 7) {
            currentY = y - 48;
          }

          if (item.dataStatus == 1) {
            context.fillStyle = 'red'
          } else {
            context.fillStyle = '#000' // 字体颜色
          }
          // context.fillStyle = "#000"; // 字体颜色
          context.fillText(words[n], x + this.gapX / 2 - testWidth / 2, currentY);
          if (len > 1) {
            y += lineHeight;
          }
        } else {
          console.log(words[n], 2);
          // 文字超出一行,需要换行展示
          // 实际大于页面width font-size: 12, 计算出显示多少行
          let singleWordwith = 13;
          // 计算一行显示的最大字数,以及显示多少行
          let len = Math.floor(maxWidth / singleWordwith);
          let lineCount = Math.ceil(words[n].length / len);
          for (let j = 0; j <= lineCount; j++) {
            // 截取出每行显示的字
            let word = words[n].substr(j * len, len);
            let wordWidth = context.measureText(word).width;
            // 写入画布
            // 画背景色遮盖箭头, 背景色自己调,跟画布统一就行
            context.fillStyle = "#fff";
            context.fillRect(
              x + this.gapX / 2 - wordWidth / 2,
              y - 4,
              wordWidth,
              lineHeight
            ); // 填充黄色背景
            let currentY = y;
            if (lineCount === 2) {
              currentY = y + 2;
            } else if (lineCount === 3) {
              currentY = y - 6;
            } else if (lineCount === 4) {
              currentY = y - 18;
            } else if (lineCount === 5) {
              currentY = y - 28;
            } else if (lineCount === 6) {
              currentY = y - 38;
            } else if (lineCount === 7) {
              currentY = y - 48;
            }
            if (item.dataStatus == 1) {
              context.fillStyle = 'red'
            } else {
              context.fillStyle = '#000' // 字体颜色
            }
            // context.fillStyle = "#000";
            context.fillText(word, x + this.gapX / 2 - wordWidth / 2, currentY);
            y += lineHeight; // 换行
          }
        }
      }
    },

六、模拟后端返回的数据

// signalTimeData: [
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: '2023-09-10 09:12:48',
      //     showInfo: "M136\r\nT_Train=9340940ms\r\nDT:16",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: '2023-09-10 09:12:49',
      //     showInfo: "M24\r\nT_Train=9341070ms",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: '2023-09-10 09:13:06',
      //     showInfo: "M136\r\nT_Train=9358940ms\r\nDT:19\r\n此时,M24之后ATP发送3条APDU",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 1
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: '2023-09-10 09:13:07',
      //     showInfo: "AK:20\r\n此时,M24之后RBC发送3条AK,无APDU",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 1
      //   },
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: '2023-09-10 09:13:08',
      //     showInfo: "TPDU_DR/SaPDU_D",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 1
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: '2023-09-10 09:13:09',
      //     showInfo: "TPDU_DC",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: "2023-09-10 09:13:09",
      //     showInfo: "DISC",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: "2023-09-10 09:13:09",
      //     showInfo: "NO CARRIER",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 0
      //   }
      // ],

 全部代码如下:

<template>
  <!-- :width="dialogWidth" -->
	<vxe-modal v-model="sigModal" :title="titles" width="1200" min-width="550" :height="dialogHeight" :position="{ top: '3vh' }" @close="closeEvent" resize destroy-on-close>
    <div class="con">
      <el-row style="margin-bottom:10px">
        <el-button type="primary" icon="el-icon-upload" @click="screenShot()">上传</el-button>
      </el-row>
      <div ref="screen" :style="{width: canvasWidth, height: canvasHeight}">
        <canvas id="myCanvas" :width="canvasWidth" :height="canvasHeight">
          你的浏览器还不支持canvas
        </canvas>
      </div>
    </div>
  </vxe-modal>
</template>

<script>
import html2canvas from 'html2canvas'
import { DownLoadFromTime } from '@/utils/times.js'
import { get_signallInfo } from '@/api/c3/offlineImportant.js'
import axios from 'axios'
export default {
  data() {
    return {
      uploadId: '',			
      titles: '信令图',
			dialogWidth: '90%',
      dialogHeight: '92%',
			sigModal: false,
      // 'ATP'-----'MT'------------ 'BTS'-------'BSC'----'MSC'------ 'RBC'
      //   Igsmr-R   Um_AMS/Um_BMS      Abis           A      PRI
      resultTitle: ['ATP', 'MT'], // 后台返回的随机顺序
      titletypeArr: ['ATP', 'MT', 'BTS', 'BSC', 'MSC', 'RBC'],
      typeArr: [],
      canvasWidth: '1080px',
      canvasHeight: (window.innerHeight) - 170 + 'px',
      minHeight: (window.innerHeight) - 170,
      // signalTimeData: [
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: '2023-09-10 09:12:48',
      //     showInfo: "M136\r\nT_Train=9340940ms\r\nDT:16",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: '2023-09-10 09:12:49',
      //     showInfo: "M24\r\nT_Train=9341070ms",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: '2023-09-10 09:13:06',
      //     showInfo: "M136\r\nT_Train=9358940ms\r\nDT:19\r\n此时,M24之后ATP发送3条APDU",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 1
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: '2023-09-10 09:13:07',
      //     showInfo: "AK:20\r\n此时,M24之后RBC发送3条AK,无APDU",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 1
      //   },
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: '2023-09-10 09:13:08',
      //     showInfo: "TPDU_DR/SaPDU_D",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 1
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: '2023-09-10 09:13:09',
      //     showInfo: "TPDU_DC",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "ATP",
      //     endDataDir: "MT",
      //     recTime: "2023-09-10 09:13:09",
      //     showInfo: "DISC",
      //     dataDir: 0,
      //     dataDirStr: "上行",
      //     lineType: 0,
      //     dataStatus: 0
      //   },
      //   {
      //     startDataDir: "MT",
      //     endDataDir: "ATP",
      //     recTime: "2023-09-10 09:13:09",
      //     showInfo: "NO CARRIER",
      //     dataDir: 1,
      //     dataDirStr: "下行",
      //     lineType: 0,
      //     dataStatus: 0
      //   }
      // ],
      xlArr: [],
      canvas: null,
      left: 0,
      paddingLeft: 120,
      paddingTop: 20,
      gapX: 320, // x轴间隔
      gapY: 90, // y轴间隔
      lineHeight: 20 // 行高
    }
  },
  created() {
    // this.typeArr = this.titletypeArr.filter(item => this.resultTitle.indexOf(item) !== -1)
    // this.xlArr = this.signalTimeData
    // this.canvasWidth = (this.typeArr.length * 320) - 120 + 'px'
    this.fnHight()
    window.addEventListener('resize', this.fnHight, true)
  },
  mounted() {},
  destroyed() {
    this.$destroy()
    window.removeEventListener('resize', this.fnHight, true)
  },
  methods: {
    // 获取信令图数据
    getSignallList(accidentId) {
      let parmsData = {
        accidentId: accidentId
      }
      get_signallInfo(parmsData).then(res => {
        if (res.status === 200) {
          if (res.data.code === 0) {
            if (res.data.data) {
              this.resultTitle = res.data.data.interfaceTypes
              this.typeArr = this.titletypeArr.filter(item => this.resultTitle.indexOf(item) !== -1)
              this.xlArr = res.data.data.mtDiagramInfoVoList
              this.canvasWidth = (this.typeArr.length * 320) - 120 + 'px'
            } else {
              this.$XModal.message({ content: '未查询到信令图数据.', status: 'warning' })
            }
          } else {
            this.$XModal.message({ content: res.message, status: 'error' })
          }
        }
      })
    },
    // 截屏
    screenShot() {
      html2canvas(this.$refs.screen, {
        backgroundColor: '#FFFFFF',
        useCORS: true
      }).then((canvas) => {
        // 获取到canvas
        canvas.toBlob(blob => {
          // 将二进制对象的内容 转成file
          const file = new File([blob], Date.now() + '.png', { type: 'image/png' })
          const formData = new FormData()
          formData.append('file', file)
          formData.append('uploadId', this.uploadId)
          // 发起请求
          axios({
            headers: {
              'Authorization': this.$store.state.user.token,
              'showInfo-Type': 'multipart/form-data'
            },
            method: 'post',
            url: '/api/offline/analysisContent/uploadReportPdf',
            data: formData,
          }).then((res) => {
            // 上传成功
            if (res.status === 200) {
              this.$XModal.message({ content: '上传成功', status: 'success'})
              this.$emit('sumitSuccess', 'ok')
            }
          }).catch((error) => {
            // 上传失败 执行对应操作
            this.$XModal.message({ content: '上传失败', status: 'error' })
          })
          
        }, 'image/png')

        if (navigator.msSaveBlob) { // IE10+ 
          let blob = canvas.msToBlob(); 
          return navigator.msSaveBlob(blob, name);
        } else {
          let imageurl = canvas.toDataURL('image/png')
          const newTime = DownLoadFromTime(new Date())
          //这里需要自己选择命名规则
          let imagename = '信令图_' + 'G1884' + '_' + newTime
          this.fileDownload(imageurl, imagename)
        }  
      })
    },
    // 下载截屏图片
    fileDownload(downloadUrl, downloadName) {
      let aLink = document.createElement("a")
      aLink.style.display = "none"
      aLink.href = downloadUrl
      aLink.download = `${downloadName}.png`
      // 触发点击-然后移除
      document.body.appendChild(aLink)
      aLink.click()
      document.body.removeChild(aLink)
    },
    closeEvent() {
      this.sigModal = false
    },
    fnHight() {
      setTimeout(() => {
        this.minHeight = (window.innerHeight) - 170
      }, 300)
    },
    // 标题的边框
    paintText(ctx, text, i) {
      // ctx.fillStyle = '#FEFECE'
      ctx.fillStyle = '#FFF'
      ctx.fillRect(320 * i + this.paddingLeft - 30, this.paddingTop, 60, 30); // 填充黄色背景
      // ctx.strokeStyle = 'red'
      ctx.strokeStyle = '#000' // '#A80036' // 标题的边框颜色
      ctx.strokeRect(320 * i + this.paddingLeft - 30, this.paddingTop, 60, 30); // 设置边框

      ctx.font = '12px "微软雅黑"' // 设置字体
      ctx.textBaseline = 'bottom' // 设置字体底线对齐绘制基线
      ctx.textAlign = 'left' // 设置字体对齐的方式
      ctx.fillStyle = 'Black'
      ctx.fillText(text, 320 * i + this.paddingLeft - 10, this.paddingTop + 22); // 填充文字标题
      // ctx.closePath()
    },
    // 箭头
    paintArr(item, s, t, direction, ctx) {
      ctx.beginPath()
      ctx.lineWidth = 1
      if (item.dataStatus == 1) {
        ctx.strokeStyle = 'red'
      } else {
        ctx.strokeStyle = '#000' // 箭头线的颜色
      }

      if (item.lineType === 1) {
        ctx.setLineDash([5, 2]); // 虚线
      }
      ctx.moveTo(s[0], s[1])
      ctx.lineTo(t[0], t[1])
      ctx.stroke()
      ctx.closePath()

      ctx.beginPath()
      if (direction === 'right') {
        ctx.moveTo(t[0] - 10, t[1] + 3)
        ctx.lineTo(t[0], t[1])
        ctx.lineTo(t[0] - 10, t[1] - 3)
      } else {
        ctx.moveTo(t[0] + 10, t[1] - 3)
        ctx.lineTo(t[0], t[1])
        ctx.lineTo(t[0] + 10, t[1] + 3)
      }
      // ctx.closePath()
      ctx.stroke()
      // ctx.fill()
    },
    // 标题列的虚线
    drawDashed(ctx, i) {
      ctx.beginPath()
      ctx.lineWidth = 1
      ctx.strokeStyle = '#696969' // '#FF8080'//虚线的颜色
      ctx.setLineDash([5, 2])
      ctx.moveTo(320 * i + this.paddingLeft, this.paddingTop + 40);
      ctx.lineTo(320 * i + this.paddingLeft, 400 * this.typeArr.length);
      ctx.fill()
      ctx.stroke()
      ctx.closePath()
    },
    // 文字自动换行 遇到换行符换行,并且超出最大宽度换行,只计算了最多显示7行的情况,超出7行得再计算
    wrapText(item, context, words, x, y, maxWidth, lineHeight) {
      // console.log(words, "words")
      let originY = y;
      let len = words.length;
      let rectWidth = 0;

      for (var n = 0; n < len; n++) {
        // 不超出一行
        var testWidth = context.measureText(words[n]).width;
        if (testWidth < maxWidth) {
          if (rectWidth < testWidth) {
            rectWidth = testWidth;
          }
        }
      }
      // 在上面循环计算出文字实际最宽的位置,画出背景色遮挡箭头
      // 画背景色遮盖箭头, 背景色自己调,跟画布统一就行
      context.fillStyle = "#fff"; // 背景颜色
      context.fillRect(
        x + this.gapX / 2 - rectWidth / 2 - 4,
        originY,
        rectWidth + 6,
        lineHeight
      ); // 填充黄色背景

      for (var n = 0; n < len; n++) {
        // 不超出一行
        var testWidth = context.measureText(words[n]).width;
        if (testWidth < maxWidth) {
          // console.log(words[n], 1);
          let currentY = y;
          if (len === 1) {
            currentY = y + 14;
          } else if (len === 2) {
            currentY = y + 2;
          } else if (len === 3) {
            currentY = y - 6;
          } else if (len === 4) {
            currentY = y - 18;
          } else if (len === 5) {
            currentY = y - 28;
          } else if (len === 6) {
            currentY = y - 38;
          } else if (len === 7) {
            currentY = y - 48;
          }

          if (item.dataStatus == 1) {
            context.fillStyle = 'red'
          } else {
            context.fillStyle = '#000' // 字体颜色
          }
          // context.fillStyle = "#000"; // 字体颜色
          context.fillText(words[n], x + this.gapX / 2 - testWidth / 2, currentY);
          if (len > 1) {
            y += lineHeight;
          }
        } else {
          console.log(words[n], 2);
          // 文字超出一行,需要换行展示
          // 实际大于页面width font-size: 12, 计算出显示多少行
          let singleWordwith = 13;
          // 计算一行显示的最大字数,以及显示多少行
          let len = Math.floor(maxWidth / singleWordwith);
          let lineCount = Math.ceil(words[n].length / len);
          for (let j = 0; j <= lineCount; j++) {
            // 截取出每行显示的字
            let word = words[n].substr(j * len, len);
            let wordWidth = context.measureText(word).width;
            // 写入画布
            // 画背景色遮盖箭头, 背景色自己调,跟画布统一就行
            context.fillStyle = "#fff";
            context.fillRect(
              x + this.gapX / 2 - wordWidth / 2,
              y - 4,
              wordWidth,
              lineHeight
            ); // 填充黄色背景
            let currentY = y;
            if (lineCount === 2) {
              currentY = y + 2;
            } else if (lineCount === 3) {
              currentY = y - 6;
            } else if (lineCount === 4) {
              currentY = y - 18;
            } else if (lineCount === 5) {
              currentY = y - 28;
            } else if (lineCount === 6) {
              currentY = y - 38;
            } else if (lineCount === 7) {
              currentY = y - 48;
            }
            if (item.dataStatus == 1) {
              context.fillStyle = 'red'
            } else {
              context.fillStyle = '#000' // 字体颜色
            }
            // context.fillStyle = "#000";
            context.fillText(word, x + this.gapX / 2 - wordWidth / 2, currentY);
            y += lineHeight; // 换行
          }
        }
      }
    },
    // 初始化加载
    initData() {
      var mycanvas = document.getElementById("myCanvas");
      this.canvas = mycanvas;
      var context = mycanvas.getContext("2d");
      // 动态设置宽高一定要在 myCanvas 节点添加之后
      document.getElementById("myCanvas").width = this.typeArr.length * 320 - 120;
      document.getElementById("myCanvas").style.background = "#fff";
      const minHeight = window.innerHeight - 180;
      if (this.xlArr.length > 0) {
        document.getElementById("myCanvas").height =
          30 * this.xlArr.length + 80 < minHeight
            ? minHeight
            : 30 * this.xlArr.length + 80;
      } else {
        document.getElementById("myCanvas").height = minHeight;
      }
      var height = this.paddingTop + 62; // 初始值
      this.xlArr.map((v, i) => {
        const k = this.typeArr.indexOf(v.startDataDir);
        const j = this.typeArr.indexOf(v.endDataDir);
        context.font = '13px "微软雅黑"'; // 设置字体
        // 时间文字
        context.fillStyle = '#000' // 时间颜色
        context.fillText(v.recTime.split(' ')[1], 40, height);

        // 箭头
        this.paintArr(
          v,
          [this.gapX * k + this.paddingLeft, height],
          [this.gapX * j + this.paddingLeft, height],
          k < j ? "right" : "left",
          context
        );

        var maxWidth = 260; // 最大宽度,超过这个宽度会自动换行
        var words = v.showInfo.split("\r\n");

        // 文字自动换行
        this.wrapText(
          v,
          context,
          words,
          this.gapX * (k < j ? k : j) + this.paddingLeft,
          height - 10,
          maxWidth,
          this.lineHeight
        );

        if (i < this.xlArr.length - 1) {
          let nextWords = this.xlArr[i + 1].showInfo.split("\r\n");
          height += (this.lineHeight * (words.length + nextWords.length)) / 2 + 30;
        } else {
          height += this.lineHeight * words.length + 30;
        }
        // console.log(height, "height")
      })
      // 画虚线以及标题
      this.typeArr.map((v, i) => {
        this.paintText(context, v, i);
        setTimeout(() => {
          this.drawDashed(context, i);
        }, 300)
      })

      // document.getElementById('container').onscroll = (e) => {
      //   // console.log('e:', e.target)
      //   this.left = e.target.scrollLeft
      // }
      // 屏蔽所有页面 右键菜单
      // document.oncontextmenu = (event) => {
      //   event.returnValue = false
      // }
      // 屏蔽当前页面 右键菜单
      // document.getElementById('container').oncontextmenu = (event) => {
      //   event.returnValue = false
      // }
    }
  }
}
</script>

<style lang="scss" scoped>
  .con {
    position: relative;
  }
  .topTitle{
    position: absolute;
    background: #fff;
    top: 38px;
    padding-left: 15px;
    // width: 2580px;
    display: flex;
    li {
      display: inline-block;
      width: 320px;
      margin: 3px 0;
    }
    li:nth-last-child(1) {
      width: 100px;
    }
    span {
      // border: 1px solid #A80036;
      border: 1px solid #000;
      padding: 5px 10px;
      // background-color: #FEFECE;
      background-color: #fff;
      display: inline-block;
    }
  }
  #container {
    // overflow-x: scroll;
    // overflow-y: scroll;
    overflow: hidden;
  }
  #canvas {
    display: block;
    background-color: #fff;
  }
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/530548.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

栈的详解和例题(力扣有效括号)

感谢各位大佬的光临&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 收入专栏:初阶数据结构_LaNzikinh篮子的博客-CSDN博客 文章目录 前言一.什么是栈二.栈的实现三.例题&#xff…

推荐系统(唐宇迪)含具体代码

一、推荐系统介绍 用户冷启动 1.1 经典流程 1.2 涉及的技术点 二、协同过滤与矩阵分解 2.1 基于物品流行度&#xff08;排行榜榜单&#xff09;的推荐算法 class popularity_recommender_py():def __init__(self):self.train_data Noneself.user_id Noneself.item_id None…

Java每日一题(三道同一类型的题)

前言 本文一共有三道题:1.两数之和 2.三数之和 3. 四数之和 为什么把这三道题放一起呢&#xff0c;因为三数之和是可以根据两数之和进行推导&#xff0c;四数之和可以根据三数之和进行推导。 两数之和 思路分析: 我的思路: 1.排序 2.使用左右指针 3.处理细节问题 先让数组…

人工智能——深度学习

4. 深度学习 4.1. 概念 深度学习是一种机器学习的分支&#xff0c;旨在通过构建和训练多层神经网络模型来实现数据的高级特征表达和复杂模式识别。与传统机器学习算法相比&#xff0c;深度学习具有以下特点&#xff1a; 多层表示学习&#xff1a;深度学习使用深层神经网络&a…

Java后端常见场景业务问题

目录 单点登录如何实现权限认证如何实现上传数据的安全性如何保证订单表每天新增500W数据,分库分表的方案应该如何设计?订单表每天新增500W数据,分库分表的方案应该如何设计?*********************项目日志如何采集已经上线的bug如何排查如何快速定位系统瓶颈单点登录如何实…

Golang使用PGO优化程序性能

文章目录 参考文章PGO是什么使用PGO的好处PGO做了什么热函数内联什么是内联内联的好处Go默认的内联策略PGO的热函数内联 去虚拟化调用指令高速缓存 PGO有什么缺点可执行程序变大构建时间变长 PGO怎么使用典型的工作流程收集CPU配置文件生产环境启动PGO代码改动重新生成CPU配置文…

基于Whisper语音识别的实时视频字幕生成 (一): 流式显示视频帧和音频帧

Whishow Whistream&#xff08;微流&#xff09;是基于Whisper语音识别的的在线字幕生成工具&#xff0c;支持rtsp/rtmp/mp4等视频流在线语音识别 1. whishow介绍 whishow&#xff08;微秀&#xff09;是在线音视频流播放python实现&#xff0c;支持rtsp/rtmp/mp4等输入&…

人工智能——大语言模型

5. 大语言模型 5.1. 语言模型历史 20世纪90年代以前的语言模型都是基于语法分析这种方法&#xff0c;效果一直不佳。到了20世纪90年代&#xff0c;采用统计学方法分析语言&#xff0c;取得了重大进展。但是在庞大而复杂的语言信息上&#xff0c;基于传统统计的因为计算量巨大…

【Docker】Docker概述及引擎

一、docker概述 DevOps DevOps是一种执行标准&#xff08;思想&#xff09;&#xff0c;主要用于促进开发、测试与运维的整合 容器与虚拟机的区别 最大的区别是&#xff0c;虚拟机中存在独立的硬件系统与操作系统&#xff0c;容器中全部是共享的宿主机中的操作系统与硬件系…

[dvwa] sql injection(Blind)

blind 0x01 low 1’ and length(version()) 6 # syntax: substr(string , from<start from 1>, cut length) 1’ and substr(version(),1,1) ‘5’ # 1’ and substr(version(),2,1) ‘.’ # 1’ and substr(version(),3,1) ‘7’ # 1’ and substr(version(),4,…

酷写写靠谱不 #知识分享#媒体

酷写写是一个值得推荐的论文写作工具&#xff0c;它不仅靠谱而且非常好用。在如今这个信息爆炸的时代&#xff0c;学术界对于论文的要求越来越严格&#xff0c;论文必须具有独创性和高质量才能获得认可。而酷写写的出现&#xff0c;为广大学生和学者提供了一个便捷、高效的写作…

SuperMap三维复杂模型建模之3D极坐标建模——原理篇

作者&#xff1a;超图研究院技术支持中心-于丁 随着SuperMap iDesktop 10i(2021) V10.2.1的上线发布&#xff0c;为进一步拓展全空间数据模型及其分析计算能力&#xff0c;一个新功能“3D极坐标建模”也随着该版本悄然上线。 3D极坐标建模功能实现根据UV参数和数学表达式&…

【Python系列】Jupyter Notebook 中执行 Shell 脚本的方法

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【随笔】Git 高级篇 -- 项目里程碑 git tag(二十)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

绝地求生:杜卡迪系列活动集合 各种活动送不停

今天更新已结束&#xff0c;速度上好开冲&#xff01; 春季签到活动&#xff08;第1轮&#xff09; 活动时间 4月9日 - 4月22日 12&#xff1a;00 任务要求 每日登录游戏&#xff08;每日上午10点刷新&#xff09; 活动期间全勤可获得奖励如下 杜卡迪物资箱 (x1) 杜卡迪活…

【C++杂货铺】详解 stack 和 queue

&#x1f308;前言&#x1f308; 欢迎收看本期【C杂货铺】&#xff0c;本期内容将讲解CSTL中stack和queue的内容&#xff0c;其中包含了stack &#xff0c; queue&#xff0c;priority_queue是什么&#xff0c;怎么使用以及模拟实现这些容器。 此外&#xff0c;还将将讲解设计模…

【迅为iMX6Q】开发板 Linux version 6.6.3 SD卡 启动

开发环境 win10 64位 VMware Workstation Pro 16 ubuntu 20.04 【迅为imx6q】开发板&#xff0c; 2G DDR RAM linux-imx 下载 使用 NXP 官方提供的 linux-imx&#xff0c;代码地址为&#xff1a; https://github.com/nxp-imx/linux-imx 使用 git 下载 linux-imx&#xff…

STM32学习和实践笔记(6):自己进行时钟配置的思路

在《STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (d)-CSDN博客》 中&#xff0c;我了解到&#xff0c;在程序执行我们写的main函数之前&#xff0c;实际上先执行了一个汇编语言所写的启动文件&#xff0c;以完成相应的初始…

python爬虫-----爬虫解析—xpath(第十八天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…