layerchart 0.59.6 → 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 {};
@@ -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.6",
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",