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.
svelte-kit-vice/src/uix/eidos/components/chart
dev 643179de0f
uix(chart): la COSTURA del texto — 49 % → 89 %, y `global` a cero
2 months ago
..
IMPROVEMENTS.md feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom) 4 months ago
README.md uix(chart): la COSTURA del texto — 49 % → 89 %, y `global` a cero 2 months ago
area-chart.svelte fix(chart): el formato del eje Y no llegaba al tooltip, asi que ambos etiquetaban distinto el mismo punto 2 months ago
bar-chart.svelte fix(chart): el formato del eje Y no llegaba al tooltip, asi que ambos etiquetaban distinto el mismo punto 2 months ago
bar-list.svelte feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom) 4 months ago
bar-segment.svelte feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom) 4 months ago
bubble-chart.svelte fix(chart): el formato del eje Y no llegaba al tooltip, asi que ambos etiquetaban distinto el mismo punto 2 months ago
calendar-heatmap.svelte feat(direction): las charts aceptan `dir` y corren la cadena canonica 2 months ago
chart-area.svelte feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom) 4 months ago
chart-bar.svelte feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom) 4 months ago
chart-grid.svelte feat(charts): zero-dep chart family — $libs/plots engine + eidos components + demos 4 months ago
chart-legend.svelte feat(charts): zero-dep chart family — $libs/plots engine + eidos components + demos 4 months ago
chart-line.svelte feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom) 4 months ago
chart-scatter.svelte feat(charts): unified Heatmap (matrix + calendar) + 5 new chart types 4 months ago
chart-tooltip.svelte feat(charts): zero-dep chart family — $libs/plots engine + eidos components + demos 4 months ago
chart-x-axis.svelte feat(charts): zero-dep chart family — $libs/plots engine + eidos components + demos 4 months ago
chart-y-axis.svelte fix(chart): en RTL las etiquetas del eje Y cruzaban el eje — text-anchor es logico y hay que voltearlo a mano 2 months ago
chart.css uix(chart): la COSTURA del texto — 49 % → 89 %, y `global` a cero 2 months ago
chart.svelte feat(direction): las charts aceptan `dir` y corren la cadena canonica 2 months ago
context.ts fix(chart): en RTL las etiquetas del eje Y cruzaban el eje — text-anchor es logico y hay que voltearlo a mano 2 months ago
funnel.svelte feat(direction): las charts aceptan `dir` y corren la cadena canonica 2 months ago
gauge.svelte feat(charts): unified Heatmap (matrix + calendar) + 5 new chart types 4 months ago
heatmap.svelte feat(direction): las charts aceptan `dir` y corren la cadena canonica 2 months ago
index.ts feat(charts): SmithChart — RF reflection-coefficient (Γ) disk 4 months ago
line-chart.svelte fix(chart): el formato del eje Y no llegaba al tooltip, asi que ambos etiquetaban distinto el mismo punto 2 months ago
pie-chart.svelte feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom) 4 months ago
polar-area.svelte feat(charts): unified Heatmap (matrix + calendar) + 5 new chart types 4 months ago
radar-chart.svelte feat(direction): las charts aceptan `dir` y corren la cadena canonica 2 months ago
rtl.svelte.ts feat(direction): eidos comparte el contexto, y el corpus registra el endgame 2 months ago
scatter-chart.svelte fix(chart): el formato del eje Y no llegaba al tooltip, asi que ambos etiquetaban distinto el mismo punto 2 months ago
smith-chart.svelte feat(charts): SmithChart — RF reflection-coefficient (Γ) disk 4 months ago
sparkline.svelte feat(charts): zero-dep chart family — $libs/plots engine + eidos components + demos 4 months ago
types.ts feat(direction): las charts aceptan `dir` y corren la cadena canonica 2 months ago

README.md

Chart

Data-visualisation family built on the in-house, zero-dependency plotting engine $libs/plots (scales · shapes · ticks · curves). SVG, themeable via design tokens, composable primitives plus ergonomic presets.

<script lang="ts">
	import { LineChart } from '$uix/eidos/components/chart';
</script>

<LineChart data={revenue} x="month" xScale="point" y={['revenue', 'expenses']} />

Surface

  • <Chart> — the compositional frame. Measures its width, computes the scales from data + x/y, and shares them with SVG children through context: <Chart.Grid> (alias <Chart.CartesianGrid>, x+y lines), <Chart.XAxis>, <Chart.YAxis>, <Chart.Line>, <Chart.Area>, <Chart.Bar>, <Chart.Scatter>, plus the config children <Chart.Tooltip> (cursor-following readout) and <Chart.Legend> (HTML legend below the plot). Point scales span edge-to-edge so the line/area touch the y-axis and the right edge.
  • Presets (named exports) — LineChart, AreaChart, BarChart, ScatterChart, Sparkline (line or as="bars"). They compose the frame + axes + grid + marks + the HTML chrome (title + legend), with the hover tooltip on by default (tooltip prop).
  • Standalone (named exports, own SVG/HTML — non-cartesian geometry) — <PieChart> (pie + donut via donut={0..1}), <RadarChart> (spider), <BarList> (ranked rows), <BarSegment> (single stacked bar). Each owns its own themed hover tooltip.

All live in this one family directory — new chart types are added here as named exports, never as separate top-level component directories. The improvement roadmap (incl. the MUI X Charts comparison) lives in IMPROVEMENTS.md.

Baseline

No Air baseline — charts did not exist in the legacy air/ layer. The family is net-new, built on the in-house $libs/plots engine. The design references are LayerCake / Pancake (compositional, Svelte) for the API shape and d3-scale / d3-shape (algorithms, reimplemented) for the math — see $libs/plots.

Passive justification (F-1.5)

scope: ['eidos'], 0 events. A chart is a derived render of its data (data → scales → SVG), like Avatar, Image and QrCode. It has no interactive state machine and emits no perceptual signal, so there is no soma behaviour and no sema projection. The opt-in hover tooltip is handled entirely inside the eidos frame (local pointer tracking → bisector.center → crosshair + readout); it stays a passive display because it neither mutates component state nor projects a sema signal — no morfo event is involved.

A2.3 exception — data marks

The mark parts (line, area, bar, point, slice) declare no data-* beyond their part marker (data-chart-{kebab}). Their visual treatment is the per-series colour, set inline from the theme palette (var(--color-{role}-solid)) — there is no enum state to project as a data-*. The axis carries data-chart-axis="x|y".

Subset (G-1.1)

Series colours cycle the canonical role palette via seriesColor(index): primary · affirm · risk · secondary · fulfill · threat · loss · neutral. A mark's color prop accepts any ColorRole. Charts do not take an intent prop — the palette is categorical (series identity), not evaluative.

Comparativa

Capability Chart (UIX) Chart.js Recharts unovis LayerCake
Zero runtime deps ✅ (own $libs/plots) ❌ ❌ (d3) ❌ (d3) ⚠️ (BYO d3)
Render SVG Canvas SVG SVG/Canvas/HTML BYO
Compositional API ✅ + presets ❌ (config) ✅ ⚠️ ✅
Themed by tokens ✅ (--color-*) ⚠️ ⚠️ ⚠️ ❌
Svelte 5 runes-native ✅ ❌ ❌ (React) ⚠️ wrappers ✅

Line/Bar/Area/Scatter/Pie/Sparkline cover the dashboard baseline. Heavy types (candlestick, sankey, treemap, geo, 3D) are out of scope — see Gaps.

Decisiones

  • One family, one morfo. chart is a single eidos component (like a layout family). The presets + pie share chartMorfo (provider + grid + axis
    • line/area/bar/point/slice + legend + title, all optional). New chart types reuse the contract instead of inventing one each.
  • Provider = the <svg role="img">. The svg is the labelled graphic (aria-label); the responsive wrapper is the eidos-only data-chart-frame shell. Matches the QrCode precedent.
  • Pixel-space marks. Marks consume already-scaled points from the context, so shapes stay decoupled from scales (the engine's compositional model).
  • --chart-* recipe for geometry, global tokens for colour. Series colours come from --color-{role}-solid and chrome from --color-content-* / --color-border-* (categorical, theme-driven). Geometric constants (stroke widths, point/bar radius, area opacity, tooltip surface, transition timing) live in a chart recipe block (lib/recipes/base.ts → --chart-*), each sourced from a canonical primitive — no magic numbers in chart.css.
  • SVG primitives in MorfoElement. Charts are the first SVG-heavy component; g · rect · circle · line · figure · figcaption were added to the element union + schema (additive).

Direction (RTL) — the rule for every chart in the family

Two decisions per chart, and they are not the same decision:

  1. Does the composition mirror? Only if the graphic has a READING axis. A time or category series does (chart's x scale, calendar-heatmap, heatmap's columns, funnel's label gutter, bar-list): under RTL the first datum sits on the right. Radial graphics do NOT (gauge, pie-chart, polar-area, radar-chart, smith-chart) — their positions are angles. Mirroring is done by inverting the pixel RANGE of the x scale, or by reflecting x coordinates (mx()); every mark reads from the same source, so they all move together. Y values are never mirrored — a chart is a sequence read the reader's way, not a mirror image.

  2. text-anchor is LOGICAL. Under direction: rtl, end means the LEFT side. So:

    • composition mirrors → leave the anchor logical, it already lands right. Flipping it too cancels the mirroring out.
    • composition does not mirror (a y-axis gutter, radar labels) → force the physical anchor with rtl.anchor() from rtl.svelte.ts.

    Getting this backwards is silent and looks like the labels walking across the graphic. Measured examples: a y-axis label ran to x=59.9 with the axis at 46; the funnel dropped its whole label column onto the funnel body.

rtl.svelte.ts holds both pieces — physicalAnchor() and createChartRtl(), which runs the canonical chain prop dir → prefs → 'ltr' through activeEidosDir, the eidos entry point (a chart has no soma provider to hand activeDir a Soma). It used to read the resolved direction off the element; the chain was simply not runnable from eidos until that entry point existed. Contract: docs/canon/direction-contract.md §7.

⚠️ The raw dir goes on the chart's HTML wrapper (<figure data-chart-figure>, <div data-chart-frame>), never on the <svg>: the attribute is mapped to direction by the HTML UA stylesheet, which does not reach SVG elements — an <svg dir="ltr"> under a dir="rtl" ancestor still computes rtl, measured in Chrome. Since text-anchor is logical, stamping the wrong node makes the maths mirror while the labels do not.

Verify by LOOKING, in RTL. Numbers mislead here: the bounding box of a mirrored path is identical to the unmirrored one, and a label 3.4px from its tick measures as "no overlap" while reading as contact.

Gaps

Item Disposition
Hover tooltip + crosshair (bisector.center) ✅ done (cursor-following, opt-in tooltip prop)
Stacked bar / area (shape-stack ready in $libs/plots) Tier 1 — IMPROVEMENTS.md
Highlight/fade · reference lines · secondary axis · interactive legend Tier 1 — IMPROVEMENTS.md
Enter animation ($motion) · log scale · gradients Tier 2 — IMPROVEMENTS.md
Keyboard data navigation + <table> data fallback Tier 3 a11y — IMPROVEMENTS.md
Canvas/WebGL · zoom/pan · candlestick / sankey / treemap / geo / 3D descartar — out of scope (SVG, "basic")

Theming

Contrato en lib/recipes/base.ts > chart — 45 claves (17 + 22 + 6 el 2026-08-22). Alcance del censo: 89 % (era 9 %), con global a CERO. Guard R-5.4 (npm run theming:sentinel -- chart <url>): 43/45 en vivo; las dos que callan son transition-duration / -ease, adjudicadas con su medición (el guard congela transiciones a propósito).

Series: var(--color-{role}-solid), inline por serie.

El texto adopta DOS ROLES del canon, a través de una costura. El valor viene de los estilos con nombre; el knob es de chart, que es lo que permite retocar ESTE componente sin mover el rol en toda la app:

--chart-caption-font-size  var(--style-caption-font-size)   anotaciones: eje,
--chart-caption-fg         var(--style-caption-color)       leyenda, tooltip, smith
--chart-label-font-size    var(--style-label-font-size)     cromo: título, bar-list,
--chart-label-fg           var(--style-label-color)         funnel, empty
--chart-label-font-weight  var(--style-label-font-weight)   el énfasis
--chart-gauge-label-font-size  var(--size-xl-font-size)     el readout del gauge

Mismo molde que --accordion-trigger-font-family: var(--style-label-font-family) (vertebración tipográfica). Tamaño y tinta son ortogonales: empty y bar-list-value llevan tamaño label con tinta caption. El número del gauge no tiene rol en el canon —un readout de dato no es un h2— y por eso su costura toma el bundle de talla.

Lo que la receta SÍ posee es la tinta de lo que no es texto, porque nada más puede nombrarlo, y cada clave es UN concepto que el CSS repite:

--chart-axis-fg            eje + rejilla del smith + conector del funnel
--chart-grid-fg            rejilla + anillos y radios del radar
--chart-separator-fg       el pelo de superficie que separa marcas contiguas:
                           porción, punto activo, punto smith, pétalo, segmento
--chart-smith-boundary-fg  el borde del disco RF, más fuerte que su rejilla
--chart-track-bg           el raíl del bar-list y el del gauge

Más la geometría del cromo (gap, los pares row-gap/column-gap de leyenda y listas, swatch-size, track-height, radios y separaciones del bar-segment) y la geometría de marca que ya existía (grosores, radios, area-opacity, timing, superficie del tooltip, crosshair).

track-height lee el bundle de talla (--size-sm-control-height), no el primitivo crudo --control-height-sm que el CSS traía: el crudo es lo que recipe-css-contract prohíbe, y su var(--space-7) de respaldo estaba muerto.

Fuera de contrato por identidad: los nueve 100% («ocupa a tu padre») y el min-inline-size: max-content del tooltip. Son los diez knobs que faltan para el 100 %, y ninguno es tema.

chart.css es code-split (se importa desde chart.svelte).

Powered by TurnKey Linux.