Skip to main content

blocks/
chart.rs

1//! ` ```chart ` — one `label: number` per line, painted as bars.
2//!
3//! Deliberately the smallest block worth shipping: no axes, no scales, no
4//! legend. What it demonstrates is the shape of a block, and a chart library
5//! behind a fence tag is a different crate's job.
6
7use gpui::{AnyElement, App, Window, div, prelude::*, px, relative};
8use theme::{TextStyle, Theme, Typeset};
9
10pub const LANGUAGE: &str = "chart";
11
12/// The label column. Wide enough for a word, narrow enough that the bars still
13/// carry the row.
14const LABEL_WIDTH: f32 = 72.0;
15const BAR_HEIGHT: f32 = 10.0;
16const BAR_RADIUS: f32 = 3.0;
17const ROW_GAP: f32 = 4.0;
18const PADDING: f32 = 12.0;
19
20pub fn render(code: &str, _: &mut Window, cx: &mut App) -> Option<AnyElement> {
21    let rows: Vec<(&str, f32)> = code
22        .lines()
23        .filter_map(|line| {
24            let (label, value) = line.split_once(':')?;
25            Some((label.trim(), value.trim().parse().ok()?))
26        })
27        .collect();
28    // A fence holding nothing a chart can read is one still being typed, and
29    // the source is more use than an empty box.
30    if rows.is_empty() {
31        return None;
32    }
33
34    let theme = Theme::of(cx);
35    let peak = rows.iter().map(|(_, value)| *value).fold(1.0, f32::max);
36    Some(
37        div()
38            .flex()
39            .flex_col()
40            .gap(px(ROW_GAP))
41            .p(px(PADDING))
42            .rounded(px(Theme::BASE_RADIUS))
43            .bg(theme.ink(0.02))
44            .children(rows.into_iter().map(|(label, value)| {
45                div()
46                    .flex()
47                    .flex_row()
48                    .items_center()
49                    .gap(px(Theme::SPACE))
50                    .child(
51                        div()
52                            .flex_none()
53                            .w(px(LABEL_WIDTH))
54                            .text_style(TextStyle::Caption)
55                            .text_color(theme.text_muted)
56                            .child(label.to_string()),
57                    )
58                    .child(
59                        div().flex_1().child(
60                            div()
61                                .h(px(BAR_HEIGHT))
62                                .w(relative(value / peak))
63                                .rounded(px(BAR_RADIUS))
64                                .bg(theme.accent),
65                        ),
66                    )
67            }))
68            .into_any_element(),
69    )
70}