diff --git a/examples/widgets/D3.ipynb b/examples/widgets/D3.ipynb index 4fe818c..6096ab6 100644 --- a/examples/widgets/D3.ipynb +++ b/examples/widgets/D3.ipynb @@ -11,114 +11,35 @@ "cell_type": "code", "collapsed": false, "input": [ - "from __future__ import print_function # py 2.7 compat" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "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": 2 - }, - { - "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", + "from __future__ import print_function # py 2.7 compat\n", "\n", - "def to_d3_json(graph):\n", - " data = json_graph.node_link_data(graph)\n", - " return json.dumps(data)" + "import networkx as nx" ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 3 - }, - { - "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": 4, - "text": [ - "'{\"multigraph\": false, \"directed\": false, \"links\": [{\"target\": 1, \"source\": 0}], \"graph\": [], \"nodes\": [{\"id\": 1}, {\"id\": 2}]}'" - ] - } - ], "prompt_number": 4 }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "G.add_node('test')\n", - "to_d3_json(G)" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 5, - "text": [ - "'{\"multigraph\": false, \"directed\": false, \"links\": [{\"target\": 1, \"source\": 0}], \"graph\": [], \"nodes\": [{\"id\": 1}, {\"id\": 2}, {\"id\": \"test\"}]}'" - ] - } - ], - "prompt_number": 5 + "source": [ + "This notebook demonstrates how NetworkX and D3 can be married using custom widget code." + ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ - "Listen To Graph Changes" + "Hooking NetworkX Graphs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Create a simple eventfull dictionary." + "NetworkX graphs do not have events that can be listened to. In order to watch the NetworkX graph object for changes a custom eventful graph object must be created. The custom eventful graph object will inherit from the base graph object and use special eventful dictionaries instead of standard Python dict instances. Because NetworkX nests dictionaries inside dictionaries, it's important that the eventful dictionary is capable of recognizing when a dictionary value is set to another dictionary instance. When this happens, the eventful dictionary needs to also make the new dictionary an eventful dictionary. This allows the eventful dictionary to listen to changes made to dictionaries within dictionaries." ] }, { @@ -212,255 +133,12 @@ " \n", " def clear(self):\n", " for key in list(self.keys()):\n", - " del self[key]\n" + " del self[key]" ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 6 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Test the eventful dictionary." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a = EventfulDict()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 7 - }, - { - "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": 8 - }, - { - "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": 9 - }, - { - "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": 10 - }, - { - "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": 11 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 12, - "text": [ - "{'a': 'goodbye', 'c': 'yay', 'd': 'no'}" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a.pop('a')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 13, - "text": [ - "'goodbye'" - ] - } - ], - "prompt_number": 13 - }, - { - "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": 14, - "text": [ - "('c', 'yay')" - ] - } - ], - "prompt_number": 14 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a['e'] = {}" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "add (e, {})\n" - ] - } - ], - "prompt_number": 15 - }, - { - "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": 16 - }, - { - "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": 17 - }, - { - "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": 18 + "prompt_number": 5 }, { "cell_type": "markdown", @@ -487,12 +165,30 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 19 + "prompt_number": 6 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To make sure that the eventful graph works, create a new graph and log the dictionary events raised." + ] }, { "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", "def echo_graph_events(eventful_graph):\n", " for key in ['graph', 'node', 'adj']:\n", " echo_dict_events(getattr(eventful_graph, key), prefix=key+' ')" @@ -500,48 +196,18 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 20 + "prompt_number": 7 }, { "cell_type": "code", "collapsed": false, "input": [ "G = EventfulGraph()\n", - "echo_graph_events(G)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 21 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "to_d3_json(G)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 22, - "text": [ - "'{\"multigraph\": false, \"directed\": false, \"links\": [], \"graph\": [], \"nodes\": []}'" - ] - } - ], - "prompt_number": 22 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ + "echo_graph_events(G)\n", + "\n", "G.add_node('hello')\n", "G.add_node('goodbye')\n", - "G.add_edges_from([(1,2),(1,3)])\n", - "to_d3_json(G)" + "G.add_edges_from([(1,2),(1,3)])" ], "language": "python", "metadata": {}, @@ -565,17 +231,9 @@ "adj set (1, {2: {}, 3: {}})\n", "adj set (3, {1: {}})\n" ] - }, - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 23, - "text": [ - "'{\"multigraph\": false, \"directed\": false, \"links\": [{\"target\": 1, \"source\": 0}, {\"target\": 3, \"source\": 1}], \"graph\": [], \"nodes\": [{\"id\": 3}, {\"id\": 1}, {\"id\": \"goodbye\"}, {\"id\": 2}, {\"id\": \"hello\"}]}'" - ] } ], - "prompt_number": 23 + "prompt_number": 8 }, { "cell_type": "code", @@ -589,13 +247,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 24, + "prompt_number": 9, "text": [ - "{3: {1: {}}, 1: {2: {}, 3: {}}, 'goodbye': {}, 2: {1: {}}, 'hello': {}}" + "{3: {1: {}}, 1: {2: {}, 3: {}}, 'hello': {}, 2: {1: {}}, 'goodbye': {}}" ] } ], - "prompt_number": 24 + "prompt_number": 9 }, { "cell_type": "code", @@ -609,70 +267,33 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 25, + "prompt_number": 10, "text": [ - "{3: {}, 1: {}, 'goodbye': {}, 2: {}, 'hello': {}}" + "{3: {}, 1: {}, 'hello': {}, 2: {}, 'goodbye': {}}" ] } ], - "prompt_number": 25 + "prompt_number": 10 }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ - "Custom Widget" + "D3 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": 26 - }, - { - "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": 27 + "prompt_number": 11 }, { "cell_type": "code", @@ -687,9 +308,6 @@ " 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", @@ -700,8 +318,15 @@ " \n", " \n", " def _repr_widget_(self, *pargs, **kwargs):\n", - " self.initial_json = to_d3_json(self._eventful_graph)\n", + " \n", + " # Show the widget, then send the current state\n", " Widget._repr_widget_(self, *pargs, **kwargs)\n", + " for (key, value) in self._eventful_graph.graph.items():\n", + " self.send({'dict': 'graph', 'action': 'add', 'key': key, 'value': value})\n", + " for (key, value) in self._eventful_graph.node.items():\n", + " self.send({'dict': 'node', 'action': 'add', 'key': key, 'value': value})\n", + " for (key, value) in self._eventful_graph.adj.items():\n", + " self.send({'dict': 'adj', 'action': 'add', 'key': key, 'value': value})\n", " \n", " \n", " def _send_dict_changes(self, eventful_dict, dict_name):\n", @@ -719,7 +344,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 28 + "prompt_number": 20 }, { "cell_type": "code", @@ -727,7 +352,7 @@ "input": [ "%%javascript\n", "\n", - "require([\"notebook/js/widget\"], function(){\n", + "require([\"http://d3js.org/d3.v3.min.js\", \"notebook/js/widget\"], function(){\n", " \n", " // Define the ForceDirectedGraphModel and register it with the widget manager.\n", " var ForceDirectedGraphModel = IPython.WidgetModel.extend({});\n", @@ -740,6 +365,7 @@ " this.guid = 'd3force' + IPython.utils.uuid();\n", " this.setElement($('
', {id: this.guid}));\n", " this.model.on_msg($.proxy(this.handle_msg, this));\n", + " this.has_drawn = false;\n", " },\n", " \n", " add_node: function(id){\n", @@ -860,34 +486,28 @@ " },\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", + " if (! this.has_drawn) {\n", + " this.has_drawn = true;\n", " \n", - " var width = 860,\n", - " height = 400;\n", + " var width = 400,\n", + " height = 300;\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", + " this.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", + " this.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", @@ -910,7 +530,7 @@ { "javascript": [ "\n", - "require([\"notebook/js/widget\"], function(){\n", + "require([\"http://d3js.org/d3.v3.min.js\", \"notebook/js/widget\"], function(){\n", " \n", " // Define the ForceDirectedGraphModel and register it with the widget manager.\n", " var ForceDirectedGraphModel = IPython.WidgetModel.extend({});\n", @@ -923,6 +543,7 @@ " this.guid = 'd3force' + IPython.utils.uuid();\n", " this.setElement($('
', {id: this.guid}));\n", " this.model.on_msg($.proxy(this.handle_msg, this));\n", + " this.has_drawn = false;\n", " },\n", " \n", " add_node: function(id){\n", @@ -1043,34 +664,28 @@ " },\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", + " if (! this.has_drawn) {\n", + " this.has_drawn = true;\n", " \n", - " var width = 860,\n", - " height = 400;\n", + " var width = 400,\n", + " height = 300;\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", + " this.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", + " this.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", @@ -1090,11 +705,19 @@ "metadata": {}, "output_type": "display_data", "text": [ - "" + "" ] } ], - "prompt_number": 29 + "prompt_number": 28 + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Test" + ] }, { "cell_type": "code", @@ -1103,55 +726,25 @@ "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)" + "G.add_edges_from([(1,2),(1,3)])" ], "language": "python", "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 36, - "text": [ - "'{\"multigraph\": false, \"directed\": false, \"links\": [{\"target\": 1, \"source\": 0}, {\"target\": 3, \"source\": 1}], \"graph\": [], \"nodes\": [{\"id\": 3}, {\"id\": 1}, {\"id\": \"goodbye\"}, {\"id\": 2}, {\"id\": \"hello\"}]}'" - ] - } - ], - "prompt_number": 36 + "outputs": [], + "prompt_number": 29 }, { "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", + "floating_container = widgets.ContainerWidget(default_view_name='ModalView')\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)" + "display(floating_container)" ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 37 + "outputs": [] }, { "cell_type": "code", @@ -1163,7 +756,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 38 + "prompt_number": 25 }, { "cell_type": "code", @@ -1174,7 +767,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 39 + "prompt_number": 26 }, { "cell_type": "code", @@ -1186,7 +779,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 40 + "prompt_number": 27 }, { "cell_type": "code",