layerchart 0.59.5 → 0.60.0

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.
@@ -0,0 +1,151 @@
1
+ <script module>
2
+ export type DagreGraphData = {
3
+ nodes: Array<{ id: string; parent?: string; label?: string | dagre.Label }>;
4
+ edges: Array<{ source: string; target: string; label?: string }>;
5
+ };
6
+
7
+ export const RankDir = {
8
+ 'top-bottom': 'TB',
9
+ 'bottom-top': 'BT',
10
+ 'left-right': 'LR',
11
+ 'right-left': 'RL',
12
+ };
13
+
14
+ export const Align = {
15
+ none: undefined,
16
+ 'up-left': 'UL',
17
+ 'up-right': 'UR',
18
+ 'down-left': 'DL',
19
+ 'down-right': 'DR',
20
+ };
21
+
22
+ export const EdgeLabelPosition = {
23
+ left: 'l',
24
+ center: 'c',
25
+ right: 'r',
26
+ };
27
+ </script>
28
+
29
+ <script lang="ts">
30
+ import dagre, { type Edge, type EdgeConfig, type GraphEdge } from '@dagrejs/dagre';
31
+
32
+ /** Data of nodes and edges to build graph */
33
+ export let data: DagreGraphData;
34
+
35
+ export let nodes = (d: any) => d.nodes;
36
+ export let nodeId = (d: any) => d.id;
37
+ export let edges = (d: any) => d.edges;
38
+
39
+ /** Set graph as directed (true, default) or undirected (false), which does not treat the order of nodes in an edge as significant. */
40
+ export let directed = true;
41
+
42
+ /** Allow a graph to have multiple edges between the same pair of nodes */
43
+ export let multigraph = false;
44
+
45
+ /** Allow a graph to have compound nodes - nodes which can be the `parent` of other nodes */
46
+ export let compound = false;
47
+
48
+ /** Type of algorithm to assigns a rank to each node in the input graph */
49
+ export let ranker: 'network-simplex' | 'tight-tree' | 'longest-path' = 'network-simplex';
50
+
51
+ /** Direction for rank nodes */
52
+ export let direction: keyof typeof RankDir = 'top-bottom';
53
+
54
+ /** Alignment for rank nodes */
55
+ export let align: keyof typeof Align | undefined = undefined;
56
+
57
+ /** Number of pixels between each rank in the layout */
58
+ export let rankSeparation = 50;
59
+
60
+ /** Number of pixels that separate nodes horizontally in the layout */
61
+ export let nodeSeparation = 50;
62
+
63
+ /** Number of pixels that separate edges horizontally in the layout */
64
+ export let edgeSeparation = 10;
65
+
66
+ /** Default node width if not defined on node */
67
+ export let nodeWidth = 100;
68
+
69
+ /** Default node height if not defined on node */
70
+ export let nodeHeight = 50;
71
+
72
+ /** Default link label width if not defined on edge */
73
+ export let edgeLabelWidth = 100;
74
+
75
+ /** Default edge label height if not defined on edge */
76
+ export let edgeLabelHeight = 20;
77
+
78
+ /** Default edge label height if not defined on edge */
79
+ export let edgeLabelPosition: keyof typeof EdgeLabelPosition = 'center';
80
+
81
+ /** Default pixels to move the label away from the edge if not defined on edge. Applies only when labelpos is l or r.*/
82
+ export let edgeLabelOffset = 10;
83
+
84
+ /** Filter nodes */
85
+ export let filterNodes: (nodeId: string, graph: dagre.graphlib.Graph) => boolean = () => true;
86
+
87
+ let graph: dagre.graphlib.Graph;
88
+ $: {
89
+ let g = new dagre.graphlib.Graph({ directed, multigraph, compound });
90
+
91
+ g.setGraph({
92
+ ranker: ranker,
93
+ rankdir: RankDir[direction],
94
+ align: align ? Align[align] : undefined,
95
+ ranksep: rankSeparation,
96
+ nodesep: nodeSeparation,
97
+ edgesep: edgeSeparation,
98
+ });
99
+
100
+ g.setDefaultEdgeLabel(() => {
101
+ return {};
102
+ });
103
+
104
+ nodes(data).forEach((n: any) => {
105
+ const id = nodeId(n);
106
+
107
+ g.setNode(nodeId(n), {
108
+ id,
109
+ label: typeof n.label === 'string' ? n.label : id,
110
+ width: nodeWidth,
111
+ height: nodeHeight,
112
+ ...(typeof n.label === 'object' ? n.label : null),
113
+ });
114
+
115
+ if (n.parent) {
116
+ g.setParent(id, n.parent);
117
+ }
118
+ });
119
+
120
+ edges(data).forEach((e: any) => {
121
+ const { source, target, label, ...rest } = e;
122
+ g.setEdge(
123
+ e.source,
124
+ e.target,
125
+ label
126
+ ? {
127
+ label: label,
128
+ labelpos: EdgeLabelPosition[edgeLabelPosition],
129
+ labeloffset: edgeLabelOffset,
130
+ width: edgeLabelWidth,
131
+ height: edgeLabelHeight,
132
+ ...rest,
133
+ }
134
+ : {}
135
+ );
136
+ });
137
+
138
+ g = filterNodes ? g.filterNodes((nodeId) => filterNodes(nodeId, graph)) : graph;
139
+
140
+ dagre.layout(g);
141
+
142
+ graph = g;
143
+ }
144
+
145
+ $: graphNodes = graph.nodes().map((id) => graph.node(id));
146
+ $: graphEdges = graph.edges().map((edge) => ({ ...edge, ...graph.edge(edge) })) as Array<
147
+ Edge & EdgeConfig & GraphEdge // `EdgeConfig` is excluded when inferred from usage
148
+ >;
149
+ </script>
150
+
151
+ <slot nodes={graphNodes} edges={graphEdges} />
@@ -0,0 +1,85 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ export type DagreGraphData = {
3
+ nodes: Array<{
4
+ id: string;
5
+ parent?: string;
6
+ label?: string | dagre.Label;
7
+ }>;
8
+ edges: Array<{
9
+ source: string;
10
+ target: string;
11
+ label?: string;
12
+ }>;
13
+ };
14
+ export declare const RankDir: {
15
+ 'top-bottom': string;
16
+ 'bottom-top': string;
17
+ 'left-right': string;
18
+ 'right-left': string;
19
+ };
20
+ export declare const Align: {
21
+ none: undefined;
22
+ 'up-left': string;
23
+ 'up-right': string;
24
+ 'down-left': string;
25
+ 'down-right': string;
26
+ };
27
+ export declare const EdgeLabelPosition: {
28
+ left: string;
29
+ center: string;
30
+ right: string;
31
+ };
32
+ import dagre from '@dagrejs/dagre';
33
+ declare const __propDef: {
34
+ props: {
35
+ /** Data of nodes and edges to build graph */ data: DagreGraphData;
36
+ nodes?: (d: any) => any;
37
+ nodeId?: (d: any) => any;
38
+ edges?: (d: any) => any;
39
+ /** Set graph as directed (true, default) or undirected (false), which does not treat the order of nodes in an edge as significant. */ directed?: boolean;
40
+ /** Allow a graph to have multiple edges between the same pair of nodes */ multigraph?: boolean;
41
+ /** Allow a graph to have compound nodes - nodes which can be the `parent` of other nodes */ compound?: boolean;
42
+ /** Type of algorithm to assigns a rank to each node in the input graph */ ranker?: "network-simplex" | "tight-tree" | "longest-path";
43
+ /** Direction for rank nodes */ direction?: keyof typeof RankDir;
44
+ /** Alignment for rank nodes */ align?: keyof typeof Align | undefined;
45
+ /** Number of pixels between each rank in the layout */ rankSeparation?: number;
46
+ /** Number of pixels that separate nodes horizontally in the layout */ nodeSeparation?: number;
47
+ /** Number of pixels that separate edges horizontally in the layout */ edgeSeparation?: number;
48
+ /** Default node width if not defined on node */ nodeWidth?: number;
49
+ /** Default node height if not defined on node */ nodeHeight?: number;
50
+ /** Default link label width if not defined on edge */ edgeLabelWidth?: number;
51
+ /** Default edge label height if not defined on edge */ edgeLabelHeight?: number;
52
+ /** Default edge label height if not defined on edge */ edgeLabelPosition?: keyof typeof EdgeLabelPosition;
53
+ /** Default pixels to move the label away from the edge if not defined on edge. Applies only when labelpos is l or r.*/ edgeLabelOffset?: number;
54
+ /** Filter nodes */ filterNodes?: (nodeId: string, graph: dagre.graphlib.Graph) => boolean;
55
+ };
56
+ events: {
57
+ [evt: string]: CustomEvent<any>;
58
+ };
59
+ slots: {
60
+ default: {
61
+ nodes: {
62
+ x: number;
63
+ y: number;
64
+ width: number;
65
+ height: number;
66
+ class?: string | undefined;
67
+ label?: string | undefined;
68
+ padding?: number | undefined;
69
+ paddingX?: number | undefined;
70
+ paddingY?: number | undefined;
71
+ rank?: number | undefined;
72
+ rx?: number | undefined;
73
+ ry?: number | undefined;
74
+ shape?: string | undefined;
75
+ }[];
76
+ edges: (dagre.Edge & dagre.EdgeConfig & dagre.GraphEdge)[];
77
+ };
78
+ };
79
+ };
80
+ export type DagreProps = typeof __propDef.props;
81
+ export type DagreEvents = typeof __propDef.events;
82
+ export type DagreSlots = typeof __propDef.slots;
83
+ export default class Dagre extends SvelteComponentTyped<DagreProps, DagreEvents, DagreSlots> {
84
+ }
85
+ export {};
@@ -49,10 +49,7 @@
49
49
  swatch?: string;
50
50
  } = {};
51
51
 
52
- $: if (scale == null && cScale) {
53
- // Read scale from chart context
54
- scale = $cScale;
55
- }
52
+ $: _scale = scale ?? (cScale ? $cScale : null);
56
53
 
57
54
  let xScale: AnyScale;
58
55
  let interpolator: ((t: number) => string) | undefined;
@@ -60,47 +57,47 @@
60
57
  let tickLabelOffset = 0;
61
58
  let tickLine = true;
62
59
 
63
- $: if (!scale) {
60
+ $: if (!_scale) {
64
61
  // do nothing
65
- } else if (scale.interpolate) {
62
+ } else if (_scale.interpolate) {
66
63
  // Continuous
67
- const n = Math.min(scale.domain().length, scale.range().length);
68
- xScale = scale.copy().rangeRound(quantize(interpolate(0, width), n));
69
- interpolator = scale.copy().domain(quantize(interpolate(0, 1), n));
64
+ const n = Math.min(_scale.domain().length, _scale.range().length);
65
+ xScale = _scale.copy().rangeRound(quantize(interpolate(0, width), n));
66
+ interpolator = _scale.copy().domain(quantize(interpolate(0, 1), n));
70
67
  tickFormat = tickFormat ?? xScale.tickFormat?.();
71
- } else if (scale.interpolator) {
68
+ } else if (_scale.interpolator) {
72
69
  // Sequential
73
- xScale = Object.assign(scale.copy().interpolator(interpolateRound(0, width)), {
70
+ xScale = Object.assign(_scale.copy().interpolator(interpolateRound(0, width)), {
74
71
  range() {
75
72
  return [0, width];
76
73
  },
77
74
  });
78
- interpolator = scale.interpolator();
75
+ interpolator = _scale.interpolator();
79
76
 
80
77
  // scaleSequentialQuantile doesn’t implement ticks or tickFormat.
81
78
  if (!xScale.ticks) {
82
79
  if (tickValues === undefined) {
83
80
  const n = Math.round(ticks + 1);
84
- tickValues = range(n).map((i) => quantile(scale.domain(), i / (n - 1)));
81
+ tickValues = range(n).map((i) => quantile(_scale.domain(), i / (n - 1)));
85
82
  }
86
83
  // if (typeof tickFormat !== "function") {
87
84
  // tickFormat = d3.format(tickFormat === undefined ? ",f" : tickFormat);
88
85
  // }
89
86
  }
90
87
  tickFormat = tickFormat ?? xScale.tickFormat?.();
91
- } else if (scale.invertExtent) {
88
+ } else if (_scale.invertExtent) {
92
89
  // Threshold
93
- const thresholds = scale.thresholds
94
- ? scale.thresholds() // scaleQuantize
95
- : scale.quantiles
96
- ? scale.quantiles() // scaleQuantile
97
- : scale.domain(); // scaleThreshold
90
+ const thresholds = _scale.thresholds
91
+ ? _scale.thresholds() // scaleQuantize
92
+ : _scale.quantiles
93
+ ? _scale.quantiles() // scaleQuantile
94
+ : _scale.domain(); // scaleThreshold
98
95
 
99
96
  xScale = scaleLinear()
100
- .domain([-1, scale.range().length - 1])
97
+ .domain([-1, _scale.range().length - 1])
101
98
  .rangeRound([0, width]);
102
99
 
103
- swatches = scale.range().map((d: any, i: number) => {
100
+ swatches = _scale.range().map((d: any, i: number) => {
104
101
  return {
105
102
  x: xScale(i - 1),
106
103
  y: 0,
@@ -117,19 +114,19 @@
117
114
  };
118
115
  } else {
119
116
  // Ordinal
120
- xScale = scaleBand().domain(scale.domain()).rangeRound([0, width]);
117
+ xScale = scaleBand().domain(_scale.domain()).rangeRound([0, width]);
121
118
 
122
- swatches = scale.domain().map((d: any) => {
119
+ swatches = _scale.domain().map((d: any) => {
123
120
  return {
124
121
  x: xScale(d),
125
122
  y: 0,
126
123
  width: Math.max(0, xScale.bandwidth() - 1),
127
124
  height,
128
- fill: scale(d),
125
+ fill: _scale(d),
129
126
  };
130
127
  });
131
128
 
132
- tickValues = scale.domain();
129
+ tickValues = _scale.domain();
133
130
  tickLabelOffset = xScale.bandwidth() / 2;
134
131
  tickLine = false;
135
132
  tickLength = 0;
@@ -159,7 +156,7 @@
159
156
  )}
160
157
  >
161
158
  <div class={cls('text-[10px] font-semibold', classes.title)}>{title}</div>
162
- <slot values={tickValues ?? []} {scale}>
159
+ <slot values={tickValues ?? []} scale={_scale}>
163
160
  {#if variant === 'ramp'}
164
161
  <svg
165
162
  {width}
@@ -210,7 +207,7 @@
210
207
  )}
211
208
  >
212
209
  {#each tickValues ?? xScale?.ticks?.(ticks) ?? [] as tick}
213
- {@const color = scale(tick)}
210
+ {@const color = _scale(tick)}
214
211
  <button
215
212
  class={cls('flex gap-1', !onClick && 'cursor-auto')}
216
213
  on:click={() => onClick?.({ tick, color })}
@@ -120,8 +120,6 @@
120
120
  if (curve) path.curve(curve);
121
121
 
122
122
  return path(data ?? $contextData);
123
- } else {
124
- return '';
125
123
  }
126
124
  }
127
125
 
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { type ComponentProps } from 'svelte';
2
3
  import { Button, Icon, MenuButton, Tooltip } from 'svelte-ux';
3
4
  import { cls } from '@layerstack/tailwind';
4
5
 
@@ -28,6 +29,7 @@
28
29
 
29
30
  export let placement: Placement = 'top-right';
30
31
  export let orientation: 'horizontal' | 'vertical' = 'vertical';
32
+ export let size: ComponentProps<Button>['size'] = 'md';
31
33
 
32
34
  type Actions = 'zoomIn' | 'zoomOut' | 'center' | 'reset' | 'scrollMode';
33
35
  export let show: Actions[] = ['zoomIn', 'zoomOut', 'center', 'reset', 'scrollMode'];
@@ -64,7 +66,7 @@
64
66
  <!-- svelte-ignore a11y-no-static-element-interactions -->
65
67
  <div
66
68
  class={cls(
67
- 'bg-surface-300/50 rounded-full m-1 backdrop-blur z-10 flex',
69
+ 'bg-surface-300/50 border rounded-full m-1 backdrop-blur z-10 flex',
68
70
  orientation === 'vertical' && 'flex-col',
69
71
  {
70
72
  'top-left': 'absolute top-0 left-0',
@@ -89,6 +91,7 @@
89
91
  <Button
90
92
  icon={mdiMagnifyPlusOutline}
91
93
  on:click={() => transform.zoomIn()}
94
+ {size}
92
95
  class="text-surface-content p-2"
93
96
  />
94
97
  </Tooltip>
@@ -99,6 +102,7 @@
99
102
  <Button
100
103
  icon={mdiMagnifyMinusOutline}
101
104
  on:click={() => transform.zoomOut()}
105
+ {size}
102
106
  class="text-surface-content p-2"
103
107
  />
104
108
  </Tooltip>
@@ -109,6 +113,7 @@
109
113
  <Button
110
114
  icon={mdiImageFilterCenterFocus}
111
115
  on:click={() => transform.translateCenter()}
116
+ {size}
112
117
  class="text-surface-content p-2"
113
118
  />
114
119
  </Tooltip>
@@ -119,6 +124,7 @@
119
124
  <Button
120
125
  icon={mdiArrowULeftTop}
121
126
  on:click={() => transform.reset()}
127
+ {size}
122
128
  class="text-surface-content p-2"
123
129
  />
124
130
  </Tooltip>
@@ -135,6 +141,7 @@
135
141
  ]}
136
142
  menuProps={{ placement: menuPlacementByOrientationAndPlacement[orientation][placement] }}
137
143
  menuIcon={null}
144
+ {size}
138
145
  value={$scrollMode}
139
146
  on:change={(e) => transform.setScrollMode(e.detail.value)}
140
147
  class="text-surface-content"
@@ -1,9 +1,12 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
+ import { type ComponentProps } from 'svelte';
3
+ import { Button } from 'svelte-ux';
2
4
  declare const __propDef: {
3
5
  props: {
4
6
  [x: string]: any;
5
7
  placement?: ("center" | "left" | "right" | "bottom" | "top" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
6
8
  orientation?: ("horizontal" | "vertical") | undefined;
9
+ size?: ComponentProps<Button>["size"];
7
10
  show?: ("reset" | "scrollMode" | "zoomIn" | "zoomOut" | "center")[] | undefined;
8
11
  };
9
12
  events: {
@@ -16,6 +16,7 @@ export { default as Circle } from './Circle.svelte';
16
16
  export { default as CircleClipPath } from './CircleClipPath.svelte';
17
17
  export { default as ClipPath } from './ClipPath.svelte';
18
18
  export { default as ColorRamp } from './ColorRamp.svelte';
19
+ export { default as Dagre } from './Dagre.svelte';
19
20
  export { default as Frame } from './Frame.svelte';
20
21
  export { default as ForceSimulation } from './ForceSimulation.svelte';
21
22
  export { default as GeoCircle } from './GeoCircle.svelte';
@@ -17,6 +17,7 @@ export { default as Circle } from './Circle.svelte';
17
17
  export { default as CircleClipPath } from './CircleClipPath.svelte';
18
18
  export { default as ClipPath } from './ClipPath.svelte';
19
19
  export { default as ColorRamp } from './ColorRamp.svelte';
20
+ export { default as Dagre } from './Dagre.svelte';
20
21
  export { default as Frame } from './Frame.svelte';
21
22
  export { default as ForceSimulation } from './ForceSimulation.svelte';
22
23
  export { default as GeoCircle } from './GeoCircle.svelte';
@@ -23,4 +23,11 @@
23
23
  });
24
24
  </script>
25
25
 
26
- <MenuField label="Curve" {options} bind:value stepper classes={{ menuIcon: 'hidden' }} />
26
+ <MenuField
27
+ label="Curve"
28
+ {options}
29
+ bind:value
30
+ stepper
31
+ classes={{ menuIcon: 'hidden' }}
32
+ {...$$restProps}
33
+ />
@@ -1,8 +1,9 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
+ [x: string]: any;
4
5
  value?: any | undefined;
5
- showOpenClosed?: boolean;
6
+ showOpenClosed?: boolean | undefined;
6
7
  };
7
8
  events: {
8
9
  [evt: string]: CustomEvent<any>;
@@ -1,5 +1,6 @@
1
1
  import type { SankeyExtraProperties, SankeyGraph, SankeyLink, SankeyNodeMinimal } from 'd3-sankey';
2
2
  import type { hierarchy as d3Hierarchy } from 'd3-hierarchy';
3
+ import dagre from '@dagrejs/dagre';
3
4
  /**
4
5
  * Convert CSV rows in format: 'source,target,value' to SankeyGraph
5
6
  */
@@ -26,3 +27,11 @@ export declare function graphFromNode(node: SankeyNodeMinimal<any, any>): {
26
27
  * Get distinct nodes from link.source and link.target
27
28
  */
28
29
  export declare function nodesFromLinks<N extends SankeyExtraProperties, L extends SankeyExtraProperties>(links: Array<SankeyLink<N, L>>): any[];
30
+ /**
31
+ * Get all upstream predecessors for dagre nodeId
32
+ */
33
+ export declare function ancestors(graph: dagre.graphlib.Graph, nodeId: string, maxDepth?: number, currentDepth?: number): dagre.Node[];
34
+ /**
35
+ * Get all downstream descendants for dagre nodeId
36
+ */
37
+ export declare function descendants(graph: dagre.graphlib.Graph, nodeId: string, maxDepth?: number, currentDepth?: number): dagre.Node[];
@@ -1,4 +1,5 @@
1
1
  import { csvParseRows } from 'd3-dsv';
2
+ import dagre from '@dagrejs/dagre';
2
3
  /**
3
4
  * Convert CSV rows in format: 'source,target,value' to SankeyGraph
4
5
  */
@@ -64,3 +65,31 @@ export function nodesFromLinks(links) {
64
65
  }
65
66
  return Array.from(nodesByName.values());
66
67
  }
68
+ /**
69
+ * Get all upstream predecessors for dagre nodeId
70
+ */
71
+ export function ancestors(graph, nodeId, maxDepth = Infinity, currentDepth = 0) {
72
+ if (currentDepth === maxDepth) {
73
+ return [];
74
+ }
75
+ const predecessors = graph.predecessors(nodeId) ?? [];
76
+ return [
77
+ ...predecessors,
78
+ // @ts-expect-error: Types from dagre appear incorrect
79
+ ...predecessors.flatMap((pId) => ancestors(graph, pId, maxDepth, currentDepth + 1)),
80
+ ];
81
+ }
82
+ /**
83
+ * Get all downstream descendants for dagre nodeId
84
+ */
85
+ export function descendants(graph, nodeId, maxDepth = Infinity, currentDepth = 0) {
86
+ if (currentDepth === maxDepth) {
87
+ return [];
88
+ }
89
+ const predecessors = graph.successors(nodeId) ?? [];
90
+ return [
91
+ ...predecessors,
92
+ // @ts-expect-error: Types from dagre appear incorrect
93
+ ...predecessors.flatMap((pId) => descendants(graph, pId, maxDepth, currentDepth + 1)),
94
+ ];
95
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import dagre from '@dagrejs/dagre';
3
+ import { ancestors, descendants } from './graph.js';
4
+ const exampleGraph = {
5
+ nodes: [
6
+ { id: 'A' },
7
+ { id: 'B' },
8
+ { id: 'C' },
9
+ { id: 'D' },
10
+ { id: 'E' },
11
+ { id: 'F' },
12
+ { id: 'G' },
13
+ { id: 'H' },
14
+ { id: 'I' },
15
+ ],
16
+ edges: [
17
+ { source: 'A', target: 'B' },
18
+ { source: 'C', target: 'B' },
19
+ { source: 'B', target: 'E' },
20
+ { source: 'B', target: 'F' },
21
+ { source: 'D', target: 'E' },
22
+ { source: 'D', target: 'F' },
23
+ { source: 'E', target: 'H' },
24
+ { source: 'G', target: 'H' },
25
+ { source: 'H', target: 'I' },
26
+ ],
27
+ };
28
+ function buildGraph(data) {
29
+ const g = new dagre.graphlib.Graph();
30
+ g.setGraph({});
31
+ data.nodes.forEach((n) => {
32
+ g.setNode(n.id, {
33
+ label: n.id,
34
+ });
35
+ });
36
+ data.edges.forEach((e) => {
37
+ g.setEdge(e.source, e.target);
38
+ });
39
+ return g;
40
+ }
41
+ describe('accessors', () => {
42
+ it('start of graph ', () => {
43
+ const graph = buildGraph(exampleGraph);
44
+ const actual = ancestors(graph, 'A');
45
+ expect(actual).length(0);
46
+ });
47
+ it('middle of graph ', () => {
48
+ const graph = buildGraph(exampleGraph);
49
+ const actual = ancestors(graph, 'E');
50
+ expect(actual).to.have.members(['A', 'B', 'C', 'D']);
51
+ });
52
+ it('end of graph ', () => {
53
+ const graph = buildGraph(exampleGraph);
54
+ const actual = ancestors(graph, 'I');
55
+ expect(actual).to.have.members(['A', 'B', 'C', 'D', 'E', 'G', 'H']);
56
+ });
57
+ it('max depth', () => {
58
+ const graph = buildGraph(exampleGraph);
59
+ const actual = ancestors(graph, 'H', 2);
60
+ expect(actual).to.have.members(['B', 'D', 'E', 'G']);
61
+ });
62
+ });
63
+ describe('descendants', () => {
64
+ it('start of graph ', () => {
65
+ const graph = buildGraph(exampleGraph);
66
+ const actual = descendants(graph, 'A');
67
+ expect(actual).to.have.members(['B', 'E', 'F', 'H', 'I']);
68
+ });
69
+ it('middle of graph ', () => {
70
+ const graph = buildGraph(exampleGraph);
71
+ const actual = descendants(graph, 'E');
72
+ expect(actual).to.have.members(['H', 'I']);
73
+ });
74
+ it('end of graph ', () => {
75
+ const graph = buildGraph(exampleGraph);
76
+ const actual = descendants(graph, 'I');
77
+ expect(actual).length(0);
78
+ });
79
+ it('max depth', () => {
80
+ const graph = buildGraph(exampleGraph);
81
+ const actual = descendants(graph, 'B', 2);
82
+ expect(actual).to.have.members(['E', 'F', 'H']);
83
+ });
84
+ });
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": "0.59.5",
7
+ "version": "0.60.0",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.27.10",
10
10
  "@mdi/js": "^7.4.47",
@@ -67,6 +67,7 @@
67
67
  },
68
68
  "type": "module",
69
69
  "dependencies": {
70
+ "@dagrejs/dagre": "^1.1.4",
70
71
  "@layerstack/svelte-actions": "^0.0.9",
71
72
  "@layerstack/svelte-stores": "^0.0.9",
72
73
  "@layerstack/tailwind": "^0.0.11",