layerchart 2.0.0-next.19 → 2.0.0-next.20
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.
- package/dist/components/Calendar.svelte +6 -2
- package/dist/components/Calendar.svelte.d.ts +1 -1
- package/dist/components/GeoPath.svelte +7 -1
- package/dist/components/MonthPath.svelte +2 -2
- package/dist/components/layout/Canvas.svelte +40 -32
- package/dist/utils/canvas.d.ts +77 -0
- package/dist/utils/canvas.js +90 -41
- package/dist/utils/string.d.ts +49 -0
- package/dist/utils/string.js +4 -2
- package/package.json +2 -1
- package/dist/utils/date.d.ts +0 -8
- package/dist/utils/date.js +0 -11
- package/dist/utils/object.js +0 -2
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
/**
|
|
37
37
|
* Props to pass to the `<text>` element for month labels.
|
|
38
38
|
*/
|
|
39
|
-
monthLabel?: Partial<ComponentProps<typeof Text>>;
|
|
39
|
+
monthLabel?: boolean | Partial<ComponentProps<typeof Text>>;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Tooltip context to setup mouse events to show tooltip for related data
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
start,
|
|
75
75
|
cellSize: cellSizeProp,
|
|
76
76
|
monthPath = false,
|
|
77
|
+
monthLabel = true,
|
|
77
78
|
tooltipContext: tooltip,
|
|
78
79
|
children,
|
|
79
|
-
monthLabel,
|
|
80
80
|
...restProps
|
|
81
81
|
}: CalendarPropsWithoutHTML = $props();
|
|
82
82
|
|
|
@@ -136,7 +136,11 @@
|
|
|
136
136
|
{#if monthPath}
|
|
137
137
|
{#each yearMonths as date}
|
|
138
138
|
<MonthPath {date} {cellSize} {...extractLayerProps(monthPath, 'calendar-month-path')} />
|
|
139
|
+
{/each}
|
|
140
|
+
{/if}
|
|
139
141
|
|
|
142
|
+
{#if monthLabel}
|
|
143
|
+
{#each yearMonths as date}
|
|
140
144
|
<Text
|
|
141
145
|
x={timeWeek.count(timeYear.floor(date), timeWeek.ceil(date)) * cellSize[0]}
|
|
142
146
|
y={-4}
|
|
@@ -30,7 +30,7 @@ export type CalendarPropsWithoutHTML = {
|
|
|
30
30
|
/**
|
|
31
31
|
* Props to pass to the `<text>` element for month labels.
|
|
32
32
|
*/
|
|
33
|
-
monthLabel?: Partial<ComponentProps<typeof Text>>;
|
|
33
|
+
monthLabel?: boolean | Partial<ComponentProps<typeof Text>>;
|
|
34
34
|
/**
|
|
35
35
|
* Tooltip context to setup mouse events to show tooltip for related data
|
|
36
36
|
*/
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { TooltipContextValue } from './tooltip/TooltipContext.svelte';
|
|
6
6
|
import { curveLinearClosed, type CurveFactory, type CurveFactoryLineOnly } from 'd3-shape';
|
|
7
7
|
import {
|
|
8
|
+
geoPath as d3GeoPath,
|
|
8
9
|
geoTransform as d3geoTransform,
|
|
9
10
|
type GeoIdentityTransform,
|
|
10
11
|
type GeoPermissibleObjects,
|
|
@@ -105,6 +106,10 @@
|
|
|
105
106
|
const geoPath = $derived.by(() => {
|
|
106
107
|
geojson;
|
|
107
108
|
if (!projection) return;
|
|
109
|
+
// Only use geoCurvePath for custom curves (performance impact)
|
|
110
|
+
if (curve === curveLinearClosed) {
|
|
111
|
+
return d3GeoPath(projection);
|
|
112
|
+
}
|
|
108
113
|
return geoCurvePath(projection, curve);
|
|
109
114
|
});
|
|
110
115
|
|
|
@@ -166,8 +171,9 @@
|
|
|
166
171
|
touchmove: restProps.ontouchmove,
|
|
167
172
|
},
|
|
168
173
|
deps: () => [
|
|
169
|
-
geojson,
|
|
170
174
|
projection,
|
|
175
|
+
geojson,
|
|
176
|
+
curve,
|
|
171
177
|
fillKey.current,
|
|
172
178
|
strokeKey.current,
|
|
173
179
|
strokeWidth,
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
|
|
32
32
|
<script lang="ts">
|
|
33
33
|
import { timeWeek, timeMonth, timeYear } from 'd3-time';
|
|
34
|
-
import { endOfInterval } from '../utils/date.js';
|
|
35
34
|
import { cls } from '@layerstack/tailwind';
|
|
35
|
+
import { endOfInterval } from '@layerstack/utils';
|
|
36
36
|
import { layerClass } from '../utils/attributes.js';
|
|
37
37
|
import Spline, { type SplinePropsWithoutHTML } from './Spline.svelte';
|
|
38
38
|
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
const startWeek = $derived(timeWeek.count(timeYear(date), date));
|
|
59
59
|
|
|
60
60
|
// end of month
|
|
61
|
-
const monthEnd = $derived(endOfInterval(
|
|
61
|
+
const monthEnd = $derived(endOfInterval('month', date));
|
|
62
62
|
const endDayOfWeek = $derived(monthEnd.getDay());
|
|
63
63
|
const endWeek = $derived(timeWeek.count(timeYear(monthEnd), monthEnd));
|
|
64
64
|
|
|
@@ -346,45 +346,53 @@
|
|
|
346
346
|
context.restore();
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
|
|
349
|
+
/*
|
|
350
|
+
* Sync hit canvas with main canvas
|
|
351
|
+
*/
|
|
350
352
|
if (hitCanvasContext) {
|
|
351
|
-
// scale hit canvas to match main canvas
|
|
352
|
-
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
|
|
353
|
-
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
|
|
354
|
-
|
|
355
|
-
// reset and sync transform to the state after retainState components
|
|
356
|
-
hitCanvasContext.resetTransform();
|
|
357
|
-
hitCanvasContext.setTransform(mainTransformAfterRetain);
|
|
358
|
-
|
|
359
|
-
// reset color generator
|
|
360
|
-
colorGenerator = rgbColorGenerator();
|
|
361
|
-
|
|
362
353
|
const inactiveMoving = !activeCanvas && transformCtx.moving;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
354
|
+
if (disableHitCanvas || transformCtx.dragging || inactiveMoving) {
|
|
355
|
+
// Skip rendering hit canvas
|
|
356
|
+
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
|
|
357
|
+
} else {
|
|
358
|
+
// scale hit canvas to match main canvas
|
|
359
|
+
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
|
|
360
|
+
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
|
|
361
|
+
|
|
362
|
+
// reset and sync transform to the state after retainState components
|
|
363
|
+
hitCanvasContext.resetTransform();
|
|
364
|
+
hitCanvasContext.setTransform(mainTransformAfterRetain);
|
|
365
|
+
|
|
366
|
+
// reset color generator
|
|
367
|
+
colorGenerator = rgbColorGenerator();
|
|
368
|
+
|
|
369
|
+
// render retainState components on hit canvas (e.g., Group)
|
|
370
|
+
for (const c of retainStateComponents) {
|
|
371
|
+
const componentHasEvents =
|
|
372
|
+
c.events && Object.values(c.events).filter((d) => d).length > 0;
|
|
373
|
+
|
|
374
|
+
if (componentHasEvents) {
|
|
375
|
+
// since the transform was already applied via setTransform, skip rendering
|
|
376
|
+
// the retainState component's transform again; proceed to its children
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
372
379
|
}
|
|
373
|
-
}
|
|
374
380
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
// render non-retainState components on hit canvas
|
|
382
|
+
for (const c of nonRetainStateComponents) {
|
|
383
|
+
const componentHasEvents =
|
|
384
|
+
c.events && Object.values(c.events).filter((d) => d).length > 0;
|
|
378
385
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
386
|
+
if (componentHasEvents) {
|
|
387
|
+
const color = getColorStr(colorGenerator.next().value);
|
|
388
|
+
const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
|
|
382
389
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
390
|
+
hitCanvasContext.save();
|
|
391
|
+
c.render(hitCanvasContext, styleOverrides);
|
|
392
|
+
hitCanvasContext.restore();
|
|
386
393
|
|
|
387
|
-
|
|
394
|
+
componentByColor.set(color, c);
|
|
395
|
+
}
|
|
388
396
|
}
|
|
389
397
|
}
|
|
390
398
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ClassValue } from 'svelte/elements';
|
|
2
|
+
import type { PatternShape } from '../components/Pattern.svelte';
|
|
3
|
+
export declare const DEFAULT_FILL = "rgb(0, 0, 0)";
|
|
4
|
+
type StyleOptions = Partial<Omit<CSSStyleDeclaration, 'fillOpacity' | 'strokeWidth' | 'opacity'> & {
|
|
5
|
+
fillOpacity?: number | string;
|
|
6
|
+
strokeWidth?: number | string;
|
|
7
|
+
opacity?: number | string;
|
|
8
|
+
}>;
|
|
9
|
+
export type ComputedStylesOptions = {
|
|
10
|
+
styles?: StyleOptions;
|
|
11
|
+
classes?: ClassValue | null;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
|
|
15
|
+
*/
|
|
16
|
+
export declare function _getComputedStyles(canvas: HTMLCanvasElement, { styles, classes }?: ComputedStylesOptions): CSSStyleDeclaration;
|
|
17
|
+
export declare const getComputedStyles: any;
|
|
18
|
+
/** Render SVG path data onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
19
|
+
export declare function renderPathData(ctx: CanvasRenderingContext2D, pathData: string | null | undefined, styleOptions?: ComputedStylesOptions): void;
|
|
20
|
+
export declare function renderText(ctx: CanvasRenderingContext2D, text: string | number | null | undefined, coords: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
24
|
+
export declare function renderRect(ctx: CanvasRenderingContext2D, coords: {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
30
|
+
export declare function renderCircle(ctx: CanvasRenderingContext2D, coords: {
|
|
31
|
+
cx: number;
|
|
32
|
+
cy: number;
|
|
33
|
+
r: number;
|
|
34
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
35
|
+
export declare function renderEllipse(ctx: CanvasRenderingContext2D, coords: {
|
|
36
|
+
cx: number;
|
|
37
|
+
cy: number;
|
|
38
|
+
rx: number;
|
|
39
|
+
ry: number;
|
|
40
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
41
|
+
/** Clear canvas accounting for Canvas `context.translate(...)` */
|
|
42
|
+
export declare function clearCanvasContext(ctx: CanvasRenderingContext2D, options: {
|
|
43
|
+
containerWidth: number;
|
|
44
|
+
containerHeight: number;
|
|
45
|
+
padding: {
|
|
46
|
+
top: number;
|
|
47
|
+
bottom: number;
|
|
48
|
+
left: number;
|
|
49
|
+
right: number;
|
|
50
|
+
};
|
|
51
|
+
}): void;
|
|
52
|
+
/**
|
|
53
|
+
Scales a canvas for high DPI / retina displays.
|
|
54
|
+
@see: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#examples
|
|
55
|
+
@see: https://web.dev/articles/canvas-hidipi
|
|
56
|
+
*/
|
|
57
|
+
export declare function scaleCanvas(ctx: CanvasRenderingContext2D, width: number, height: number): {
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
};
|
|
61
|
+
/** Get pixel color (r,g,b,a) at canvas coordinates */
|
|
62
|
+
export declare function getPixelColor(ctx: CanvasRenderingContext2D, x: number, y: number): {
|
|
63
|
+
r: number;
|
|
64
|
+
g: number;
|
|
65
|
+
b: number;
|
|
66
|
+
a: number;
|
|
67
|
+
};
|
|
68
|
+
export declare function _createLinearGradient(ctx: CanvasRenderingContext2D, x0: number, y0: number, x1: number, y1: number, stops: {
|
|
69
|
+
offset: number;
|
|
70
|
+
color: string;
|
|
71
|
+
}[]): CanvasGradient;
|
|
72
|
+
/** Create linear gradient and memoize result to fix reactivity */
|
|
73
|
+
export declare const createLinearGradient: any;
|
|
74
|
+
export declare function _createPattern(ctx: CanvasRenderingContext2D, width: number, height: number, shapes: PatternShape[], background?: string): CanvasPattern | null;
|
|
75
|
+
/** Create pattern and memoize result to fix reactivity */
|
|
76
|
+
export declare const createPattern: any;
|
|
77
|
+
export {};
|
package/dist/utils/canvas.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
import memoize from 'memoize';
|
|
1
2
|
import { cls } from '@layerstack/tailwind';
|
|
2
|
-
import { memoize } from 'lodash-es';
|
|
3
3
|
export const DEFAULT_FILL = 'rgb(0, 0, 0)';
|
|
4
4
|
const CANVAS_STYLES_ELEMENT_ID = '__layerchart_canvas_styles_id';
|
|
5
|
+
const supportedStyles = [
|
|
6
|
+
'fill',
|
|
7
|
+
'fillOpacity',
|
|
8
|
+
'stroke',
|
|
9
|
+
'strokeWidth',
|
|
10
|
+
'opacity',
|
|
11
|
+
'fontWeight',
|
|
12
|
+
'fontSize',
|
|
13
|
+
'fontFamily',
|
|
14
|
+
'textAnchor',
|
|
15
|
+
'textAlign',
|
|
16
|
+
'paintOrder',
|
|
17
|
+
];
|
|
5
18
|
/**
|
|
6
19
|
* Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
|
|
7
20
|
*/
|
|
8
|
-
export function
|
|
21
|
+
export function _getComputedStyles(canvas, { styles, classes } = {}) {
|
|
22
|
+
// console.count(`getComputedStyles: ${getComputedStylesKey(canvas, { styles, classes })}`);
|
|
9
23
|
try {
|
|
10
24
|
// Get or create `<svg>` below `<canvas>`
|
|
11
25
|
let svg = document.getElementById(CANVAS_STYLES_ELEMENT_ID);
|
|
@@ -32,7 +46,11 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
|
|
|
32
46
|
.filter((s) => !s.startsWith('transition-'))
|
|
33
47
|
.join(' '));
|
|
34
48
|
}
|
|
35
|
-
|
|
49
|
+
// Capture copy to enable memoization and avoid capturing all styles (which is very slow)
|
|
50
|
+
const computedStyles = supportedStyles.reduce((acc, style) => {
|
|
51
|
+
acc[style] = window.getComputedStyle(svg)[style];
|
|
52
|
+
return acc;
|
|
53
|
+
}, {});
|
|
36
54
|
return computedStyles;
|
|
37
55
|
}
|
|
38
56
|
catch (e) {
|
|
@@ -40,38 +58,71 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
|
|
|
40
58
|
return {};
|
|
41
59
|
}
|
|
42
60
|
}
|
|
61
|
+
function getComputedStylesKey(canvas, { styles, classes } = {}) {
|
|
62
|
+
return JSON.stringify({ canvasId: canvas.id, styles, classes });
|
|
63
|
+
}
|
|
64
|
+
export const getComputedStyles = memoize(_getComputedStyles, {
|
|
65
|
+
cacheKey: ([canvas, styleOptions]) => {
|
|
66
|
+
return getComputedStylesKey(canvas, styleOptions);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
43
69
|
/** Render onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
44
|
-
function render(ctx, render, styleOptions = {}) {
|
|
70
|
+
function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
45
71
|
// console.count('render');
|
|
46
72
|
// TODO: Consider memoizing? How about reactiving to CSS variable changes (light/dark mode toggle)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
let resolvedStyles;
|
|
74
|
+
if (styleOptions.classes == null &&
|
|
75
|
+
!Object.values(styleOptions.styles ?? {}).some((v) => typeof v === 'string' && v.includes('var('))) {
|
|
76
|
+
// Skip resolving styles if no classes are provided and no styles are using CSS variables
|
|
77
|
+
resolvedStyles = styleOptions.styles ?? {};
|
|
52
78
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
else {
|
|
80
|
+
// Remove constant non-css variable properties (ex. `strokeWidth: 0.5`, `fill: #123456`) as not needed and improves memoization cache hit
|
|
81
|
+
const { constantStyles, variableStyles } = Object.entries(styleOptions.styles ?? {}).reduce((acc, [key, value]) => {
|
|
82
|
+
if (typeof value === 'number' || (typeof value === 'string' && !value.includes('var('))) {
|
|
83
|
+
acc.constantStyles[key] = value;
|
|
84
|
+
}
|
|
85
|
+
else if (typeof value === 'string' && value.includes('var(')) {
|
|
86
|
+
acc.variableStyles[key] = value;
|
|
87
|
+
}
|
|
88
|
+
return acc;
|
|
89
|
+
}, { constantStyles: {}, variableStyles: {} });
|
|
90
|
+
const computedStyles = getComputedStyles(ctx.canvas, {
|
|
91
|
+
styles: variableStyles,
|
|
92
|
+
classes: styleOptions.classes,
|
|
93
|
+
});
|
|
94
|
+
resolvedStyles = { ...computedStyles, ...constantStyles };
|
|
58
95
|
}
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
// Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
|
|
97
|
+
const paintOrder = resolvedStyles?.paintOrder === 'stroke' ? ['stroke', 'fill'] : ['fill', 'stroke'];
|
|
98
|
+
if (resolvedStyles?.opacity) {
|
|
99
|
+
ctx.globalAlpha = Number(resolvedStyles?.opacity);
|
|
61
100
|
}
|
|
62
|
-
|
|
63
|
-
|
|
101
|
+
// font/text properties can be expensive to set (not sure why), so only apply if needed (renderText())
|
|
102
|
+
if (applyText) {
|
|
103
|
+
// Text properties
|
|
104
|
+
ctx.font = `${resolvedStyles.fontWeight} ${resolvedStyles.fontSize} ${resolvedStyles.fontFamily}`; // build string instead of using `computedStyles.font` to fix/workaround `tabular-nums` returning `null`
|
|
105
|
+
// TODO: Hack to handle `textAnchor` with canvas. Try to find a better approach
|
|
106
|
+
if (resolvedStyles.textAnchor === 'middle') {
|
|
107
|
+
ctx.textAlign = 'center';
|
|
108
|
+
}
|
|
109
|
+
else if (resolvedStyles.textAnchor === 'end') {
|
|
110
|
+
ctx.textAlign = 'right';
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
ctx.textAlign = resolvedStyles.textAlign; // TODO: Handle/map `justify` and `match-parent`?
|
|
114
|
+
}
|
|
115
|
+
// TODO: Handle `textBaseline` / `verticalAnchor` (Text)
|
|
116
|
+
// ctx.textBaseline = 'top';
|
|
117
|
+
// ctx.textBaseline = 'middle';
|
|
118
|
+
// ctx.textBaseline = 'bottom';
|
|
119
|
+
// ctx.textBaseline = 'alphabetic';
|
|
120
|
+
// ctx.textBaseline = 'hanging';
|
|
121
|
+
// ctx.textBaseline = 'ideographic';
|
|
64
122
|
}
|
|
65
|
-
// TODO: Handle `textBaseline` / `verticalAnchor` (Text)
|
|
66
|
-
// ctx.textBaseline = 'top';
|
|
67
|
-
// ctx.textBaseline = 'middle';
|
|
68
|
-
// ctx.textBaseline = 'bottom';
|
|
69
|
-
// ctx.textBaseline = 'alphabetic';
|
|
70
|
-
// ctx.textBaseline = 'hanging';
|
|
71
|
-
// ctx.textBaseline = 'ideographic';
|
|
72
123
|
// Dashed lines
|
|
73
|
-
if (
|
|
74
|
-
const dashArray =
|
|
124
|
+
if (resolvedStyles.strokeDasharray?.includes(',')) {
|
|
125
|
+
const dashArray = resolvedStyles.strokeDasharray
|
|
75
126
|
.split(',')
|
|
76
127
|
.map((s) => Number(s.replace('px', '')));
|
|
77
128
|
ctx.setLineDash(dashArray);
|
|
@@ -83,11 +134,11 @@ function render(ctx, render, styleOptions = {}) {
|
|
|
83
134
|
styleOptions.styles?.fill instanceof CanvasPattern ||
|
|
84
135
|
!styleOptions.styles?.fill?.includes('var'))
|
|
85
136
|
? styleOptions.styles.fill
|
|
86
|
-
:
|
|
137
|
+
: resolvedStyles?.fill;
|
|
87
138
|
if (fill && !['none', DEFAULT_FILL].includes(fill)) {
|
|
88
139
|
const currentGlobalAlpha = ctx.globalAlpha;
|
|
89
|
-
const fillOpacity = Number(
|
|
90
|
-
const opacity = Number(
|
|
140
|
+
const fillOpacity = Number(resolvedStyles?.fillOpacity);
|
|
141
|
+
const opacity = Number(resolvedStyles?.opacity);
|
|
91
142
|
ctx.globalAlpha = fillOpacity * opacity;
|
|
92
143
|
ctx.fillStyle = fill;
|
|
93
144
|
render.fill(ctx);
|
|
@@ -100,12 +151,12 @@ function render(ctx, render, styleOptions = {}) {
|
|
|
100
151
|
(styleOptions.styles?.stroke instanceof CanvasGradient ||
|
|
101
152
|
!styleOptions.styles?.stroke?.includes('var'))
|
|
102
153
|
? styleOptions.styles?.stroke
|
|
103
|
-
:
|
|
154
|
+
: resolvedStyles?.stroke;
|
|
104
155
|
if (stroke && !['none'].includes(stroke)) {
|
|
105
156
|
ctx.lineWidth =
|
|
106
|
-
typeof
|
|
107
|
-
? Number(
|
|
108
|
-
: (
|
|
157
|
+
typeof resolvedStyles?.strokeWidth === 'string'
|
|
158
|
+
? Number(resolvedStyles?.strokeWidth?.replace('px', ''))
|
|
159
|
+
: (resolvedStyles?.strokeWidth ?? 1);
|
|
109
160
|
ctx.strokeStyle = stroke;
|
|
110
161
|
render.stroke(ctx);
|
|
111
162
|
}
|
|
@@ -125,7 +176,7 @@ export function renderText(ctx, text, coords, styleOptions = {}) {
|
|
|
125
176
|
render(ctx, {
|
|
126
177
|
fill: (ctx) => ctx.fillText(text.toString(), coords.x, coords.y),
|
|
127
178
|
stroke: (ctx) => ctx.strokeText(text.toString(), coords.x, coords.y),
|
|
128
|
-
}, styleOptions);
|
|
179
|
+
}, styleOptions, { applyText: true });
|
|
129
180
|
}
|
|
130
181
|
}
|
|
131
182
|
export function renderRect(ctx, coords, styleOptions = {}) {
|
|
@@ -194,9 +245,8 @@ export function _createLinearGradient(ctx, x0, y0, x1, y1, stops) {
|
|
|
194
245
|
return gradient;
|
|
195
246
|
}
|
|
196
247
|
/** Create linear gradient and memoize result to fix reactivity */
|
|
197
|
-
export const createLinearGradient = memoize(_createLinearGradient,
|
|
198
|
-
|
|
199
|
-
return key;
|
|
248
|
+
export const createLinearGradient = memoize(_createLinearGradient, {
|
|
249
|
+
cacheKey: (args) => JSON.stringify(args.slice(1)), // Ignore `ctx` argument
|
|
200
250
|
});
|
|
201
251
|
export function _createPattern(ctx, width, height, shapes, background) {
|
|
202
252
|
const patternCanvas = document.createElement('canvas');
|
|
@@ -229,7 +279,6 @@ export function _createPattern(ctx, width, height, shapes, background) {
|
|
|
229
279
|
return pattern;
|
|
230
280
|
}
|
|
231
281
|
/** Create pattern and memoize result to fix reactivity */
|
|
232
|
-
export const createPattern = memoize(_createPattern,
|
|
233
|
-
|
|
234
|
-
return key;
|
|
282
|
+
export const createPattern = memoize(_createPattern, {
|
|
283
|
+
cacheKey: (args) => JSON.stringify(args.slice(1)), // Ignore `ctx` argument
|
|
235
284
|
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export declare const getStringWidth: any;
|
|
2
|
+
export type RasterizeTextOptions = {
|
|
3
|
+
fontSize?: string;
|
|
4
|
+
fontWeight?: number;
|
|
5
|
+
fontFamily?: string;
|
|
6
|
+
textAlign?: CanvasTextAlign;
|
|
7
|
+
textBaseline?: CanvasTextBaseline;
|
|
8
|
+
spacing?: number;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
x?: number;
|
|
12
|
+
y?: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function rasterizeText(text: string, options?: RasterizeTextOptions): number[][];
|
|
15
|
+
export declare function toTitleCase(str: string): string;
|
|
16
|
+
export type TruncateTextOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* The maximum pixel width (optional if maxChars is provided).
|
|
19
|
+
*/
|
|
20
|
+
maxWidth?: number;
|
|
21
|
+
/**
|
|
22
|
+
* CSS style for width calculation
|
|
23
|
+
*/
|
|
24
|
+
style?: CSSStyleDeclaration;
|
|
25
|
+
/**
|
|
26
|
+
* The maximum character count
|
|
27
|
+
*/
|
|
28
|
+
maxChars?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Where to place the ellipsis: 'start', 'middle', or 'end'
|
|
31
|
+
*
|
|
32
|
+
* @default 'end'
|
|
33
|
+
*/
|
|
34
|
+
position?: 'start' | 'middle' | 'end';
|
|
35
|
+
/**
|
|
36
|
+
* The character(s) to use as the ellipsis
|
|
37
|
+
*
|
|
38
|
+
* @default '…'
|
|
39
|
+
*/
|
|
40
|
+
ellipsis?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Truncates a string to fit within a specified pixel width or character count.
|
|
44
|
+
* If the string's width exceeds the maxWidth, it will be truncated. If the character
|
|
45
|
+
* count exceeds maxChars, it will also be truncated.
|
|
46
|
+
*
|
|
47
|
+
* The ellipsis can be placed at the start, middle, or end of the string.
|
|
48
|
+
*/
|
|
49
|
+
export declare function truncateText(text: string, { position, ellipsis, maxWidth, style, maxChars }: TruncateTextOptions): string;
|
package/dist/utils/string.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import memoize from 'memoize';
|
|
2
2
|
const MEASUREMENT_ELEMENT_ID = '__text_measurement_id';
|
|
3
3
|
function _getStringWidth(str, style) {
|
|
4
4
|
try {
|
|
@@ -24,7 +24,9 @@ function _getStringWidth(str, style) {
|
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
export const getStringWidth = memoize(_getStringWidth,
|
|
27
|
+
export const getStringWidth = memoize(_getStringWidth, {
|
|
28
|
+
cacheKey: ([str, style]) => `${str}_${JSON.stringify(style)}`,
|
|
29
|
+
});
|
|
28
30
|
export function rasterizeText(text, options = {}) {
|
|
29
31
|
const fontSize = options.fontSize ?? '200px';
|
|
30
32
|
const fontWeight = options.fontWeight ?? 600;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"author": "Sean Lynch <techniq35@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "techniq/layerchart",
|
|
7
|
-
"version": "2.0.0-next.
|
|
7
|
+
"version": "2.0.0-next.20",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@changesets/cli": "^2.29.4",
|
|
10
10
|
"@iconify-json/lucide": "^1.2.44",
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"d3-tile": "^1.0.0",
|
|
98
98
|
"d3-time": "^3.1.0",
|
|
99
99
|
"lodash-es": "^4.17.21",
|
|
100
|
+
"memoize": "^10.1.0",
|
|
100
101
|
"runed": "^0.28.0"
|
|
101
102
|
},
|
|
102
103
|
"peerDependencies": {
|
package/dist/utils/date.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type TimeInterval } from 'd3-time';
|
|
2
|
-
/**
|
|
3
|
-
* Get the date at the end of the interval
|
|
4
|
-
* Similar to `interval.ceil(date)` except:
|
|
5
|
-
* - returns end of day instead of start of next day
|
|
6
|
-
* - properly handles start of day (i.e. not return same date)
|
|
7
|
-
*/
|
|
8
|
-
export declare function endOfInterval(date: Date, interval: TimeInterval): Date;
|
package/dist/utils/date.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import {} from 'd3-time';
|
|
2
|
-
/**
|
|
3
|
-
* Get the date at the end of the interval
|
|
4
|
-
* Similar to `interval.ceil(date)` except:
|
|
5
|
-
* - returns end of day instead of start of next day
|
|
6
|
-
* - properly handles start of day (i.e. not return same date)
|
|
7
|
-
*/
|
|
8
|
-
export function endOfInterval(date, interval) {
|
|
9
|
-
// Can not use `new Date(+interval.ceil(date) - 1)`; as `.ceil()` will return same date when start of the day (matching `.floor()`)
|
|
10
|
-
return new Date(interval.offset(interval.floor(date), 1).getTime() - 1);
|
|
11
|
-
}
|
package/dist/utils/object.js
DELETED