チャート
リストのシグナルの行を、棒、積み上げ棒、横棒、折れ線、面、ドーナツで描くチャートです。値の軸の目盛りはブラウザが値に合わせて決めます。
実行結果
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::bar | label を名前に持ち、rows を描き、category でラベルを付ける棒グラフ |
Chart::stacked_bar | label を名前に持ち、rows を描き、category でラベルを付ける積み上げ棒グラフ |
Chart::horizontal_bar | label を名前に持ち、rows を描き、category でラベルを付ける横棒グラフ |
Chart::line | label を名前に持ち、rows を描き、category でラベルを付ける折れ線グラフ |
Chart::area | label を名前に持ち、rows を描き、category でラベルを付ける面グラフ |
series | 数値のフィールド field を color で描く、name という名前の系列を足します。 |
max_labels | カテゴリの軸にラベルを付ける行を最大 count 個にします。設定するまでは 12 個です。 |
format | 値と軸のラベルを format の形で書きます。 |
DonutChart::new | label を名前に持ち、rows の value を category でラベルを付けた弧で描くドーナツグラフ |
token::chart_1 | チャートの 1 つめの系列の色 |