├── css └── graphviz.svg.css ├── demo.html ├── demo.gv ├── README.md ├── js └── jquery.graphviz.svg.js └── demo.svg /css/graphviz.svg.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Mountainstorm 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* this element needs tooltip positioning to work */ 24 | .graphviz-svg { 25 | position: relative; 26 | } 27 | 28 | /* stop tooltips wrapping */ 29 | .graphviz-svg .tooltip-inner { 30 | white-space: nowrap; 31 | } 32 | 33 | /* stop people selecting text on nodes */ 34 | .graphviz-svg text { 35 | -webkit-touch-callout: none; 36 | -webkit-user-select: none; 37 | -khtml-user-select: none; 38 | -moz-user-select: none; 39 | -ms-user-select: none; 40 | user-select: none; 41 | cursor: default; 42 | } 43 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 37 | 38 |

Click node to highlight; Shift-scroll to zoom; Esc to unhighlight

39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /demo.gv: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Mountainstorm 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | digraph world { 24 | bgcolor="#2e3e56" 25 | pad="0.5" /* add padding round the edge of the graph */ 26 | /* 27 | rankdir="LR" make graph layout left->right rather than top->bottom 28 | graph [fontname="Helvetica Neue", fontcolor="#fcfcfc"] 29 | labelloc="t" label at top 30 | label="Test Setup" 31 | 32 | dark blue (background): #2e3e56 33 | white (text/lines): #fcfcfc 34 | dark line (hidden lines): #445773 35 | 36 | red: #ea555b - crashes 37 | yellow: #edad56 - nodes in target 38 | gold: #AB6D16 - static libs 39 | dark green: #29b89d 40 | purple: #9362a8 41 | pink: #f2688d - buckets 42 | green: #a5cf80 - 3rd party library 43 | blue: #8eabd9 - start 44 | 45 | 0.1pt == 3.25px 46 | */ 47 | 48 | node [shape="circle", width="0.6", style="filled", fillcolor="#edad56", color="#edad56", penwidth="3", label=""] 49 | edge [color="#fcfcfc", penwidth="2", fontname="helvetica Neue Ultra Light"] 50 | 51 | S35 [tooltip="Go S35", fillcolor="#8eabd9", color="#8eabd9"]; 52 | 23 [shape="house", tooltip="Go 23"]; 53 | T1 [shape="box", tooltip="Go T1", fillcolor="#9362a8", color="#9362a8"]; 54 | T99 [shape="rectangle", tooltip="Go T1", fillcolor="#9362a8", color="#9362a8"]; 55 | P4 [shape="ellipse", tooltip="Go P4", fillcolor="#f2688d", color="#f2688d"]; 56 | T30 [shape="circle", tooltip="Go T30", fillcolor="#ea555b", color="#ea555b"]; 57 | 58 | 43 [fillcolor="#a5cf80", color="#a5cf80"]; 59 | 40 [fillcolor="#a5cf80", color="#a5cf80", label="Another"]; 60 | 61 | 28 [fillcolor="#AB6D16", color="#AB6D16"]; 62 | 19 [fillcolor="#AB6D16", color="#AB6D16"]; 63 | 64 | T30 -> P4 65 | 66 | S8 -> 9; 67 | S24 -> 25; 68 | S24 -> 27; 69 | S1 -> 2; 70 | S1 -> 10; 71 | S35 -> 43; 72 | S35 -> 36; 73 | S30 -> 31; 74 | S30 -> 33; 75 | 9 -> 42; 76 | 9 -> T1; 77 | 25 -> T1 [color="#445773", weight="0", comment="weaklink"]; 78 | 25 -> 26; 79 | 27 -> T24; 80 | 2 -> {3 ; 16 ; 17 ; T1 ; 18} 81 | 10 -> { 11 ; 14 ; T1 ; 13; 12;} 82 | 31 -> T1 83 | 31 -> 32; 84 | 33 -> T30; 85 | 33 -> 34; 86 | 42 -> 4; 87 | 26 -> 4; 88 | 3 -> 4; 89 | 16 -> 15; 90 | 17 -> 19; 91 | 18 -> 29; 92 | 11 -> 4; 93 | 14 -> 15; 94 | 37 -> {39 ; 41 ; 38 ; 40;} 95 | 13 -> 19; 96 | 12 -> 29; 97 | 43 -> 38; 98 | 43 -> 40; 99 | 36 -> 19; 100 | 32 -> 23; 101 | 34 -> 29; 102 | 39 -> 15; 103 | 41 -> 29; 104 | 38 -> 4; 105 | 40 -> 19; 106 | 4 -> 5; 107 | 19 -> {21 ; 20 ; 28;} 108 | 5 -> {6 ; T35 ; 23;} 109 | 21 -> 22; 110 | 20 -> 15; 111 | 28 -> 29; 112 | 6 -> 7; 113 | 15 -> T1; 114 | 22 -> T35; 115 | 22 -> 23; 116 | 29 -> T30; 117 | 7 -> T8; 118 | 23 -> T24; 119 | 23 -> T1; 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jquery.graphviz.svg 2 | =================== 3 | 4 | jQuery plugin to make Graphviz SVG output more interactive and easier to navigate. Makes it easy to have features like: 5 | * Highlight nodes/edges 6 | * Zoom in/out 7 | * Graph navigation - select linked nodes 8 | * Fancy UI tooltips; bootstrap supported out the box 9 | * Move things forward/back in the graph 10 | 11 | Have a look at the demo: https://cdn.rawgit.com/mountainstorm/jquery.graphviz.svg/master/demo.html 12 | 13 | 14 | Documentation 15 | ------------- 16 | 17 | Create a node where you want your SVG graph to be displayed. Typically you will want to: 18 | * set its size (width/height/top/bottom etc) 19 | * set it as a positioning root; set `positioning: relative` or somethign else 20 | * enable `overflow: scroll` 21 | 22 | Something like this works pretty well: 23 | `
` 24 | 25 | Next includes the css, and javascript: 26 | `` 27 | 28 | `` 29 | 30 | Then init the node as a Graphviz object: 31 | ``` 32 | $(document).ready(function(){ 33 | $("#graph").graphviz({ 34 | url: "demo.svg", 35 | ready: function() { 36 | var gv = this 37 | } 38 | }); 39 | }); 40 | ``` 41 | 42 | Depending on the options passed this will load, adopt and setup the Graphviz generated SVG under `#graph` and call your supplied `ready` function when the setup is complete. 43 | 44 | Options: 45 | * __url__: if present the url to fetch the svg from 46 | * __svg__: raw SVG (xml) data to adopt 47 | * __shrink__: the amount to shrink nodes by; this gives a nice gap between nodes and edges. Default '0.125pt' 48 | * __tooltips__: object containing callbacks for `init`, `show`, `hide` and `update`. Default implementation uses bootstrap 49 | * __zoom__: enable shift-scroll zoom. Default true 50 | * __highlight__: object containing callbacks for `selected`, `unselected`; Default dims color of unselected 51 | * __ready__: callback when setup is complete. Will be asyncronous if loading svg from a url 52 | 53 | The demo (demo.html) and the source (`GraphvizSvg.DEFAULTS`) show how these work in detail. 54 | 55 | There are also other methods you can call to navigate the graph, select elements, highlight, move etc. To access these you need to get the 'graphviz.svg' object from the jQuery element you initialized (`$('#graph').data('graphviz.svg')`); it is also supplied as `this` to the `ready` callback. 56 | 57 | `GraphvizSvg.nodes()` 58 | Returns all node DOM elements 59 | 60 | `GraphvizSvg.edges()` 61 | Returns all edge DOM elements 62 | 63 | `GraphvizSvg.nodesByName()` 64 | Returns an object mapping graphviz node names to its DOM element 65 | 66 | `GraphvizSvg.edgesByName()` 67 | Returns an object mapping graphviz edge names to its DOM element 68 | 69 | `GraphvizSvg.linkedTo(node, includeEdges)` 70 | Returns a jQuery set of DOM elements linked to `node`; if includeEdges is true if also includes the edges 71 | 72 | `GraphvizSvg.linkedFrom(node, includeEdges)` 73 | Returns a jQuery set of DOM elements linked from `node`; if includeEdges is true if also includes the edges 74 | 75 | `GraphvizSvg.linked(node, includeEdges)` 76 | Returns a jQuery set of DOM elements linked with `node` (in an undirected graph); if includeEdges is true if also includes the edges 77 | 78 | `GraphvizSvg.tooltip($elements, show)` 79 | Show/hide tooltips on the SOM elements in the `$elements` set 80 | 81 | `GraphvizSvg.bringToFront($elements)` 82 | Brings the DOM elements in the jQuery set to the front 83 | 84 | `GraphvizSvg.sendToBack($elements)` 85 | Sends the DOM elements in the jQuery set to the back 86 | 87 | `GraphvizSvg.highlight($nodesEdges, tooltips)` 88 | Highlight the DOM elements in `$nodesEdges`, if `tooltips` is true also show tooltips. If no nodes are passed it unselects all nodes/edges 89 | 90 | 91 | Dependencies 92 | ------------ 93 | 94 | * jquery-2.1.3.js; for everything 95 | * Bootstrap; for default tooltips - not needed if you disable/dont use your own tooltips 96 | * jquery.color.js; for default highligh coloring - not needed it you supply your own 97 | * jquery.mousewheel.js; for scrolling - not needed if you turn it off 98 | 99 | 100 | Keywords 101 | -------- 102 | jQuery, Graphviz, dot, svg 103 | 104 | 105 | License 106 | ------- 107 | 108 | Copyright (c) 2015 Mountainstorm 109 | Permission is hereby granted, free of charge, to any person obtaining a copy 110 | of this software and associated documentation files (the "Software"), to deal 111 | in the Software without restriction, including without limitation the rights 112 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 113 | copies of the Software, and to permit persons to whom the Software is 114 | furnished to do so, subject to the following conditions: 115 | The above copyright notice and this permission notice shall be included in all 116 | copies or substantial portions of the Software. 117 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 118 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 119 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 120 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 121 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 122 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 123 | SOFTWARE. -------------------------------------------------------------------------------- /js/jquery.graphviz.svg.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Mountainstorm 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | 24 | +function ($) { 25 | 'use strict' 26 | 27 | // Cross Browser starts/endsWith support 28 | // ===================================== 29 | String.prototype.startsWith = function(prefix) { 30 | return this.indexOf(prefix) == 0; 31 | }; 32 | 33 | String.prototype.endsWith = function(suffix) { 34 | return this.indexOf(suffix, this.length - suffix.length) !== -1; 35 | }; 36 | 37 | // GRAPHVIZSVG PUBLIC CLASS DEFINITION 38 | // =================================== 39 | 40 | var GraphvizSvg = function (element, options) { 41 | this.type = null 42 | this.options = null 43 | this.enabled = null 44 | this.$element = null 45 | 46 | this.init('graphviz.svg', element, options) 47 | } 48 | 49 | GraphvizSvg.VERSION = '1.0.1' 50 | 51 | GraphvizSvg.GVPT_2_PX = 32.5 // used to ease removal of extra space 52 | 53 | GraphvizSvg.DEFAULTS = { 54 | url: null, 55 | svg: null, 56 | shrink: '0.125pt', 57 | tooltips: { 58 | init: function ($graph) { 59 | var $a = $(this) 60 | $a.tooltip({ 61 | container: $graph, 62 | placement: 'auto left', 63 | animation: false, 64 | viewport: null 65 | }).on('hide.bs.tooltip', function() { 66 | // keep them visible even if you acidentally mouse over 67 | if ($a.attr('data-tooltip-keepvisible')) { 68 | return false 69 | } 70 | }) 71 | }, 72 | show: function () { 73 | var $a = $(this) 74 | $a.attr('data-tooltip-keepvisible', true) 75 | $a.tooltip('show') 76 | }, 77 | hide: function () { 78 | var $a = $(this) 79 | $a.removeAttr('data-tooltip-keepvisible') 80 | $a.tooltip('hide') 81 | }, 82 | update: function () { 83 | var $this = $(this) 84 | if ($this.attr('data-tooltip-keepvisible')) { 85 | $this.tooltip('show') 86 | return 87 | } 88 | } 89 | }, 90 | zoom: true, 91 | highlight: { 92 | selected: function (col, bg) { 93 | return col 94 | }, 95 | unselected: function (col, bg) { 96 | return jQuery.Color(col).transition(bg, 0.9) 97 | } 98 | }, 99 | ready: null 100 | } 101 | 102 | GraphvizSvg.prototype.init = function (type, element, options) { 103 | this.enabled = true 104 | this.type = type 105 | this.$element = $(element) 106 | this.options = this.getOptions(options) 107 | 108 | if (options.url) { 109 | var that = this 110 | $.get(options.url, null, function(data) { 111 | var svg = $("svg", data) 112 | that.$element.html(document.adoptNode(svg[0])) 113 | that.setup() 114 | }, "xml") 115 | } else { 116 | if (options.svg) { 117 | this.$element.html(options.svg) 118 | } 119 | this.setup() 120 | } 121 | } 122 | 123 | GraphvizSvg.prototype.getDefaults = function () { 124 | return GraphvizSvg.DEFAULTS 125 | } 126 | 127 | GraphvizSvg.prototype.getOptions = function (options) { 128 | options = $.extend({}, this.getDefaults(), this.$element.data(), options) 129 | 130 | if (options.shrink) { 131 | if (typeof options.shrink != 'object') { 132 | options.shrink = { 133 | x: options.shrink, 134 | y: options.shrink 135 | } 136 | } 137 | options.shrink.x = this.convertToPx(options.shrink.x) 138 | options.shrink.y = this.convertToPx(options.shrink.y) 139 | } 140 | return options 141 | } 142 | 143 | GraphvizSvg.prototype.setup = function () { 144 | var options = this.options 145 | 146 | // save key elements in the graph for easy access 147 | var $svg = $(this.$element.children('svg')) 148 | var $graph = $svg.children('g:first') 149 | this.$svg = $svg 150 | this.$graph = $graph 151 | this.$background = $graph.children('polygon:first') // might not exist 152 | this.$nodes = $graph.children('.node') 153 | this.$edges = $graph.children('.edge') 154 | this._nodesByName = {} 155 | this._edgesByName = {} 156 | 157 | // add top level class and copy background color to element 158 | this.$element.addClass('graphviz-svg') 159 | if (this.$background.length) { 160 | this.$element.css('background', this.$background.attr('fill')) 161 | } 162 | 163 | // setup all the nodes and edges 164 | var that = this 165 | this.$nodes.each(function () { that.setupNodesEdges($(this), true) }) 166 | this.$edges.each(function () { that.setupNodesEdges($(this), false) }) 167 | 168 | // remove the graph title element 169 | var $title = this.$graph.children('title') 170 | this.$graph.attr('data-name', $title.text()) 171 | $title.remove() 172 | 173 | if (options.zoom) { 174 | this.setupZoom() 175 | } 176 | 177 | // tell people we're done 178 | if (options.ready) { 179 | options.ready.call(this) 180 | } 181 | } 182 | 183 | GraphvizSvg.prototype.setupNodesEdges = function ($el, isNode) { 184 | var that = this 185 | var options = this.options 186 | 187 | // save the colors of the paths, ellipses and polygons 188 | $el.find('polygon, ellipse, path').each(function () { 189 | var $this = $(this) 190 | // save original colors 191 | $this.data('graphviz.svg.color', { 192 | fill: $this.attr('fill'), 193 | stroke: $this.attr('stroke') 194 | 195 | }) 196 | 197 | // shrink it if it's a node 198 | if (isNode && options.shrink) { 199 | that.scaleNode($this) 200 | } 201 | }) 202 | 203 | // save the node name and check if theres a comment above; save it 204 | var $title = $el.children('title') 205 | if ($title[0]) { 206 | // remove any compass points: 207 | var title = $title.text().replace(/:[snew][ew]?/g,'') 208 | $el.attr('data-name', title) 209 | $title.remove() 210 | if (isNode) { 211 | this._nodesByName[title] = $el[0] 212 | } else { 213 | this._edgesByName[title] = $el[0] 214 | } 215 | // without a title we can't tell if its a user comment or not 216 | var previousSibling = $el[0].previousSibling 217 | while (previousSibling && previousSibling.nodeType != 8) { 218 | previousSibling = previousSibling.previousSibling 219 | } 220 | if (previousSibling != null && previousSibling.nodeType == 8) { 221 | var htmlDecode = function (input) { 222 | var e = document.createElement('div') 223 | e.innerHTML = input 224 | return e.childNodes[0].nodeValue 225 | } 226 | var value = htmlDecode(previousSibling.nodeValue.trim()) 227 | if (value != title) { 228 | // user added comment 229 | $el.attr('data-comment', value) 230 | } 231 | } 232 | } 233 | 234 | // remove namespace from a[xlink:title] 235 | $el.children('a').filter(function () { return $(this).attr('xlink:title') }).each(function () { 236 | var $a = $(this) 237 | $a.attr('title', $a.attr('xlink:title')) 238 | $a.removeAttr('xlink:title') 239 | if (options.tooltips) { 240 | options.tooltips.init.call(this, that.$element) 241 | } 242 | }) 243 | } 244 | 245 | GraphvizSvg.prototype.setupZoom = function() { 246 | var that = this 247 | var $element = this.$element 248 | var $svg = this.$svg 249 | this.zoom = {width: $svg.attr('width'), height: $svg.attr('height'), percentage: null } 250 | this.scaleView(100.0) 251 | $element.mousewheel(function (evt) { 252 | if (evt.shiftKey) { 253 | var percentage = that.zoom.percentage 254 | percentage -= evt.deltaY * evt.deltaFactor 255 | if (percentage < 100.0) { 256 | percentage = 100.0 257 | } 258 | // get pointer offset in view 259 | // ratio offset within svg 260 | var dx = evt.pageX - $svg.offset().left 261 | var dy = evt.pageY - $svg.offset().top 262 | var rx = dx / $svg.width() 263 | var ry = dy / $svg.height() 264 | 265 | // offset within frame ($element) 266 | var px = evt.pageX - $element.offset().left 267 | var py = evt.pageY - $element.offset().top 268 | 269 | that.scaleView(percentage) 270 | // scroll so pointer is still in same place 271 | $element.scrollLeft((rx * $svg.width()) + 0.5 - px) 272 | $element.scrollTop((ry * $svg.height()) + 0.5 - py) 273 | return false // stop propogation 274 | } 275 | }) 276 | } 277 | 278 | GraphvizSvg.prototype.scaleView = function(percentage) { 279 | var that = this 280 | var $svg = this.$svg 281 | $svg.attr('width', percentage + '%') 282 | $svg.attr('height', percentage + '%') 283 | this.zoom.percentage = percentage 284 | // now callback to update tooltip position 285 | var $everything = this.$nodes.add(this.$edges) 286 | $everything.children('a[title]').each(function () { 287 | that.options.tooltips.update.call(this) 288 | }) 289 | } 290 | 291 | GraphvizSvg.prototype.scaleNode = function($node) { 292 | var dx = this.options.shrink.x 293 | var dy = this.options.shrink.y 294 | var tagName = $node.prop('tagName') 295 | if (tagName == 'ellipse') { 296 | $node.attr('rx', parseFloat($node.attr('rx')) - dx) 297 | $node.attr('ry', parseFloat($node.attr('ry')) - dy) 298 | } else if (tagName == 'polygon') { 299 | // this is more complex - we need to scale it manually 300 | var bbox = $node[0].getBBox() 301 | var cx = bbox.x + (bbox.width / 2) 302 | var cy = bbox.y + (bbox.height / 2) 303 | var pts = $node.attr('points').split(' ') 304 | var points = '' // new value 305 | for (var i in pts) { 306 | var xy = pts[i].split(',') 307 | var ox = parseFloat(xy[0]) 308 | var oy = parseFloat(xy[1]) 309 | points += (((cx - ox) / (bbox.width / 2) * dx) + ox) + 310 | ',' + 311 | (((cy - oy) / (bbox.height / 2) * dy) + oy) + 312 | ' ' 313 | } 314 | $node.attr('points', points) 315 | } 316 | } 317 | 318 | GraphvizSvg.prototype.convertToPx = function (val) { 319 | var retval = val 320 | if (typeof val == 'string') { 321 | var end = val.length 322 | var factor = 1.0 323 | if (val.endsWith('px')) { 324 | end -= 2 325 | } else if (val.endsWith('pt')) { 326 | end -= 2 327 | factor = GraphvizSvg.GVPT_2_PX 328 | } 329 | retval = parseFloat(val.substring(0, end)) * factor 330 | } 331 | return retval 332 | } 333 | 334 | GraphvizSvg.prototype.findEdge = function (nodeName, testEdge, $retval) { 335 | var retval = [] 336 | for (var name in this._edgesByName) { 337 | var match = testEdge(nodeName, name) 338 | if (match) { 339 | if ($retval) { 340 | $retval.push(this._edgesByName[name]) 341 | } 342 | retval.push(match) 343 | } 344 | } 345 | return retval 346 | } 347 | 348 | GraphvizSvg.prototype.findLinked = function (node, includeEdges, testEdge, $retval) { 349 | var that = this 350 | var $node = $(node) 351 | var $edges = null 352 | if (includeEdges) { 353 | $edges = $retval 354 | } 355 | var names = this.findEdge($node.attr('data-name'), testEdge, $edges) 356 | for (var i in names) { 357 | var n = this._nodesByName[names[i]] 358 | if (!$retval.is(n)) { 359 | $retval.push(n) 360 | that.findLinked(n, includeEdges, testEdge, $retval) 361 | } 362 | } 363 | } 364 | 365 | GraphvizSvg.prototype.colorElement = function ($el, getColor) { 366 | var bg = this.$element.css('background') 367 | $el.find('polygon, ellipse, path').each(function() { 368 | var $this = $(this) 369 | var color = $this.data('graphviz.svg.color') 370 | if (color.fill && $this.prop('tagName') != 'path') { 371 | $this.attr('fill', getColor(color.fill, bg)) // don't set fill if it's a path 372 | } 373 | if (color.stroke) { 374 | $this.attr('stroke', getColor(color.stroke, bg)) 375 | } 376 | }) 377 | } 378 | 379 | GraphvizSvg.prototype.restoreElement = function ($el) { 380 | $el.find('polygon, ellipse, path').each(function() { 381 | var $this = $(this) 382 | var color = $this.data('graphviz.svg.color') 383 | if (color.fill) { 384 | $this.attr('fill', color.fill) // don't set fill if it's a path 385 | } 386 | if (color.stroke) { 387 | $this.attr('stroke', color.stroke) 388 | } 389 | }) 390 | } 391 | 392 | 393 | // methods users can actually call 394 | GraphvizSvg.prototype.nodes = function () { 395 | return this.$nodes 396 | } 397 | 398 | GraphvizSvg.prototype.edges = function () { 399 | return this.$edges 400 | } 401 | 402 | GraphvizSvg.prototype.nodesByName = function () { 403 | return this._nodesByName 404 | } 405 | 406 | GraphvizSvg.prototype.edgesByName = function () { 407 | return this._edgesByName 408 | } 409 | 410 | GraphvizSvg.prototype.linkedTo = function (node, includeEdges) { 411 | var $retval = $() 412 | this.findLinked(node, includeEdges, function (nodeName, edgeName) { 413 | var other = null; 414 | var match = '->' + nodeName 415 | if (edgeName.endsWith(match)) { 416 | other = edgeName.substring(0, edgeName.length - match.length); 417 | } 418 | return other; 419 | }, $retval) 420 | return $retval 421 | } 422 | 423 | GraphvizSvg.prototype.linkedFrom = function (node, includeEdges) { 424 | var $retval = $() 425 | this.findLinked(node, includeEdges, function (nodeName, edgeName) { 426 | var other = null; 427 | var match = nodeName + '->' 428 | if (edgeName.startsWith(match)) { 429 | other = edgeName.substring(match.length); 430 | } 431 | return other; 432 | }, $retval) 433 | return $retval 434 | } 435 | 436 | GraphvizSvg.prototype.linked = function (node, includeEdges) { 437 | var $retval = $() 438 | this.findLinked(node, includeEdges, function (nodeName, edgeName) { 439 | return '^' + name + '--(.*)$' 440 | }, $retval) 441 | this.findLinked(node, includeEdges, function (nodeName, edgeName) { 442 | return '^(.*)--' + name + '$' 443 | }, $retval) 444 | return $retval 445 | } 446 | 447 | GraphvizSvg.prototype.tooltip = function ($elements, show) { 448 | var that = this 449 | var options = this.options 450 | $elements.each(function () { 451 | $(this).children('a[title]').each(function () { 452 | if (show) { 453 | options.tooltips.show.call(this) 454 | } else { 455 | options.tooltips.hide.call(this) 456 | } 457 | }) 458 | }) 459 | } 460 | 461 | GraphvizSvg.prototype.bringToFront = function ($elements) { 462 | $elements.detach().appendTo(this.$graph) 463 | } 464 | 465 | GraphvizSvg.prototype.sendToBack = function ($elements) { 466 | if (this.$background.length) { 467 | $element.insertAfter(this.$background) 468 | } else { 469 | $elements.detach().prependTo(this.$graph) 470 | } 471 | } 472 | 473 | GraphvizSvg.prototype.highlight = function ($nodesEdges, tooltips) { 474 | var that = this 475 | var options = this.options 476 | var $everything = this.$nodes.add(this.$edges) 477 | if ($nodesEdges && $nodesEdges.length > 0) { 478 | // create set of all other elements and dim them 479 | $everything.not($nodesEdges).each(function () { 480 | that.colorElement($(this), options.highlight.unselected) 481 | that.tooltip($(this)) 482 | }) 483 | $nodesEdges.each(function () { 484 | that.colorElement($(this), options.highlight.selected) 485 | }) 486 | if (tooltips) { 487 | this.tooltip($nodesEdges, true) 488 | } 489 | } else { 490 | $everything.each(function () { 491 | that.restoreElement($(this)) 492 | }) 493 | this.tooltip($everything) 494 | } 495 | } 496 | 497 | GraphvizSvg.prototype.destroy = function () { 498 | var that = this 499 | this.hide(function () { 500 | that.$element.off('.' + that.type).removeData(that.type) 501 | }) 502 | } 503 | 504 | 505 | // GRAPHVIZSVG PLUGIN DEFINITION 506 | // ============================= 507 | 508 | function Plugin(option) { 509 | return this.each(function () { 510 | var $this = $(this) 511 | var data = $this.data('graphviz.svg') 512 | var options = typeof option == 'object' && option 513 | 514 | if (!data && /destroy/.test(option)) return 515 | if (!data) $this.data('graphviz.svg', (data = new GraphvizSvg(this, options))) 516 | if (typeof option == 'string') data[option]() 517 | }) 518 | } 519 | 520 | var old = $.fn.graphviz 521 | 522 | $.fn.graphviz = Plugin 523 | $.fn.graphviz.Constructor = GraphvizSvg 524 | 525 | 526 | // GRAPHVIZ NO CONFLICT 527 | // ==================== 528 | 529 | $.fn.graphviz.noConflict = function () { 530 | $.fn.graphviz = old 531 | return this 532 | } 533 | 534 | }(jQuery) 535 | -------------------------------------------------------------------------------- /demo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | world 11 | 12 | 13 | S35 14 | 15 | 16 | 17 | 18 | 19 | 43 20 | 21 | 22 | 23 | S35->43 24 | 25 | 26 | 27 | 28 | 36 29 | 30 | 31 | 32 | S35->36 33 | 34 | 35 | 36 | 37 | 23 38 | 39 | 40 | 41 | 42 | 43 | T1 44 | 45 | 46 | 47 | 48 | 49 | 23->T1 50 | 51 | 52 | 53 | 54 | T24 55 | 56 | 57 | 58 | 23->T24 59 | 60 | 61 | 62 | 63 | T99 64 | 65 | 66 | 67 | 68 | 69 | P4 70 | 71 | 72 | 73 | 74 | 75 | T30 76 | 77 | 78 | 79 | 80 | 81 | T30->P4 82 | 83 | 84 | 85 | 86 | 40 87 | 88 | Another 89 | 90 | 91 | 43->40 92 | 93 | 94 | 95 | 96 | 38 97 | 98 | 99 | 100 | 43->38 101 | 102 | 103 | 104 | 105 | 19 106 | 107 | 108 | 109 | 40->19 110 | 111 | 112 | 113 | 114 | 28 115 | 116 | 117 | 118 | 29 119 | 120 | 121 | 122 | 28->29 123 | 124 | 125 | 126 | 127 | 19->28 128 | 129 | 130 | 131 | 132 | 21 133 | 134 | 135 | 136 | 19->21 137 | 138 | 139 | 140 | 141 | 20 142 | 143 | 144 | 145 | 19->20 146 | 147 | 148 | 149 | 150 | S8 151 | 152 | 153 | 154 | 9 155 | 156 | 157 | 158 | S8->9 159 | 160 | 161 | 162 | 163 | 9->T1 164 | 165 | 166 | 167 | 168 | 42 169 | 170 | 171 | 172 | 9->42 173 | 174 | 175 | 176 | 177 | S24 178 | 179 | 180 | 181 | 25 182 | 183 | 184 | 185 | S24->25 186 | 187 | 188 | 189 | 190 | 27 191 | 192 | 193 | 194 | S24->27 195 | 196 | 197 | 198 | 199 | 200 | 25->T1 201 | 202 | 203 | 204 | 205 | 26 206 | 207 | 208 | 209 | 25->26 210 | 211 | 212 | 213 | 214 | 27->T24 215 | 216 | 217 | 218 | 219 | S1 220 | 221 | 222 | 223 | 2 224 | 225 | 226 | 227 | S1->2 228 | 229 | 230 | 231 | 232 | 10 233 | 234 | 235 | 236 | S1->10 237 | 238 | 239 | 240 | 241 | 2->T1 242 | 243 | 244 | 245 | 246 | 3 247 | 248 | 249 | 250 | 2->3 251 | 252 | 253 | 254 | 255 | 16 256 | 257 | 258 | 259 | 2->16 260 | 261 | 262 | 263 | 264 | 17 265 | 266 | 267 | 268 | 2->17 269 | 270 | 271 | 272 | 273 | 18 274 | 275 | 276 | 277 | 2->18 278 | 279 | 280 | 281 | 282 | 10->T1 283 | 284 | 285 | 286 | 287 | 11 288 | 289 | 290 | 291 | 10->11 292 | 293 | 294 | 295 | 296 | 14 297 | 298 | 299 | 300 | 10->14 301 | 302 | 303 | 304 | 305 | 13 306 | 307 | 308 | 309 | 10->13 310 | 311 | 312 | 313 | 314 | 12 315 | 316 | 317 | 318 | 10->12 319 | 320 | 321 | 322 | 323 | 36->19 324 | 325 | 326 | 327 | 328 | S30 329 | 330 | 331 | 332 | 31 333 | 334 | 335 | 336 | S30->31 337 | 338 | 339 | 340 | 341 | 33 342 | 343 | 344 | 345 | S30->33 346 | 347 | 348 | 349 | 350 | 31->T1 351 | 352 | 353 | 354 | 355 | 32 356 | 357 | 358 | 359 | 31->32 360 | 361 | 362 | 363 | 364 | 33->T30 365 | 366 | 367 | 368 | 369 | 34 370 | 371 | 372 | 373 | 33->34 374 | 375 | 376 | 377 | 378 | 4 379 | 380 | 381 | 382 | 42->4 383 | 384 | 385 | 386 | 387 | 26->4 388 | 389 | 390 | 391 | 392 | 3->4 393 | 394 | 395 | 396 | 397 | 15 398 | 399 | 400 | 401 | 16->15 402 | 403 | 404 | 405 | 406 | 17->19 407 | 408 | 409 | 410 | 411 | 18->29 412 | 413 | 414 | 415 | 416 | 11->4 417 | 418 | 419 | 420 | 421 | 14->15 422 | 423 | 424 | 425 | 426 | 13->19 427 | 428 | 429 | 430 | 431 | 12->29 432 | 433 | 434 | 435 | 436 | 32->23 437 | 438 | 439 | 440 | 441 | 34->29 442 | 443 | 444 | 445 | 446 | 5 447 | 448 | 449 | 450 | 4->5 451 | 452 | 453 | 454 | 455 | 15->T1 456 | 457 | 458 | 459 | 460 | 29->T30 461 | 462 | 463 | 464 | 465 | 37 466 | 467 | 468 | 469 | 37->40 470 | 471 | 472 | 473 | 474 | 39 475 | 476 | 477 | 478 | 37->39 479 | 480 | 481 | 482 | 483 | 41 484 | 485 | 486 | 487 | 37->41 488 | 489 | 490 | 491 | 492 | 37->38 493 | 494 | 495 | 496 | 497 | 39->15 498 | 499 | 500 | 501 | 502 | 41->29 503 | 504 | 505 | 506 | 507 | 38->4 508 | 509 | 510 | 511 | 512 | 5->23 513 | 514 | 515 | 516 | 517 | 6 518 | 519 | 520 | 521 | 5->6 522 | 523 | 524 | 525 | 526 | T35 527 | 528 | 529 | 530 | 5->T35 531 | 532 | 533 | 534 | 535 | 22 536 | 537 | 538 | 539 | 21->22 540 | 541 | 542 | 543 | 544 | 20->15 545 | 546 | 547 | 548 | 549 | 7 550 | 551 | 552 | 553 | 6->7 554 | 555 | 556 | 557 | 558 | 22->23 559 | 560 | 561 | 562 | 563 | 22->T35 564 | 565 | 566 | 567 | 568 | T8 569 | 570 | 571 | 572 | 7->T8 573 | 574 | 575 | 576 | 577 | 578 | --------------------------------------------------------------------------------