From b81e39a8fd6e1b347425d6f9944f90743a62d2a4 Mon Sep 17 00:00:00 2001 From: Jeffrey Lewis Date: Mon, 7 Sep 2026 22:56:31 +0000 Subject: [PATCH] Make the person page's ideology bucket tooltip work on touch (Phase 2, site 5 of 5) Last of the 5 tooltip sites from #449. Simpler than the party/committee ideology charts: this one binds mouseover/mouseout once to a single shared selection (all histogram bars), not per-point in a loop, so it uses the simple combined addTouchTooltip (bindTouchTap + bindTouchDismiss together) like the map tooltip sites, not the split version. Verified with headless Chromium on /person/29147 (found the correct bar to test by reading the bound datum for window.memberIdealBucket, since the tooltip only shows content for the member's own bucket): - Desktop (mouse): hover shows the correct ideology-score content and hides on mouseout - Touch (iPhone 13 emulation): tapping the member's bucket shows the tooltip with correct content; tapping elsewhere dismisses it; no page errors - Full existing 41-check suite (unrelated files, sanity check only) still passes This completes Phase 2 (#449) -- all 5 tooltip sites (vote map, party map + ideology chart, committee ideology chart, person ideology bucket) now work on touch as well as mouse. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013KJMYfgTNsxrmjHZpZKAHT --- static/js/personIdeology.js | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/static/js/personIdeology.js b/static/js/personIdeology.js index bd16d1a70..4b8b8593e 100644 --- a/static/js/personIdeology.js +++ b/static/js/personIdeology.js @@ -1,3 +1,26 @@ +// Tooltips bound only to mouseover/mouseout are invisible on touch +// devices, and it's not safe to just add a tap alongside them -- touch +// devices synthesize a mouseover/click sequence for a tap, but +// unreliably (a synthetic mouseout can follow right after the tap once +// whatever the tap triggers redraws the chart). This replaces the hover +// bindings entirely on coarse-pointer devices, driving the tooltip from +// taps only. +function addTouchTooltip(selection, showFn, hideFn) { + if (!window.matchMedia || !window.matchMedia("(pointer: coarse)").matches) return; + selection.on("mouseover", null).on("mouseout", null).on("mousemove", null); + selection.on("click.touchTooltip", function(d, i) { + d3.event.stopPropagation(); + showFn.call(this, d, i); + }); + document.addEventListener("click", function(e) { + var tappedInside = false; + selection.each(function() { + if (this === e.target || this.contains(e.target)) { tappedInside = true; } + }); + if (!tappedInside) { hideFn(); } + }); +} + $("#congSelector").change(reloadIdeology); $(".nav-tabs > li > a").click(switchTab); @@ -246,12 +269,14 @@ function fillLoyaltyDrawHist(error, data) }); // Attach the tooltips. + function showBucketTooltip(d) { if(d.x==memberIdealBucket) { labelTip.attr('class','d3-tip animate').offset([-10,0]).show(d); }} + function hideBucketTooltip(d) { labelTip.attr('class','d3-tip').hide(); } nominateHist.on("postRender", function(c){ - c.svg() - .selectAll("rect") - .call(labelTip) - .on('mouseover', function(d) { if(d.x==memberIdealBucket) { labelTip.attr('class','d3-tip animate').offset([-10,0]).show(d); }}) - .on('mouseout', function(d) { labelTip.attr('class','d3-tip').hide(); }) + var histBars = c.svg().selectAll("rect").call(labelTip); + histBars + .on('mouseover', showBucketTooltip) + .on('mouseout', hideBucketTooltip); + addTouchTooltip(histBars, showBucketTooltip, hideBucketTooltip); }); // Turn off y axis ticks, since they are not meaningful here.