Skip to content

Commit a315e3c

Browse files
realmarcinclaude
andcommitted
CommunityMech review cleanup: CVD-safe category palette, PaCMAP labels, data-table, de-emoji, footer, discussions/examples dark
Addresses the remaining dataviz/artifact-design review findings (stacked on the dark-mode branch): - Category color: collapse the 14-hue hand-picked rainbow to the dataviz skill's validated 8-slot CVD-safe categorical palette (blue/aqua/yellow/green/violet/ red/magenta/orange, assigned in the CVD-optimized order) with the long tail → grey "Other". Unique shape per coloured category retained so identity is never colour-alone; grey categories stay distinguished by shape. - PaCMAP relabel: drive the projection axis/label wording from the actual reduction method in umap_generator.py (pacmap→"PaCMAP", umap→"UMAP", sfdp→ "Layout") instead of hard-coded "UMAP". - Data-table fallback (<details>) added below the scatter on the UMAP + graph pages. - Removed decorative emoji from the landing, browser, and UMAP headers/cards. - Family footer no longer self-links CommunityMech (plain text). - Extended the dark theme + toggle + reduced-motion guard to the previously-missed app/discussions/ and docs/examples/ pages (vendored theme-toggle.js alongside). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9c89808 commit a315e3c

11 files changed

Lines changed: 509 additions & 114 deletions

File tree

app/discussions/index.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>Knowledge gaps & discussions</title>
77
<style>
8-
:root { --fg:#1a1a1a; --muted:#666; --line:#e2e2e2; --accent:#2a6; --bg:#fff; }
8+
:root { --fg:#1a1a1a; --muted:#666; --line:#e2e2e2; --accent:#2a6; --bg:#fff; --card:#fff; }
99
* { box-sizing: border-box; }
1010
body { font-family: -apple-system, system-ui, sans-serif; color: var(--fg); margin: 0; background: var(--bg); }
1111
header { padding: 1rem 1.25rem; border-bottom: 1px solid var(--line); }
@@ -27,6 +27,20 @@
2727
a { color: var(--accent); text-decoration: none; }
2828
a:hover { text-decoration: underline; }
2929
.empty { color: var(--muted); padding: 2rem; text-align: center; }
30+
/* ---- Dark theme (OS default; superseded by the toggle) ---- */
31+
@media (prefers-color-scheme: dark){
32+
:root:not([data-theme="light"]){--fg:#e8ecf4; --muted:#99a2b5; --line:#2a3142; --accent:#5fbf90; --bg:#101420; --card:#1a1f2e;}
33+
:root:not([data-theme="light"]) .badge{background:#243044; color:#cdd7ff;}
34+
:root:not([data-theme="light"]) .badge.gap{background:#3a2330; color:#f2b8c6;}
35+
:root:not([data-theme="light"]) .rationale{color:#c9d2e3;}
36+
}
37+
:root[data-theme="dark"]{--fg:#e8ecf4; --muted:#99a2b5; --line:#2a3142; --accent:#5fbf90; --bg:#101420; --card:#1a1f2e;}
38+
:root[data-theme="dark"] .badge{background:#243044; color:#cdd7ff;}
39+
:root[data-theme="dark"] .badge.gap{background:#3a2330; color:#f2b8c6;}
40+
:root[data-theme="dark"] .rationale{color:#c9d2e3;}
41+
@media (prefers-reduced-motion: reduce){
42+
*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important;}
43+
}
3044
</style>
3145
</head>
3246
<body>
@@ -123,5 +137,6 @@ <h1 id="title">Knowledge gaps &amp; discussions</h1>
123137
renderFacets(); render();
124138
})();
125139
</script>
140+
<script src="theme-toggle.js"></script>
126141
</body>
127142
</html>

app/discussions/theme-toggle.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/* Mech theme toggle — vendored byte-identical across all Mech sites.
2+
* Applies the saved theme to <html data-theme> (falling back to the OS
3+
* preference) before paint, and injects a self-contained fixed toggle button.
4+
* No dependencies; no per-page markup or CSS required beyond loading this file.
5+
* The page's own dark palette is supplied via :root[data-theme="dark"] and the
6+
* prefers-color-scheme media query in that page's stylesheet. */
7+
(function () {
8+
var KEY = 'mech-theme';
9+
var root = document.documentElement;
10+
11+
function osDark() {
12+
return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
13+
}
14+
function current() {
15+
return root.getAttribute('data-theme') || (osDark() ? 'dark' : 'light');
16+
}
17+
18+
// Apply the saved preference as early as possible to avoid a flash.
19+
try {
20+
var saved = localStorage.getItem(KEY);
21+
if (saved === 'dark' || saved === 'light') root.setAttribute('data-theme', saved);
22+
} catch (e) {}
23+
24+
function setIcon(btn) {
25+
var dark = current() === 'dark';
26+
btn.querySelector('.theme-toggle-icon').textContent = dark ? '☀' : '☾'; // sun / moon
27+
btn.setAttribute('aria-pressed', dark ? 'true' : 'false');
28+
}
29+
function toggle(btn) {
30+
var next = current() === 'dark' ? 'light' : 'dark';
31+
root.setAttribute('data-theme', next);
32+
try { localStorage.setItem(KEY, next); } catch (e) {}
33+
setIcon(btn);
34+
}
35+
function mount() {
36+
if (document.querySelector('.theme-toggle')) return;
37+
38+
var css =
39+
'.theme-toggle{position:fixed;bottom:18px;right:18px;z-index:2000;width:40px;height:40px;' +
40+
'border-radius:50%;border:1px solid var(--line,var(--border,rgba(128,128,128,.4)));' +
41+
'background:var(--card,var(--surface,var(--card-bg,#fff)));' +
42+
'color:var(--ink,var(--text,var(--fg,#1a1a1a)));font-size:18px;line-height:1;cursor:pointer;' +
43+
'display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0,0,0,.25);' +
44+
'transition:border-color .15s,transform .15s;}' +
45+
'.theme-toggle:hover{border-color:var(--accent,var(--primary,var(--brand-1,#888)));transform:translateY(-1px);}' +
46+
'.theme-toggle:focus-visible{outline:2px solid var(--accent,var(--primary,#888));outline-offset:2px;}' +
47+
'@media (prefers-reduced-motion: reduce){.theme-toggle{transition:none;}}';
48+
var st = document.createElement('style');
49+
st.textContent = css;
50+
document.head.appendChild(st);
51+
52+
var btn = document.createElement('button');
53+
btn.type = 'button';
54+
btn.className = 'theme-toggle';
55+
btn.setAttribute('aria-label', 'Toggle dark mode');
56+
btn.title = 'Toggle light / dark';
57+
btn.innerHTML = '<span class="theme-toggle-icon" aria-hidden="true"></span>';
58+
btn.addEventListener('click', function () { toggle(btn); });
59+
document.body.appendChild(btn);
60+
setIcon(btn);
61+
62+
// If the visitor has no explicit choice, follow live OS-theme changes.
63+
if (window.matchMedia) {
64+
try {
65+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
66+
if (!root.getAttribute('data-theme')) setIcon(btn);
67+
});
68+
} catch (e) {}
69+
}
70+
}
71+
72+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount);
73+
else mount();
74+
})();

docs/browser.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,8 +595,8 @@ <h3>Metal Relevance</h3>
595595
<!-- Right side: Community grid -->
596596
<div class="content-area">
597597
<div class="umap-link">
598-
<h2>📊 Explore Community Relationships</h2>
599-
<p>View the UMAP projection of all communities based on their taxonomic composition embeddings from KG-Microbe.</p>
598+
<h2>Explore Community Relationships</h2>
599+
<p>View the PaCMAP projection of all communities based on their taxonomic composition embeddings from KG-Microbe.</p>
600600
<a href="community_umap.html">View Community Embedding Space →</a>
601601
</div>
602602

docs/community_graph.html

Lines changed: 126 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,50 @@
442442
flex: 0 0 100%;
443443
}
444444
}
445+
/* Accessible data-table fallback (dataviz #10) */
446+
.data-table-details {
447+
background: var(--surface);
448+
border: 1px solid var(--border);
449+
border-radius: 8px;
450+
padding: 1rem 1.5rem;
451+
margin-bottom: 1.5rem;
452+
}
453+
454+
.data-table-details summary {
455+
cursor: pointer;
456+
font-weight: 600;
457+
color: var(--primary);
458+
}
459+
460+
.data-table-wrap {
461+
overflow-x: auto;
462+
margin-top: 1rem;
463+
}
464+
465+
#data-table {
466+
width: 100%;
467+
border-collapse: collapse;
468+
font-size: 0.85rem;
469+
}
470+
471+
#data-table th,
472+
#data-table td {
473+
text-align: left;
474+
padding: 0.4rem 0.6rem;
475+
border-bottom: 1px solid var(--border);
476+
color: var(--text);
477+
}
478+
479+
#data-table th {
480+
position: sticky;
481+
top: 0;
482+
background: var(--surface);
483+
}
484+
485+
#data-table a {
486+
color: var(--primary);
487+
}
488+
445489
/* ---- Dark theme (OS default; superseded by the toggle) ---- */
446490
@media (prefers-color-scheme: dark){
447491
:root:not([data-theme="light"]){--primary:#6ea6f0; --primary-dark:#8fbcf5; --secondary:#99a2b5; --background:#101420; --surface:#1a1f2e; --border:#2a3142; --text:#e8ecf4; --text-light:#99a2b5; --success:#34d399; --warning:#fbbf24; --error:#f87171; --point-stroke:#1a1f2e;}
@@ -470,7 +514,7 @@
470514
<body>
471515
<div class="container">
472516
<header>
473-
<h1>🔬 Community Embedding Space</h1>
517+
<h1>Community Embedding Space</h1>
474518
<p class="subtitle">sfdp force-directed layout of 292 microbial communities based on taxonomic composition</p>
475519
<a href="index.html" class="nav-link">← Back to Community Index</a>
476520
</header>
@@ -562,6 +606,27 @@ <h3>Origin</h3>
562606
</div>
563607
</div>
564608

609+
<details class="data-table-details">
610+
<summary>View data as table</summary>
611+
<div class="data-table-wrap">
612+
<table id="data-table">
613+
<thead>
614+
<tr>
615+
<th>Community</th>
616+
<th>ID</th>
617+
<th>Category</th>
618+
<th>Ecological state</th>
619+
<th>Origin</th>
620+
<th>Taxa</th>
621+
<th>Interactions</th>
622+
<th>Coverage %</th>
623+
</tr>
624+
</thead>
625+
<tbody id="data-table-body"></tbody>
626+
</table>
627+
</div>
628+
</details>
629+
565630
<div class="info-panel">
566631
<h2>How to Interpret This Visualization</h2>
567632
<ul>
@@ -4674,21 +4739,25 @@ <h2>How to Interpret This Visualization</h2>
46744739
const margin = { top: 20, right: 20, bottom: 40, left: 40 };
46754740

46764741
// Color schemes for different attributes with semantic, non-clashing colors
4742+
// Categorical 8 (CVD-safe, ordered by frequency) + Other (grey).
4743+
// The 8 most frequent categories get a distinct colour; the rest
4744+
// (and OTHER) collapse to grey. Order is the colourblind-safety mechanism.
46774745
const categoryColors = {
4678-
'AMD': '#e74c3c', // Red - extreme acidic
4679-
'BIOMINING': '#f39c12', // Orange - metal extraction
4680-
'BIOREMEDIATION': '#27ae60', // Green - cleanup/restoration
4681-
'BIOTECHNOLOGY': '#3498db', // Blue - engineered systems
4682-
'CARBON_SEQUESTRATION': '#16a085', // Teal - carbon cycling
4683-
'DIET': '#9b59b6', // Purple - electron transfer
4684-
'EXTREME_ENVIRONMENT': '#c0392b', // Dark red - extreme
4685-
'LIGNOCELLULOSE': '#d35400', // Brown-orange - plant matter
4686-
'METAL_REDUCTION': '#95a5a6', // Gray - metal cycling
4687-
'METHANOGENESIS': '#8e44ad', // Dark purple - methane
4688-
'OTHER': '#7f8c8d', // Medium gray - uncategorized
4689-
'PHYTOPLANKTON': '#1abc9c', // Cyan - marine/aquatic
4690-
'RHIZOSPHERE': '#2ecc71', // Bright green - plant roots
4691-
'SYNTROPHY': '#e67e22' // Orange-red - partnerships
4746+
'RHIZOSPHERE': '#2a78d6', // blue
4747+
'BIOTECHNOLOGY': '#1baf7a', // aqua
4748+
'BIOREMEDIATION': '#eda100', // yellow
4749+
'LIGNOCELLULOSE': '#008300', // green
4750+
'SYNTROPHY': '#4a3aa7', // violet
4751+
'METHANOGENESIS': '#e34948', // red
4752+
'PHYTOPLANKTON': '#e87ba4', // magenta
4753+
'CARBON_SEQUESTRATION': '#eb6834', // orange
4754+
'OTHER': '#898781', // grey - collapsed
4755+
'EXTREME_ENVIRONMENT': '#898781', // grey - collapsed
4756+
'BIOMINING': '#898781', // grey - collapsed
4757+
'DIET': '#898781', // grey - collapsed
4758+
'AMD': '#898781', // grey - collapsed
4759+
'ORAL': '#898781', // grey - collapsed
4760+
'METAL_REDUCTION': '#898781' // grey - collapsed
46924761
};
46934762

46944763
const stateColors = {
@@ -4704,21 +4773,24 @@ <h2>How to Interpret This Visualization</h2>
47044773
};
47054774

47064775
// Symbol shapes for each category (accessibility!)
4776+
// Unique shape per surviving (coloured) category; grey categories reuse
4777+
// the shapes but are distinguished from each other by shape alone.
47074778
const categorySymbols = {
4708-
'AMD': d3.symbolCircle, // ● Circle
4709-
'BIOMINING': d3.symbolSquare, // ■ Square
4710-
'BIOREMEDIATION': d3.symbolTriangle, // ▲ Triangle
4711-
'BIOTECHNOLOGY': d3.symbolDiamond, // ◆ Diamond
4712-
'CARBON_SEQUESTRATION': d3.symbolCross, // + Cross
4713-
'DIET': d3.symbolStar, // ★ Star
4714-
'EXTREME_ENVIRONMENT': d3.symbolWye, // Y Wye
4715-
'LIGNOCELLULOSE': d3.symbolCircle, // ● Circle (different color from AMD)
4716-
'METAL_REDUCTION': d3.symbolSquare, // ■ Square (different color from BIOMINING)
4717-
'METHANOGENESIS': d3.symbolTriangle, // ▲ Triangle (different color from BIOREMEDIATION)
4718-
'OTHER': d3.symbolDiamond, // ◆ Diamond (different color from BIOTECHNOLOGY)
4719-
'PHYTOPLANKTON': d3.symbolCross, // + Cross (different color from CARBON_SEQ)
4720-
'RHIZOSPHERE': d3.symbolStar, // ★ Star (different color from DIET)
4721-
'SYNTROPHY': d3.symbolWye // Y Wye (different color from EXTREME)
4779+
'RHIZOSPHERE': d3.symbolCircle, // ● Circle
4780+
'BIOTECHNOLOGY': d3.symbolSquare, // ■ Square
4781+
'BIOREMEDIATION': d3.symbolTriangle, // ▲ Triangle
4782+
'LIGNOCELLULOSE': d3.symbolDiamond, // ◆ Diamond
4783+
'SYNTROPHY': d3.symbolCross, // + Cross
4784+
'METHANOGENESIS': d3.symbolStar, // ★ Star
4785+
'PHYTOPLANKTON': d3.symbolWye, // Y Wye
4786+
'CARBON_SEQUESTRATION': d3.symbolCircle, // ● Circle (distinct colour)
4787+
'OTHER': d3.symbolDiamond, // ◆ grey
4788+
'EXTREME_ENVIRONMENT': d3.symbolWye, // Y grey
4789+
'BIOMINING': d3.symbolSquare, // ■ grey
4790+
'DIET': d3.symbolStar, // ★ grey
4791+
'AMD': d3.symbolCircle, // ● grey
4792+
'ORAL': d3.symbolTriangle, // ▲ grey
4793+
'METAL_REDUCTION': d3.symbolCross // + grey
47224794
};
47234795

47244796
const colorSchemes = {
@@ -5293,6 +5365,29 @@ <h2>How to Interpret This Visualization</h2>
52935365
// Initial render
52945366
updateVisualization();
52955367

5368+
// Populate the accessible data-table fallback (dataviz #10) from the
5369+
// same data the plot renders.
5370+
(function buildDataTable() {
5371+
const tbody = document.getElementById('data-table-body');
5372+
if (!tbody) return;
5373+
const esc = s => String(s == null ? '' : s)
5374+
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
5375+
const rows = communityData.slice().sort((a, b) => a.name.localeCompare(b.name));
5376+
tbody.innerHTML = rows.map(d =>
5377+
'<tr>' +
5378+
'<td><a href="' + esc(d.url) + '">' + esc(d.name) + '</a></td>' +
5379+
'<td>' + esc(d.id) + '</td>' +
5380+
'<td>' + esc(d.category) + '</td>' +
5381+
'<td>' + esc(d.ecological_state) + '</td>' +
5382+
'<td>' + esc(d.origin) + '</td>' +
5383+
'<td>' + esc(d.num_taxa) + '</td>' +
5384+
'<td>' + esc(d.num_interactions) + '</td>' +
5385+
'<td>' + (typeof d.coverage_pct === 'number'
5386+
? d.coverage_pct.toFixed(1) : esc(d.coverage_pct)) + '</td>' +
5387+
'</tr>'
5388+
).join('');
5389+
})();
5390+
52965391
// Add axes
52975392
const xAxis = d3.axisBottom(xScale).ticks(5);
52985393
const yAxis = d3.axisLeft(yScale).ticks(5);
@@ -5305,7 +5400,7 @@ <h2>How to Interpret This Visualization</h2>
53055400
.attr('y', 35)
53065401
.attr('fill', 'currentColor')
53075402
.attr('text-anchor', 'middle')
5308-
.text('UMAP Dimension 1');
5403+
.text('Layout Dimension 1');
53095404

53105405
svg.append('g')
53115406
.attr('transform', `translate(${margin.left},0)`)
@@ -5316,7 +5411,7 @@ <h2>How to Interpret This Visualization</h2>
53165411
.attr('y', -30)
53175412
.attr('fill', 'currentColor')
53185413
.attr('text-anchor', 'middle')
5319-
.text('UMAP Dimension 2');
5414+
.text('Layout Dimension 2');
53205415
</script>
53215416
<script src="theme-toggle.js"></script>
53225417
</body>

0 commit comments

Comments
 (0)