diff --git a/.changeset/swift-wasps-chew.md b/.changeset/swift-wasps-chew.md new file mode 100644 index 000000000..a63127d8f --- /dev/null +++ b/.changeset/swift-wasps-chew.md @@ -0,0 +1,5 @@ +--- +'layerchart': patch +--- + +fix(Spline): Improve defaultPathData() by handling explicit `pathData` (ex. Bar) and non-cartesian (ex. graph/hierarchy) usage diff --git a/packages/layerchart/src/lib/components/Spline.svelte b/packages/layerchart/src/lib/components/Spline.svelte index ec9523e6d..c9f54e598 100644 --- a/packages/layerchart/src/lib/components/Spline.svelte +++ b/packages/layerchart/src/lib/components/Spline.svelte @@ -20,6 +20,7 @@ import { motionStore } from '$lib/stores/motionStore.js'; import { accessor, type Accessor } from '../utils/common.js'; import { isScaleBand } from '../utils/scales.js'; + import { flattenPathData } from '../utils/path.js'; const { data: contextData, @@ -29,6 +30,7 @@ y: contextY, yRange, radial, + config, } = chartContext(); /** Override data instead of using context */ @@ -100,19 +102,27 @@ /** Provide initial `0` horizontal baseline and initially hide/untrack scale changes so not reactive (only set on initial mount) */ function defaultPathData() { - const path = $radial - ? lineRadial() - .angle((d) => $xScale(xAccessor(d))) - .radius((d) => Math.min($yScale(0), $yRange[0])) - : d3Line() - .x((d) => $xScale(xAccessor(d)) + xOffset) - .y((d) => Math.min($yScale(0), $yRange[0])); - - path.defined(defined ?? ((d) => xAccessor(d) != null && yAccessor(d) != null)); - - if (curve) path.curve(curve); - - return path(data ?? $contextData); + if (pathData) { + // Flatten all `y` coordinates of pre-defined `pathData` + return flattenPathData(pathData, Math.min($yScale(0), $yRange[0])); + } else if ($config.x) { + // Only use default line if `x` accessor is defined (cartesian chart) + const path = $radial + ? lineRadial() + .angle((d) => $xScale(xAccessor(d))) + .radius((d) => Math.min($yScale(0), $yRange[0])) + : d3Line() + .x((d) => $xScale(xAccessor(d)) + xOffset) + .y((d) => Math.min($yScale(0), $yRange[0])); + + path.defined(defined ?? ((d) => xAccessor(d) != null && yAccessor(d) != null)); + + if (curve) path.curve(curve); + + return path(data ?? $contextData); + } else { + return ''; + } } let d: string | null = ''; diff --git a/packages/layerchart/src/lib/utils/path.ts b/packages/layerchart/src/lib/utils/path.ts index 38b18fe64..d0b77e5d6 100644 --- a/packages/layerchart/src/lib/utils/path.ts +++ b/packages/layerchart/src/lib/utils/path.ts @@ -29,3 +29,28 @@ export function circlePath(dimensions: { a ${r},${r} 0 1,${sweep} -${r * 2},0 `; } + +/** Flatten all `y` coordinates to `0` */ +export function flattenPathData(pathData: string, yOverride = 0) { + let result = pathData; + + // Match commands with y-coordinates, and replace `y` coordinate with `0` (or override such as `yScale(0)`) + result = result.replace(/([MLTQCSAZ])(-?\d*\.?\d+),(-?\d*\.?\d+)/g, (match, command, x, y) => { + return `${command}${x},${yOverride}`; + }); + + // Replace all vertical line commands (ex. `v123`) with `0` height + result = result.replace(/([v])(-?\d*\.?\d+)/g, (match, command, l) => { + return `${command}${0}`; + }); + + // TODO: Flatten all elliptical arc commands (ex. `a4,4 0 0 1 4,4`) with `0` height + // result = result.replace( + // /a(\d+),(\d+) (\d+) (\d+) (\d+) (\d+),(\d+)/g, + // (match, rx, ry, rot, large, sweep, x, y) => { + // return `a${rx},0 ${rot} ${large} ${sweep} ${x},0`; + // } + // ); + + return result; +} diff --git a/packages/layerchart/src/routes/docs/examples/Columns/+page.svelte b/packages/layerchart/src/routes/docs/examples/Columns/+page.svelte index 840076de3..671e4f2b2 100644 --- a/packages/layerchart/src/routes/docs/examples/Columns/+page.svelte +++ b/packages/layerchart/src/routes/docs/examples/Columns/+page.svelte @@ -768,6 +768,51 @@ +

Tween on mount (rounded edge)

+ + +
+ + + +
+ + +
+ + + + format(d, PeriodType.Day, { variant: 'short' })} + rule + /> + {#if show} + + {/if} + + +
+
+
+

Stagger tween on mount

@@ -816,6 +861,55 @@ +

Stagger tween on mount (rounded edge)

+ + +
+ + + +
+ + +
+ + + + format(d, PeriodType.Day, { variant: 'short' })} + rule + /> + {#if show} + {#each data as bar, i} + + {/each} + {/if} + + +
+
+
+

Grouped