///|
/// Draw a ring chart and return normalized slice fractions. `inner_radius` is a
/// fraction of the outer radius: 0 is a filled pie, and 0.55 leaves a transparent
/// hole. The ratio must be finite, non-negative and strictly less than 1.
pub fn Figure::donut(
  self : Figure,
  values : Array[Double],
  labels~ : Array[String] = [],
  colors~ : Array[String] = [],
  start_angle~ : Double = 0.0,
  inner_radius~ : Double = 0.55,
) -> Array[Double] raise @core.DataError {
  self.axes().donut(values, labels~, colors~, start_angle~, inner_radius~)
}

///|
/// Draw an annular pie with a real transparent hole. Slice colors, labels and
/// original indices follow `pie`; the container and interaction kind remain Pie.
/// Invalid input is reported as `DataError::InvalidPie` before adding artists.
pub fn Axes::donut(
  self : Axes,
  values : Array[Double],
  labels~ : Array[String] = [],
  colors~ : Array[String] = [],
  start_angle~ : Double = 0.0,
  inner_radius~ : Double = 0.55,
) -> Array[Double] raise @core.DataError {
  validate_pie(values, labels, colors, start_angle)
  if inner_radius.is_nan() ||
    inner_radius.is_inf() ||
    inner_radius < 0.0 ||
    inner_radius >= 1.0 {
    raise @core.DataError::InvalidPie(
      message="donut inner_radius must be a finite ratio in [0, 1)",
    )
  }
  let mut total = 0.0
  for value in values {
    total += value
  }
  if total.is_inf() {
    raise @core.DataError::InvalidPie(
      message="donut values must have a finite sum",
    )
  }
  let resolved_colors = resolve_pie_colors(self, colors, values.length())
  let fractions = values.map(value => value / total)
  let start = self.state.val.artists.length()
  self.push_artist(
    Artist::Donut(
      values.copy(),
      labels.copy(),
      resolved_colors,
      start_angle,
      inner_radius,
    ),
  )
  self.add_container(ContainerKind::Pie, start, "")
  fractions
}