##// END OF EJS Templates
Basic NetworkX/D3 example
Jonathan Frederic -
Show More
This diff has been collapsed as it changes many lines, (1222 lines changed) Show them Hide them
@@ -0,0 +1,1222 b''
1 {
2 "metadata": {
3 "name": ""
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
9 "cells": [
10 {
11 "cell_type": "heading",
12 "level": 1,
13 "metadata": {},
14 "source": [
15 "Validate NetworkX version"
16 ]
17 },
18 {
19 "cell_type": "code",
20 "collapsed": false,
21 "input": [
22 "import networkx as nx\n",
23 "version = float('.'.join(nx.__version__.split('.')[0:2]))\n",
24 "if version < 1.8:\n",
25 " raise Exception('This notebook requires networkx version 1.8 or later. Version %s is installed on this machine.' % nx.__version__)"
26 ],
27 "language": "python",
28 "metadata": {},
29 "outputs": [],
30 "prompt_number": 1
31 },
32 {
33 "cell_type": "heading",
34 "level": 1,
35 "metadata": {},
36 "source": [
37 "Simple Output Test"
38 ]
39 },
40 {
41 "cell_type": "code",
42 "collapsed": false,
43 "input": [
44 "from networkx.readwrite import json_graph\n",
45 "import json\n",
46 "\n",
47 "def to_d3_json(graph):\n",
48 " data = json_graph.node_link_data(graph)\n",
49 " return json.dumps(data)"
50 ],
51 "language": "python",
52 "metadata": {},
53 "outputs": [],
54 "prompt_number": 2
55 },
56 {
57 "cell_type": "code",
58 "collapsed": false,
59 "input": [
60 "G = nx.Graph([(1,2)])\n",
61 "to_d3_json(G)"
62 ],
63 "language": "python",
64 "metadata": {},
65 "outputs": [
66 {
67 "metadata": {},
68 "output_type": "pyout",
69 "prompt_number": 3,
70 "text": [
71 "'{\"directed\": false, \"graph\": [], \"nodes\": [{\"id\": 1}, {\"id\": 2}], \"links\": [{\"source\": 0, \"target\": 1}], \"multigraph\": false}'"
72 ]
73 }
74 ],
75 "prompt_number": 3
76 },
77 {
78 "cell_type": "code",
79 "collapsed": false,
80 "input": [
81 "G.add_node('test')\n",
82 "to_d3_json(G)"
83 ],
84 "language": "python",
85 "metadata": {},
86 "outputs": [
87 {
88 "metadata": {},
89 "output_type": "pyout",
90 "prompt_number": 4,
91 "text": [
92 "'{\"directed\": false, \"graph\": [], \"nodes\": [{\"id\": \"test\"}, {\"id\": 1}, {\"id\": 2}], \"links\": [{\"source\": 1, \"target\": 2}], \"multigraph\": false}'"
93 ]
94 }
95 ],
96 "prompt_number": 4
97 },
98 {
99 "cell_type": "heading",
100 "level": 1,
101 "metadata": {},
102 "source": [
103 "Listen To Graph Changes"
104 ]
105 },
106 {
107 "cell_type": "markdown",
108 "metadata": {},
109 "source": [
110 "Create a simple eventfull dictionary."
111 ]
112 },
113 {
114 "cell_type": "code",
115 "collapsed": false,
116 "input": [
117 "class EventfulDict(dict):\n",
118 " \n",
119 " def __init__(self, *args, **kwargs):\n",
120 " self._add_callbacks = []\n",
121 " self._del_callbacks = []\n",
122 " self._set_callbacks = []\n",
123 " dict.__init__(self, *args, **kwargs)\n",
124 " \n",
125 " def on_add(self, callback, remove=False):\n",
126 " self._register_callback(self._add_callbacks, callback, remove)\n",
127 " def on_del(self, callback, remove=False):\n",
128 " self._register_callback(self._del_callbacks, callback, remove)\n",
129 " def on_set(self, callback, remove=False):\n",
130 " self._register_callback(self._set_callbacks, callback, remove)\n",
131 " def _register_callback(self, callback_list, callback, remove=False):\n",
132 " if callable(callback):\n",
133 " if remove and callback in callback_list:\n",
134 " callback_list.remove(callback)\n",
135 " elif not remove and not callback in callback_list:\n",
136 " callback_list.append(callback)\n",
137 " else:\n",
138 " raise Exception('Callback must be callable.')\n",
139 "\n",
140 " def _handle_add(self, key, value):\n",
141 " self._try_callbacks(self._add_callbacks, key, value)\n",
142 " def _handle_del(self, key):\n",
143 " self._try_callbacks(self._del_callbacks, key)\n",
144 " def _handle_set(self, key, value):\n",
145 " self._try_callbacks(self._set_callbacks, key, value)\n",
146 " def _try_callbacks(self, callback_list, *pargs, **kwargs):\n",
147 " for callback in callback_list:\n",
148 " callback(*pargs, **kwargs)\n",
149 " \n",
150 " def __setitem__(self, key, value):\n",
151 " return_val = None\n",
152 " exists = False\n",
153 " if key in self:\n",
154 " exists = True\n",
155 " \n",
156 " # If the user sets the property to a new dict, make the dict\n",
157 " # eventful and listen to the changes of it ONLY if it is not\n",
158 " # already eventful. Any modification to this new dict will\n",
159 " # fire a set event of the parent dict.\n",
160 " if isinstance(value, dict) and not isinstance(value, EventfulDict):\n",
161 " new_dict = EventfulDict(value)\n",
162 " \n",
163 " def handle_change(*pargs, **kwargs):\n",
164 " self._try_callbacks(self._set_callbacks, key, dict.__getitem__(self, key))\n",
165 " \n",
166 " new_dict.on_add(handle_change)\n",
167 " new_dict.on_del(handle_change)\n",
168 " new_dict.on_set(handle_change)\n",
169 " return_val = dict.__setitem__(self, key, new_dict)\n",
170 " else:\n",
171 " return_val = dict.__setitem__(self, key, value)\n",
172 " \n",
173 " if exists:\n",
174 " self._handle_set(key, value)\n",
175 " else:\n",
176 " self._handle_add(key, value)\n",
177 " return return_val\n",
178 " \n",
179 "\n",
180 " def __delitem__(self, key):\n",
181 " return_val = dict.__delitem__(self, key)\n",
182 " self._handle_del(key)\n",
183 " return return_val\n",
184 "\n",
185 " \n",
186 " def pop(self, key):\n",
187 " return_val = dict.pop(self, key)\n",
188 " if key in self:\n",
189 " self._handle_del(key)\n",
190 " return return_val\n",
191 "\n",
192 " def popitem(self):\n",
193 " popped = dict.popitem(self)\n",
194 " if popped is not None and popped[0] is not None:\n",
195 " self._handle_del(popped[0])\n",
196 " return popped\n",
197 "\n",
198 " def update(self, other_dict):\n",
199 " for (key, value) in other_dict.items():\n",
200 " self[key] = value\n",
201 " \n",
202 " def clear(self):\n",
203 " for key in list(self.keys()):\n",
204 " del self[key]\n"
205 ],
206 "language": "python",
207 "metadata": {},
208 "outputs": [],
209 "prompt_number": 5
210 },
211 {
212 "cell_type": "markdown",
213 "metadata": {},
214 "source": [
215 "Test the eventful dictionary."
216 ]
217 },
218 {
219 "cell_type": "code",
220 "collapsed": false,
221 "input": [
222 "a = EventfulDict()"
223 ],
224 "language": "python",
225 "metadata": {},
226 "outputs": [],
227 "prompt_number": 6
228 },
229 {
230 "cell_type": "code",
231 "collapsed": false,
232 "input": [
233 "def echo_dict_events(eventful_dict, prefix=''):\n",
234 " def key_add(key, value):\n",
235 " print prefix + 'add (%s, %s)' % (key, str(value))\n",
236 " def key_set(key, value):\n",
237 " print prefix + 'set (%s, %s)' % (key, str(value))\n",
238 " def key_del(key):\n",
239 " print prefix + 'del %s' % key\n",
240 " eventful_dict.on_add(key_add)\n",
241 " eventful_dict.on_set(key_set)\n",
242 " eventful_dict.on_del(key_del)\n",
243 " \n",
244 "echo_dict_events(a)"
245 ],
246 "language": "python",
247 "metadata": {},
248 "outputs": [],
249 "prompt_number": 7
250 },
251 {
252 "cell_type": "code",
253 "collapsed": false,
254 "input": [
255 "a['a'] = 'hello'"
256 ],
257 "language": "python",
258 "metadata": {},
259 "outputs": [
260 {
261 "output_type": "stream",
262 "stream": "stdout",
263 "text": [
264 "add (a, hello)\n"
265 ]
266 }
267 ],
268 "prompt_number": 8
269 },
270 {
271 "cell_type": "code",
272 "collapsed": false,
273 "input": [
274 "a['a'] = 'goodbye'"
275 ],
276 "language": "python",
277 "metadata": {},
278 "outputs": [
279 {
280 "output_type": "stream",
281 "stream": "stdout",
282 "text": [
283 "set (a, goodbye)\n"
284 ]
285 }
286 ],
287 "prompt_number": 9
288 },
289 {
290 "cell_type": "code",
291 "collapsed": false,
292 "input": [
293 "b = {'c': 'yay', 'd': 'no'}\n",
294 "a.update(b)"
295 ],
296 "language": "python",
297 "metadata": {},
298 "outputs": [
299 {
300 "output_type": "stream",
301 "stream": "stdout",
302 "text": [
303 "add (c, yay)\n",
304 "add (d, no)\n"
305 ]
306 }
307 ],
308 "prompt_number": 10
309 },
310 {
311 "cell_type": "code",
312 "collapsed": false,
313 "input": [
314 "a"
315 ],
316 "language": "python",
317 "metadata": {},
318 "outputs": [
319 {
320 "metadata": {},
321 "output_type": "pyout",
322 "prompt_number": 11,
323 "text": [
324 "{'a': 'goodbye', 'c': 'yay', 'd': 'no'}"
325 ]
326 }
327 ],
328 "prompt_number": 11
329 },
330 {
331 "cell_type": "code",
332 "collapsed": false,
333 "input": [
334 "a.pop('a')"
335 ],
336 "language": "python",
337 "metadata": {},
338 "outputs": [
339 {
340 "metadata": {},
341 "output_type": "pyout",
342 "prompt_number": 12,
343 "text": [
344 "'goodbye'"
345 ]
346 }
347 ],
348 "prompt_number": 12
349 },
350 {
351 "cell_type": "code",
352 "collapsed": false,
353 "input": [
354 "a.popitem()"
355 ],
356 "language": "python",
357 "metadata": {},
358 "outputs": [
359 {
360 "output_type": "stream",
361 "stream": "stdout",
362 "text": [
363 "del c\n"
364 ]
365 },
366 {
367 "metadata": {},
368 "output_type": "pyout",
369 "prompt_number": 13,
370 "text": [
371 "('c', 'yay')"
372 ]
373 }
374 ],
375 "prompt_number": 13
376 },
377 {
378 "cell_type": "code",
379 "collapsed": false,
380 "input": [
381 "a['e'] = {}"
382 ],
383 "language": "python",
384 "metadata": {},
385 "outputs": [
386 {
387 "output_type": "stream",
388 "stream": "stdout",
389 "text": [
390 "add (e, {})\n"
391 ]
392 }
393 ],
394 "prompt_number": 14
395 },
396 {
397 "cell_type": "code",
398 "collapsed": false,
399 "input": [
400 "a['e']['a'] = 0"
401 ],
402 "language": "python",
403 "metadata": {},
404 "outputs": [
405 {
406 "output_type": "stream",
407 "stream": "stdout",
408 "text": [
409 "set (e, {'a': 0})\n"
410 ]
411 }
412 ],
413 "prompt_number": 15
414 },
415 {
416 "cell_type": "code",
417 "collapsed": false,
418 "input": [
419 "a['e']['b'] = 1"
420 ],
421 "language": "python",
422 "metadata": {},
423 "outputs": [
424 {
425 "output_type": "stream",
426 "stream": "stdout",
427 "text": [
428 "set (e, {'a': 0, 'b': 1})\n"
429 ]
430 }
431 ],
432 "prompt_number": 16
433 },
434 {
435 "cell_type": "code",
436 "collapsed": false,
437 "input": [
438 "a.clear()"
439 ],
440 "language": "python",
441 "metadata": {},
442 "outputs": [
443 {
444 "output_type": "stream",
445 "stream": "stdout",
446 "text": [
447 "del e\n",
448 "del d\n"
449 ]
450 }
451 ],
452 "prompt_number": 17
453 },
454 {
455 "cell_type": "markdown",
456 "metadata": {},
457 "source": [
458 "Override the NetworkX Graph object to make an eventful graph."
459 ]
460 },
461 {
462 "cell_type": "code",
463 "collapsed": false,
464 "input": [
465 "class EventfulGraph(nx.Graph):\n",
466 " \n",
467 " def __init__(self, *pargs, **kwargs):\n",
468 " \"\"\"Initialize a graph with edges, name, graph attributes.\"\"\"\n",
469 " super(EventfulGraph, self).__init__(*pargs, **kwargs)\n",
470 " \n",
471 " self.graph = EventfulDict(self.graph)\n",
472 " self.node = EventfulDict(self.node)\n",
473 " self.adj = EventfulDict(self.adj)\n",
474 " "
475 ],
476 "language": "python",
477 "metadata": {},
478 "outputs": [],
479 "prompt_number": 18
480 },
481 {
482 "cell_type": "code",
483 "collapsed": false,
484 "input": [
485 "def echo_graph_events(eventful_graph):\n",
486 " for key in ['graph', 'node', 'adj']:\n",
487 " echo_dict_events(getattr(eventful_graph, key), prefix=key+' ')"
488 ],
489 "language": "python",
490 "metadata": {},
491 "outputs": [],
492 "prompt_number": 19
493 },
494 {
495 "cell_type": "code",
496 "collapsed": false,
497 "input": [
498 "G = EventfulGraph()\n",
499 "echo_graph_events(G)"
500 ],
501 "language": "python",
502 "metadata": {},
503 "outputs": [],
504 "prompt_number": 20
505 },
506 {
507 "cell_type": "code",
508 "collapsed": false,
509 "input": [
510 "to_d3_json(G)"
511 ],
512 "language": "python",
513 "metadata": {},
514 "outputs": [
515 {
516 "metadata": {},
517 "output_type": "pyout",
518 "prompt_number": 21,
519 "text": [
520 "'{\"directed\": false, \"graph\": [], \"nodes\": [], \"links\": [], \"multigraph\": false}'"
521 ]
522 }
523 ],
524 "prompt_number": 21
525 },
526 {
527 "cell_type": "code",
528 "collapsed": false,
529 "input": [
530 "G.add_node('hello')\n",
531 "G.add_node('goodbye')\n",
532 "G.add_edges_from([(1,2),(1,3)])\n",
533 "to_d3_json(G)"
534 ],
535 "language": "python",
536 "metadata": {},
537 "outputs": [
538 {
539 "output_type": "stream",
540 "stream": "stdout",
541 "text": [
542 "adj add (hello, {})\n",
543 "node add (hello, {})\n",
544 "adj add (goodbye, {})\n",
545 "node add (goodbye, {})\n",
546 "adj add (1, {})\n",
547 "node add (1, {})\n",
548 "adj add (2, {})\n",
549 "node add (2, {})\n",
550 "adj set (1, {2: {}})\n",
551 "adj set (2, {1: {}})\n",
552 "adj add (3, {})\n",
553 "node add (3, {})\n",
554 "adj set (1, {2: {}, 3: {}})\n",
555 "adj set (3, {1: {}})\n"
556 ]
557 },
558 {
559 "metadata": {},
560 "output_type": "pyout",
561 "prompt_number": 22,
562 "text": [
563 "'{\"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}'"
564 ]
565 }
566 ],
567 "prompt_number": 22
568 },
569 {
570 "cell_type": "code",
571 "collapsed": false,
572 "input": [
573 "G.adj"
574 ],
575 "language": "python",
576 "metadata": {},
577 "outputs": [
578 {
579 "metadata": {},
580 "output_type": "pyout",
581 "prompt_number": 23,
582 "text": [
583 "{1: {2: {}, 3: {}}, 2: {1: {}}, 3: {1: {}}, 'goodbye': {}, 'hello': {}}"
584 ]
585 }
586 ],
587 "prompt_number": 23
588 },
589 {
590 "cell_type": "code",
591 "collapsed": false,
592 "input": [
593 "G.node"
594 ],
595 "language": "python",
596 "metadata": {},
597 "outputs": [
598 {
599 "metadata": {},
600 "output_type": "pyout",
601 "prompt_number": 24,
602 "text": [
603 "{1: {}, 2: {}, 3: {}, 'goodbye': {}, 'hello': {}}"
604 ]
605 }
606 ],
607 "prompt_number": 24
608 },
609 {
610 "cell_type": "heading",
611 "level": 1,
612 "metadata": {},
613 "source": [
614 "Custom Widget"
615 ]
616 },
617 {
618 "cell_type": "code",
619 "collapsed": false,
620 "input": [
621 "%%html\n",
622 "<div id=\"d3loadindicator\" style=\"background: red; color: white;\"><center>Loading D3...<center></div>\n",
623 "<script>\n",
624 " $.getScript('http://d3js.org/d3.v3.min.js', function(){\n",
625 " $('#d3loadindicator')\n",
626 " .css('background', 'green')\n",
627 " .html('<center>D3 Loaded Successfully</center>');\n",
628 " });\n",
629 "</script>"
630 ],
631 "language": "python",
632 "metadata": {},
633 "outputs": [
634 {
635 "html": [
636 "<div id=\"d3loadindicator\" style=\"background: red; color: white;\"><center>Loading D3...<center></div>\n",
637 "<script>\n",
638 " $.getScript('http://d3js.org/d3.v3.min.js', function(){\n",
639 " $('#d3loadindicator')\n",
640 " .css('background', 'green')\n",
641 " .html('<center>D3 Loaded Successfully</center>');\n",
642 " });\n",
643 "</script>"
644 ],
645 "metadata": {},
646 "output_type": "display_data",
647 "text": [
648 "<IPython.core.display.HTML at 0x2e2c590>"
649 ]
650 }
651 ],
652 "prompt_number": 25
653 },
654 {
655 "cell_type": "code",
656 "collapsed": false,
657 "input": [
658 "from IPython.html import widgets # Widget definitions\n",
659 "from IPython.display import display # Used to display widgets in the notebook"
660 ],
661 "language": "python",
662 "metadata": {},
663 "outputs": [],
664 "prompt_number": 26
665 },
666 {
667 "cell_type": "code",
668 "collapsed": false,
669 "input": [
670 "# Import the base Widget class and the traitlets Unicode class.\n",
671 "from IPython.html.widgets import Widget\n",
672 "from IPython.utils.traitlets import Unicode\n",
673 "\n",
674 "# Define our ForceDirectedGraphWidget and its target model and default view.\n",
675 "class ForceDirectedGraphWidget(Widget):\n",
676 " target_name = Unicode('ForceDirectedGraphModel')\n",
677 " default_view_name = Unicode('D3ForceDirectedGraphView')\n",
678 " \n",
679 " _keys = ['initial_json']\n",
680 " initial_json = Unicode()\n",
681 " \n",
682 " def __init__(self, eventful_graph, *pargs, **kwargs):\n",
683 " Widget.__init__(self, *pargs, **kwargs)\n",
684 " \n",
685 " self._eventful_graph = eventful_graph\n",
686 " self._send_dict_changes(eventful_graph.graph, 'graph')\n",
687 " self._send_dict_changes(eventful_graph.node, 'node')\n",
688 " self._send_dict_changes(eventful_graph.adj, 'adj')\n",
689 " \n",
690 " \n",
691 " def _repr_widget_(self, *pargs, **kwargs):\n",
692 " self.initial_json = to_d3_json(self._eventful_graph)\n",
693 " Widget._repr_widget_(self, *pargs, **kwargs)\n",
694 " \n",
695 " \n",
696 " def _send_dict_changes(self, eventful_dict, dict_name):\n",
697 " def key_add(key, value):\n",
698 " self.send({'dict': dict_name, 'action': 'add', 'key': key, 'value': value})\n",
699 " def key_set(key, value):\n",
700 " self.send({'dict': dict_name, 'action': 'set', 'key': key, 'value': value})\n",
701 " def key_del(key):\n",
702 " self.send({'dict': dict_name, 'action': 'del', 'key': key})\n",
703 " eventful_dict.on_add(key_add)\n",
704 " eventful_dict.on_set(key_set)\n",
705 " eventful_dict.on_del(key_del)\n",
706 " "
707 ],
708 "language": "python",
709 "metadata": {},
710 "outputs": [],
711 "prompt_number": 27
712 },
713 {
714 "cell_type": "code",
715 "collapsed": false,
716 "input": [
717 "%%javascript\n",
718 "\n",
719 "require([\"notebook/js/widget\"], function(){\n",
720 " \n",
721 " // Define the ForceDirectedGraphModel and register it with the widget manager.\n",
722 " var ForceDirectedGraphModel = IPython.WidgetModel.extend({});\n",
723 " IPython.widget_manager.register_widget_model('ForceDirectedGraphModel', ForceDirectedGraphModel);\n",
724 " \n",
725 " // Define the D3ForceDirectedGraphView\n",
726 " var D3ForceDirectedGraphView = IPython.WidgetView.extend({\n",
727 " \n",
728 " render: function(){\n",
729 " this.guid = 'd3force' + IPython.utils.uuid();\n",
730 " this.setElement($('<div />', {id: this.guid}));\n",
731 " this.model.on_msg($.proxy(this.handle_msg, this));\n",
732 " },\n",
733 " \n",
734 " add_node: function(id){\n",
735 " var index = this.find_node(id);\n",
736 " if (index == -1) {\n",
737 " var node = {id: id};\n",
738 " this.nodes.push(node);\n",
739 " return node;\n",
740 " } else {\n",
741 " return this.nodes[index];\n",
742 " }\n",
743 " },\n",
744 " \n",
745 " remove_node: function(id){\n",
746 " var found_index = this.find_node(id);\n",
747 " if (found_index>=0) {\n",
748 " this.nodes.splice(found_index, 1);\n",
749 " }\n",
750 " \n",
751 " clear_links(id);\n",
752 " },\n",
753 " \n",
754 " find_node: function(id){\n",
755 " var found_index = -1;\n",
756 " for (var index in this.nodes) {\n",
757 " if (this.nodes[index].id == id) {\n",
758 " found_index = index;\n",
759 " break;\n",
760 " }\n",
761 " }\n",
762 " return found_index;\n",
763 " },\n",
764 " \n",
765 " clear_links: function(id){\n",
766 " \n",
767 " // Remove existing links\n",
768 " var found_indexs = [];\n",
769 " for (var index in this.links) {\n",
770 " if (this.links[index].source.id == id) {\n",
771 " found_indexs.push(index);\n",
772 " }\n",
773 " }\n",
774 " \n",
775 " for (var index in found_indexs) {\n",
776 " this.links.splice(found_indexs[index], 1);\n",
777 " }\n",
778 " },\n",
779 " \n",
780 " handle_msg: function(content){\n",
781 " var dict = content.dict;\n",
782 " var action = content.action;\n",
783 " var key = content.key;\n",
784 " console.log(dict, action, key);\n",
785 " \n",
786 " if (dict=='node') {\n",
787 " \n",
788 " // Only support node ADD and DEL actions for now...\n",
789 " if (action=='add') {\n",
790 " this.add_node(key)\n",
791 " } else if (action=='del') {\n",
792 " this.remove_node(key);\n",
793 " }\n",
794 " \n",
795 " } else if (dict=='adj') {\n",
796 " this.clear_links(key);\n",
797 " \n",
798 " // Add all links\n",
799 " if (action != 'del') {\n",
800 " var value = content.value;\n",
801 " for (var link_to in value) {\n",
802 " var source_node = this.add_node(key);\n",
803 " var target_node = this.add_node(link_to);\n",
804 " this.links.push({source: source_node, target: target_node});\n",
805 " }\n",
806 " }\n",
807 " }\n",
808 " this.start();\n",
809 " },\n",
810 " \n",
811 " start: function() {\n",
812 " var node = this.svg.selectAll(\".node\"),\n",
813 " link = this.svg.selectAll(\".link\");\n",
814 " \n",
815 " var link = link.data(this.force.links(), function(d) { return d.source.id + \"-\" + d.target.id; });\n",
816 " link.enter()\n",
817 " .insert(\"line\", \".node\")\n",
818 " .attr(\"class\", \"link\")\n",
819 " .style(\"stroke-width\", '1.5px')\n",
820 " .style('stroke', '#999');\n",
821 " link.exit().remove();\n",
822 " \n",
823 " var node = node.data(this.force.nodes(), function(d) { return d.id;});\n",
824 " var that = this;\n",
825 " node.enter()\n",
826 " .append(\"circle\")\n",
827 " .attr(\"class\", function(d) { return \"node \" + d.id; })\n",
828 " .attr(\"r\", 8)\n",
829 " .style(\"fill\", function(d) { return that.color(d.group); })\n",
830 " .style(\"stroke\", \"#fff\")\n",
831 " .style(\"stroke-width\", \"1.5px\")\n",
832 " .call(this.force.drag);\n",
833 " node.exit().remove();\n",
834 " \n",
835 " this.force.start();\n",
836 " },\n",
837 " \n",
838 " tick: function() {\n",
839 " var node = this.svg.selectAll(\".node\"),\n",
840 " link = this.svg.selectAll(\".link\");\n",
841 " \n",
842 " link.attr(\"x1\", function(d) { return d.source.x; })\n",
843 " .attr(\"y1\", function(d) { return d.source.y; })\n",
844 " .attr(\"x2\", function(d) { return d.target.x; })\n",
845 " .attr(\"y2\", function(d) { return d.target.y; });\n",
846 " \n",
847 " node.attr(\"cx\", function(d) { return d.x; })\n",
848 " .attr(\"cy\", function(d) { return d.y; });\n",
849 " },\n",
850 " \n",
851 " update: function(){\n",
852 " var initial_json = this.model.get('initial_json');\n",
853 " if (this.initial_json != initial_json) {\n",
854 " this.initial_json = initial_json;\n",
855 " \n",
856 " var width = 860,\n",
857 " height = 400;\n",
858 " \n",
859 " this.color = d3.scale.category20();\n",
860 " \n",
861 " var graph = JSON.parse(initial_json);\n",
862 " this.nodes = [];\n",
863 " $.extend(this.nodes, graph.nodes);\n",
864 " this.links = [];\n",
865 " $.extend(this.links, graph.links);\n",
866 " \n",
867 " var force = d3.layout.force()\n",
868 " .nodes(this.nodes)\n",
869 " .links(this.links)\n",
870 " .charge(-120)\n",
871 " .linkDistance(30)\n",
872 " .size([width, height])\n",
873 " .on(\"tick\", $.proxy(this.tick, this));\n",
874 " this.force = force;\n",
875 " \n",
876 " var svg = d3.select(\"#\" + this.guid).append(\"svg\")\n",
877 " .attr(\"width\", width)\n",
878 " .attr(\"height\", height);\n",
879 " this.svg = svg;\n",
880 " \n",
881 " var that = this;\n",
882 " setTimeout(function() {\n",
883 " that.start();\n",
884 " }, 0);\n",
885 " }\n",
886 " \n",
887 " return IPython.WidgetView.prototype.update.call(this);\n",
888 " },\n",
889 " \n",
890 " });\n",
891 " \n",
892 " // Register the D3ForceDirectedGraphView with the widget manager.\n",
893 " IPython.widget_manager.register_widget_view('D3ForceDirectedGraphView', D3ForceDirectedGraphView);\n",
894 "});"
895 ],
896 "language": "python",
897 "metadata": {},
898 "outputs": [
899 {
900 "javascript": [
901 "\n",
902 "require([\"notebook/js/widget\"], function(){\n",
903 " \n",
904 " // Define the ForceDirectedGraphModel and register it with the widget manager.\n",
905 " var ForceDirectedGraphModel = IPython.WidgetModel.extend({});\n",
906 " IPython.widget_manager.register_widget_model('ForceDirectedGraphModel', ForceDirectedGraphModel);\n",
907 " \n",
908 " // Define the D3ForceDirectedGraphView\n",
909 " var D3ForceDirectedGraphView = IPython.WidgetView.extend({\n",
910 " \n",
911 " render: function(){\n",
912 " this.guid = 'd3force' + IPython.utils.uuid();\n",
913 " this.setElement($('<div />', {id: this.guid}));\n",
914 " this.model.on_msg($.proxy(this.handle_msg, this));\n",
915 " },\n",
916 " \n",
917 " add_node: function(id){\n",
918 " var index = this.find_node(id);\n",
919 " if (index == -1) {\n",
920 " var node = {id: id};\n",
921 " this.nodes.push(node);\n",
922 " return node;\n",
923 " } else {\n",
924 " return this.nodes[index];\n",
925 " }\n",
926 " },\n",
927 " \n",
928 " remove_node: function(id){\n",
929 " var found_index = this.find_node(id);\n",
930 " if (found_index>=0) {\n",
931 " this.nodes.splice(found_index, 1);\n",
932 " }\n",
933 " \n",
934 " clear_links(id);\n",
935 " },\n",
936 " \n",
937 " find_node: function(id){\n",
938 " var found_index = -1;\n",
939 " for (var index in this.nodes) {\n",
940 " if (this.nodes[index].id == id) {\n",
941 " found_index = index;\n",
942 " break;\n",
943 " }\n",
944 " }\n",
945 " return found_index;\n",
946 " },\n",
947 " \n",
948 " clear_links: function(id){\n",
949 " \n",
950 " // Remove existing links\n",
951 " var found_indexs = [];\n",
952 " for (var index in this.links) {\n",
953 " if (this.links[index].source.id == id) {\n",
954 " found_indexs.push(index);\n",
955 " }\n",
956 " }\n",
957 " \n",
958 " for (var index in found_indexs) {\n",
959 " this.links.splice(found_indexs[index], 1);\n",
960 " }\n",
961 " },\n",
962 " \n",
963 " handle_msg: function(content){\n",
964 " var dict = content.dict;\n",
965 " var action = content.action;\n",
966 " var key = content.key;\n",
967 " console.log(dict, action, key);\n",
968 " \n",
969 " if (dict=='node') {\n",
970 " \n",
971 " // Only support node ADD and DEL actions for now...\n",
972 " if (action=='add') {\n",
973 " this.add_node(key)\n",
974 " } else if (action=='del') {\n",
975 " this.remove_node(key);\n",
976 " }\n",
977 " \n",
978 " } else if (dict=='adj') {\n",
979 " this.clear_links(key);\n",
980 " \n",
981 " // Add all links\n",
982 " if (action != 'del') {\n",
983 " var value = content.value;\n",
984 " for (var link_to in value) {\n",
985 " var source_node = this.add_node(key);\n",
986 " var target_node = this.add_node(link_to);\n",
987 " this.links.push({source: source_node, target: target_node});\n",
988 " }\n",
989 " }\n",
990 " }\n",
991 " this.start();\n",
992 " },\n",
993 " \n",
994 " start: function() {\n",
995 " var node = this.svg.selectAll(\".node\"),\n",
996 " link = this.svg.selectAll(\".link\");\n",
997 " \n",
998 " var link = link.data(this.force.links(), function(d) { return d.source.id + \"-\" + d.target.id; });\n",
999 " link.enter()\n",
1000 " .insert(\"line\", \".node\")\n",
1001 " .attr(\"class\", \"link\")\n",
1002 " .style(\"stroke-width\", '1.5px')\n",
1003 " .style('stroke', '#999');\n",
1004 " link.exit().remove();\n",
1005 " \n",
1006 " var node = node.data(this.force.nodes(), function(d) { return d.id;});\n",
1007 " var that = this;\n",
1008 " node.enter()\n",
1009 " .append(\"circle\")\n",
1010 " .attr(\"class\", function(d) { return \"node \" + d.id; })\n",
1011 " .attr(\"r\", 8)\n",
1012 " .style(\"fill\", function(d) { return that.color(d.group); })\n",
1013 " .style(\"stroke\", \"#fff\")\n",
1014 " .style(\"stroke-width\", \"1.5px\")\n",
1015 " .call(this.force.drag);\n",
1016 " node.exit().remove();\n",
1017 " \n",
1018 " this.force.start();\n",
1019 " },\n",
1020 " \n",
1021 " tick: function() {\n",
1022 " var node = this.svg.selectAll(\".node\"),\n",
1023 " link = this.svg.selectAll(\".link\");\n",
1024 " \n",
1025 " link.attr(\"x1\", function(d) { return d.source.x; })\n",
1026 " .attr(\"y1\", function(d) { return d.source.y; })\n",
1027 " .attr(\"x2\", function(d) { return d.target.x; })\n",
1028 " .attr(\"y2\", function(d) { return d.target.y; });\n",
1029 " \n",
1030 " node.attr(\"cx\", function(d) { return d.x; })\n",
1031 " .attr(\"cy\", function(d) { return d.y; });\n",
1032 " },\n",
1033 " \n",
1034 " update: function(){\n",
1035 " var initial_json = this.model.get('initial_json');\n",
1036 " if (this.initial_json != initial_json) {\n",
1037 " this.initial_json = initial_json;\n",
1038 " \n",
1039 " var width = 860,\n",
1040 " height = 400;\n",
1041 " \n",
1042 " this.color = d3.scale.category20();\n",
1043 " \n",
1044 " var graph = JSON.parse(initial_json);\n",
1045 " this.nodes = [];\n",
1046 " $.extend(this.nodes, graph.nodes);\n",
1047 " this.links = [];\n",
1048 " $.extend(this.links, graph.links);\n",
1049 " \n",
1050 " var force = d3.layout.force()\n",
1051 " .nodes(this.nodes)\n",
1052 " .links(this.links)\n",
1053 " .charge(-120)\n",
1054 " .linkDistance(30)\n",
1055 " .size([width, height])\n",
1056 " .on(\"tick\", $.proxy(this.tick, this));\n",
1057 " this.force = force;\n",
1058 " \n",
1059 " var svg = d3.select(\"#\" + this.guid).append(\"svg\")\n",
1060 " .attr(\"width\", width)\n",
1061 " .attr(\"height\", height);\n",
1062 " this.svg = svg;\n",
1063 " \n",
1064 " var that = this;\n",
1065 " setTimeout(function() {\n",
1066 " that.start();\n",
1067 " }, 0);\n",
1068 " }\n",
1069 " \n",
1070 " return IPython.WidgetView.prototype.update.call(this);\n",
1071 " },\n",
1072 " \n",
1073 " });\n",
1074 " \n",
1075 " // Register the D3ForceDirectedGraphView with the widget manager.\n",
1076 " IPython.widget_manager.register_widget_view('D3ForceDirectedGraphView', D3ForceDirectedGraphView);\n",
1077 "});"
1078 ],
1079 "metadata": {},
1080 "output_type": "display_data",
1081 "text": [
1082 "<IPython.core.display.Javascript at 0x2e820d0>"
1083 ]
1084 }
1085 ],
1086 "prompt_number": 28
1087 },
1088 {
1089 "cell_type": "code",
1090 "collapsed": false,
1091 "input": [
1092 "G = EventfulGraph()\n",
1093 "G.add_node('hello')\n",
1094 "G.add_node('goodbye')\n",
1095 "G.add_edges_from([(1,2),(1,3)])\n",
1096 "to_d3_json(G)"
1097 ],
1098 "language": "python",
1099 "metadata": {},
1100 "outputs": [
1101 {
1102 "metadata": {},
1103 "output_type": "pyout",
1104 "prompt_number": 30,
1105 "text": [
1106 "'{\"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}'"
1107 ]
1108 }
1109 ],
1110 "prompt_number": 30
1111 },
1112 {
1113 "cell_type": "code",
1114 "collapsed": false,
1115 "input": [
1116 "floating_container = widgets.ContainerWidget()\n",
1117 "floating_container.set_css({\n",
1118 " 'position': 'relative',\n",
1119 " 'left': '0px',\n",
1120 " 'top': '0px',\n",
1121 " 'z-index': '999',\n",
1122 " 'background': '#FFF',\n",
1123 " 'opacity': '0.8'\n",
1124 "})\n",
1125 "\n",
1126 "d3 = ForceDirectedGraphWidget(G, parent=floating_container)\n",
1127 "display(floating_container)\n",
1128 "\n",
1129 "detach_button = widgets.ButtonWidget(description=\"Detach\")\n",
1130 "def handle_detach(sender):\n",
1131 " if sender.description == \"Detach\":\n",
1132 " sender.description = \"Attach\"\n",
1133 " floating_container.set_css('position', 'absolute')\n",
1134 " else:\n",
1135 " sender.description = \"Detach\"\n",
1136 " floating_container.set_css('position', 'relative')\n",
1137 "detach_button.on_click(handle_detach)\n",
1138 "display(detach_button)"
1139 ],
1140 "language": "python",
1141 "metadata": {},
1142 "outputs": [],
1143 "prompt_number": 31
1144 },
1145 {
1146 "cell_type": "code",
1147 "collapsed": false,
1148 "input": [
1149 "G.add_node('beep')\n",
1150 "G.add_node('beep2')"
1151 ],
1152 "language": "python",
1153 "metadata": {},
1154 "outputs": [],
1155 "prompt_number": 32
1156 },
1157 {
1158 "cell_type": "code",
1159 "collapsed": false,
1160 "input": [
1161 "G.remove_node('beep')"
1162 ],
1163 "language": "python",
1164 "metadata": {},
1165 "outputs": [],
1166 "prompt_number": 33
1167 },
1168 {
1169 "cell_type": "code",
1170 "collapsed": false,
1171 "input": [
1172 "\n",
1173 "G.add_edges_from([(4,5),(4,6)])"
1174 ],
1175 "language": "python",
1176 "metadata": {},
1177 "outputs": [],
1178 "prompt_number": 34
1179 },
1180 {
1181 "cell_type": "code",
1182 "collapsed": false,
1183 "input": [
1184 "import time\n",
1185 "for i in range(30):\n",
1186 " G.add_edges_from([(7+i, 8+i), (7+i, 9+i)])\n",
1187 " time.sleep(0.1)"
1188 ],
1189 "language": "python",
1190 "metadata": {},
1191 "outputs": [],
1192 "prompt_number": 35
1193 },
1194 {
1195 "cell_type": "code",
1196 "collapsed": false,
1197 "input": [
1198 "\n",
1199 "for i in range(30):\n",
1200 " G.add_edge(7, i+8)\n",
1201 " time.sleep(0.25)"
1202 ],
1203 "language": "python",
1204 "metadata": {},
1205 "outputs": [],
1206 "prompt_number": 36
1207 },
1208 {
1209 "cell_type": "code",
1210 "collapsed": false,
1211 "input": [
1212 "G.clear()"
1213 ],
1214 "language": "python",
1215 "metadata": {},
1216 "outputs": []
1217 }
1218 ],
1219 "metadata": {}
1220 }
1221 ]
1222 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now