layerchart 2.0.0-next.53 → 2.0.0-next.55
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/Arc.svelte +8 -7
- package/dist/components/Arc.svelte.test.js +1 -1
- package/dist/components/ArcLabel.svelte +1 -1
- package/dist/components/Axis.svelte +10 -2
- package/dist/components/Axis.svelte.d.ts +8 -2
- package/dist/components/Bar.svelte +10 -38
- package/dist/components/Circle.svelte +23 -3
- package/dist/components/Circle.svelte.d.ts +6 -0
- package/dist/components/CircleClipPath.svelte +13 -31
- package/dist/components/CircleClipPath.svelte.d.ts +7 -1
- package/dist/components/ClipPath.svelte +64 -21
- package/dist/components/ClipPath.svelte.d.ts +21 -12
- package/dist/components/Connector.svelte +18 -0
- package/dist/components/Connector.svelte.d.ts +5 -0
- package/dist/components/GeoClipPath.svelte +72 -0
- package/dist/components/GeoClipPath.svelte.d.ts +35 -0
- package/dist/components/Grid.svelte +15 -4
- package/dist/components/Grid.svelte.d.ts +14 -4
- package/dist/components/Highlight.svelte +1 -0
- package/dist/components/Hull.svelte +20 -2
- package/dist/components/Hull.svelte.d.ts +2 -2
- package/dist/components/Line.svelte +30 -3
- package/dist/components/Line.svelte.d.ts +7 -0
- package/dist/components/Link.svelte +9 -0
- package/dist/components/Pie.svelte +8 -2
- package/dist/components/Rect.svelte +98 -7
- package/dist/components/Rect.svelte.d.ts +13 -1
- package/dist/components/RectClipPath.svelte +11 -15
- package/dist/components/RectClipPath.svelte.d.ts +6 -0
- package/dist/components/Text.svelte +70 -16
- package/dist/components/Text.svelte.d.ts +10 -0
- package/dist/components/Tree.svelte +7 -3
- package/dist/components/charts/BarChart.svelte.test.js +1 -1
- package/dist/components/charts/DefaultTooltip.svelte.test.js +18 -18
- package/dist/components/charts/LineChart.svelte.test.js +1 -1
- package/dist/components/charts/PieChart.svelte.test.js +2 -2
- package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-series-tooltip-should-use-explicit-series-colors--not-color-scale-1.png +0 -0
- package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-series-tooltip-should-use-explicit-series-colors--not-color-scale-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-fade-non-highlighted-tooltip-series-items-on-hover-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-fade-non-highlighted-tooltip-series-items-on-hover-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-series-colors-in-tooltip-items-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-series-colors-in-tooltip-items-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-single-series-without-total-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-single-series-without-total-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-LineChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-LineChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x--y--and-r-items-when-r-is-configured-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x--y--and-r-items-when-r-is-configured-2.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x-and-y-items-in-tooltip-1.png +0 -0
- package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x-and-y-items-in-tooltip-2.png +0 -0
- package/dist/components/charts/__screenshots__/LineChart.svelte.test.ts/LineChart-tooltip-should-prefer-cScale-color-over-default-series-color-when-cScale-is-explicitly-provided-1.png +0 -0
- package/dist/components/charts/__screenshots__/LineChart.svelte.test.ts/LineChart-tooltip-should-prefer-cScale-color-over-default-series-color-when-cScale-is-explicitly-provided-2.png +0 -0
- package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-1.png +0 -0
- package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-2.png +0 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/components/tooltip/Tooltip.svelte +145 -29
- package/dist/components/tooltip/Tooltip.svelte.d.ts +16 -0
- package/dist/components/tooltip/Tooltip.svelte.test.d.ts +1 -0
- package/dist/components/tooltip/Tooltip.svelte.test.js +294 -0
- package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-portal-tooltip-to-a-custom-selector-target-1.png +0 -0
- package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-portal-tooltip-to-a-custom-selector-target-2.png +0 -0
- package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-render-tooltip-inline-when-portal-is-false-1.png +0 -0
- package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-render-tooltip-inline-when-portal-is-false-2.png +0 -0
- package/dist/utils/__screenshots__/canvas.svelte.test.ts/renderPathData-composes-element-opacity-with-inherited-globalAlpha--Group-opacity--1.png +0 -0
- package/dist/utils/__screenshots__/canvas.svelte.test.ts/renderPathData-composes-element-opacity-with-inherited-globalAlpha--Group-opacity--2.png +0 -0
- package/dist/utils/canvas.d.ts +2 -0
- package/dist/utils/canvas.js +13 -7
- package/dist/utils/canvas.svelte.test.js +55 -0
- package/dist/utils/connectorUtils.d.ts +13 -0
- package/dist/utils/connectorUtils.js +120 -1
- package/dist/utils/path.d.ts +19 -0
- package/dist/utils/path.js +72 -0
- package/dist/utils/rect.svelte.d.ts +18 -0
- package/dist/utils/rect.svelte.js +33 -0
- package/package.json +13 -13
package/dist/utils/canvas.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import memoize from 'memoize';
|
|
2
2
|
import { cls } from '@layerstack/tailwind';
|
|
3
|
+
import { roundedRectPath } from './path.js';
|
|
3
4
|
/** @deprecated - use `isTransparentFill` instead */
|
|
4
5
|
export const DEFAULT_FILL = 'rgb(0, 0, 0)';
|
|
5
6
|
/**
|
|
@@ -152,7 +153,7 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
152
153
|
// Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
|
|
153
154
|
const paintOrder = resolvedStyles?.paintOrder === 'stroke' ? ['stroke', 'fill'] : ['fill', 'stroke'];
|
|
154
155
|
if (resolvedStyles?.opacity) {
|
|
155
|
-
ctx.globalAlpha
|
|
156
|
+
ctx.globalAlpha *= Number(resolvedStyles?.opacity);
|
|
156
157
|
}
|
|
157
158
|
// font/text properties can be expensive to set (not sure why), so only apply if needed (renderText())
|
|
158
159
|
if (applyText) {
|
|
@@ -203,8 +204,7 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
203
204
|
if (fill && !isTransparentFill(fill)) {
|
|
204
205
|
const currentGlobalAlpha = ctx.globalAlpha;
|
|
205
206
|
const fillOpacity = Number(resolvedStyles?.fillOpacity);
|
|
206
|
-
|
|
207
|
-
ctx.globalAlpha = fillOpacity * opacity;
|
|
207
|
+
ctx.globalAlpha *= isNaN(fillOpacity) ? 1 : fillOpacity;
|
|
208
208
|
ctx.fillStyle = fill;
|
|
209
209
|
render.fill(ctx);
|
|
210
210
|
// Restore in case it was modified by `fillOpacity`
|
|
@@ -222,7 +222,7 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
222
222
|
const strokeOpacity = Number(resolvedStyles?.strokeOpacity);
|
|
223
223
|
const opacity = Number(resolvedStyles?.opacity);
|
|
224
224
|
if (!isNaN(strokeOpacity) && strokeOpacity !== 1) {
|
|
225
|
-
ctx.globalAlpha
|
|
225
|
+
ctx.globalAlpha *= strokeOpacity;
|
|
226
226
|
}
|
|
227
227
|
ctx.lineWidth =
|
|
228
228
|
typeof resolvedStyles?.strokeWidth === 'string'
|
|
@@ -253,11 +253,12 @@ export function renderText(ctx, text, coords, styleOptions = {}) {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
export function renderRect(ctx, coords, styleOptions = {}) {
|
|
256
|
-
const { x, y, width, height } = coords;
|
|
256
|
+
const { x, y, width, height, corners } = coords;
|
|
257
257
|
const rx = coords.rx ?? 0;
|
|
258
258
|
const ry = coords.ry ?? rx; // Default ry to rx if not provided (SVG behavior)
|
|
259
|
+
const perCorner = corners && !corners.every((c) => c === corners[0]);
|
|
259
260
|
// No rounding - use simple rect methods
|
|
260
|
-
if (rx === 0 && ry === 0) {
|
|
261
|
+
if (!perCorner && rx === 0 && ry === 0 && !corners) {
|
|
261
262
|
render(ctx, {
|
|
262
263
|
fill: (ctx) => ctx.fillRect(x, y, width, height),
|
|
263
264
|
stroke: (ctx) => ctx.strokeRect(x, y, width, height),
|
|
@@ -267,7 +268,7 @@ export function renderRect(ctx, coords, styleOptions = {}) {
|
|
|
267
268
|
// Try native roundRect if available (modern browsers)
|
|
268
269
|
if (typeof ctx.roundRect === 'function') {
|
|
269
270
|
ctx.beginPath();
|
|
270
|
-
ctx.roundRect(x, y, width, height, [rx, ry]);
|
|
271
|
+
ctx.roundRect(x, y, width, height, corners ?? [rx, ry]);
|
|
271
272
|
render(ctx, {
|
|
272
273
|
fill: (ctx) => ctx.fill(),
|
|
273
274
|
stroke: (ctx) => ctx.stroke(),
|
|
@@ -276,6 +277,11 @@ export function renderRect(ctx, coords, styleOptions = {}) {
|
|
|
276
277
|
return;
|
|
277
278
|
}
|
|
278
279
|
// Fallback: use path rendering for rounded corners
|
|
280
|
+
if (corners) {
|
|
281
|
+
const pathData = roundedRectPath(x, y, width, height, corners);
|
|
282
|
+
renderPathData(ctx, pathData, styleOptions);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
279
285
|
// Clamp radii to half the width/height
|
|
280
286
|
const clampedRx = Math.min(rx, width / 2);
|
|
281
287
|
const clampedRy = Math.min(ry, height / 2);
|
|
@@ -286,6 +286,61 @@ describe('renderPathData', () => {
|
|
|
286
286
|
// During stroke, globalAlpha should be 0.4 * 1 = 0.4
|
|
287
287
|
expect(globalAlphaValues[0]).toBeCloseTo(0.4);
|
|
288
288
|
});
|
|
289
|
+
it('composes fill opacity with inherited globalAlpha (Group opacity)', () => {
|
|
290
|
+
// Simulate a parent Group setting globalAlpha to 0.5
|
|
291
|
+
ctx.globalAlpha = 0.5;
|
|
292
|
+
const globalAlphaValues = [];
|
|
293
|
+
const originalFill = ctx.fill.bind(ctx);
|
|
294
|
+
vi.spyOn(ctx, 'fill').mockImplementation(function (...args) {
|
|
295
|
+
globalAlphaValues.push(ctx.globalAlpha);
|
|
296
|
+
originalFill(...args);
|
|
297
|
+
});
|
|
298
|
+
renderPathData(ctx, 'M0,0 L100,0 L100,100 Z', {
|
|
299
|
+
styles: { fill: 'red', fillOpacity: '1', opacity: '1', stroke: 'none' },
|
|
300
|
+
});
|
|
301
|
+
// Should be 0.5 (inherited) * 1 * 1 = 0.5, not overwritten to 1
|
|
302
|
+
expect(globalAlphaValues[0]).toBeCloseTo(0.5);
|
|
303
|
+
});
|
|
304
|
+
it('composes stroke opacity with inherited globalAlpha (Group opacity)', () => {
|
|
305
|
+
ctx.globalAlpha = 0.5;
|
|
306
|
+
const globalAlphaValues = [];
|
|
307
|
+
const originalStroke = ctx.stroke.bind(ctx);
|
|
308
|
+
vi.spyOn(ctx, 'stroke').mockImplementation(function () {
|
|
309
|
+
globalAlphaValues.push(ctx.globalAlpha);
|
|
310
|
+
originalStroke();
|
|
311
|
+
});
|
|
312
|
+
renderPathData(ctx, 'M0,0 L100,0', {
|
|
313
|
+
styles: {
|
|
314
|
+
fill: 'none',
|
|
315
|
+
stroke: 'black',
|
|
316
|
+
strokeOpacity: '0.4',
|
|
317
|
+
opacity: '1',
|
|
318
|
+
strokeWidth: '2',
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
// Should be 0.5 (inherited) * 0.4 = 0.2
|
|
322
|
+
expect(globalAlphaValues[0]).toBeCloseTo(0.2);
|
|
323
|
+
});
|
|
324
|
+
it('composes element opacity with inherited globalAlpha (Group opacity)', () => {
|
|
325
|
+
ctx.globalAlpha = 0.5;
|
|
326
|
+
const globalAlphaValues = [];
|
|
327
|
+
const originalFill = ctx.fill.bind(ctx);
|
|
328
|
+
vi.spyOn(ctx, 'fill').mockImplementation(function (...args) {
|
|
329
|
+
globalAlphaValues.push(ctx.globalAlpha);
|
|
330
|
+
originalFill(...args);
|
|
331
|
+
});
|
|
332
|
+
renderPathData(ctx, 'M0,0 L100,0 L100,100 Z', {
|
|
333
|
+
styles: { fill: 'red', fillOpacity: '1', opacity: '0.6', stroke: 'none' },
|
|
334
|
+
});
|
|
335
|
+
// Should be 0.5 (inherited) * 0.6 (element opacity) * 1 (fillOpacity) = 0.3
|
|
336
|
+
expect(globalAlphaValues[0]).toBeCloseTo(0.3);
|
|
337
|
+
});
|
|
338
|
+
it('restores globalAlpha after rendering with inherited alpha', () => {
|
|
339
|
+
ctx.globalAlpha = 0.5;
|
|
340
|
+
renderPathData(ctx, 'M0,0 L100,0 L100,100 Z', plainFill('red'));
|
|
341
|
+
// globalAlpha should be restored to inherited value after rendering
|
|
342
|
+
expect(ctx.globalAlpha).toBeCloseTo(0.5);
|
|
343
|
+
});
|
|
289
344
|
it('respects paintOrder stroke (stroke before fill)', () => {
|
|
290
345
|
const callOrder = [];
|
|
291
346
|
vi.spyOn(ctx, 'fill').mockImplementation(() => {
|
|
@@ -18,4 +18,17 @@ type GetConnectorD3PathProps = Omit<GetConnectorPresetPathProps, 'radius' | 'typ
|
|
|
18
18
|
curve: CurveFactory;
|
|
19
19
|
};
|
|
20
20
|
export declare function getConnectorD3Path({ source, target, sweep, curve }: GetConnectorD3PathProps): string;
|
|
21
|
+
type GetConnectorRadialPresetPathProps = {
|
|
22
|
+
source: ConnectorCoords;
|
|
23
|
+
target: ConnectorCoords;
|
|
24
|
+
type: PresetConnectorType;
|
|
25
|
+
radius: number;
|
|
26
|
+
};
|
|
27
|
+
export declare function getConnectorRadialPresetPath({ source, target, type, radius, }: GetConnectorRadialPresetPathProps): string;
|
|
28
|
+
type GetConnectorRadialD3PathProps = {
|
|
29
|
+
source: ConnectorCoords;
|
|
30
|
+
target: ConnectorCoords;
|
|
31
|
+
curve?: CurveFactory;
|
|
32
|
+
};
|
|
33
|
+
export declare function getConnectorRadialD3Path({ source, target, curve, }: GetConnectorRadialD3PathProps): string;
|
|
21
34
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { line as d3Line,
|
|
1
|
+
import { curveLinear, curveStep, curveStepAfter, curveStepBefore, line as d3Line, lineRadial, linkRadial, } from 'd3-shape';
|
|
2
2
|
function isSamePoint(p1, p2) {
|
|
3
3
|
return Math.abs(p1.x - p2.x) < 1e-6 && Math.abs(p1.y - p2.y) < 1e-6;
|
|
4
4
|
}
|
|
@@ -109,3 +109,122 @@ export function getConnectorD3Path({ source, target, sweep, curve }) {
|
|
|
109
109
|
return FALLBACK_PATH;
|
|
110
110
|
return d;
|
|
111
111
|
}
|
|
112
|
+
function radialGeometry(source, target) {
|
|
113
|
+
const sa = source.x - Math.PI / 2;
|
|
114
|
+
const sr = source.y;
|
|
115
|
+
const ta = target.x - Math.PI / 2;
|
|
116
|
+
const tr = target.y;
|
|
117
|
+
const sc = Math.cos(sa);
|
|
118
|
+
const ss = Math.sin(sa);
|
|
119
|
+
const tc = Math.cos(ta);
|
|
120
|
+
const ts = Math.sin(ta);
|
|
121
|
+
const sweepFlag = Math.abs(ta - sa) > Math.PI ? (ta <= sa ? 1 : 0) : ta > sa ? 1 : 0;
|
|
122
|
+
return {
|
|
123
|
+
sa,
|
|
124
|
+
sr,
|
|
125
|
+
ta,
|
|
126
|
+
tr,
|
|
127
|
+
sc,
|
|
128
|
+
ss,
|
|
129
|
+
tc,
|
|
130
|
+
ts,
|
|
131
|
+
sx: sr * sc,
|
|
132
|
+
sy: sr * ss,
|
|
133
|
+
tx: tr * tc,
|
|
134
|
+
ty: tr * ts,
|
|
135
|
+
sweepFlag,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export function getConnectorRadialPresetPath({ source, target, type, radius, }) {
|
|
139
|
+
const g = radialGeometry(source, target);
|
|
140
|
+
const { sr, ta, tr, sc, ss, tc, ts, sx, sy, tx, ty, sweepFlag } = g;
|
|
141
|
+
if (type === 'straight') {
|
|
142
|
+
return `M${sx},${sy}L${tx},${ty}`;
|
|
143
|
+
}
|
|
144
|
+
if (type === 'rounded') {
|
|
145
|
+
// visx LinkRadialCurve: cubic Bezier with rotated offset (percent controls tension)
|
|
146
|
+
const percent = 0.2;
|
|
147
|
+
const dx = tx - sx;
|
|
148
|
+
const dy = ty - sy;
|
|
149
|
+
const ix = percent * (dx + dy);
|
|
150
|
+
const iy = percent * (dy - dx);
|
|
151
|
+
return `M${sx},${sy}C${sx + ix},${sy + iy} ${tx + iy},${ty - ix} ${tx},${ty}`;
|
|
152
|
+
}
|
|
153
|
+
if (type === 'square') {
|
|
154
|
+
// Source at origin — degenerate arc, just radial to target
|
|
155
|
+
if (sr < 1e-6)
|
|
156
|
+
return `M${sx},${sy}L${tx},${ty}`;
|
|
157
|
+
// Step at midpoint radius: radial + arc + radial
|
|
158
|
+
const mr = (sr + tr) / 2;
|
|
159
|
+
const p1x = mr * sc;
|
|
160
|
+
const p1y = mr * ss;
|
|
161
|
+
const p2x = mr * tc;
|
|
162
|
+
const p2y = mr * ts;
|
|
163
|
+
return `M${sx},${sy}L${p1x},${p1y}A${mr},${mr},0,0,${sweepFlag},${p2x},${p2y}L${tx},${ty}`;
|
|
164
|
+
}
|
|
165
|
+
// 'beveled': visx-style step with chord at source radius and chamfered corner
|
|
166
|
+
const cornerX = sr * tc;
|
|
167
|
+
const cornerY = sr * ts;
|
|
168
|
+
const chordDx = cornerX - sx;
|
|
169
|
+
const chordDy = cornerY - sy;
|
|
170
|
+
const chordLen = Math.hypot(chordDx, chordDy);
|
|
171
|
+
if (chordLen < 1e-6) {
|
|
172
|
+
// Source at origin — chord degenerates, just radial to target
|
|
173
|
+
return `M${sx},${sy}L${tx},${ty}`;
|
|
174
|
+
}
|
|
175
|
+
const radialLen = Math.abs(tr - sr) || 1;
|
|
176
|
+
const r = Math.max(0, Math.min(radius, chordLen, radialLen));
|
|
177
|
+
const cux = chordDx / chordLen;
|
|
178
|
+
const cuy = chordDy / chordLen;
|
|
179
|
+
const radialDir = Math.sign(tr - sr) || 1;
|
|
180
|
+
const p1x = cornerX - r * cux;
|
|
181
|
+
const p1y = cornerY - r * cuy;
|
|
182
|
+
const p2x = cornerX + radialDir * r * tc;
|
|
183
|
+
const p2y = cornerY + radialDir * r * ts;
|
|
184
|
+
return `M${sx},${sy}L${p1x},${p1y}L${p2x},${p2y}L${tx},${ty}`;
|
|
185
|
+
}
|
|
186
|
+
export function getConnectorRadialD3Path({ source, target, curve, }) {
|
|
187
|
+
const g = radialGeometry(source, target);
|
|
188
|
+
const { sr, tr, sc, ss, tc, ts, sx, sy, tx, ty, sweepFlag } = g;
|
|
189
|
+
// Step curves render as polar arcs/radials rather than cartesian stairs.
|
|
190
|
+
// When source is at origin (root), degenerate to straight radial line.
|
|
191
|
+
if (curve === curveStepBefore || curve === curveStepAfter || curve === curveStep) {
|
|
192
|
+
if (sr < 1e-6)
|
|
193
|
+
return `M${sx},${sy}L${tx},${ty}`;
|
|
194
|
+
}
|
|
195
|
+
if (curve === curveStepBefore) {
|
|
196
|
+
// arc at source radius, then radial to target
|
|
197
|
+
const ax = sr * tc;
|
|
198
|
+
const ay = sr * ts;
|
|
199
|
+
return `M${sx},${sy}A${sr},${sr},0,0,${sweepFlag},${ax},${ay}L${tx},${ty}`;
|
|
200
|
+
}
|
|
201
|
+
if (curve === curveStepAfter) {
|
|
202
|
+
// radial at source angle to target radius, then arc at target radius
|
|
203
|
+
const ax = tr * sc;
|
|
204
|
+
const ay = tr * ss;
|
|
205
|
+
return `M${sx},${sy}L${ax},${ay}A${tr},${tr},0,0,${sweepFlag},${tx},${ty}`;
|
|
206
|
+
}
|
|
207
|
+
if (curve === curveStep) {
|
|
208
|
+
// radial to mid-radius, arc at mid-radius, radial to target
|
|
209
|
+
const mr = (sr + tr) / 2;
|
|
210
|
+
const p1x = mr * sc;
|
|
211
|
+
const p1y = mr * ss;
|
|
212
|
+
const p2x = mr * tc;
|
|
213
|
+
const p2y = mr * ts;
|
|
214
|
+
return `M${sx},${sy}L${p1x},${p1y}A${mr},${mr},0,0,${sweepFlag},${p2x},${p2y}L${tx},${ty}`;
|
|
215
|
+
}
|
|
216
|
+
if (curve) {
|
|
217
|
+
// Other curves: apply in polar space via d3.lineRadial between the two nodes
|
|
218
|
+
const gen = lineRadial().curve(curve);
|
|
219
|
+
const d = gen([
|
|
220
|
+
[source.x, source.y],
|
|
221
|
+
[target.x, target.y],
|
|
222
|
+
]);
|
|
223
|
+
return d ?? FALLBACK_PATH;
|
|
224
|
+
}
|
|
225
|
+
// Default: smooth radial curve via d3.linkRadial (visx LinkRadial)
|
|
226
|
+
const linkGen = linkRadial()
|
|
227
|
+
.angle((d) => d.x)
|
|
228
|
+
.radius((d) => d.y);
|
|
229
|
+
return linkGen({ source, target }) ?? FALLBACK_PATH;
|
|
230
|
+
}
|
package/dist/utils/path.d.ts
CHANGED
|
@@ -30,6 +30,25 @@ export declare function roundedPolygonPath(coords: {
|
|
|
30
30
|
x: number;
|
|
31
31
|
y: number;
|
|
32
32
|
}[], radius: number): string;
|
|
33
|
+
/**
|
|
34
|
+
* SVG path `d` attribute for a rectangle with per-corner rounding.
|
|
35
|
+
* Corners are ordered `[top-left, top-right, bottom-right, bottom-left]`
|
|
36
|
+
* (matching CSS `border-radius` shorthand). Path is drawn clockwise from
|
|
37
|
+
* the top-left corner.
|
|
38
|
+
*/
|
|
39
|
+
export declare function roundedRectPath(x: number, y: number, width: number, height: number, [tl, tr, br, bl]: [number, number, number, number]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Normalize a dash-array value (CSS `stroke-dasharray`) to a numeric array.
|
|
42
|
+
* Accepts `"4 2"`, `"4,2"`, `[4, 2]`, or a single number (e.g. `4` → `[4, 4]`).
|
|
43
|
+
* Returns `null` when the input is empty or all zeros (i.e. solid stroke).
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseDashArray(value: string | number | number[] | undefined): number[] | null;
|
|
46
|
+
/**
|
|
47
|
+
* Build a CSS `repeating-linear-gradient` string approximating a `stroke-dasharray`
|
|
48
|
+
* pattern along a horizontal line (use with `background` on a rotated `<div>`).
|
|
49
|
+
* Alternates stops between `color` (dash) and `transparent` (gap) to match SVG.
|
|
50
|
+
*/
|
|
51
|
+
export declare function dashArrayToGradient(dashArray: number[], color: string, direction?: string): string;
|
|
33
52
|
/** Vector anchor position */
|
|
34
53
|
export type VectorAnchor = 'start' | 'middle' | 'end';
|
|
35
54
|
/**
|
package/dist/utils/path.js
CHANGED
|
@@ -61,6 +61,78 @@ export function roundedPolygonPath(coords, radius) {
|
|
|
61
61
|
path += 'Z';
|
|
62
62
|
return path;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* SVG path `d` attribute for a rectangle with per-corner rounding.
|
|
66
|
+
* Corners are ordered `[top-left, top-right, bottom-right, bottom-left]`
|
|
67
|
+
* (matching CSS `border-radius` shorthand). Path is drawn clockwise from
|
|
68
|
+
* the top-left corner.
|
|
69
|
+
*/
|
|
70
|
+
export function roundedRectPath(x, y, width, height, [tl, tr, br, bl]) {
|
|
71
|
+
const topEdge = width - tl - tr;
|
|
72
|
+
const rightEdge = height - tr - br;
|
|
73
|
+
const bottomEdge = width - br - bl;
|
|
74
|
+
const leftEdge = height - bl - tl;
|
|
75
|
+
return [
|
|
76
|
+
`M${x + tl},${y}`,
|
|
77
|
+
`h${topEdge}`,
|
|
78
|
+
tr > 0 ? `a${tr},${tr} 0 0 1 ${tr},${tr}` : '',
|
|
79
|
+
`v${rightEdge}`,
|
|
80
|
+
br > 0 ? `a${br},${br} 0 0 1 ${-br},${br}` : '',
|
|
81
|
+
`h${-bottomEdge}`,
|
|
82
|
+
bl > 0 ? `a${bl},${bl} 0 0 1 ${-bl},${-bl}` : '',
|
|
83
|
+
`v${-leftEdge}`,
|
|
84
|
+
tl > 0 ? `a${tl},${tl} 0 0 1 ${tl},${-tl}` : '',
|
|
85
|
+
'z',
|
|
86
|
+
]
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.join(' ');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Normalize a dash-array value (CSS `stroke-dasharray`) to a numeric array.
|
|
92
|
+
* Accepts `"4 2"`, `"4,2"`, `[4, 2]`, or a single number (e.g. `4` → `[4, 4]`).
|
|
93
|
+
* Returns `null` when the input is empty or all zeros (i.e. solid stroke).
|
|
94
|
+
*/
|
|
95
|
+
export function parseDashArray(value) {
|
|
96
|
+
if (value == null || value === '' || value === 'none')
|
|
97
|
+
return null;
|
|
98
|
+
let arr;
|
|
99
|
+
if (typeof value === 'number') {
|
|
100
|
+
arr = [value, value];
|
|
101
|
+
}
|
|
102
|
+
else if (Array.isArray(value)) {
|
|
103
|
+
arr = value.filter((n) => Number.isFinite(n));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
arr = value
|
|
107
|
+
.split(/[\s,]+/)
|
|
108
|
+
.filter((s) => s.length > 0)
|
|
109
|
+
.map((s) => Number(s.replace('px', '')))
|
|
110
|
+
.filter((n) => Number.isFinite(n));
|
|
111
|
+
}
|
|
112
|
+
if (arr.length === 0 || arr.every((n) => n === 0))
|
|
113
|
+
return null;
|
|
114
|
+
// SVG/Canvas semantics: an odd-length array is repeated (e.g. `[5]` → `[5, 5]`)
|
|
115
|
+
if (arr.length % 2 === 1)
|
|
116
|
+
arr = [...arr, ...arr];
|
|
117
|
+
return arr;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build a CSS `repeating-linear-gradient` string approximating a `stroke-dasharray`
|
|
121
|
+
* pattern along a horizontal line (use with `background` on a rotated `<div>`).
|
|
122
|
+
* Alternates stops between `color` (dash) and `transparent` (gap) to match SVG.
|
|
123
|
+
*/
|
|
124
|
+
export function dashArrayToGradient(dashArray, color, direction = 'to right') {
|
|
125
|
+
const stops = [];
|
|
126
|
+
let offset = 0;
|
|
127
|
+
for (let i = 0; i < dashArray.length; i++) {
|
|
128
|
+
const length = dashArray[i];
|
|
129
|
+
const isDash = i % 2 === 0;
|
|
130
|
+
const c = isDash ? color : 'transparent';
|
|
131
|
+
stops.push(`${c} ${offset}px ${offset + length}px`);
|
|
132
|
+
offset += length;
|
|
133
|
+
}
|
|
134
|
+
return `repeating-linear-gradient(${direction}, ${stops.join(', ')})`;
|
|
135
|
+
}
|
|
64
136
|
/**
|
|
65
137
|
* Create arrow vector path data (pointing up by default).
|
|
66
138
|
* The path is centered on the anchor point at origin — use SVG `transform` to position and rotate.
|
|
@@ -49,4 +49,22 @@ export declare function createDimensionGetter<TData>(ctx: ChartState<TData>, get
|
|
|
49
49
|
* Useful when x/y getters for band scale are an array (such as for histograms)
|
|
50
50
|
*/
|
|
51
51
|
export declare function firstValue(value: number | number[] | undefined): number | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Per-corner radii, ordered [top-left, top-right, bottom-right, bottom-left] —
|
|
54
|
+
* matching CSS `border-radius` shorthand and Canvas `roundRect`.
|
|
55
|
+
*/
|
|
56
|
+
export type CornerRadii = [number, number, number, number];
|
|
57
|
+
export type Corners = number | CornerRadii | {
|
|
58
|
+
topLeft?: number;
|
|
59
|
+
topRight?: number;
|
|
60
|
+
bottomRight?: number;
|
|
61
|
+
bottomLeft?: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Normalize a `Corners` value to `[tl, tr, br, bl]`, clamping each corner to
|
|
65
|
+
* half the shorter side so opposite corners cannot overlap.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveCorners(corners: Corners | undefined, width: number, height: number): CornerRadii;
|
|
68
|
+
/** True when all four corner radii are equal (lets callers use simpler `rx`/`ry` rendering). */
|
|
69
|
+
export declare function cornersUniform([tl, tr, br, bl]: CornerRadii): boolean;
|
|
52
70
|
export {};
|
|
@@ -183,3 +183,36 @@ export function createDimensionGetter(ctx, getOptions) {
|
|
|
183
183
|
export function firstValue(value) {
|
|
184
184
|
return Array.isArray(value) ? value[0] : value;
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Normalize a `Corners` value to `[tl, tr, br, bl]`, clamping each corner to
|
|
188
|
+
* half the shorter side so opposite corners cannot overlap.
|
|
189
|
+
*/
|
|
190
|
+
export function resolveCorners(corners, width, height) {
|
|
191
|
+
let tl = 0;
|
|
192
|
+
let tr = 0;
|
|
193
|
+
let br = 0;
|
|
194
|
+
let bl = 0;
|
|
195
|
+
if (typeof corners === 'number') {
|
|
196
|
+
tl = tr = br = bl = corners;
|
|
197
|
+
}
|
|
198
|
+
else if (Array.isArray(corners)) {
|
|
199
|
+
[tl, tr, br, bl] = corners;
|
|
200
|
+
}
|
|
201
|
+
else if (corners) {
|
|
202
|
+
tl = corners.topLeft ?? 0;
|
|
203
|
+
tr = corners.topRight ?? 0;
|
|
204
|
+
br = corners.bottomRight ?? 0;
|
|
205
|
+
bl = corners.bottomLeft ?? 0;
|
|
206
|
+
}
|
|
207
|
+
const max = Math.min(width, height) / 2;
|
|
208
|
+
return [
|
|
209
|
+
Math.min(Math.max(0, tl), max),
|
|
210
|
+
Math.min(Math.max(0, tr), max),
|
|
211
|
+
Math.min(Math.max(0, br), max),
|
|
212
|
+
Math.min(Math.max(0, bl), max),
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
/** True when all four corner radii are equal (lets callers use simpler `rx`/`ry` rendering). */
|
|
216
|
+
export function cornersUniform([tl, tr, br, bl]) {
|
|
217
|
+
return tl === tr && tr === br && br === bl;
|
|
218
|
+
}
|
package/package.json
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "techniq/layerchart",
|
|
7
7
|
"homepage": "https://layerchart.com",
|
|
8
|
-
"version": "2.0.0-next.
|
|
8
|
+
"version": "2.0.0-next.55",
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"@changesets/cli": "^2.30.0",
|
|
11
11
|
"@napi-rs/canvas": "^0.1.97",
|
|
12
12
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
13
|
-
"@sveltejs/kit": "^2.
|
|
13
|
+
"@sveltejs/kit": "^2.57.1",
|
|
14
14
|
"@sveltejs/package": "^2.5.7",
|
|
15
15
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
16
16
|
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
|
|
@@ -33,19 +33,19 @@
|
|
|
33
33
|
"@types/d3-scale-chromatic": "^3.1.0",
|
|
34
34
|
"@types/d3-shape": "^3.1.8",
|
|
35
35
|
"@types/d3-time": "^3.0.4",
|
|
36
|
-
"@vitest/browser": "^4.1.
|
|
37
|
-
"@vitest/browser-playwright": "^4.1.
|
|
38
|
-
"@vitest/ui": "^4.1.
|
|
39
|
-
"playwright": "^1.
|
|
40
|
-
"prettier": "^3.8.
|
|
36
|
+
"@vitest/browser": "^4.1.4",
|
|
37
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
38
|
+
"@vitest/ui": "^4.1.4",
|
|
39
|
+
"playwright": "^1.59.1",
|
|
40
|
+
"prettier": "^3.8.2",
|
|
41
41
|
"prettier-plugin-svelte": "^3.5.1",
|
|
42
|
-
"svelte": "5.
|
|
43
|
-
"svelte-check": "^4.4.
|
|
44
|
-
"svelte2tsx": "^0.7.
|
|
42
|
+
"svelte": "5.55.3",
|
|
43
|
+
"svelte-check": "^4.4.6",
|
|
44
|
+
"svelte2tsx": "^0.7.53",
|
|
45
45
|
"tslib": "^2.8.1",
|
|
46
|
-
"typescript": "^
|
|
47
|
-
"vite": "^8.0.
|
|
48
|
-
"vitest": "^4.1.
|
|
46
|
+
"typescript": "^6.0.2",
|
|
47
|
+
"vite": "^8.0.8",
|
|
48
|
+
"vitest": "^4.1.4",
|
|
49
49
|
"vitest-browser-svelte": "^2.1.0"
|
|
50
50
|
},
|
|
51
51
|
"type": "module",
|