├── 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 |
578 |
--------------------------------------------------------------------------------