// Radar (spider) chart.
///|
/// Angle of axis `i` of `n`, in radians, starting at the top (12 o'clock) and
/// going clockwise.
fn radar_angle(i : Int, n : Int) -> Double {
-@math.PI / 2.0 + i.to_double() * 2.0 * @math.PI / n.to_double()
}
///|
/// Render a radar (spider) chart: each entry of `axes` becomes a spoke and each
/// series in `series` is drawn as a closed polygon over the spokes. `series` is
/// a list of `(name, values)` where `values[i]` is the value on axis `i`.
/// `title`, `width`, `height` and `theme` are optional.
pub fn radar_chart(
axes : Array[String],
series : Array[(String, Array[Double])],
title? : String = "",
width? : Double = 460.0,
height? : Double = 380.0,
theme? : Theme = Theme::light(),
) -> String {
let title_h = if title == "" { 8.0 } else { 30.0 }
let legend_h = 22.0
let top = title_h + legend_h
let n = axes.length()
let cx = width / 2.0
let cy = top + (height - top) / 2.0
// Leave room for axis labels around the web.
let radius = {
let by_h = (height - top - 36.0) / 2.0
let by_w = width / 2.0 - 64.0
if by_h < by_w {
by_h
} else {
by_w
}
}
let mut max = 0.0
for s in series {
for v in s.1 {
if v > max {
max = v
}
}
}
if max <= 0.0 {
max = 1.0
}
let (_, axis_hi, ticks) = nice_ticks(0.0, max, 4)
let body = StringBuilder::new()
body.write_string(background_rect(theme, width, height))
if n >= 3 {
// Concentric grid rings at each tick.
for t in ticks {
if t > 0.0 {
let rr = t / axis_hi * radius
let ring = StringBuilder::new()
for i in 0.. 0 {
ring.write_string(" ")
}
ring.write_string(
num(cx + rr * @math.cos(a)) + "," + num(cy + rr * @math.sin(a)),
)
}
body.write_string(
elem("polygon", [
("points", ring.to_string()),
("fill", "none"),
("stroke", theme.grid),
("stroke-width", "1"),
]),
)
}
}
// Spokes and axis labels.
for i in 0.. 0.3 {
"start"
} else if @math.cos(a) < -0.3 {
"end"
} else {
"middle"
}
body.write_string(label(lx, ly, axes[i], anchor~, fill=theme.axis))
}
// One filled polygon per series.
for si in 0.. 0 {
poly.write_string(" ")
}
poly.write_string(
num(cx + rr * @math.cos(a)) + "," + num(cy + rr * @math.sin(a)),
)
}
body.write_string(
elem("polygon", [
("points", poly.to_string()),
("fill", color),
("fill-opacity", "0.15"),
("stroke", color),
("stroke-width", "2"),
]),
)
}
}
let legend_items : Array[(String, String)] = []
for si in 0..