2. 直线与圆的位置关系

功能简介:通过调整直线方程参数和圆的圆心、半径,实时展示直线与圆的位置关系(相离、相切、相交),计算圆心到直线的距离、交点坐标。帮助学生理解直线与圆位置关系的判定方法和几何意义。
在这里插入图片描述
ArkTS代码

@Entry
@Component
struct LineCircleRelation {
  @State private lineParams: number[] = [1, -1, 0] // ax + by + c = 0
  @State private circleCenter: number[] = [0, 0]
  @State private circleRadius: number = 5
  @State private relation: string = ''
  @State private distance: number = 0
  @State private intersectionPoints: string[] = []
  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)
        .onReady(() => this.drawRelation())

      Text('直线方程: ax + by + c = 0')
        .fontSize(16).fontColor('#666').margin({ top: 20, bottom: 10 })

      ForEach(['a', 'b', 'c'], (param: string, index: number) => {
        Row() {
          Text(`${param}: `)
            .width(40)
          Slider({
            value: this.lineParams[index], 
            min: -10, 
            max: 10, 
            step: 0.1
          })
            .width(200)
            .onChange((val: number) => {
              this.lineParams[index] = val
              this.calculateRelation()
            })
          Text(this.lineParams[index].toFixed(1))
            .width(60)
        }
        .margin({ bottom: 10 })
      })

      Text('圆: (x - h)² + (y - k)² = r²')
        .fontSize(16).fontColor('#666').margin({ top: 20, bottom: 10 })

      Row() {
        Text('圆心(h, k): ')
          .width(80)
        TextInput({ 
          placeholder: 'h', 
          text: this.circleCenter[0].toString() 
        })
          .width(80)
          .onChange((v: string) => {
            this.circleCenter[0] = parseFloat(v) || 0
            this.calculateRelation()
          })
        TextInput({ 
          placeholder: 'k', 
          text: this.circleCenter[1].toString() 
        })
          .width(80)
          .onChange((v: string) => {
            this.circleCenter[1] = parseFloat(v) || 0
            this.calculateRelation()
          })
      }
      .margin({ bottom: 10 })

      Row() {
        Text('半径 r: ')
          .width(80)
        Slider({
          value: this.circleRadius, 
          min: 1, 
          max: 10,
          step: 0.1
        })
          .width(200)
          .onChange((val: number) => {
            this.circleRadius = val
            this.calculateRelation()
          })
        Text(this.circleRadius.toFixed(1))
          .width(60)
      }
      .margin({ bottom: 20 })

      Text(this.relation)
        .fontSize(16).fontColor('#2196F3').margin({ bottom: 10 })
      Text(`圆心到直线的距离: d = ${this.distance.toFixed(2)}`)
        .fontSize(14).fontColor('#666')
      if (this.intersectionPoints.length > 0) {
        Text('交点坐标:')
          .fontSize(14).fontColor('#666').margin({ top: 10, bottom: 5 })
        ForEach(this.intersectionPoints, (point: string) => {
          Text(point)
            .fontSize(14).fontColor('#666')
        })
      }
    }
    .padding(20)
  }

  private calculateRelation() {
    const a = this.lineParams[0]
    const b = this.lineParams[1]
    const c = this.lineParams[2]
    const h = this.circleCenter[0]
    const k = this.circleCenter[1]
    
    // 计算圆心到直线的距离
    this.distance = Math.abs(a * h + b * k + c) / Math.sqrt(a * a + b * b)

    // 确定位置关系
    if (this.distance > this.circleRadius) {
      this.relation = '位置关系: 相离'
      this.intersectionPoints = []
    } else if (Math.abs(this.distance - this.circleRadius) < 0.01) {
      this.relation = '位置关系: 相切'
      this.calculateIntersectionPoints()
    } else {
      this.relation = '位置关系: 相交'
      this.calculateIntersectionPoints()
    }
    this.drawRelation()
  }

  private calculateIntersectionPoints() {
    const a = this.lineParams[0]
    const b = this.lineParams[1]
    const c = this.lineParams[2]
    const h = this.circleCenter[0]
    const k = this.circleCenter[1]
    const r = this.circleRadius
    this.intersectionPoints = []

    // 处理特殊情况
    if (Math.abs(a) < 0.001 && Math.abs(b) < 0.001) {
      return
    }

    if (Math.abs(a) < 0.001) {
      // 直线平行于x轴: y = -c/b
      const y = -c / b
      const discriminant = r * r - (y - k) * (y - k)
      
      if (discriminant >= -0.001) {
        if (Math.abs(discriminant) < 0.001) {
          // 相切
          const x = h
          this.intersectionPoints.push(`P: (${x.toFixed(2)}, ${y.toFixed(2)})`)
        } else {
          // 相交
          const sqrtDiscriminant = Math.sqrt(discriminant)
          const x1 = h - sqrtDiscriminant
          const x2 = h + sqrtDiscriminant
          this.intersectionPoints.push(`P1: (${x1.toFixed(2)}, ${y.toFixed(2)})`)
          this.intersectionPoints.push(`P2: (${x2.toFixed(2)}, ${y.toFixed(2)})`)
        }
      }
    } else if (Math.abs(b) < 0.001) {
      // 直线平行于y轴: x = -c/a
      const x = -c / a
      const discriminant = r * r - (x - h) * (x - h)
      
      if (discriminant >= -0.001) {
        if (Math.abs(discriminant) < 0.001) {
          // 相切
          const y = k
          this.intersectionPoints.push(`P: (${x.toFixed(2)}, ${y.toFixed(2)})`)
        } else {
          // 相交
          const sqrtDiscriminant = Math.sqrt(discriminant)
          const y1 = k - sqrtDiscriminant
          const y2 = k + sqrtDiscriminant
          this.intersectionPoints.push(`P1: (${x.toFixed(2)}, ${y1.toFixed(2)})`)
          this.intersectionPoints.push(`P2: (${x.toFixed(2)}, ${y2.toFixed(2)})`)
        }
      }
    } else {
      // 一般情况:解方程组
      const m = -a / b
      const n = -c / b
      
      // 展开后得到:ax² + bx + c = 0
      const A = 1 + m * m
      const B = -2 * h + 2 * m * (k - n)
      const C = h * h + (k - n) * (k - n) - r * r
      
      const discriminant = B * B - 4 * A * C
      
      if (discriminant >= -0.001) {
        if (Math.abs(discriminant) < 0.001) {
          // 相切
          const x = -B / (2 * A)
          const y = m * x + n
          this.intersectionPoints.push(`P: (${x.toFixed(2)}, ${y.toFixed(2)})`)
        } else {
          // 相交
          const sqrtDiscriminant = Math.sqrt(discriminant)
          const x1 = (-B - sqrtDiscriminant) / (2 * A)
          const x2 = (-B + sqrtDiscriminant) / (2 * A)
          const y1 = m * x1 + n
          const y2 = m * x2 + n
          this.intersectionPoints.push(`P1: (${x1.toFixed(2)}, ${y1.toFixed(2)})`)
          this.intersectionPoints.push(`P2: (${x2.toFixed(2)}, ${y2.toFixed(2)})`)
        }
      }
    }
  }

  private drawRelation() {
    const width = 400
    const height = 300
    const centerX = width / 2
    const centerY = height / 2
    const scale = 20 // 缩放因子

    const ctx = this.context
    ctx.clearRect(0, 0, width, height)

    // 绘制坐标系
    this.drawCoordinateSystem(ctx, centerX, centerY, scale)

    // 绘制圆
    this.drawCircle(ctx, centerX, centerY, scale)

    // 绘制直线
    this.drawLine(ctx, centerX, centerY, scale)

    // 绘制交点
    this.drawIntersectionPoints(ctx, centerX, centerY, scale)

    // 绘制圆心到直线的距离
    this.drawDistance(ctx, centerX, centerY, scale)
  }

  private drawCoordinateSystem(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, scale: number) {
    // 绘制坐标轴
    ctx.strokeStyle = '#666666'
    ctx.lineWidth = 2

    // X轴
    ctx.beginPath()
    ctx.moveTo(0, centerY)
    ctx.lineTo(400, centerY)
    ctx.stroke()

    // Y轴
    ctx.beginPath()
    ctx.moveTo(centerX, 0)
    ctx.lineTo(centerX, 300)
    ctx.stroke()

    // 绘制刻度
    ctx.font = '10px sans-serif'
    ctx.fillStyle = '#666666'
    ctx.textAlign = 'center'

    // X轴刻度
    for (let x = centerX; x < 400; x += scale * 2) {
      ctx.beginPath()
      ctx.moveTo(x, centerY - 5)
      ctx.lineTo(x, centerY + 5)
      ctx.stroke()
      const value = (x - centerX) / scale
      ctx.fillText(value.toString(), x, centerY + 20)
    }

    for (let x = centerX; x > 0; x -= scale * 2) {
      ctx.beginPath()
      ctx.moveTo(x, centerY - 5)
      ctx.lineTo(x, centerY + 5)
      ctx.stroke()
      const value = (x - centerX) / scale
      ctx.fillText(value.toString(), x, centerY + 20)
    }

    // Y轴刻度
    for (let y = centerY; y < 300; y += scale * 2) {
      ctx.beginPath()
      ctx.moveTo(centerX - 5, y)
      ctx.lineTo(centerX + 5, y)
      ctx.stroke()
      const value = (centerY - y) / scale
      ctx.fillText(value.toString(), centerX - 20, y + 4)
    }

    for (let y = centerY; y > 0; y -= scale * 2) {
      ctx.beginPath()
      ctx.moveTo(centerX - 5, y)
      ctx.lineTo(centerX + 5, y)
      ctx.stroke()
      const value = (centerY - y) / scale
      ctx.fillText(value.toString(), centerX - 20, y + 4)
    }

    // 绘制原点
    ctx.fillText('O', centerX - 10, centerY + 15)
  }

  private drawCircle(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, scale: number) {
    // 绘制圆
    const h = this.circleCenter[0]
    const k = this.circleCenter[1]
    const screenX = centerX + h * scale
    const screenY = centerY - k * scale
    const screenRadius = this.circleRadius * scale

    ctx.strokeStyle = '#3498DB'
    ctx.lineWidth = 2
    ctx.beginPath()
    ctx.arc(screenX, screenY, screenRadius, 0, 2 * Math.PI)
    ctx.stroke()

    // 绘制圆心
    ctx.fillStyle = '#E74C3C'
    ctx.beginPath()
    ctx.arc(screenX, screenY, 4, 0, 2 * Math.PI)
    ctx.fill()

    // 绘制圆心标签
    ctx.fillStyle = '#E74C3C'
    ctx.font = '14px sans-serif'
    ctx.textAlign = 'center'
    ctx.fillText('C', screenX, screenY - 10)
  }

  private drawLine(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, scale: number) {
    // 绘制直线 ax + by + c = 0
    const a = this.lineParams[0]
    const b = this.lineParams[1]
    const c = this.lineParams[2]
    ctx.strokeStyle = '#27AE60'
    ctx.lineWidth = 2

    // 计算直线与画布边界的交点
    let x1: number, y1: number, x2: number, y2: number

    if (Math.abs(b) < 0.001) {
      // 直线垂直于x轴
      x1 = x2 = -c / a
      y1 = -10
      y2 = 10
    } else {
      // 计算直线与左右边界的交点
      x1 = -10
      y1 = (-a * x1 - c) / b
      x2 = 10
      y2 = (-a * x2 - c) / b
    }

    // 转换为屏幕坐标
    const screenX1 = centerX + x1 * scale
    const screenY1 = centerY - y1 * scale
    const screenX2 = centerX + x2 * scale
    const screenY2 = centerY - y2 * scale

    ctx.beginPath()
    ctx.moveTo(screenX1, screenY1)
    ctx.lineTo(screenX2, screenY2)
    ctx.stroke()

    // 绘制直线方程
    ctx.fillStyle = '#27AE60'
    ctx.font = '14px sans-serif'
    ctx.textAlign = 'left'
    ctx.fillText(`${a.toFixed(1)}x + ${b.toFixed(1)}y + ${c.toFixed(1)} = 0`, 20, 30)
  }

  private drawIntersectionPoints(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, scale: number) {
    // 绘制交点
    ctx.fillStyle = '#F39C12'
    ctx.font = '14px sans-serif'
    ctx.textAlign = 'center'

    for (let i = 0; i < this.intersectionPoints.length; i++) {
      const pointStr = this.intersectionPoints[i]
      // 从字符串中提取坐标
      const match = pointStr.match(/\(([^,]+),\s*([^)]+)\)/)
      if (match) {
        const x = parseFloat(match[1])
        const y = parseFloat(match[2])
        const screenX = centerX + x * scale
        const screenY = centerY - y * scale

        // 绘制交点
        ctx.beginPath()
        ctx.arc(screenX, screenY, 4, 0, 2 * Math.PI)
        ctx.fill()

        // 绘制交点标签
        ctx.fillText(`P${i+1}`, screenX, screenY - 10)
      }
    }
  }

  private drawDistance(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, scale: number) {
    // 绘制圆心到直线的距离
    const a = this.lineParams[0]
    const b = this.lineParams[1]
    const c = this.lineParams[2]
    const h = this.circleCenter[0]
    const k = this.circleCenter[1]

    if (Math.abs(a) < 0.001 && Math.abs(b) < 0.001) {
      return
    }

    // 计算垂足坐标
    const denominator = a * a + b * b
    const footX = (b * (b * h - a * k) - a * c) / denominator
    const footY = (a * (-b * h + a * k) - b * c) / denominator

    // 转换为屏幕坐标
    const screenCenterX = centerX + h * scale
    const screenCenterY = centerY - k * scale
    const screenFootX = centerX + footX * scale
    const screenFootY = centerY - footY * scale

    // 绘制垂线
    ctx.strokeStyle = '#95A5A6'
    ctx.lineWidth = 1
    ctx.setLineDash([5, 5])

    ctx.beginPath()
    ctx.moveTo(screenCenterX, screenCenterY)
    ctx.lineTo(screenFootX, screenFootY)
    ctx.stroke()

    ctx.setLineDash([])

    // 绘制距离标签
    ctx.fillStyle = '#95A5A6'
    ctx.font = '12px sans-serif'
    ctx.textAlign = 'center'
    const midX = (screenCenterX + screenFootX) / 2
    const midY = (screenCenterY + screenFootY) / 2
    ctx.fillText(`d = ${this.distance.toFixed(2)}`, midX, midY - 10)
  }
}
Logo

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

更多推荐