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.
@@ -8,6 +8,7 @@ const { width, height, padding } = getContext('LayerCake');
8
8
  y={-$padding.top}
9
9
  width={$width + $padding.left + $padding.right}
10
10
  height={$height + $padding.top + $padding.bottom}
11
+ on:click
11
12
  {...$$restProps}
12
13
  >
13
14
  <slot />
@@ -4,6 +4,8 @@ declare const __propDef: {
4
4
  [x: string]: any;
5
5
  };
6
6
  events: {
7
+ click: MouseEvent;
8
+ } & {
7
9
  [evt: string]: CustomEvent<any>;
8
10
  };
9
11
  slots: {
@@ -15,7 +15,7 @@ export let tweened = undefined;
15
15
  </ClipPath>
16
16
 
17
17
  {#if $$slots.default}
18
- <g style="clip-path: url(#{id})">
18
+ <g style="clip-path: url(#{id})" on:click>
19
19
  <slot {id} />
20
20
  </g>
21
21
  {/if}
@@ -10,6 +10,8 @@ declare const __propDef: {
10
10
  tweened?: boolean | Parameters<typeof tweenedStore>[1];
11
11
  };
12
12
  events: {
13
+ click: MouseEvent;
14
+ } & {
13
15
  [evt: string]: CustomEvent<any>;
14
16
  };
15
17
  slots: {
@@ -16,7 +16,7 @@ export let tweened = undefined;
16
16
  </ClipPath>
17
17
 
18
18
  {#if $$slots.default}
19
- <g style="clip-path: url(#{id})">
19
+ <g style="clip-path: url(#{id})" on:click>
20
20
  <slot {id} />
21
21
  </g>
22
22
  {/if}
@@ -11,6 +11,8 @@ declare const __propDef: {
11
11
  tweened?: boolean | Parameters<typeof tweenedStore>[1];
12
12
  };
13
13
  events: {
14
+ click: MouseEvent;
15
+ } & {
14
16
  [evt: string]: CustomEvent<any>;
15
17
  };
16
18
  slots: {
@@ -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 {};
@@ -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
- <rect
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
- fill="transparent"
119
- />
120
- <g transform="translate({newTranslate.x},{newTranslate.y}) scale({$scale.x},{$scale.y})">
121
- <slot scale={$scale} />
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.1",
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 {