Skip to content

Flint 0.5 - #88

Open
Chenglong-MS wants to merge 173 commits into
mainfrom
dev
Open

Flint 0.5#88
Chenglong-MS wants to merge 173 commits into
mainfrom
dev

Conversation

@Chenglong-MS

Copy link
Copy Markdown
Contributor

Theme as a formal specification language

fix2015 and others added 30 commits July 27, 2026 17:57
fix: use Math.max for pyramid chart right-bar palette fallback
…0260728-095216

docs: quote pip extras install examples
…aps pages

- labelOneBody: operate on the layer host after a facet-channel promotion, and
  stop copying facet/row/column onto the label layer (Vega-Lite refuses split
  channels inside a layer; the promoted operator already covers every layer).
  Fixes assemble crash on faceted rose with data labels (nyt, mckinsey).
- applyFurniture: decline on a radius-encoded or faceted arc
  (radialResistsFurniture). A rose sizes its petals from the panel width, which
  the vconcat furniture wrapper rescopes (faceted: child_width render error) or
  collapses (plain: shrunk disc). A theta-only pie is untouched.
- Add theme-lab-r2 and theme-lab-gaps playground pages (lazy, family-paged).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…/450

- ground.ts: dataLabels.show:always now yields to the same band+marks fit
  facts as whenTheyFit (shared metric, higher marks cap 120), so nyt/mckinsey
  stop smearing values on bar-n100, pie-25, grouped-color-continuous while
  keeping pie-10/pyramid-12 labelled. Fixes a first attempt that divided band
  by series and wrongly suppressed mirrored pyramids.
- theme-lab-r2-data: design at 300x300 with a 450x450 stretch ceiling.
- ThemeLabR2 page: fixed-width theme-lab tiles in a horizontal-scroll row
  (no more unreadable 7-col shrink), ScaleToFit charts, pill metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…t labels, gate showPoints on density

line-8 under nyt rendered as dotted smears: a per-series strokeDash (dash as a
redundant channel, fired because 8 series > 5 inks) collapsed to stipple on a
dense daily path, and showPoints:true drew ~100 haloed points per series that
ate the line.

- ground.ts: within redundantEncoding:whenNeeded, withhold dash/shape when each
  series is named at its own mark (seriesEnd/inline legend) — the label already
  carries identity.
- templates/line.ts: withhold the per-observation point overlay when per-series
  point spacing falls below ~8px; nyt's endpoint-emphasis dots then activate
  correctly. Keeps points on sparse lines (line-ordinal-30).

Same 'preference yields to fit' principle as the always-label floor. Full corpus
0 failures; round-1 lab 121/0; 42 theme tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
Two cases too particular for a general coverage-round fix:
- scatter-color-n50: 50 nominal series exceed any palette; colours cycle and a
  top legend blows the width out. A real fix (measured legend wrap/fallback +
  a more-series-than-inks policy) is larger than a patch and risky blind.
- pie-25: label smear fixed in iter 2, but palette exhausts past ~24 slices —
  inherent to a pie past legibility.

Gaps page switched to the same fixed-tile horizontal-scroll layout as R2 so the
parked cases stay readable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…no value and needs no band-end label

A violin and a density plot both draw a distribution as an area-mark
silhouette. Grounding treated them as ordinary bands: isSummarised (the
fact that spares a box plot its data labels) only knew the boxplot mark,
so these looked labelable, and bandFamily offered them the seriesEnd
placement whose anchor is the band's last reading — meaningful on a
streamgraph, an arbitrary near-zero tail on a distribution.

Under the direct-labelling houses this stamped a number on the shape
(Junior 61660.08, Treatment B 0.00217) and piled the series names at the
baseline. Extend isSummarised to key off the chart type (Violin Plot,
Density Plot), not just the mark; that one fact drives both gates — the
data-label value is withheld and the in-band placement falls through to
the house legend. Streamgraphs are not summarised and keep their inline
end labels.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…olumn stops leaking into it

The connector span binds a synthetic `__wf_lead` field on x2. With no
explicit title, Vega-Lite folds that helper's name into the axis title,
which rendered as "Month, __wf_lead". Pin the x-axis title to the real
field so the internal column never surfaces in the chart.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
… measure house does not flip the bins

A histogram's binned x is an index axis — the reader keys counts off its
intervals — but the template never declared it banded, so grounding
classed the quantitative field as a measure. A house that seats its
measure axis opposite (economist: y on the right) then flipped the bins
to the *top* of the plot, where the tick labels collided with the title
and no longer lined up with the bars. Add declareLayoutMode marking x
banded, matching the bar family and grounding's own stated intent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…ayer

applyZeroRule converted a unit spec into a layer by hand to append the
zero rule, moving the encoding — facet channel and all — into the inner
layer. Vega-Lite drops a facet/row/column split left inside a layer's
encoding, so any faceted chart whose measure crossed zero (a small-
multiple range-area over cities with sub-zero lows) silently lost its
faceting and drew every panel's band in one plot as a sawtooth.

Route the conversion through appendLayer, which already promotes the
split to a real facet operator wrapping the layer. Non-faceted and
already-layered charts are unchanged; the zero rule now draws per panel.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…s out fill marks on white)

A sequential ramp whose low end approaches the plot surface makes filled
bars invisible — right for a heatmap (cell gaps outline it), wrong for
bars. The generalizable principle (a fill mark needs a contrast floor
against its surface) touches every sequential-coloured chart and is too
risky to land blind mid-round, so it is documented as a reference gap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
A budget-variance table runs +120 to -80. McKinsey declared only a
sequential ramp, so ground.ts painted -80 the palest blue and +120 the
darkest -- the reader sees 'less/more', never 'minus/plus'. Every other
house declares a diverging ramp; McKinsey did not. A single-hue house
cannot diverge, so this is the one place it reaches for a second hue:
accent blue below zero, a restrained warm above, its light surface tint
at the break. selection.signed set to diverging to match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
6a: a faceted *table* (facet operator whose panel is an hconcat) rendered
blank when a house hung furniture. applyFurniture wraps the chart in an
outer vconcat, turning facet>concat into concat>facet>concat; Vega-Lite
rescopes the panel width signals out of reach and every column collapses.
This is the failure radialResistsFurniture already documents for the
faceted disc -- the guard was just too narrow. Added
facetedTableResistsFurniture: a facet-of-concat refuses the wrapper and
keeps the house rule off, as the radial facet already does. Plain faceted
units (line-facet, area-facet) are untouched.

6c: on a line with two encodings (colour=series, strokeDash=actual/forecast),
the direct-label houses name the series at each line end and hide the
colour key, but the strokeDash key stayed top-right where the end labels
now sit, piling into a smear. Added relocateSecondaryLegends: once series
are named at the line end, any surviving non-colour key steps to the foot
of the plot. Houses that keep a legend are untouched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
The Waterfall template rebuilds its own encoding objects, and it read
only `.field` off `ctx.resolvedEncodings` — discarding the titles the
assembler had already resolved from `field_display_names`. The bar layer
hardcoded the raw field name, and the shared x encoding carried no title
at all.

Vega-Lite derives an axis title from the field name of every untitled
encoding on a shared scale and concatenates them, so the untitled x also
picked up the internal connector column: the axis rendered as
"week, __wf_lead".

Carrying the resolved titles across fixes both symptoms at once. An
explicit title on the primary channel settles the whole shared scale, so
the internal columns need no treatment of their own — `title: null` on
them would blank the axis instead, since an explicit null anywhere in a
shared-scale title set wins.

Verified by compiling with vl.compile and reading the Vega axis titles,
not just the assembled spec. Swept every bundled gallery case: Waterfall
was the only template leaking an internal column into an axis title.

652 tests passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`tools/run_full_eval.py` exits with "manifest.json not found" and
evaluates nothing. `FIXTURES` still resolves to
`packages/flint-py/tests/fixtures/`, but the corpus moved to
`shared/test-data/` in 1d1ee39 and this constant did not follow.

The report path is derived from `FIXTURES.parent`, so repointing the
corpus alone would have carried the report out of `tests/` along with
it. Giving the report its own `REPORT_DIR` keeps both outputs on the
locations where the committed copies already live: `results.json` next
to the corpus, `FULL_GALLERY_REPORT.md` under `tests/`.

With this applied the tool completes and reports PASS 476 / MISMATCH 180
/ PY_ERROR 0 / JS_ERROR 1 / FIXTURE_MISSING 0. The mismatch count agrees
with `pytest -q`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two label-overflow fixes found by inspecting rendered R2 audit sheets.

bar-n30 / bar-n5 — a house that prints values above its bars clipped the
labels on the tallest bars (TV 825, Speaker 749, HDD 707): the bar that
defines the scale maximum reaches the plot ceiling and leaves no strip of
plot above it for its label. Outer top padding cannot clear it because the
title already sits there. Give the measure scale headroom instead, so the
tallest bar stops short of the ceiling and every label lands in whitespace
inside the plot — the way a labelled bar chart is actually drawn. With
headroom the scale-end inside-flip is redundant, so it is dropped on
vertical bars (kept on horizontal, where the margin, not the domain, is the
remedy).

scatter-color-n50 — nyt and economist lay a colour key horizontally at the
top, and fifty names in one row run off the side of the canvas, crushing the
plot to a sliver. A row has a width, so the entries are wrapped into as many
columns as the block holds and capped to a few rows — the same overflow the
baseline already resolves, restored to the themed path.

All 85 R2 sheets render (0 failures); theme-lab unchanged bar the known
exam-ecdf headline note; 694 tests pass; tsc clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…left legends

Bar value labels: measure the value-label width against the per-bar slot (band
step, or band/seriesCount for grouped/dodged) and, when the label cannot fit,
flip a single-series bar's label outside the mark and suppress it for grouped
bars (narrow sub-bands with no borrowable padding). Keeps the legend/axis intact.

readableOn: pick whichever candidate ink sits furthest from the background in
luminance instead of assuming which one is light. Fixes invisible contrast labels
on dark houses (powerbi) while leaving light houses unchanged; contrast is a
legibility floor, not a fact about which ink a house names "primary".

Pie: hide value labels on slices whose arc at the label radius is thinner than one
line, via conditional opacity (not a row filter, which would recompute the shared
stacked theta and drift the remaining labels off their arcs).

Legend: lay a horizontal top/bottom key flush-left with the start-anchored title
(anchor:'start', bounds:'full') so the caption begins where the graphic begins,
rather than indented to the plot rectangle. Merges with the multi-key row-stacking
layout. Updated theme-legend-rows tests to assert the vertical-stacking decision
rather than the whole layout object.

ThemeLabR2: wrap cells instead of horizontal scroll per section.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
… slopes

Series-end labels (seriesEnd/inline placement) ranked each series' rows by the
domain field sorted descending, which for an ordinal axis with an explicit sort
(months) picks the alphabetically-last value ("Sep"), not the last band drawn
("Dec"). Labels landed mid-plot under the crossing lines. Where the domain
carries an explicit sort array, rank by each row's index in it; where the sort
is 'descending', rank the field ascending; otherwise keep the descending-field
default. Labels now sit in the reserved right margin like a line chart's.

Layout: a bump chart has two discrete axes, so the square-cells rule (meant for
heatmap tones) pulled both steps equal, giving a tall square panel where every
rank change is a near-vertical cliff and twelve crossing series are unreadable.
A connected mark is a line, not a grid of cells. For a connected mark with both
axes discrete (bars have a continuous value axis; heatmaps declare no
cross-section, so neither is caught) skip squaring and instead hold the thin
cross axis (rank) to its declared cross-section while stretching the run axis
(time) to a bounded 2:1 multiple, clamped to the width budget. bump-many goes
336x336 -> 384x240; the horizontal room untangles the crossings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
A slopegraph reads in one of two ways, and which one is a house matter.
Editorial houses take the value axis away, so the values must be printed on
the marks — a "Name Value" label outside each endpoint, no colour key. A
house that keeps its measure axis (nature: a journal spine + always-on
legend) reads values off the axis and tells the lines apart with the legend,
like ordinary Vega-Lite. Printing names on its endpoints only fights the axis
for the same margin.

- resolveChartDefaults: enable slope end labels (showText/showSeriesInLabel)
  as a themed default only where the house omits the measure-axis line;
  a house that keeps its axis is left with its legend.
- assemble.ts: create chartProperties for any theme (not only those declaring
  chartDefaults), or the default never runs.
- slope template: drop the now-redundant colour legend once names are on the
  marks; GroundingContext.namesOnMarks generalises the colour→single-ink drop
  to any house naming its series on the mark (was hard-keyed to economist).
- minBandStep: a chart-specific floor on the per-category band step a house's
  layout.bandStep may grow but not undercut. slope declares 120, so a compact
  house (nature's 46px) can't pack the two columns near-vertical with no room
  for labels.

Gates: flint-js tsc clean; 694 vitest; theme-audit 62 sheets 0 FAILED;
R2 85 sheets 0 FAILED.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
…ading

Two faults in the name-in-the-band labels a house knocks out of a stacked
area at its last reading.

1. The band is an `area`, which Vega-Lite stacks on its own; the label is a
   `text`, which it does not. Left implicit, every name landed at its series'
   *raw* value and the whole set piled up at the foot of the plot — the stack
   might as well not have been there. Naming the band's own offset on the label
   layer lifts each name to the middle of the band it belongs to.

2. "The end" was found by sorting the domain descending. On a bare nominal
   index ("Stage 1"…"Stage 12") that sorts as strings, so every series ended at
   "Stage 9" — the label sat mid-plot AND printed Stage 9's number, not Stage
   12's. Rank by declared order where the house gives one, by the field where
   it is a time or number line, and by arrival order for a plain nominal domain
   (matching applySeriesEndLabels). The name now sits at the true last column,
   whose point-scale position is the plot's right edge, so an outside name
   clears the plot without a fragile width expression that a concatenated view
   (economist's rule header) resolves against the wrong signal.

Gates: flint-js tsc clean; 694 vitest; theme-audit 62 sheets 0 FAILED;
R2 85 sheets 0 FAILED.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33e4b998-4b58-4d7e-9cf8-c107b10e45c2
Chenglong-MS and others added 30 commits August 4, 2026 22:22
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
ThemeSpec is now a Language design guide rather than an API detail readers
have to infer. Its interactive panel keeps three complete Flint inputs behind
Preset, Custom, and Inherit tabs; the preset selector updates both the named
form and the inherited base so the control cannot teach one value while the
code shows another. The guide covers every shipped preset, the policy blocks a
custom theme can state, the boundary between chart meaning and presentation,
and the current Vega-Lite scope in both English and Chinese.

The inherited form is implemented, not merely documented. A ThemeSpec can
`extend` a shipped preset and deeply override selected policy objects. Arrays
and scalars replace in full because merging ordered palettes or furniture item
by item would invent a vocabulary neither theme authored. Variant resolution
and inheritance share the same merge helper, keeping those semantics from
drifting.

The examples use real type tokens and replace both categorical palette tiers;
otherwise a high-cardinality chart could quietly return to the base preset's
brand colors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
The guide now uses the same left-code/right-result relationship as the landing
showcase. A compact World Bank life-expectancy example exercises series colour,
direct labels, axes, and legend policy while staying small enough to read.
Changing the form or preset recompiles the chart immediately.

The displayed snippet omits data and collapses semantic_types and chart_spec to
`{ ... }`; the complete input still drives the preview. This leaves only
`theme_spec` expanded and highlighted, which keeps the lesson on the field the
controls actually change. The code uses a smaller light treatment so it reads
as documentation rather than another dark editor panel.

Dark house canvas colours extend through the preview viewport, matching the
front-page showcase instead of leaving a dark chart floating on white paper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
The code keeps chart_spec visible while abbreviating only data and semantic
types. The chart now stands without preview chrome, and the selected theme's
explanation sits directly beneath it with no separator competing with the
figure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
The preset catalogue now reads directly from THEME_PRESETS and pairs every
house with its own SVG, name, stable id, and localized description. The preset
and base-theme selector is now an icon-aware menu instead of a native select,
so its options carry the same identity as the catalogue and public picker.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
The hero now mentions themes in its existing lead paragraph and adds Explore
Themes as a standard quick action, without introducing a new button treatment.

How it works gains a fifth feature in the same numbered-card and stacked-demo
shape as the first four. One real grouped-revenue spec is compiled twice, once
as Economist and once as Swiss, so the comparison changes only the formal theme
while data and encodings stay fixed. A small gold point in the feature's top
right is the only new-feature accent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
The hero roster now names NYT, Economist, and Swiss beside the backend list,
then links to the remaining six without turning the line into a catalogue. The
new-theme markers are flat bright yellow dots with no glow or shadow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 175e0e03-5dda-4a02-aa14-c181660b9c99
fix(site): reset scroll position when navigating between docs
fix(flint-py): point run_full_eval at the moved fixture corpus
feat(chartjs): add Lollipop Chart template
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants