9. 平面向量的坐标运算

功能简介:支持平面向量的坐标表示、加减、数乘、点积运算,通过坐标系可视化展示向量运算过程。计算向量的模、夹角、单位向量,帮助学生理解平面向量的坐标运算和几何意义。
在这里插入图片描述
ArkTS代码

@Entry
@Component
struct VectorCoordinate {
  @State private vector1: number[] = [1, 2]
  @State private vector2: number[] = [3, 4]
  @State private scalar: number = 2
  @State private operation: string = 'add'
  @State private result: string = ''
  @State private vector1Modulus: number = Math.sqrt(5)
  @State private vector2Modulus: number = Math.sqrt(25)
  @State private angle: number = 0
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

  build() {
    Column() {
      Text('🔄 平面向量坐标运算')
        .fontSize(24).fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      Canvas(this.context)
        .width(400).height(300)
        .backgroundColor('#f5f5f5')
        .onReady(() => this.drawVectors())

      Text('运算类型')
        .fontSize(18).fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 10 })

      Row() {
        Button('加法')
          .width(80)
          .onClick(() => { this.operation = 'add'; this.calculate() })
        Button('减法')
          .width(80)
          .onClick(() => { this.operation = 'subtract'; this.calculate() })
        Button('数乘')
          .width(80)
          .onClick(() => { this.operation = 'scalar'; this.calculate() })
        Button('点积')
          .width(80)
          .onClick(() => { this.operation = 'dot'; this.calculate() })
      }
      .margin({ bottom: 20 })

      Text('向量1 (x, y): ')
        .fontSize(16).fontWeight(FontWeight.Bold)
        .margin({ bottom: 5 })

      Row() {
        TextInput({
          placeholder: 'x',
          text: this.vector1[0].toString()
        })
          .width(80)
          .onChange((v: string) => {
            this.vector1[0] = parseFloat(v) || 0
            this.calculate()
          })
        TextInput({
          placeholder: 'y',
          text: this.vector1[1].toString()
        })
          .width(80)
          .onChange((v: string) => {
            this.vector1[1] = parseFloat(v) || 0
            this.calculate()
          })
      }
      .margin({ bottom: 15 })

      Text('向量2 (x, y): ')
        .fontSize(16).fontWeight(FontWeight.Bold)
        .margin({ bottom: 5 })

      Row() {
        TextInput({
          placeholder: 'x',
          text: this.vector2[0].toString()
        })
          .width(80)
          .onChange((v: string) => {
            this.vector2[0] = parseFloat(v) || 0
            this.calculate()
          })
        TextInput({
          placeholder: 'y',
          text: this.vector2[1].toString()
        })
          .width(80)
          .onChange((v: string) => {
            this.vector2[1] = parseFloat(v) || 0
            this.calculate()
          })
      }
      .margin({ bottom: 15 })

      Text('标量: ')
        .fontSize(16).fontWeight(FontWeight.Bold)
        .margin({ bottom: 5 })

      Row() {
        TextInput({
          placeholder: '标量',
          text: this.scalar.toString()
        })
          .width(100)
          .onChange((v: string) => {
            this.scalar = parseFloat(v) || 0
            if (this.operation === 'scalar') {
              this.calculate()
            }
          })
      }
      .margin({ bottom: 20 })

      Text('运算结果')
        .fontSize(18).fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 })

      Text(this.result)
        .fontSize(16).fontColor('#2196F3')

      Text('向量属性')
        .fontSize(18).fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 10 })

      Text(`向量1的模: ${this.vector1Modulus.toFixed(2)}`)
        .fontSize(14).fontColor('#666')
      Text(`向量2的模: ${this.vector2Modulus.toFixed(2)}`)
        .fontSize(14).fontColor('#666')
      Text(`两向量夹角: ${this.angle.toFixed(2)}°`)
        .fontSize(14).fontColor('#666')

      Text('向量运算的几何意义')
        .fontSize(18).fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 10 })

      Text('加法: 平行四边形法则')
        .fontSize(14).fontColor('#666')
      Text('减法: 三角形法则')
        .fontSize(14).fontColor('#666')
      Text('数乘: 长度缩放,方向不变或相反')
        .fontSize(14).fontColor('#666')
      Text('点积: 一个向量在另一个向量上的投影')
        .fontSize(14).fontColor('#666')
    }
    .padding(20)
  }

  private calculate() {
    // 计算向量的模
    this.vector1Modulus = Math.sqrt(this.vector1[0] ** 2 + this.vector1[1] ** 2)
    this.vector2Modulus = Math.sqrt(this.vector2[0] ** 2 + this.vector2[1] ** 2)
    
    // 计算两向量夹角
    const dotProduct = this.vector1[0] * this.vector2[0] + this.vector1[1] * this.vector2[1]
    if (this.vector1Modulus > 0 && this.vector2Modulus > 0) {
      const cosTheta = dotProduct / (this.vector1Modulus * this.vector2Modulus)
      this.angle = Math.acos(Math.max(-1, Math.min(1, cosTheta))) * 180 / Math.PI
    } else {
      this.angle = 0
    }
    
    // 执行运算
    switch (this.operation) {
      case 'add':
        const sum = [this.vector1[0] + this.vector2[0], this.vector1[1] + this.vector2[1]]
        this.result = `和向量: (${sum[0].toFixed(2)}, ${sum[1].toFixed(2)})`
        break
      case 'subtract':
        const diff = [this.vector1[0] - this.vector2[0], this.vector1[1] - this.vector2[1]]
        this.result = `差向量: (${diff[0].toFixed(2)}, ${diff[1].toFixed(2)})`
        break
      case 'scalar':
        const scaled = [this.vector1[0] * this.scalar, this.vector1[1] * this.scalar]
        this.result = `数乘结果: (${scaled[0].toFixed(2)}, ${scaled[1].toFixed(2)})`
        break
      case 'dot':
        const dot = this.vector1[0] * this.vector2[0] + this.vector1[1] * this.vector2[1]
        this.result = `点积: ${dot.toFixed(2)}`
        break
    }
    
    this.drawVectors()
  }

  private drawVectors() {
    const ctx = this.context
    const width = 400
    const height = 300
    const centerX = width / 2
    const centerY = height / 2
    const scale = 30
    
    // 清空画布
    ctx.clearRect(0, 0, width, height)
    
    // 绘制坐标系
    ctx.beginPath()
    ctx.moveTo(50, centerY)
    ctx.lineTo(width - 50, centerY)
    ctx.moveTo(centerX, 50)
    ctx.lineTo(centerX, height - 50)
    ctx.strokeStyle = '#000'
    ctx.lineWidth = 1
    ctx.stroke()
    
    // 绘制刻度
    for (let i = -5; i <= 5; i++) {
      const x = centerX + i * scale
      ctx.beginPath()
      ctx.moveTo(x, centerY - 5)
      ctx.lineTo(x, centerY + 5)
      ctx.stroke()
      ctx.font = '12px Arial'
      ctx.fillStyle = '#000'
      ctx.fillText(i.toString(), x - 5, centerY + 15)
    }
    
    for (let i = -5; i <= 5; i++) {
      const y = centerY - i * scale
      ctx.beginPath()
      ctx.moveTo(centerX - 5, y)
      ctx.lineTo(centerX + 5, y)
      ctx.stroke()
      ctx.font = '12px Arial'
      ctx.fillStyle = '#000'
      ctx.fillText(i.toString(), centerX - 15, y + 4)
    }
    
    // 绘制向量1
    const x1 = centerX + this.vector1[0] * scale
    const y1 = centerY - this.vector1[1] * scale
    
    ctx.beginPath()
    ctx.moveTo(centerX, centerY)
    ctx.lineTo(x1, y1)
    ctx.strokeStyle = '#2196F3'
    ctx.lineWidth = 2
    ctx.stroke()
    
    // 绘制向量1的箭头
    this.drawArrow(ctx, centerX, centerY, x1, y1, '#2196F3')
    
    // 绘制向量2
    const x2 = centerX + this.vector2[0] * scale
    const y2 = centerY - this.vector2[1] * scale
    
    ctx.beginPath()
    ctx.moveTo(centerX, centerY)
    ctx.lineTo(x2, y2)
    ctx.strokeStyle = '#4CAF50'
    ctx.lineWidth = 2
    ctx.stroke()
    
    // 绘制向量2的箭头
    this.drawArrow(ctx, centerX, centerY, x2, y2, '#4CAF50')
    
    // 绘制运算结果
    switch (this.operation) {
      case 'add':
        const sumX = centerX + (this.vector1[0] + this.vector2[0]) * scale
        const sumY = centerY - (this.vector1[1] + this.vector2[1]) * scale
        
        // 绘制平行四边形法则
        ctx.beginPath()
        ctx.moveTo(x1, y1)
        ctx.lineTo(sumX, sumY)
        ctx.lineTo(x2, y2)
        ctx.strokeStyle = '#FF9800'
        ctx.lineWidth = 1
        ctx.setLineDash([5, 5])
        ctx.stroke()
        ctx.setLineDash([])
        
        // 绘制和向量
        ctx.beginPath()
        ctx.moveTo(centerX, centerY)
        ctx.lineTo(sumX, sumY)
        ctx.strokeStyle = '#FF9800'
        ctx.lineWidth = 2
        ctx.stroke()
        
        // 绘制和向量的箭头
        this.drawArrow(ctx, centerX, centerY, sumX, sumY, '#FF9800')
        break
        
      case 'subtract':
        const diffX = centerX + (this.vector1[0] - this.vector2[0]) * scale
        const diffY = centerY - (this.vector1[1] - this.vector2[1]) * scale
        
        // 绘制三角形法则
        ctx.beginPath()
        ctx.moveTo(x2, y2)
        ctx.lineTo(diffX, diffY)
        ctx.strokeStyle = '#9C27B0'
        ctx.lineWidth = 1
        ctx.setLineDash([5, 5])
        ctx.stroke()
        ctx.setLineDash([])
        
        // 绘制差向量
        ctx.beginPath()
        ctx.moveTo(centerX, centerY)
        ctx.lineTo(diffX, diffY)
        ctx.strokeStyle = '#9C27B0'
        ctx.lineWidth = 2
        ctx.stroke()
        
        // 绘制差向量的箭头
        this.drawArrow(ctx, centerX, centerY, diffX, diffY, '#9C27B0')
        break
        
      case 'scalar':
        const scaledX = centerX + (this.vector1[0] * this.scalar) * scale
        const scaledY = centerY - (this.vector1[1] * this.scalar) * scale
        
        // 绘制数乘结果
        ctx.beginPath()
        ctx.moveTo(centerX, centerY)
        ctx.lineTo(scaledX, scaledY)
        ctx.strokeStyle = '#FF5722'
        ctx.lineWidth = 2
        ctx.stroke()
        
        // 绘制数乘结果的箭头
        this.drawArrow(ctx, centerX, centerY, scaledX, scaledY, '#FF5722')
        break
    }
    
    // 绘制标签
    ctx.font = '12px Arial'
    ctx.fillStyle = '#2196F3'
    ctx.fillText('v₁', x1 + 10, y1 - 10)
    ctx.fillStyle = '#4CAF50'
    ctx.fillText('v₂', x2 + 10, y2 - 10)
  }

  private drawArrow(ctx: CanvasRenderingContext2D, fromX: number, fromY: number, toX: number, toY: number, color: string) {
    const headLength = 10
    const angle = Math.atan2(toY - fromY, toX - fromX)
    
    ctx.beginPath()
    ctx.moveTo(toX, toY)
    ctx.lineTo(
      toX - headLength * Math.cos(angle - Math.PI / 6),
      toY - headLength * Math.sin(angle - Math.PI / 6)
    )
    ctx.lineTo(
      toX - headLength * Math.cos(angle + Math.PI / 6),
      toY - headLength * Math.sin(angle + Math.PI / 6)
    )
    ctx.closePath()
    ctx.fillStyle = color
    ctx.fill()
  }
}
Logo

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

更多推荐