aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/components
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-04 23:24:31 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-04 23:24:31 +0900
commitbb0d752b04eae60b88ec162d07e9409483159d95 (patch)
treea023bb7b3e87f31ecd840839b2031529ef050f40 /src/components
parentbbefd07f238dd0cecb82fa75d36ffcffe9613ef8 (diff)
sanpi.mdx: 賛否一覧に令和7年6月・4月臨時会・令和6年12月を追加
各表の下に安竹側・反対会派の賛否理由を簡潔に記載した。安竹列のセル着色、請願バッジ、見出しの改行、上部の会派別グラフ(安竹を先頭・太字、れ新(中倉)・市民(水口))を追加。生成スクリプトを同じ並び・氏名なし・請願バッジ・見出し改行に対応させた。
Diffstat (limited to 'src/components')
-rw-r--r--src/components/SanpiTable.astro122
1 files changed, 120 insertions, 2 deletions
diff --git a/src/components/SanpiTable.astro b/src/components/SanpiTable.astro
index 31c8678..1040bb4 100644
--- a/src/components/SanpiTable.astro
+++ b/src/components/SanpiTable.astro
@@ -11,6 +11,114 @@
// タップがホバーとして扱われ、ツールチップが残るのを避けるため)。
const CAN_HOVER = window.matchMedia("(hover: hover) and (pointer: fine)").matches;
+ // 「与党寄り」の基準にする会派(見出しの短名)。
+ const REFERENCE = "フォ";
+ // グラフの先頭に固定する議員(安竹洋平)。
+ const ME_NAME = "安竹洋平";
+ // 一人会派のグラフ表示名(議員名を添える)。
+ const DISPLAY_NAME = { "れ新": "れ新(中倉)", "市民": "市民(水口)" };
+
+ function groupLabel(headerText) {
+ const text = headerText.replace(/\s+/g, "").trim();
+ const m = text.match(/^(.+?)((.+?))$/);
+ if (!m) return text;
+ const name = m[1];
+ const label = m[2];
+ // 会派(N人)は会派そのもの。
+ if (/^\d+人$/.test(label)) return name;
+ // 個人の(政和)は政和会に合算する(外山議員は常に、石津議員も一人でないときは政和会)。
+ if (label === "政和") return "政和";
+ // (一人)などは議員ごとに分ける。
+ return name;
+ }
+
+ function voteChar(cell) {
+ const text = (cell.textContent || "").replace(/\s+/g, "");
+ if (/^[〇○◯]$/.test(text)) return "〇";
+ if (/^[×✕✖]$/.test(text)) return "×";
+ return null;
+ }
+
+ // 各会派の賛否が、フォーラム小平とどれだけ一致したか(=与党寄りか)をグラフにする。
+ function buildChart(content) {
+ const host = content.querySelector(".sanpi-chart");
+ if (!host) return;
+
+ const stats = new Map();
+ content.querySelectorAll("table").forEach((table) => {
+ const headers = [...table.querySelectorAll("thead th")].map((th) => th.textContent.trim());
+ const refIdx = headers.findIndex((h) => groupLabel(h) === REFERENCE);
+ if (refIdx < 0) return;
+
+ table.querySelectorAll("tbody tr").forEach((tr) => {
+ const cells = [...tr.children];
+ const refVote = voteChar(cells[refIdx]);
+ if (!refVote) return;
+ cells.forEach((cell, i) => {
+ if (i < 2 || i === refIdx) return;
+ const group = groupLabel(headers[i] || "");
+ if (!group || group === REFERENCE) return;
+ const vote = voteChar(cell);
+ if (!vote) return;
+ const s = stats.get(group) || { match: 0, total: 0 };
+ s.total += 1;
+ if (vote === refVote) s.match += 1;
+ stats.set(group, s);
+ });
+ });
+ });
+
+ const rows = [...stats.entries()]
+ .map(([group, s]) => ({ group, rate: s.total ? s.match / s.total : 0, total: s.total }))
+ .filter((row) => row.total > 0)
+ .sort((a, b) => {
+ if (a.group === ME_NAME) return -1;
+ if (b.group === ME_NAME) return 1;
+ return b.rate - a.rate || a.group.localeCompare(b.group, "ja");
+ });
+
+ if (!rows.length) {
+ host.hidden = true;
+ return;
+ }
+
+ const caption = document.createElement("p");
+ caption.className = "sanpi-chart-caption";
+ caption.textContent =
+ "フォーラム小平と賛否が一致した割合です(〇・×が同じ議案のみを数え、—は除く)。高いほど与党寄り、低いほど野党寄りです。件数が少ない会派は参考程度にご覧ください。";
+
+ const list = document.createElement("ul");
+ list.className = "sanpi-chart-list";
+ rows.forEach(({ group, rate, total }) => {
+ const percent = Math.round(rate * 100);
+ const hue = Math.round(210 * rate);
+ const item = document.createElement("li");
+ item.className = "sanpi-chart-item";
+
+ const label = document.createElement("span");
+ label.className = "sanpi-chart-label";
+ label.textContent = DISPLAY_NAME[group] || group;
+ if (group === ME_NAME) label.style.fontWeight = "700";
+
+ const bar = document.createElement("span");
+ bar.className = "sanpi-chart-bar";
+ const fill = document.createElement("span");
+ fill.className = "sanpi-chart-fill";
+ fill.style.width = `${percent}%`;
+ fill.style.background = `hsl(${hue} 70% 45%)`;
+ bar.appendChild(fill);
+
+ const val = document.createElement("span");
+ val.className = "sanpi-chart-val";
+ val.innerHTML = `${percent}%<small>(${total}件)</small>`;
+
+ item.append(label, bar, val);
+ list.appendChild(item);
+ });
+
+ host.append(caption, list);
+ }
+
function enhanceSanpiTables() {
const marker = document.querySelector(".sanpi-marker");
if (!marker) return;
@@ -52,8 +160,16 @@
const span = document.createElement("span");
span.className = "sanpi-title";
- while (cell.firstChild) span.appendChild(cell.firstChild);
- cell.appendChild(span);
+ // 「請願」バッジは省略の対象外にして、つねに表示する。
+ const badge = cell.querySelector(":scope > .sanpi-shugan");
+ Array.from(cell.childNodes).forEach((node) => {
+ if (node !== badge) span.appendChild(node);
+ });
+ if (badge) {
+ cell.insertBefore(span, badge.nextSibling);
+ } else {
+ cell.appendChild(span);
+ }
const full = span.textContent.trim();
span.dataset.full = full;
@@ -86,6 +202,8 @@
window.addEventListener("scroll", hideTip, { passive: true });
window.addEventListener("resize", hideTip);
+
+ buildChart(content);
}
if (document.readyState === "loading") {