1. 分段函数绘制
    • 功能:定义分段函数规则,自动绘制不连续的函数图像。
    • 支持创建多个分段函数,每个分段可以是不同类型
      支持三种函数类型:一次函数、二次函数、常量函数
      可调节每个分段的函数系数(a、b、c)
      可设置每个分段的定义域(起点和终点)
      可控制端点是否包含(开区间或闭区间)
      自动绘制不连续的函数图像
      支持显示选项控制(网格、坐标轴、标签)
      可编辑和删除已有分段
      在这里插入图片描述
// 分段函数绘制
// 功能:定义分段函数规则,自动绘制不连续的函数图像

// 分段数据接口
interface PiecewiseSegment {
  id: number;
  type: 'linear' | 'quadratic' | 'constant';
  a: number;
  b: number;
  c: number;
  start: number;
  end: number;
  includeStart: boolean;
  includeEnd: boolean;
  color: string;
}

// 端点数据接口
interface EndpointData {
  x: number;
  include: boolean;
}

@Entry
@Component
struct PiecewiseFunction {
  @State canvasWidth: number = 350;
  @State canvasHeight: number = 350;
  @State showGrid: boolean = true;
  @State showAxes: boolean = true;
  @State showLabels: boolean = true;
  @State segments: PiecewiseSegment[] = [];
  @State selectedSegmentId: number = -1;
  @State nextId: number = 1;
  
  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: 10 }) {
        Text('分段定义')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
        
        if (this.segments.length === 0) {
          Column() {
            Text('暂无分段,请添加分段')
              .fontSize(14)
              .fontColor('#999999')
          }
          .width('100%')
          .padding(20)
          .backgroundColor('#F5F5F5')
          .borderRadius(8)
        } else {
          Column({ space: 8 }) {
            ForEach(this.segments, (segment: PiecewiseSegment) => {
              Column({ space: 5 }) {
                Row({ space: 10 }) {
                  Text(`分段 ${segment.id}`)
                    .fontSize(14)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(segment.color)
                  
                  Text(this.getSegmentExpression(segment))
                    .fontSize(14)
                    .fontColor('#333333')
                    .layoutWeight(1)
                  
                  Text('删除')
                    .fontSize(12)
                    .fontColor('#F44336')
                    .onClick(() => {
                      this.deleteSegment(segment.id)
                    })
                }
                .width('100%')
                
                Text(`定义域: ${this.formatDomain(segment)}`)
                  .fontSize(12)
                  .fontColor('#666666')
              }
              .width('100%')
              .padding(10)
              .backgroundColor(segment.id === this.selectedSegmentId ? '#E3F2FD' : '#F5F5F5')
              .borderRadius(8)
              .border({ width: segment.id === this.selectedSegmentId ? 2 : 1, color: segment.color })
              .onClick(() => {
                this.selectedSegmentId = segment.id
              })
            })
          }
          .width('100%')
        }
        
        Button('添加分段')
          .width('100%')
          .height(40)
          .fontSize(14)
          .backgroundColor('#2196F3')
          .onClick(() => {
            this.addSegment()
          })
      }
      .width('90%')
      .padding(10)
      .backgroundColor('#FAFAFA')
      .borderRadius(10)

      if (this.selectedSegmentId !== -1) {
        this.SegmentEditor()
      }

      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.showLabels ? '✓' : '✗')
              .fontSize(14)
              .fontColor(this.showLabels ? '#4CAF50' : '#F44336')
          }
          .onClick(() => {
            this.showLabels = !this.showLabels
            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('• 选择分段类型(一次、二次、常量)')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 调整函数系数和定义域')
          .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 addSegment() {
    const colors = ['#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#F44336', '#00BCD4']
    const color = colors[(this.segments.length) % colors.length]
    
    const newSegment: PiecewiseSegment = {
      id: this.nextId,
      type: 'linear',
      a: 1,
      b: 0,
      c: 0,
      start: -2,
      end: 2,
      includeStart: true,
      includeEnd: false,
      color: color
    }
    
    this.segments.push(newSegment)
    this.selectedSegmentId = this.nextId
    this.nextId++
    this.drawGraph()
  }

  private deleteSegment(id: number) {
    this.segments = this.segments.filter(s => s.id !== id)
    if (this.selectedSegmentId === id) {
      this.selectedSegmentId = -1
    }
    this.drawGraph()
  }

  private updateSegmentType(id: number, type: 'linear' | 'quadratic' | 'constant') {
    const segment = this.segments.find(s => s.id === id)
    if (segment) {
      segment.type = type
      if (type === 'constant') {
        segment.a = 0
        segment.b = 0
      }
      this.drawGraph()
    }
  }

  private updateSegmentParameter(id: number, param: string, value: number | boolean) {
    const segment = this.segments.find(s => s.id === id)
    if (segment) {
      if (param === 'a') {
        segment.a = value as number
      } else if (param === 'b') {
        segment.b = value as number
      } else if (param === 'c') {
        segment.c = value as number
      } else if (param === 'start') {
        segment.start = value as number
      } else if (param === 'end') {
        segment.end = value as number
      } else if (param === 'includeStart') {
        segment.includeStart = value as boolean
      } else if (param === 'includeEnd') {
        segment.includeEnd = value as boolean
      }
      this.drawGraph()
    }
  }

  private getSegmentExpression(segment: PiecewiseSegment): string {
    switch (segment.type) {
      case 'linear':
        return `${segment.a.toFixed(2)}x + ${segment.b.toFixed(2)}`
      case 'quadratic':
        return `${segment.a.toFixed(2)}x² + ${segment.b.toFixed(2)}x + ${segment.c.toFixed(2)}`
      case 'constant':
        return `${segment.c.toFixed(2)}`
    }
  }

  private formatDomain(segment: PiecewiseSegment): string {
    const startBracket = segment.includeStart ? '[' : '('
    const endBracket = segment.includeEnd ? ']' : ')'
    return `${startBracket}${segment.start.toFixed(2)}, ${segment.end.toFixed(2)}${endBracket}`
  }

  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.segments.forEach((segment: PiecewiseSegment) => {
      this.drawSegment(ctx, segment)
    })
  }

  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 drawSegment(ctx: CanvasRenderingContext2D, segment: PiecewiseSegment) {
    const centerX = this.canvasWidth / 2
    const centerY = this.canvasHeight / 2
    const scale = 30
    
    ctx.strokeStyle = segment.color
    ctx.lineWidth = 3
    ctx.beginPath()
    
    let firstPoint = true
    for (let x = segment.start; x <= segment.end; x += 0.05) {
      let y: number
      
      switch (segment.type) {
        case 'linear':
          y = segment.a * x + segment.b
          break
        case 'quadratic':
          y = segment.a * x * x + segment.b * x + segment.c
          break
        case 'constant':
          y = segment.c
          break
      }
      
      const pixelX = centerX + x * scale
      const pixelY = centerY - y * scale
      
      if (firstPoint) {
        ctx.moveTo(pixelX, pixelY)
        firstPoint = false
      } else {
        ctx.lineTo(pixelX, pixelY)
      }
    }
    ctx.stroke()
    
    if (this.showLabels) {
      const midX = (segment.start + segment.end) / 2
      let midY: number
      
      switch (segment.type) {
        case 'linear':
          midY = segment.a * midX + segment.b
          break
        case 'quadratic':
          midY = segment.a * midX * midX + segment.b * midX + segment.c
          break
        case 'constant':
          midY = segment.c
          break
      }
      
      const pixelX = centerX + midX * scale
      const pixelY = centerY - midY * scale
      
      ctx.fillStyle = segment.color
      ctx.font = '12px sans-serif'
      ctx.fillText(`分段${segment.id}`, pixelX + 5, pixelY - 5)
    }
    
    this.drawEndpoints(ctx, segment)
  }

  private drawEndpoints(ctx: CanvasRenderingContext2D, segment: PiecewiseSegment) {
    const centerX = this.canvasWidth / 2
    const centerY = this.canvasHeight / 2
    const scale = 30
    
    const endpoint1: EndpointData = {
      x: segment.start,
      include: segment.includeStart
    }
    
    const endpoint2: EndpointData = {
      x: segment.end,
      include: segment.includeEnd
    }
    
    const endpoints: EndpointData[] = [endpoint1, endpoint2]
    
    endpoints.forEach((endpoint: EndpointData) => {
      let y: number
      
      switch (segment.type) {
        case 'linear':
          y = segment.a * endpoint.x + segment.b
          break
        case 'quadratic':
          y = segment.a * endpoint.x * endpoint.x + segment.b * endpoint.x + segment.c
          break
        case 'constant':
          y = segment.c
          break
      }
      
      const pixelX = centerX + endpoint.x * scale
      const pixelY = centerY - y * scale
      
      ctx.fillStyle = endpoint.include ? segment.color : '#FFFFFF'
      ctx.strokeStyle = segment.color
      ctx.lineWidth = 2
      ctx.beginPath()
      ctx.arc(pixelX, pixelY, endpoint.include ? 5 : 4, 0, 2 * Math.PI)
      ctx.fill()
      ctx.stroke()
    })
  }

  private reset() {
    this.segments = []
    this.selectedSegmentId = -1
    this.nextId = 1
    this.showGrid = true
    this.showAxes = true
    this.showLabels = true
    this.drawGraph()
  }

  @Builder
  SegmentEditor() {
    Column({ space: 10 }) {
      Text('编辑分段')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .textAlign(TextAlign.Center)
      
      Column({ space: 8 }) {
        Text('函数类型')
          .fontSize(14)
          .fontColor('#666666')
        
        Row({ space: 10 }) {
          Button('一次函数')
            .width('31%')
            .height(35)
            .fontSize(12)
            .backgroundColor(this.segments.find(s => s.id === this.selectedSegmentId)?.type === 'linear' ? '#2196F3' : '#E0E0E0')
            .onClick(() => {
              this.updateSegmentType(this.selectedSegmentId, 'linear')
            })
          
          Button('二次函数')
            .width('31%')
            .height(35)
            .fontSize(12)
            .backgroundColor(this.segments.find(s => s.id === this.selectedSegmentId)?.type === 'quadratic' ? '#2196F3' : '#E0E0E0')
            .onClick(() => {
              this.updateSegmentType(this.selectedSegmentId, 'quadratic')
            })
          
          Button('常量函数')
            .width('31%')
            .height(35)
            .fontSize(12)
            .backgroundColor(this.segments.find(s => s.id === this.selectedSegmentId)?.type === 'constant' ? '#2196F3' : '#E0E0E0')
            .onClick(() => {
              this.updateSegmentType(this.selectedSegmentId, 'constant')
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)

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

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

        Column({ space: 8 }) {
          Text(`c值: ${this.segments.find(s => s.id === this.selectedSegmentId)?.c.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.segments.find(s => s.id === this.selectedSegmentId)?.c || 0,
            min: -10,
            max: 10,
            step: 0.1
          })
            .width('100%')
            .blockColor('#2196F3')
            .trackColor('#E0E0E0')
            .selectedColor('#2196F3')
            .onChange((value: number) => {
              this.updateSegmentParameter(this.selectedSegmentId, 'c', value)
            })
        }
        .width('100%')
        .padding(8)
        .backgroundColor('#F5F5F5')
        .borderRadius(6)

        Column({ space: 8 }) {
          Text(`定义域起点: ${this.segments.find(s => s.id === this.selectedSegmentId)?.start.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.segments.find(s => s.id === this.selectedSegmentId)?.start || 0,
            min: -5,
            max: 5,
            step: 0.1
          })
            .width('100%')
            .blockColor('#4CAF50')
            .trackColor('#E0E0E0')
            .selectedColor('#4CAF50')
            .onChange((value: number) => {
              this.updateSegmentParameter(this.selectedSegmentId, 'start', value)
            })
        }
        .width('100%')
        .padding(8)
        .backgroundColor('#F5F5F5')
        .borderRadius(6)

        Column({ space: 8 }) {
          Text(`定义域终点: ${this.segments.find(s => s.id === this.selectedSegmentId)?.end.toFixed(2)}`)
            .fontSize(14)
            .fontColor('#666666')
          
          Slider({
            value: this.segments.find(s => s.id === this.selectedSegmentId)?.end || 0,
            min: -5,
            max: 5,
            step: 0.1
          })
            .width('100%')
            .blockColor('#4CAF50')
            .trackColor('#E0E0E0')
            .selectedColor('#4CAF50')
            .onChange((value: number) => {
              this.updateSegmentParameter(this.selectedSegmentId, 'end', value)
            })
        }
        .width('100%')
        .padding(8)
        .backgroundColor('#F5F5F5')
        .borderRadius(6)

        Row({ space: 10 }) {
          Row({ space: 5 }) {
            Text('包含起点')
              .fontSize(14)
            Text(this.segments.find(s => s.id === this.selectedSegmentId)?.includeStart ? '✓' : '✗')
              .fontSize(14)
              .fontColor(this.segments.find(s => s.id === this.selectedSegmentId)?.includeStart ? '#4CAF50' : '#F44336')
          }
          .onClick(() => {
            this.updateSegmentParameter(this.selectedSegmentId, 'includeStart', !this.segments.find(s => s.id === this.selectedSegmentId)?.includeStart)
          })
          
          Row({ space: 5 }) {
            Text('包含终点')
              .fontSize(14)
            Text(this.segments.find(s => s.id === this.selectedSegmentId)?.includeEnd ? '✓' : '✗')
              .fontSize(14)
              .fontColor(this.segments.find(s => s.id === this.selectedSegmentId)?.includeEnd ? '#4CAF50' : '#F44336')
          }
          .onClick(() => {
            this.updateSegmentParameter(this.selectedSegmentId, 'includeEnd', !this.segments.find(s => s.id === this.selectedSegmentId)?.includeEnd)
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceAround)
      }
      .width('100%')
    }
    .width('90%')
    .padding(10)
    .backgroundColor('#FAFAFA')
    .borderRadius(10)
  }
}
Logo

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

更多推荐