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

要求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>

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Canvas 绘制栅格曲线统计图,你可以创建一个栅格化的背景,并在其上绘制曲线。以下是一个示例代码,演示如何使用 Canvas 绘制栅格曲线统计图: ```html <!DOCTYPE html> <html> <head> <title>Canvas 栅格曲线统计图</title> </head> <body> <canvas id="myCanvas" width="600" height="400"></canvas> <script> const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); const data = [20, 50, 100, 80, 120, 200, 180]; // 统计数据 const gridSpacing = 50; // 栅格间距 const margin = 30; // 边距 const canvasWidth = canvas.width; const canvasHeight = canvas.height; // 绘制栅格背景 function drawGrid() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); // 绘制横向栅格线 ctx.strokeStyle = '#ccc'; ctx.lineWidth = 1; for (let y = margin; y < canvasHeight - margin; y += gridSpacing) { ctx.beginPath(); ctx.moveTo(margin, y); ctx.lineTo(canvasWidth - margin, y); ctx.stroke(); } // 绘制纵向栅格线 for (let x = margin; x < canvasWidth - margin; x += gridSpacing) { ctx.beginPath(); ctx.moveTo(x, margin); ctx.lineTo(x, canvasHeight - margin); ctx.stroke(); } } // 绘制曲线 function drawLineChart() { const maxValue = Math.max(...data); const scaleY = (canvasHeight - 2 * margin) / maxValue; // Y轴缩放比例 ctx.strokeStyle = 'blue'; ctx.lineWidth = 2; ctx.beginPath(); for (let i = 0; i < data.length; i++) { const x = margin + i * gridSpacing; const y = canvasHeight - margin - data[i] * scaleY; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } } ctx.stroke(); } // 调用绘制函数 drawGrid(); drawLineChart(); </script> </body> </html> ``` 在上面的示例代码中,我们首先创建了一个 Canvas 元素并获取了绘图上下文(2D)对象。然后定义了一个统计数据数组 `data`,表示每个数据点的值。`gridSpacing` 定义了栅格间距,`margin` 定义了边距。接下来,我们定义了两个函数:`drawGrid` 用于绘制栅格背景,`drawLineChart` 用于绘制曲线。在 `drawGrid` 函数中,我们使用循环绘制横向和纵向的栅格线。在 `drawLineChart` 函数中,我们根据数据点的值计算出 Y 坐标,并使用连线将数据点连接起来。 最后,我们调用 `drawGrid` 和 `drawLineChart` 函数来绘制栅格曲线统计图。你可以根据实际需要修改数据点和样式,以及其他绘图属性。希望这个示例对你有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值