diff --git a/.changeset/line-cap-options.md b/.changeset/line-cap-options.md
new file mode 100644
index 00000000..2ea20a1a
--- /dev/null
+++ b/.changeset/line-cap-options.md
@@ -0,0 +1,6 @@
+---
+'@tanstack/charts': minor
+---
+
+Add `lineCap` and `lineJoin` options to `lineY` and `lineX`. Both options use
+the renderer-supported `SceneStyle` values and continue to default to `round`.
diff --git a/API-FRICTION.md b/API-FRICTION.md
index 5204435b..14c78c25 100644
--- a/API-FRICTION.md
+++ b/API-FRICTION.md
@@ -331,6 +331,7 @@ Each entry records:
| F-292 | Fixed preview paints ignored the selected site theme | Tooling | resolved |
| F-293 | Root scale slots blocked named axes | API | resolved |
| F-294 | Automatic mark renderers imposed shared host plumbing | API | resolved |
+| F-295 | Line marks forced round endpoints | API | resolved |
## Findings
@@ -8505,3 +8506,20 @@ Each entry records:
React Native Metro gates, and framework package checks pass. Bundle boundary
checks keep SVG-only entries free of Canvas and measure the opt-in mixed
representative and React consumers at 35.68 KiB and 41.63 KiB gzip.
+
+### F-295 - Line marks forced round endpoints
+
+- Status: resolved
+- Severity: medium
+- Owner: API
+- Observed in: migrating Rewardo's composed statistics chart from Recharts
+- Friction: `lineY` and `lineX` exposed stroke width, opacity, dash pattern,
+ and curve options, but hard-coded round caps and joins. Reproducing the
+ previous Recharts line, which inherited SVG's butt cap, required replacing
+ or patching rendered output.
+- Decision: expose `lineCap` and `lineJoin` on the shared line options using
+ the values from `SceneStyle`. Preserve round as the default for both options.
+- Verification: public type tests bind both line APIs to the scene renderer's
+ supported values. Scene, SVG, Canvas, motion, export, and React Native tests
+ preserve configured values. The Rewardo chart can use `lineCap: 'butt'` on
+ its existing pink `lineY` mark.
diff --git a/benchmarks/bundle-size/universal-baseline.json b/benchmarks/bundle-size/universal-baseline.json
index 486ae8f9..8f88756c 100644
--- a/benchmarks/bundle-size/universal-baseline.json
+++ b/benchmarks/bundle-size/universal-baseline.json
@@ -3,16 +3,16 @@
"policy": "Exact minified and gzip output for entries that optional features must not affect. Review every change before updating.",
"bundles": {
"D3-scale line scene": {
- "bytes": 50813,
- "gzip": 19070
+ "bytes": 50836,
+ "gzip": 19079
},
"D3-scale line + static SVG": {
- "bytes": 55544,
- "gzip": 20776
+ "bytes": 55567,
+ "gzip": 20785
},
"Representative marks": {
- "bytes": 75617,
- "gzip": 27582
+ "bytes": 75640,
+ "gzip": 27591
},
"TanStack DOM host": {
"bytes": 74947,
@@ -23,24 +23,24 @@
"gzip": 26831
},
"React line consumer": {
- "bytes": 100766,
- "gzip": 36222
+ "bytes": 100789,
+ "gzip": 36231
},
"Compact-scale line scene": {
- "bytes": 33265,
- "gzip": 11935
+ "bytes": 33288,
+ "gzip": 11944
},
"React compact-scale line consumer": {
- "bytes": 83266,
- "gzip": 29144
+ "bytes": 83289,
+ "gzip": 29153
},
"Custom-scale line scene": {
- "bytes": 31447,
- "gzip": 11193
+ "bytes": 31470,
+ "gzip": 11201
},
"D3 linear-scale line scene": {
- "bytes": 50745,
- "gzip": 19032
+ "bytes": 50768,
+ "gzip": 19041
}
}
}
diff --git a/benchmarks/comparison/bundle-baseline.json b/benchmarks/comparison/bundle-baseline.json
index a22a54c1..2087d60d 100644
--- a/benchmarks/comparison/bundle-baseline.json
+++ b/benchmarks/comparison/bundle-baseline.json
@@ -1,8 +1,8 @@
{
"schemaVersion": 4,
- "generatedAt": "2026-08-26T21:51:12.832Z",
+ "generatedAt": "2026-08-27T21:38:32.371Z",
"packageVersions": {
- "tanstack": "0.15.0",
+ "tanstack": "0.16.0",
"chartjs": "4.5.1",
"echarts": "6.1.0",
"recharts": "3.10.1",
@@ -12,7 +12,7 @@
"tanstack": {
"kind": "workspace",
"revision": "3df87d71f0305e5a450c10b66940f76f3e14259a",
- "inputDigest": "sha256:b1016e599e63d05e1647b9d27adabaaed7a38bb44dac8d6ba0fef06b8b8bd38e"
+ "inputDigest": "sha256:a5332d205ee6b678314f0a444dc2198cb002c6b55b49c7bf1c5acd0ea7ed1783"
},
"chartjs": {
"kind": "package",
@@ -45,25 +45,25 @@
},
"bundles": {
"tanstack-line-basic": {
- "minifiedBytes": 110431,
- "gzipBytes": 39891,
- "brotliBytes": 35325,
- "incrementalGzipBytes": 39891,
- "incrementalBrotliBytes": 35325
+ "minifiedBytes": 110454,
+ "gzipBytes": 39901,
+ "brotliBytes": 35367,
+ "incrementalGzipBytes": 39901,
+ "incrementalBrotliBytes": 35367
},
"tanstack-line-interactive": {
- "minifiedBytes": 115872,
- "gzipBytes": 41648,
- "brotliBytes": 36746,
- "incrementalGzipBytes": 41648,
- "incrementalBrotliBytes": 36746
+ "minifiedBytes": 115895,
+ "gzipBytes": 41661,
+ "brotliBytes": 36828,
+ "incrementalGzipBytes": 41661,
+ "incrementalBrotliBytes": 36828
},
"tanstack-line-advanced": {
- "minifiedBytes": 123062,
- "gzipBytes": 43974,
- "brotliBytes": 38764,
- "incrementalGzipBytes": 43974,
- "incrementalBrotliBytes": 38764
+ "minifiedBytes": 123085,
+ "gzipBytes": 43988,
+ "brotliBytes": 38803,
+ "incrementalGzipBytes": 43988,
+ "incrementalBrotliBytes": 38803
},
"tanstack-bar-basic": {
"minifiedBytes": 119225,
diff --git a/benchmarks/conformance/cases/70-composed-chart/case.json b/benchmarks/conformance/cases/70-composed-chart/case.json
index 62dac152..b80c5320 100644
--- a/benchmarks/conformance/cases/70-composed-chart/case.json
+++ b/benchmarks/conformance/cases/70-composed-chart/case.json
@@ -13,6 +13,7 @@
"left axis and stacked right axes",
"daily observations",
"monotone curves",
+ "butt line cap",
"resolved maximum bar thickness"
],
"geometry": [
diff --git a/benchmarks/conformance/cases/70-composed-chart/example.tsx b/benchmarks/conformance/cases/70-composed-chart/example.tsx
index 02187acb..c92d3bbc 100644
--- a/benchmarks/conformance/cases/70-composed-chart/example.tsx
+++ b/benchmarks/conformance/cases/70-composed-chart/example.tsx
@@ -41,6 +41,7 @@ export const createExampleChart = (input: ChartOptions) => {
y: 'temp_min',
stroke: '#ff7300',
strokeWidth: 2,
+ lineCap: 'butt',
curve: monotone,
}),
dot(rows, {
diff --git a/benchmarks/conformance/catalog-index.json b/benchmarks/conformance/catalog-index.json
index 9d3c84f0..54a371a0 100644
--- a/benchmarks/conformance/catalog-index.json
+++ b/benchmarks/conformance/catalog-index.json
@@ -2394,6 +2394,7 @@
"left axis and stacked right axes",
"daily observations",
"monotone curves",
+ "butt line cap",
"resolved maximum bar thickness"
],
"geometry": [
diff --git a/benchmarks/conformance/previews/70-composed-chart.svg b/benchmarks/conformance/previews/70-composed-chart.svg
index 5de7df71..091ffce8 100644
--- a/benchmarks/conformance/previews/70-composed-chart.svg
+++ b/benchmarks/conformance/previews/70-composed-chart.svg
@@ -1 +1 @@
-
+
diff --git a/benchmarks/conformance/previews/manifest.json b/benchmarks/conformance/previews/manifest.json
index e1176205..8f98afe7 100644
--- a/benchmarks/conformance/previews/manifest.json
+++ b/benchmarks/conformance/previews/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"width": 288,
"height": 192,
- "sourceHash": "d3177faf65fca2414b431c195a003cf9afdceb8ae2f1b37e4f8cb81578dc8293",
+ "sourceHash": "99895d05b94419426e1065524564e39ba82fc1cdeae9c5875b4718ba6aeae5ee",
"assets": [
{
"id": "01-line-gaps",
@@ -306,8 +306,8 @@
},
{
"id": "70-composed-chart",
- "sha256": "642139631cae72ef76ff81e148e6d40e2ff83225a11b582a32878d0f166beae7",
- "bytes": 14436
+ "sha256": "db93c404c4e097cad237dca330dab72a1080f1e38e873755ef7ca86a81bbeb30",
+ "bytes": 14435
},
{
"id": "71-recharts-population-pyramid",
diff --git a/docs/comparison.md b/docs/comparison.md
index 912adf20..83f66b70 100644
--- a/docs/comparison.md
+++ b/docs/comparison.md
@@ -90,7 +90,7 @@ output model.
## Bundle snapshot
-Baseline date: `2026-08-26`.
+Baseline date: `2026-08-27`.
Controlled ranges cover 12 independently built, minified browser consumers:
line, bar, area, and scatter at basic, interactive, and advanced tiers. Only
@@ -106,7 +106,7 @@ Vega-Lite, AG Charts, and uPlot main exports were read from Bundlephobia on July
| Library | Bundle size | React externalized | Evidence |
| ------------------ | -------------------------------------- | -----------------: | ---------------------------------------------------------- |
-| TanStack Charts | 38.96–44.98 KiB | Not applicable | Controlled suite |
+| TanStack Charts | 38.97–44.98 KiB | Not applicable | Controlled suite |
| D3 | 90 KB gzip | — | External main export |
| Chart.js | 44.70–58.21 KiB | — | Controlled suite |
| Apache ECharts | 153.10–173.18 KiB | — | Controlled suite |
diff --git a/docs/examples/lines-and-areas.md b/docs/examples/lines-and-areas.md
index 03e8bd35..d72d0323 100644
--- a/docs/examples/lines-and-areas.md
+++ b/docs/examples/lines-and-areas.md
@@ -48,6 +48,7 @@ const chart = defineChart({
y: 'downloads',
points: true,
stroke: '#2563eb',
+ lineCap: 'butt',
}),
],
scales: {
@@ -64,6 +65,10 @@ const chart = defineChart({
export default chart
```
+`lineCap: 'butt'` ends the stroke at each path endpoint. Omit it to retain the
+default round endpoints. `lineX` accepts the same `lineCap` and `lineJoin`
+options.
+
## Compare several series from a common baseline
Indexing each series to its first observation compares relative change when the
diff --git a/docs/reference/marks/line-and-area.md b/docs/reference/marks/line-and-area.md
index 2f776f67..7a3c4978 100644
--- a/docs/reference/marks/line-and-area.md
+++ b/docs/reference/marks/line-and-area.md
@@ -55,6 +55,8 @@ function lineY(
| `strokeOpacity` | `number` | SVG default | Stroke opacity |
| `strokeWidth` | `number` | `2.25` | Stroke width |
| `strokeDasharray` | `string` | None | SVG dash array |
+| `lineCap` | `SceneStyle["lineCap"]` | `"round"` | Stroke endpoint shape |
+| `lineJoin` | `SceneStyle["lineJoin"]` | `"round"` | Stroke corner shape |
| `points` | `boolean` | `false` | Draws a radius-`2.5` dot at each valid point |
| `curve` | `ChartCurve` | Straight segments | Optional path generator |
| `states` | `readonly ChartMarkState[]` | None | Focus-driven presentation overrides |
diff --git a/packages/charts-core/docs/comparison.md b/packages/charts-core/docs/comparison.md
index 912adf20..83f66b70 100644
--- a/packages/charts-core/docs/comparison.md
+++ b/packages/charts-core/docs/comparison.md
@@ -90,7 +90,7 @@ output model.
## Bundle snapshot
-Baseline date: `2026-08-26`.
+Baseline date: `2026-08-27`.
Controlled ranges cover 12 independently built, minified browser consumers:
line, bar, area, and scatter at basic, interactive, and advanced tiers. Only
@@ -106,7 +106,7 @@ Vega-Lite, AG Charts, and uPlot main exports were read from Bundlephobia on July
| Library | Bundle size | React externalized | Evidence |
| ------------------ | -------------------------------------- | -----------------: | ---------------------------------------------------------- |
-| TanStack Charts | 38.96–44.98 KiB | Not applicable | Controlled suite |
+| TanStack Charts | 38.97–44.98 KiB | Not applicable | Controlled suite |
| D3 | 90 KB gzip | — | External main export |
| Chart.js | 44.70–58.21 KiB | — | Controlled suite |
| Apache ECharts | 153.10–173.18 KiB | — | Controlled suite |
diff --git a/packages/charts-core/docs/examples/lines-and-areas.md b/packages/charts-core/docs/examples/lines-and-areas.md
index 03e8bd35..d72d0323 100644
--- a/packages/charts-core/docs/examples/lines-and-areas.md
+++ b/packages/charts-core/docs/examples/lines-and-areas.md
@@ -48,6 +48,7 @@ const chart = defineChart({
y: 'downloads',
points: true,
stroke: '#2563eb',
+ lineCap: 'butt',
}),
],
scales: {
@@ -64,6 +65,10 @@ const chart = defineChart({
export default chart
```
+`lineCap: 'butt'` ends the stroke at each path endpoint. Omit it to retain the
+default round endpoints. `lineX` accepts the same `lineCap` and `lineJoin`
+options.
+
## Compare several series from a common baseline
Indexing each series to its first observation compares relative change when the
diff --git a/packages/charts-core/docs/reference/marks/line-and-area.md b/packages/charts-core/docs/reference/marks/line-and-area.md
index 2f776f67..7a3c4978 100644
--- a/packages/charts-core/docs/reference/marks/line-and-area.md
+++ b/packages/charts-core/docs/reference/marks/line-and-area.md
@@ -55,6 +55,8 @@ function lineY(
| `strokeOpacity` | `number` | SVG default | Stroke opacity |
| `strokeWidth` | `number` | `2.25` | Stroke width |
| `strokeDasharray` | `string` | None | SVG dash array |
+| `lineCap` | `SceneStyle["lineCap"]` | `"round"` | Stroke endpoint shape |
+| `lineJoin` | `SceneStyle["lineJoin"]` | `"round"` | Stroke corner shape |
| `points` | `boolean` | `false` | Draws a radius-`2.5` dot at each valid point |
| `curve` | `ChartCurve` | Straight segments | Optional path generator |
| `states` | `readonly ChartMarkState[]` | None | Focus-driven presentation overrides |
diff --git a/packages/charts-core/src/canvas.test.ts b/packages/charts-core/src/canvas.test.ts
index 5ce7d73c..99609d49 100644
--- a/packages/charts-core/src/canvas.test.ts
+++ b/packages/charts-core/src/canvas.test.ts
@@ -77,6 +77,79 @@ afterEach(() => {
})
describe('Canvas renderer', () => {
+ it('paints authored caps and joins for both line directions', () => {
+ const scene = createChartScene(
+ defineChart({
+ marks: [
+ lineY([4, 9], { lineCap: 'butt', lineJoin: 'bevel' }),
+ lineX([4, 9], { lineCap: 'square', lineJoin: 'miter' }),
+ ],
+ scales: {
+ x: { scale: scaleLinear },
+ y: { scale: scaleLinear },
+ },
+ guides: false,
+ }),
+ { width: 300, height: 180 },
+ )
+ const container = document.createElement('div')
+ const surface = createCanvasChartRenderer().mount(container, () => {})
+
+ surface.render(scene, renderOptions())
+
+ const canvas = container.querySelector(
+ '.ts-chart-canvas__scene',
+ )
+ const painted = canvas ? contexts.get(canvas) : undefined
+ if (!painted) throw new Error('Expected a painted scene canvas')
+ expect(painted.operations).toEqual(
+ expect.arrayContaining([
+ 'lineCap:butt',
+ 'lineJoin:bevel',
+ 'lineCap:square',
+ 'lineJoin:miter',
+ ]),
+ )
+
+ surface.destroy()
+ })
+
+ it('paints round caps and joins by default for both line directions', () => {
+ const scene = createChartScene(
+ defineChart({
+ marks: [lineY([4, 9]), lineX([4, 9])],
+ scales: {
+ x: { scale: scaleLinear },
+ y: { scale: scaleLinear },
+ },
+ guides: false,
+ }),
+ { width: 300, height: 180 },
+ )
+ const container = document.createElement('div')
+ const surface = createCanvasChartRenderer().mount(container, () => {})
+
+ surface.render(scene, renderOptions())
+
+ const canvas = container.querySelector(
+ '.ts-chart-canvas__scene',
+ )
+ const painted = canvas ? contexts.get(canvas) : undefined
+ if (!painted) throw new Error('Expected a painted scene canvas')
+ expect(
+ painted.operations.filter((operation) =>
+ operation.startsWith('lineCap:'),
+ ),
+ ).toEqual(['lineCap:round', 'lineCap:round'])
+ expect(
+ painted.operations.filter((operation) =>
+ operation.startsWith('lineJoin:'),
+ ),
+ ).toEqual(['lineJoin:round', 'lineJoin:round'])
+
+ surface.destroy()
+ })
+
it('composes renderer-tagged marks in source order', () => {
const data = [
{ id: 'a', category: 'A', value: 3 },
diff --git a/packages/charts-core/src/export.test.ts b/packages/charts-core/src/export.test.ts
index 0e64ed83..2218614b 100644
--- a/packages/charts-core/src/export.test.ts
+++ b/packages/charts-core/src/export.test.ts
@@ -18,6 +18,8 @@ describe('optional export', () => {
defineChart({
marks: [
lineY([1, 3, 2], {
+ lineCap: 'butt',
+ lineJoin: 'bevel',
stroke: 'currentColor',
}),
],
@@ -34,6 +36,8 @@ describe('optional export', () => {
expect(result).toContain('width="480"')
expect(result).toContain('height="260"')
expect(result).toContain('aria-label="Exported chart"')
+ expect(result).toContain('stroke-linecap="butt"')
+ expect(result).toContain('stroke-linejoin="bevel"')
expect(result).not.toContain('data-ts-focus-layer')
})
diff --git a/packages/charts-core/src/line.test.ts b/packages/charts-core/src/line.test.ts
index 7d897ad3..3b67d052 100644
--- a/packages/charts-core/src/line.test.ts
+++ b/packages/charts-core/src/line.test.ts
@@ -5,11 +5,13 @@ import { d3Curve } from './d3-shape'
import { lineX, lineY } from './line'
import { createChartScene, defineChart } from './scene'
import { renderChartSvg } from './svg'
+import type { LineXOptions, LineYOptions } from './line'
import type {
ChartDefinition,
ChartMark,
SceneNode,
ScenePolyline,
+ SceneStyle,
} from './types'
interface Row {
@@ -40,6 +42,75 @@ const rows: readonly Row[] = [
},
]
+describe('line presentation', () => {
+ it('applies authored caps and joins to vertical and horizontal lines', () => {
+ const scene = createChartScene(
+ defineChart({
+ marks: [
+ lineY([4, 9], {
+ id: 'vertical',
+ lineCap: 'butt',
+ lineJoin: 'bevel',
+ }),
+ lineX([4, 9], {
+ id: 'horizontal',
+ lineCap: 'square',
+ lineJoin: 'miter-clip',
+ }),
+ ],
+ scales: {
+ x: { scale: scaleLinear },
+ y: { scale: scaleLinear },
+ },
+ }),
+ { width: 480, height: 260 },
+ )
+ const lines = flatten(scene.nodes).filter(
+ (node): node is ScenePolyline => node.kind === 'polyline',
+ )
+ const svg = renderChartSvg(scene, { ariaLabel: 'Line presentation' })
+
+ expect(lines.map(({ style }) => style)).toEqual([
+ expect.objectContaining({ lineCap: 'butt', lineJoin: 'bevel' }),
+ expect.objectContaining({ lineCap: 'square', lineJoin: 'miter-clip' }),
+ ])
+ expect(svg).toContain('stroke-linecap="butt" stroke-linejoin="bevel"')
+ expect(svg).toContain(
+ 'stroke-linecap="square" stroke-linejoin="miter-clip"',
+ )
+ })
+
+ it('keeps round caps and joins as the default', () => {
+ const scene = createChartScene(
+ defineChart({
+ marks: [
+ lineY([4, 9], { id: 'vertical' }),
+ lineX([4, 9], { id: 'horizontal' }),
+ ],
+ scales: {
+ x: { scale: scaleLinear },
+ y: { scale: scaleLinear },
+ },
+ }),
+ { width: 480, height: 260 },
+ )
+ const lines = flatten(scene.nodes).filter(
+ (node): node is ScenePolyline => node.kind === 'polyline',
+ )
+ const svg = renderChartSvg(scene, {
+ ariaLabel: 'Default line presentation',
+ })
+
+ expect(lines.map(({ style }) => style)).toEqual([
+ expect.objectContaining({ lineCap: 'round', lineJoin: 'round' }),
+ expect.objectContaining({ lineCap: 'round', lineJoin: 'round' }),
+ ])
+ expect(
+ svg.match(/stroke-linecap="round" stroke-linejoin="round"/g),
+ ).toHaveLength(2)
+ })
+})
+
describe('lineX mark', () => {
it('transposes the implicit value and index channels from lineY', () => {
const values = [4, 9, 7]
@@ -232,6 +303,24 @@ if (false) {
expectTypeOf(numeric).toEqualTypeOf>()
expectTypeOf(temporal).toEqualTypeOf>()
+ expectTypeOf['lineCap']>().toEqualTypeOf<
+ SceneStyle['lineCap']
+ >()
+ expectTypeOf['lineJoin']>().toEqualTypeOf<
+ SceneStyle['lineJoin']
+ >()
+ expectTypeOf['lineCap']>().toEqualTypeOf<
+ SceneStyle['lineCap']
+ >()
+ expectTypeOf['lineJoin']>().toEqualTypeOf<
+ SceneStyle['lineJoin']
+ >()
+
+ // @ts-expect-error Line caps must use a renderer-supported scene value.
+ lineY(rows, { x: 'at', y: 'value', lineCap: 'flat' })
+
+ // @ts-expect-error Line joins must use a renderer-supported scene value.
+ lineX(rows, { x: 'value', y: 'at', lineJoin: 'sharp' })
// @ts-expect-error Horizontal line values must be numeric.
lineX(rows, { x: 'category', y: 'at' })
diff --git a/packages/charts-core/src/line.ts b/packages/charts-core/src/line.ts
index 1734f929..937d7e52 100644
--- a/packages/charts-core/src/line.ts
+++ b/packages/charts-core/src/line.ts
@@ -13,6 +13,7 @@ import type {
Channel,
CartesianChartMark,
CartesianScaleBindings,
+ ChartCurve,
ChartKey,
ChartMark,
ChartMarkMotionOptions,
@@ -24,7 +25,7 @@ import type {
MarkChannelOutput,
MarkScaleBindings,
SceneNode,
- ChartCurve,
+ SceneStyle,
VisualChannel,
} from './types'
@@ -38,6 +39,8 @@ interface LineOptions
strokeOpacity?: number
strokeWidth?: number
strokeDasharray?: string
+ lineCap?: SceneStyle['lineCap']
+ lineJoin?: SceneStyle['lineJoin']
points?: boolean
curve?: ChartCurve
states?: readonly ChartMarkState>[]
@@ -299,8 +302,8 @@ function createLineMark(
strokeOpacity: options.strokeOpacity,
strokeWidth: options.strokeWidth ?? 2.25,
strokeDasharray: options.strokeDasharray,
- lineCap: 'round',
- lineJoin: 'round',
+ lineCap: options.lineCap || 'round',
+ lineJoin: options.lineJoin || 'round',
},
})
segment = []
diff --git a/packages/charts-core/src/motion.test.ts b/packages/charts-core/src/motion.test.ts
index a8f24f91..f8e3058a 100644
--- a/packages/charts-core/src/motion.test.ts
+++ b/packages/charts-core/src/motion.test.ts
@@ -28,6 +28,52 @@ const rows = [
]
describe('SVG motion', () => {
+ it('applies discrete line cap and join revisions synchronously', () => {
+ const definition = (
+ lineCap: 'butt' | 'round',
+ lineJoin: 'bevel' | 'round',
+ ) =>
+ defineChart({
+ motion: {
+ transition: { type: 'tween', duration: 100, easing: 'linear' },
+ },
+ marks: [
+ lineY(rows, {
+ id: 'series',
+ x: 'category',
+ y: 'value',
+ key: 'id',
+ lineCap,
+ lineJoin,
+ }),
+ ],
+ scales: {
+ x: { scale: scaleBand().domain(['A', 'B']) },
+ y: { scale: scaleLinear().domain([0, 100]) },
+ },
+ guides: false,
+ })
+ const first = createChartScene(definition('round', 'round'), {
+ width: 300,
+ height: 200,
+ })
+ const next = createChartScene(definition('butt', 'bevel'), {
+ width: 300,
+ height: 200,
+ })
+ const container = document.createElement('div')
+ const surface = motion({ initial: false }).mount(container, () => {})
+ surface.render(first, { ariaLabel: 'Line style motion' })
+ const line = container.querySelector('g.ts-chart__line > path')
+
+ surface.render(next, { ariaLabel: 'Line style motion' })
+
+ expect(container.querySelector('g.ts-chart__line > path')).toBe(line)
+ expect(line?.getAttribute('stroke-linecap')).toBe('butt')
+ expect(line?.getAttribute('stroke-linejoin')).toBe('bevel')
+ surface.destroy()
+ })
+
it('maps client coordinates through the rendered SVG transform', () => {
const scene = createChartScene(
defineChart({
diff --git a/packages/react-native-charts/src/SvgScene.test.tsx b/packages/react-native-charts/src/SvgScene.test.tsx
index 272eab7a..5e6924db 100644
--- a/packages/react-native-charts/src/SvgScene.test.tsx
+++ b/packages/react-native-charts/src/SvgScene.test.tsx
@@ -37,6 +37,8 @@ describe('React Native SVG scene renderer', () => {
expect(markup).toContain('