You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
404 lines
14 KiB
404 lines
14 KiB
/**
|
|
* 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 <figure> 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;
|
|
}
|