{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", " \n", " \n", " \n", "
\n", " \n", " BokehJS successfully loaded.\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.io import output_notebook, show\n", "output_notebook()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import pandas as pd\n", "import networkx as nx\n", "\n", "from utils import GPG" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "220" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gpg = GPG()\n", "data = pd.DataFrame(gpg.to_dict())\n", "data = data.fillna('')\n", "len(data)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "g = nx.Graph()\n", "\n", "def add_node(row):\n", " key = row.keyid\n", " g.add_node(key)\n", " \n", "def add_edge(row):\n", " key = row.keyid\n", " for connection in row.signedbykeys:\n", " g.add_edge(key, connection)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Add nodes & edges to Graph and layout" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "220" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data.apply(add_node, axis=1)\n", "len(g.nodes())" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "225" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data.apply(add_edge, axis=1)\n", "len(g.edges())" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "pts = nx.spring_layout(g)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Make data frames" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "225" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "start_keyids = []\n", "end_keyids = []\n", "xs = []\n", "ys = []\n", "for edge in g.edges():\n", " # Each edge is a tuple of start and end.\n", " start_id = edge[0]\n", " end_id = edge[1]\n", " start_keyids.append(start_id)\n", " end_keyids.append(end_id)\n", " \n", " # Get the xs and ys\n", " start_pt = pts[start_id]\n", " end_pt = pts[end_id]\n", " xs.append([start_pt[0], end_pt[0]])\n", " ys.append([start_pt[1], end_pt[1]])\n", "edges = pd.DataFrame(dict(keyid=start_keyids, end_keyids=end_keyids, xs=xs, ys=ys))\n", "len(edges)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "220" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "points = pd.DataFrame(pts)\n", "points = points.transpose()\n", "points = points.reset_index()\n", "points = points.rename(columns={\n", " 0: 'x',\n", " 1: 'y',\n", " 'index': 'keyid'\n", " })\n", "\n", "points = data.merge(points)\n", "def lookup_line_index(point_row):\n", " keyid = point_row.keyid\n", " dir_1 = tuple(edges[edges.keyid == keyid].index.get_values())\n", " dir_2 = tuple(edges[edges.end_keyids == keyid].index.get_values())\n", " selected_edges = dir_1 + dir_2\n", " return selected_edges\n", "\n", "points['line_indexes'] = points.apply(lookup_line_index, axis=1)\n", "points.line_indexes = points.line_indexes.apply(lambda x: list(x))\n", "points.uid = points.uid.str[0:60]\n", "len(points)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
keyidsignedbysignedbykeystimestampuidxyline_indexes
0ED18B68D16194553[][]Sat, 25 Jul 2015 23:56:19 -00000.2628290.617023[0]
177EEDF0317F55690[][]Sat, 25 Jul 2015 23:56:19 -00000.4953310.371764[1]
23D533EC06F80F701[][]Sat, 25 Jul 2015 23:56:19 -00000.7463420.707128[2]
3231CD035A9D1ACF9[][]Sat, 25 Jul 2015 23:56:19 -00000.4913450.897368[9]
4DE24858541A53190[][]Sat, 25 Jul 2015 23:56:19 -00000.1101710.412812[4]
\n", "
" ], "text/plain": [ " keyid signedby signedbykeys timestamp \\\n", "0 ED18B68D16194553 [] [] Sat, 25 Jul 2015 23:56:19 -0000 \n", "1 77EEDF0317F55690 [] [] Sat, 25 Jul 2015 23:56:19 -0000 \n", "2 3D533EC06F80F701 [] [] Sat, 25 Jul 2015 23:56:19 -0000 \n", "3 231CD035A9D1ACF9 [] [] Sat, 25 Jul 2015 23:56:19 -0000 \n", "4 DE24858541A53190 [] [] Sat, 25 Jul 2015 23:56:19 -0000 \n", "\n", " uid x y line_indexes \n", "0 0.262829 0.617023 [0] \n", "1 0.495331 0.371764 [1] \n", "2 0.746342 0.707128 [2] \n", "3 0.491345 0.897368 [9] \n", "4 0.110171 0.412812 [4] " ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "points.head()" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.models import (\n", " Plot, \n", " ColumnDataSource, \n", " HoverTool, \n", " TapTool, \n", " Callback, \n", " Circle, \n", " MultiLine, \n", " DataRange1d,\n", " BoxZoomTool,\n", " WheelZoomTool,\n", " ResetTool,\n", " ResizeTool,\n", " Text,\n", ")\n", "\n", "GRAY_50 = '#fafafa'\n", "GRAY_100 = '#f5f5f5'\n", "GRAY_200 = '#eeeeee'\n", "GRAY_300 = '#e0e0e0'\n", "GRAY_400 = '#bdbdbd'\n", "GRAY_500 = '#9e9e9e'\n", "GRAY_600 = '#757575'\n", "GRAY_700 = '#616161'\n", "GRAY_800 = '#424242'\n", "GRAY_900 = '#212121'\n", "\n", "CYAN_50 = '#e0f7fa'\n", "CYAN_100 = '#b2ebf2'\n", "CYAN_200 = '#80deea'\n", "CYAN_300 = '#4dd0e1'\n", "CYAN_400 = '#26c6da'\n", "CYAN_500 = '#00bcd4'\n", "CYAN_600 = '#00acc1'\n", "CYAN_700 = '#0097a7'\n", "CYAN_800 = '#00838f'\n", "CYAN_900 = '#006064'\n", "\n", "PINK_50 = '#fce4ec'\n", "PINK_100 = '#f8bbd0'\n", "PINK_200 = '#f48fb1'\n", "PINK_300 = '#f06292'\n", "PINK_400 = '#ec407a'\n", "PINK_500 = '#e91e63'\n", "PINK_600 = '#d81b60'\n", "PINK_700 = '#c2185b'\n", "PINK_800 = '#ad1457'\n", "PINK_900 = '#880e4f'\n", "\n", "inner_circle_source = ColumnDataSource(points)\n", "text_source = ColumnDataSource(points)\n", "outer_circle_source = ColumnDataSource(points)\n", "line_source = ColumnDataSource(edges)\n", "\n", "p = Plot(\n", " x_range=DataRange1d(), \n", " y_range=DataRange1d(), \n", " plot_height=600, \n", " plot_width=800,\n", " background_fill=GRAY_900, \n", " border_fill=GRAY_900,\n", " outline_line_color=None,\n", ")\n", "\n", "# Add lines - lowest level\n", " \n", "line_props = dict(xs='xs', ys='ys', line_width=2, line_alpha=0.4)\n", "nonselected_line = MultiLine(line_color=CYAN_900, **line_props)\n", "selected_line = MultiLine(line_color=CYAN_50, **line_props)\n", "p.add_glyph(\n", " line_source, \n", " glyph=nonselected_line,\n", " nonselection_glyph=nonselected_line,\n", " selection_glyph=selected_line\n", ")\n", "\n", "# Add outer circle and tap\n", "\n", "outer_circle_props = dict(x='x', y='y', size=15, line_color=None, fill_alpha=0.5)\n", "nonselected_outer_circle = Circle(fill_color=CYAN_800, **outer_circle_props)\n", "selected_outer_circle = Circle(fill_color=PINK_400, **outer_circle_props)\n", "outer_cr = p.add_glyph(\n", " outer_circle_source, \n", " glyph=nonselected_outer_circle, \n", " nonselection_glyph=nonselected_outer_circle, \n", " selection_glyph=selected_outer_circle\n", ")\n", "p.add_tools(TapTool(renderers=[outer_cr]))\n", "tap_code = \"\"\"\n", " var empty_selection,\n", " points_selection,\n", " lines_selection,\n", " points_selected,\n", " row_idx,\n", " signed_by,\n", " lines_indexes,\n", " new_points_selection;\n", "\n", " // What is the selected node?\n", " points_selection = outer_points.get('selected');\n", " points_selected = points_selection['1d'].indices;\n", " row_idx = points_selected[0]\n", "\n", " // Who signed the selected node?\n", " points_data = outer_points.get('data')\n", " signed_by = points_data['signedby']; // The signed_by_columns\n", "\n", " // Set the selection to the signees\n", " new_points_selection = points_selected.concat(signed_by[row_idx]);\n", " points_selection['1d'].indices = new_points_selection;\n", " outer_points.set('selected', points_selection);\n", "\"\"\"\n", "tap_callback = Callback(args=dict(outer_points=outer_circle_source), code=tap_code)\n", "outer_circle_source.callback = tap_callback\n", "\n", "# Add text\n", "text_properties = dict(x=-0.2, y=-0.2, text='uid')\n", "nonselected_text = Text(text_color=GRAY_900, **text_properties) # Background color\n", "selected_text = Text(text_color=CYAN_50, **text_properties)\n", "p.add_glyph(\n", " text_source, \n", " glyph=nonselected_text,\n", " nonselection_glyph=nonselected_text,\n", " selection_glyph=selected_text\n", ")\n", "\n", "# Add inner circle and hover\n", "\n", "inner_circle_props = dict(x='x', y='y', size=10, fill_alpha=1)\n", "nonselected_inner_circle = Circle(fill_color=CYAN_700, line_color=None, **inner_circle_props)\n", "selected_inner_circle = Circle(fill_color=CYAN_200, line_color=CYAN_200, **inner_circle_props)\n", "inner_cr = p.add_glyph(\n", " inner_circle_source, \n", " glyph=nonselected_inner_circle, \n", " nonselection_glyph=nonselected_inner_circle, \n", " selection_glyph=selected_inner_circle\n", ")\n", "\n", "hover_code = \"\"\"\n", " var empty_selection,\n", " points_selection,\n", " lines_selection,\n", " text_selection,\n", " points_selected,\n", " row_idx,\n", " signed_by,\n", " lines_indexes,\n", " new_points_selection;\n", " \n", " empty_selection = {\n", " '0d': {'flag': false, 'indices': []},\n", " '1d': {'indices': []},\n", " '2d': {'indices': []}\n", " };\n", "\n", " // What is the selected node?\n", " points_selection = cb_data['index'];\n", " lines_selection = lines.get('selected');\n", " text_selection = text.get('selected');\n", " \n", " if (points_selection['1d'].indices.length > 0) {\n", " points_selected = points_selection['1d'].indices;\n", " \n", " // Highlight the node text\n", " text_selection['1d'].indices = points_selected;\n", " text.set('selected', text_selection);\n", " \n", " row_idx = points_selected[0]\n", "\n", " // Who signed the selected node?\n", " points_data = points.get('data')\n", " signed_by = points_data['signedby']; // The signed_by_columns\n", " lines_indexes = points_data['line_indexes']; // The line_indexes column\n", "\n", " // Set the selection to the signees\n", " new_points_selection = points_selected.concat(signed_by[row_idx]);\n", " points_selection['1d'].indices = new_points_selection;\n", " points.set('selected', points_selection);\n", "\n", " // Highlight the lines connected to selected_node\n", " lines_selection['1d'].indices = lines_indexes[row_idx];\n", " lines.set('selected', lines_selection);\n", " \n", " } else {\n", " points.set('selected', points_selection);\n", " lines.set('selected', empty_selection);\n", " text.set('selected', text_selection);\n", " }\n", "\"\"\"\n", "hover_callback = Callback(args=dict(points=inner_circle_source, lines=line_source, text=text_source), code=hover_code)\n", "p.add_tools(HoverTool(tooltips=None, renderers=[inner_cr], callback=hover_callback))\n", "\n", "#p.add_tools(WheelZoomTool(), BoxZoomTool(), ResetTool(), ResizeTool())\n", "\n", "show(p)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.3" } }, "nbformat": 4, "nbformat_minor": 0 }