What happens
The Rank semantic type's documented behaviour is honoured by the Bar Chart template but ignored by Bar Table, which encodes the ordinal as a bar length and a sequential colour ramp. The result is a chart that reads as the inverse of the data: rank 1 gets the shortest, palest bar and the last-placed item gets the longest, darkest one.
The agent skill documents (flint://agent-skill):
Rank → reversed axis (1 on top), discrete color
Neither template emits a reverse, and Bar Table additionally applies a sequential colour scale rather than a discrete one.
validate_chart returns valid: true with zero warnings in both cases, so nothing in the toolchain flags it.
Reproduction
flint-chart-mcp@0.4.1, backend vegalite.
{
"data": { "values": [
{ "Engine": "Inworld TTS-2", "Rank": 1 },
{ "Engine": "xAI leo", "Rank": 2 },
{ "Engine": "Kokoro am_michael", "Rank": 3 },
{ "Engine": "Gemini", "Rank": 4 },
{ "Engine": "Inworld 1.5-max", "Rank": 5 }
]},
"semantic_types": { "Engine": "Name", "Rank": "Rank" },
"chart_spec": {
"chartType": "Bar Table",
"encodings": { "y": { "field": "Engine" }, "x": { "field": "Rank" } },
"baseSize": { "width": 560, "height": 280 }
}
}
Swapping chartType to "Bar Chart" with everything else identical produces a sensible ordinal rendering — equal-sized marks positioned by rank, correct row order, no magnitude encoding. That contrast is what makes this look like a template gap rather than intended behaviour.
Inspecting the compiled Vega-Lite:
|
sort |
reverse |
| Bar Table |
null |
absent |
| Bar Chart |
null, "ascending" |
absent |
Why it matters
An ordinal is not a magnitude — "how much better is 1st than 2nd" has no answer, so any length encoding of it is unfounded. This is the failure mode that is hardest to catch downstream, because the output is well-formed, passes validation, and is confidently wrong in a direction a reader will not question.
Suggested fix
Either honour the documented Rank behaviour in the Bar Table template (discrete colour, no length encoding, 1 first), or have validate_chart warn when an ordinal semantic type (Rank, and arguably ID) is bound to a length-encoding channel.
A validator warning would be the more valuable of the two, since it generalises to any template that adds a magnitude encoding later.
Two smaller observations from the same session
Bar Table appears to apply no number formatting at all. Compiling identical data as Bar Chart emits "format": ",.12~g"; as Bar Table no format string is emitted, so Price produces no currency symbol and Percentage produces no percent sign. Both are documented as formatting behaviours of those types.
- Long field names are altered in
Bar Table headers: a field named Hits across 6 clips renders as Hits_across 6_clips when the header wraps.
Thanks for open-sourcing this — the compact-spec-plus-compiler split is the right shape, and the Bar Table template in particular is excellent for dense label+value data once the type behaves.
What happens
The
Ranksemantic type's documented behaviour is honoured by the Bar Chart template but ignored by Bar Table, which encodes the ordinal as a bar length and a sequential colour ramp. The result is a chart that reads as the inverse of the data: rank 1 gets the shortest, palest bar and the last-placed item gets the longest, darkest one.The agent skill documents (
flint://agent-skill):Neither template emits a
reverse, andBar Tableadditionally applies a sequential colour scale rather than a discrete one.validate_chartreturnsvalid: truewith zero warnings in both cases, so nothing in the toolchain flags it.Reproduction
flint-chart-mcp@0.4.1, backendvegalite.{ "data": { "values": [ { "Engine": "Inworld TTS-2", "Rank": 1 }, { "Engine": "xAI leo", "Rank": 2 }, { "Engine": "Kokoro am_michael", "Rank": 3 }, { "Engine": "Gemini", "Rank": 4 }, { "Engine": "Inworld 1.5-max", "Rank": 5 } ]}, "semantic_types": { "Engine": "Name", "Rank": "Rank" }, "chart_spec": { "chartType": "Bar Table", "encodings": { "y": { "field": "Engine" }, "x": { "field": "Rank" } }, "baseSize": { "width": 560, "height": 280 } } }Swapping
chartTypeto"Bar Chart"with everything else identical produces a sensible ordinal rendering — equal-sized marks positioned by rank, correct row order, no magnitude encoding. That contrast is what makes this look like a template gap rather than intended behaviour.Inspecting the compiled Vega-Lite:
sortreversenullnull,"ascending"Why it matters
An ordinal is not a magnitude — "how much better is 1st than 2nd" has no answer, so any length encoding of it is unfounded. This is the failure mode that is hardest to catch downstream, because the output is well-formed, passes validation, and is confidently wrong in a direction a reader will not question.
Suggested fix
Either honour the documented
Rankbehaviour in theBar Tabletemplate (discrete colour, no length encoding, 1 first), or havevalidate_chartwarn when an ordinal semantic type (Rank, and arguablyID) is bound to a length-encoding channel.A validator warning would be the more valuable of the two, since it generalises to any template that adds a magnitude encoding later.
Two smaller observations from the same session
Bar Tableappears to apply no number formatting at all. Compiling identical data asBar Chartemits"format": ",.12~g"; asBar Tableno format string is emitted, soPriceproduces no currency symbol andPercentageproduces no percent sign. Both are documented as formatting behaviours of those types.Bar Tableheaders: a field namedHits across 6 clipsrenders asHits_across 6_clipswhen the header wraps.Thanks for open-sourcing this — the compact-spec-plus-compiler split is the right shape, and the
Bar Tabletemplate in particular is excellent for dense label+value data once the type behaves.