English

チャート

リストのシグナルの行を、棒、積み上げ棒、横棒、折れ線、面、ドーナツで描くチャートです。値の軸の目盛りはブラウザが値に合わせて決めます。

実行結果

chart.rs
use hypersig::html::div;
use hypersig::style::pattern::stack;
use hypersig::style::value::rem;
use hypersig::{Page, Signals};
use hypersig_ui::{ButtonSize, ButtonVariant, Chart, DonutChart, Tabs, button, token};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum Kind {
    Bar,
    Stacked,
    Horizontal,
    Donut,
}

#[derive(Signals, Serialize, Deserialize)]
pub struct Month {
    name: String,
    desktop: i32,
    mobile: i32,
}

#[derive(Signals, Serialize, Deserialize)]
pub struct Browser {
    name: String,
    visitors: i32,
}

#[derive(Signals, Serialize, Deserialize)]
pub struct Traffic {
    kind: Kind,
    months: Vec<Month>,
    browsers: Vec<Browser>,
}

pub fn page() -> Page<Traffic> {
    // Each kind of chart draws the same two series of the same months.
    let visitors = |chart: Chart<Traffic, Month>| {
        chart
            .series(
                "デスクトップ",
                Month::desktop(),
                token::chart_1(),
            )
            .series(
                "モバイル",
                Month::mobile(),
                token::chart_2(),
            )
    };
    let label = "訪問者数";
    let names = [
        L { en: "Jan", ja: "1 月" },
        L { en: "Feb", ja: "2 月" },
        L { en: "Mar", ja: "3 月" },
        L { en: "Apr", ja: "4 月" },
        L { en: "May", ja: "5 月" },
        L { en: "Jun", ja: "6 月" },
    ];
    let months = names
        .into_iter()
        .zip([(186, 80), (305, 200), (237, 120), (73, 190), (209, 130), (214, 140)])
        .map(|(name, (desktop, mobile))| Month {
            name: site.t(name).to_owned(),
            desktop,
            mobile,
        })
        .collect();
    let browsers = ["Chrome", "Safari", "Firefox", "Edge"]
        .into_iter()
        .map(str::to_owned)
        .chain(["その他"
            .to_owned()])
        .zip([275, 200, 187, 173, 90])
        .map(|(name, visitors)| Browser { name, visitors })
        .collect();
    let grow = Traffic::months().update_where(
        |month| month.index().equals(3),
        Month::desktop(),
        |month| month.at(Month::desktop()) + 400,
    );
    let tabs = Tabs::new(label, Traffic::kind())
        .tab(
            Kind::Bar,
            "棒",
            visitors(Chart::bar(label, Traffic::months(), Month::name())).render(),
        )
        .tab(
            Kind::Stacked,
            "積み上げ",
            visitors(Chart::stacked_bar(label, Traffic::months(), Month::name())).render(),
        )
        .tab(
            Kind::Horizontal,
            "横棒",
            visitors(Chart::horizontal_bar(label, Traffic::months(), Month::name())).render(),
        )
        .tab(
            Kind::Donut,
            "ドーナツ",
            DonutChart::new(
                "ブラウザごとの訪問者数",
                Traffic::browsers(),
                Browser::name(),
                Browser::visitors(),
            )
            .render(),
        );
    Page::new(
        label,
        Traffic {
            kind: Kind::Bar,
            months,
            browsers,
        },
    )
    .child(
        div()
            .css(stack(rem(1)))
            .child(
                div().child(
                    button(ButtonVariant::Outline, ButtonSize::Sm)
                        .on_click(grow)
                        .text("4 月のデスクトップに 400 人足す"),
                ),
            )
            .child(tabs),
    )
}

アクセシビリティ

描画は viewBox="0 0 100 100" と preserveAspectRatio="none" を持つ <svg> で、枠に合わせて伸び縮みします。線は vector-effect="non-scaling-stroke" で太さを保ちます。軸のラベル、凡例、ポインタの下に出る行の値は、伸び縮みしない HTML で置きます。DonutChart は各行を pathLength="100" の円で描き、その stroke-dasharray に合計に占める行の割合を百分率で書きます。

描画は支援技術から隠します。スクリーンリーダーは、チャートの名前をキャプションに持つ、視覚的に隠した同じ行の <table> を読みます。ポインタの下に出る値をキーボードで見る手段は、この表のほかにありません。

系列は行の型の数値のフィールドを読むので、別の構造体のフィールドや文字列のフィールドを渡すとコンパイルエラーになります。目盛りは、最小値か 0 の小さいほう以下の切りのよい数から、最大値以上の切りのよい数までで、刻み幅は 10 の累乗の 1、2、2.5、5 倍です。ブラウザが max_of、min_of、let_in で計算するので、文やパッチで行が変わると目盛りも合わせ直され、負の値の棒は 0 の線から下に伸びます。棒や目盛りの線はそれぞれ全行から目盛りを計算するので、変更 1 回の手間は行数の 2 乗で増えます。

API

項目概要
Chart::barlabel を名前に持ち、rows を描き、category でラベルを付ける棒グラフ
Chart::stacked_barlabel を名前に持ち、rows を描き、category でラベルを付ける積み上げ棒グラフ
Chart::horizontal_barlabel を名前に持ち、rows を描き、category でラベルを付ける横棒グラフ
Chart::linelabel を名前に持ち、rows を描き、category でラベルを付ける折れ線グラフ
Chart::arealabel を名前に持ち、rows を描き、category でラベルを付ける面グラフ
series数値のフィールド field を color で描く、name という名前の系列を足します。
max_labelsカテゴリの軸にラベルを付ける行を最大 count 個にします。設定するまでは 12 個です。
format値と軸のラベルを format の形で書きます。
DonutChart::newlabel を名前に持ち、rows の value を category でラベルを付けた弧で描くドーナツグラフ
token::chart_1チャートの 1 つめの系列の色