【OpenHarmony/HarmonyOs 】数学曲线画板实战圆、椭圆、双曲线、抛物线的参数化绘制项目类型OpenHarmony / HarmonyOS ArkTS 数学学习应用项目名称数学视界对应主题悬浮导航栏、沉浸光感、全新视觉与交互体验等关键词ArkTS、Canvas、解析几何、参数化绘制、数学画板、端侧计算 一、为什么这篇不写普通 Canvas而写“数学曲线画板”之前很多 OpenHarmony 文章会写 Canvas 画线、画圆、画动画但数学学习 App 里更有价值的是把抽象公式变成可交互、可观察、可收藏的学习对象。在“数学视界”项目中CanvasBoard.ets不只是一个涂鸦板它更像一个解析几何实验台⭕ 圆支持圆心、半径、方程、半径线 双曲线支持横向/纵向、渐近线、顶点、焦点⬭ 椭圆支持长短轴、中心、顶点 抛物线支持顶点、方向、参数 函数支持表达式采样绘制 坐标系支持网格、坐标轴、标签、平移。所以这篇文章重点不是“Canvas 怎么画圆”而是讲如何把数学模型参数化然后再映射到屏幕坐标中绘制出来。二、整体状态设计画板先保存数学对象画板页里维护了两类核心数据Stateshapes: DrawShape[] []StatefunctionGraphs: FuncGraph[] []StatemodelList: MathModel[] []StategraphRange: 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}constx: number ((mathX -this.graphRange.xMin) / (this.graphRange.xMax -this.graphRange.xMin)) *this.canvasWidthconsty: number ((this.graphRange.yMax - mathY) / (this.graphRange.yMax -this.graphRange.yMin)) *this.canvasHeightreturn{ x, y } }反向转换则用于触摸操作canvasToMath(canvasX: number, canvasY: number): DrawPoint {constmathX: number (canvasX /this.canvasWidth) * (this.graphRange.xMax -this.graphRange.xMin) this.graphRange.xMinconstmathY: 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:}, ]当用户点击“添加模型”时会把当前参数固化为一个MathModelif(this.selectedTool circle) {constmodel: 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.colorctx.fillStyle model.colorctx.lineWidth model.strokeWidthctx.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.hmodel.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{leteq:stringif(Math.abs(h) 0.01)eqx²elseeq (x${h 0?-:}${Math.abs(h).toFixed(1)})²eq if(Math.abs(k) 0.01)eqy²elseeq (y${k 0?-:}${Math.abs(k).toFixed(1)})²eq r.toFixed(2) ²returneq}这段代码看似只是字符串拼接但对学习体验很重要。学生调整圆心和半径后可以马上看到方程变化圆心在原点x² y² r²圆心右移(x-h)² y² r²圆心上移x² (y-k)² r²这就是数学画板比静态公式表更有价值的地方。七、双曲线、椭圆、抛物线统一模型不同绘制添加双曲线时参数是h、k、a、b、modelTypeconstmodel: 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) { casecircle:this.drawCircleModel(ctx, model)breakcasehyperbola:this.drawHyperbolaModel(ctx, model)breakcaseellipse:this.drawEllipseModel(ctx, model)breakcaseparabola:this.drawParabolaModel(ctx, model)break} }这种“统一模型 分类绘制”的结构很适合继续扩展比如后续增加直线抛物线标准式切换极坐标曲线参数方程圆锥曲线综合模式。八、触摸交互画板不是静态展示画板也支持触摸绘制、选中、平移、橡皮擦。触摸事件会先转成数学坐标consttouch: TouchObject event.touches[0]constcanvasX: number touch.xconstcanvasY: number touch.yconstmathPt: DrawPoint this.canvasToMath(canvasX, canvasY)this.cursorPos { x: canvasX, y: canvasY, mathX: mathPt.x, mathY: mathPt.y }如果当前工具是平移constdx: number (canvasX -this.lastPanPos.x) /this.canvasWidth * (this.graphRange.xMax -this.graphRange.xMin)this.graphRange.xMin - dxthis.graphRange.xMax - dx这样平移改变的是坐标视野而不是移动像素图层。这一点非常重要因为数学画板应该始终以坐标系为中心而不是以屏幕截图为中心。九、学习数据联动画图也算学习行为当用户添加模型或完成绘制时项目会记录学习行为AppState.recordDrawing()this.redraw()这让画板和首页学习进度、成就系统产生连接。用户不是孤立地画一个图而是在完成一次“数学探究”。十、总结这篇文章对应的是“全新视觉与交互体验”主题但它不是泛泛写 UI而是聚焦数学项目特有的解析几何画板。核心实现可以总结为 用MathModel保存圆、椭圆、双曲线、抛物线参数 用mathToCanvas()和canvasToMath()打通数学坐标和屏幕坐标⭕ 用参数化方式绘制圆并显示圆心、半径、方程 用统一drawModel()分发不同曲线绘制 用触摸事件支持平移、绘制、删除 用AppState.recordDrawing()接入学习统计。数学类应用最吸引人的地方不是把公式堆在页面上而是让公式真正动起来、画出来、被操作。这个画板就是数学视界里最有辨识度的功能之一。
【OpenHarmony/HarmonyOs 】数学曲线画板实战:圆、椭圆、双曲线、抛物线的参数化绘制
【OpenHarmony/HarmonyOs 】数学曲线画板实战圆、椭圆、双曲线、抛物线的参数化绘制项目类型OpenHarmony / HarmonyOS ArkTS 数学学习应用项目名称数学视界对应主题悬浮导航栏、沉浸光感、全新视觉与交互体验等关键词ArkTS、Canvas、解析几何、参数化绘制、数学画板、端侧计算 一、为什么这篇不写普通 Canvas而写“数学曲线画板”之前很多 OpenHarmony 文章会写 Canvas 画线、画圆、画动画但数学学习 App 里更有价值的是把抽象公式变成可交互、可观察、可收藏的学习对象。在“数学视界”项目中CanvasBoard.ets不只是一个涂鸦板它更像一个解析几何实验台⭕ 圆支持圆心、半径、方程、半径线 双曲线支持横向/纵向、渐近线、顶点、焦点⬭ 椭圆支持长短轴、中心、顶点 抛物线支持顶点、方向、参数 函数支持表达式采样绘制 坐标系支持网格、坐标轴、标签、平移。所以这篇文章重点不是“Canvas 怎么画圆”而是讲如何把数学模型参数化然后再映射到屏幕坐标中绘制出来。二、整体状态设计画板先保存数学对象画板页里维护了两类核心数据Stateshapes: DrawShape[] []StatefunctionGraphs: FuncGraph[] []StatemodelList: MathModel[] []StategraphRange: 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}constx: number ((mathX -this.graphRange.xMin) / (this.graphRange.xMax -this.graphRange.xMin)) *this.canvasWidthconsty: number ((this.graphRange.yMax - mathY) / (this.graphRange.yMax -this.graphRange.yMin)) *this.canvasHeightreturn{ x, y } }反向转换则用于触摸操作canvasToMath(canvasX: number, canvasY: number): DrawPoint {constmathX: number (canvasX /this.canvasWidth) * (this.graphRange.xMax -this.graphRange.xMin) this.graphRange.xMinconstmathY: 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:}, ]当用户点击“添加模型”时会把当前参数固化为一个MathModelif(this.selectedTool circle) {constmodel: 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.colorctx.fillStyle model.colorctx.lineWidth model.strokeWidthctx.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.hmodel.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{leteq:stringif(Math.abs(h) 0.01)eqx²elseeq (x${h 0?-:}${Math.abs(h).toFixed(1)})²eq if(Math.abs(k) 0.01)eqy²elseeq (y${k 0?-:}${Math.abs(k).toFixed(1)})²eq r.toFixed(2) ²returneq}这段代码看似只是字符串拼接但对学习体验很重要。学生调整圆心和半径后可以马上看到方程变化圆心在原点x² y² r²圆心右移(x-h)² y² r²圆心上移x² (y-k)² r²这就是数学画板比静态公式表更有价值的地方。七、双曲线、椭圆、抛物线统一模型不同绘制添加双曲线时参数是h、k、a、b、modelTypeconstmodel: 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) { casecircle:this.drawCircleModel(ctx, model)breakcasehyperbola:this.drawHyperbolaModel(ctx, model)breakcaseellipse:this.drawEllipseModel(ctx, model)breakcaseparabola:this.drawParabolaModel(ctx, model)break} }这种“统一模型 分类绘制”的结构很适合继续扩展比如后续增加直线抛物线标准式切换极坐标曲线参数方程圆锥曲线综合模式。八、触摸交互画板不是静态展示画板也支持触摸绘制、选中、平移、橡皮擦。触摸事件会先转成数学坐标consttouch: TouchObject event.touches[0]constcanvasX: number touch.xconstcanvasY: number touch.yconstmathPt: DrawPoint this.canvasToMath(canvasX, canvasY)this.cursorPos { x: canvasX, y: canvasY, mathX: mathPt.x, mathY: mathPt.y }如果当前工具是平移constdx: number (canvasX -this.lastPanPos.x) /this.canvasWidth * (this.graphRange.xMax -this.graphRange.xMin)this.graphRange.xMin - dxthis.graphRange.xMax - dx这样平移改变的是坐标视野而不是移动像素图层。这一点非常重要因为数学画板应该始终以坐标系为中心而不是以屏幕截图为中心。九、学习数据联动画图也算学习行为当用户添加模型或完成绘制时项目会记录学习行为AppState.recordDrawing()this.redraw()这让画板和首页学习进度、成就系统产生连接。用户不是孤立地画一个图而是在完成一次“数学探究”。十、总结这篇文章对应的是“全新视觉与交互体验”主题但它不是泛泛写 UI而是聚焦数学项目特有的解析几何画板。核心实现可以总结为 用MathModel保存圆、椭圆、双曲线、抛物线参数 用mathToCanvas()和canvasToMath()打通数学坐标和屏幕坐标⭕ 用参数化方式绘制圆并显示圆心、半径、方程 用统一drawModel()分发不同曲线绘制 用触摸事件支持平移、绘制、删除 用AppState.recordDrawing()接入学习统计。数学类应用最吸引人的地方不是把公式堆在页面上而是让公式真正动起来、画出来、被操作。这个画板就是数学视界里最有辨识度的功能之一。