背景 在另一篇文章 Python 可交互的网络图可视化工具 中总结了一些提供 Python API 的网络图可视化交互工具库,例如 Plotly、PyVis、PyEcharts 等,其各有千秋。在逛 github 时突然发现另一个眼前一亮的可视化库 —— bqplot,同样提供了网络图的可视化 Python 接口,而且功能更加强大更好看,因此学习下 bqplot 中较为关注的 network graph 网络图可视化方法。
bqplot bqplot 是基于图形语法构建的用于 Jupyter 的交互式 2D 绘图库,具有以下特点:
用 Python 语言提供统一的可视化框架; bqplot 利用 widget 基础提供第一个在 Python 和 JAVAScript 代码之间通信的绘图库; bqplot 的可视化是基于 D3.js 和 SVG 的,支持快速交互和漂亮的动画; 源码仓库:https://github.com/bqplot/bqplot
官方文档:https://bqplot.readthedocs.io/
bqplot 提供非常多的交互图绘制接口,支持的图表格式如下所示:
Bars: Bar mark Bins: Backend histogram mark Boxplot: Boxplot mark Candles: OHLC mark FlexLine: Flexible lines mark Graph: Network mark GridHeatMap: Grid heatmap mark HeatMap: Heatmap mark Hist: Histogram mark Image: Image mark Label: Label mark Lines: Lines mark Map: Geographical map mark Market Map: Tile map mark Pie: Pie mark Scatter: Scatter mark Mega Scatter: webgl-based Scatter mark 其它类型图表形式暂不关注,本文主要学习下其中的 Network graph 绘制方法。
Graph:Network mark 中需要提供节点和边数据。
节点属性如下
Attribute Type Description Default label str node label mandatory attribute label_display {center, outside, none} label display options center shape {circle, ellipse, rect} node shape circle shape_attrs dict node SVG attributes {‘r’: 15}
边属性如下
Attribute Type Description Default source int source node index mandatory attribute target int target node index mandatory attribute value float value of the link. Use np.nan if you do not want a link -
绘图实践 安装方式:pip install bqplot
首先导入必要的库
1 2 3 4 5 import numpy as npfrom bqplot import Graph, LinearScale, ColorScale, Figure, Tooltipfrom ipywidgets import Layoutfig_layout = Layout(width='600px' , height='600px' )
👇🏻以下学习下图各个属性的设置。
有向图 如果不指定节点位置默认为 force layout
。
1 2 3 4 5 6 7 8 9 10 11 12 13 node_data = [ dict (label='A' , shape='rect' ), dict (label='B' , shape='ellipse' ), dict (label='C' , shape='ellipse' ), dict (label='D' , shape='rect' ), dict (label='E' , shape='ellipse' ), dict (label='F' , shape='circle' ), dict (label='G' , shape='ellipse' ), ] link_data = [{'source' : s, 'target' : t} for s, t in np.random.randint(0 , 7 , (10 , 2 )) if s != t] graph = Graph(node_data=node_data, link_data=link_data, charge=-600 , colors=['lightblue' ] * 7 ) graph.link_type = 'arc' Figure(marks=[graph], layout=fig_layout)
效果图如下:
固定位置 可以根据 x, y 坐标设定每个节点的绝对位置,注意设定位置后节点不可拖动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 node_data = list ('ABCDEFG' ) link_matrix = np.zeros((7 , 7 )) xs = LinearScale() ys = LinearScale() x = [80 , 150 , 200 , 250 , 250 , 250 , 300 ] y = [3 , 1.5 , 5 , 9 , 7 , 5 , 2 ] graph = Graph(node_data=node_data, link_matrix=link_matrix, link_type='arc' , colors=['lightblue' ] * 7 , scales={'x' : xs, 'y' : ys, }, x=x, y=y, directed=True ) Figure(marks=[graph], layout=fig_layout)
效果图如下:
颜色模式 点颜色模式 :设定图 color
属性值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 node_data = list ('ABCDEFG' ) link_matrix = np.zeros((7 , 7 )) xs = LinearScale(min =0 , max =500 ) ys = LinearScale(min =0 , max =10 ) cs = ColorScale(scheme='Reds' ) x = [100 , 200 , 200 , 300 , 300 , 300 , 300 ] y = [2 , .5 , 4 , 8 , 6 , 4 , 1 ] graph3 = Graph(node_data=node_data, link_matrix=link_matrix, link_type='line' , color=np.random.rand(7 ), scales={'x' : xs, 'y' : ys, 'color' : cs}, x=x, y=y, directed=False ) Figure(marks=[graph3], layout=fig_layout)
效果图如下:
边颜色模式 :设定图 link_color
属性值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 node_data = list ('ABCDEFG' ) link_data = [{'source' : s, 'target' : t, 'value' : np.random.rand()} for s, t in np.random.randint(0 , 7 , (20 , 2 ))] xs = LinearScale() ys = LinearScale() lcs = ColorScale(scheme='Reds' ) x = [100 , 200 , 200 , 300 , 300 , 300 , 300 ] y = [2 , .5 , 4 , 8 , 6 , 4 , 1 ] graph4 = Graph(node_data=node_data, link_data=link_data, link_type='line' , colors=['lightblue' ], directed=False , scales={'x' : xs, 'y' : ys, 'link_color' : lcs}, x=x, y=y, color=np.random.rand(7 )) Figure(marks=[graph4], layout=fig_layout)
效果图如下:
定制节点 可以自定义节点属性及其节点悬浮和点击的 Action。
1 2 3 4 5 6 7 8 9 10 11 node_data = [ {'label' : 'A' , 'shape' : 'circle' , 'shape_attrs' : {'r' : 20 }, 'foo' : 1 }, {'label' : 'B' , 'shape' : 'rect' , 'shape_attrs' : {'rx' : 10 , 'ry' : 10 , 'width' : 40 }, 'foo' : 2 }, {'label' : 'C' , 'shape' : 'ellipse' , 'foo' : 4 }, {'label' : 'D' , 'shape' : 'rect' , 'shape_attrs' : {'width' : 30 , 'height' : 30 }, 'foo' : 100 }, ] link_data = [{'source' : s, 'target' : t, 'value' : np.random.rand()} for s, t in np.random.randint(0 , 4 , (8 , 2 ))] graph5 = Graph(node_data=node_data, link_data=link_data, link_distance=150 ) Figure(marks=[graph5], layout=fig_layout)
节点添加 tooltips:
1 2 tooltip = Tooltip(fields=['label' , 'foo' ], formats=['' , '' , '' ]) graph5.tooltip = tooltip
为 tooltip 添加一个折线图:
1 2 3 4 5 import bqplot.pyplot as pltplt.clear() line = plt.plot(np.cumsum(np.random.randn(20 ))) graph5.tooltip = plt.current_figure()
设置选中、悬浮操作:
1 2 3 4 5 graph5.hovered_style = {'stroke' : 'red' } graph5.unhovered_style = {'opacity' : '0.4' } graph5.selected_style = {'opacity' : '1' , 'stroke' : 'white' , 'stroke-width' : '2.5' } graph5.selected
设置点击事件:
1 2 3 4 5 def print_event (self, target ): print (target) graph5.on_background_click(print_event) graph5.on_element_click(print_event)
效果如下:
总结 整体而言,bqplot 符合大部分绘图需求,存在的问题是网络图只能在 Jupyter 中交互,保存时格式只有 png 或者 svg,期待后续功能迭代…