Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::charts_axis_axis_from_ticks::{axis_to_value, line_to_value};
use super::charts_axis_band_axis::band_axis;
use super::charts_axis_grid_lines::grid_lines;
use super::charts_axis_number_axis::number_axis;
use super::charts_format_format_tick::format_tick;
use super::charts_layout_legend_rows::{legend_item_to_value, legend_rows};
use super::charts_layout_plot_area::{plot_area, plot_area_to_value};
use super::charts_layout_types::{LegendItem, Margins};
use super::charts_palette::palette; ← from charts.palette ^1.0.0 · built alongside by fune
use super::charts_scale_band_scale::band_scale;
use super::charts_scale_linear_scale::linear_scale;
use super::charts_scale_nice_domain::nice_domain;
use super::charts_shape_bar_rects::{bar_rects, rect_to_value};
use super::charts_shape_types::BarSpec;
use super::charts_stack::stack; ← from charts.stack ^1.0.0 · built alongside by fune
use super::charts_ticks_nice_ticks::nice_ticks;
use super::charts_ticks_tick_step::tick_step;
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
// Fixed layout constants; the README explains each.
const CHAR_WIDTH: f64 = 7.0;
const SWATCH: f64 = 10.0;
const LEGEND_GAP: f64 = 16.0;
const LEGEND_ROW: f64 = 18.0;
const EDGE: f64 = 8.0;
const TICK_SIZE: f64 = 6.0;
const LABEL_PADDING: f64 = 3.0;
const TOP_WITHOUT_LEGEND: f64 = 12.0;
const RIGHT: f64 = 20.0;
const BOTTOM: f64 = 30.0;
const CATEGORY_PADDING_INNER: f64 = 0.2;
const CATEGORY_PADDING_OUTER: f64 = 0.1;
const SERIES_PADDING: f64 = 0.05;
/// A whole bar chart as geometry, grouped or stacked, by the fixed rules in
/// the README.
///
/// # Panics
/// Panics on an unknown mode, no categories or series, a series of the wrong
/// length, repeated categories or series names, or a chart too small for its
/// margins.
pub fn bar_chart(spec: &BarChartSpec) -> BarChart {
let (width, height, categories, series) = (spec.width, spec.height, &spec.categories, &spec.series);
if spec.mode != "grouped" && spec.mode != "stacked" {
panic!("mode must be grouped or stacked, received {}", spec.mode);
}
if categories.is_empty() {
panic!("a bar chart needs at least one category");
}
if series.is_empty() {
panic!("a bar chart needs at least one series");
}
for s in series {
if s.values.len() != categories.len() {
panic!(
"series \"{}\" has {} values but there are {} categories",
s.name,
s.values.len(),
categories.len()
);
}
}
let names: Vec<String> = series.iter().map(|s| s.name.clone()).collect();
let mut legend: Vec<LegendItem> = Vec::new();
let mut top = TOP_WITHOUT_LEGEND;
if series.len() > 1 {
let rows = legend_rows(&names, width - 2.0 * EDGE, CHAR_WIDTH, SWATCH, LEGEND_GAP, LEGEND_ROW);
legend = rows
.iter()
.map(|it| LegendItem {
label: it.label.clone(),
row: it.row,
x: round_float(it.x + EDGE, 2),
y: round_float(it.y + EDGE, 2),
width: it.width,
text_x: round_float(it.text_x + EDGE, 2),
})
.collect();
top = EDGE + (rows[rows.len() - 1].row + 1) as f64 * LEGEND_ROW + EDGE;
}
let stacked = if spec.mode == "stacked" {
let values: Vec<Vec<f64>> = series.iter().map(|s| s.values.clone()).collect();
Some(stack(&values, "diverging"))
} else {
None
};
let (mut lo, mut hi) = (0.0f64, 0.0f64);
for (i, s) in series.iter().enumerate() {
for (j, v) in s.values.iter().enumerate() {
let ends = match &stacked {
None => vec![*v],
Some(st) => vec![st[i][j].y0, st[i][j].y1],
};
for e in ends {
if e < lo {
lo = e;
}
if e > hi {
hi = e;
}
}
}
}
if lo == hi {
hi = 1.0;
}
let y_count = (((height - top - BOTTOM) / 50.0).floor() as i64).max(2);
let y_domain = nice_domain(&[lo, hi], y_count).domain;
let y_step = tick_step(y_domain[0], y_domain[1], y_count);
let widest = nice_ticks(y_domain[0], y_domain[1], y_count)
.iter()
.map(|v| format_tick(*v, y_step).chars().count())
.max()
.unwrap_or(0);
let left = TICK_SIZE + LABEL_PADDING + widest as f64 * CHAR_WIDTH + EDGE;
let plot = plot_area(width, height, &Margins { top, right: RIGHT, bottom: BOTTOM, left });
let bottom_y = plot.y + plot.height;
let y_range = vec![bottom_y, plot.y];
let y = |v: f64| linear_scale(&y_domain, &y_range, v, false);
let y_axis = number_axis(&y_domain, &y_range, y_count, "left", plot.x, TICK_SIZE);
let x_range = vec![plot.x, plot.x + plot.width];
let x_axis = band_axis(categories, &x_range, CATEGORY_PADDING_INNER, CATEGORY_PADDING_OUTER, "bottom", bottom_y, TICK_SIZE);
let colors = palette("okabe-ito", series.len() as i64, true);
let mut specs: Vec<BarSpec> = Vec::new();
let mut meta: Vec<(String, String, f64, String)> = Vec::new();
for (i, s) in series.iter().enumerate() {
for (j, c) in categories.iter().enumerate() {
let outer = band_scale(categories, &x_range, c, CATEGORY_PADDING_INNER, CATEGORY_PADDING_OUTER, 0.5);
let bar = match &stacked {
Some(st) => BarSpec { band: outer.start, thickness: outer.width, base: y(st[i][j].y0), value: y(st[i][j].y1) },
None if series.len() == 1 => {
BarSpec { band: outer.start, thickness: outer.width, base: y(0.0), value: y(s.values[j]) }
}
None => {
let inner = band_scale(&names, &[outer.start, outer.start + outer.width], &s.name, SERIES_PADDING, 0.0, 0.5);
BarSpec { band: inner.start, thickness: inner.width, base: y(0.0), value: y(s.values[j]) }
}
};
specs.push(bar);
meta.push((s.name.clone(), c.clone(), s.values[j], colors[i].clone()));
}
}
let rects = bar_rects(&specs, "vertical");
let bars: Vec<ChartBar> = meta
.into_iter()
.zip(rects)
.map(|((series, category, value, color), rect)| ChartBar { series, category, value, color, rect })
.collect();
BarChart {
width: round_float(width, 2),
height: round_float(height, 2),
grid_lines: grid_lines(&y_axis, plot.width),
plot,
x_axis,
y_axis,
bars,
colors,
legend,
}
}
pub fn bar_chart_spec_from_value(v: &Value) -> BarChartSpec {
BarChartSpec {
width: v.get("width").as_f64(),
height: v.get("height").as_f64(),
categories: v.get("categories").as_arr().iter().map(|c| c.as_str().to_string()).collect(),
series: v
.get("series")
.as_arr()
.iter()
.map(|s| BarSeries {
name: s.get("name").as_str().to_string(),
values: s.get("values").as_arr().iter().map(|x| x.as_f64()).collect(),
})
.collect(),
mode: v.get("mode").as_str().to_string(),
}
}
pub fn bar_chart_to_value(c: &BarChart) -> Value {
Value::obj(vec![
("width", Value::Float(c.width)),
("height", Value::Float(c.height)),
("plot", plot_area_to_value(&c.plot)),
("xAxis", axis_to_value(&c.x_axis)),
("yAxis", axis_to_value(&c.y_axis)),
("gridLines", Value::Arr(c.grid_lines.iter().map(line_to_value).collect())),
(
"bars",
Value::Arr(
c.bars
.iter()
.map(|b| {
Value::obj(vec![
("series", Value::str(&b.series)),
("category", Value::str(&b.category)),
("value", Value::Float(b.value)),
("color", Value::str(&b.color)),
("rect", rect_to_value(&b.rect)),
])
})
.collect(),
),
),
("colors", Value::Arr(c.colors.iter().map(|s| Value::str(s)).collect())),
("legend", Value::Arr(c.legend.iter().map(legend_item_to_value).collect())),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
bar_chart_to_value(&bar_chart(&bar_chart_spec_from_value(&args[0])))
}