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>
|