layerchart 0.6.2 → 0.6.5

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.
@@ -20,10 +20,7 @@ $: tickVals = Array.isArray(ticks)
20
20
 
21
21
  <g class="axis y-axis" transform="translate({-$padding.left}, 0)">
22
22
  {#each tickVals as tick, i}
23
- <g
24
- class="tick tick-{tick}"
25
- transform="translate({$xRange[0] + (isBand ? $padding.left : 0)}, {$yScale(tick)})"
26
- >
23
+ <g class="tick tick-{tick}" transform="translate({$xRange[0]}, {$yScale(tick)})">
27
24
  {#if gridlines !== false}
28
25
  <line
29
26
  x1={$padding.left}
@@ -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: {
@@ -1,4 +1,5 @@
1
- <script>import { getContext } from 'svelte';
1
+ <script>import { isScaleBand } from '../utils/scales';
2
+ import { getContext } from 'svelte';
2
3
  import { get } from 'svelte/store';
3
4
  import Circle from './Circle.svelte';
4
5
  import Line from './Line.svelte';
@@ -10,33 +11,68 @@ $: x = $xGet(data);
10
11
  function getColor(index) {
11
12
  return color ?? get(zScale)(index) ?? 'var(--color-blue-500)';
12
13
  }
13
- $: points = Array.isArray(data)
14
- ? // Stack series
15
- data.map((yValue, i) => ({
16
- x,
17
- y: $yScale(yValue),
18
- color: getColor(i)
19
- }))
20
- : [
14
+ let lines = [];
15
+ $: if (Array.isArray(x)) {
16
+ // `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
17
+ lines = x.map((xItem, i) => ({
18
+ x1: xItem,
19
+ y1: 0,
20
+ x2: xItem,
21
+ y2: $yRange[0]
22
+ }));
23
+ }
24
+ else {
25
+ lines = [
26
+ {
27
+ x1: x,
28
+ y1: 0,
29
+ x2: x,
30
+ y2: $yRange[0]
31
+ }
32
+ ];
33
+ }
34
+ let points = [];
35
+ $: yOffset = isScaleBand($yScale) ? $yScale.bandwidth() / 2 : 0;
36
+ $: if (Array.isArray(x)) {
37
+ // `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
38
+ points = x.map((xItem, i) => ({
39
+ x: xItem,
40
+ y: $yGet(data) + yOffset,
41
+ color: getColor(i)
42
+ }));
43
+ }
44
+ else if (Array.isArray(data)) {
45
+ // Stack series
46
+ points = data.map((yValue, i) => ({
47
+ x,
48
+ y: $yScale(yValue) + yOffset,
49
+ color: getColor(i)
50
+ }));
51
+ }
52
+ else {
53
+ points = [
21
54
  {
22
55
  x,
23
- y: $yGet(data) - $padding.top,
56
+ y: $yGet(data) + yOffset,
24
57
  color: getColor(0)
25
58
  }
26
59
  ];
60
+ }
27
61
  </script>
28
62
 
29
- <Line
30
- spring
31
- x1={x}
32
- y1={0}
33
- x2={x}
34
- y2={$yRange[0]}
35
- stroke="rgba(0,0,0,.5)"
36
- stroke-width={2}
37
- style="pointerEvents: none"
38
- stroke-dasharray="2,2"
39
- />
63
+ {#each lines as line}
64
+ <Line
65
+ spring
66
+ x1={line.x1}
67
+ y1={line.y1}
68
+ x2={line.x2}
69
+ y2={line.y2}
70
+ stroke="rgba(0,0,0,.5)"
71
+ stroke-width={2}
72
+ style="pointerEvents: none"
73
+ stroke-dasharray="2,2"
74
+ />
75
+ {/each}
40
76
 
41
77
  {#each points as point}
42
78
  <Circle
@@ -1,22 +1,30 @@
1
1
  <script>import { getContext } from 'svelte';
2
+ import { max, min } from 'd3-array';
2
3
  import { isScaleBand } from '../utils/scales';
3
4
  import Rect from './Rect.svelte';
4
5
  export let data;
5
6
  const { flatData, xScale, x, xGet, yRange, padding } = getContext('LayerCake');
6
7
  $: isBand = isScaleBand($xScale);
8
+ $: xCoord = $xGet(data);
7
9
  let width = 0;
8
10
  $: if (isBand) {
9
11
  width = $xScale.step();
10
12
  }
13
+ else if (Array.isArray(xCoord)) {
14
+ // `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
15
+ // Use first/last values for width
16
+ width = max(xCoord) - min(xCoord);
17
+ xCoord = min(xCoord); // Use left-most value for top left of rect
18
+ }
11
19
  else {
12
20
  // Find width to next data point
13
21
  let index = $flatData.findIndex((d) => Number($x(d)) === Number($x(data)));
14
22
  let nextDataPoint = $x($flatData[index + 1]);
15
- width = ($xScale(nextDataPoint) ?? 0) - ($xGet(data) ?? 0);
23
+ width = ($xScale(nextDataPoint) ?? 0) - (xCoord ?? 0);
16
24
  }
17
25
  $: dimensions = {
18
- x: $xGet(data) - (isBand ? ($xScale.padding() * $xScale.step()) / 2 : 0),
19
- y: -$padding.top,
26
+ x: xCoord - (isBand ? ($xScale.padding() * $xScale.step()) / 2 : 0),
27
+ y: 0,
20
28
  width,
21
29
  height: $yRange[0]
22
30
  };
@@ -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,43 @@
1
+ <script>import '$lib/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
+ /** x position of the text. */
16
+ export let x = 0;
17
+ /** y position of the text. */
18
+ export let y = 0;
19
+ /** dx offset of the text. */
20
+ export let dx = 0;
21
+ /** dy offset of the text. */
22
+ export let dy = 0;
23
+ /** Horizontal text anchor. */
24
+ export let textAnchor = 'start';
25
+ /** Vertical text anchor. */
26
+ export let verticalAnchor = 'end'; // default SVG behavior
27
+ /** Rotational angle of the text. */
28
+ export let rotate = undefined;
29
+ </script>
30
+
31
+ <!-- overflow: visible; allow contents to be shown outside element -->
32
+ <!-- paint-order: stroke; support stroke outlining text -->
33
+ <!-- <svg x={dx} y={dy} style="overflow: visible; paint-order: stroke;">
34
+ {#if isValidXOrY(x) && isValidXOrY(y)}
35
+ <text {x} {y} {transform} text-anchor={textAnchor} {...$$restProps}>
36
+ {#each wordsByLines as line, index}
37
+ <tspan {x} dy={index === 0 ? startDy : lineHeight}>
38
+ {line.words.join(' ')}
39
+ </tspan>
40
+ {/each}
41
+ </text>
42
+ {/if}
43
+ </svg> -->
@@ -0,0 +1,22 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ /** x position of the text. */ x?: string | number;
5
+ /** y position of the text. */ y?: string | number;
6
+ /** dx offset of the text. */ dx?: string | number;
7
+ /** dy offset of the text. */ dy?: string | number;
8
+ /** Horizontal text anchor. */ textAnchor?: 'start' | 'middle' | 'end' | 'inherit';
9
+ /** Vertical text anchor. */ verticalAnchor?: 'start' | 'middle' | 'end' | 'inherit';
10
+ /** Rotational angle of the text. */ rotate?: number;
11
+ };
12
+ events: {
13
+ [evt: string]: CustomEvent<any>;
14
+ };
15
+ slots: {};
16
+ };
17
+ export declare type Text2Props = typeof __propDef.props;
18
+ export declare type Text2Events = typeof __propDef.events;
19
+ export declare type Text2Slots = typeof __propDef.slots;
20
+ export default class Text2 extends SvelteComponentTyped<Text2Props, Text2Events, Text2Slots> {
21
+ }
22
+ export {};
@@ -46,21 +46,43 @@ function handleTooltip(event) {
46
46
  const localY = point?.y ?? 0;
47
47
  let tooltipData;
48
48
  if (isScaleBand($xScale)) {
49
- // `x` value at mouse coordinate
50
- const xValue = scaleBandInvert($xScale)(localX);
51
- tooltipData = $flatData.find((d) => $x(d) === xValue);
49
+ // `x` value at mouse/touch coordinate
50
+ const valueAtPoint = scaleBandInvert($xScale)(localX);
51
+ tooltipData = $flatData.find((d) => $x(d) === valueAtPoint);
52
52
  }
53
53
  else {
54
- // `x` value at mouse coordinate
55
- const xValue = $xScale.invert(localX);
56
- const bisectX = bisector($x).left;
57
- const index = bisectX($flatData, xValue, 1);
54
+ // `x` value at mouse/touch coordinate
55
+ const valueAtPoint = $xScale.invert(localX);
56
+ const bisectX = bisector((d) => {
57
+ const value = $x(d);
58
+ if (Array.isArray(value)) {
59
+ // `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
60
+ // Using first value. Consider using average, max, etc
61
+ // const midpoint = new Date((value[1].valueOf() + value[0].getTime()) / 2);
62
+ // return midpoint;
63
+ return value[0];
64
+ }
65
+ else {
66
+ return value;
67
+ }
68
+ }).left;
69
+ const index = bisectX($flatData, valueAtPoint, 1);
58
70
  const data0 = $flatData[index - 1];
59
71
  const data1 = $flatData[index];
60
72
  switch (findTooltipData) {
61
73
  case 'closest':
62
- tooltipData =
63
- Number(xValue) - Number($x(data0)) > Number($x(data1)) - Number(xValue) ? data1 : data0;
74
+ if (data1 === undefined) {
75
+ tooltipData = data0;
76
+ }
77
+ else if (data0 === undefined) {
78
+ tooltipData = data1;
79
+ }
80
+ else {
81
+ tooltipData =
82
+ Number(valueAtPoint) - Number($x(data0)) > Number($x(data1)) - Number(valueAtPoint)
83
+ ? data1
84
+ : data0;
85
+ }
64
86
  break;
65
87
  case 'left':
66
88
  tooltipData = data0;
@@ -114,16 +114,20 @@ $: newTranslate = {
114
114
  };
115
115
  </script>
116
116
 
117
- <rect
118
- x={-$padding.left}
119
- y={-$padding.top}
120
- width={$width + $padding.left + $padding.right}
121
- height={$height + $padding.top + $padding.bottom}
117
+ <g
122
118
  on:mousewheel={handleWheel}
123
119
  on:mousedown={handleMouseDown}
124
120
  on:dblclick={handleDoubleClick}
125
- fill="transparent"
126
- />
127
- <g transform="translate({newTranslate.x},{newTranslate.y}) scale({$scale.x},{$scale.y})">
128
- <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>
129
133
  </g>
@@ -18,6 +18,8 @@ declare const __propDef: {
18
18
  }) => void;
19
19
  };
20
20
  events: {
21
+ click: MouseEvent;
22
+ } & {
21
23
  [evt: string]: CustomEvent<any>;
22
24
  };
23
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.2",
6
+ "version": "0.6.5",
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/genData.js CHANGED
@@ -1,4 +1,4 @@
1
- import { subDays } from 'date-fns';
1
+ import { startOfToday, subDays } from 'date-fns';
2
2
  import { degreesToRadians, radiansToDegrees } from './math';
3
3
  /**
4
4
  * Get random number between min (inclusive) and max (exclusive)
@@ -17,7 +17,7 @@ export function getRandomInteger(min, max, includeMax = true) {
17
17
  return Math.floor(Math.random() * (max - min + (includeMax ? 1 : 0)) + min);
18
18
  }
19
19
  export function createDateSeries(options) {
20
- const now = new Date();
20
+ const now = startOfToday();
21
21
  const count = options.count ?? 10;
22
22
  const min = options.min;
23
23
  const max = options.max;
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/scales.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { tweened } from 'svelte/motion';
1
+ import { tweened, spring } from 'svelte/motion';
2
2
  import { MotionOptions } from '../stores/motionStore';
3
3
  /**
4
4
  * Implemenation for missing `scaleBand().invert()`
@@ -19,7 +19,15 @@ export declare function tweenedScale(scale: any, tweenedOptions?: Parameters<typ
19
19
  range: (values: any) => Promise<void>;
20
20
  };
21
21
  /**
22
- * Create a store wrapper around a d3-scale which interpolates the domain and/or range using `tweened()` or `spring()` stores. Fallbacks to `writable()` if not interpolating
22
+ * Animate d3-scale as domain and/or range are updated using spring store
23
+ */
24
+ export declare function springScale(scale: any, springOptions?: Parameters<typeof spring>[1]): {
25
+ subscribe: (this: void, run: import("svelte/store").Subscriber<any>, invalidate?: (value?: any) => void) => import("svelte/store").Unsubscriber;
26
+ domain: (values: any) => Promise<void>;
27
+ range: (values: any) => Promise<void>;
28
+ };
29
+ /**
30
+ * Create a store wrapper around a d3-scale which interpolates the domain and/or range using `tweened()` or `spring()` stores. Fallbacks to `writable()` store if not interpolating
23
31
  */
24
32
  export declare function motionScale(scale: any, options: MotionOptions): {
25
33
  subscribe: (this: void, run: import("svelte/store").Subscriber<any>, invalidate?: (value?: any) => void) => import("svelte/store").Unsubscriber;
package/utils/scales.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { derived } from 'svelte/store';
2
- import { tweened } from 'svelte/motion';
2
+ import { tweened, spring } from 'svelte/motion';
3
3
  import { motionStore } from '../stores/motionStore';
4
4
  /**
5
5
  * Implemenation for missing `scaleBand().invert()`
@@ -45,7 +45,29 @@ export function tweenedScale(scale, tweenedOptions = {}) {
45
45
  };
46
46
  }
47
47
  /**
48
- * Create a store wrapper around a d3-scale which interpolates the domain and/or range using `tweened()` or `spring()` stores. Fallbacks to `writable()` if not interpolating
48
+ * Animate d3-scale as domain and/or range are updated using spring store
49
+ */
50
+ export function springScale(scale, springOptions = {}) {
51
+ const domainStore = spring(undefined, springOptions);
52
+ const rangeStore = spring(undefined, springOptions);
53
+ const tweenedScale = derived([domainStore, rangeStore], ([domain, range]) => {
54
+ const scaleInstance = scale.domain ? scale : scale(); // support `scaleLinear` or `scaleLinear()` (which could have `.interpolate()` and others set)
55
+ if (domain) {
56
+ scaleInstance.domain(domain);
57
+ }
58
+ if (range) {
59
+ scaleInstance.range(range);
60
+ }
61
+ return scaleInstance;
62
+ });
63
+ return {
64
+ subscribe: tweenedScale.subscribe,
65
+ domain: (values) => domainStore.set(values),
66
+ range: (values) => rangeStore.set(values)
67
+ };
68
+ }
69
+ /**
70
+ * Create a store wrapper around a d3-scale which interpolates the domain and/or range using `tweened()` or `spring()` stores. Fallbacks to `writable()` store if not interpolating
49
71
  */
50
72
  export function motionScale(scale, options) {
51
73
  const domainStore = motionStore(undefined, options);
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 {