Skip to content

Commit a4cdf1e

Browse files
committed
add network support
1 parent d17b911 commit a4cdf1e

7 files changed

Lines changed: 11990 additions & 2 deletions

File tree

‎datashader_cli/cli.py‎

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,127 @@ def points(data_path, x, y, w=600, h=600, x_range=None, y_range=None,
188188
t6 = datetime.datetime.now()
189189
print("Time to create image: ", t6-t5)
190190
print("Total time: ", t6-t1)
191-
main.add_command(points)
192191

193192

193+
import datashader as ds
194+
import datashader.transfer_functions as tf
195+
196+
197+
def nodesplot(nodes, name=None, canvas=None, cat=None, cvsopts=None):
198+
canvas = ds.Canvas(**cvsopts) if canvas is None else canvas
199+
aggregator=None if cat is None else ds.count_cat(cat)
200+
agg=canvas.points(nodes,'x','y',aggregator)
201+
return tf.spread(tf.shade(agg, cmap=["#FF3333"]), px=1, name=name)
202+
203+
def edgesplot(edges, name=None, canvas=None, cvsopts=None, edge_cmap=None):
204+
canvas = ds.Canvas(**cvsopts) if canvas is None else canvas
205+
206+
if edge_cmap:
207+
return tf.shade(canvas.line(edges, 'x','y', agg=ds.count()), cmap=cc.palette[edge_cmap], name=name)
208+
return tf.shade(canvas.line(edges, 'x','y', agg=ds.count()), name=name)
209+
210+
def graphplot(nodes, edges, name="", canvas=None, cat=None, cvsopts=None, edge_cmap=None):
211+
if canvas is None:
212+
xr = nodes.x.min(), nodes.x.max()
213+
yr = nodes.y.min(), nodes.y.max()
214+
canvas = ds.Canvas(x_range=xr, y_range=yr, **cvsopts)
215+
216+
np = nodesplot(nodes, name + " nodes", canvas, cat, cvsopts)
217+
ep = edgesplot(edges, name + " edges", canvas, cvsopts, edge_cmap)
218+
return tf.stack(ep, np, how="over", name=name)
219+
220+
221+
@click.command()
222+
@click.argument('nodes_file', type=click.Path(exists=True), required=True)
223+
@click.argument('edges_file', type=click.Path(exists=True), required=True)
224+
@click.argument('output_path',default = None, type = str, required=True)
225+
226+
@click.option('--w', default=600,type = int, required=False, help = 'How many pixels wide to make the image')
227+
@click.option('--h', default=600, type = int,required=False, help = 'How many pixels high to make the image')
228+
@click.option('--x',default='x',type= str, required=False, help = """Column name for x coordinates, e.g. "x", if use layout="geo", x is required """)
229+
@click.option('--y',default='y',type= str, required=False, help = """Column name for y coordinates, e.g. "y", if use layout="geo", y is required """)
230+
@click.option('--source',default='source',type= str, required=False, help = """Column name for source node, e.g. "source" """)
231+
@click.option('--target',default='target',type= str, required=False, help = """Column name for target node, e.g. "target" """)
232+
@click.option('--id',default=None,type= str, required=False, help = """Column name for node id, e.g. "id" """)
233+
@click.option('--layout',default='random',type= str, required=False, help = """Layout algorithm, e.g. "random", "forceatlas2", "geo" """)
234+
@click.option('--cat',default=None, type = str, required=False, help = """Column to group by, e.g. "category", see datashader docs (https://datashader.org/api.html#reductions) for more options""")
235+
@click.option('--background',default=None, required=False, type = str, help = """Background color, e.g. "black", "white", "#000000", "#ffffff" """)
236+
@click.option('--bundle',default=False, required=False, type = bool, help = """Whether to bundle the edges""")
237+
@click.option('--bw', default=None,type = float, required=False, help = 'initial_bandwidth for bundling')
238+
@click.option('--decay', default=None, type = float, required=False, help = 'decay for bundling')
239+
@click.option('--edge_cmap',default= None, required=False, type = str, help = 'Name of the colormap for edges, see https://colorcet.holoviz.org for more options')
240+
def network(nodes_file, edges_file, output_path, w=600, h=600, x="x",y="y", source="source", target="target", id=None,
241+
layout="forceatlas2",cat=None,
242+
background="white", bundle=False, bw=None, decay=None, edge_cmap=None):
243+
244+
# read data
245+
import pandas as pd
246+
from datashader.layout import random_layout, circular_layout, forceatlas2_layout
247+
from datashader.bundling import connect_edges, hammer_bundle
248+
249+
if nodes_file.endswith('.parquet'):
250+
cnodes = pd.read_parquet(nodes_file)
251+
elif nodes_file.endswith('.csv'):
252+
cnodes = pd.read_csv(nodes_file)
253+
else:
254+
raise ValueError('Unsupported data format')
255+
256+
cnodes = cnodes.rename(columns={x:"x", y:"y"})
257+
258+
259+
260+
# cnodes.drop(columns=['AirportID'], inplace=True)
261+
# this script can't handle id
262+
263+
if edges_file.endswith('.parquet'):
264+
cedges = pd.read_parquet(edges_file)
265+
elif edges_file.endswith('.csv'):
266+
cedges = pd.read_csv(edges_file)
267+
else:
268+
raise ValueError('Unsupported data format')
269+
270+
cedges = cedges.rename(columns={source:"source", target:"target"})
271+
272+
if id:
273+
cnodes["order"] = range(len(cnodes))
274+
mapper = cnodes.set_index(id)["order"].to_dict()
275+
cedges["source"] = cedges["source"].map(mapper)
276+
cedges["target"] = cedges["target"].map(mapper)
277+
# print(cedges.head())
278+
# layout
279+
layouts = {
280+
"random":random_layout,
281+
"circular":circular_layout,
282+
"forceatlas2":forceatlas2_layout
283+
}
284+
285+
if layout == "geo":
286+
layout = cnodes.rename(columns={x:"x", y:"y"})
287+
# print(layout.head())
288+
else:
289+
290+
layout = layouts[layout](cnodes, cedges)
291+
print(layout.head())
292+
293+
cvsopts = dict(plot_height=h, plot_width=w)
294+
if bundle:
295+
bundle_keywords = {
296+
}
297+
if bw:
298+
bundle_keywords["initial_bandwidth"] = bw
299+
if decay:
300+
bundle_keywords["decay"] = decay
301+
plot = graphplot(layout, hammer_bundle(layout,cedges, **bundle_keywords), "title", cat=cat, cvsopts=cvsopts, edge_cmap=edge_cmap)
302+
303+
else:
304+
plot = graphplot(layout, connect_edges(layout,cedges), "title", cat=cat, cvsopts=cvsopts, edge_cmap=edge_cmap)
305+
img = tf.set_background(plot, background)
306+
img.to_pil().save(output_path)
307+
308+
309+
310+
main.add_command(points)
311+
main.add_command(network)
194312

195313

196314
if __name__ == "__main__":

‎requirements.txt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ datashader
33
pandas
44
matplotlib
55
geopandas
6-
pyarrow
6+
pyarrow
7+
scikit-image

‎test.png‎

27.8 KB
Loading

0 commit comments

Comments
 (0)