use super::funejson::Value; use super::math_round_float::round_float; // Space between the swatch and its text. const SWATCH_PADDING: f64 = 4.0; /// Legend items left to right, wrapping when the next would pass max_width. /// Text width is estimated as code points times char_width; no font is /// measured, so the answer is the same in every language. /// /// # Panics /// Panics if max_width is not positive or any other size is negative. pub fn legend_rows( labels: &[String], max_width: f64, char_width: f64, swatch_size: f64, gap: f64, row_height: f64, ) -> Vec { if !(max_width > 0.0) { panic!("maxWidth must be greater than zero, received {}", max_width); } if !(char_width >= 0.0 && swatch_size >= 0.0 && gap >= 0.0 && row_height >= 0.0) { panic!("legend sizes must not be negative"); } let mut items = Vec::new(); let mut row: i64 = 0; let mut x = 0.0; let mut on_row = 0; for label in labels { let width = swatch_size + SWATCH_PADDING + label.chars().count() as f64 * char_width; if on_row > 0 && x + width > max_width { row += 1; x = 0.0; on_row = 0; } items.push(LegendItem { label: label.clone(), row, x: round_float(x, 2), y: round_float(row as f64 * row_height, 2), width: round_float(width, 2), text_x: round_float(x + swatch_size + SWATCH_PADDING, 2), }); x = x + width + gap; on_row += 1; } items } pub fn legend_item_to_value(item: &LegendItem) -> Value { Value::obj(vec![ ("label", Value::str(&item.label)), ("row", Value::Int(item.row)), ("x", Value::Float(item.x)), ("y", Value::Float(item.y)), ("width", Value::Float(item.width)), ("textX", Value::Float(item.text_x)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let labels: Vec = args[0].as_arr().iter().map(|v| v.as_str().to_string()).collect(); let items = legend_rows( &labels, args[1].as_f64(), args[2].as_f64(), args[3].as_f64(), args[4].as_f64(), args[5].as_f64(), ); Value::Arr(items.iter().map(legend_item_to_value).collect()) }