使用Canvas绘制一个自适应长度的折线图

发布于:2024-03-11 ⋅ 阅读:(51) ⋅ 点赞:(0)

要求x轴根据数据长度自适应
y轴根据数据最大值取长度值
在这里插入图片描述

<template>
  <div ref="cvsContainer" class="cvs-container">
    <canvas ref="cvs" class="canvas"></canvas>
  </div>
</template>

<script setup>
import {computed, defineProps, onMounted, ref} from "vue";

onMounted(() => {
  initLine()
})

const data = defineProps({
  list: {
    type: Array,
    default: () => [1,2,3,4,5,6,7,8,9,10]
  }
});

const max = computed(() => {
  return Math.max(...data.list)
})
console.log(max.value)
const dataSize = computed(() => {
  return data.list.length
})
console.log(dataSize.value)

const cvs = ref(null);
const cvsContainer = ref(null);
const initLine = () => {
  const container = cvsContainer.value
  const width = container.offsetWidth
  const height = container.offsetHeight
  cvs.value.width = width
  cvs.value.height = height
  const ctx = cvs.value.getContext('2d')
  ctx.beginPath()
  ctx.moveTo(50, 50)
  ctx.lineTo(50, 400)
  ctx.lineTo(600, 400)
  ctx.stroke()

  // 画x刻度
  // x轴总长550,我们用数据总长度计算出每个刻度要画多长,这里刻度数量=数据长度,例如10cm想要分成2个刻度,那就是开头和结束两个刻度,就等于10个要分成一段也就是10/(2-1)
  const xScale = 550 / (dataSize.value-1);
  for (let i = 1; i <= dataSize.value; i++) {
    ctx.beginPath()
    ctx.strokeStyle = 'blue'
    ctx.lineWidth = 1
    ctx.moveTo(50 + i * xScale, 400)
    ctx.lineTo(50 + i * xScale, 390)
    ctx.stroke()
    ctx.fillText(i, 47 + (i-1) * xScale, 415,)
  }

  // 画y刻度
  // (我们只显示7个y轴刻度)我们计算出最大值分成7份每份有多长
  const yScale = (max.value / 7).toFixed(0);
  console.log('yScale', yScale)
  for (let i = 0; i <= 7; i++) {
    ctx.beginPath()
    ctx.strokeStyle = 'blue'
    ctx.lineWidth = 1
    ctx.moveTo(50, 400 - i * 50)
    ctx.lineTo(60, 400 - i * 50)
    ctx.stroke()
    ctx.fillText(i * yScale, 50 - max.value.toString().length * 8, 403 - i * 50,)
  }

  //  画折线
  for (let i = 0; i < data.list.length; i++) {
    setTimeout(() => {
      ctx.beginPath()
      ctx.strokeStyle = 'red'
      ctx.lineWidth = 1
      ctx.moveTo(50 + i * xScale, 400 - data.list[i] * 350 / max.value)
      ctx.lineTo(50 + (i + 1) * xScale, 400 - data.list[i + 1] * 350 / max.value)
      ctx.stroke()
      ctx.fillText(data.list[i], 45 + i * xScale, 400 - data.list[i] * 350 / max.value,)
    }, 500 / data.list.length * i)
  }
}
</script>

<style lang="scss" scoped>
.cvs-container {
  width: 1200px;
  height: 800px;
  background-color: white;
  border-radius: 15px;

  .canvas {

  }
}

</style>

增加了鼠标移动的数值提示框

在这里插入图片描述

<template>
  <div ref="cvsContainer" class="cvs-container">
    <canvas ref="cvs" class="canvas"></canvas>
    <div v-show="pageData.pointerShow" class="pointer" :style="`left:${pageData.pointerX}px;top: ${pageData.pointerY}px;`">
      {{pageData.pointer}}
    </div>
  </div>
</template>

<script setup>
import {computed, defineProps, onMounted, reactive, ref} from "vue";

onMounted(() => {
  initLine()
})

const data = defineProps({
  list: {
    type: Array,
    default: () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  },
  xAxios: {
    type: Array,
    default: () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  }
});

const pageData = reactive({
  pointer:0,
  pointerX:0,
  pointerY:0,
  pointerShow:false
});

const max = computed(() => {
  return Math.max(...data.list)
})
console.log(max.value)


const cvs = ref(null);
const cvsContainer = ref(null);
const initLine = () => {
  const container = cvsContainer.value
  const width = container.offsetWidth
  const height = container.offsetHeight
  cvs.value.width = width
  cvs.value.height = height
  const ctx = cvs.value.getContext('2d')
  ctx.beginPath()
  ctx.moveTo(50, 50)
  ctx.lineTo(50, 400)
  ctx.lineTo(600, 400)
  ctx.stroke()

  // 画x刻度
  // x轴总长550,我们用数据总长度计算出每个刻度要画多长,这里刻度数量=数据长度,例如10cm想要分成2个刻度,那就是开头和结束两个刻度,就等于10个要分成一段也就是10/(2-1)
  const xScale = 550 / (data.xAxios.length - 1);
  for (let i = 0; i < data.xAxios.length; i++) {
    ctx.beginPath()
    ctx.strokeStyle = '#66666666'
    ctx.lineWidth = 0.5
    ctx.moveTo(50 + i * xScale, 400)
    ctx.lineTo(50 + i * xScale, 50)
    ctx.stroke()
    ctx.fillText(data.xAxios[i], 47 + i * xScale, 415,)
  }

  // 画y刻度
  // (我们只显示7个y轴刻度)我们计算出最大值分成7份每份有多长
  const yScale = (max.value / 7);
  console.log('yScale', yScale)
  for (let i = 0; i <= 7; i++) {
    ctx.beginPath()
    ctx.strokeStyle = '#66666666'
    ctx.lineWidth = 0.5
    ctx.moveTo(50, 400 - i * 50)
    ctx.lineTo(600, 400 - i * 50)
    ctx.stroke()
    ctx.fillText((i * yScale).toFixed(0), 50 - max.value.toString().length * 8, 403 - i * 50,)
  }

  //  画折线
  for (let i = 0; i < data.list.length; i++) {
    // 这里使用定时器渲染,模拟动画
    setTimeout(() => {
      ctx.beginPath()
      ctx.strokeStyle = 'rgba(31,121,211,.7)'
      ctx.lineWidth = 2
      ctx.moveTo(50 + i * xScale, 400 - data.list[i] * 350 / max.value)
      ctx.lineTo(50 + (i + 1) * xScale, 400 - data.list[i + 1] * 350 / max.value)
      ctx.stroke()
      // 字体颜色
      ctx.fillText(data.list[i], 45 + i * xScale, 400 - data.list[i] * 350 / max.value)
    }, 500 / data.list.length * i) // 渲染总时长/数据长度=每个数据渲染时长,使用定时器模拟动画
  }

  // 获取元素的边界信息
  const rect = cvs.value.getBoundingClientRect();
  // 绑定鼠标移动事件
  cvs.value.addEventListener('mousemove', (e) => {
    // 计算鼠标在元素内部的相对位置
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    // 打印出相对位置
    if (x>50){
      const index = Math.round((x - 50) / xScale);
      // 四舍五入
      // 获取当前点的数据
      const value = data.list[index];
      // 更新提示框的值
      pageData.pointer = value
      pageData.pointerX=x+15
      pageData.pointerY=y+15
    }
  })
  cvs.value.addEventListener('mouseleave', () => {
    pageData.pointerShow = false;
  })
  cvs.value.addEventListener('mouseenter',()=>{
    pageData.pointerShow = true;
  })
}
</script>

<style lang="scss" scoped>
.cvs-container {
  width: 1200px;
  height: 800px;
  background-color: white;
  border-radius: 15px;
  position: relative;
  .canvas {

  }
  .pointer{
    position: absolute;
    width: 100px;
    height: 50px;
    font-size: 24px;
    border-radius: 10px;
    background-color: #0675c5;
    display: flex;
    justify-content: center;
    align-items: center;
    color: white;
    box-shadow: 3px 3px 6px  rgba(0, 0, 0, 0.3);
  }
}

</style>

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到