Spaces:
Running
Running
Simple input conversion. Simple table viewer.
Browse files- server/basic_ops.py +14 -2
- server/main.py +2 -2
- server/ops.py +39 -1
- web/src/LynxKiteFlow.svelte +5 -26
- web/src/LynxKiteNode.svelte +4 -2
- web/src/{NodeWithGraphVisualization.svelte → NodeWithGraphView.svelte} +2 -3
- web/src/NodeWithTableView.svelte +39 -0
server/basic_ops.py
CHANGED
@@ -14,11 +14,11 @@ def create_scale_free_graph(*, nodes: int = 10):
|
|
14 |
return nx.scale_free_graph(nodes)
|
15 |
|
16 |
@ops.op("Compute PageRank")
|
17 |
-
def compute_pagerank(graph, *, damping: 0.85, iterations: 3):
|
18 |
return nx.pagerank(graph)
|
19 |
|
20 |
@ops.op("Visualize graph")
|
21 |
-
def visualize_graph(graph) -> '
|
22 |
return {
|
23 |
'attributes': {
|
24 |
'name': 'My Graph'
|
@@ -40,3 +40,15 @@ def visualize_graph(graph) -> 'graphviz':
|
|
40 |
}
|
41 |
]
|
42 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
return nx.scale_free_graph(nodes)
|
15 |
|
16 |
@ops.op("Compute PageRank")
|
17 |
+
def compute_pagerank(graph: nx.Graph, *, damping: 0.85, iterations: 3):
|
18 |
return nx.pagerank(graph)
|
19 |
|
20 |
@ops.op("Visualize graph")
|
21 |
+
def visualize_graph(graph: ops.Bundle) -> 'graph_view':
|
22 |
return {
|
23 |
'attributes': {
|
24 |
'name': 'My Graph'
|
|
|
40 |
}
|
41 |
]
|
42 |
}
|
43 |
+
|
44 |
+
@ops.op("View table")
|
45 |
+
def view_table(dfs: ops.Bundle) -> 'table_view':
|
46 |
+
v = {
|
47 |
+
'dataframes': { name: {
|
48 |
+
'columns': [str(c) for c in df.columns],
|
49 |
+
'data': df.values.tolist(),
|
50 |
+
} for name, df in dfs.dfs.items() },
|
51 |
+
'edges': dfs.edges,
|
52 |
+
}
|
53 |
+
print(v)
|
54 |
+
return v
|
server/main.py
CHANGED
@@ -72,8 +72,8 @@ def execute(ws):
|
|
72 |
continue
|
73 |
data.error = None
|
74 |
outputs[node.id] = output
|
75 |
-
if op.type == '
|
76 |
-
data.
|
77 |
|
78 |
|
79 |
@app.post("/api/save")
|
|
|
72 |
continue
|
73 |
data.error = None
|
74 |
outputs[node.id] = output
|
75 |
+
if op.type == 'graph_view' or op.type == 'table_view':
|
76 |
+
data.view = output
|
77 |
|
78 |
|
79 |
@app.post("/api/save")
|
server/ops.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1 |
'''API for implementing LynxKite operations.'''
|
2 |
import dataclasses
|
3 |
import inspect
|
|
|
|
|
4 |
|
5 |
ALL_OPS = {}
|
6 |
|
@@ -21,6 +23,14 @@ class Op:
|
|
21 |
t = sig.parameters[p].annotation
|
22 |
if t == int:
|
23 |
params[p] = int(params[p])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
res = self.func(*inputs, **params)
|
25 |
return res
|
26 |
|
@@ -39,6 +49,33 @@ class Bundle:
|
|
39 |
dfs: dict
|
40 |
edges: list[EdgeDefinition]
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
def op(name):
|
43 |
'''Decorator for defining an operation.'''
|
44 |
def decorator(func):
|
@@ -53,7 +90,8 @@ def op(name):
|
|
53 |
name: param.default if param.default is not inspect._empty else None
|
54 |
for name, param in sig.parameters.items()
|
55 |
if param.kind == param.KEYWORD_ONLY}
|
56 |
-
|
|
|
57 |
ALL_OPS[name] = op
|
58 |
return func
|
59 |
return decorator
|
|
|
1 |
'''API for implementing LynxKite operations.'''
|
2 |
import dataclasses
|
3 |
import inspect
|
4 |
+
import networkx as nx
|
5 |
+
import pandas as pd
|
6 |
|
7 |
ALL_OPS = {}
|
8 |
|
|
|
23 |
t = sig.parameters[p].annotation
|
24 |
if t == int:
|
25 |
params[p] = int(params[p])
|
26 |
+
# Convert inputs.
|
27 |
+
inputs = list(inputs)
|
28 |
+
for i, (x, p) in enumerate(zip(inputs, sig.parameters.values())):
|
29 |
+
t = p.annotation
|
30 |
+
if t == nx.Graph and isinstance(x, Bundle):
|
31 |
+
inputs[i] = o.to_nx()
|
32 |
+
elif t == Bundle and isinstance(x, nx.Graph):
|
33 |
+
inputs[i] = Bundle.from_nx(x)
|
34 |
res = self.func(*inputs, **params)
|
35 |
return res
|
36 |
|
|
|
49 |
dfs: dict
|
50 |
edges: list[EdgeDefinition]
|
51 |
|
52 |
+
@classmethod
|
53 |
+
def from_nx(cls, graph: nx.Graph):
|
54 |
+
edges = nx.to_pandas_edgelist(graph)
|
55 |
+
print(edges)
|
56 |
+
nodes = pd.DataFrame({'id': list(graph.nodes)})
|
57 |
+
print(nodes)
|
58 |
+
return cls(
|
59 |
+
dfs={'edges': edges, 'nodes': nodes},
|
60 |
+
edges=[
|
61 |
+
EdgeDefinition(
|
62 |
+
df='edges',
|
63 |
+
source_column='source',
|
64 |
+
target_column='target',
|
65 |
+
source_table='nodes',
|
66 |
+
target_table='nodes',
|
67 |
+
source_key='id',
|
68 |
+
target_key='id',
|
69 |
+
)
|
70 |
+
]
|
71 |
+
)
|
72 |
+
|
73 |
+
def to_nx(self):
|
74 |
+
graph = nx.from_pandas_edgelist(self.dfs['edges'])
|
75 |
+
nx.set_node_attributes(graph, self.dfs['nodes'].set_index('id').to_dict('index'))
|
76 |
+
return graph
|
77 |
+
|
78 |
+
|
79 |
def op(name):
|
80 |
'''Decorator for defining an operation.'''
|
81 |
def decorator(func):
|
|
|
90 |
name: param.default if param.default is not inspect._empty else None
|
91 |
for name, param in sig.parameters.items()
|
92 |
if param.kind == param.KEYWORD_ONLY}
|
93 |
+
outputs = {'output': 'yes'} if type == 'basic' else {} # Maybe more fancy later.
|
94 |
+
op = Op(func, name, params=params, inputs=inputs, outputs=outputs, type=type)
|
95 |
ALL_OPS[name] = op
|
96 |
return func
|
97 |
return decorator
|
web/src/LynxKiteFlow.svelte
CHANGED
@@ -13,7 +13,8 @@
|
|
13 |
type Edge,
|
14 |
} from '@xyflow/svelte';
|
15 |
import NodeWithParams from './NodeWithParams.svelte';
|
16 |
-
import
|
|
|
17 |
import NodeSearch from './NodeSearch.svelte';
|
18 |
import '@xyflow/svelte/dist/style.css';
|
19 |
|
@@ -21,33 +22,11 @@
|
|
21 |
|
22 |
const nodeTypes: NodeTypes = {
|
23 |
basic: NodeWithParams,
|
24 |
-
|
|
|
25 |
};
|
26 |
|
27 |
-
const nodes = writable<Node[]>([
|
28 |
-
{
|
29 |
-
id: '1',
|
30 |
-
type: 'basic',
|
31 |
-
data: { title: 'Compute PageRank', params: { damping: 0.85, iterations: 3 } },
|
32 |
-
position: { x: 0, y: 0 },
|
33 |
-
sourcePosition: Position.Right,
|
34 |
-
targetPosition: Position.Left,
|
35 |
-
},
|
36 |
-
{
|
37 |
-
id: '3',
|
38 |
-
type: 'basic',
|
39 |
-
data: { title: 'Import Parquet', params: { filename: '/tmp/x.parquet' } },
|
40 |
-
position: { x: -400, y: 0 },
|
41 |
-
sourcePosition: Position.Right,
|
42 |
-
},
|
43 |
-
{
|
44 |
-
id: '4',
|
45 |
-
type: 'graphviz',
|
46 |
-
data: { title: 'Visualize graph', params: {} },
|
47 |
-
position: { x: 300, y: 0 },
|
48 |
-
targetPosition: Position.Left,
|
49 |
-
},
|
50 |
-
]);
|
51 |
|
52 |
const edges = writable<Edge[]>([
|
53 |
{
|
|
|
13 |
type Edge,
|
14 |
} from '@xyflow/svelte';
|
15 |
import NodeWithParams from './NodeWithParams.svelte';
|
16 |
+
import NodeWithGraphView from './NodeWithGraphView.svelte';
|
17 |
+
import NodeWithTableView from './NodeWithTableView.svelte';
|
18 |
import NodeSearch from './NodeSearch.svelte';
|
19 |
import '@xyflow/svelte/dist/style.css';
|
20 |
|
|
|
22 |
|
23 |
const nodeTypes: NodeTypes = {
|
24 |
basic: NodeWithParams,
|
25 |
+
graph_view: NodeWithGraphView,
|
26 |
+
table_view: NodeWithTableView,
|
27 |
};
|
28 |
|
29 |
+
const nodes = writable<Node[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
const edges = writable<Edge[]>([
|
32 |
{
|
web/src/LynxKiteNode.svelte
CHANGED
@@ -56,12 +56,14 @@
|
|
56 |
}
|
57 |
.node-container {
|
58 |
padding: 8px;
|
59 |
-
min-width: 170px;
|
60 |
-
max-width: 300px;
|
61 |
}
|
62 |
.lynxkite-node {
|
63 |
box-shadow: 0px 5px 50px 0px rgba(0, 0, 0, 0.3);
|
64 |
background: white;
|
|
|
|
|
|
|
|
|
65 |
}
|
66 |
.title {
|
67 |
background: #ff8800;
|
|
|
56 |
}
|
57 |
.node-container {
|
58 |
padding: 8px;
|
|
|
|
|
59 |
}
|
60 |
.lynxkite-node {
|
61 |
box-shadow: 0px 5px 50px 0px rgba(0, 0, 0, 0.3);
|
62 |
background: white;
|
63 |
+
min-width: 170px;
|
64 |
+
max-width: 300px;
|
65 |
+
max-height: 400px;
|
66 |
+
overflow-y: auto;
|
67 |
}
|
68 |
.title {
|
69 |
background: #ff8800;
|
web/src/{NodeWithGraphVisualization.svelte → NodeWithGraphView.svelte}
RENAMED
@@ -5,14 +5,13 @@
|
|
5 |
import * as graphologyLibrary from 'graphology-library';
|
6 |
import LynxKiteNode from './LynxKiteNode.svelte';
|
7 |
type $$Props = NodeProps;
|
8 |
-
export let id: $$Props['id'];
|
9 |
export let data: $$Props['data'];
|
10 |
let sigmaCanvas: HTMLElement;
|
11 |
let sigmaInstance: Sigma;
|
12 |
|
13 |
$: if (sigmaCanvas) sigmaInstance = new Sigma(new graphology.Graph(), sigmaCanvas);
|
14 |
-
$: if (sigmaInstance && data.
|
15 |
-
const graph = graphology.Graph.from(data.
|
16 |
graphologyLibrary.layout.random.assign(graph);
|
17 |
const settings = graphologyLibrary.layoutForceAtlas2.inferSettings(graph);
|
18 |
graphologyLibrary.layoutForceAtlas2.assign(graph, { iterations: 10, settings });
|
|
|
5 |
import * as graphologyLibrary from 'graphology-library';
|
6 |
import LynxKiteNode from './LynxKiteNode.svelte';
|
7 |
type $$Props = NodeProps;
|
|
|
8 |
export let data: $$Props['data'];
|
9 |
let sigmaCanvas: HTMLElement;
|
10 |
let sigmaInstance: Sigma;
|
11 |
|
12 |
$: if (sigmaCanvas) sigmaInstance = new Sigma(new graphology.Graph(), sigmaCanvas);
|
13 |
+
$: if (sigmaInstance && data.view) {
|
14 |
+
const graph = graphology.Graph.from(data.view);
|
15 |
graphologyLibrary.layout.random.assign(graph);
|
16 |
const settings = graphologyLibrary.layoutForceAtlas2.inferSettings(graph);
|
17 |
graphologyLibrary.layoutForceAtlas2.assign(graph, { iterations: 10, settings });
|
web/src/NodeWithTableView.svelte
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { type NodeProps } from '@xyflow/svelte';
|
3 |
+
import LynxKiteNode from './LynxKiteNode.svelte';
|
4 |
+
type $$Props = NodeProps;
|
5 |
+
export let data: $$Props['data'];
|
6 |
+
</script>
|
7 |
+
|
8 |
+
<LynxKiteNode {...$$props}>
|
9 |
+
{#if data.view}
|
10 |
+
{#each Object.entries(data.view.dataframes) as [name, df]}
|
11 |
+
<div class="df-head">{name}</div>
|
12 |
+
<table>
|
13 |
+
<tr>
|
14 |
+
{#each df.columns as column}
|
15 |
+
<th>{column}</th>
|
16 |
+
{/each}
|
17 |
+
</tr>
|
18 |
+
{#each df.data as row}
|
19 |
+
<tr>
|
20 |
+
{#each row as cell}
|
21 |
+
<td>{cell}</td>
|
22 |
+
{/each}
|
23 |
+
</tr>
|
24 |
+
{/each}
|
25 |
+
</table>
|
26 |
+
{/each}
|
27 |
+
{/if}
|
28 |
+
</LynxKiteNode>
|
29 |
+
<style>
|
30 |
+
.df-head {
|
31 |
+
font-weight: bold;
|
32 |
+
padding: 8px;
|
33 |
+
background: #f0f0f0;
|
34 |
+
}
|
35 |
+
table {
|
36 |
+
margin: 8px;
|
37 |
+
border-collapse: collapse;
|
38 |
+
}
|
39 |
+
</style>
|