Spaces:
Running
Running
Color by PageRank.
Browse files- requirements.txt +2 -0
- server/basic_ops.py +36 -12
- server/ops.py +10 -5
- web/src/NodeWithGraphView.svelte +15 -2
requirements.txt
CHANGED
@@ -1,5 +1,7 @@
|
|
1 |
fastapi
|
|
|
2 |
networkx
|
3 |
numpy
|
4 |
pandas
|
|
|
5 |
uvicorn
|
|
|
1 |
fastapi
|
2 |
+
matplotlib
|
3 |
networkx
|
4 |
numpy
|
5 |
pandas
|
6 |
+
scipy
|
7 |
uvicorn
|
server/basic_ops.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1 |
'''Some operations. To be split into separate files when we have more.'''
|
2 |
from . import ops
|
3 |
-
import
|
4 |
import networkx as nx
|
|
|
5 |
|
6 |
@ops.op("Import Parquet")
|
7 |
def import_parquet(*, filename: str):
|
@@ -14,28 +15,51 @@ 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: nx.Graph, *, damping
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
@ops.op("Visualize graph")
|
21 |
-
def visualize_graph(graph: ops.Bundle) -> 'graph_view':
|
22 |
-
nodes = graph.dfs['nodes']
|
|
|
|
|
|
|
|
|
23 |
edges = graph.dfs['edges'].drop_duplicates(['source', 'target'])
|
24 |
-
edges = edges.
|
25 |
-
|
|
|
26 |
'attributes': {},
|
27 |
'options': {},
|
28 |
-
'nodes': [
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
|
|
31 |
|
32 |
-
@ops.op("View
|
33 |
-
def
|
34 |
v = {
|
35 |
'dataframes': { name: {
|
36 |
'columns': [str(c) for c in df.columns],
|
37 |
'data': df.values.tolist(),
|
38 |
} for name, df in dfs.dfs.items() },
|
39 |
-
'
|
40 |
}
|
41 |
return v
|
|
|
1 |
'''Some operations. To be split into separate files when we have more.'''
|
2 |
from . import ops
|
3 |
+
import matplotlib
|
4 |
import networkx as nx
|
5 |
+
import pandas as pd
|
6 |
|
7 |
@ops.op("Import Parquet")
|
8 |
def import_parquet(*, filename: str):
|
|
|
15 |
return nx.scale_free_graph(nodes)
|
16 |
|
17 |
@ops.op("Compute PageRank")
|
18 |
+
def compute_pagerank(graph: nx.Graph, *, damping=0.85, iterations=3):
|
19 |
+
graph = graph.copy()
|
20 |
+
pr = nx.pagerank(graph, alpha=damping, max_iter=iterations)
|
21 |
+
nx.set_node_attributes(graph, pr, 'pagerank')
|
22 |
+
return graph
|
23 |
+
|
24 |
+
|
25 |
+
def _map_color(value):
|
26 |
+
cmap = matplotlib.cm.get_cmap('viridis')
|
27 |
+
value = (value - value.min()) / (value.max() - value.min())
|
28 |
+
rgba = cmap(value)
|
29 |
+
return ['#{:02x}{:02x}{:02x}'.format(int(r*255), int(g*255), int(b*255)) for r, g, b in rgba[:, :3]]
|
30 |
|
31 |
@ops.op("Visualize graph")
|
32 |
+
def visualize_graph(graph: ops.Bundle, *, color_nodes_by: 'node_attribute' = None) -> 'graph_view':
|
33 |
+
nodes = graph.dfs['nodes'].copy()
|
34 |
+
node_attributes = sorted(nodes.columns)
|
35 |
+
if color_nodes_by:
|
36 |
+
nodes['color'] = _map_color(nodes[color_nodes_by])
|
37 |
+
nodes = nodes.to_records()
|
38 |
edges = graph.dfs['edges'].drop_duplicates(['source', 'target'])
|
39 |
+
edges = edges.to_records()
|
40 |
+
v = {
|
41 |
+
'node_attributes': node_attributes,
|
42 |
'attributes': {},
|
43 |
'options': {},
|
44 |
+
'nodes': [
|
45 |
+
{
|
46 |
+
'key': str(n.id),
|
47 |
+
'attributes': {'color': n.color, 'size': 5} if color_nodes_by else {}
|
48 |
+
}
|
49 |
+
for n in nodes],
|
50 |
+
'edges': [
|
51 |
+
{'key': str(r.source) + ' -> ' + str(r.target), 'source': str(r.source), 'target': str(r.target)}
|
52 |
+
for r in edges],
|
53 |
}
|
54 |
+
return v
|
55 |
|
56 |
+
@ops.op("View tables")
|
57 |
+
def view_tables(dfs: ops.Bundle) -> 'table_view':
|
58 |
v = {
|
59 |
'dataframes': { name: {
|
60 |
'columns': [str(c) for c in df.columns],
|
61 |
'data': df.values.tolist(),
|
62 |
} for name, df in dfs.dfs.items() },
|
63 |
+
'relations': dfs.relations,
|
64 |
}
|
65 |
return v
|
server/ops.py
CHANGED
@@ -21,8 +21,12 @@ class Op:
|
|
21 |
for p in params:
|
22 |
if p in self.params:
|
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())):
|
@@ -35,7 +39,7 @@ class Op:
|
|
35 |
return res
|
36 |
|
37 |
@dataclasses.dataclass
|
38 |
-
class
|
39 |
df: str
|
40 |
source_column: str
|
41 |
target_column: str
|
@@ -47,16 +51,17 @@ class EdgeDefinition:
|
|
47 |
@dataclasses.dataclass
|
48 |
class Bundle:
|
49 |
dfs: dict
|
50 |
-
|
51 |
|
52 |
@classmethod
|
53 |
def from_nx(cls, graph: nx.Graph):
|
54 |
edges = nx.to_pandas_edgelist(graph)
|
55 |
-
nodes = pd.DataFrame(
|
|
|
56 |
return cls(
|
57 |
dfs={'edges': edges, 'nodes': nodes},
|
58 |
-
|
59 |
-
|
60 |
df='edges',
|
61 |
source_column='source',
|
62 |
target_column='target',
|
|
|
21 |
for p in params:
|
22 |
if p in self.params:
|
23 |
t = sig.parameters[p].annotation
|
24 |
+
if t is inspect._empty:
|
25 |
+
t = type(sig.parameters[p].default)
|
26 |
if t == int:
|
27 |
params[p] = int(params[p])
|
28 |
+
elif t == float:
|
29 |
+
params[p] = float(params[p])
|
30 |
# Convert inputs.
|
31 |
inputs = list(inputs)
|
32 |
for i, (x, p) in enumerate(zip(inputs, sig.parameters.values())):
|
|
|
39 |
return res
|
40 |
|
41 |
@dataclasses.dataclass
|
42 |
+
class RelationDefinition:
|
43 |
df: str
|
44 |
source_column: str
|
45 |
target_column: str
|
|
|
51 |
@dataclasses.dataclass
|
52 |
class Bundle:
|
53 |
dfs: dict
|
54 |
+
relations: list[RelationDefinition]
|
55 |
|
56 |
@classmethod
|
57 |
def from_nx(cls, graph: nx.Graph):
|
58 |
edges = nx.to_pandas_edgelist(graph)
|
59 |
+
nodes = pd.DataFrame.from_dict(dict(graph.nodes(data=True)), orient='index')
|
60 |
+
nodes['id'] = nodes.index
|
61 |
return cls(
|
62 |
dfs={'edges': edges, 'nodes': nodes},
|
63 |
+
relations=[
|
64 |
+
RelationDefinition(
|
65 |
df='edges',
|
66 |
source_column='source',
|
67 |
target_column='target',
|
web/src/NodeWithGraphView.svelte
CHANGED
@@ -1,17 +1,21 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { type NodeProps } from '@xyflow/svelte';
|
3 |
import Sigma from 'sigma';
|
4 |
import * as graphology from 'graphology';
|
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 |
-
|
|
|
|
|
15 |
graphologyLibrary.layout.random.assign(graph);
|
16 |
const settings = graphologyLibrary.layoutForceAtlas2.inferSettings(graph);
|
17 |
graphologyLibrary.layoutForceAtlas2.assign(graph, { iterations: 10, settings });
|
@@ -22,6 +26,15 @@
|
|
22 |
</script>
|
23 |
|
24 |
<LynxKiteNode {...$$props}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
<div bind:this={sigmaCanvas} style="height: 200px; width: 200px;" >
|
26 |
</div>
|
27 |
</LynxKiteNode>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { type NodeProps, useSvelteFlow } from '@xyflow/svelte';
|
3 |
import Sigma from 'sigma';
|
4 |
import * as graphology from 'graphology';
|
5 |
import * as graphologyLibrary from 'graphology-library';
|
6 |
import LynxKiteNode from './LynxKiteNode.svelte';
|
7 |
type $$Props = NodeProps;
|
8 |
+
const { updateNodeData } = useSvelteFlow();
|
9 |
+
export let id: $$Props['id'];
|
10 |
export let data: $$Props['data'];
|
11 |
let sigmaCanvas: HTMLElement;
|
12 |
let sigmaInstance: Sigma;
|
13 |
|
14 |
$: if (sigmaCanvas) sigmaInstance = new Sigma(new graphology.Graph(), sigmaCanvas);
|
15 |
$: if (sigmaInstance && data.view) {
|
16 |
+
// Graphology will modify this in place, so we make a copy.
|
17 |
+
const view = JSON.parse(JSON.stringify(data.view));
|
18 |
+
const graph = graphology.Graph.from(view);
|
19 |
graphologyLibrary.layout.random.assign(graph);
|
20 |
const settings = graphologyLibrary.layoutForceAtlas2.inferSettings(graph);
|
21 |
graphologyLibrary.layoutForceAtlas2.assign(graph, { iterations: 10, settings });
|
|
|
26 |
</script>
|
27 |
|
28 |
<LynxKiteNode {...$$props}>
|
29 |
+
{#if data.view}
|
30 |
+
<label>Color by
|
31 |
+
<select on:change={(evt) => updateNodeData(id, { params: { ...data.params, color_nodes_by: evt.currentTarget.value } })}>
|
32 |
+
<option value="">nothing</option>
|
33 |
+
{#each data.view.node_attributes as attr}
|
34 |
+
<option value={attr} selected={attr === data.params.color_nodes_by}>{attr}</option>
|
35 |
+
{/each}
|
36 |
+
</select></label>
|
37 |
+
{/if}
|
38 |
<div bind:this={sigmaCanvas} style="height: 200px; width: 200px;" >
|
39 |
</div>
|
40 |
</LynxKiteNode>
|