HARMONYOS应用实例262:函数图像变换
·
- 函数图像变换
- 功能:演示 y=f(x)y=f(x)y=f(x) 到 y=f(x+a)y=f(x+a)y=f(x+a) 的平移过程,对比“左加右减”。
演示 y=f(x) 到 y=f(x+a) 的平移过程
支持三种函数类型:一次函数、二次函数、正弦函数
可调节平移量a,范围从-5到5
提供动画演示平移过程
对比"左加右减"规律
实时显示变换规律和函数表达式
支持显示选项控制(原函数、变换函数、网格)
- 功能:演示 y=f(x)y=f(x)y=f(x) 到 y=f(x+a)y=f(x+a)y=f(x+a) 的平移过程,对比“左加右减”。
// 函数图像变换
// 功能:演示 y=f(x) 到 y=f(x+a) 的平移过程,对比"左加右减"
// 函数类型
interface FunctionType {
type: 'linear' | 'quadratic' | 'sine';
a: number;
b: number;
c: number;
}
@Entry
@Component
struct FunctionTransform {
@State canvasWidth: number = 350;
@State canvasHeight: number = 350;
@State showGrid: boolean = true;
@State showAxes: boolean = true;
@State showOriginal: boolean = true;
@State showTransformed: boolean = true;
@State showAnimation: boolean = false;
@State currentShift: number = 0;
@State targetShift: number = 0;
@State animationProgress: number = 0;
@State functionType: FunctionType = {
type: 'quadratic',
a: 1,
b: 0,
c: 0
};
build() {
Column({ space: 15 }) {
Text('函数图像变换')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
Column() {
Text('功能介绍')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('演示 y=f(x) 到 y=f(x+a) 的平移过程,对比"左加右减"规律,直观理解函数图像的水平平移变换')
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Center)
}
.width('100%')
.backgroundColor('#E3F2FD')
.borderRadius(10)
.padding(15)
Column({ space: 10 }) {
Text('函数图像')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
Canvas(this.canvasContext)
.width(this.canvasWidth)
.height(this.canvasHeight)
.backgroundColor('#FFFFFF')
.border({ width: 2, color: '#333' })
.borderRadius(10)
.onReady(() => {
this.drawGraph()
})
}
.width('100%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(10)
Column({ space: 10 }) {
Text('函数选择')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
Row({ space: 10 }) {
Button('一次函数')
.width('31%')
.height(40)
.fontSize(14)
.backgroundColor(this.functionType.type === 'linear' ? '#2196F3' : '#E0E0E0')
.onClick(() => {
this.functionType = {
type: 'linear',
a: 1,
b: 0,
c: 0
}
this.drawGraph()
})
Button('二次函数')
.width('31%')
.height(40)
.fontSize(14)
.backgroundColor(this.functionType.type === 'quadratic' ? '#2196F3' : '#E0E0E0')
.onClick(() => {
this.functionType = {
type: 'quadratic',
a: 1,
b: 0,
c: 0
}
this.drawGraph()
})
Button('正弦函数')
.width('31%')
.height(40)
.fontSize(14)
.backgroundColor(this.functionType.type === 'sine' ? '#2196F3' : '#E0E0E0')
.onClick(() => {
this.functionType = {
type: 'sine',
a: 1,
b: 1,
c: 0
}
this.drawGraph()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.width('90%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(10)
Column({ space: 10 }) {
Text('平移控制')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
Column({ space: 8 }) {
Text(`平移量 a: ${this.targetShift.toFixed(2)}`)
.fontSize(16)
.fontColor('#666666')
.fontWeight(FontWeight.Medium)
Slider({
value: this.targetShift,
min: -5,
max: 5,
step: 0.1
})
.width('100%')
.blockColor('#FF9800')
.trackColor('#E0E0E0')
.selectedColor('#FF9800')
.onChange((value: number) => {
this.targetShift = value
if (!this.showAnimation) {
this.currentShift = value
this.drawGraph()
}
})
}
.width('100%')
.padding(10)
.backgroundColor('#F5F5F5')
.borderRadius(8)
Row({ space: 10 }) {
Button('开始动画')
.width('48%')
.height(45)
.fontSize(16)
.backgroundColor('#4CAF50')
.onClick(() => {
this.startAnimation()
})
Button('重置')
.width('48%')
.height(45)
.fontSize(16)
.backgroundColor('#FF9800')
.onClick(() => {
this.reset()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.width('90%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(10)
Column({ space: 10 }) {
Text('变换规律')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
Column({ space: 8 }) {
Text(`原函数: y = ${this.getOriginalExpression()}`)
.fontSize(16)
.fontColor('#2196F3')
.textAlign(TextAlign.Center)
Text(`变换函数: y = ${this.getTransformedExpression()}`)
.fontSize(16)
.fontColor('#4CAF50')
.textAlign(TextAlign.Center)
if (this.targetShift > 0) {
Text(`平移规律: 向左平移 ${this.targetShift.toFixed(2)} 个单位`)
.fontSize(16)
.fontColor('#FF9800')
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
} else if (this.targetShift < 0) {
Text(`平移规律: 向右平移 ${Math.abs(this.targetShift).toFixed(2)} 个单位`)
.fontSize(16)
.fontColor('#FF9800')
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
} else {
Text(`平移规律: 无平移`)
.fontSize(16)
.fontColor('#999999')
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
}
Text(`"左加右减": a > 0 时向左,a < 0 时向右`)
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(15)
.backgroundColor('#E8F5E9')
.borderRadius(10)
}
.width('100%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(10)
Column({ space: 8 }) {
Text('显示选项')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Row({ space: 10 }) {
Row({ space: 5 }) {
Text('显示原函数')
.fontSize(14)
Text(this.showOriginal ? '✓' : '✗')
.fontSize(14)
.fontColor(this.showOriginal ? '#4CAF50' : '#F44336')
}
.onClick(() => {
this.showOriginal = !this.showOriginal
this.drawGraph()
})
Row({ space: 5 }) {
Text('显示变换函数')
.fontSize(14)
Text(this.showTransformed ? '✓' : '✗')
.fontSize(14)
.fontColor(this.showTransformed ? '#4CAF50' : '#F44336')
}
.onClick(() => {
this.showTransformed = !this.showTransformed
this.drawGraph()
})
Row({ space: 5 }) {
Text('显示网格')
.fontSize(14)
Text(this.showGrid ? '✓' : '✗')
.fontSize(14)
.fontColor(this.showGrid ? '#4CAF50' : '#F44336')
}
.onClick(() => {
this.showGrid = !this.showGrid
this.drawGraph()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
.width('90%')
.padding(10)
.backgroundColor('#F5F5F5')
.borderRadius(8)
Column({ space: 8 }) {
Text('使用说明')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text('• 选择函数类型(一次、二次、正弦)')
.fontSize(14)
.fontColor('#666666')
Text('• 调节平移量a,观察图像变化')
.fontSize(14)
.fontColor('#666666')
Text('• 点击"开始动画"演示平移过程')
.fontSize(14)
.fontColor('#666666')
Text('• 理解"左加右减"的规律')
.fontSize(14)
.fontColor('#666666')
Text('• a > 0 时向左平移,a < 0 时向右平移')
.fontSize(14)
.fontColor('#666666')
}
.width('95%')
.padding(12)
.backgroundColor('#FFF3E0')
.borderRadius(10)
}
.width('100%')
.height('100%')
.padding(10)
.justifyContent(FlexAlign.Start)
}
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D();
private startAnimation() {
this.showAnimation = true
this.animationProgress = 0
const startValue = this.currentShift
const endValue = this.targetShift
const totalSteps = 50
let step = 0
const animate = () => {
if (step <= totalSteps) {
this.animationProgress = step / totalSteps
this.currentShift = startValue + (endValue - startValue) * this.animationProgress
this.drawGraph()
step++
setTimeout(animate, 30)
} else {
this.showAnimation = false
}
}
animate()
}
private reset() {
this.targetShift = 0
this.currentShift = 0
this.animationProgress = 0
this.showAnimation = false
this.drawGraph()
}
private getOriginalExpression(): string {
switch (this.functionType.type) {
case 'linear':
return `${this.functionType.a.toFixed(2)}x + ${this.functionType.b.toFixed(2)}`
case 'quadratic':
return `${this.functionType.a.toFixed(2)}x² + ${this.functionType.b.toFixed(2)}x + ${this.functionType.c.toFixed(2)}`
case 'sine':
return `${this.functionType.a.toFixed(2)}sin(${this.functionType.b.toFixed(2)}x)`
}
}
private getTransformedExpression(): string {
const shiftValue = this.targetShift
const shiftStr = this.targetShift.toFixed(2)
const sign = shiftValue >= 0 ? '+' : ''
switch (this.functionType.type) {
case 'linear':
return `${this.functionType.a.toFixed(2)}(x ${sign}${shiftStr}) + ${this.functionType.b.toFixed(2)}`
case 'quadratic':
return `${this.functionType.a.toFixed(2)}(x ${sign}${shiftStr})² + ${this.functionType.b.toFixed(2)}(x ${sign}${shiftStr}) + ${this.functionType.c.toFixed(2)}`
case 'sine':
return `${this.functionType.a.toFixed(2)}sin(${this.functionType.b.toFixed(2)}(x ${sign}${shiftStr}))`
}
}
private drawGraph() {
const ctx = this.canvasContext
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
if (this.showGrid) {
this.drawGridLines(ctx)
}
if (this.showAxes) {
this.drawAxes(ctx)
}
if (this.showOriginal) {
this.drawFunction(ctx, 0, '#2196F3', '原函数')
}
if (this.showTransformed) {
this.drawFunction(ctx, this.currentShift, '#4CAF50', '变换函数')
}
}
private drawGridLines(ctx: CanvasRenderingContext2D) {
ctx.strokeStyle = '#E0E0E0'
ctx.lineWidth = 1
const centerX = this.canvasWidth / 2
const centerY = this.canvasHeight / 2
const scale = 30
for (let i = -5; i <= 5; i++) {
ctx.beginPath()
ctx.moveTo(centerX + i * scale, 0)
ctx.lineTo(centerX + i * scale, this.canvasHeight)
ctx.stroke()
}
for (let i = -5; i <= 5; i++) {
ctx.beginPath()
ctx.moveTo(0, centerY + i * scale)
ctx.lineTo(this.canvasWidth, centerY + i * scale)
ctx.stroke()
}
}
private drawAxes(ctx: CanvasRenderingContext2D) {
const centerX = this.canvasWidth / 2
const centerY = this.canvasHeight / 2
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
ctx.beginPath()
ctx.moveTo(centerX, 0)
ctx.lineTo(centerX, this.canvasHeight)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(0, centerY)
ctx.lineTo(this.canvasWidth, centerY)
ctx.stroke()
ctx.fillStyle = '#333'
ctx.font = '12px sans-serif'
ctx.fillText('x', this.canvasWidth - 15, centerY + 15)
ctx.fillText('y', centerX + 5, 15)
ctx.fillText('O', centerX + 5, centerY + 15)
const scale = 30
for (let i = -5; i <= 5; i++) {
if (i !== 0) {
ctx.fillText(i.toString(), centerX + i * scale - 3, centerY + 20)
ctx.fillText((-i).toString(), centerX - 15, centerY + i * scale + 3)
}
}
}
private drawFunction(ctx: CanvasRenderingContext2D, shift: number, color: string, label: string) {
const centerX = this.canvasWidth / 2
const centerY = this.canvasHeight / 2
const scale = 30
ctx.strokeStyle = color
ctx.lineWidth = 3
ctx.beginPath()
for (let x = -5; x <= 5; x += 0.05) {
let y: number
switch (this.functionType.type) {
case 'linear':
y = this.functionType.a * (x + shift) + this.functionType.b
break
case 'quadratic':
y = this.functionType.a * (x + shift) * (x + shift) + this.functionType.b * (x + shift) + this.functionType.c
break
case 'sine':
y = this.functionType.a * Math.sin(this.functionType.b * (x + shift))
break
}
const pixelX = centerX + x * scale
const pixelY = centerY - y * scale
if (x === -5) {
ctx.moveTo(pixelX, pixelY)
} else {
ctx.lineTo(pixelX, pixelY)
}
}
ctx.stroke()
ctx.fillStyle = color
ctx.font = '14px sans-serif'
ctx.fillText(label, 10, label === '原函数' ? 30 : 50)
}
}
更多推荐


所有评论(0)