svelteplot 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@
9
9
  tension?: number;
10
10
  sort?: ConstantAccessor<RawValue> | { channel: 'stroke' | 'fill' };
11
11
  stack?: Partial<StackOptions>;
12
+ canvas?: boolean;
12
13
  };
13
14
  </script>
14
15
 
@@ -22,6 +23,7 @@
22
23
  import callWithProps from '../helpers/callWithProps.js';
23
24
  import { maybeCurve } from '../helpers/curves.js';
24
25
  import { isValid } from '../helpers/index.js';
26
+ import AreaCanvas from './helpers/AreaCanvas.svelte';
25
27
 
26
28
  import type {
27
29
  CurveName,
@@ -52,7 +54,8 @@
52
54
  /** the curve */
53
55
  curve = 'linear',
54
56
  tension = 0,
55
- class: className = null,
57
+ class: className = '',
58
+ canvas = false,
56
59
  ...options
57
60
  }: AreaProps = $props();
58
61
 
@@ -61,7 +64,7 @@
61
64
 
62
65
  const groupByKey = $derived(options.z || options.fill || options.stroke);
63
66
 
64
- const areaPath: (d: ScaledDataRecord[]) => string = $derived(
67
+ const areaPath = $derived(
65
68
  callWithProps(area, [], {
66
69
  curve: maybeCurve(curve, tension),
67
70
  defined: (d: ScaledDataRecord) =>
@@ -113,34 +116,38 @@
113
116
  {...options}>
114
117
  {#snippet children({ mark, usedScales, scaledData })}
115
118
  {@const grouped = groupAndSort(scaledData)}
116
- <GroupMultiple length={grouped.length}>
117
- {#each grouped as areaData}
118
- {#snippet el(datum: ScaledDataRecord)}
119
- {@const [style, styleClass] = resolveStyles(
120
- plot,
121
- datum,
122
- options,
123
- 'fill',
124
- usedScales
125
- )}
126
- <path
127
- class={['svelteplot-area', className, styleClass]}
128
- clip-path={options.clipPath}
129
- d={areaPath(areaData)}
130
- {style} />
131
- {/snippet}
132
- {#if areaData.length > 0}
133
- {#if options.href}
134
- <a
135
- href={resolveProp(options.href, areaData[0].datum, '')}
136
- target={resolveProp(options.target, areaData[0].datum, '_self')}>
119
+ {#if canvas}
120
+ <AreaCanvas groupedAreaData={grouped} {mark} {usedScales} {areaPath} />
121
+ {:else}
122
+ <GroupMultiple length={grouped.length}>
123
+ {#each grouped as areaData}
124
+ {#snippet el(datum: ScaledDataRecord)}
125
+ {@const [style, styleClass] = resolveStyles(
126
+ plot,
127
+ datum,
128
+ options,
129
+ 'fill',
130
+ usedScales
131
+ )}
132
+ <path
133
+ class={['svelteplot-area', className, styleClass]}
134
+ clip-path={options.clipPath}
135
+ d={areaPath(areaData)}
136
+ {style} />
137
+ {/snippet}
138
+ {#if areaData.length > 0}
139
+ {#if options.href}
140
+ <a
141
+ href={resolveProp(options.href, areaData[0].datum, '')}
142
+ target={resolveProp(options.target, areaData[0].datum, '_self')}>
143
+ {@render el(areaData[0])}
144
+ </a>
145
+ {:else}
137
146
  {@render el(areaData[0])}
138
- </a>
139
- {:else}
140
- {@render el(areaData[0])}
147
+ {/if}
141
148
  {/if}
142
- {/if}
143
- {/each}
144
- </GroupMultiple>
149
+ {/each}
150
+ </GroupMultiple>
151
+ {/if}
145
152
  {/snippet}
146
153
  </Mark>
@@ -10,6 +10,7 @@ export type AreaMarkProps = {
10
10
  channel: 'stroke' | 'fill';
11
11
  };
12
12
  stack?: Partial<StackOptions>;
13
+ canvas?: boolean;
13
14
  };
14
15
  import { type CurveFactory } from 'd3-shape';
15
16
  import type { CurveName, DataRecord, BaseMarkProps, ConstantAccessor, ChannelAccessor } from '../types.js';
@@ -0,0 +1,107 @@
1
+ <script lang="ts">
2
+ import type {
3
+ Mark,
4
+ BaseMarkProps,
5
+ PlotContext,
6
+ ScaledDataRecord,
7
+ UsedScales
8
+ } from '../../types.js';
9
+ import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
10
+ import { getContext } from 'svelte';
11
+ import { type Area } from 'd3-shape';
12
+ import CanvasLayer from './CanvasLayer.svelte';
13
+ import type { Attachment } from 'svelte/attachments';
14
+ import { devicePixelRatio } from 'svelte/reactivity/window';
15
+ import { resolveColor } from './canvas';
16
+
17
+ let {
18
+ mark,
19
+ groupedAreaData,
20
+ usedScales,
21
+ areaPath
22
+ }: {
23
+ mark: Mark<BaseMarkProps>;
24
+ groupedAreaData: ScaledDataRecord[][];
25
+ usedScales: UsedScales;
26
+ areaPath: Area<ScaledDataRecord>;
27
+ } = $props();
28
+
29
+ const { getPlotState } = getContext<PlotContext>('svelteplot');
30
+ const plot = $derived(getPlotState());
31
+
32
+ function maybeOpacity(value: unknown) {
33
+ return value == null ? 1 : +value;
34
+ }
35
+
36
+ const render = ((canvas: HTMLCanvasElement) => {
37
+ const context = canvas.getContext('2d');
38
+
39
+ $effect(() => {
40
+ if (context) {
41
+ areaPath.context(context);
42
+ context.resetTransform();
43
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
44
+
45
+ for (const group of groupedAreaData) {
46
+ if (group.length < 2) continue;
47
+
48
+ // Get the first point to determine area styles
49
+ const firstPoint = group[0];
50
+ if (!firstPoint || !firstPoint.valid) continue;
51
+
52
+ let { fill, stroke, ...restStyles } = resolveScaledStyleProps(
53
+ firstPoint.datum,
54
+ mark.options,
55
+ usedScales,
56
+ plot,
57
+ 'fill'
58
+ );
59
+
60
+ const opacity = maybeOpacity(restStyles['opacity']);
61
+ const fillOpacity = maybeOpacity(restStyles['fill-opacity']);
62
+ const strokeOpacity = maybeOpacity(restStyles['stroke-opacity']);
63
+
64
+ const strokeWidth = resolveProp(
65
+ mark.options.strokeWidth,
66
+ firstPoint.datum,
67
+ 0
68
+ ) as number;
69
+
70
+ fill = resolveColor(fill || 'currentColor', canvas);
71
+ stroke = resolveColor(stroke, canvas);
72
+
73
+ // Start drawing the area
74
+ context.beginPath();
75
+ areaPath(group);
76
+
77
+ // Fill the area
78
+ if (fill && fill !== 'none') {
79
+ context.fillStyle = fill;
80
+ context.globalAlpha = opacity * fillOpacity;
81
+ context.fill();
82
+ }
83
+
84
+ // Stroke the area outline if strokeWidth > 0
85
+ if (stroke && stroke !== 'none' && strokeWidth > 0) {
86
+ context.strokeStyle = stroke;
87
+ context.lineWidth = strokeWidth;
88
+ context.globalAlpha = opacity * strokeOpacity;
89
+ context.stroke();
90
+ }
91
+ }
92
+ areaPath.context(null);
93
+ }
94
+
95
+ return () => {
96
+ context?.clearRect(
97
+ 0,
98
+ 0,
99
+ plot.width * (devicePixelRatio.current ?? 1),
100
+ plot.height * (devicePixelRatio.current ?? 1)
101
+ );
102
+ };
103
+ });
104
+ }) as Attachment;
105
+ </script>
106
+
107
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,11 @@
1
+ import type { Mark, BaseMarkProps, ScaledDataRecord, UsedScales } from '../../types.js';
2
+ import { type Area } from 'd3-shape';
3
+ type $$ComponentProps = {
4
+ mark: Mark<BaseMarkProps>;
5
+ groupedAreaData: ScaledDataRecord[][];
6
+ usedScales: UsedScales;
7
+ areaPath: Area<ScaledDataRecord>;
8
+ };
9
+ declare const AreaCanvas: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type AreaCanvas = ReturnType<typeof AreaCanvas>;
11
+ export default AreaCanvas;
@@ -1,4 +1,4 @@
1
- import { CSS_URL, CSS_VAR } from "../../constants";
1
+ import { CSS_URL, CSS_VAR } from '../../constants';
2
2
  export function resolveColor(color, canvas) {
3
3
  if (`${color}`.toLowerCase() === 'currentcolor') {
4
4
  color = getComputedStyle(canvas?.parentElement?.parentElement).getPropertyValue('color');
@@ -18,13 +18,11 @@ export function resolveColor(color, canvas) {
18
18
  const x1 = +gradient.getAttribute('x2');
19
19
  const y0 = +gradient.getAttribute('y1');
20
20
  const y1 = +gradient.getAttribute('y2');
21
- const ctxGradient = canvas
22
- .getContext('2d')
23
- .createLinearGradient(x0, y0, x1, y1);
21
+ const ctxGradient = canvas.getContext('2d').createLinearGradient(x0, y0, x1, y1);
24
22
  for (const stop of gradient.querySelectorAll('stop')) {
25
23
  const offset = +stop.getAttribute('offset');
26
24
  const color = resolveColor(stop.getAttribute('stop-color'), canvas);
27
- ctxGradient.addColorStop(offset, color);
25
+ ctxGradient.addColorStop(Math.min(1, Math.max(0, offset)), color);
28
26
  }
29
27
  return ctxGradient;
30
28
  }
package/package.json CHANGED
@@ -1,121 +1,120 @@
1
1
  {
2
- "name": "svelteplot",
3
- "version": "0.2.2",
4
- "license": "ISC",
5
- "author": {
6
- "name": "Gregor Aisch",
7
- "email": "gka@users.noreply.github.com"
2
+ "name": "svelteplot",
3
+ "version": "0.2.4",
4
+ "license": "ISC",
5
+ "author": {
6
+ "name": "Gregor Aisch",
7
+ "email": "gka@users.noreply.github.com"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "svelte": "./dist/index.js"
8
13
  },
9
- "scripts": {
10
- "dev": "vite dev",
11
- "build": "vite build",
12
- "preview": "vite preview",
13
- "test": "npm run test:unit",
14
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
15
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
16
- "lint": "prettier --check src && eslint src",
17
- "format": "prettier --write .",
18
- "test:unit": "vitest",
19
- "prepack": "npx svelte-package",
20
- "release-next": "npm version prerelease --preid next && npm publish && git push && git push --tags && sleep 1 && npm dist-tag add svelteplot@$(npm view . version) next",
21
- "docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/"
14
+ "./*.js": {
15
+ "import": "./dist/*.js"
22
16
  },
23
- "exports": {
24
- ".": {
25
- "types": "./dist/index.d.ts",
26
- "svelte": "./dist/index.js"
27
- },
28
- "./*.js": {
29
- "import": "./dist/*.js"
30
- },
31
- "./*.svelte": {
32
- "import": "./dist/*.svelte"
33
- },
34
- "./core/*.svelte": {
35
- "import": "./dist/core/*.svelte"
36
- },
37
- "./marks/*.svelte": {
38
- "import": "./dist/marks/*.svelte"
39
- }
17
+ "./*.svelte": {
18
+ "import": "./dist/*.svelte"
40
19
  },
41
- "main": "./dist/index.js",
42
- "files": [
43
- "dist",
44
- "!dist/**/*.test.*",
45
- "!dist/**/*.spec.*"
46
- ],
47
- "devDependencies": {
48
- "@aitodotai/json-stringify-pretty-compact": "^1.3.0",
49
- "@emotion/css": "^11.13.5",
50
- "@sveltejs/adapter-auto": "^6.0.1",
51
- "@sveltejs/adapter-static": "^3.0.8",
52
- "@sveltejs/eslint-config": "^8.2.0",
53
- "@sveltejs/kit": "^2.21.0",
54
- "@sveltejs/package": "^2.3.11",
55
- "@sveltejs/vite-plugin-svelte": "5.0.3",
56
- "@sveltepress/theme-default": "^6.0.3",
57
- "@sveltepress/twoslash": "^1.2.2",
58
- "@sveltepress/vite": "^1.2.2",
59
- "@testing-library/svelte": "^5.2.7",
60
- "@testing-library/user-event": "^14.6.1",
61
- "@types/d3-array": "^3.2.1",
62
- "@types/d3-color": "^3.1.3",
63
- "@types/d3-dsv": "^3.0.7",
64
- "@types/d3-geo": "^3.1.0",
65
- "@types/d3-interpolate": "^3.0.4",
66
- "@types/d3-path": "^3.1.1",
67
- "@types/d3-random": "^3.0.3",
68
- "@types/d3-scale": "^4.0.9",
69
- "@types/d3-scale-chromatic": "^3.1.0",
70
- "@types/d3-shape": "^3.1.7",
71
- "@typescript-eslint/eslint-plugin": "^8.32.1",
72
- "@typescript-eslint/parser": "^8.32.1",
73
- "csstype": "^3.1.3",
74
- "d3-dsv": "^3.0.1",
75
- "d3-fetch": "^3.0.1",
76
- "d3-force": "^3.0.0",
77
- "eslint": "^9.26.0",
78
- "eslint-config-prettier": "^10.1.5",
79
- "eslint-plugin-svelte": "3.7.0",
80
- "jsdom": "^26.1.0",
81
- "prettier": "^3.5.3",
82
- "prettier-plugin-svelte": "^3.4.0",
83
- "remark-code-extra": "^1.0.1",
84
- "remark-code-frontmatter": "^1.0.0",
85
- "resize-observer-polyfill": "^1.5.1",
86
- "sass": "^1.89.0",
87
- "svelte-check": "^4.2.1",
88
- "svelte-eslint-parser": "1.2.0",
89
- "svelte-highlight": "^7.8.3",
90
- "svg-path-parser": "^1.1.0",
91
- "topojson-client": "^3.1.0",
92
- "tslib": "^2.8.1",
93
- "typedoc": "^0.28.4",
94
- "typedoc-plugin-markdown": "^4.6.3",
95
- "typescript": "^5.8.3",
96
- "vite": "^6.3.5",
97
- "vitest": "^3.1.3",
98
- "vitest-matchmedia-mock": "^2.0.3"
20
+ "./core/*.svelte": {
21
+ "import": "./dist/core/*.svelte"
99
22
  },
100
- "types": "./dist/index.d.ts",
101
- "type": "module",
102
- "dependencies": {
103
- "d3-array": "^3.2.4",
104
- "d3-color": "^3.1.0",
105
- "d3-format": "^3.1.0",
106
- "d3-geo": "^3.1.1",
107
- "d3-interpolate": "^3.0.1",
108
- "d3-path": "^3.1.0",
109
- "d3-quadtree": "^3.0.1",
110
- "d3-random": "^3.0.1",
111
- "d3-regression": "^1.3.10",
112
- "d3-scale": "^4.0.2",
113
- "d3-scale-chromatic": "^3.1.0",
114
- "d3-shape": "^3.2.0",
115
- "d3-time": "^3.1.0",
116
- "es-toolkit": "^1.37.2",
117
- "fast-equals": "^5.2.2",
118
- "merge-deep": "^3.0.3",
119
- "svelte": "5.30.1"
23
+ "./marks/*.svelte": {
24
+ "import": "./dist/marks/*.svelte"
120
25
  }
121
- }
26
+ },
27
+ "main": "./dist/index.js",
28
+ "files": [
29
+ "dist",
30
+ "!dist/**/*.test.*",
31
+ "!dist/**/*.spec.*"
32
+ ],
33
+ "devDependencies": {
34
+ "@aitodotai/json-stringify-pretty-compact": "^1.3.0",
35
+ "@emotion/css": "^11.13.5",
36
+ "@sveltejs/adapter-auto": "^6.0.1",
37
+ "@sveltejs/adapter-static": "^3.0.8",
38
+ "@sveltejs/eslint-config": "^8.2.0",
39
+ "@sveltejs/kit": "^2.21.0",
40
+ "@sveltejs/package": "^2.3.11",
41
+ "@sveltejs/vite-plugin-svelte": "5.0.3",
42
+ "@sveltepress/theme-default": "^6.0.3",
43
+ "@sveltepress/twoslash": "^1.2.2",
44
+ "@sveltepress/vite": "^1.2.2",
45
+ "@testing-library/svelte": "^5.2.7",
46
+ "@testing-library/user-event": "^14.6.1",
47
+ "@types/d3-array": "^3.2.1",
48
+ "@types/d3-color": "^3.1.3",
49
+ "@types/d3-dsv": "^3.0.7",
50
+ "@types/d3-geo": "^3.1.0",
51
+ "@types/d3-interpolate": "^3.0.4",
52
+ "@types/d3-path": "^3.1.1",
53
+ "@types/d3-random": "^3.0.3",
54
+ "@types/d3-scale": "^4.0.9",
55
+ "@types/d3-scale-chromatic": "^3.1.0",
56
+ "@types/d3-shape": "^3.1.7",
57
+ "@typescript-eslint/eslint-plugin": "^8.32.1",
58
+ "@typescript-eslint/parser": "^8.32.1",
59
+ "csstype": "^3.1.3",
60
+ "d3-dsv": "^3.0.1",
61
+ "d3-fetch": "^3.0.1",
62
+ "d3-force": "^3.0.0",
63
+ "eslint": "^9.26.0",
64
+ "eslint-config-prettier": "^10.1.5",
65
+ "eslint-plugin-svelte": "3.7.0",
66
+ "jsdom": "^26.1.0",
67
+ "prettier": "^3.5.3",
68
+ "prettier-plugin-svelte": "^3.4.0",
69
+ "remark-code-extra": "^1.0.1",
70
+ "remark-code-frontmatter": "^1.0.0",
71
+ "resize-observer-polyfill": "^1.5.1",
72
+ "sass": "^1.89.0",
73
+ "svelte-check": "^4.2.1",
74
+ "svelte-eslint-parser": "1.2.0",
75
+ "svelte-highlight": "^7.8.3",
76
+ "svg-path-parser": "^1.1.0",
77
+ "topojson-client": "^3.1.0",
78
+ "tslib": "^2.8.1",
79
+ "typedoc": "^0.28.4",
80
+ "typedoc-plugin-markdown": "^4.6.3",
81
+ "typescript": "^5.8.3",
82
+ "vite": "^6.3.5",
83
+ "vitest": "^3.1.3",
84
+ "vitest-matchmedia-mock": "^2.0.3"
85
+ },
86
+ "types": "./dist/index.d.ts",
87
+ "type": "module",
88
+ "dependencies": {
89
+ "d3-array": "^3.2.4",
90
+ "d3-color": "^3.1.0",
91
+ "d3-format": "^3.1.0",
92
+ "d3-geo": "^3.1.1",
93
+ "d3-interpolate": "^3.0.1",
94
+ "d3-path": "^3.1.0",
95
+ "d3-quadtree": "^3.0.1",
96
+ "d3-random": "^3.0.1",
97
+ "d3-regression": "^1.3.10",
98
+ "d3-scale": "^4.0.2",
99
+ "d3-scale-chromatic": "^3.1.0",
100
+ "d3-shape": "^3.2.0",
101
+ "d3-time": "^3.1.0",
102
+ "es-toolkit": "^1.37.2",
103
+ "fast-equals": "^5.2.2",
104
+ "merge-deep": "^3.0.3",
105
+ "svelte": "5.30.1"
106
+ },
107
+ "scripts": {
108
+ "dev": "vite dev",
109
+ "build": "vite build",
110
+ "preview": "vite preview",
111
+ "test": "npm run test:unit",
112
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
113
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
114
+ "lint": "prettier --check src && eslint src",
115
+ "format": "prettier --write .",
116
+ "test:unit": "vitest",
117
+ "release-next": "npm version prerelease --preid next && npm publish && git push && git push --tags && sleep 1 && npm dist-tag add svelteplot@$(npm view . version) next",
118
+ "docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/"
119
+ }
120
+ }