函数图像绘制小工具

文章说明

方便绘制一些数学的基础图象,制作该款小工具,不过尚为雏形阶段,等待后续逐步完善

核心代码

采用canvas绘图实现,核心代码如下

<script setup>
import {onMounted, reactive} from "vue";
import {ElMessage} from "element-plus";

const data = reactive({
  visible: false,
  xStart: "-10",
  xEnd: "10",
  expression: "y=x",
  point: {
    x: "",
    y: "",
  },
  tagPointList: [
    {
      id: 1,
      x: 0,
      y: 0,
    }
  ]
});

let canvas;
let context;
const xList = [];
const yList = [];
let width;
let height;

onMounted(() => {
  canvas = document.getElementsByClassName("canvas")[0];
  width = canvas.width;
  height = canvas.height;
  context = canvas.getContext("2d");
});

function clearCanvas() {
  context.clearRect(0, 0, width, height);
}

function draw() {
  if (!data.expression.startsWith("y=")) {
    ElMessage({
      message: "表达式应以y=开头",
      type: 'warning',
    });
    return;
  }

  xList.length = 0;
  for (let i = parseFloat(data.xStart); i <= parseFloat(data.xEnd); i += 0.1) {
    xList.push(parseFloat(i.toFixed(2)));
  }

  yList.length = 0;
  for (let i = 0; i < xList.length; i++) {
    const expression = data.expression.replace("y=", "").replaceAll("-x", "-1*x").replaceAll("x", "(" + xList[i] + ")");
    yList.push(parseFloat(eval(expression).toFixed(2)));
  }

  // 绘制基准线、X轴、Y轴
  context.beginPath();
  context.strokeStyle = 'black';
  context.moveTo(20, height / 2);
  context.lineTo(width - 20, height / 2);
  context.moveTo(width / 2, 20);
  context.lineTo(width / 2, height - 20);
  context.stroke();
  const originX = width / 2;
  const originY = height / 2;

  // 绘制刻度
  const xValueList = [];
  const yValueList = [];
  for (let i = -10; i <= 10; i++) {
    xValueList.push(i);
  }
  for (let i = -7; i <= 7; i++) {
    yValueList.push(i);
  }
  const defaultTagPointList = [];
  for (let i = 0; i < xValueList.length; i++) {
    for (let j = 0; j < yValueList.length; j++) {
      defaultTagPointList.push({
        x: xValueList[i],
        y: yValueList[j],
      });
    }
  }

  // 计算单位长度表示的x、y坐标的距离
  context.strokeStyle = 'red';
  const eachLengthX = width / xValueList.length - 1;
  const eachLengthY = height / yValueList.length - 1;
  drawXValue(xValueList, originX, originY, eachLengthX);
  drawYValue(yValueList, originX, originY, eachLengthY);
  context.stroke();

  // 绘制每个点及每条线
  context.strokeStyle = 'black';
  for (let i = 0; i < xList.length - 1; i++) {
    const pos = getPos(xList[i], yList[i], originX, originY, eachLengthX, eachLengthY);
    context.moveTo(pos[0], pos[1]);
    const nextPos = getPos(xList[i + 1], yList[i + 1], originX, originY, eachLengthX, eachLengthY);
    context.lineTo(nextPos[0], nextPos[1]);

    // 绘制默认点
    if (isDefaultTagPoint(defaultTagPointList, xList[i], yList[i])) {
      context.arc(pos[0], pos[1], 3, 0, 2 * Math.PI);
    }
    if (i + 1 === xList.length - 1) {
      if (isDefaultTagPoint(defaultTagPointList, xList[i + 1], yList[i + 1])) {
        context.arc(nextPos[0], nextPos[1], 3, 0, 2 * Math.PI);
      }
    }
  }

  context.stroke();
  context.closePath();
}

function isDefaultTagPoint(defaultTagPointList, x, y) {
  for (let i = 0; i < defaultTagPointList.length; i++) {
    if (defaultTagPointList[i].x === x && defaultTagPointList[i].y === y) {
      return true;
    }
  }
  return false;
}

function drawXValue(xValueList, originX, originY, eachLengthX) {
  for (let i = 0; i < xValueList.length; i++) {
    if (xValueList[i] > 0) {
      context.moveTo(xValueList[i] * eachLengthX + originX, originY);
      context.lineTo(xValueList[i] * eachLengthX + originX, originY - 5);
      context.fillText(xValueList[i], xValueList[i] * eachLengthX + originX - 3, originY - 10);
    } else if (xValueList[i] < 0) {
      context.moveTo(xValueList[i] * eachLengthX + originX, originY);
      context.lineTo(xValueList[i] * eachLengthX + originX, originY + 5);
      context.fillText(xValueList[i], xValueList[i] * eachLengthX + originX - 5, originY + 20);
    }
  }
}

function drawYValue(yValueList, originX, originY, eachLengthY) {
  for (let i = 0; i < yValueList.length; i++) {
    if (yValueList[i] > 0) {
      context.moveTo(originX, yValueList[i] * eachLengthY + originY);
      context.lineTo(originX + 5, yValueList[i] * eachLengthY + originY);
      context.fillText(-yValueList[i] + "", originX - 15, yValueList[i] * eachLengthY + originY + 3);
    } else if (yValueList[i] < 0) {
      context.moveTo(originX, yValueList[i] * eachLengthY + originY);
      context.lineTo(originX - 5, yValueList[i] * eachLengthY + originY);
      context.fillText(-yValueList[i] + "", originX + 5, yValueList[i] * eachLengthY + originY + 3);
    }
  }
}

function getPos(x, y, originX, originY, eachLengthX, eachLengthY) {
  const posX = parseFloat((x * eachLengthX + originX).toFixed(2));
  const posY = parseFloat((-y * eachLengthY + originY).toFixed(2));
  return [posX, posY];
}

function addPoint() {
  const x = parseFloat(data.point.x);
  const y = parseFloat(data.point.y);

  let flag = false;
  for (let i = 0; i < data.tagPointList.length; i++) {
    if (data.tagPointList[i].x === x && data.tagPointList[i].y === y) {
      flag = true;
      break;
    }
  }
  if (!flag) {
    ElMessage({
      message: "该点已被标注",
      type: 'warning',
    });
    return;
  }
  data.tagPointList.push({
    id: data.tagPointList.length + 1,
    x: x,
    y: y
  });
  draw();
}

function removeItem(row) {
  let flag = false;
  for (let i = 0; i < data.tagPointList.length; i++) {
    if (data.tagPointList[i].x === row.x && data.tagPointList[i].y === row.y) {
      flag = true;
      data.tagPointList.splice(i, 1);
      break;
    }
  }
  if (!flag) {
    ElMessage({
      message: "该点未被标注",
      type: 'warning',
    });
    return;
  }
  draw();
}
</script>

<template>
  <div class="container">
    <el-row style="justify-content: center">
      <el-input v-model="data.xStart" placeholder="x起始值" style="width: 110px"/>
      <el-input v-model="data.xEnd" placeholder="x结束值" style="width: 110px; margin-left: 20px"/>
      <el-input v-model="data.expression" placeholder="函数表达式" style="width: 240px; margin-left: 20px"/>
      <el-button style="margin-left: 20px" type="primary" @click="draw">绘图</el-button>
      <div style="position: absolute; right: 0">
        <el-button type="primary" @click="clearCanvas">清空绘图内容</el-button>
      </div>
    </el-row>
    <el-row style="justify-content: center; margin-top: 20px">
      <el-input placeholder="x" style="width: 240px"/>
      <el-input placeholder="y" style="width: 240px; margin-left: 20px"/>
      <el-button style="margin-left: 20px" type="primary" @click="addPoint">标点</el-button>
      <div style="position: absolute; right: 0">
        <el-button type="primary" @click="data.visible = true">查看标点信息</el-button>
      </div>
    </el-row>

    <canvas class="canvas" height="700" width="1000"></canvas>
  </div>

  <el-dialog v-model="data.visible" title="标点信息" width="80%">
    <el-table :data="data.tagPointList" border>
      <el-table-column align="center" label="编号" prop="id"/>
      <el-table-column align="center" label="X坐标" prop="x"/>
      <el-table-column align="center" label="Y坐标" prop="y"/>
      <el-table-column align="center" label="操作">
        <template #default="scope">
          <el-button type="danger" @click="removeItem(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
</template>

<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.container {
  margin: 50px auto;
  width: fit-content;
}

.canvas {
  margin-top: 50px;
  box-shadow: 0 0 20px 3px #88888888;
}
</style>

效果展示

y=x^3
在这里插入图片描述

y=x^2
在这里插入图片描述

y=1/x
在这里插入图片描述

y=2^x
在这里插入图片描述

源码下载

函数图像绘制小工具

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值