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
}