From 826ffd902263d4cfc47d1d46d12e3c2231bdabeb Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Thu, 29 Jan 2026 13:08:46 +0100 Subject: [PATCH] ui: polish voting results chart --- .../voting/VotingResultsChart.astro | 170 +++++++++++------- 1 file changed, 108 insertions(+), 62 deletions(-) diff --git a/frontend/src/components/voting/VotingResultsChart.astro b/frontend/src/components/voting/VotingResultsChart.astro index e46eb1c..2f3dae9 100644 --- a/frontend/src/components/voting/VotingResultsChart.astro +++ b/frontend/src/components/voting/VotingResultsChart.astro @@ -41,6 +41,7 @@ const { proposalId, apiBase = '' } = Astro.props; private data: any; private apiBase: string; private enabled = false; + private tooltip?: HTMLElement; constructor(container: HTMLElement) { this.container = container; @@ -161,9 +162,10 @@ const { proposalId, apiBase = '' } = Astro.props; const isWinner = index === 0; const safeLabel = this.escapeHtml(result.label); + const safeValue = this.escapeHtml(this.formatScore(score, method)); return ` -
+
#${result.rank || index + 1} ${safeLabel} @@ -187,8 +189,11 @@ const { proposalId, apiBase = '' } = Astro.props;
${barsHtml}
+ `; + this.attachChartInteractions(chartArea); + // Trigger animation setTimeout(() => { chartArea.querySelectorAll('.bar-fill').forEach((bar) => { @@ -197,6 +202,54 @@ const { proposalId, apiBase = '' } = Astro.props; }, 100); } + attachChartInteractions(chartArea: HTMLElement): void { + const tooltip = chartArea.querySelector('#chart-tooltip'); + if (!tooltip) return; + this.tooltip = tooltip; + + const show = (bar: HTMLElement) => { + if (!this.tooltip) return; + const label = bar.dataset.label || ''; + const value = bar.dataset.value || ''; + this.tooltip.textContent = `${label} • ${value}`; + this.tooltip.setAttribute('aria-hidden', 'false'); + this.tooltip.classList.add('is-visible'); + + const rect = bar.getBoundingClientRect(); + const left = Math.max(12, Math.min(window.innerWidth - 12, rect.left + rect.width * 0.35)); + const top = Math.max(12, rect.top - 10); + this.tooltip.style.left = `${left}px`; + this.tooltip.style.top = `${top}px`; + }; + + const hide = () => { + if (!this.tooltip) return; + this.tooltip.setAttribute('aria-hidden', 'true'); + this.tooltip.classList.remove('is-visible'); + }; + + chartArea.addEventListener('mouseover', (e) => { + const target = e.target as HTMLElement | null; + const bar = target?.closest?.('.result-bar') as HTMLElement | null; + if (bar) show(bar); + }); + + chartArea.addEventListener('mouseout', (e) => { + const related = (e as MouseEvent).relatedTarget as HTMLElement | null; + if (!related || !related.closest?.('.result-bar')) hide(); + }); + + chartArea.addEventListener('focusin', (e) => { + const target = e.target as HTMLElement | null; + const bar = target?.closest?.('.result-bar') as HTMLElement | null; + if (bar) show(bar); + }); + + chartArea.addEventListener('focusout', () => { + hide(); + }); + } + renderDetails(): void { const detailsArea = this.container.querySelector('#details-area'); const roundsArea = this.container.querySelector('#rounds-area'); @@ -366,7 +419,7 @@ const { proposalId, apiBase = '' } = Astro.props;