fun arc()

in canvas/src/commonMain/kotlin/org/jetbrains/letsPlot/core/canvas/Path2d.kt [131:224]


    fun arc(
        x1: Number, y1: Number,
        x2: Number, y2: Number,
        rxIn: Number, ryIn: Number,
        angle: Number,
        largeArcFlag: Boolean, sweepFlag: Boolean,
        connect: Boolean = true, // lineTo() to the arc start point
        at: AffineTransform = AffineTransform.IDENTITY
    ): Path2d {
        if (rxIn == 0.0 || ryIn == 0.0) {
            // If either radius is zero, draw a line
            lineTo(x2.toDouble(), y2.toDouble(), at = at)
            return this
        }

        val startX = x1.toDouble()
        val startY = y1.toDouble()
        val endX = x2.toDouble()
        val endY = y2.toDouble()
        val angleRad = toRadians(angle.toDouble())

        // Ensure radii are positive
        var rx = abs(rxIn.toDouble())
        var ry = abs(ryIn.toDouble())

        // Step 1: Transform start/end points into ellipse space
        val dx2 = (startX - endX) / 2.0
        val dy2 = (startY - endY) / 2.0

        val x1p = cos(angleRad) * dx2 + sin(angleRad) * dy2
        val y1p = -sin(angleRad) * dx2 + cos(angleRad) * dy2

        // Correct radii if they are too small
        val lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry)
        if (lambda > 1) {
            val sqrtLambda = sqrt(lambda)
            rx *= sqrtLambda
            ry *= sqrtLambda
        }

        val rxSq = rx * rx
        val rySq = ry * ry
        val x1pSq = x1p * x1p
        val y1pSq = y1p * y1p

        // Step 2: Compute center (cx, cy) of the ellipse
        val denom = (rxSq * y1pSq + rySq * x1pSq)
        val num = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq)
        val factor = sqrt(max(0.0, num / denom)) * if (largeArcFlag == sweepFlag) -1 else 1

        val cxp = factor * (rx * y1p / ry)
        val cyp = factor * (-ry * x1p / rx)

        // Step 3: Transform center back to the original coordinate system
        val cx = cos(angleRad) * cxp - sin(angleRad) * cyp + (startX + endX) / 2.0
        val cy = sin(angleRad) * cxp + cos(angleRad) * cyp + (startY + endY) / 2.0

        // Step 4: Compute start angle and end angle
        val v1x = (x1p - cxp) / rx
        val v1y = (y1p - cyp) / ry
        val v2x = (-x1p - cxp) / rx
        val v2y = (-y1p - cyp) / ry

        val theta1 = atan2(v1y, v1x)
        var deltaTheta = atan2(v2y, v2x) - theta1

        // Ensure correct arc selection
        if (sweepFlag) {
            if (deltaTheta < 0) deltaTheta += 2 * PI
        } else {
            if (deltaTheta > 0) deltaTheta -= 2 * PI
        }

        val startAngle = theta1
        val endAngle = theta1 + deltaTheta

        // Determine direction (anticlockwise = !sweepFlag)
        val anticlockwise = !sweepFlag

        arc(
            x = cx,
            y = cy,
            radiusX = rx,
            radiusY = ry,
            rotation = angleRad,
            startAngle = startAngle,
            endAngle = endAngle,
            anticlockwise = anticlockwise,
            connect = connect,
            at = at
        )

        return this
    }