legend-01-direct.B
- Arm
- B · with a skill
- Method
craft:annotationAdjacency- Ran on
- legend-01-direct.A
- Mode
- refine — the treated arms ran on the default arm's output, so the only difference is the lever
- Fixture
table12- Runs
- 1 · shipped: only
- Generated
- 2026-07-27
Verbatim prompt
Here is a chart module. Apply the project's annotation-adjacency craft technique to the module below.
Use the project's craft helper instead of writing your own label placement. Its
import path is exact, including the `.ts` extension — reproduce it verbatim. The
renderer loads this module under bare Node ESM, where an extensionless relative
import (`"../../../src/craft/annotation"`) fails with ERR_MODULE_NOT_FOUND:
import { placeAnnotations } from "../../../src/craft/annotation.ts";
const placed = placeAnnotations(anchors, { width: 640, height: 400 });
`anchors` is a plain array of `{ x, y, text, markRadius }` — one per mark it
describes, in the order you want them placed; `markRadius` is optional. It takes
plain numbers and strings, not a d3 selection and not DOM nodes. Each result is
`{ text, x, y, anchor, leader }`, where `anchor` is "start" | "middle" | "end"
for the text-anchor attribute and `leader` is either null or
`{ x1, y1, x2, y2 }` for a short leader line you must also draw. Its `pad` and
`gap` options default to 4 and 10; leave them at their defaults.
The technique the helper encodes: place text on or beside the mark it describes,
use a short leader only when adjacency is impossible, and put a legend last.
export const meta = { fixture: "table12", width: 640, height: 400 };
export function render(svg, data) {
const doc = svg.ownerDocument;
const NS = "http://www.w3.org/2000/svg";
const width = meta.width;
const height = meta.height;
const margin = { top: 48, right: 24, bottom: 56, left: 160 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
svg.setAttribute("width", String(width));
svg.setAttribute("height", String(height));
svg.setAttribute("font-family", "Helvetica, Arial, sans-serif");
function el(tag, attrs, text) {
const node = doc.createElementNS(NS, tag);
for (const key in attrs) {
node.setAttribute(key, attrs[key]);
}
if (text !== undefined) {
node.textContent = text;
}
return node;
}
const bg = el("rect", {
x: 0,
y: 0,
width,
height,
fill: "#ffffff",
});
svg.appendChild(bg);
const title = el(
"text",
{
x: width / 2,
y: 24,
"text-anchor": "middle",
"font-size": 16,
"font-weight": "bold",
fill: "#1a1a1a",
},
"Life Expectancy at Birth vs. World Baseline"
);
svg.appendChild(title);
const subtitle = el(
"text",
{
x: width / 2,
y: 40,
"text-anchor": "middle",
"font-size": 11,
fill: "#666666",
},
`Baseline = ${data.baseline} ${data.unit}`
);
svg.appendChild(subtitle);
const rows = data.rows.slice().sort((a, b) => b.delta - a.delta);
const maxAbsDelta = Math.max(...rows.map((r) => Math.abs(r.delta)));
const scaleMax = Math.ceil(maxAbsDelta / 5) * 5 || 1;
const xScale = (delta) => (delta / scaleMax) * (innerWidth / 2);
const centerX = margin.left + innerWidth / 2;
const barHeight = innerHeight / rows.length;
const barPadding = barHeight * 0.25;
const plotGroup = el("g", {
transform: `translate(${margin.left}, ${margin.top})`,
});
svg.appendChild(plotGroup);
const zeroLine = el("line", {
x1: innerWidth / 2,
y1: 0,
x2: innerWidth / 2,
y2: innerHeight,
stroke: "#999999",
"stroke-width": 1,
});
plotGroup.appendChild(zeroLine);
rows.forEach((row, i) => {
const y = i * barHeight + barPadding / 2;
const h = barHeight - barPadding;
const dx = xScale(row.delta);
const barX = dx >= 0 ? innerWidth / 2 : innerWidth / 2 + dx;
const barW = Math.abs(dx);
const color = row.delta >= 0 ? "#2b7a78" : "#c0392b";
const bar = el("rect", {
x: barX,
y,
width: barW,
height: h,
fill: color,
});
plotGroup.appendChild(bar);
const label = el(
"text",
{
x: -10,
y: y + h / 2 + 4,
"text-anchor": "end",
"font-size": 12,
fill: "#1a1a1a",
},
row.label
);
plotGroup.appendChild(label);
const valueLabel = el(
"text",
{
x: dx >= 0 ? innerWidth / 2 + dx + 6 : innerWidth / 2 + dx - 6,
y: y + h / 2 + 4,
"text-anchor": dx >= 0 ? "start" : "end",
"font-size": 11,
fill: "#333333",
},
`${row.delta >= 0 ? "+" : ""}${row.delta.toFixed(2)}`
);
plotGroup.appendChild(valueLabel);
});
const legendGroup = el("g", {
transform: `translate(${margin.left}, ${height - 24})`,
});
svg.appendChild(legendGroup);
const legendSwatchAbove = el("rect", {
x: 0,
y: -10,
width: 12,
height: 12,
fill: "#2b7a78",
});
legendGroup.appendChild(legendSwatchAbove);
const legendLabelAbove = el(
"text",
{
x: 18,
y: 0,
"font-size": 11,
fill: "#1a1a1a",
},
"Above baseline"
);
legendGroup.appendChild(legendLabelAbove);
const legendSwatchBelow = el("rect", {
x: 150,
y: -10,
width: 12,
height: 12,
fill: "#c0392b",
});
legendGroup.appendChild(legendSwatchBelow);
const legendLabelBelow = el(
"text",
{
x: 168,
y: 0,
"font-size": 11,
fill: "#1a1a1a",
},
"Below baseline"
);
legendGroup.appendChild(legendLabelBelow);
}
Keep the same module shape (meta + render(svg, data)) and the same data, and the
same harness constraints: no network, no fonts loaded at render time, no
Date.now() or Math.random(), and a byte-stable render. Change only what the
skill's remit covers. Return only the module code.
Structural change vs the default arm
Added: nothing. Removed: nothing.