【OpenHarmony/HarmonyOs 】数学曲线画板实战:圆、椭圆、双曲线、抛物线的参数化绘制
【OpenHarmony/HarmonyOs 】数学曲线画板实战:圆、椭圆、双曲线、抛物线的参数化绘制
项目类型:OpenHarmony / HarmonyOS ArkTS 数学学习应用
项目名称:数学视界
对应主题:悬浮导航栏、沉浸光感、全新视觉与交互体验等
关键词:ArkTS、Canvas、解析几何、参数化绘制、数学画板、端侧计算 📐
一、为什么这篇不写普通 Canvas,而写“数学曲线画板”?
之前很多 OpenHarmony 文章会写 Canvas 画线、画圆、画动画,但数学学习 App 里更有价值的是:把抽象公式变成可交互、可观察、可收藏的学习对象。
在“数学视界”项目中,CanvasBoard.ets 不只是一个涂鸦板,它更像一个解析几何实验台:
- ⭕ 圆:支持圆心、半径、方程、半径线;
- 📐 双曲线:支持横向/纵向、渐近线、顶点、焦点;
- ⬭ 椭圆:支持长短轴、中心、顶点;
- 🔻 抛物线:支持顶点、方向、参数;
- 📈 函数:支持表达式采样绘制;
- 🧭 坐标系:支持网格、坐标轴、标签、平移。
所以这篇文章重点不是“Canvas 怎么画圆”,而是讲如何把数学模型参数化,然后再映射到屏幕坐标中绘制出来。
二、整体状态设计:画板先保存数学对象
画板页里维护了两类核心数据:
@State shapes: DrawShape[] = []
@State functionGraphs: FuncGraph[] = []
@State modelList: MathModel[] = []
@State graphRange: DrawGraphRange = {
xMin: -10,
xMax: 10,
yMin: -10,
yMax: 10
}
这里的关键是:画板不是直接保存像素点,而是保存数学对象。
shapes保存用户手绘的线段、点;functionGraphs保存函数表达式;modelList保存圆、椭圆、双曲线、抛物线等参数模型;graphRange保存当前坐标视野。
这样做有一个非常大的好处:当用户缩放、平移、切换深色模式时,不需要重新生成业务数据,只需要根据当前坐标范围重新绘制。
三、坐标转换:数学坐标和屏幕坐标的桥梁
Canvas 绘制使用的是屏幕坐标,左上角是 (0, 0),向右为 x 正方向,向下为 y 正方向。数学坐标系通常以中心为原点,向上为 y 正方向。
所以画板必须先做坐标转换:
mathToCanvas(mathX: number, mathY: number): DrawPoint {
if (this.canvasWidth === 0) return { x: 0, y: 0 }
const x: number =
((mathX - this.graphRange.xMin) /
(this.graphRange.xMax - this.graphRange.xMin)) *
this.canvasWidth
const y: number =
((this.graphRange.yMax - mathY) /
(this.graphRange.yMax - this.graphRange.yMin)) *
this.canvasHeight
return { x, y }
}
反向转换则用于触摸操作:
canvasToMath(canvasX: number, canvasY: number): DrawPoint {
const mathX: number =
(canvasX / this.canvasWidth) *
(this.graphRange.xMax - this.graphRange.xMin) +
this.graphRange.xMin
const mathY: number =
this.graphRange.yMax -
(canvasY / this.canvasHeight) *
(this.graphRange.yMax - this.graphRange.yMin)
return { x: mathX, y: mathY }
}
这两个函数是整个数学画板的基础。只要坐标转换准确,后续的圆、椭圆、双曲线、函数图像都可以稳定绘制。
四、添加数学模型:从参数面板到 modelList
项目中通过 selectedTool 判断当前添加哪类模型:
private modelTools: DrawTool[] = [
{ id: 'circle', label: '圆', icon: '⭕' },
{ id: 'hyperbola', label: '双曲线', icon: '📐' },
{ id: 'ellipse', label: '椭圆', icon: '⬭' },
{ id: 'parabola', label: '抛物线', icon: '🔻' },
{ id: 'function', label: '函数', icon: '📈' },
]
当用户点击“添加模型”时,会把当前参数固化为一个 MathModel:
if (this.selectedTool === 'circle') {
const model: MathModel = {
id: this.newId(),
type: 'circle',
modelType: 'horizontal',
h: this.circle_h,
k: this.circle_k,
r: this.circle_r,
a: 0,
b: 0,
color: this.circleColor,
strokeWidth: this.strokeWidth,
filled: this.isFilled,
dashed: this.isDashed,
showEquation: this.circleShowEq,
showCenter: this.circleShowCenter,
showRadius: this.circleShowRadius,
showVertices: false,
showAsymptotes: false,
showFoci: false,
}
this.modelList.push(model)
}
圆需要的核心参数是:
h:圆心 x 坐标;k:圆心 y 坐标;r:半径;showEquation:是否显示方程;showCenter:是否显示圆心;showRadius:是否显示半径线。
这比直接在 Canvas 上画一次圆更灵活,因为模型对象可以删除、重绘、收藏、分享。
五、圆的绘制:标准方程可视化
圆的标准方程是:
(x - h)^2 + (y - k)^2 = r^2
代码中先把圆心和半径转换成 Canvas 坐标:
drawCircleModel(ctx: CanvasRenderingContext2D, model: MathModel): void {
const c: DrawPoint = this.mathToCanvas(model.h, model.k)
const rPx: number =
Math.abs(this.mathToCanvas(model.h + model.r, model.k).x - c.x)
ctx.strokeStyle = model.color
ctx.fillStyle = model.color
ctx.lineWidth = model.strokeWidth
ctx.beginPath()
ctx.arc(c.x, c.y, rPx, 0, Math.PI * 2)
ctx.stroke()
}
这里的 rPx 很有意思:它不是直接把数学半径当像素,而是通过 mathToCanvas(model.h + model.r, model.k) 计算出半径对应的屏幕长度。这样当坐标范围变化时,圆会自动缩放。
如果用户开启显示圆心:
if (model.showCenter) {
ctx.beginPath()
ctx.arc(c.x, c.y, 4, 0, Math.PI * 2)
ctx.fill()
}
如果开启显示半径:
if (model.showRadius) {
const re: DrawPoint = this.mathToCanvas(model.h + model.r, model.k)
ctx.setLineDash([4, 2])
ctx.beginPath()
ctx.moveTo(c.x, c.y)
ctx.lineTo(re.x, re.y)
ctx.stroke()
ctx.setLineDash([])
}
这种设计对学生很友好:不仅看到圆,还能看到圆心、半径、方程之间的关系。
六、方程显示:让图像和公式互相对应
圆的方程由 buildCircleEq() 生成:
buildCircleEq(h: number, k: number, r: number): string {
let eq: string = ''
if (Math.abs(h) < 0.01) eq += 'x²'
else eq += `(x${h >= 0 ? '-' : '+'}${Math.abs(h).toFixed(1)})²`
eq += ' + '
if (Math.abs(k) < 0.01) eq += 'y²'
else eq += `(y${k >= 0 ? '-' : '+'}${Math.abs(k).toFixed(1)})²`
eq += ' = ' + r.toFixed(2) + '²'
return eq
}
这段代码看似只是字符串拼接,但对学习体验很重要。学生调整圆心和半径后,可以马上看到方程变化:
- 圆心在原点:
x² + y² = r² - 圆心右移:
(x-h)² + y² = r² - 圆心上移:
x² + (y-k)² = r²
这就是数学画板比静态公式表更有价值的地方。
七、双曲线、椭圆、抛物线:统一模型,不同绘制
添加双曲线时,参数是 h、k、a、b、modelType:
const model: MathModel = {
id: this.newId(),
type: 'hyperbola',
modelType: this.hyperbolaType,
h: this.hyperbola_h,
k: this.hyperbola_k,
r: 0,
a: this.hyperbola_a,
b: this.hyperbola_b,
color: this.hyperbolaColor,
strokeWidth: this.strokeWidth,
filled: false,
dashed: this.isDashed,
showEquation: this.hyperbolaShowEq,
showCenter: true,
showVertices: this.hyperbolaShowVertices,
showAsymptotes: this.hyperbolaShowAsymptotes,
showFoci: this.hyperbolaShowFoci,
}
模型结构保持统一,绘制时通过 type 分发:
drawModel(ctx: CanvasRenderingContext2D, model: MathModel): void {
switch (model.type) {
case 'circle':
this.drawCircleModel(ctx, model)
break
case 'hyperbola':
this.drawHyperbolaModel(ctx, model)
break
case 'ellipse':
this.drawEllipseModel(ctx, model)
break
case 'parabola':
this.drawParabolaModel(ctx, model)
break
}
}
这种“统一模型 + 分类绘制”的结构很适合继续扩展,比如后续增加:
- 直线;
- 抛物线标准式切换;
- 极坐标曲线;
- 参数方程;
- 圆锥曲线综合模式。
八、触摸交互:画板不是静态展示
画板也支持触摸绘制、选中、平移、橡皮擦。触摸事件会先转成数学坐标:
const touch: TouchObject = event.touches[0]
const canvasX: number = touch.x
const canvasY: number = touch.y
const mathPt: DrawPoint = this.canvasToMath(canvasX, canvasY)
this.cursorPos = {
x: canvasX,
y: canvasY,
mathX: mathPt.x,
mathY: mathPt.y
}
如果当前工具是平移:
const dx: number =
(canvasX - this.lastPanPos.x) /
this.canvasWidth *
(this.graphRange.xMax - this.graphRange.xMin)
this.graphRange.xMin -= dx
this.graphRange.xMax -= dx
这样平移改变的是坐标视野,而不是移动像素图层。这一点非常重要,因为数学画板应该始终以坐标系为中心,而不是以屏幕截图为中心。
九、学习数据联动:画图也算学习行为
当用户添加模型或完成绘制时,项目会记录学习行为:
AppState.recordDrawing()
this.redraw()
这让画板和首页学习进度、成就系统产生连接。用户不是孤立地画一个图,而是在完成一次“数学探究”。
十、总结
这篇文章对应的是“全新视觉与交互体验”主题,但它不是泛泛写 UI,而是聚焦数学项目特有的解析几何画板。
核心实现可以总结为:
- 📐 用
MathModel保存圆、椭圆、双曲线、抛物线参数; - 🧭 用
mathToCanvas()和canvasToMath()打通数学坐标和屏幕坐标; - ⭕ 用参数化方式绘制圆,并显示圆心、半径、方程;
- 📈 用统一
drawModel()分发不同曲线绘制; - 👆 用触摸事件支持平移、绘制、删除;
- 🎯 用
AppState.recordDrawing()接入学习统计。
数学类应用最吸引人的地方,不是把公式堆在页面上,而是让公式真正动起来、画出来、被操作。这个画板就是数学视界里最有辨识度的功能之一。🚀

更多推荐


所有评论(0)