{ "metadata": { "name": "", "signature": "sha256:1e40485b2a1d12de34ede252d197cb50eb5b1f3fb8afaefe2a5c70dda7249c52" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Dynamic visualization of graphs using d3.js" ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Brian Granger and Jon Frederic." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Visualizing integer factoring" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, we visualize the factoring of small integers. We use a simple algorithm:\n", "\n", "* For a given integer find all of its divisors in increasing order: $10 \\rightarrow 2, 5$\n", "* For all of those divisors recursively find *their* divisors\n", "* Continue until all of the divisors are prime\n", "* Nodes in the graph are the divisors\n", "* Edges are added between an integer and each of its divisors" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from intfact import factorizer\n", "factorizer()" ], "language": "python", "metadata": {}, "outputs": [ { "javascript": [ "require([\"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min.js\",\n", " \"widgets/js/widget\"], function(d3, WidgetManager){\n", "\n", " // Define the D3ForceDirectedGraphView\n", " var D3ForceDirectedGraphView = IPython.DOMWidgetView.extend({\n", " \n", " render: function(){\n", " this.guid = 'd3force' + IPython.utils.uuid();\n", " this.setElement($('
', {id: this.guid}));\n", " \n", " this.model.on('msg:custom', this.on_msg, this);\n", " this.has_drawn = false;\n", " \n", " // Wait for element to be added to the DOM\n", " var that = this;\n", " setTimeout(function() {\n", " that.update();\n", " }, 0);\n", " },\n", " \n", " try_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", " update_node: function(node, attributes) {\n", " if (node !== null) {\n", " for (var key in attributes) {\n", " node[key] = attributes[key];\n", " }\n", " this._update_circle(d3.select('#' + this.guid + node.id));\n", " this._update_text(d3.select('#' + this.guid + node.id + '-text'));\n", " }\n", " },\n", " \n", " remove_node: function(id){\n", " this.remove_links_to(id);\n", " \n", " var found_index = this.find_node(id);\n", " if (found_index>=0) {\n", " this.nodes.splice(found_index, 1);\n", " }\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", " find_link: function(source_id, target_id){\n", " for (var index in this.links) {\n", " if (this.links[index].source.id == source_id && this.links[index].target.id == target_id) {\n", " return index;\n", " }\n", " }\n", " return -1;\n", " },\n", " \n", " try_add_link: function(source_id, target_id){\n", " var index = this.find_link(source_id, target_id);\n", " if (index == -1) {\n", " var source_node = this.try_add_node(source_id);\n", " var target_node = this.try_add_node(target_id);\n", " var new_link = {source: source_node, target: target_node};\n", " this.links.push(new_link);\n", " return new_link;\n", " } else {\n", " return this.links[index]\n", " }\n", " },\n", " \n", " update_link: function(link, attributes){\n", " if (link != null) {\n", " for (var key in attributes) {\n", " link[key] = attributes[key];\n", " }\n", " this._update_edge(d3.select('#' + this.guid + link.source.id + \"-\" + link.target.id));\n", " }\n", " },\n", " \n", " remove_links: function(source_id){\n", " var found_indicies = [];\n", " for (var index in this.links) {\n", " if (this.links[index].source.id == source_id) {\n", " found_indicies.push(index);\n", " }\n", " }\n", " found_indicies.reverse();\n", " \n", " for (var index in found_indicies) {\n", " this.links.splice(index, 1);\n", " };\n", " },\n", " \n", " remove_links_to: function(id){\n", " var found_indicies = [];\n", " for (var index in this.links) {\n", " if (this.links[index].source.id == id || this.links[index].target.id == id) {\n", " found_indicies.push(index);\n", " }\n", " }\n", " found_indicies.reverse();\n", " \n", " for (var index in found_indicies) {\n", " this.links.splice(index, 1);\n", " };\n", " },\n", " \n", " on_msg: function(content){\n", " this.update();\n", " \n", " var dict = content.dict;\n", " var action = content.action;\n", " var key = content.key;\n", " \n", " if (dict=='node') {\n", " if (action=='add' || action=='set') {\n", " this.update_node(this.try_add_node(key), content.value)\n", " } else if (action=='del') {\n", " this.remove_node(key);\n", " }\n", " \n", " } else if (dict=='adj') {\n", " if (action=='add' || action=='set') {\n", " var links = content.value;\n", " for (var target_id in links) {\n", " this.update_link(this.try_add_link(key, target_id), links[target_id]);\n", " }\n", " } else if (action=='del') {\n", " this.remove_links(key);\n", " }\n", " }\n", " this.start();\n", " },\n", " \n", " start: function() {\n", " var node = this.svg.selectAll(\".gnode\"),\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", " this._update_edge(link.enter().insert(\"line\", \".gnode\"))\n", " link.exit().remove();\n", " \n", " var node = node.data(this.force.nodes(), function(d) { return d.id;});\n", " var that = this;\n", "\n", " var gnode = node.enter()\n", " .append(\"g\")\n", " .attr('class', 'gnode')\n", " .call(this.force.drag);\n", " this._update_circle(gnode.append(\"circle\"));\n", " this._update_text(gnode.append(\"text\"));\n", " node.exit().remove();\n", " \n", " this.force.start();\n", " },\n", "\n", " _update_circle: function(circle) {\n", " var that = this;\n", "\n", " circle\n", " .attr(\"id\", function(d) { return that.guid + d.id; })\n", " .attr(\"class\", function(d) { return \"node \" + d.id; })\n", " .attr(\"r\", function(d) {\n", " if (d.r == undefined) {\n", " return 8; \n", " } else {\n", " return d.r;\n", " }\n", " \n", " })\n", " .style(\"fill\", function(d) {\n", " if (d.fill == undefined) {\n", " return that.color(d.group); \n", " } else {\n", " return d.fill;\n", " }\n", " \n", " })\n", " .style(\"stroke\", function(d) {\n", " if (d.stroke == undefined) {\n", " return \"#FFF\"; \n", " } else {\n", " return d.stroke;\n", " }\n", " \n", " })\n", " .style(\"stroke-width\", function(d) {\n", " if (d.strokewidth == undefined) {\n", " return \"#FFF\"; \n", " } else {\n", " return d.strokewidth;\n", " }\n", " \n", " })\n", " .attr('dx', 0)\n", " .attr('dy', 0);\n", " },\n", " \n", " _update_text: function(text) {\n", " var that = this;\n", "\n", " text\n", " .attr(\"id\", function(d) { return that.guid + d.id + '-text'; })\n", " .text(function(d) { \n", " if (d.label) {\n", " return d.label;\n", " } else {\n", " return '';\n", " }\n", " })\n", " .style(\"font-size\",function(d) { \n", " if (d.font_size) {\n", " return d.font_size;\n", " } else {\n", " return '11pt';\n", " }\n", " })\n", " .attr(\"text-anchor\", \"middle\")\n", " .style(\"fill\", function(d) { \n", " if (d.color) {\n", " return d.color;\n", " } else {\n", " return 'white';\n", " }\n", " })\n", " .attr('dx', function(d) { \n", " if (d.dx) {\n", " return d.dx;\n", " } else {\n", " return 0;\n", " }\n", " })\n", " .attr('dy', function(d) { \n", " if (d.dy) {\n", " return d.dy;\n", " } else {\n", " return 5;\n", " }\n", " })\n", " .style(\"pointer-events\", 'none');\n", " },\n", " \n", " _update_edge: function(edge) {\n", " var that = this;\n", " edge\n", " .attr(\"id\", function(d) { return that.guid + d.source.id + \"-\" + d.target.id; })\n", " .attr(\"class\", \"link\")\n", " .style(\"stroke-width\", function(d) {\n", " if (d.strokewidth == undefined) {\n", " return \"1.5px\"; \n", " } else {\n", " return d.strokewidth;\n", " }\n", " \n", " })\n", " .style('stroke', function(d) {\n", " if (d.stroke == undefined) {\n", " return \"#999\"; \n", " } else {\n", " return d.stroke;\n", " }\n", " \n", " });\n", " },\n", " \n", " tick: function() {\n", " var gnode = this.svg.selectAll(\".gnode\"),\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", " // Translate the groups\n", " gnode.attr(\"transform\", function(d) { return \"translate(\" + d.x + \",\" + d.y + \")\"; }); \n", " },\n", " \n", " update: function(){\n", " if (!this.has_drawn) {\n", " this.has_drawn = true;\n", " var width = this.model.get('width'),\n", " height = this.model.get('height');\n", " \n", " this.color = d3.scale.category20();\n", " \n", " this.nodes = [];\n", " this.links = [];\n", " \n", " var that = this;\n", " this.force = d3.layout.force()\n", " .nodes(this.nodes)\n", " .links(this.links)\n", " .charge(function (d) {\n", " if (d.charge === undefined) {\n", " return -280;\n", " } else {\n", " return d.charge;\n", " }\n", " })\n", " .linkDistance(function (d) {\n", " if (d.distance === undefined) {\n", " return 30;\n", " } else {\n", " return d.distance;\n", " }\n", " })\n", " .linkStrength(function (d) {\n", " if (d.strength === undefined) {\n", " return 0.3;\n", " } else {\n", " return d.strength;\n", " }\n", " })\n", " .size([width, height])\n", " .on(\"tick\", $.proxy(this.tick, this));\n", " \n", " this.svg = d3.select(\"#\" + this.guid).append(\"svg\")\n", " .attr(\"width\", width)\n", " .attr(\"height\", height);\n", " }\n", "\n", " var that = this;\n", " setTimeout(function() {\n", " that.start();\n", " }, 0);\n", " return D3ForceDirectedGraphView.__super__.update.apply(this);\n", " },\n", " \n", " });\n", " \n", " // Register the D3ForceDirectedGraphView with the widget manager.\n", " WidgetManager.register_widget_view('D3ForceDirectedGraphView', D3ForceDirectedGraphView);\n", "});\n" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 1 } ], "metadata": {} } ] }