Skip to content

Commit c9558fe

Browse files
authored
Merge pull request #459 from WileyLabs/fix-383-add-jsonld-vis
Add Visualized tab to playground
2 parents 441ff75 + 8c56c6d commit c9558fe

File tree

4 files changed

+345
-0
lines changed

4 files changed

+345
-0
lines changed

playground/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/g/bootstrap@2.3.1(css/bootstrap-responsive.min.css),codemirror@3.22.0(codemirror.css+addon/lint/lint.css+addon/hint/show-hint.css+theme/neat.css)">
2424
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/fontawesome/3.2.1/css/font-awesome.min.css">
2525
<link rel="stylesheet" type="text/css" href="./playground.css">
26+
<link rel="stylesheet" type="text/css" href="./jsonld-vis.css">
2627
<link rel="stylesheet" type="text/css" href="../static/css/rdf-font/rdf-font.css">
2728

2829

@@ -330,6 +331,12 @@ <h3>Bitcoin (ECDSA Koblitz) Public Key for Verification</h3>
330331
<span>Normalized</span>
331332
</a>
332333
</li>
334+
<li>
335+
<a id="tab-visualized" href="#pane-visualized" data-toggle="tab" name="tab-visualized">
336+
<i class="icon-eye-open"></i>
337+
<span>Visualized</span>
338+
</a>
339+
</li>
333340
<li>
334341
<a id="tab-signed-rsa" href="#pane-signed-rsa" data-toggle="tab" name="tab-signed-rsa">
335342
<i class="icon-pencil"></i>
@@ -363,6 +370,9 @@ <h3>Bitcoin (ECDSA Koblitz) Public Key for Verification</h3>
363370
<div id="pane-normalized" class="tab-pane">
364371
<textarea id="normalized" class="codemirror-output"></textarea>
365372
</div>
373+
<div id="pane-visualized" class="tab-pane">
374+
<div id="visualized" style="background: white"></div>
375+
</div>
366376
<div id="pane-signed-rsa" class="tab-pane">
367377
<textarea id="signed-rsa" class="codemirror-output"></textarea>
368378
</div>
@@ -388,6 +398,9 @@ <h3>Bitcoin (ECDSA Koblitz) Public Key for Verification</h3>
388398
<!-- sccdn scripts -->
389399
<script src="//cdn.jsdelivr.net/g/async@1.5.0,jquery@1.11.0,es6-promise@1.0.0,bootstrap@2.3.2,codemirror@3.22.0(codemirror.min.js+addon/lint/lint.js+addon/edit/matchbrackets.js+addon/edit/closebrackets.js+addon/display/placeholder.js+addon/hint/show-hint.js+mode/ntriples/ntriples.js+mode/javascript/javascript.js)"></script>
390400
<script src="//cdnjs.cloudflare.com/ajax/libs/jsonld/0.4.2/jsonld.js"></script>
401+
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
402+
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script>
403+
<script src="./jsonld-vis.js"></script>
391404

392405
<!-- local scripts -->
393406
<!-- <script src="./jsonld.js"></script> -->

playground/jsonld-vis.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
@import url("http://fonts.googleapis.com/css?family=Open+Sans:300,400,600");
2+
3+
svg {
4+
border: none;
5+
}
6+
7+
.node {
8+
cursor: pointer;
9+
}
10+
11+
.node text {
12+
font-size: 12px;
13+
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, sans-serif;
14+
fill: #333333;
15+
}
16+
17+
.d3-tip {
18+
font-size: 14px;
19+
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, sans-serif;
20+
color: #333333;
21+
border: 1px solid #CCCCCC;
22+
border-radius: 5px;
23+
padding: 10px 20px;
24+
max-width: 250px;
25+
word-wrap: break-word;
26+
background-color: rgba(255, 255, 255, 0.9);
27+
text-align: left;
28+
}
29+
30+
.link {
31+
fill: none;
32+
stroke: #DADFE1;
33+
stroke-width: 1px;
34+
}

playground/jsonld-vis.js

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
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+
})();

playground/playground.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,13 @@
815815
options.format = 'application/nquads';
816816
promise = processor.normalize(input, options);
817817
}
818+
else if(playground.activeTab === 'tab-visualized') {
819+
// early return because this isn't an editor
820+
return new Promise(function() {
821+
$('#visualized').empty();
822+
d3.jsonldVis(input, '#visualized');
823+
});
824+
}
818825
else if(playground.activeTab === 'tab-signed-rsa') {
819826
options.format = 'application/ld+json';
820827

0 commit comments

Comments
 (0)