函数图像绘制小工具
- 文章说明
- 核心代码
- 效果展示
- 源码下载
文章说明
方便绘制一些数学的基础图象,制作该款小工具,不过尚为雏形阶段,等待后续逐步完善
核心代码
采用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
源码下载
函数图像绘制小工具