spb/modelmap Public License
Internal cartography of local LLMs on Apple Silicon — registered, gated, negative-first. Public atlas at modelmap.io.
Python 66.3%
JavaScript 24.5%
CSS 8.1%
Shell 0.7%
1// ============================================================================2// Project : modelmap3// File : site/lib/charts.js4// Purpose : Server-rendered SVG charts for real result maps (dataviz spec)5// Author : Simon-Pierre Boucher6// Contact : contact@spboucher.ai7// Website : https://modelmap.io8// Created : 2026-08-129// Modified : 2026-08-1210// Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr)11// License : All rights reserved (research code)12// ============================================================================13"use strict";1415// Categorical palette validated with the dataviz six-checks script16// (violet/orange PASS all; aqua passes with a contrast WARN → bars using it17// always carry direct value labels). The twin/null is NOT a categorical slot:18// it renders as a dashed neutral reference line, direct-labeled.19const S1 = "#6d4fc4"; // series 1 — violet20const S2 = "#eb6834"; // series 2 — orange21const S3 = "#1baf7a"; // series 3 — aqua (direct labels mandatory)22const REF = "#8b8798"; // reference/null line (muted ink, dashed)2324function esc(s) {25 return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));26}2728const fmt = (v) => (Math.abs(v) >= 100 ? v.toFixed(0) : Math.abs(v) >= 10 ? v.toFixed(1) : v.toFixed(2));2930/**31 * Layer-profile line chart: x = layer index, y in [yMin,yMax].32 * series: [{label, color, dash?, ref?, values:[y per layer]}]33 * Direct labels at line ends + legend; per-point hover targets.34 */35function layerLineSvg({ title, series, yLabel, yMin = 0, yMax = 1, caption, xTitle = "layer", xTickLabels = null }) {36 const W = 720, H = 300, ML = 52, MR = 118, MT = 16, MB = 40;37 const iw = W - ML - MR, ih = H - MT - MB;38 const nx = Math.max(...series.map((s) => s.values.length));39 const X = (i) => ML + (i / (nx - 1)) * iw;40 const Y = (v) => MT + ih - ((v - yMin) / (yMax - yMin)) * ih;4142 let grid = "";43 const defaultTicks = [0, 0.25, 0.5, 0.75, 1].filter((v) => v >= yMin && v <= yMax);44 const ticks = (yMin >= 0 && yMax <= 1 && defaultTicks.length >= 3)45 ? defaultTicks46 : [0, 1, 2, 3, 4].map((i) => yMin + (i / 4) * (yMax - yMin));47 for (const v of ticks) {48 grid += `<line x1="${ML}" y1="${Y(v)}" x2="${ML + iw}" y2="${Y(v)}" class="grid"/>` +49 `<text x="${ML - 8}" y="${Y(v) + 4}" class="tick" text-anchor="end">${fmt(v)}</text>`;50 }51 if (yMin < 0 && yMax > 0) {52 grid += `<line x1="${ML}" y1="${Y(0)}" x2="${ML + iw}" y2="${Y(0)}" class="axis"/>`;53 }54 let xt = "";55 const xlab = (i) => (xTickLabels ? (xTickLabels[i] ?? "") : String(i));56 for (let i = 0; i < nx; i += Math.ceil(nx / 8)) {57 xt += `<text x="${X(i)}" y="${MT + ih + 18}" class="tick" text-anchor="middle">${xlab(i)}</text>`;58 }59 if (!xTickLabels) xt += `<text x="${X(nx - 1)}" y="${MT + ih + 18}" class="tick" text-anchor="middle">${xlab(nx - 1)}</text>`;60 else for (let i = 1; i < nx; i += 1) if (i % Math.ceil(nx / 8) !== 0) xt += `<text x="${X(i)}" y="${MT + ih + 18}" class="tick" text-anchor="middle">${xlab(i)}</text>`;6162 let lines = "", dots = "", labels = "";63 const usedY = [];64 for (const s of series) {65 const d = s.values.map((v, i) => `${i ? "L" : "M"}${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join(" ");66 lines += `<path d="${d}" fill="none" stroke="${s.color}" stroke-width="2" ` +67 `stroke-linejoin="round"${s.dash ? ' stroke-dasharray="6 5"' : ""}/>`;68 s.values.forEach((v, i) => {69 dots += `<circle cx="${X(i).toFixed(1)}" cy="${Y(v).toFixed(1)}" r="7" fill="transparent" ` +70 `class="pt" data-tip="${esc(s.label)} · layer ${i}: ${fmt(v)}"/>`;71 });72 let ly = Y(s.values[s.values.length - 1]);73 while (usedY.some((u) => Math.abs(u - ly) < 13)) ly += 13; // collision nudge74 usedY.push(ly);75 labels += `<text x="${ML + iw + 8}" y="${ly + 4}" class="series-label" fill="${s.ref ? REF : s.color}">${esc(s.label)}</text>`;76 }77 const legend = series.map((s) =>78 `<span class="lg"><span class="lg-swatch" style="background:${s.ref ? "transparent" : s.color};` +79 `${s.ref ? `border-top:2px dashed ${REF};height:0;` : ""}"></span>${esc(s.label)}</span>`).join("");8081 return `<figure class="chart-fig">82${title ? `<figcaption class="chart-title">${esc(title)}</figcaption>` : ""}83<svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(title || yLabel)}">84<line x1="${ML}" y1="${MT + ih}" x2="${ML + iw}" y2="${MT + ih}" class="axis"/>85${grid}${xt}${lines}${dots}${labels}86<text x="${ML + iw / 2}" y="${H - 4}" class="axis-title" text-anchor="middle">${esc(xTitle)}</text>87<text transform="rotate(-90 14 ${MT + ih / 2})" x="14" y="${MT + ih / 2}" class="axis-title" text-anchor="middle">${esc(yLabel)}</text>88</svg>89<div class="chart-legend">${legend}</div>90<div class="chart-tip" hidden></div>91${caption ? `<figcaption>${caption}</figcaption>` : ""}92</figure>`;93}9495/**96 * Small-multiple bar panels sharing one y-scale (identity on the x axis →97 * single hue; every bar direct-labeled; 4px rounded data ends; 2px gaps).98 * panels: [{title, bars: [{label, value}]}]99 */100function barPanelsSvg({ title, panels, yLabel, unit = "", color = S1, caption }) {101 const PW = 340, H = 280, ML = 46, MR = 8, MT = 30, MB = 58;102 const ih = H - MT - MB;103 const vmax = Math.max(...panels.flatMap((p) => p.bars.map((b) => b.value))) * 1.15;104 const W = panels.length * PW;105 let out = "";106 panels.forEach((panel, pi) => {107 const x0 = pi * PW;108 const iw = PW - ML - MR;109 const n = panel.bars.length;110 const bw = Math.min(46, (iw / n) * 0.62);111 out += `<text x="${x0 + ML + iw / 2}" y="${MT - 12}" class="panel-title" text-anchor="middle">${esc(panel.title)}</text>`;112 for (const v of [0.25, 0.5, 0.75, 1]) {113 const y = MT + ih - v * ih;114 out += `<line x1="${x0 + ML}" y1="${y}" x2="${x0 + ML + iw}" y2="${y}" class="grid"/>`;115 if (pi === 0) out += `<text x="${x0 + ML - 8}" y="${y + 4}" class="tick" text-anchor="end">${fmt(vmax * v)}</text>`;116 }117 panel.bars.forEach((b, i) => {118 const cx = x0 + ML + ((i + 0.5) / n) * iw;119 const h = Math.max(2, (b.value / vmax) * ih);120 const y = MT + ih - h;121 out += `<rect x="${(cx - bw / 2).toFixed(1)}" y="${y.toFixed(1)}" width="${bw}" height="${h.toFixed(1)}" ` +122 `rx="4" fill="${color}" class="pt" data-tip="${esc(panel.title)} · ${esc(b.label)}: ${fmt(b.value)} ${esc(unit)}"/>` +123 `<rect x="${(cx - bw / 2).toFixed(1)}" y="${(MT + ih - 2).toFixed(1)}" width="${bw}" height="2" fill="${color}"/>` +124 `<text x="${cx}" y="${y - 5}" class="bar-val" text-anchor="middle">${fmt(b.value)}</text>` +125 `<text x="${cx}" y="${MT + ih + 16}" class="tick" text-anchor="middle">${esc(b.label)}</text>`;126 });127 out += `<line x1="${x0 + ML}" y1="${MT + ih}" x2="${x0 + ML + iw}" y2="${MT + ih}" class="axis"/>`;128 });129 return `<figure class="chart-fig">130${title ? `<figcaption class="chart-title">${esc(title)}</figcaption>` : ""}131<svg viewBox="0 0 ${panels.length * PW} ${H}" role="img" aria-label="${esc(title || yLabel)}">132${out}133<text transform="rotate(-90 12 ${MT + ih / 2})" x="12" y="${MT + ih / 2}" class="axis-title" text-anchor="middle">${esc(yLabel)}</text>134</svg>135<div class="chart-tip" hidden></div>136${caption ? `<figcaption>${caption}</figcaption>` : ""}137</figure>`;138}139140/**141 * Grouped bars: groups on x, one bar per mode, fixed mode→color assignment,142 * every bar direct-labeled (satisfies the aqua contrast WARN relief).143 * groups: [{label, values: {modeKey: value}}], modes: [{key,label,color}]144 */145function groupedBarSvg({ title, groups, modes, yLabel, unit = "", caption }) {146 const W = 720, H = 300, ML = 52, MR = 10, MT = 16, MB = 58;147 const iw = W - ML - MR, ih = H - MT - MB;148 const vmax = Math.max(...groups.flatMap((g) => modes.map((m) => g.values[m.key] || 0))) * 1.18;149 const gw = iw / groups.length;150 const bw = Math.min(34, (gw / modes.length) * 0.6);151 let out = "";152 for (const v of [0.25, 0.5, 0.75, 1]) {153 const y = MT + ih - v * ih;154 out += `<line x1="${ML}" y1="${y}" x2="${ML + iw}" y2="${y}" class="grid"/>` +155 `<text x="${ML - 8}" y="${y + 4}" class="tick" text-anchor="end">${fmt(vmax * v)}</text>`;156 }157 groups.forEach((g, gi) => {158 const gx = ML + gi * gw + gw / 2;159 modes.forEach((m, mi) => {160 const v = g.values[m.key];161 if (v == null) return;162 const cx = gx + (mi - (modes.length - 1) / 2) * (bw + 2);163 const h = Math.max(2, (v / vmax) * ih);164 const y = MT + ih - h;165 out += `<rect x="${(cx - bw / 2).toFixed(1)}" y="${y.toFixed(1)}" width="${bw}" height="${h.toFixed(1)}" ` +166 `rx="4" fill="${m.color}" class="pt" data-tip="${esc(g.label)} · ${esc(m.label)}: ${fmt(v)} ${esc(unit)}"/>` +167 `<text x="${cx}" y="${y - 5}" class="bar-val" text-anchor="middle">${fmt(v)}</text>`;168 });169 out += `<text x="${gx}" y="${MT + ih + 18}" class="tick" text-anchor="middle">${esc(g.label)}</text>`;170 });171 const legend = modes.map((m) =>172 `<span class="lg"><span class="lg-swatch" style="background:${m.color}"></span>${esc(m.label)}</span>`).join("");173 return `<figure class="chart-fig">174${title ? `<figcaption class="chart-title">${esc(title)}</figcaption>` : ""}175<svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(title || yLabel)}">176${out}<line x1="${ML}" y1="${MT + ih}" x2="${ML + iw}" y2="${MT + ih}" class="axis"/>177<text transform="rotate(-90 12 ${MT + ih / 2})" x="12" y="${MT + ih / 2}" class="axis-title" text-anchor="middle">${esc(yLabel)}</text>178</svg>179<div class="chart-legend">${legend}</div>180<div class="chart-tip" hidden></div>181${caption ? `<figcaption>${caption}</figcaption>` : ""}182</figure>`;183}184185module.exports = { layerLineSvg, barPanelsSvg, groupedBarSvg, S1, S2, S3, REF };186