Skip to content

Commit

Permalink
fix(Spline): Improve defaultPathData() by handling explicit `pathData…
Browse files Browse the repository at this point in the history
…` (ex. Bar) and non-cartesian (ex. graph/hierarchy) usage
  • Loading branch information
techniq committed Nov 12, 2024
1 parent 43d0a31 commit 7dcd042
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-wasps-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(Spline): Improve defaultPathData() by handling explicit `pathData` (ex. Bar) and non-cartesian (ex. graph/hierarchy) usage
36 changes: 23 additions & 13 deletions packages/layerchart/src/lib/components/Spline.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -29,6 +30,7 @@
y: contextY,
yRange,
radial,
config,
} = chartContext();
/** Override data instead of using context */
Expand Down Expand Up @@ -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 = '';
Expand Down
25 changes: 25 additions & 0 deletions packages/layerchart/src/lib/utils/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
94 changes: 94 additions & 0 deletions packages/layerchart/src/routes/docs/examples/Columns/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,51 @@
</Preview>
</Toggle>

<h2>Tween on mount (rounded edge)</h2>

<Toggle on let:on={show} let:toggle>
<div class="grid grid-cols-[auto,1fr] gap-2 mb-2">
<Field label="Show bars" let:id>
<Switch checked={show} on:change={toggle} {id} size="md" />
</Field>
</div>

<Preview {data}>
<div class="h-[300px] p-4 border rounded">
<Chart
{data}
x="date"
xScale={scaleBand().padding(0.4)}
y="value"
yDomain={[0, null]}
yNice={4}
padding={{ left: 16, bottom: 24 }}
>
<Svg>
<Axis placement="left" grid rule />
<Axis
placement="bottom"
format={(d) => format(d, PeriodType.Day, { variant: 'short' })}
rule
/>
{#if show}
<Bars
tweened={{
duration: 500,
easing: cubicInOut,
}}
radius={4}
rounded="edge"
strokeWidth={1}
class="fill-primary"
/>
{/if}
</Svg>
</Chart>
</div>
</Preview>
</Toggle>

<h2>Stagger tween on mount</h2>

<Toggle on let:on={show} let:toggle>
Expand Down Expand Up @@ -816,6 +861,55 @@
</Preview>
</Toggle>

<h2>Stagger tween on mount (rounded edge)</h2>

<Toggle on let:on={show} let:toggle>
<div class="grid grid-cols-[auto,1fr] gap-2 mb-2">
<Field label="Show bars" let:id>
<Switch checked={show} on:change={toggle} {id} size="md" />
</Field>
</div>

<Preview {data}>
<div class="h-[300px] p-4 border rounded">
<Chart
{data}
x="date"
xScale={scaleBand().padding(0.4)}
y="value"
yDomain={[0, null]}
yNice={4}
padding={{ left: 16, bottom: 24 }}
>
<Svg>
<Axis placement="left" grid rule />
<Axis
placement="bottom"
format={(d) => format(d, PeriodType.Day, { variant: 'short' })}
rule
/>
{#if show}
{#each data as bar, i}
<Bar
{bar}
tweened={{
duration: 500,
easing: cubicInOut,
delay: i * 30,
}}
radius={4}
rounded="edge"
strokeWidth={1}
class="fill-primary"
/>
{/each}
{/if}
</Svg>
</Chart>
</div>
</Preview>
</Toggle>

<h2>Grouped</h2>

<Preview data={groupedData}>
Expand Down

0 comments on commit 7dcd042

Please sign in to comment.