layerchart 0.6.1 → 0.6.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.
- package/components/ChartClipPath.svelte +1 -0
- package/components/ChartClipPath.svelte.d.ts +2 -0
- package/components/CircleClipPath.svelte +1 -1
- package/components/CircleClipPath.svelte.d.ts +2 -0
- package/components/RectClipPath.svelte +1 -1
- package/components/RectClipPath.svelte.d.ts +2 -0
- package/components/Text2.svelte +134 -0
- package/components/Text2.svelte.d.ts +28 -0
- package/components/Zoom.svelte +20 -9
- package/components/Zoom.svelte.d.ts +3 -0
- package/package.json +2 -1
- package/utils/index.d.ts +4 -0
- package/utils/index.js +4 -0
- package/utils/stack.d.ts +3 -1
- package/utils/stack.js +2 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script>import { getStringWidth } from '../utils/string';
|
|
2
|
+
/*
|
|
3
|
+
TODO:
|
|
4
|
+
- [ ] Handle styled text (use <slot /> to measure?)
|
|
5
|
+
- [ ] Simplify by using `alignment-baseline` / `dominant-baseline`, rework multiline or drop support, etc
|
|
6
|
+
- https://svelte.dev/repl/f12d3003313a43ba8a0be53e5786f1c7?version=3.44.3
|
|
7
|
+
- https://observablehq.com/@neocartocnrs/cheat-sheet-on-texts-in-svg
|
|
8
|
+
|
|
9
|
+
Reference:
|
|
10
|
+
- https://bl.ocks.org/mbostock/7555321
|
|
11
|
+
- https://github.com/airbnb/visx/blob/master/packages/visx-text/src/Text.tsx
|
|
12
|
+
- https://airbnb.io/visx/text
|
|
13
|
+
- https://github.com/airbnb/visx/blob/master/packages/visx-demo/src/pages/text.tsx
|
|
14
|
+
*/
|
|
15
|
+
/** text value */
|
|
16
|
+
export let value = 0;
|
|
17
|
+
/** Maximum width to occupy (approximate as words are not split). */
|
|
18
|
+
export let width = undefined;
|
|
19
|
+
/** x position of the text. */
|
|
20
|
+
export let x = 0;
|
|
21
|
+
/** y position of the text. */
|
|
22
|
+
export let y = 0;
|
|
23
|
+
/** dx offset of the text. */
|
|
24
|
+
export let dx = 0;
|
|
25
|
+
/** dy offset of the text. */
|
|
26
|
+
export let dy = 0;
|
|
27
|
+
/** Desired "line height" of the text, implemented as y offsets. */
|
|
28
|
+
export let lineHeight = '1em';
|
|
29
|
+
/** Cap height of the text. */
|
|
30
|
+
export let capHeight = '0.71em'; // Magic number from d3
|
|
31
|
+
/** Whether to scale the fontSize to accommodate the specified width. */
|
|
32
|
+
export let scaleToFit = false;
|
|
33
|
+
/** Horizontal text anchor. */
|
|
34
|
+
export let textAnchor = 'start';
|
|
35
|
+
/** Vertical text anchor. */
|
|
36
|
+
export let verticalAnchor = 'end'; // default SVG behavior
|
|
37
|
+
/** Rotational angle of the text. */
|
|
38
|
+
export let rotate = undefined;
|
|
39
|
+
let wordsByLines = [];
|
|
40
|
+
let wordsWithWidth = [];
|
|
41
|
+
let spaceWidth = 0;
|
|
42
|
+
let style = undefined; // TODO: read from DOM?
|
|
43
|
+
$: words = value ? value.toString().split(/(?:(?!\u00A0+)\s+)/) : [];
|
|
44
|
+
$: wordsWithWidth = words.map((word) => ({
|
|
45
|
+
word,
|
|
46
|
+
width: getStringWidth(word, style) || 0
|
|
47
|
+
}));
|
|
48
|
+
$: spaceWidth = getStringWidth('\u00A0', style) || 0;
|
|
49
|
+
$: wordsByLines = wordsWithWidth.reduce((result, item) => {
|
|
50
|
+
const currentLine = result[result.length - 1];
|
|
51
|
+
if (currentLine &&
|
|
52
|
+
(width == null || scaleToFit || (currentLine.width || 0) + item.width + spaceWidth < width)) {
|
|
53
|
+
// Word can be added to an existing line
|
|
54
|
+
currentLine.words.push(item.word);
|
|
55
|
+
currentLine.width = currentLine.width || 0;
|
|
56
|
+
currentLine.width += item.width + spaceWidth;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Add first word to line or word is too long to scaleToFit on existing line
|
|
60
|
+
const newLine = { words: [item.word], width: item.width };
|
|
61
|
+
result.push(newLine);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}, []);
|
|
65
|
+
$: lines = wordsByLines.length;
|
|
66
|
+
/**
|
|
67
|
+
* Convert css value to pixel value (ex. 0.71em => 11.36)
|
|
68
|
+
*/
|
|
69
|
+
function getPixelValue(cssValue) {
|
|
70
|
+
// TODO: Properly measure pixel values using DOM (handle inherited font size, zoom, etc)
|
|
71
|
+
// console.log(cssValue);
|
|
72
|
+
const [match, value, units] = cssValue.match(/([\d.]+)(\D+)/);
|
|
73
|
+
// console.log({ value, units });
|
|
74
|
+
const number = Number(value);
|
|
75
|
+
switch (units) {
|
|
76
|
+
case 'px':
|
|
77
|
+
return number;
|
|
78
|
+
case 'em':
|
|
79
|
+
case 'rem':
|
|
80
|
+
return number * 16;
|
|
81
|
+
default:
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let startDy = 0;
|
|
86
|
+
$: if (verticalAnchor === 'start') {
|
|
87
|
+
startDy = getPixelValue(capHeight);
|
|
88
|
+
}
|
|
89
|
+
else if (verticalAnchor === 'middle') {
|
|
90
|
+
startDy = ((lines - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
startDy = (lines - 1) * -getPixelValue(lineHeight);
|
|
94
|
+
}
|
|
95
|
+
let scaleTransform = '';
|
|
96
|
+
$: if (scaleToFit &&
|
|
97
|
+
lines > 0 &&
|
|
98
|
+
typeof x == 'number' &&
|
|
99
|
+
typeof y == 'number' &&
|
|
100
|
+
typeof width == 'number') {
|
|
101
|
+
const lineWidth = wordsByLines[0].width || 1;
|
|
102
|
+
const sx = width / lineWidth;
|
|
103
|
+
const sy = sx;
|
|
104
|
+
const originX = x - sx * x;
|
|
105
|
+
const originY = y - sy * y;
|
|
106
|
+
scaleTransform = `matrix(${sx}, 0, 0, ${sy}, ${originX}, ${originY})`;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
scaleTransform = '';
|
|
110
|
+
}
|
|
111
|
+
$: rotateTransform = rotate ? `rotate(${rotate}, ${x}, ${y})` : '';
|
|
112
|
+
$: transform = `${scaleTransform} ${rotateTransform}`;
|
|
113
|
+
function isValidXOrY(xOrY) {
|
|
114
|
+
return (
|
|
115
|
+
// number that is not NaN or Infinity
|
|
116
|
+
(typeof xOrY === 'number' && Number.isFinite(xOrY)) ||
|
|
117
|
+
// for percentage
|
|
118
|
+
typeof xOrY === 'string');
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<!-- overflow: visible; allow contents to be shown outside element -->
|
|
123
|
+
<!-- paint-order: stroke; support stroke outlining text -->
|
|
124
|
+
<svg x={dx} y={dy} style="overflow: visible; paint-order: stroke;">
|
|
125
|
+
{#if isValidXOrY(x) && isValidXOrY(y)}
|
|
126
|
+
<text {x} {y} {transform} text-anchor={textAnchor} {...$$restProps}>
|
|
127
|
+
{#each wordsByLines as line, index}
|
|
128
|
+
<tspan {x} dy={index === 0 ? startDy : lineHeight}>
|
|
129
|
+
{line.words.join(' ')}
|
|
130
|
+
</tspan>
|
|
131
|
+
{/each}
|
|
132
|
+
</text>
|
|
133
|
+
{/if}
|
|
134
|
+
</svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
[x: string]: any;
|
|
5
|
+
value?: string | number;
|
|
6
|
+
width?: number;
|
|
7
|
+
x?: string | number;
|
|
8
|
+
y?: string | number;
|
|
9
|
+
dx?: string | number;
|
|
10
|
+
dy?: string | number;
|
|
11
|
+
lineHeight?: string;
|
|
12
|
+
capHeight?: string;
|
|
13
|
+
scaleToFit?: boolean;
|
|
14
|
+
textAnchor?: 'start' | 'middle' | 'end' | 'inherit';
|
|
15
|
+
verticalAnchor?: 'start' | 'middle' | 'end' | 'inherit';
|
|
16
|
+
rotate?: number;
|
|
17
|
+
};
|
|
18
|
+
events: {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
};
|
|
21
|
+
slots: {};
|
|
22
|
+
};
|
|
23
|
+
export declare type Text2Props = typeof __propDef.props;
|
|
24
|
+
export declare type Text2Events = typeof __propDef.events;
|
|
25
|
+
export declare type Text2Slots = typeof __propDef.slots;
|
|
26
|
+
export default class Text2 extends SvelteComponentTyped<Text2Props, Text2Events, Text2Slots> {
|
|
27
|
+
}
|
|
28
|
+
export {};
|
package/components/Zoom.svelte
CHANGED
|
@@ -3,6 +3,7 @@ import { motionStore } from '../stores/motionStore';
|
|
|
3
3
|
const { width, height, padding } = getContext('LayerCake');
|
|
4
4
|
export let spring = undefined;
|
|
5
5
|
export let tweened = undefined;
|
|
6
|
+
export let disablePointer = false;
|
|
6
7
|
let dragging = false;
|
|
7
8
|
const translate = motionStore({ x: 0, y: 0 }, { spring, tweened });
|
|
8
9
|
const scale = motionStore({ x: 1, y: 1 }, { spring, tweened });
|
|
@@ -44,6 +45,8 @@ export function zoomTo(newTranslate, newScale) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
function handleMouseDown(e) {
|
|
48
|
+
if (disablePointer)
|
|
49
|
+
return;
|
|
47
50
|
dragging = true;
|
|
48
51
|
svgEl = e.target.ownerSVGElement;
|
|
49
52
|
startPoint = localPoint(svgEl, e);
|
|
@@ -68,9 +71,13 @@ function handleMouseMove(e) {
|
|
|
68
71
|
}, spring ? { hard: true } : tweened ? { duration: 0 } : undefined);
|
|
69
72
|
}
|
|
70
73
|
function handleDoubleClick() {
|
|
74
|
+
if (disablePointer)
|
|
75
|
+
return;
|
|
71
76
|
increase();
|
|
72
77
|
}
|
|
73
78
|
function handleWheel(e) {
|
|
79
|
+
if (disablePointer)
|
|
80
|
+
return;
|
|
74
81
|
e.preventDefault();
|
|
75
82
|
const scaleBy = -e.deltaY > 0 ? 1.1 : 0.9;
|
|
76
83
|
// TODO: Update to match d3-zoom delta
|
|
@@ -107,16 +114,20 @@ $: newTranslate = {
|
|
|
107
114
|
};
|
|
108
115
|
</script>
|
|
109
116
|
|
|
110
|
-
<
|
|
111
|
-
x={-$padding.left}
|
|
112
|
-
y={-$padding.top}
|
|
113
|
-
width={$width + $padding.left + $padding.right}
|
|
114
|
-
height={$height + $padding.top + $padding.bottom}
|
|
117
|
+
<g
|
|
115
118
|
on:mousewheel={handleWheel}
|
|
116
119
|
on:mousedown={handleMouseDown}
|
|
117
120
|
on:dblclick={handleDoubleClick}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
121
|
+
on:click
|
|
122
|
+
>
|
|
123
|
+
<rect
|
|
124
|
+
x={-$padding.left}
|
|
125
|
+
y={-$padding.top}
|
|
126
|
+
width={$width + $padding.left + $padding.right}
|
|
127
|
+
height={$height + $padding.top + $padding.bottom}
|
|
128
|
+
fill="transparent"
|
|
129
|
+
/>
|
|
130
|
+
<g transform="translate({newTranslate.x},{newTranslate.y}) scale({$scale.x},{$scale.y})">
|
|
131
|
+
<slot scale={$scale} />
|
|
132
|
+
</g>
|
|
122
133
|
</g>
|
|
@@ -4,6 +4,7 @@ declare const __propDef: {
|
|
|
4
4
|
props: {
|
|
5
5
|
spring?: boolean | Parameters<typeof motionStore>[1]['spring'];
|
|
6
6
|
tweened?: boolean | Parameters<typeof motionStore>[1]['tweened'];
|
|
7
|
+
disablePointer?: boolean;
|
|
7
8
|
reset?: () => void;
|
|
8
9
|
increase?: () => void;
|
|
9
10
|
decrease?: () => void;
|
|
@@ -17,6 +18,8 @@ declare const __propDef: {
|
|
|
17
18
|
}) => void;
|
|
18
19
|
};
|
|
19
20
|
events: {
|
|
21
|
+
click: MouseEvent;
|
|
22
|
+
} & {
|
|
20
23
|
[evt: string]: CustomEvent<any>;
|
|
21
24
|
};
|
|
22
25
|
slots: {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"author": "Sean Lynch <techniq35@gmail.com>",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "techniq/layerchart",
|
|
6
|
-
"version": "0.6.
|
|
6
|
+
"version": "0.6.4",
|
|
7
7
|
"devDependencies": {
|
|
8
8
|
"@rollup/plugin-dsv": "^2.0.3",
|
|
9
9
|
"@sveltejs/adapter-vercel": "^1.0.0-next.58",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"./components/RectClipPath.svelte": "./components/RectClipPath.svelte",
|
|
81
81
|
"./components/Sankey.svelte": "./components/Sankey.svelte",
|
|
82
82
|
"./components/Text.svelte": "./components/Text.svelte",
|
|
83
|
+
"./components/Text2.svelte": "./components/Text2.svelte",
|
|
83
84
|
"./components/Threshold.svelte": "./components/Threshold.svelte",
|
|
84
85
|
"./components/Tooltip.svelte": "./components/Tooltip.svelte",
|
|
85
86
|
"./components/Tree.svelte": "./components/Tree.svelte",
|
package/utils/index.d.ts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
+
export { graphFromCsv, graphFromHierarchy, graphFromNode } from './graph';
|
|
2
|
+
export { findAncestor } from './hierarchy';
|
|
3
|
+
export { degreesToRadians, radiansToDegrees } from './math';
|
|
4
|
+
export { createStackData, stackOffsetSeparated } from './stack';
|
|
1
5
|
export { getMajorTicks, getMinorTicks } from './ticks';
|
package/utils/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
+
export { graphFromCsv, graphFromHierarchy, graphFromNode } from './graph';
|
|
2
|
+
export { findAncestor } from './hierarchy';
|
|
3
|
+
export { degreesToRadians, radiansToDegrees } from './math';
|
|
4
|
+
export { createStackData, stackOffsetSeparated } from './stack';
|
|
1
5
|
export { getMajorTicks, getMinorTicks } from './ticks';
|
package/utils/stack.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { stackOffsetNone } from 'd3-shape';
|
|
1
|
+
import { stackOffsetNone, stackOrderNone } from 'd3-shape';
|
|
2
|
+
declare type OrderType = typeof stackOrderNone;
|
|
2
3
|
declare type OffsetType = typeof stackOffsetNone;
|
|
3
4
|
export declare function createStackData(data: any[], options: {
|
|
4
5
|
xKey: string;
|
|
5
6
|
groupBy?: string;
|
|
6
7
|
stackBy?: string;
|
|
8
|
+
order?: OrderType;
|
|
7
9
|
offset?: OffsetType;
|
|
8
10
|
}): any[];
|
|
9
11
|
/**
|
package/utils/stack.js
CHANGED
|
@@ -10,7 +10,7 @@ export function createStackData(data, options) {
|
|
|
10
10
|
const itemData = d.slice(-1)[0]; // last item
|
|
11
11
|
const pivotData = pivotWider(itemData, options.xKey, options.stackBy, 'value');
|
|
12
12
|
const stackKeys = [...new Set(itemData.map((d) => d[options.stackBy]))];
|
|
13
|
-
const stackData = stack().keys(stackKeys).offset(options.offset)(pivotData);
|
|
13
|
+
const stackData = stack().keys(stackKeys).order(options.order).offset(options.offset)(pivotData);
|
|
14
14
|
//console.log({ pivotData, stackData })
|
|
15
15
|
return stackData.flatMap((series) => {
|
|
16
16
|
//console.log({ series })
|
|
@@ -29,7 +29,7 @@ export function createStackData(data, options) {
|
|
|
29
29
|
// Stack only
|
|
30
30
|
const pivotData = pivotWider(data, options.xKey, options.stackBy, 'value');
|
|
31
31
|
const stackKeys = [...new Set(data.map((d) => d[options.stackBy]))];
|
|
32
|
-
const stackData = stack().keys(stackKeys).offset(options.offset)(pivotData);
|
|
32
|
+
const stackData = stack().keys(stackKeys).order(options.order).offset(options.offset)(pivotData);
|
|
33
33
|
const result = stackData.flatMap((series) => {
|
|
34
34
|
return series.flatMap((s) => {
|
|
35
35
|
return {
|