/** * Chart recipe — styling for the SVG frame, axes, grid, marks and the hover * tooltip, plus the HTML chrome (title + legend) the presets compose. Every * geometric constant (stroke widths, radii, opacities, transition timing, * tooltip surface) comes from a `--chart-*` recipe token sourced from canonical * primitives — see `lib/recipes/base.ts`. Series fills/strokes are themed per * series inline (`var(--color-{role}-solid)`). * * TEXT takes its SIZE ROLE from the canon's named styles — `caption` for the * annotations (axis labels, legend, tooltip, smith labels) and `label` for the * chrome text (title, bar-list rows, funnel) — and its INK and WEIGHT from the * system's content / weight slots, which is what those slots are for. The * recipe mints NO typography of its own (theming §6, D-TH.2 precedent: * code-block / kbd / announce read the same layer). * * What the recipe DOES own, because nothing else can name it: the ink of the * lines and fills that are not text — axis, grid, the hairline that separates * adjacent marks, the rails — plus its geometry. */ [data-chart-frame] { display: flex; flex-direction: column; gap: var(--chart-gap); inline-size: 100%; } [data-chart-plot] { position: relative; /* anchor for the absolutely-positioned hover tooltip */ inline-size: 100%; } /* The plotting surface (morfo Provider — role="img"). */ [data-chart] { display: block; inline-size: 100%; block-size: auto; overflow: visible; font-family: inherit; } /* Localised empty state when there is no data. */ [data-chart-empty] { display: grid; place-items: center; block-size: 100%; color: var(--color-content-secondary); font-size: var(--style-label-font-size); } /* ── Axes ─────────────────────────────────────────────────────────────── */ [data-chart-axis-line], [data-chart-axis-tick] { stroke: var(--chart-axis-fg); stroke-width: var(--chart-axis-width); } [data-chart-axis-label] { fill: var(--color-content-secondary); font-size: var(--style-caption-font-size); } /* ── Grid ─────────────────────────────────────────────────────────────── */ [data-chart-grid-line] { stroke: var(--chart-grid-fg); stroke-width: var(--chart-grid-width); } /* ── Marks ────────────────────────────────────────────────────────────── */ [data-chart-line] { stroke-width: var(--chart-line-width); transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-area] { fill-opacity: var(--chart-area-opacity); /* So the `grow` enter scales the area from its own bottom edge (the baseline) — it fills up. */ transform-box: fill-box; transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-bar] { rx: var(--chart-bar-radius); /* So the `grow` enter's transform-origin (50% 100%) is the bar's own box, not the SVG viewport. */ transform-box: fill-box; transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-point] { r: var(--chart-point-radius); transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } /* Pie / donut slices, separated by a hairline of the page surface. */ [data-chart-slice] { stroke: var(--chart-separator-fg); stroke-width: var(--chart-slice-stroke-width); /* So the `bloom` enter scales each slice from the chart (viewport) centre — a radial grow. */ transform-box: view-box; transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } /* ── Hover crosshair + active markers ─────────────────────────────────── */ [data-chart-crosshair] { stroke: var(--chart-crosshair-fg); stroke-width: var(--chart-crosshair-width); stroke-dasharray: 3 3; pointer-events: none; } [data-chart-active-dot] { r: var(--chart-point-radius); stroke: var(--chart-separator-fg); stroke-width: var(--chart-axis-width); pointer-events: none; } /* ── Hover tooltip ────────────────────────────────────────────────────── */ [data-chart-tooltip] { position: absolute; z-index: var(--z-index-tooltip); pointer-events: none; /* Anchored at the active datum's x (inline `left`) + cursor y (`top`); offset to the side and vertically centred. Flips left near the edge. */ transform: translate(var(--space-3), -50%); /* Glide between data points + flip side smoothly (the abrupt jump otherwise). */ transition: left var(--chart-transition-duration) var(--chart-transition-ease), transform var(--chart-transition-duration) var(--chart-transition-ease); display: flex; flex-direction: column; gap: var(--chart-tooltip-gap); padding: var(--chart-tooltip-padding); background: var(--chart-tooltip-bg); border: var(--chart-axis-width) solid var(--chart-tooltip-border); border-radius: var(--chart-tooltip-radius); box-shadow: var(--chart-tooltip-shadow); font-size: var(--style-caption-font-size); color: var(--color-content-primary); min-inline-size: max-content; } [data-chart-tooltip][data-flip='true'] { transform: translate(calc(-100% - var(--space-3)), -50%); } [data-chart-tooltip-x] { font-weight: var(--font-weight-medium); color: var(--color-content-primary); } [data-chart-tooltip-row] { display: flex; align-items: center; gap: var(--chart-tooltip-gap); color: var(--color-content-secondary); } [data-chart-tooltip-swatch] { inline-size: var(--chart-tooltip-swatch-size); block-size: var(--chart-tooltip-swatch-size); border-radius: var(--chart-swatch-radius); flex: none; } [data-chart-tooltip-value] { margin-inline-start: var(--space-4); font-variant-numeric: tabular-nums; color: var(--color-content-primary); } /* ── Figure chrome (presets) ──────────────────────────────────────────── */ [data-chart-figure] { /* Reset the browser's default
margin (1em 40px) so the chart fills and aligns with its container instead of sitting inset / off-centre. */ margin: 0; position: relative; /* anchor for the pie's hover tooltip */ display: flex; flex-direction: column; gap: var(--chart-gap); inline-size: 100%; } [data-chart-title] { margin: 0; font-size: var(--style-label-font-size); font-weight: var(--font-weight-medium); color: var(--color-content-primary); } [data-chart-legend] { display: flex; flex-wrap: wrap; row-gap: var(--chart-legend-row-gap); column-gap: var(--chart-legend-column-gap); align-items: center; } [data-chart-legend-item] { display: inline-flex; align-items: center; gap: var(--chart-legend-item-gap); font-size: var(--style-caption-font-size); color: var(--color-content-secondary); } [data-chart-swatch] { inline-size: var(--chart-swatch-size); block-size: var(--chart-swatch-size); border-radius: var(--chart-swatch-radius); flex: none; } /* ── Bar list (ranked horizontal bars) ────────────────────────────────── */ /* ONE grid for the whole list so the label / track / value columns line up across every row — the track (the grey "100%" rail) is then identical-width on every row, so the filled bars are directly comparable. */ [data-chart-bar-list] { position: relative; /* anchor for the hover tooltip */ display: grid; grid-template-columns: minmax(0, max-content) 1fr auto; align-items: center; row-gap: var(--chart-bar-list-row-gap); column-gap: var(--chart-bar-list-column-gap); inline-size: 100%; } [data-chart-bar-list-item] { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; } [data-chart-bar-list-label] { font-size: var(--style-label-font-size); color: var(--color-content-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } [data-chart-bar-list-track] { block-size: var(--chart-track-height); background: var(--chart-track-bg); border-radius: var(--chart-track-radius); overflow: hidden; } [data-chart-bar-list-bar] { block-size: 100%; border-radius: var(--chart-bar-radius); transition: inline-size var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-bar-list-value] { font-size: var(--style-label-font-size); color: var(--color-content-secondary); font-variant-numeric: tabular-nums; text-align: end; } /* ── Bar segment (single stacked bar) ─────────────────────────────────── */ [data-chart-bar-segment] { position: relative; /* anchor for the hover tooltip */ display: flex; flex-direction: column; gap: var(--chart-bar-segment-gap); inline-size: 100%; } [data-chart-bar-segment-track] { display: flex; gap: var(--chart-bar-segment-track-gap); block-size: var(--chart-track-height); border-radius: var(--chart-bar-segment-track-radius); overflow: hidden; } [data-chart-bar-segment-fill] { block-size: 100%; min-inline-size: var(--chart-bar-segment-fill-min-inline-size); transition: inline-size var(--chart-transition-duration) var(--chart-transition-ease), opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-bar-segment-fill][data-dim='true'] { opacity: var(--opacity-scrim); } [data-chart-bar-segment-legend] { display: flex; flex-wrap: wrap; row-gap: var(--chart-bar-segment-legend-row-gap); column-gap: var(--chart-bar-segment-legend-column-gap); } [data-chart-bar-segment-legend-item] { display: inline-flex; align-items: center; gap: var(--chart-legend-item-gap); font-size: var(--style-caption-font-size); color: var(--color-content-secondary); } [data-chart-bar-segment-legend-value] { color: var(--color-content-primary); font-variant-numeric: tabular-nums; } /* ── Radar / spider chart ─────────────────────────────────────────────── */ [data-chart-radar-ring], [data-chart-radar-spoke] { fill: none; stroke: var(--chart-grid-fg); stroke-width: var(--chart-grid-width); } [data-chart-radar-area] { fill-opacity: var(--chart-area-opacity); stroke-width: var(--chart-line-width); stroke-linejoin: round; /* So the `bloom` enter scales the polygon from the radar centre — each vertex travels from the origin to its place. */ transform-box: view-box; } /* ── Heatmap (grid of value-coloured cells) ───────────────────────────────── */ [data-chart-heat-cell] { rx: var(--chart-bar-radius); transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-heat-cell][data-dim='true'] { opacity: var(--opacity-scrim); } /* Calendar heatmap (GitHub-style day cells). */ [data-chart-cal-cell] { rx: var(--chart-bar-radius); transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-cal-cell][data-dim='true'] { opacity: var(--opacity-scrim); } /* Smith chart (RF reflection-coefficient disk). */ [data-chart-smith-boundary] { fill: none; stroke: var(--chart-smith-boundary-fg); stroke-width: var(--border-width-medium); } [data-chart-smith-grid] { fill: none; stroke: var(--chart-axis-fg); stroke-width: var(--border-width-thin); } [data-chart-smith-label] { fill: var(--color-content-secondary); font-size: var(--style-caption-font-size); font-variant-numeric: tabular-nums; pointer-events: none; } [data-chart-smith-point] { stroke: var(--chart-separator-fg); stroke-width: var(--border-width-medium); } [data-chart-smith-point-label] { fill: var(--color-content-primary); font-size: var(--style-caption-font-size); font-weight: var(--font-weight-medium); pointer-events: none; } /* ── Polar area (Nightingale / coxcomb petals) ────────────────────────────── */ [data-chart-polar-petal] { stroke: var(--chart-separator-fg); stroke-width: var(--chart-slice-stroke-width); fill-opacity: 0.9; /* So the `bloom` enter scales each petal from the chart centre (radial grow). */ transform-box: view-box; transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-polar-petal][data-dim='true'] { opacity: var(--opacity-scrim); } /* ── Funnel (tapering stages) ─────────────────────────────────────────────── */ [data-chart-funnel-segment] { stroke: var(--chart-separator-fg); stroke-width: var(--chart-slice-stroke-width); } [data-chart-funnel-label] { font-size: var(--style-label-font-size); font-weight: var(--font-weight-medium); pointer-events: none; } /* Right-side labels (always legible — theme text colour, never on a coloured fill). */ [data-chart-funnel-connector] { stroke: var(--chart-axis-fg); stroke-width: var(--chart-grid-width); } [data-chart-funnel-side] { font-size: var(--style-label-font-size); } [data-chart-funnel-name] { fill: var(--color-content-primary); font-weight: var(--font-weight-medium); } [data-chart-funnel-meta] { fill: var(--color-content-secondary); font-variant-numeric: tabular-nums; } /* ── Gauge (radial single value) ──────────────────────────────────────────── */ [data-chart-gauge-track] { fill: var(--chart-track-bg); } [data-chart-gauge-value] { transition: opacity var(--chart-transition-duration) var(--chart-transition-ease); } [data-chart-gauge-label] { fill: var(--color-content-primary); font-size: var(--font-size-xl); font-weight: var(--font-weight-medium); font-variant-numeric: tabular-nums; }