|
| 1 | +(function() { |
| 2 | + 'use strict'; |
| 3 | + |
| 4 | + function jsonldVis(jsonld, selector, config) { |
| 5 | + if (!arguments.length) return jsonldVis; |
| 6 | + config = config || {}; |
| 7 | + |
| 8 | + var h = config.h || 600 |
| 9 | + , w = config.w || 800 |
| 10 | + , maxLabelWidth = config.maxLabelWidth || 250 |
| 11 | + , transitionDuration = config.transitionDuration || 750 |
| 12 | + , transitionEase = config.transitionEase || 'cubic-in-out' |
| 13 | + , minRadius = config.minRadius || 5 |
| 14 | + , scalingFactor = config.scalingFactor || 2; |
| 15 | + |
| 16 | + var i = 0; |
| 17 | + |
| 18 | + var tree = d3.layout.tree() |
| 19 | + .size([h, w]); |
| 20 | + |
| 21 | + var diagonal = d3.svg.diagonal() |
| 22 | + .projection(function(d) { return [d.y, d.x]; }); |
| 23 | + |
| 24 | + var svg = d3.select(selector).append('svg') |
| 25 | + .attr('width', w) |
| 26 | + .attr('height', h) |
| 27 | + .append('g') |
| 28 | + .attr('transform', 'translate(' + maxLabelWidth + ',0)'); |
| 29 | + |
| 30 | + var tip = d3.tip() |
| 31 | + .direction(function(d) { |
| 32 | + return d.children || d._children ? 'w' : 'e'; |
| 33 | + }) |
| 34 | + .offset(function(d) { |
| 35 | + return d.children || d._children ? [0, -3] : [0, 3]; |
| 36 | + }) |
| 37 | + .attr('class', 'd3-tip') |
| 38 | + .html(function(d) { |
| 39 | + return '<span>' + d.valueExtended + '</span>'; |
| 40 | + }); |
| 41 | + |
| 42 | + svg.call(tip); |
| 43 | + |
| 44 | + var root = jsonldTree(jsonld); |
| 45 | + root.x0 = h / 2; |
| 46 | + root.y0 = 0; |
| 47 | + root.children.forEach(collapse); |
| 48 | + |
| 49 | + function changeSVGWidth(newWidth) { |
| 50 | + if (w !== newWidth) { |
| 51 | + d3.select(selector + ' > svg').attr('width', newWidth); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + function jsonldTree(source) { |
| 56 | + var tree = {}; |
| 57 | + |
| 58 | + if ('@id' in source) { |
| 59 | + tree.isIdNode = true; |
| 60 | + tree.name = source['@id']; |
| 61 | + if (tree.name.length > maxLabelWidth / 9) { |
| 62 | + tree.valueExtended = tree.name; |
| 63 | + tree.name = '...' + tree.valueExtended.slice(-Math.floor(maxLabelWidth / 9)); |
| 64 | + } |
| 65 | + } else { |
| 66 | + tree.isIdNode = true; |
| 67 | + tree.isBlankNode = true; |
| 68 | + // random id, can replace with actual uuid generator if needed |
| 69 | + tree.name = '_' + Math.random().toString(10).slice(-7); |
| 70 | + } |
| 71 | + |
| 72 | + var children = []; |
| 73 | + Object.keys(source).forEach(function(key) { |
| 74 | + if (key === '@id' || key === '@context' || source[key] === null) return; |
| 75 | + |
| 76 | + var valueExtended, value; |
| 77 | + if (typeof source[key] === 'object' && !Array.isArray(source[key])) { |
| 78 | + children.push({ |
| 79 | + name: key, |
| 80 | + children: [jsonldTree(source[key])] |
| 81 | + }); |
| 82 | + } else if (Array.isArray(source[key])) { |
| 83 | + children.push({ |
| 84 | + name: key, |
| 85 | + children: source[key].map(function(item) { |
| 86 | + if (typeof item === 'object') { |
| 87 | + return jsonldTree(item); |
| 88 | + } else { |
| 89 | + return { name: item }; |
| 90 | + } |
| 91 | + }) |
| 92 | + }); |
| 93 | + } else { |
| 94 | + valueExtended = source[key]; |
| 95 | + value = valueExtended; |
| 96 | + if (value.length > maxLabelWidth / 9) { |
| 97 | + value = value.slice(0, Math.floor(maxLabelWidth / 9)) + '...'; |
| 98 | + children.push({ |
| 99 | + name: key, |
| 100 | + value: value, |
| 101 | + valueExtended: valueExtended |
| 102 | + }); |
| 103 | + } else { |
| 104 | + children.push({ |
| 105 | + name: key, |
| 106 | + value: value |
| 107 | + }); |
| 108 | + } |
| 109 | + } |
| 110 | + }); |
| 111 | + |
| 112 | + if (children.length) { |
| 113 | + tree.children = children; |
| 114 | + } |
| 115 | + |
| 116 | + return tree; |
| 117 | + } |
| 118 | + |
| 119 | + function update(source) { |
| 120 | + var nodes = tree.nodes(root).reverse(); |
| 121 | + var links = tree.links(nodes); |
| 122 | + |
| 123 | + nodes.forEach(function(d) { d.y = d.depth * maxLabelWidth; }); |
| 124 | + |
| 125 | + var node = svg.selectAll('g.node') |
| 126 | + .data(nodes, function(d) { return d.id || (d.id = ++i); }); |
| 127 | + |
| 128 | + var nodeEnter = node.enter() |
| 129 | + .append('g') |
| 130 | + .attr('class', 'node') |
| 131 | + .attr('transform', function(d) { return 'translate(' + source.y0 + ',' + source.x0 + ')'; }) |
| 132 | + .on('click', click); |
| 133 | + |
| 134 | + nodeEnter.append('circle') |
| 135 | + .attr('r', 0) |
| 136 | + .style('stroke-width', function(d) { |
| 137 | + return d.isIdNode ? '2px' : '1px'; |
| 138 | + }) |
| 139 | + .style('stroke', function(d) { |
| 140 | + return d.isIdNode ? '#F7CA18' : '#4ECDC4'; |
| 141 | + }) |
| 142 | + .style('fill', function(d) { |
| 143 | + if (d.isIdNode) { |
| 144 | + return d._children ? '#F5D76E' : 'white'; |
| 145 | + } else { |
| 146 | + return d._children ? '#86E2D5' : 'white'; |
| 147 | + } |
| 148 | + }) |
| 149 | + .on('mouseover', function(d) { if (d.valueExtended) tip.show(d); }) |
| 150 | + .on('mouseout', tip.hide); |
| 151 | + |
| 152 | + nodeEnter.append('text') |
| 153 | + .attr('x', function(d) { |
| 154 | + var spacing = computeRadius(d) + 5; |
| 155 | + return d.children || d._children ? -spacing : spacing; |
| 156 | + }) |
| 157 | + .attr('dy', '4') |
| 158 | + .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; }) |
| 159 | + .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); }) |
| 160 | + .style('fill-opacity', 0); |
| 161 | + |
| 162 | + var maxSpan = Math.max.apply(Math, nodes.map(function(d) { return d.y + maxLabelWidth; })); |
| 163 | + if (maxSpan + maxLabelWidth + 20 > w) { |
| 164 | + changeSVGWidth(maxSpan + maxLabelWidth); |
| 165 | + d3.select(selector).node().scrollLeft = source.y0; |
| 166 | + } |
| 167 | + |
| 168 | + var nodeUpdate = node.transition() |
| 169 | + .duration(transitionDuration) |
| 170 | + .ease(transitionEase) |
| 171 | + .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; }); |
| 172 | + |
| 173 | + nodeUpdate.select('circle') |
| 174 | + .attr('r', function(d) { return computeRadius(d); }) |
| 175 | + .style('stroke-width', function(d) { |
| 176 | + return d.isIdNode ? '2px' : '1px'; |
| 177 | + }) |
| 178 | + .style('stroke', function(d) { |
| 179 | + return d.isIdNode ? '#F7CA18' : '#4ECDC4'; |
| 180 | + }) |
| 181 | + .style('fill', function(d) { |
| 182 | + if (d.isIdNode) { |
| 183 | + return d._children ? '#F5D76E' : 'white'; |
| 184 | + } else { |
| 185 | + return d._children ? '#86E2D5' : 'white'; |
| 186 | + } |
| 187 | + }); |
| 188 | + |
| 189 | + nodeUpdate.select('text').style('fill-opacity', 1); |
| 190 | + |
| 191 | + var nodeExit = node.exit().transition() |
| 192 | + .duration(transitionDuration) |
| 193 | + .ease(transitionEase) |
| 194 | + .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; }) |
| 195 | + .remove(); |
| 196 | + |
| 197 | + nodeExit.select('circle').attr('r', 0); |
| 198 | + nodeExit.select('text').style('fill-opacity', 0); |
| 199 | + |
| 200 | + var link = svg.selectAll('path.link') |
| 201 | + .data(links, function(d) { return d.target.id; }); |
| 202 | + |
| 203 | + link.enter().insert('path', 'g') |
| 204 | + .attr('class', 'link') |
| 205 | + .attr('d', function(d) { |
| 206 | + var o = { x: source.x0, y: source.y0 }; |
| 207 | + return diagonal({ source: o, target: o }); |
| 208 | + }); |
| 209 | + |
| 210 | + link.transition() |
| 211 | + .duration(transitionDuration) |
| 212 | + .ease(transitionEase) |
| 213 | + .attr('d', diagonal); |
| 214 | + |
| 215 | + link.exit().transition() |
| 216 | + .duration(transitionDuration) |
| 217 | + .ease(transitionEase) |
| 218 | + .attr('d', function(d) { |
| 219 | + var o = { x: source.x, y: source.y }; |
| 220 | + return diagonal({ source: o, target: o }); |
| 221 | + }) |
| 222 | + .remove(); |
| 223 | + |
| 224 | + nodes.forEach(function(d) { |
| 225 | + d.x0 = d.x; |
| 226 | + d.y0 = d.y; |
| 227 | + }); |
| 228 | + } |
| 229 | + |
| 230 | + function computeRadius(d) { |
| 231 | + if (d.children || d._children) { |
| 232 | + return minRadius + (numEndNodes(d) / scalingFactor); |
| 233 | + } else { |
| 234 | + return minRadius; |
| 235 | + } |
| 236 | + } |
| 237 | + |
| 238 | + function numEndNodes(n) { |
| 239 | + var num = 0; |
| 240 | + if (n.children) { |
| 241 | + n.children.forEach(function(c) { |
| 242 | + num += numEndNodes(c); |
| 243 | + }); |
| 244 | + } else if (n._children) { |
| 245 | + n._children.forEach(function(c) { |
| 246 | + num += numEndNodes(c); |
| 247 | + }); |
| 248 | + } else { |
| 249 | + num++; |
| 250 | + } |
| 251 | + return num; |
| 252 | + } |
| 253 | + |
| 254 | + function click(d) { |
| 255 | + if (d.children) { |
| 256 | + d._children = d.children; |
| 257 | + d.children = null; |
| 258 | + } else { |
| 259 | + d.children = d._children; |
| 260 | + d._children = null; |
| 261 | + } |
| 262 | + |
| 263 | + update(d); |
| 264 | + |
| 265 | + // fast-forward blank nodes |
| 266 | + if (d.children) { |
| 267 | + d.children.forEach(function(child) { |
| 268 | + if (child.isBlankNode && child._children) { |
| 269 | + click(child); |
| 270 | + } |
| 271 | + }); |
| 272 | + } |
| 273 | + } |
| 274 | + |
| 275 | + function collapse(d) { |
| 276 | + if (d.children) { |
| 277 | + d._children = d.children; |
| 278 | + d._children.forEach(collapse); |
| 279 | + d.children = null; |
| 280 | + } |
| 281 | + } |
| 282 | + |
| 283 | + update(root); |
| 284 | + } |
| 285 | + |
| 286 | + if (typeof module !== 'undefined' && module.exports) { |
| 287 | + module.exports = jsonldVis; |
| 288 | + } else { |
| 289 | + d3.jsonldVis = jsonldVis; |
| 290 | + } |
| 291 | +})(); |
0 commit comments