1. 函数交点求法
  • 功能:同时绘制两个函数图像,高亮显示交点坐标,演示方程组的几何意义。同时绘制两个函数图像(一次函数、二次函数、反比例函数)
    支持三种函数类型的组合:一次函数与一次函数、一次函数与二次函数、一次函数与反比例函数、二次函数与二次函数、二次函数与反比例函数
    实时计算并高亮显示交点坐标
    演示方程组的几何意义
    可调节函数系数,观察交点变化
    支持显示选项控制(网格、坐标轴、交点)
    在这里插入图片描述
// 函数交点求法
// 功能:同时绘制两个函数图像,高亮显示交点坐标,演示方程组的几何意义

// 交点数据接口
interface IntersectionPoint {
  x: number;
  y: number;
}

// 函数类型
interface FunctionConfig {
  type: 'linear' | 'quadratic' | 'inverse';
  a: number;
  b: number;
  c: number;
  color: string;
  name: string;
}

@Entry
@Component
struct FunctionIntersection {
  @State canvasWidth: number = 350;
  @State canvasHeight: number = 350;
  @State showGrid: boolean = true;
  @State showAxes: boolean = true;
  @State showIntersections: boolean = true;
  @State intersections: IntersectionPoint[] = [];
  
  @State function1: FunctionConfig = {
    type: 'linear',
    a: 1,
    b: 0,
    c: 0,
    color: '#2196F3',
    name: 'f(x)'
  };
  
  @State function2: FunctionConfig = {
    type: 'quadratic',
    a: 1,
    b: 0,
    c: -4,
    color: '#4CAF50',
    name: 'g(x)'
  };
  
  build() {
    Column({ space: 15 }) {
      Text('函数交点求法')
        .fontSize(26)
        .fontWeight(FontWeight.Bold)
        .textAlign(TextAlign.Center)

      Column() {
        Text('功能介绍')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        Text('同时绘制两个函数图像,高亮显示交点坐标,演示方程组的几何意义')
          .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: 15 }) {
        Text('函数1设置')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
        
        Row({ space: 10 }) {
          Button('一次函数')
            .width('31%')
            .height(40)
            .fontSize(14)
            .backgroundColor(this.function1.type === 'linear' ? '#2196F3' : '#E0E0E0')
            .onClick(() => {
              this.function1.type = 'linear'
              this.updateIntersections()
            })
          
          Button('二次函数')
            .width('31%')
            .height(40)
            .fontSize(14)
            .backgroundColor(this.function1.type === 'quadratic' ? '#2196F3' : '#E0E0E0')
            .onClick(() => {
              this.function1.type = 'quadratic'
              this.updateIntersections()
            })
          
          Button('反比例函数')
            .width('31%')
            .height(40)
            .fontSize(14)
            .backgroundColor(this.function1.type === 'inverse' ? '#2196F3' : '#E0E0E0')
            .onClick(() => {
              this.function1.type = 'inverse'
              this.updateIntersections()
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)

        Column({ space: 8 }) {
          Text(`a值: ${this.function1.a.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.function1.a,
            min: -2,
            max: 2,
            step: 0.1
          })
            .width('100%')
            .blockColor('#2196F3')
            .trackColor('#E0E0E0')
            .selectedColor('#2196F3')
            .onChange((value: number) => {
              if (value === 0 && (this.function1.type === 'inverse' || this.function1.type === 'quadratic')) {
                value = 0.1
              }
              this.function1.a = value
              this.updateIntersections()
            })
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)

        Column({ space: 8 }) {
          Text(`b值: ${this.function1.b.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.function1.b,
            min: -10,
            max: 10,
            step: 0.1
          })
            .width('100%')
            .blockColor('#2196F3')
            .trackColor('#E0E0E0')
            .selectedColor('#2196F3')
            .onChange((value: number) => {
              this.function1.b = value
              this.updateIntersections()
            })
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)

        Column({ space: 8 }) {
          Text(`c值: ${this.function1.c.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.function1.c,
            min: -10,
            max: 10,
            step: 0.1
          })
            .width('100%')
            .blockColor('#2196F3')
            .trackColor('#E0E0E0')
            .selectedColor('#2196F3')
            .onChange((value: number) => {
              this.function1.c = value
              this.updateIntersections()
            })
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)
      }
      .width('90%')
      .padding(10)
      .backgroundColor('#FAFAFA')
      .borderRadius(10)

      Column({ space: 15 }) {
        Text('函数2设置')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
        
        Row({ space: 10 }) {
          Button('一次函数')
            .width('31%')
            .height(40)
            .fontSize(14)
            .backgroundColor(this.function2.type === 'linear' ? '#4CAF50' : '#E0E0E0')
            .onClick(() => {
              this.function2.type = 'linear'
              this.updateIntersections()
            })
          
          Button('二次函数')
            .width('31%')
            .height(40)
            .fontSize(14)
            .backgroundColor(this.function2.type === 'quadratic' ? '#4CAF50' : '#E0E0E0')
            .onClick(() => {
              this.function2.type = 'quadratic'
              this.updateIntersections()
            })
          
          Button('反比例函数')
            .width('31%')
            .height(40)
            .fontSize(14)
            .backgroundColor(this.function2.type === 'inverse' ? '#4CAF50' : '#E0E0E0')
            .onClick(() => {
              this.function2.type = 'inverse'
              this.updateIntersections()
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)

        Column({ space: 8 }) {
          Text(`a值: ${this.function2.a.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.function2.a,
            min: -2,
            max: 2,
            step: 0.1
          })
            .width('100%')
            .blockColor('#4CAF50')
            .trackColor('#E0E0E0')
            .selectedColor('#4CAF50')
            .onChange((value: number) => {
              if (value === 0 && (this.function2.type === 'inverse' || this.function2.type === 'quadratic')) {
                value = 0.1
              }
              this.function2.a = value
              this.updateIntersections()
            })
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)

        Column({ space: 8 }) {
          Text(`b值: ${this.function2.b.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.function2.b,
            min: -10,
            max: 10,
            step: 0.1
          })
            .width('100%')
            .blockColor('#4CAF50')
            .trackColor('#E0E0E0')
            .selectedColor('#4CAF50')
            .onChange((value: number) => {
              this.function2.b = value
              this.updateIntersections()
            })
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)

        Column({ space: 8 }) {
          Text(`c值: ${this.function2.c.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.function2.c,
            min: -10,
            max: 10,
            step: 0.1
          })
            .width('100%')
            .blockColor('#4CAF50')
            .trackColor('#E0E0E0')
            .selectedColor('#4CAF50')
            .onChange((value: number) => {
              this.function2.c = value
              this.updateIntersections()
            })
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)
      }
      .width('90%')
      .padding(10)
      .backgroundColor('#FAFAFA')
      .borderRadius(10)

      Column({ space: 10 }) {
        Text('交点分析')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
        
        if (this.intersections.length > 0) {
          Column({ space: 8 }) {
            Text(`找到 ${this.intersections.length} 个交点:`)
              .fontSize(16)
              .fontColor('#2196F3')
            
            ForEach(this.intersections, (point: IntersectionPoint, index: number) => {
              Text(`交点 ${index + 1}: (${point.x.toFixed(2)}, ${point.y.toFixed(2)})`)
                .fontSize(14)
                .fontColor('#4CAF50')
            })
            
            Text('几何意义: 交点坐标是方程组的解')
              .fontSize(14)
              .fontColor('#666666')
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#E8F5E9')
          .borderRadius(10)
        } else {
          Column() {
            Text('没有找到交点')
              .fontSize(16)
              .fontColor('#F44336')
            
            Text('尝试调整函数参数以获得交点')
              .fontSize(14)
              .fontColor('#666666')
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#FFEBEE')
          .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.showGrid ? '✓' : '✗')
              .fontSize(14)
              .fontColor(this.showGrid ? '#4CAF50' : '#F44336')
          }
          .onClick(() => {
            this.showGrid = !this.showGrid
            this.drawGraph()
          })
          
          Row({ space: 5 }) {
            Text('显示坐标轴')
              .fontSize(14)
            Text(this.showAxes ? '✓' : '✗')
              .fontSize(14)
              .fontColor(this.showAxes ? '#4CAF50' : '#F44336')
          }
          .onClick(() => {
            this.showAxes = !this.showAxes
            this.drawGraph()
          })
          
          Row({ space: 5 }) {
            Text('显示交点')
              .fontSize(14)
            Text(this.showIntersections ? '✓' : '✗')
              .fontSize(14)
              .fontColor(this.showIntersections ? '#4CAF50' : '#F44336')
          }
          .onClick(() => {
            this.showIntersections = !this.showIntersections
            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、b、c')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 观察函数图像的交点')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 交点坐标即为方程组的解')
          .fontSize(14)
          .fontColor('#666666')
      }
      .width('95%')
      .padding(12)
      .backgroundColor('#FFF3E0')
      .borderRadius(10)

      Row({ space: 10 }) {
        Button('重置')
          .width('100%')
          .height(50)
          .fontSize(16)
          .backgroundColor('#FF9800')
          .onClick(() => {
            this.reset()
          })
      }
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .padding(10)
    .justifyContent(FlexAlign.Start)
  }

  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D();

  private updateIntersections() {
    this.calculateIntersections()
    this.drawGraph()
  }

  private calculateIntersections() {
    this.intersections = []
    
    if (this.function1.type === 'linear' && this.function2.type === 'linear') {
      this.calculateLinearLinearIntersection()
    } else if (this.function1.type === 'linear' && this.function2.type === 'quadratic') {
      this.calculateLinearQuadraticIntersection()
    } else if (this.function1.type === 'quadratic' && this.function2.type === 'linear') {
      this.calculateLinearQuadraticIntersection(true)
    } else if (this.function1.type === 'linear' && this.function2.type === 'inverse') {
      this.calculateLinearInverseIntersection()
    } else if (this.function1.type === 'inverse' && this.function2.type === 'linear') {
      this.calculateLinearInverseIntersection(true)
    } else if (this.function1.type === 'quadratic' && this.function2.type === 'inverse') {
      this.calculateQuadraticInverseIntersection()
    } else if (this.function1.type === 'inverse' && this.function2.type === 'quadratic') {
      this.calculateQuadraticInverseIntersection(true)
    } else if (this.function1.type === 'quadratic' && this.function2.type === 'quadratic') {
      this.calculateQuadraticQuadraticIntersection()
    }
  }

  private calculateLinearLinearIntersection() {
    const a1 = this.function1.a
    const b1 = this.function1.b
    const c1 = this.function1.c
    const a2 = this.function2.a
    const b2 = this.function2.b
    const c2 = this.function2.c
    
    const denominator = a1 * b2 - a2 * b1
    if (denominator !== 0) {
      const x = (b1 * c2 - b2 * c1) / denominator
      const y = (a2 * c1 - a1 * c2) / denominator
      const point: IntersectionPoint = {
        x: x,
        y: y
      }
      this.intersections.push(point)
    }
  }

  private calculateLinearQuadraticIntersection(swap: boolean = false) {
    const linear = swap ? this.function2 : this.function1
    const quadratic = swap ? this.function1 : this.function2
    
    const a = quadratic.a
    const b = quadratic.b - linear.a
    const c = quadratic.c - linear.b
    
    const discriminant = b * b - 4 * a * c
    if (discriminant >= 0) {
      const x1 = (-b + Math.sqrt(discriminant)) / (2 * a)
      const y1 = linear.a * x1 + linear.b
      const point1: IntersectionPoint = {
        x: x1,
        y: y1
      }
      this.intersections.push(point1)
      
      if (discriminant > 0) {
        const x2 = (-b - Math.sqrt(discriminant)) / (2 * a)
        const y2 = linear.a * x2 + linear.b
        const point2: IntersectionPoint = {
          x: x2,
          y: y2
        }
        this.intersections.push(point2)
      }
    }
  }

  private calculateLinearInverseIntersection(swap: boolean = false) {
    const linear = swap ? this.function2 : this.function1
    const inverse = swap ? this.function1 : this.function2
    
    const a = linear.a
    const b = linear.b
    const k = inverse.a
    
    const quadraticA = a
    const quadraticB = b
    const quadraticC = -k
    
    const discriminant = b * b + 4 * a * k
    if (discriminant >= 0) {
      const x1 = (-b + Math.sqrt(discriminant)) / (2 * a)
      if (x1 !== 0) {
        const y1 = k / x1
        const point1: IntersectionPoint = {
          x: x1,
          y: y1
        }
        this.intersections.push(point1)
      }
      
      if (discriminant > 0) {
        const x2 = (-b - Math.sqrt(discriminant)) / (2 * a)
        if (x2 !== 0) {
          const y2 = k / x2
          const point2: IntersectionPoint = {
            x: x2,
            y: y2
          }
          this.intersections.push(point2)
        }
      }
    }
  }

  private calculateQuadraticInverseIntersection(swap: boolean = false) {
    const quadratic = swap ? this.function2 : this.function1
    const inverse = swap ? this.function1 : this.function2
    
    const a = quadratic.a
    const b = quadratic.b
    const c = quadratic.c
    const k = inverse.a
    
    for (let x = -10; x <= 10; x += 0.1) {
      if (x !== 0) {
        const y1 = a * x * x + b * x + c
        const y2 = k / x
        if (Math.abs(y1 - y2) < 0.1) {
          const point: IntersectionPoint = {
            x: x,
            y: y1
          }
          this.intersections.push(point)
        }
      }
    }
  }

  private calculateQuadraticQuadraticIntersection() {
    const a1 = this.function1.a
    const b1 = this.function1.b
    const c1 = this.function1.c
    const a2 = this.function2.a
    const b2 = this.function2.b
    const c2 = this.function2.c
    
    const a = a1 - a2
    const b = b1 - b2
    const c = c1 - c2
    
    if (a === 0) {
      if (b !== 0) {
        const x = -c / b
        const y = a1 * x * x + b1 * x + c1
        const point: IntersectionPoint = {
          x: x,
          y: y
        }
        this.intersections.push(point)
      }
    } else {
      const discriminant = b * b - 4 * a * c
      if (discriminant >= 0) {
        const x1 = (-b + Math.sqrt(discriminant)) / (2 * a)
        const y1 = a1 * x1 * x1 + b1 * x1 + c1
        const point1: IntersectionPoint = {
          x: x1,
          y: y1
        }
        this.intersections.push(point1)
        
        if (discriminant > 0) {
          const x2 = (-b - Math.sqrt(discriminant)) / (2 * a)
          const y2 = a1 * x2 * x2 + b1 * x2 + c1
          const point2: IntersectionPoint = {
            x: x2,
            y: y2
          }
          this.intersections.push(point2)
        }
      }
    }
  }

  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)
    }
    
    this.drawFunction(ctx, this.function1)
    this.drawFunction(ctx, this.function2)
    
    if (this.showIntersections) {
      this.drawIntersections(ctx)
    }
  }

  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, func: FunctionConfig) {
    const centerX = this.canvasWidth / 2
    const centerY = this.canvasHeight / 2
    const scale = 30
    
    ctx.strokeStyle = func.color
    ctx.lineWidth = 3
    ctx.beginPath()
    
    for (let x = -5; x <= 5; x += 0.1) {
      let y: number
      
      switch (func.type) {
        case 'linear':
          y = func.a * x + func.b
          break
        case 'quadratic':
          y = func.a * x * x + func.b * x + func.c
          break
        case 'inverse':
          if (x !== 0) {
            y = func.a / x
          } else {
            continue
          }
          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 = func.color
    ctx.font = '14px sans-serif'
    let equation: string
    switch (func.type) {
      case 'linear':
        equation = `${func.name} = ${func.a.toFixed(2)}x + ${func.b.toFixed(2)}`
        break
      case 'quadratic':
        equation = `${func.name} = ${func.a.toFixed(2)}x² + ${func.b.toFixed(2)}x + ${func.c.toFixed(2)}`
        break
      case 'inverse':
        equation = `${func.name} = ${func.a.toFixed(2)}/x`
        break
    }
    ctx.fillText(equation, 10, func === this.function1 ? 30 : 50)
  }

  private drawIntersections(ctx: CanvasRenderingContext2D) {
    const centerX = this.canvasWidth / 2
    const centerY = this.canvasHeight / 2
    const scale = 30
    
    this.intersections.forEach((point: IntersectionPoint) => {
      const pixelX = centerX + point.x * scale
      const pixelY = centerY - point.y * scale
      
      ctx.fillStyle = '#FF9800'
      ctx.beginPath()
      ctx.arc(pixelX, pixelY, 8, 0, 2 * Math.PI)
      ctx.fill()
      
      ctx.strokeStyle = '#FF9800'
      ctx.lineWidth = 2
      ctx.stroke()
      
      ctx.fillStyle = '#FF9800'
      ctx.font = '12px sans-serif'
      ctx.fillText(`(${point.x.toFixed(2)}, ${point.y.toFixed(2)})`, pixelX + 10, pixelY - 10)
    })
  }

  private reset() {
    this.function1 = {
      type: 'linear',
      a: 1,
      b: 0,
      c: 0,
      color: '#2196F3',
      name: 'f(x)'
    }
    
    this.function2 = {
      type: 'quadratic',
      a: 1,
      b: 0,
      c: -4,
      color: '#4CAF50',
      name: 'g(x)'
    }
    
    this.showGrid = true
    this.showAxes = true
    this.showIntersections = true
    this.updateIntersections()
  }
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐