Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmarks/conformance/DEFINITION-COVERAGE-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ ownership boundary.
| [54 — Bump ranking](./cases/54-bump-ranking/example.tsx) | Definition now | Public ranking and maximum-date selection feed native line, dot, and text marks. |
| [59 — Grouped reducer bars](./cases/59-grouped-reducer-bars/example.tsx) | Definition now | Public grouped mean output feeds native bars and labels. |
| [60 — Lag autocorrelation](./cases/60-lag-autocorrelation/example.tsx) | Definition now | Public two-row windows form lag pairs for native dots and a reference line. |
| [70 — Composed chart](./cases/70-composed-chart/example.tsx) | First-party primitive | Native `barY.maxThickness` applies a centered cap after final band, subgroup, and inset resolution; one private resolver also serves `barX`. |
| [70 — Three-axis composed chart](./cases/70-composed-chart/example.tsx) | First-party primitive | Named scales place precipitation and wind on stacked right axes, while native `barY.maxThickness` caps the final resolved band. |
| [72 — Mixed bars](./cases/72-recharts-mixed-bars/example.tsx) | Definition now | Public grouped bar layout expresses adjacent stacked and independent slots. |
| [73 — Many-point scatter](./cases/73-many-point-scatter/example.tsx) | Definition now | Native dots own explicit keys and a configured radius scale over selected source rows. |
| [102 — World choropleth](./cases/102-world-choropleth/example.tsx) | Definition now | `geoShape`, a public Equal Earth descriptor, and threshold color render joined countries. |
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/conformance/DEFINITION-COVERAGE-OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ presentation policy.
| [63-violin-distributions — Violin distribution comparison](./cases/63-violin-distributions/example.tsx) | First-party primitive | `violin-mark` | `example.tsx`, `tanstack.test.ts` |
| [64-marimekko-mosaic — Marimekko survey composition](./cases/64-marimekko-mosaic/example.tsx) | First-party primitive | `mosaic-layout` | `example.tsx`, `tanstack.test.ts` |
| [65-voronoi-nearest-tooltip — Voronoi nearest-point interaction](./cases/65-voronoi-nearest-tooltip/example.tsx) | Optional primitive | `spatial-voronoi` | `example.tsx` |
| [70-composed-chart — Layered Seattle weather chart](./cases/70-composed-chart/example.tsx) | First-party primitive | `bar-max-thickness` | `example.tsx`, `tanstack.test.ts` |
| [70-composed-chart — Seattle weather with three y axes](./cases/70-composed-chart/example.tsx) | First-party primitive | `bar-max-thickness` | `example.tsx`, `tanstack.test.ts` |
| [71-recharts-population-pyramid — Palmer penguins by species and sex](./cases/71-recharts-population-pyramid/example.tsx) | Definition now | `current-definition-api` | `example.tsx`, `tanstack.test.ts` |
| [72-recharts-mixed-bars — Stacked and adjacent Seattle weather bars](./cases/72-recharts-mixed-bars/example.tsx) | Definition now | `current-definition-api` | `example.tsx` |
| [73-many-point-scatter — Automobile specifications scatter](./cases/73-many-point-scatter/example.tsx) | Definition now | `current-definition-api` | `example.tsx`, `tanstack.test.ts` |
Expand Down
11 changes: 6 additions & 5 deletions benchmarks/conformance/cases/70-composed-chart/case.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
"referenceRenderer": "recharts",
"order": 700,
"id": "70-composed-chart",
"title": "Layered Seattle weather chart",
"title": "Seattle weather with three y axes",
"family": "composition",
"intent": "Compare daily maximum temperature, precipitation, minimum temperature, and wind using an area, bars, a line, and points.",
"intent": "Compare daily temperature, precipitation, and wind without forcing unlike units onto one y scale.",
"support": "composed",
"features": [
"mixed marks",
"independent y scales",
"left axis and stacked right axes",
"daily observations",
"monotone curves",
"shared numeric scale",
"resolved maximum bar thickness"
],
"geometry": [
Expand All @@ -25,7 +26,7 @@
"url": "https://recharts.github.io/en-US/examples/LineBarAreaComposedChart/"
},
"ai": {
"create": "Create one chart from Seattle weather observations that layers maximum temperature as an area, precipitation as bars, minimum temperature as a monotone line, and wind as red points.",
"maintain": "Select a different weather window while preserving source dates and measures, mark order, colors, and cast-free channel inference."
"create": "Create one chart from Seattle weather observations that uses a left temperature axis, an independent right precipitation axis for bars, and a second right wind axis for points.",
"maintain": "Select a different weather window while preserving source dates, units, named scale bindings, stacked right axes, mark order, colors, and cast-free channel inference."
}
}
34 changes: 31 additions & 3 deletions benchmarks/conformance/cases/70-composed-chart/example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const createExampleChart = (input: ChartOptions) => {
id: 'precipitation-bars',
x: 'date',
y: 'precipitation',
yScale: 'precipitation',
fill: '#413ea0',
maxThickness: 20,
}),
Expand All @@ -43,8 +44,10 @@ export const createExampleChart = (input: ChartOptions) => {
curve: monotone,
}),
dot(rows, {
id: 'wind-points',
x: 'date',
y: 'wind',
yScale: 'wind',
fill: '#ef4444',
r: 4.5,
}),
Expand All @@ -56,10 +59,35 @@ export const createExampleChart = (input: ChartOptions) => {
ticks: { format: (value: Date) => dateFormat.format(value) },
},
},
y: { scale: scaleLinear, grid: true, axis: { ticks: { count: 5 } } },
y: {
scale: scaleLinear,
grid: true,
axis: {
label: 'Temperature (°C)',
ticks: { count: 5 },
},
},
precipitation: {
channel: 'y',
side: 'right',
scale: scaleLinear,
axis: {
label: 'Precipitation (mm)',
ticks: { count: 5 },
},
},
wind: {
channel: 'y',
side: 'right',
scale: scaleLinear,
axis: {
label: 'Wind (m/s)',
ticks: { count: 5 },
},
},
},

margin: { top: 20, right: 20, bottom: 50, left: 80 },
margin: { top: 20, bottom: 50 },
},
{ keyboard: true, tooltip: exampleTooltip },
)
Expand All @@ -68,7 +96,7 @@ export interface ChartOptions {
revision: number
}

export const exampleAriaLabel = 'Layered Seattle weather'
export const exampleAriaLabel = 'Seattle weather with three y axes'

export const chart = createExampleChart({
revision: 0,
Expand Down
40 changes: 37 additions & 3 deletions benchmarks/conformance/cases/70-composed-chart/recharts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function chart(input: ConformanceInput) {
margin: { top: 20, right: 20, bottom: 20, left: 20 },
accessibilityLayer: true,
role: 'img',
title: 'Layered Seattle weather',
title: 'Seattle weather with three y axes',
},
[
createElement(CartesianGrid, {
Expand All @@ -47,14 +47,45 @@ function chart(input: ConformanceInput) {
value instanceof Date ? dateFormat.format(value) : String(value),
}),
createElement(YAxis, {
key: 'y',
key: 'temperature-axis',
yAxisId: 'temperature',
tickCount: 5,
width: 60,
label: {
value: 'Temperature (°C)',
angle: -90,
position: 'insideLeft',
},
}),
createElement(YAxis, {
key: 'precipitation-axis',
yAxisId: 'precipitation',
orientation: 'right',
tickCount: 5,
width: 72,
label: {
value: 'Precipitation (mm)',
angle: 90,
position: 'insideRight',
},
}),
createElement(YAxis, {
key: 'wind-axis',
yAxisId: 'wind',
orientation: 'right',
tickCount: 5,
width: 60,
label: {
value: 'Wind (m/s)',
angle: 90,
position: 'insideRight',
},
}),
createElement(Area, {
key: 'area',
type: 'monotone',
dataKey: 'temp_max',
yAxisId: 'temperature',
fill: '#8884d8',
fillOpacity: 0.2,
stroke: '#8884d8',
Expand All @@ -63,6 +94,7 @@ function chart(input: ConformanceInput) {
createElement(Bar, {
key: 'bar',
dataKey: 'precipitation',
yAxisId: 'precipitation',
barSize: 20,
fill: '#413ea0',
isAnimationActive: false,
Expand All @@ -71,6 +103,7 @@ function chart(input: ConformanceInput) {
key: 'line',
type: 'monotone',
dataKey: 'temp_min',
yAxisId: 'temperature',
stroke: '#ff7300',
strokeWidth: 2,
dot: false,
Expand All @@ -79,11 +112,12 @@ function chart(input: ConformanceInput) {
createElement(Scatter, {
key: 'scatter',
dataKey: 'wind',
yAxisId: 'wind',
fill: '#ef4444',
isAnimationActive: false,
}),
],
)
}

export const mount = rechartsMount(chart, 'Layered Seattle weather')
export const mount = rechartsMount(chart, 'Seattle weather with three y axes')
29 changes: 28 additions & 1 deletion benchmarks/conformance/cases/70-composed-chart/tanstack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createChartRuntime } from '@tanstack/charts'
import { describe, expect, it } from 'vitest'
import { loadTanStackSources } from '../../native-catalog'
import { createExampleChart } from './tanstack'
import type { SceneNode } from '@tanstack/charts'
import type { SceneNode, SceneRule } from '@tanstack/charts'
import type { ConformanceInput } from '../../types'

const input = {
Expand Down Expand Up @@ -42,13 +42,40 @@ describe('native composed-chart bar sizing', () => {
})
})

it('binds named scales and stacks both independent right-side axes', () => {
const scene = render(input)
const precipitationPoint = scene.points.find(
({ markId }) => markId === 'precipitation-bars',
)
const precipitationAxis = flatten(scene.nodes).find(
(node): node is SceneRule =>
node.kind === 'rule' && node.key === 'precipitation-axis',
)
const windAxis = flatten(scene.nodes).find(
(node): node is SceneRule =>
node.kind === 'rule' && node.key === 'wind-axis',
)
if (!precipitationPoint) {
throw new Error('Expected a precipitation point')
}

expect(scene.scales.precipitation).toBeDefined()
expect(precipitationPoint.y).toBe(
scene.scales.precipitation?.map(precipitationPoint.yValue),
)
expect(precipitationAxis?.x1).toBe(scene.chart.x + scene.chart.width)
expect(windAxis?.x1).toBeGreaterThan(precipitationAxis?.x1 ?? Infinity)
})

it('does not hide responsive bar geometry outside the definition', async () => {
const closure = await loadTanStackSources('70-composed-chart')
const source = closure.files.map((file) => file.source).join('\n')

expect(closure.files.map((file) => file.path)).toEqual(['example.tsx'])
expect(closure.roles.support.files).toBe(0)
expect(source).toContain('maxThickness: 20')
expect(source).toContain("yScale: 'precipitation'")
expect(source).toContain("yScale: 'wind'")
expect(source).not.toContain('defineChart(({')
expect(source).not.toContain('innerWidth')
expect(source).not.toContain('categoryBandwidth')
Expand Down
6 changes: 5 additions & 1 deletion benchmarks/conformance/cases/70-composed-chart/tanstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { tanstackExampleMount } from '../../shared/mount'

export * from './example'

export const mount = tanstackExampleMount(createExampleChart, exampleAriaLabel)
export const mount = tanstackExampleMount(
createExampleChart,
exampleAriaLabel,
{ guides: true, margin: true },
)

export const catalogCase = mount
11 changes: 6 additions & 5 deletions benchmarks/conformance/catalog-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -2384,15 +2384,16 @@
"referenceRenderer": "recharts",
"order": 700,
"id": "70-composed-chart",
"title": "Layered Seattle weather chart",
"title": "Seattle weather with three y axes",
"family": "composition",
"intent": "Compare daily maximum temperature, precipitation, minimum temperature, and wind using an area, bars, a line, and points.",
"intent": "Compare daily temperature, precipitation, and wind without forcing unlike units onto one y scale.",
"support": "composed",
"features": [
"mixed marks",
"independent y scales",
"left axis and stacked right axes",
"daily observations",
"monotone curves",
"shared numeric scale",
"resolved maximum bar thickness"
],
"geometry": [
Expand All @@ -2418,8 +2419,8 @@
"url": "https://recharts.github.io/en-US/examples/LineBarAreaComposedChart/"
},
"ai": {
"create": "Create one chart from Seattle weather observations that layers maximum temperature as an area, precipitation as bars, minimum temperature as a monotone line, and wind as red points.",
"maintain": "Select a different weather window while preserving source dates and measures, mark order, colors, and cast-free channel inference."
"create": "Create one chart from Seattle weather observations that uses a left temperature axis, an independent right precipitation axis for bars, and a second right wind axis for points.",
"maintain": "Select a different weather window while preserving source dates, units, named scale bindings, stacked right axes, mark order, colors, and cast-free channel inference."
},
"entries": {
"example": "benchmarks/conformance/cases/70-composed-chart/example.tsx"
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/conformance/previews/70-composed-chart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions benchmarks/conformance/previews/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 1,
"width": 288,
"height": 192,
"sourceHash": "99fa46439aa2b513dc5c09e9bc5e9f4d7dacb18ed9e5f5d289df9d16082bb7fa",
"sourceHash": "77c9a85664a8a74ac8d3d00ebbdc9ff1be08ecd5859b962d16077cef8d996da0",
"assets": [
{
"id": "01-line-gaps",
Expand Down Expand Up @@ -306,8 +306,8 @@
},
{
"id": "70-composed-chart",
"sha256": "e67a703c89949a36f5b336a2095223a53e5b5a91a8a055868cb475a1a046003d",
"bytes": 4707
"sha256": "642139631cae72ef76ff81e148e6d40e2ff83225a11b582a32878d0f166beae7",
"bytes": 14436
},
{
"id": "71-recharts-population-pyramid",
Expand Down
2 changes: 1 addition & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@
"to": "/charts/catalog/charts/65-voronoi-nearest-tooltip/"
},
{
"label": "Layered Seattle weather chart",
"label": "Seattle weather with three y axes",
"to": "/charts/catalog/charts/70-composed-chart/"
},
{
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/scales-guides-and-color.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ const chart = defineChart({
})
```

<!-- ::chart-example id=70-composed-chart height=480 -->

A named scale must declare `channel: 'x'` or `channel: 'y'`. Its mark binding
must use the same channel. The ID `color` is reserved for the shared visual
color scale and cannot name a Cartesian position scale.
Expand Down
35 changes: 35 additions & 0 deletions examples/sandbox/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import type { PenguinsRow } from '@tanstack/charts-data/penguins'
import {
createAgreementChart,
createAaplPriceVolumeChart,
createCarEconomyChart,
createIndustryChart,
createPenguinChart,
Expand Down Expand Up @@ -46,6 +47,13 @@ export function App() {
svgAnimation: { duration: 800, easing: 'ease-out' },
},
),
aaplPriceVolume: defineChart(
createAaplPriceVolumeChart({ rows: data.aapl }),
{
keyboard: false,
svgAnimation: { duration: 720, easing: 'ease-out' },
},
),
industries: defineChart(
createIndustryChart({
rows: data.industries,
Expand Down Expand Up @@ -469,6 +477,33 @@ export function App() {
))}
</div>
</article>

<article className="card multi-axis-card">
<CardHeader
eyebrow="AAPL price and volume"
value="Independent axes"
>
<div className="legend">
<span>
<i style={{ background: '#ff625a' }} />
Close
</span>
<span>
<i style={{ background: '#8579ff' }} />
Volume
</span>
</div>
</CardHeader>
<div className="chart-wrap">
<Chart
definition={definitions.aaplPriceVolume}
height={280}
initialWidth={1120}
ariaLabel="Apple closing price and trading volume"
ariaDescription="Closing price uses the left dollar axis. Trading volume uses the independent right axis."
/>
</div>
</article>
</section>
</div>
</main>
Expand Down
Loading