diff --git a/examples/widgets/D3.ipynb b/examples/widgets/D3.ipynb new file mode 100644 index 0000000..ae9963e --- /dev/null +++ b/examples/widgets/D3.ipynb @@ -0,0 +1,1222 @@ +{ + "metadata": { + "name": "" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Validate NetworkX version" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import networkx as nx\n", + "version = float('.'.join(nx.__version__.split('.')[0:2]))\n", + "if version < 1.8:\n", + " raise Exception('This notebook requires networkx version 1.8 or later. Version %s is installed on this machine.' % nx.__version__)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 1 + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Simple Output Test" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from networkx.readwrite import json_graph\n", + "import json\n", + "\n", + "def to_d3_json(graph):\n", + " data = json_graph.node_link_data(graph)\n", + " return json.dumps(data)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 2 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G = nx.Graph([(1,2)])\n", + "to_d3_json(G)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 3, + "text": [ + "'{\"directed\": false, \"graph\": [], \"nodes\": [{\"id\": 1}, {\"id\": 2}], \"links\": [{\"source\": 0, \"target\": 1}], \"multigraph\": false}'" + ] + } + ], + "prompt_number": 3 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G.add_node('test')\n", + "to_d3_json(G)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 4, + "text": [ + "'{\"directed\": false, \"graph\": [], \"nodes\": [{\"id\": \"test\"}, {\"id\": 1}, {\"id\": 2}], \"links\": [{\"source\": 1, \"target\": 2}], \"multigraph\": false}'" + ] + } + ], + "prompt_number": 4 + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Listen To Graph Changes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a simple eventfull dictionary." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class EventfulDict(dict):\n", + " \n", + " def __init__(self, *args, **kwargs):\n", + " self._add_callbacks = []\n", + " self._del_callbacks = []\n", + " self._set_callbacks = []\n", + " dict.__init__(self, *args, **kwargs)\n", + " \n", + " def on_add(self, callback, remove=False):\n", + " self._register_callback(self._add_callbacks, callback, remove)\n", + " def on_del(self, callback, remove=False):\n", + " self._register_callback(self._del_callbacks, callback, remove)\n", + " def on_set(self, callback, remove=False):\n", + " self._register_callback(self._set_callbacks, callback, remove)\n", + " def _register_callback(self, callback_list, callback, remove=False):\n", + " if callable(callback):\n", + " if remove and callback in callback_list:\n", + " callback_list.remove(callback)\n", + " elif not remove and not callback in callback_list:\n", + " callback_list.append(callback)\n", + " else:\n", + " raise Exception('Callback must be callable.')\n", + "\n", + " def _handle_add(self, key, value):\n", + " self._try_callbacks(self._add_callbacks, key, value)\n", + " def _handle_del(self, key):\n", + " self._try_callbacks(self._del_callbacks, key)\n", + " def _handle_set(self, key, value):\n", + " self._try_callbacks(self._set_callbacks, key, value)\n", + " def _try_callbacks(self, callback_list, *pargs, **kwargs):\n", + " for callback in callback_list:\n", + " callback(*pargs, **kwargs)\n", + " \n", + " def __setitem__(self, key, value):\n", + " return_val = None\n", + " exists = False\n", + " if key in self:\n", + " exists = True\n", + " \n", + " # If the user sets the property to a new dict, make the dict\n", + " # eventful and listen to the changes of it ONLY if it is not\n", + " # already eventful. Any modification to this new dict will\n", + " # fire a set event of the parent dict.\n", + " if isinstance(value, dict) and not isinstance(value, EventfulDict):\n", + " new_dict = EventfulDict(value)\n", + " \n", + " def handle_change(*pargs, **kwargs):\n", + " self._try_callbacks(self._set_callbacks, key, dict.__getitem__(self, key))\n", + " \n", + " new_dict.on_add(handle_change)\n", + " new_dict.on_del(handle_change)\n", + " new_dict.on_set(handle_change)\n", + " return_val = dict.__setitem__(self, key, new_dict)\n", + " else:\n", + " return_val = dict.__setitem__(self, key, value)\n", + " \n", + " if exists:\n", + " self._handle_set(key, value)\n", + " else:\n", + " self._handle_add(key, value)\n", + " return return_val\n", + " \n", + "\n", + " def __delitem__(self, key):\n", + " return_val = dict.__delitem__(self, key)\n", + " self._handle_del(key)\n", + " return return_val\n", + "\n", + " \n", + " def pop(self, key):\n", + " return_val = dict.pop(self, key)\n", + " if key in self:\n", + " self._handle_del(key)\n", + " return return_val\n", + "\n", + " def popitem(self):\n", + " popped = dict.popitem(self)\n", + " if popped is not None and popped[0] is not None:\n", + " self._handle_del(popped[0])\n", + " return popped\n", + "\n", + " def update(self, other_dict):\n", + " for (key, value) in other_dict.items():\n", + " self[key] = value\n", + " \n", + " def clear(self):\n", + " for key in list(self.keys()):\n", + " del self[key]\n" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 5 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test the eventful dictionary." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a = EventfulDict()" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 6 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "def echo_dict_events(eventful_dict, prefix=''):\n", + " def key_add(key, value):\n", + " print prefix + 'add (%s, %s)' % (key, str(value))\n", + " def key_set(key, value):\n", + " print prefix + 'set (%s, %s)' % (key, str(value))\n", + " def key_del(key):\n", + " print prefix + 'del %s' % key\n", + " eventful_dict.on_add(key_add)\n", + " eventful_dict.on_set(key_set)\n", + " eventful_dict.on_del(key_del)\n", + " \n", + "echo_dict_events(a)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 7 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a['a'] = 'hello'" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "add (a, hello)\n" + ] + } + ], + "prompt_number": 8 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a['a'] = 'goodbye'" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "set (a, goodbye)\n" + ] + } + ], + "prompt_number": 9 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "b = {'c': 'yay', 'd': 'no'}\n", + "a.update(b)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "add (c, yay)\n", + "add (d, no)\n" + ] + } + ], + "prompt_number": 10 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 11, + "text": [ + "{'a': 'goodbye', 'c': 'yay', 'd': 'no'}" + ] + } + ], + "prompt_number": 11 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a.pop('a')" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 12, + "text": [ + "'goodbye'" + ] + } + ], + "prompt_number": 12 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a.popitem()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "del c\n" + ] + }, + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 13, + "text": [ + "('c', 'yay')" + ] + } + ], + "prompt_number": 13 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a['e'] = {}" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "add (e, {})\n" + ] + } + ], + "prompt_number": 14 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a['e']['a'] = 0" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "set (e, {'a': 0})\n" + ] + } + ], + "prompt_number": 15 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a['e']['b'] = 1" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "set (e, {'a': 0, 'b': 1})\n" + ] + } + ], + "prompt_number": 16 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "a.clear()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "del e\n", + "del d\n" + ] + } + ], + "prompt_number": 17 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Override the NetworkX Graph object to make an eventful graph." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class EventfulGraph(nx.Graph):\n", + " \n", + " def __init__(self, *pargs, **kwargs):\n", + " \"\"\"Initialize a graph with edges, name, graph attributes.\"\"\"\n", + " super(EventfulGraph, self).__init__(*pargs, **kwargs)\n", + " \n", + " self.graph = EventfulDict(self.graph)\n", + " self.node = EventfulDict(self.node)\n", + " self.adj = EventfulDict(self.adj)\n", + " " + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 18 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "def echo_graph_events(eventful_graph):\n", + " for key in ['graph', 'node', 'adj']:\n", + " echo_dict_events(getattr(eventful_graph, key), prefix=key+' ')" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 19 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G = EventfulGraph()\n", + "echo_graph_events(G)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 20 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "to_d3_json(G)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 21, + "text": [ + "'{\"directed\": false, \"graph\": [], \"nodes\": [], \"links\": [], \"multigraph\": false}'" + ] + } + ], + "prompt_number": 21 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G.add_node('hello')\n", + "G.add_node('goodbye')\n", + "G.add_edges_from([(1,2),(1,3)])\n", + "to_d3_json(G)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "adj add (hello, {})\n", + "node add (hello, {})\n", + "adj add (goodbye, {})\n", + "node add (goodbye, {})\n", + "adj add (1, {})\n", + "node add (1, {})\n", + "adj add (2, {})\n", + "node add (2, {})\n", + "adj set (1, {2: {}})\n", + "adj set (2, {1: {}})\n", + "adj add (3, {})\n", + "node add (3, {})\n", + "adj set (1, {2: {}, 3: {}})\n", + "adj set (3, {1: {}})\n" + ] + }, + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 22, + "text": [ + "'{\"directed\": false, \"graph\": [], \"nodes\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}, {\"id\": \"hello\"}, {\"id\": \"goodbye\"}], \"links\": [{\"source\": 0, \"target\": 1}, {\"source\": 0, \"target\": 2}], \"multigraph\": false}'" + ] + } + ], + "prompt_number": 22 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G.adj" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 23, + "text": [ + "{1: {2: {}, 3: {}}, 2: {1: {}}, 3: {1: {}}, 'goodbye': {}, 'hello': {}}" + ] + } + ], + "prompt_number": 23 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G.node" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 24, + "text": [ + "{1: {}, 2: {}, 3: {}, 'goodbye': {}, 'hello': {}}" + ] + } + ], + "prompt_number": 24 + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Custom Widget" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%html\n", + "
Loading D3...
\n", + "" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "
Loading D3...
\n", + "" + ], + "metadata": {}, + "output_type": "display_data", + "text": [ + "" + ] + } + ], + "prompt_number": 25 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from IPython.html import widgets # Widget definitions\n", + "from IPython.display import display # Used to display widgets in the notebook" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 26 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# Import the base Widget class and the traitlets Unicode class.\n", + "from IPython.html.widgets import Widget\n", + "from IPython.utils.traitlets import Unicode\n", + "\n", + "# Define our ForceDirectedGraphWidget and its target model and default view.\n", + "class ForceDirectedGraphWidget(Widget):\n", + " target_name = Unicode('ForceDirectedGraphModel')\n", + " default_view_name = Unicode('D3ForceDirectedGraphView')\n", + " \n", + " _keys = ['initial_json']\n", + " initial_json = Unicode()\n", + " \n", + " def __init__(self, eventful_graph, *pargs, **kwargs):\n", + " Widget.__init__(self, *pargs, **kwargs)\n", + " \n", + " self._eventful_graph = eventful_graph\n", + " self._send_dict_changes(eventful_graph.graph, 'graph')\n", + " self._send_dict_changes(eventful_graph.node, 'node')\n", + " self._send_dict_changes(eventful_graph.adj, 'adj')\n", + " \n", + " \n", + " def _repr_widget_(self, *pargs, **kwargs):\n", + " self.initial_json = to_d3_json(self._eventful_graph)\n", + " Widget._repr_widget_(self, *pargs, **kwargs)\n", + " \n", + " \n", + " def _send_dict_changes(self, eventful_dict, dict_name):\n", + " def key_add(key, value):\n", + " self.send({'dict': dict_name, 'action': 'add', 'key': key, 'value': value})\n", + " def key_set(key, value):\n", + " self.send({'dict': dict_name, 'action': 'set', 'key': key, 'value': value})\n", + " def key_del(key):\n", + " self.send({'dict': dict_name, 'action': 'del', 'key': key})\n", + " eventful_dict.on_add(key_add)\n", + " eventful_dict.on_set(key_set)\n", + " eventful_dict.on_del(key_del)\n", + " " + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 27 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%javascript\n", + "\n", + "require([\"notebook/js/widget\"], function(){\n", + " \n", + " // Define the ForceDirectedGraphModel and register it with the widget manager.\n", + " var ForceDirectedGraphModel = IPython.WidgetModel.extend({});\n", + " IPython.widget_manager.register_widget_model('ForceDirectedGraphModel', ForceDirectedGraphModel);\n", + " \n", + " // Define the D3ForceDirectedGraphView\n", + " var D3ForceDirectedGraphView = IPython.WidgetView.extend({\n", + " \n", + " render: function(){\n", + " this.guid = 'd3force' + IPython.utils.uuid();\n", + " this.setElement($('
', {id: this.guid}));\n", + " this.model.on_msg($.proxy(this.handle_msg, this));\n", + " },\n", + " \n", + " add_node: function(id){\n", + " var index = this.find_node(id);\n", + " if (index == -1) {\n", + " var node = {id: id};\n", + " this.nodes.push(node);\n", + " return node;\n", + " } else {\n", + " return this.nodes[index];\n", + " }\n", + " },\n", + " \n", + " remove_node: function(id){\n", + " var found_index = this.find_node(id);\n", + " if (found_index>=0) {\n", + " this.nodes.splice(found_index, 1);\n", + " }\n", + " \n", + " clear_links(id);\n", + " },\n", + " \n", + " find_node: function(id){\n", + " var found_index = -1;\n", + " for (var index in this.nodes) {\n", + " if (this.nodes[index].id == id) {\n", + " found_index = index;\n", + " break;\n", + " }\n", + " }\n", + " return found_index;\n", + " },\n", + " \n", + " clear_links: function(id){\n", + " \n", + " // Remove existing links\n", + " var found_indexs = [];\n", + " for (var index in this.links) {\n", + " if (this.links[index].source.id == id) {\n", + " found_indexs.push(index);\n", + " }\n", + " }\n", + " \n", + " for (var index in found_indexs) {\n", + " this.links.splice(found_indexs[index], 1);\n", + " }\n", + " },\n", + " \n", + " handle_msg: function(content){\n", + " var dict = content.dict;\n", + " var action = content.action;\n", + " var key = content.key;\n", + " console.log(dict, action, key);\n", + " \n", + " if (dict=='node') {\n", + " \n", + " // Only support node ADD and DEL actions for now...\n", + " if (action=='add') {\n", + " this.add_node(key)\n", + " } else if (action=='del') {\n", + " this.remove_node(key);\n", + " }\n", + " \n", + " } else if (dict=='adj') {\n", + " this.clear_links(key);\n", + " \n", + " // Add all links\n", + " if (action != 'del') {\n", + " var value = content.value;\n", + " for (var link_to in value) {\n", + " var source_node = this.add_node(key);\n", + " var target_node = this.add_node(link_to);\n", + " this.links.push({source: source_node, target: target_node});\n", + " }\n", + " }\n", + " }\n", + " this.start();\n", + " },\n", + " \n", + " start: function() {\n", + " var node = this.svg.selectAll(\".node\"),\n", + " link = this.svg.selectAll(\".link\");\n", + " \n", + " var link = link.data(this.force.links(), function(d) { return d.source.id + \"-\" + d.target.id; });\n", + " link.enter()\n", + " .insert(\"line\", \".node\")\n", + " .attr(\"class\", \"link\")\n", + " .style(\"stroke-width\", '1.5px')\n", + " .style('stroke', '#999');\n", + " link.exit().remove();\n", + " \n", + " var node = node.data(this.force.nodes(), function(d) { return d.id;});\n", + " var that = this;\n", + " node.enter()\n", + " .append(\"circle\")\n", + " .attr(\"class\", function(d) { return \"node \" + d.id; })\n", + " .attr(\"r\", 8)\n", + " .style(\"fill\", function(d) { return that.color(d.group); })\n", + " .style(\"stroke\", \"#fff\")\n", + " .style(\"stroke-width\", \"1.5px\")\n", + " .call(this.force.drag);\n", + " node.exit().remove();\n", + " \n", + " this.force.start();\n", + " },\n", + " \n", + " tick: function() {\n", + " var node = this.svg.selectAll(\".node\"),\n", + " link = this.svg.selectAll(\".link\");\n", + " \n", + " link.attr(\"x1\", function(d) { return d.source.x; })\n", + " .attr(\"y1\", function(d) { return d.source.y; })\n", + " .attr(\"x2\", function(d) { return d.target.x; })\n", + " .attr(\"y2\", function(d) { return d.target.y; });\n", + " \n", + " node.attr(\"cx\", function(d) { return d.x; })\n", + " .attr(\"cy\", function(d) { return d.y; });\n", + " },\n", + " \n", + " update: function(){\n", + " var initial_json = this.model.get('initial_json');\n", + " if (this.initial_json != initial_json) {\n", + " this.initial_json = initial_json;\n", + " \n", + " var width = 860,\n", + " height = 400;\n", + " \n", + " this.color = d3.scale.category20();\n", + " \n", + " var graph = JSON.parse(initial_json);\n", + " this.nodes = [];\n", + " $.extend(this.nodes, graph.nodes);\n", + " this.links = [];\n", + " $.extend(this.links, graph.links);\n", + " \n", + " var force = d3.layout.force()\n", + " .nodes(this.nodes)\n", + " .links(this.links)\n", + " .charge(-120)\n", + " .linkDistance(30)\n", + " .size([width, height])\n", + " .on(\"tick\", $.proxy(this.tick, this));\n", + " this.force = force;\n", + " \n", + " var svg = d3.select(\"#\" + this.guid).append(\"svg\")\n", + " .attr(\"width\", width)\n", + " .attr(\"height\", height);\n", + " this.svg = svg;\n", + " \n", + " var that = this;\n", + " setTimeout(function() {\n", + " that.start();\n", + " }, 0);\n", + " }\n", + " \n", + " return IPython.WidgetView.prototype.update.call(this);\n", + " },\n", + " \n", + " });\n", + " \n", + " // Register the D3ForceDirectedGraphView with the widget manager.\n", + " IPython.widget_manager.register_widget_view('D3ForceDirectedGraphView', D3ForceDirectedGraphView);\n", + "});" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "javascript": [ + "\n", + "require([\"notebook/js/widget\"], function(){\n", + " \n", + " // Define the ForceDirectedGraphModel and register it with the widget manager.\n", + " var ForceDirectedGraphModel = IPython.WidgetModel.extend({});\n", + " IPython.widget_manager.register_widget_model('ForceDirectedGraphModel', ForceDirectedGraphModel);\n", + " \n", + " // Define the D3ForceDirectedGraphView\n", + " var D3ForceDirectedGraphView = IPython.WidgetView.extend({\n", + " \n", + " render: function(){\n", + " this.guid = 'd3force' + IPython.utils.uuid();\n", + " this.setElement($('
', {id: this.guid}));\n", + " this.model.on_msg($.proxy(this.handle_msg, this));\n", + " },\n", + " \n", + " add_node: function(id){\n", + " var index = this.find_node(id);\n", + " if (index == -1) {\n", + " var node = {id: id};\n", + " this.nodes.push(node);\n", + " return node;\n", + " } else {\n", + " return this.nodes[index];\n", + " }\n", + " },\n", + " \n", + " remove_node: function(id){\n", + " var found_index = this.find_node(id);\n", + " if (found_index>=0) {\n", + " this.nodes.splice(found_index, 1);\n", + " }\n", + " \n", + " clear_links(id);\n", + " },\n", + " \n", + " find_node: function(id){\n", + " var found_index = -1;\n", + " for (var index in this.nodes) {\n", + " if (this.nodes[index].id == id) {\n", + " found_index = index;\n", + " break;\n", + " }\n", + " }\n", + " return found_index;\n", + " },\n", + " \n", + " clear_links: function(id){\n", + " \n", + " // Remove existing links\n", + " var found_indexs = [];\n", + " for (var index in this.links) {\n", + " if (this.links[index].source.id == id) {\n", + " found_indexs.push(index);\n", + " }\n", + " }\n", + " \n", + " for (var index in found_indexs) {\n", + " this.links.splice(found_indexs[index], 1);\n", + " }\n", + " },\n", + " \n", + " handle_msg: function(content){\n", + " var dict = content.dict;\n", + " var action = content.action;\n", + " var key = content.key;\n", + " console.log(dict, action, key);\n", + " \n", + " if (dict=='node') {\n", + " \n", + " // Only support node ADD and DEL actions for now...\n", + " if (action=='add') {\n", + " this.add_node(key)\n", + " } else if (action=='del') {\n", + " this.remove_node(key);\n", + " }\n", + " \n", + " } else if (dict=='adj') {\n", + " this.clear_links(key);\n", + " \n", + " // Add all links\n", + " if (action != 'del') {\n", + " var value = content.value;\n", + " for (var link_to in value) {\n", + " var source_node = this.add_node(key);\n", + " var target_node = this.add_node(link_to);\n", + " this.links.push({source: source_node, target: target_node});\n", + " }\n", + " }\n", + " }\n", + " this.start();\n", + " },\n", + " \n", + " start: function() {\n", + " var node = this.svg.selectAll(\".node\"),\n", + " link = this.svg.selectAll(\".link\");\n", + " \n", + " var link = link.data(this.force.links(), function(d) { return d.source.id + \"-\" + d.target.id; });\n", + " link.enter()\n", + " .insert(\"line\", \".node\")\n", + " .attr(\"class\", \"link\")\n", + " .style(\"stroke-width\", '1.5px')\n", + " .style('stroke', '#999');\n", + " link.exit().remove();\n", + " \n", + " var node = node.data(this.force.nodes(), function(d) { return d.id;});\n", + " var that = this;\n", + " node.enter()\n", + " .append(\"circle\")\n", + " .attr(\"class\", function(d) { return \"node \" + d.id; })\n", + " .attr(\"r\", 8)\n", + " .style(\"fill\", function(d) { return that.color(d.group); })\n", + " .style(\"stroke\", \"#fff\")\n", + " .style(\"stroke-width\", \"1.5px\")\n", + " .call(this.force.drag);\n", + " node.exit().remove();\n", + " \n", + " this.force.start();\n", + " },\n", + " \n", + " tick: function() {\n", + " var node = this.svg.selectAll(\".node\"),\n", + " link = this.svg.selectAll(\".link\");\n", + " \n", + " link.attr(\"x1\", function(d) { return d.source.x; })\n", + " .attr(\"y1\", function(d) { return d.source.y; })\n", + " .attr(\"x2\", function(d) { return d.target.x; })\n", + " .attr(\"y2\", function(d) { return d.target.y; });\n", + " \n", + " node.attr(\"cx\", function(d) { return d.x; })\n", + " .attr(\"cy\", function(d) { return d.y; });\n", + " },\n", + " \n", + " update: function(){\n", + " var initial_json = this.model.get('initial_json');\n", + " if (this.initial_json != initial_json) {\n", + " this.initial_json = initial_json;\n", + " \n", + " var width = 860,\n", + " height = 400;\n", + " \n", + " this.color = d3.scale.category20();\n", + " \n", + " var graph = JSON.parse(initial_json);\n", + " this.nodes = [];\n", + " $.extend(this.nodes, graph.nodes);\n", + " this.links = [];\n", + " $.extend(this.links, graph.links);\n", + " \n", + " var force = d3.layout.force()\n", + " .nodes(this.nodes)\n", + " .links(this.links)\n", + " .charge(-120)\n", + " .linkDistance(30)\n", + " .size([width, height])\n", + " .on(\"tick\", $.proxy(this.tick, this));\n", + " this.force = force;\n", + " \n", + " var svg = d3.select(\"#\" + this.guid).append(\"svg\")\n", + " .attr(\"width\", width)\n", + " .attr(\"height\", height);\n", + " this.svg = svg;\n", + " \n", + " var that = this;\n", + " setTimeout(function() {\n", + " that.start();\n", + " }, 0);\n", + " }\n", + " \n", + " return IPython.WidgetView.prototype.update.call(this);\n", + " },\n", + " \n", + " });\n", + " \n", + " // Register the D3ForceDirectedGraphView with the widget manager.\n", + " IPython.widget_manager.register_widget_view('D3ForceDirectedGraphView', D3ForceDirectedGraphView);\n", + "});" + ], + "metadata": {}, + "output_type": "display_data", + "text": [ + "" + ] + } + ], + "prompt_number": 28 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G = EventfulGraph()\n", + "G.add_node('hello')\n", + "G.add_node('goodbye')\n", + "G.add_edges_from([(1,2),(1,3)])\n", + "to_d3_json(G)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 30, + "text": [ + "'{\"directed\": false, \"graph\": [], \"nodes\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}, {\"id\": \"hello\"}, {\"id\": \"goodbye\"}], \"links\": [{\"source\": 0, \"target\": 1}, {\"source\": 0, \"target\": 2}], \"multigraph\": false}'" + ] + } + ], + "prompt_number": 30 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "floating_container = widgets.ContainerWidget()\n", + "floating_container.set_css({\n", + " 'position': 'relative',\n", + " 'left': '0px',\n", + " 'top': '0px',\n", + " 'z-index': '999',\n", + " 'background': '#FFF',\n", + " 'opacity': '0.8'\n", + "})\n", + "\n", + "d3 = ForceDirectedGraphWidget(G, parent=floating_container)\n", + "display(floating_container)\n", + "\n", + "detach_button = widgets.ButtonWidget(description=\"Detach\")\n", + "def handle_detach(sender):\n", + " if sender.description == \"Detach\":\n", + " sender.description = \"Attach\"\n", + " floating_container.set_css('position', 'absolute')\n", + " else:\n", + " sender.description = \"Detach\"\n", + " floating_container.set_css('position', 'relative')\n", + "detach_button.on_click(handle_detach)\n", + "display(detach_button)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 31 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G.add_node('beep')\n", + "G.add_node('beep2')" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 32 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G.remove_node('beep')" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 33 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "\n", + "G.add_edges_from([(4,5),(4,6)])" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 34 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import time\n", + "for i in range(30):\n", + " G.add_edges_from([(7+i, 8+i), (7+i, 9+i)])\n", + " time.sleep(0.1)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 35 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "\n", + "for i in range(30):\n", + " G.add_edge(7, i+8)\n", + " time.sleep(0.25)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 36 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "G.clear()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file