diff options
Diffstat (limited to 'src/components/SanpiTable.astro')
| -rw-r--r-- | src/components/SanpiTable.astro | 122 |
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") { |
