Chart legends: the conventional pattern, nothing cleverer
Some checks are pending
build-gate / build (push) Waiting to run

Driver, after rejecting both experiments: "I cannot scroll right, and the
column numbers have this super white background -- it looks odd, it was to
be invisible... I am not impressed with that design. Use conventional
designs only."

He is right, and the two rejected variants earned it: scroll-to-read hid
the numbers' geometry behind a gesture, and the frozen column needed a
painted backing precisely BECAUSE something could slide beneath it. The
conventional row every chart library ships has neither problem: swatch,
label that ELLIPSIZES at the available width (full name in the row's
existing tooltip), count right-aligned in a fixed-width slot. No horizontal
scroll, no sticky, no background -- the number needs no backing because
nothing can ever slide beneath it.

Measured, both legends: transparent count background, position static,
right-aligned, ONE shared right edge, labels ellipsizing, zero horizontal
scroll. Screenshot read before pushing.
This commit is contained in:
doxa-maps-88655c70 2026-09-23 22:14:33 +00:00
parent 80d5981229
commit ab83fd4ca7
8 changed files with 38 additions and 61 deletions

View file

@ -189,17 +189,8 @@ useShadowStyles(`
reclaim vertical space, lower -webkit-line-clamp or trim the LABEL TEXT upstream — do NOT
restore white-space:nowrap + ellipsis here, and do not re-cap this to the donut's height:
that cap is precisely what forced the unreadable ellipsis the Driver rejected. */
/* ★ A REAL COLUMN, NOT PER-ROW FLEX (Driver 2026-09-23: "when I scroll right on the region
chart the numbers don't stay justified right, not uniform... they look less like a column and
more like cards"). He is describing exactly what flex rows do under horizontal scroll: each
row is its own width (min-width:max-content), so each count sat at ITS row's right edge --
rows of different lengths put their numbers at different x positions the moment the container
scrolled. sticky can only pin an element within its own row's box, so it could not save this.
The container is now a two-column GRID (label 1fr, count auto) and every row joins it via
subgrid: ALL counts share the single auto track, so the column is uniform at every scroll
position by construction -- the track IS the column. */
.rd-legend{ display:table; border-collapse:separate; border-spacing:0 2px; font-size:10.5px; color:var(--dx-text, #3b463d); max-height:120px;
overflow-y:auto; overflow-x:auto; flex:1 1 auto; min-width:0;
.rd-legend{ display:flex; flex-direction:column; gap:2px; font-size:10.5px; color:var(--dx-text, #3b463d); max-height:120px;
overflow-y:auto; overflow-x:hidden; flex:1 1 auto; min-width:0;
scrollbar-width:thin; scrollbar-color:var(--dx-scroll-thumb, #c4c8c0) transparent; }
.rd-legend::-webkit-scrollbar{ width:6px; height:6px; }
.rd-legend::-webkit-scrollbar-track{ background:transparent; }
@ -209,21 +200,18 @@ useShadowStyles(`
now two lines tall, and centring would float the swatch and the count in the middle of that
block, detached from the first line of text they label. The count is top-aligned with a 1px
nudge (.rd-li b) so it reads as belonging to the row's first line. */
/* ★ REAL TABLE CSS, ON THE DRIVER'S OWN PRESCRIPTION (2026-09-23): "the number column needs to
use some type of TABLE css so that the numbers really are set apart as a column, uniform and
justified right, so that it does not move when I scroll left and right." He is right that
table layout is the honest tool here: display:table guarantees every row's second cell shares
ONE column edge in every browser ever shipped -- no subgrid support question, no per-row flex
widths, the column is a column by the layout algorithm itself. (The previous subgrid attempt
measured correct in the built-tree browser but subgrid is younger than table layout by 25
years; the table needs no such caveat.)
The count cell is ALSO sticky right:0 with an opaque background, so when a long label forces
horizontal scroll the numbers stay pinned at the visible edge while the words slide beneath --
the frozen-column behaviour he asked for earlier, now on top of true column geometry. */
.rd-li{ display:table-row; cursor:pointer; }
.rd-li > i{ display:table-cell; width:9px; min-width:9px; padding-top:4px; }
.rd-li .rd-lt{ display:table-cell; padding:1px 6px 1px 5px; }
.rd-li b{ display:table-cell; } /* max-content: the row is as wide as its label, so the scrollport has something to scroll */
/* ★ THE CONVENTIONAL LEGEND ROW, on the Driver's final ruling (2026-09-23): "I am not
impressed with that design -- use conventional designs only", after rejecting both the
scroll-to-read variant ("I cannot scroll right") and the frozen column's painted backing
("super white background, it looks odd; it was to be invisible"). This is the pattern every
chart library ships: swatch, label that ELLIPSIZES at the available width, count right-
aligned in a fixed-width slot. No horizontal scroll, no sticky, no background -- the number
needs no backing because nothing can ever slide beneath it. The full label lives in the
row's existing :title tooltip. Rows stretch to the container, so every count shares one
right edge by ordinary flex geometry. */
.rd-li{ display:flex; align-items:center; gap:5px; cursor:pointer; padding:1px 2px; border-radius:3px; }
.rd-li > i{ flex:0 0 auto; }
.rd-li .rd-lt{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } /* max-content: the row is as wide as its label, so the scrollport has something to scroll */
.rd-li:hover{ background:var(--dx-surface, #f3f3f1); } .rd-li.on{ background:#fff4ea; font-weight:700; color:var(--rd-ink, #3b463d); }
/* margin-top nudges keep the swatch and the count optically on the label's FIRST line now that
a row can be two lines tall. */
@ -250,11 +238,8 @@ useShadowStyles(`
a little air. sticky right:0 (from the frozen-column fix) is what makes it immovable while the
label scrolls beneath. Both donut legends share .rd-li, so religion and region get this from
the one rule -- which is exactly why it lives here and not per-chart. */
.rd-li b{ position:sticky; right:0; z-index:1;
min-width:3.4em; text-align:right; white-space:nowrap; vertical-align:top;
background:var(--rd-panel, #fff); box-shadow:-6px 0 6px -4px var(--rd-panel, #fff);
color:var(--rd-muted, #6d766e); font-variant-numeric:tabular-nums;
padding:1px 2px 1px 6px; }
.rd-li b{ flex:0 0 auto; min-width:3.4em; text-align:right;
color:var(--rd-muted, #6d766e); font-variant-numeric:tabular-nums; }
/* The label is the ONLY item allowed to shrink. It used to be a bare text node, which in a
nowrap flex row cannot shrink at all: the row grew past .rd-legend (overflow-x:hidden) and
carried the VALUE out of sight — "Ethnoreligion - Chinese Folk Religion" rendered with no

View file

@ -574,24 +574,20 @@
.rd-donut.has-sel .rd-slice{ opacity:.42; }
.rd-slice.on, .rd-donut.has-sel .rd-slice.on{ opacity:1; stroke:var(--rd-ink, #3b463d); stroke-width:2.5;
transform:scale(1.08); filter:drop-shadow(0 1px 2px rgba(31,39,51,.35)); }
.rd-legend{ display:table; border-collapse:separate; border-spacing:0 2px; font-size:10.5px; color:var(--dx-text, #3b463d); max-height:120px;
overflow-y:auto; overflow-x:auto; flex:1 1 auto; min-width:0;
.rd-legend{ display:flex; flex-direction:column; gap:2px; font-size:10.5px; color:var(--dx-text, #3b463d); max-height:120px;
overflow-y:auto; overflow-x:hidden; flex:1 1 auto; min-width:0;
scrollbar-width:thin; scrollbar-color:var(--dx-scroll-thumb, #c4c8c0) transparent; }
.rd-legend::-webkit-scrollbar{ width:6px; height:6px; }
.rd-legend::-webkit-scrollbar-track{ background:transparent; }
.rd-legend::-webkit-scrollbar-thumb{ background:var(--dx-scroll-thumb, #c4c8c0); border-radius:6px; }
.rd-legend::-webkit-scrollbar-thumb:hover{ background:#9aa197; }
.rd-li{ display:table-row; cursor:pointer; }
.rd-li > i{ display:table-cell; width:9px; min-width:9px; padding-top:4px; }
.rd-li .rd-lt{ display:table-cell; padding:1px 6px 1px 5px; }
.rd-li b{ display:table-cell; }
.rd-li{ display:flex; align-items:center; gap:5px; cursor:pointer; padding:1px 2px; border-radius:3px; }
.rd-li > i{ flex:0 0 auto; }
.rd-li .rd-lt{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rd-li:hover{ background:var(--dx-surface, #f3f3f1); } .rd-li.on{ background:#fff4ea; font-weight:700; color:var(--rd-ink, #3b463d); }
.rd-li i{ width:9px; height:9px; border-radius:2px; flex:0 0 auto; margin-top:3px; }
.rd-li b{ position:sticky; right:0; z-index:1;
min-width:3.4em; text-align:right; white-space:nowrap; vertical-align:top;
background:var(--rd-panel, #fff); box-shadow:-6px 0 6px -4px var(--rd-panel, #fff);
color:var(--rd-muted, #6d766e); font-variant-numeric:tabular-nums;
padding:1px 2px 1px 6px; }
.rd-li b{ flex:0 0 auto; min-width:3.4em; text-align:right;
color:var(--rd-muted, #6d766e); font-variant-numeric:tabular-nums; }
.rd-lt{ flex:1 1 auto; min-width:0; white-space:nowrap; line-height:1.28; overflow:visible; }
.rd-bars{ display:flex; flex-direction:column; gap:3px; }
.rd-bar{ display:flex; align-items:center; gap:6px; cursor:pointer; font-size:10.5px; padding:1px 2px; border-radius:3px; }

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
<script src="../../dashboard-plus-grid.js" integrity="sha384-lFahIRCFBh2RLzn3g9lHxF9rvYyK1YL2zPkJ+FwDVYQ8+aEcv2MPphETm99tMmfr" crossorigin="anonymous"></script>
<script src="../../dashboard-plus-grid.js" integrity="sha384-yl2t7RiaXtN9JiEX6E/96LPcXR2UKH2QrsDgDGLHjpt5cx5jLkAGlbKCNJvf96vB" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
<script src="../dashboard-plus-grid.js" integrity="sha384-lFahIRCFBh2RLzn3g9lHxF9rvYyK1YL2zPkJ+FwDVYQ8+aEcv2MPphETm99tMmfr" crossorigin="anonymous"></script>
<script src="../dashboard-plus-grid.js" integrity="sha384-yl2t7RiaXtN9JiEX6E/96LPcXR2UKH2QrsDgDGLHjpt5cx5jLkAGlbKCNJvf96vB" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -193,7 +193,7 @@
"bundle": "dashboard-plus-grid",
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
"output": "doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js",
"integrity": "sha384-lFahIRCFBh2RLzn3g9lHxF9rvYyK1YL2zPkJ+FwDVYQ8+aEcv2MPphETm99tMmfr",
"integrity": "sha384-yl2t7RiaXtN9JiEX6E/96LPcXR2UKH2QrsDgDGLHjpt5cx5jLkAGlbKCNJvf96vB",
"elements": [
"dashboard-plus-grid"
],
@ -213,7 +213,7 @@
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
"embed": {
"iframe": "<iframe src=\"./dashboard-plus-grid/dashboard-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-lFahIRCFBh2RLzn3g9lHxF9rvYyK1YL2zPkJ+FwDVYQ8+aEcv2MPphETm99tMmfr\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-yl2t7RiaXtN9JiEX6E/96LPcXR2UKH2QrsDgDGLHjpt5cx5jLkAGlbKCNJvf96vB\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
},
"instances": [
{
@ -878,7 +878,7 @@
"bundle": "research-dashboard",
"scriptUrl": "./research-dashboard/research-dashboard.js",
"output": "doxa-maps/research-dashboard/research-dashboard.js",
"integrity": "sha384-QtXmg1sKDCXljaSvuGeRuUkpFuNWiAMRsz2Sk5tluaUTXkVfz8y2MKWdGLVCdSig",
"integrity": "sha384-OMWktfdHg6hivE32ReD3LQIwg3SxgBdgdltPhfWFSEUybEPU6fsynv4PP22z4yEn",
"elements": [
"research-dashboard-map"
],
@ -894,7 +894,7 @@
"scriptUrl": "./research-dashboard/research-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-QtXmg1sKDCXljaSvuGeRuUkpFuNWiAMRsz2Sk5tluaUTXkVfz8y2MKWdGLVCdSig\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-OMWktfdHg6hivE32ReD3LQIwg3SxgBdgdltPhfWFSEUybEPU6fsynv4PP22z4yEn\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
},
"bundle": "research-dashboard"
}

View file

@ -576,24 +576,20 @@
.rd-donut.has-sel .rd-slice{ opacity:.42; }
.rd-slice.on, .rd-donut.has-sel .rd-slice.on{ opacity:1; stroke:var(--rd-ink, #3b463d); stroke-width:2.5;
transform:scale(1.08); filter:drop-shadow(0 1px 2px rgba(31,39,51,.35)); }
.rd-legend{ display:table; border-collapse:separate; border-spacing:0 2px; font-size:10.5px; color:var(--dx-text, #3b463d); max-height:120px;
overflow-y:auto; overflow-x:auto; flex:1 1 auto; min-width:0;
.rd-legend{ display:flex; flex-direction:column; gap:2px; font-size:10.5px; color:var(--dx-text, #3b463d); max-height:120px;
overflow-y:auto; overflow-x:hidden; flex:1 1 auto; min-width:0;
scrollbar-width:thin; scrollbar-color:var(--dx-scroll-thumb, #c4c8c0) transparent; }
.rd-legend::-webkit-scrollbar{ width:6px; height:6px; }
.rd-legend::-webkit-scrollbar-track{ background:transparent; }
.rd-legend::-webkit-scrollbar-thumb{ background:var(--dx-scroll-thumb, #c4c8c0); border-radius:6px; }
.rd-legend::-webkit-scrollbar-thumb:hover{ background:#9aa197; }
.rd-li{ display:table-row; cursor:pointer; }
.rd-li > i{ display:table-cell; width:9px; min-width:9px; padding-top:4px; }
.rd-li .rd-lt{ display:table-cell; padding:1px 6px 1px 5px; }
.rd-li b{ display:table-cell; }
.rd-li{ display:flex; align-items:center; gap:5px; cursor:pointer; padding:1px 2px; border-radius:3px; }
.rd-li > i{ flex:0 0 auto; }
.rd-li .rd-lt{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rd-li:hover{ background:var(--dx-surface, #f3f3f1); } .rd-li.on{ background:#fff4ea; font-weight:700; color:var(--rd-ink, #3b463d); }
.rd-li i{ width:9px; height:9px; border-radius:2px; flex:0 0 auto; margin-top:3px; }
.rd-li b{ position:sticky; right:0; z-index:1;
min-width:3.4em; text-align:right; white-space:nowrap; vertical-align:top;
background:var(--rd-panel, #fff); box-shadow:-6px 0 6px -4px var(--rd-panel, #fff);
color:var(--rd-muted, #6d766e); font-variant-numeric:tabular-nums;
padding:1px 2px 1px 6px; }
.rd-li b{ flex:0 0 auto; min-width:3.4em; text-align:right;
color:var(--rd-muted, #6d766e); font-variant-numeric:tabular-nums; }
.rd-lt{ flex:1 1 auto; min-width:0; white-space:nowrap; line-height:1.28; overflow:visible; }
.rd-bars{ display:flex; flex-direction:column; gap:3px; }
.rd-bar{ display:flex; align-items:center; gap:6px; cursor:pointer; font-size:10.5px; padding:1px 2px; border-radius:3px; }

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <research-dashboard-map> custom element. -->
<script src="../research-dashboard.js" integrity="sha384-QtXmg1sKDCXljaSvuGeRuUkpFuNWiAMRsz2Sk5tluaUTXkVfz8y2MKWdGLVCdSig" crossorigin="anonymous"></script>
<script src="../research-dashboard.js" integrity="sha384-OMWktfdHg6hivE32ReD3LQIwg3SxgBdgdltPhfWFSEUybEPU6fsynv4PP22z4yEn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: