Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul Canvas support #295

Merged
merged 55 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
7848b5e
feat: Add new `renderPathData()` canvas util to simplify rendering SV…
techniq Dec 13, 2024
403a1fe
fix(circlePath): Correctly handle sweep argument
techniq Dec 13, 2024
fc84ef8
feat(Spline): Support Canvas context
techniq Dec 13, 2024
eab12cf
breaking(GeoPath): Simplify render prop use case by leveraging render…
techniq Dec 13, 2024
ac200d6
fix(GeoPath): Always apply classes defined on GeoPath onto <canvas> p…
techniq Dec 14, 2024
dc7ab11
Update more GeoPath render usage
techniq Dec 14, 2024
be414c2
docs(Spline): Resolve check errors
techniq Dec 15, 2024
d494b45
docs(Spline): Resolve additional check error (not sure why not failin…
techniq Dec 15, 2024
8c39f51
fix(Spline): Remove transfering classes to <canvas> (after removing f…
techniq Dec 15, 2024
f5b6024
docs(Spline): Add canvas explicit example
techniq Dec 15, 2024
3f7827d
feat(Area): Support Canvas context
techniq Dec 15, 2024
54561c0
fix(Canvas): Support multiple children (fix infinite loops, coordinat…
techniq Jan 3, 2025
52de26e
Cleanup unused imports
techniq Jan 6, 2025
4738e04
Add changeset (ComputedStyles)
techniq Jan 7, 2025
0f531c6
fix(Canvas/GeoPath): Fix tooltip ghosting (recreate geoPath() when `g…
techniq Jan 7, 2025
2a1dccf
Fix `pnpm check` errors/warnings
techniq Jan 7, 2025
f91c40c
docs(ZoomableMap): Hide tooltip when selecting state on canvas examples
techniq Jan 7, 2025
71f2f19
Add and use new `getComputedStyles()` to create/reuse single `svg` wh…
techniq Jan 9, 2025
df87438
fix(Circle): Redraw on position changes (fix tooltip highlight)
techniq Jan 9, 2025
50681a8
fix(renderPathData()): Adhere to CSS paint order: https://developer.m…
techniq Jan 9, 2025
2e5db72
fix(Rule): Remove unnecessary classes
techniq Jan 9, 2025
55782c7
feat(Line): Support Canvas context
techniq Jan 9, 2025
59807f8
fix(Line): Use tweened coords when rendering via canvas
techniq Jan 9, 2025
f3ebda0
fix(renderPathData()): Respect `opacity` CSS style via `globalAlpha` …
techniq Jan 9, 2025
0346677
feat: Add new `renderText()` canvas util to simplify rendering SVG pa…
techniq Jan 9, 2025
4259572
feat(Text): Support Canvas context
techniq Jan 9, 2025
a147c1c
docs: Use new `renderText()` to simplify manual rendering on geo example
techniq Jan 9, 2025
bf3b41d
feat: Add new `renderRect()` canvas util to simplify rendering rectan…
techniq Jan 9, 2025
bc83c5c
feat(Rect): Support Canvas context
techniq Jan 9, 2025
39e855f
fix(render()): Support `strokeDasharray` style via `ctx.setLineDash()`
techniq Jan 9, 2025
4bcd239
fix(Points): Render primative components (Circle /Link) instead of us…
techniq Jan 10, 2025
ba6da85
fix(Spline): Fix opacity for svg context
techniq Jan 10, 2025
30e14c7
Add `spikePath()` util
techniq Jan 10, 2025
2d1f4d8
docs: Simplify SpikeMap canvas example using `renderPathData()` and …
techniq Jan 10, 2025
dc5b22e
feat: Support `renderContext` prop to switch between Svg (default) an…
techniq Jan 10, 2025
2a3f7da
feat(Canvas): Support `center` prop (similar to `Svg`) to translate c…
techniq Jan 10, 2025
3c3c939
fix(PieChart): Use `center` prop (broke after recent refactor)
techniq Jan 10, 2025
1130406
Fix CI failures (`pnpm check`)
techniq Jan 10, 2025
7f392ad
feat(render): Support `fill-opacity`
techniq Jan 10, 2025
085fd8c
feat(Arc): Support Canvas context
techniq Jan 10, 2025
a9c16ba
feat(Group): Support Canvas context
techniq Jan 10, 2025
6e91781
fix(Canvas): Change registration API. Pass config instead of simple …
techniq Jan 10, 2025
dcc9440
fix: Reduce likihood of clipping for Canvas-rendered simplified chart…
techniq Jan 10, 2025
a27cc5a
fix: Support `fillOpacity` with more primatives
techniq Jan 10, 2025
ba1ff68
fix(render): Restore `globalAlpha` after using for `fillOpacity`
techniq Jan 10, 2025
6108ae5
fix(Text): Fix font color class override on canvas
techniq Jan 10, 2025
a2b69b1
fix(render): Do not apply fill when set to `none`
techniq Jan 10, 2025
811fe38
fix(Bar): Fix fully rounded when using canvas renderContext by always…
techniq Jan 10, 2025
279e73e
fix(render): Workaround when using `tabular-nums` causing `computedSt…
techniq Jan 10, 2025
3a0d228
feat(LinearGradient): Support Canvas context (WIP)
techniq Jan 12, 2025
231e624
Add parsePercent util and use for gradient color stops
techniq Jan 12, 2025
8e18545
breaking(LinearGradient|RadialGradient): Rename `url` slot prop to `g…
techniq Jan 12, 2025
133468e
chore(RadialGradient): Stub out canvas support
techniq Jan 12, 2025
683dc93
Simplify changesets
techniq Jan 12, 2025
3b0415c
Refine changeset
techniq Jan 12, 2025
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
5 changes: 5 additions & 0 deletions .changeset/bright-buses-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

feat: Support Canvas context for most primatives (Arc, Area, Circle, Group, Line, LinearGradient, Rect, Spline, and Text). Also updates components using primatives (Axis, Bar, Grid, Rule, and more)
5 changes: 5 additions & 0 deletions .changeset/chilly-jeans-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

Add `spikePath()` util
5 changes: 5 additions & 0 deletions .changeset/chilly-moles-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(circlePath): Correctly handle sweep argument
5 changes: 5 additions & 0 deletions .changeset/cold-penguins-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

feat: Update all simplified charts to support `renderContext` prop to switch between Svg (default) and Canvas (AreaChart, BarChart, LineChart, PieChart, and ScatterChart)
5 changes: 5 additions & 0 deletions .changeset/dry-dodos-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

feat(Canvas): Support `center` prop (similar to `Svg`) to translate children to center (useful for radial layouts)
5 changes: 5 additions & 0 deletions .changeset/dry-masks-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

feat: Add new `renderPathData()` canvas util to simplify rendering SVG path data onto canvas context with CSS variable and class support
5 changes: 5 additions & 0 deletions .changeset/dry-singers-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

feat: Add new `renderRect()` canvas util to simplify rendering rectangles onto canvas context with CSS variable and class support
5 changes: 5 additions & 0 deletions .changeset/early-keys-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

breaking(LinearGradient|RadialGradient): Rename `url` slot prop to `gradient`. Improves name, especially within canvas context
5 changes: 5 additions & 0 deletions .changeset/gentle-months-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix: Reduce likihood of clipping for Canvas-rendered simplified charts by increasing default padding (and add top))
5 changes: 5 additions & 0 deletions .changeset/ninety-numbers-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(Canvas): Support multiple children (fix infinite loops, coordinating redraws, etc). Resolves issue #158
5 changes: 5 additions & 0 deletions .changeset/orange-parrots-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

feat: Add new `renderText()` canvas util to simplify rendering text onto canvas context with CSS variable and class support
5 changes: 5 additions & 0 deletions .changeset/popular-stingrays-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

feat: Improve Canvas implementation with registering render functions and common invalidation to synchronize redrawing
5 changes: 5 additions & 0 deletions .changeset/pretty-bears-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

feat: Add `scaleCanvas` util
5 changes: 5 additions & 0 deletions .changeset/shaggy-rocks-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

Add `clearCanvasContext()` util
5 changes: 5 additions & 0 deletions .changeset/warm-cars-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

feat: Add `ComputedStyles` component to easily resolve classes / CSS variable values (useful when working with <canvas>)
5 changes: 5 additions & 0 deletions .changeset/wicked-mirrors-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

breaking(GeoPath): Simplify render prop use case by leveraging renderPathData() (ex. HitCanvas)
2 changes: 1 addition & 1 deletion packages/layerchart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"type": "module",
"dependencies": {
"@dagrejs/dagre": "^1.1.4",
"@layerstack/svelte-actions": "^0.0.9",
"@layerstack/svelte-actions": "^0.0.11",
"@layerstack/svelte-stores": "^0.0.9",
"@layerstack/tailwind": "^0.0.11",
"@layerstack/utils": "^0.0.7",
Expand Down
110 changes: 83 additions & 27 deletions packages/layerchart/src/lib/components/Arc.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// https://svelte.dev/repl/09711e43a1264ba18945d7db7cab9335?version=3.38.2
// https://codepen.io/simeydotme/pen/rrOEmO/

import { tick } from 'svelte';
import { onDestroy, tick } from 'svelte';
import type { SVGAttributes } from 'svelte/elements';
import type { spring as springStore, tweened as tweenedStore } from 'svelte/motion';
import { arc as d3arc } from 'd3-shape';
Expand All @@ -28,6 +28,8 @@
import { motionStore } from '$lib/stores/motionStore.js';
import { degreesToRadians } from '$lib/utils/math.js';
import type { TooltipContextValue } from './tooltip/TooltipContext.svelte';
import { getCanvasContext } from './layout/Canvas.svelte';
import { renderPathData } from '../utils/canvas.js';

export let spring: boolean | Parameters<typeof springStore>[1] = undefined;
export let tweened: boolean | Parameters<typeof tweenedStore>[1] = undefined;
Expand Down Expand Up @@ -80,6 +82,11 @@
export let padAngle = 0;
// export let padRadius = 0;

export let fill: string | undefined = undefined;
export let fillOpacity: number | undefined = undefined;
export let stroke: string | undefined = undefined;
export let strokeWidth: number | undefined = undefined;

export let track: boolean | SVGAttributes<SVGPathElement> = false;

const { yRange } = chartContext();
Expand Down Expand Up @@ -183,36 +190,85 @@
* Data to set when showing tooltip
*/
export let data: any = undefined;

const canvasContext = getCanvasContext();
const renderContext = canvasContext ? 'canvas' : 'svg';

function render(ctx: CanvasRenderingContext2D) {
ctx.translate(xOffset, yOffset);

// Track
const trackProps = { ...(typeof track === 'object' ? track : null) };
renderPathData(ctx, trackArc(), {
styles: {
fill: trackProps['fill'] ?? undefined,
fillOpacity: trackProps['fill-opacity'] ?? undefined,
stroke: trackProps['stroke'] ?? undefined,
strokeWidth: trackProps['stroke-width'] ?? undefined,
opacity: trackProps['opacity'] ?? undefined,
},
classes: trackProps.class ?? undefined,
});

// Arc
renderPathData(ctx, arc(), {
styles: { fill, fillOpacity, stroke, strokeWidth },
classes: $$props.class,
});
}

let canvasUnregister: ReturnType<typeof canvasContext.register>;
$: if (renderContext === 'canvas') {
canvasUnregister = canvasContext.register({ name: 'Arc', render });
}

$: if (renderContext === 'canvas') {
// Redraw when props changes (TODO: styles, class, etc)
arc && trackArc;
canvasContext.invalidate();
}

onDestroy(() => {
if (renderContext === 'canvas') {
canvasUnregister();
}
});
</script>

{#if track}
{#if renderContext === 'svg'}
{#if track}
<path
d={trackArc()}
class="track"
bind:this={trackArcEl}
{...typeof track === 'object' ? track : null}
/>
{/if}

<!-- svelte-ignore a11y-no-static-element-interactions -->
<path
d={trackArc()}
class="track"
bind:this={trackArcEl}
{...typeof track === 'object' ? track : null}
d={arc()}
transform="translate({xOffset}, {yOffset})"
{fill}
fill-opacity={fillOpacity}
{stroke}
stroke-width={strokeWidth}
{...$$restProps}
on:pointerenter={(e) => tooltip?.show(e, data)}
on:pointermove={(e) => tooltip?.show(e, data)}
on:pointerleave={(e) => tooltip?.hide()}
on:touchmove={(e) => {
if (tooltip) {
// Prevent touch to not interfer with pointer when using tooltip
e.preventDefault();
}
}}
on:click
on:pointerenter
on:pointermove
on:pointerleave
on:touchmove
/>
{/if}

<!-- svelte-ignore a11y-no-static-element-interactions -->
<path
d={arc()}
transform="translate({xOffset}, {yOffset})"
{...$$restProps}
on:pointerenter={(e) => tooltip?.show(e, data)}
on:pointermove={(e) => tooltip?.show(e, data)}
on:pointerleave={(e) => tooltip?.hide()}
on:touchmove={(e) => {
if (tooltip) {
// Prevent touch to not interfer with pointer when using tooltip
e.preventDefault();
}
}}
on:click
on:pointerenter
on:pointermove
on:pointerleave
on:touchmove
/>

<slot value={$tweened_value} centroid={trackArcCentroid} {boundingBox} />
63 changes: 51 additions & 12 deletions packages/layerchart/src/lib/components/Area.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { type ComponentProps } from 'svelte';
import { onDestroy, type ComponentProps } from 'svelte';
import type { tweened as tweenedStore } from 'svelte/motion';
import { type Area, area as d3Area, areaRadial } from 'd3-shape';
import type { CurveFactory } from 'd3-shape';
Expand All @@ -14,6 +14,8 @@
import Spline from './Spline.svelte';
import { accessor, type Accessor } from '../utils/common.js';
import { isScaleBand } from '../utils/scales.js';
import { renderPathData } from '../utils/canvas.js';
import { getCanvasContext } from './layout/Canvas.svelte';

const {
data: contextData,
Expand All @@ -23,8 +25,8 @@
y,
yDomain,
yRange,
config,
radial,
config,
} = chartContext();

/** Override data instead of using context */
Expand Down Expand Up @@ -52,6 +54,11 @@
/** Enable showing line */
export let line: boolean | Partial<ComponentProps<Spline>> = false;

export let fill: string | undefined = undefined;
export let fillOpacity: number | undefined = undefined;
export let stroke: string | undefined = undefined;
export let strokeWidth: number | undefined = undefined;

const xAccessor = x ? accessor(x) : $contextX;
const y0Accessor = y0 ? accessor(y0) : (d: any) => min($yDomain);
const y1Accessor = y1 ? accessor(y1) : $y;
Expand Down Expand Up @@ -125,8 +132,34 @@
const d = pathData ?? path(data ?? $contextData);
tweened_d.set(d ?? '');
}

const canvasContext = getCanvasContext();
const renderContext = canvasContext ? 'canvas' : 'svg';

function render(ctx: CanvasRenderingContext2D) {
renderPathData(ctx, $tweened_d, {
styles: { fill, fillOpacity, stroke, strokeWidth },
classes: $$props.class,
});
}

let canvasUnregister: ReturnType<typeof canvasContext.register>;
$: if (renderContext === 'canvas') {
canvasUnregister = canvasContext.register({ name: 'Area', render });

tweened_d.subscribe(() => {
canvasContext.invalidate();
});
}

onDestroy(() => {
if (renderContext === 'canvas') {
canvasUnregister();
}
});
</script>

<!-- TODO: Find way to not clear <Canvas> when rendering Spline (remove Area rendering). Idea: https://github.com/techniq/layerchart/issues/158#issuecomment-2543416108 -->
{#if line}
<Spline
{data}
Expand All @@ -139,13 +172,19 @@
/>
{/if}

<!-- svelte-ignore a11y-no-static-element-interactions -->
<path
d={$tweened_d}
clip-path={clipPath}
{...$$restProps}
class={cls('path-area', $$props.class)}
on:click
on:pointermove
on:pointerleave
/>
{#if renderContext === 'svg'}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<path
d={$tweened_d}
clip-path={clipPath}
{fill}
fill-opacity={fillOpacity}
{stroke}
stroke-width={strokeWidth}
{...$$restProps}
class={cls('path-area', $$props.class)}
on:click
on:pointermove
on:pointerleave
/>
{/if}
6 changes: 5 additions & 1 deletion packages/layerchart/src/lib/components/Bar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { isScaleBand } from '../utils/scales.js';
import { accessor, type Accessor } from '../utils/common.js';
import { greatestAbs } from '@layerstack/utils';
import { getCanvasContext } from './layout/Canvas.svelte';

const { x: xContext, y: yContext, xScale } = chartContext();

Expand Down Expand Up @@ -106,9 +107,12 @@
z`
.split('\n')
.join('');

const canvasContext = getCanvasContext();
const renderContext = canvasContext ? 'canvas' : 'svg';
</script>

{#if _rounded === 'all' || radius === 0}
{#if (_rounded === 'all' || radius === 0) && renderContext === 'svg'}
<Rect
{fill}
{spring}
Expand Down
Loading
Loading