darabos commited on
Commit
b6d30cb
·
1 Parent(s): 05acf81

Color by PageRank.

Browse files
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 pandas as pd
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: 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
- nodes = graph.dfs['nodes']['id'].tolist()
 
 
 
 
23
  edges = graph.dfs['edges'].drop_duplicates(['source', 'target'])
24
- edges = edges.to_dict(orient='records')
25
- return {
 
26
  'attributes': {},
27
  'options': {},
28
- 'nodes': [{'key': id} for id in nodes],
29
- 'edges': [{'key': str(r['source']) + ' -> ' + str(r['target']), **r} for r in edges],
 
 
 
 
 
 
 
30
  }
 
31
 
32
- @ops.op("View table")
33
- def view_table(dfs: ops.Bundle) -> 'table_view':
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
- 'edges': dfs.edges,
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 EdgeDefinition:
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
- edges: list[EdgeDefinition]
51
 
52
  @classmethod
53
  def from_nx(cls, graph: nx.Graph):
54
  edges = nx.to_pandas_edgelist(graph)
55
- nodes = pd.DataFrame({'id': list(graph.nodes)})
 
56
  return cls(
57
  dfs={'edges': edges, 'nodes': nodes},
58
- edges=[
59
- EdgeDefinition(
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
- 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 });
@@ -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>