svelteplot 0.3.3 → 0.3.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.
@@ -5,3 +5,5 @@ export declare function getBaseStylesObject(datum: DataRow, props: Partial<Chann
5
5
  };
6
6
  export default function (datum: DataRow, props: Partial<Channels>): string;
7
7
  export declare function maybeToPixel(cssKey: string, value: string | number): string | number;
8
+ export declare function maybeFromPixel(value: string | number): string | number;
9
+ export declare function maybeFromRem(value: string | number, rootFontSize?: number): string | number;
@@ -41,3 +41,11 @@ export function maybeToPixel(cssKey, value) {
41
41
  }
42
42
  return value;
43
43
  }
44
+ export function maybeFromPixel(value) {
45
+ return typeof value === 'string' && value.endsWith('px') ? +value.slice(0, -2) : value;
46
+ }
47
+ export function maybeFromRem(value, rootFontSize = 16) {
48
+ return typeof value === 'string' && value.endsWith('rem')
49
+ ? +value.slice(0, -3) * rootFontSize
50
+ : value;
51
+ }
@@ -18,6 +18,11 @@
18
18
  * the line anchor for vertical position; top, bottom, or middle
19
19
  */
20
20
  lineAnchor?: ConstantAccessor<'bottom' | 'top' | 'middle'>;
21
+ /**
22
+ * line height as multiplier of font size
23
+ * @default 1.2
24
+ */
25
+ lineHeight?: ConstantAccessor<number>;
21
26
  frameAnchor?: ConstantAccessor<
22
27
  | 'bottom'
23
28
  | 'top'
@@ -35,7 +40,6 @@
35
40
  import { getContext, type Snippet } from 'svelte';
36
41
  import GroupMultiple from './helpers/GroupMultiple.svelte';
37
42
  import type {
38
- PlotContext,
39
43
  DataRecord,
40
44
  BaseMarkProps,
41
45
  ConstantAccessor,
@@ -46,11 +50,14 @@
46
50
  import Mark from '../Mark.svelte';
47
51
  import { sort } from '../index.js';
48
52
 
53
+ import MultilineText from './helpers/MultilineText.svelte';
54
+
49
55
  const DEFAULTS = {
50
56
  fontSize: 12,
51
57
  fontWeight: 500,
52
58
  strokeWidth: 1.6,
53
59
  frameAnchor: 'center',
60
+ lineHeight: 1.1,
54
61
  ...getContext<PlotDefaults>('svelteplot/_defaults').text
55
62
  };
56
63
 
@@ -65,21 +72,12 @@
65
72
  ...markProps
66
73
  });
67
74
 
68
- const { getPlotState } = getContext<PlotContext>('svelteplot');
69
- let plot = $derived(getPlotState());
70
-
71
- const LINE_ANCHOR = {
72
- bottom: 'auto',
73
- middle: 'central',
74
- top: 'hanging'
75
- } as const;
76
-
77
75
  const args = $derived(
78
76
  sort({
79
77
  data,
80
78
  ...options
81
79
  })
82
- );
80
+ ) as TextMarkProps;
83
81
  </script>
84
82
 
85
83
  <Mark
@@ -101,99 +99,9 @@
101
99
  <GroupMultiple class="text {className}" length={className ? 2 : args.data.length}>
102
100
  {#each scaledData as d, i (i)}
103
101
  {#if d.valid}
104
- {@const title = resolveProp(args.title, d.datum, '')}
105
- {@const frameAnchor = resolveProp(args.frameAnchor, d.datum)}
106
- {@const isLeft =
107
- frameAnchor === 'left' ||
108
- frameAnchor === 'top-left' ||
109
- frameAnchor === 'bottom-left'}
110
- {@const isRight =
111
- frameAnchor === 'right' ||
112
- frameAnchor === 'top-right' ||
113
- frameAnchor === 'bottom-right'}
114
- {@const isTop =
115
- frameAnchor === 'top' ||
116
- frameAnchor === 'top-left' ||
117
- frameAnchor === 'top-right'}
118
- {@const isBottom =
119
- frameAnchor === 'bottom' ||
120
- frameAnchor === 'bottom-left' ||
121
- frameAnchor === 'bottom-right'}
122
- {@const [x, y] =
123
- args.x != null && args.y != null
124
- ? [d.x, d.y]
125
- : [
126
- args.x != null
127
- ? d.x
128
- : isLeft
129
- ? plot.options.marginLeft
130
- : isRight
131
- ? plot.options.marginLeft + plot.facetWidth
132
- : plot.options.marginLeft + plot.facetWidth * 0.5,
133
- args.y != null
134
- ? d.y
135
- : isTop
136
- ? plot.options.marginTop
137
- : isBottom
138
- ? plot.options.marginTop + plot.facetHeight
139
- : plot.options.marginTop + plot.facetHeight * 0.5
140
- ]}
141
-
142
- {@const dx = +resolveProp(args.dx, d.datum, 0)}
143
- {@const dy = +resolveProp(args.dy, d.datum, 0)}
144
102
  {@const textLines = String(resolveProp(args.text, d.datum, '')).split('\n')}
145
- {@const lineAnchor = resolveProp(
146
- args.lineAnchor,
147
- d.datum,
148
- args.y != null ? 'middle' : isTop ? 'top' : isBottom ? 'bottom' : 'middle'
149
- )}
150
- {@const textClassName = resolveProp(args.textClass, d.datum, null)}
151
-
152
- {@const [style, styleClass] = resolveStyles(
153
- plot,
154
- { ...d, __tspanIndex: 0 },
155
- {
156
- fontSize: 12,
157
- fontWeight: 500,
158
- strokeWidth: 1.6,
159
- textAnchor: isLeft ? 'start' : isRight ? 'end' : 'middle',
160
- ...args
161
- },
162
- 'fill',
163
- usedScales
164
- )}
165
103
 
166
- {#if textLines.length > 1}
167
- <!-- multiline text-->
168
- {@const fontSize = resolveProp(args.fontSize, d.datum) || 12}
169
- <text
170
- class={[textClassName]}
171
- dominant-baseline={LINE_ANCHOR[lineAnchor]}
172
- transform="translate({Math.round(x + dx)},{Math.round(
173
- y +
174
- dy -
175
- (lineAnchor === 'bottom'
176
- ? textLines.length - 1
177
- : lineAnchor === 'middle'
178
- ? (textLines.length - 1) * 0.5
179
- : 0) *
180
- fontSize
181
- )})"
182
- >{#each textLines as line, l (l)}<tspan
183
- x="0"
184
- dy={l ? fontSize : 0}
185
- class={styleClass}
186
- {style}>{line}</tspan
187
- >{/each}{#if title}<title>{title}</title>{/if}</text>
188
- {:else}
189
- <!-- singleline text-->
190
- <text
191
- class={[textClassName, styleClass]}
192
- dominant-baseline={LINE_ANCHOR[lineAnchor]}
193
- transform="translate({Math.round(x + dx)},{Math.round(y + dy)})"
194
- {style}
195
- >{textLines[0]}{#if title}<title>{title}</title>{/if}</text>
196
- {/if}
104
+ <MultilineText {textLines} {d} {args} {usedScales} />
197
105
  {/if}
198
106
  {/each}
199
107
  </GroupMultiple>
@@ -13,6 +13,11 @@ export type TextMarkProps = BaseMarkProps & {
13
13
  * the line anchor for vertical position; top, bottom, or middle
14
14
  */
15
15
  lineAnchor?: ConstantAccessor<'bottom' | 'top' | 'middle'>;
16
+ /**
17
+ * line height as multiplier of font size
18
+ * @default 1.2
19
+ */
20
+ lineHeight?: ConstantAccessor<number>;
16
21
  frameAnchor?: ConstantAccessor<'bottom' | 'top' | 'left' | 'right' | 'top-left' | 'bottom-left' | 'top-right' | 'bottom-right'>;
17
22
  };
18
23
  import { type Snippet } from 'svelte';
@@ -0,0 +1,158 @@
1
+ <script lang="ts">
2
+ import { resolveProp, resolveStyles } from '../../helpers/resolve';
3
+ import { getContext, type ComponentProps } from 'svelte';
4
+ import type { PlotContext, ScaledDataRecord, UsedScales } from '../../types';
5
+ import type Text from '../Text.svelte';
6
+ import { CSS_VAR } from '../../constants';
7
+ import { maybeFromPixel, maybeFromRem } from '../../helpers/getBaseStyles';
8
+
9
+ const LINE_ANCHOR = {
10
+ bottom: 'auto',
11
+ middle: 'central',
12
+ top: 'hanging'
13
+ } as const;
14
+
15
+ const { getPlotState } = getContext<PlotContext>('svelteplot');
16
+ const plot = $derived(getPlotState());
17
+
18
+ let {
19
+ textLines,
20
+ d,
21
+ args,
22
+ usedScales
23
+ }: {
24
+ textLines: string[];
25
+ d: ScaledDataRecord;
26
+ args: ComponentProps<typeof Text>;
27
+ usedScales: UsedScales;
28
+ } = $props();
29
+
30
+ const title = $derived(resolveProp(args.title, d.datum, ''));
31
+ const frameAnchor = $derived(resolveProp(args.frameAnchor, d.datum));
32
+ const isLeft = $derived(
33
+ frameAnchor === 'left' || frameAnchor === 'top-left' || frameAnchor === 'bottom-left'
34
+ );
35
+ const isRight = $derived(
36
+ frameAnchor === 'right' || frameAnchor === 'top-right' || frameAnchor === 'bottom-right'
37
+ );
38
+ const isTop = $derived(
39
+ frameAnchor === 'top' || frameAnchor === 'top-left' || frameAnchor === 'top-right'
40
+ );
41
+ const isBottom = $derived(
42
+ frameAnchor === 'bottom' || frameAnchor === 'bottom-left' || frameAnchor === 'bottom-right'
43
+ );
44
+ const lineAnchor = $derived(
45
+ resolveProp(
46
+ args.lineAnchor,
47
+ d.datum,
48
+ args.y != null ? 'middle' : isTop ? 'top' : isBottom ? 'bottom' : 'middle'
49
+ )
50
+ );
51
+ const textClassName = $derived(resolveProp(args.textClass, d.datum, null));
52
+ const [x, y] = $derived(
53
+ args.x != null && args.y != null
54
+ ? [d.x, d.y]
55
+ : [
56
+ args.x != null
57
+ ? d.x
58
+ : isLeft
59
+ ? plot.options.marginLeft
60
+ : isRight
61
+ ? plot.options.marginLeft + plot.facetWidth
62
+ : plot.options.marginLeft + plot.facetWidth * 0.5,
63
+ args.y != null
64
+ ? d.y
65
+ : isTop
66
+ ? plot.options.marginTop
67
+ : isBottom
68
+ ? plot.options.marginTop + plot.facetHeight
69
+ : plot.options.marginTop + plot.facetHeight * 0.5
70
+ ]
71
+ );
72
+
73
+ const dx = $derived(+resolveProp(args.dx, d.datum, 0));
74
+ const dy = $derived(+resolveProp(args.dy, d.datum, 0));
75
+
76
+ const [style, styleClass] = $derived(
77
+ resolveStyles(
78
+ plot,
79
+ { ...d, __tspanIndex: 0 },
80
+ {
81
+ fontSize: 12,
82
+ fontWeight: 500,
83
+ strokeWidth: 1.6,
84
+ textAnchor: isLeft ? 'start' : isRight ? 'end' : 'middle',
85
+ ...args
86
+ },
87
+ 'fill',
88
+ usedScales
89
+ )
90
+ );
91
+
92
+ const fontSize = $derived(
93
+ textLines.length > 1 ? (resolveProp(args.fontSize, d.datum) ?? 12) : 0
94
+ );
95
+ let textElement: SVGTextElement | null = $state(null);
96
+
97
+ const rootFontSize = $derived(
98
+ textElement?.ownerDocument?.documentElement && textLines.length > 1
99
+ ? maybeFromPixel(getComputedStyle(textElement.ownerDocument.documentElement).fontSize)
100
+ : 14
101
+ );
102
+
103
+ const computedFontSize = $derived(
104
+ textElement && textLines.length > 1 && CSS_VAR.test(fontSize)
105
+ ? maybeFromRem(
106
+ maybeFromPixel(
107
+ getComputedStyle(textElement).getPropertyValue(
108
+ `--${fontSize.match(CSS_VAR)[1]}`
109
+ )
110
+ ),
111
+ rootFontSize
112
+ )
113
+ : fontSize
114
+ );
115
+
116
+ const lineHeight = $derived(
117
+ textLines.length > 1 ? (resolveProp(args.lineHeight, d.datum) ?? 1.2) : 0
118
+ );
119
+ </script>
120
+
121
+ {#if textLines.length > 1}
122
+ <!-- multiline text-->
123
+ <text
124
+ bind:this={textElement}
125
+ class={[textClassName]}
126
+ dominant-baseline={LINE_ANCHOR[lineAnchor]}
127
+ transform="translate({Math.round(x + dx)},{Math.round(
128
+ y +
129
+ dy -
130
+ (lineAnchor === 'bottom'
131
+ ? textLines.length - 1
132
+ : lineAnchor === 'middle'
133
+ ? (textLines.length - 1) * 0.5
134
+ : 0) *
135
+ computedFontSize *
136
+ lineHeight
137
+ )})"
138
+ >{#each textLines as line, l (l)}<tspan
139
+ x="0"
140
+ dy={l ? computedFontSize * lineHeight : 0}
141
+ class={styleClass}
142
+ {style}>{line}</tspan
143
+ >{/each}{#if title}<title>{title}</title>{/if}</text>
144
+ {:else}
145
+ <!-- singleline text-->
146
+ <text
147
+ class={[textClassName, styleClass]}
148
+ dominant-baseline={LINE_ANCHOR[lineAnchor]}
149
+ transform="translate({Math.round(x + dx)},{Math.round(y + dy)})"
150
+ {style}
151
+ >{textLines[0]}{#if title}<title>{title}</title>{/if}</text>
152
+ {/if}
153
+
154
+ <style>
155
+ text {
156
+ paint-order: stroke fill;
157
+ }
158
+ </style>
@@ -0,0 +1,12 @@
1
+ import { type ComponentProps } from 'svelte';
2
+ import type { ScaledDataRecord, UsedScales } from '../../types';
3
+ import type Text from '../Text.svelte';
4
+ type $$ComponentProps = {
5
+ textLines: string[];
6
+ d: ScaledDataRecord;
7
+ args: ComponentProps<typeof Text>;
8
+ usedScales: UsedScales;
9
+ };
10
+ declare const MultilineText: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type MultilineText = ReturnType<typeof MultilineText>;
12
+ export default MultilineText;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "license": "ISC",
5
5
  "author": {
6
6
  "name": "Gregor Aisch",
@@ -39,7 +39,7 @@
39
39
  "@sveltejs/adapter-auto": "^6.0.1",
40
40
  "@sveltejs/adapter-static": "^3.0.8",
41
41
  "@sveltejs/eslint-config": "^8.2.0",
42
- "@sveltejs/kit": "^2.21.2",
42
+ "@sveltejs/kit": "^2.21.4",
43
43
  "@sveltejs/package": "^2.3.11",
44
44
  "@sveltejs/vite-plugin-svelte": "5.1.0",
45
45
  "@sveltepress/theme-default": "^6.0.3",
@@ -57,15 +57,15 @@
57
57
  "@types/d3-scale": "^4.0.9",
58
58
  "@types/d3-scale-chromatic": "^3.1.0",
59
59
  "@types/d3-shape": "^3.1.7",
60
- "@typescript-eslint/eslint-plugin": "^8.33.1",
61
- "@typescript-eslint/parser": "^8.33.1",
60
+ "@typescript-eslint/eslint-plugin": "^8.34.0",
61
+ "@typescript-eslint/parser": "^8.34.0",
62
62
  "csstype": "^3.1.3",
63
63
  "d3-dsv": "^3.0.1",
64
64
  "d3-fetch": "^3.0.1",
65
65
  "d3-force": "^3.0.0",
66
66
  "eslint": "^9.28.0",
67
67
  "eslint-config-prettier": "^10.1.5",
68
- "eslint-plugin-svelte": "3.9.1",
68
+ "eslint-plugin-svelte": "3.9.2",
69
69
  "jsdom": "^26.1.0",
70
70
  "prettier": "^3.5.3",
71
71
  "prettier-plugin-svelte": "^3.4.0",
@@ -73,19 +73,19 @@
73
73
  "remark-code-extra": "^1.0.1",
74
74
  "remark-code-frontmatter": "^1.0.0",
75
75
  "resize-observer-polyfill": "^1.5.1",
76
- "sass": "^1.89.1",
76
+ "sass": "^1.89.2",
77
77
  "svelte-check": "^4.2.1",
78
78
  "svelte-eslint-parser": "1.2.0",
79
79
  "svelte-highlight": "^7.8.3",
80
80
  "svg-path-parser": "^1.1.0",
81
81
  "topojson-client": "^3.1.0",
82
- "ts-essentials": "^10.0.4",
82
+ "ts-essentials": "^10.1.0",
83
83
  "tslib": "^2.8.1",
84
84
  "typedoc": "^0.28.5",
85
85
  "typedoc-plugin-markdown": "^4.6.4",
86
86
  "typescript": "^5.8.3",
87
87
  "vite": "^6.3.5",
88
- "vitest": "^3.2.2",
88
+ "vitest": "^3.2.3",
89
89
  "vitest-matchmedia-mock": "^2.0.3"
90
90
  },
91
91
  "types": "./dist/index.d.ts",
@@ -104,10 +104,10 @@
104
104
  "d3-scale-chromatic": "^3.1.0",
105
105
  "d3-shape": "^3.2.0",
106
106
  "d3-time": "^3.1.0",
107
- "es-toolkit": "^1.39.1",
107
+ "es-toolkit": "^1.39.3",
108
108
  "fast-equals": "^5.2.2",
109
109
  "merge-deep": "^3.0.3",
110
- "svelte": "5.33.14"
110
+ "svelte": "5.33.19"
111
111
  },
112
112
  "scripts": {
113
113
  "dev": "vite dev",