1
|
|
|
/** global: jsondash */ |
2
|
|
|
/** global: c3 */ |
3
|
|
|
/** global: d3 */ |
4
|
|
|
/** global: venn */ |
5
|
|
|
/** global: Plotly */ |
6
|
|
|
|
7
|
|
|
jsondash.getJSON = function(container, url, callback) { |
8
|
|
|
if(!url) throw new Error('Invalid URL: ' + url); |
|
|
|
|
9
|
|
|
var err_msg = null; |
10
|
|
|
d3.json(url, function(error, data){ |
11
|
|
|
if(error) { |
12
|
|
|
jsondash.unload(container); |
13
|
|
|
err_msg = 'Error: ' + error.status + ' ' + error.statusText; |
14
|
|
|
} |
15
|
|
|
else if(!data) { |
16
|
|
|
jsondash.unload(container); |
17
|
|
|
err_msg = 'No data was found (invalid response).'; |
18
|
|
|
} |
19
|
|
|
if(error || !data) { |
20
|
|
|
container.classed({error: true}); |
21
|
|
|
container.select('.error-overlay') |
22
|
|
|
.classed({hidden: false}) |
23
|
|
|
.select('.alert') |
24
|
|
|
.text(err_msg); |
25
|
|
|
jsondash.unload(container); |
26
|
|
|
return; |
27
|
|
|
} |
28
|
|
|
callback(error, data); |
29
|
|
|
}); |
30
|
|
|
}; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* [getDynamicWidth Return the width for a container that has no specified width |
34
|
|
|
* (e.g. grid mode)] |
35
|
|
|
*/ |
36
|
|
|
jsondash.getDynamicWidth = function(container, config) { |
37
|
|
|
if(isNaN(config.width)) { |
38
|
|
|
return d3.round(container.node().getBoundingClientRect().width); |
39
|
|
|
} |
40
|
|
|
return parseInt(config.width, 10); |
41
|
|
|
}; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* [getDiameter Calculate a valid diameter for a circular widget, |
45
|
|
|
* based on width/height to ensure the size never goes out of the container bounds.] |
46
|
|
|
*/ |
47
|
|
|
jsondash.getDiameter = function(container, config) { |
48
|
|
|
var width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
49
|
|
|
return d3.min([d3.round(width), config.height]); |
50
|
|
|
}; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Handler for all vega-lite specifications |
54
|
|
|
*/ |
55
|
|
|
jsondash.handlers.handleVegaLite = function(container, config) { |
56
|
|
|
'use strict'; |
57
|
|
|
container.selectAll('.chart-container').remove(); |
58
|
|
|
container.append('div').classed({'chart-container': true}); |
59
|
|
|
|
60
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, vlspec){ |
61
|
|
|
var SCALE_FACTOR = 0.7; // very important to get sizing jusst right. |
62
|
|
|
var selector = '[data-guid="' + config.guid + '"] .chart-container'; |
63
|
|
|
var width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
64
|
|
|
var size = d3.max([config.height, width]); |
65
|
|
|
var overrides = { |
66
|
|
|
width: ~~(size * SCALE_FACTOR), |
67
|
|
|
height: ~~(config.height * SCALE_FACTOR) |
68
|
|
|
}; |
69
|
|
|
var embedSpec = { |
70
|
|
|
mode: 'vega-lite', |
71
|
|
|
spec: $.extend({}, vlspec, overrides) |
72
|
|
|
}; |
73
|
|
|
vg.embed(selector, embedSpec, function(error, result) { |
|
|
|
|
74
|
|
|
// Callback receiving the View instance and parsed Vega spec |
75
|
|
|
// result.view is the View, which resides under the '#vis' element |
76
|
|
|
if(error) { |
77
|
|
|
throw new Error('Error loading chart: ' + error); |
78
|
|
|
} |
79
|
|
|
// Change look of default buttons |
80
|
|
|
container.select('.vega-actions') |
81
|
|
|
.classed({'btn-group': true}) |
82
|
|
|
.selectAll('a') |
83
|
|
|
.classed({'btn btn-xs btn-default': true}); |
84
|
|
|
|
85
|
|
|
// Look for callbacks potentially registered for third party code. |
86
|
|
|
jsondash.api.runCallbacks(container, config); |
87
|
|
|
jsondash.unload(container); |
88
|
|
|
}); |
89
|
|
|
}); |
90
|
|
|
}; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Handlers for various widget types. The method signatures are always the same, |
94
|
|
|
* but each handler can handle them differently. |
95
|
|
|
*/ |
96
|
|
|
jsondash.handlers.handleYoutube = function(container, config) { |
97
|
|
|
// Clean up all previous. |
98
|
|
|
'use strict'; |
99
|
|
|
container.selectAll('iframe').remove(); |
100
|
|
|
|
101
|
|
|
function getAttr(prop, props) { |
102
|
|
|
// Return the propery from a list of properties for the iframe. |
103
|
|
|
// e.g. getAttr('width', ["width="900""]) --> "900" |
104
|
|
|
return props.filter(function(k, v){ |
|
|
|
|
105
|
|
|
return k.startsWith(prop); |
106
|
|
|
})[0]; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
|
110
|
|
|
var url = config.dataSource; |
|
|
|
|
111
|
|
|
var parts = config.dataSource.split(' '); |
112
|
|
|
var yt_width = parseInt(getAttr('width', parts).split('=')[1].replace(/"/gi, ''), 10); |
113
|
|
|
var height = parseInt(getAttr('height', parts).split('=')[1].replace(/"/gi, ''), 10); |
114
|
|
|
var width = isNaN(config.width) ? '100%' : yt_width; |
115
|
|
|
var url = getAttr('src', parts).replace('src=', '').replace(/"/gi, ''); |
|
|
|
|
116
|
|
|
|
117
|
|
|
// In the case of YouTube, we have to override the config dimensions |
118
|
|
|
// as this will be wonky when the aspect ratio is calculated. We will |
119
|
|
|
// defer to YouTube calculations instead. |
120
|
|
|
container.append('iframe') |
121
|
|
|
.attr('width', width) |
122
|
|
|
.attr('height', height) |
123
|
|
|
.attr('src', url) |
124
|
|
|
.attr('allowfullscreen', true) |
125
|
|
|
.attr('frameborder', 0); |
126
|
|
|
// Look for callbacks potentially registered for third party code. |
127
|
|
|
jsondash.api.runCallbacks(container, config); |
128
|
|
|
jsondash.unload(container); |
129
|
|
|
}; |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* [handleGraph creates graphs using the dot format |
133
|
|
|
* spec with d3 and dagre-d3] |
134
|
|
|
*/ |
135
|
|
|
jsondash.handlers.handleGraph = function(container, config) { |
136
|
|
|
'use strict'; |
137
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, data){ |
138
|
|
|
container.selectAll('.chart-graph').remove(); |
139
|
|
|
var h = config.height - jsondash.config.WIDGET_MARGIN_Y; |
|
|
|
|
140
|
|
|
var w = config.width - jsondash.config.WIDGET_MARGIN_X; |
|
|
|
|
141
|
|
|
var svg = container.append('svg').classed({'chart-graph': true}); |
142
|
|
|
var svg_group = svg.append('g'); |
143
|
|
|
var g = graphlibDot.read(data.graph); |
|
|
|
|
144
|
|
|
var bbox = null; |
|
|
|
|
145
|
|
|
// Create the renderer |
146
|
|
|
var render = new dagreD3.render(); |
|
|
|
|
147
|
|
|
render(svg_group, g); |
148
|
|
|
bbox = svg.node().getBBox(); |
149
|
|
|
svg.attr('width', bbox.width) |
150
|
|
|
.attr('height', bbox.height); |
151
|
|
|
// Look for callbacks potentially registered for third party code. |
152
|
|
|
jsondash.api.runCallbacks(container, config); |
153
|
|
|
jsondash.unload(container); |
154
|
|
|
}); |
155
|
|
|
}; |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* [handleWordCloud create word clouds using the d3-cloud extension.] |
159
|
|
|
*/ |
160
|
|
|
jsondash.handlers.handleWordCloud = function(container, config) { |
161
|
|
|
'use strict'; |
162
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, data){ |
163
|
|
|
container.selectAll('.wordcloud').remove(); |
164
|
|
|
var h = config.height - jsondash.config.WIDGET_MARGIN_Y; |
165
|
|
|
var w = config.width - jsondash.config.WIDGET_MARGIN_X; |
166
|
|
|
var svg = container.append('svg').classed({'wordcloud': true}); |
167
|
|
|
var fill = d3.scale.category20(); |
|
|
|
|
168
|
|
|
var cloud = d3.layout.cloud; |
169
|
|
|
var words = data.map(function(d) { |
170
|
|
|
return {text: d.text, size: d.size}; |
171
|
|
|
}); |
172
|
|
|
var layout = cloud() |
173
|
|
|
.size([w, h]) |
174
|
|
|
.words(words) |
175
|
|
|
.padding(4) |
176
|
|
|
.rotate(function() {return ~~(Math.random() * 1) * 90;}) |
177
|
|
|
.font('Arial') |
178
|
|
|
.fontSize(function(d) {return d.size;}) |
179
|
|
|
.on('end', draw); |
180
|
|
|
|
181
|
|
|
layout.start(); |
182
|
|
|
|
183
|
|
|
function draw(words) { |
184
|
|
|
svg |
185
|
|
|
.attr('width', layout.size()[0]) |
186
|
|
|
.attr('height', layout.size()[1]) |
187
|
|
|
.append('g') |
188
|
|
|
.attr('transform', 'translate(' + layout.size()[0] / 2 + ',' + layout.size()[1] / 2 + ')') |
189
|
|
|
.selectAll('text').data(words) |
190
|
|
|
.enter().append('text') |
191
|
|
|
.style('font-size', function(d) { return d.size + 'px'; }) |
192
|
|
|
.style('font-family', 'arial') |
193
|
|
|
.style('fill', function(d, i) { return "#000"; }) |
|
|
|
|
194
|
|
|
.attr('text-anchor', 'middle') |
195
|
|
|
.attr('transform', function(d) { |
196
|
|
|
return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')'; |
197
|
|
|
}) |
198
|
|
|
.text(function(d) { return d.text; }); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
// Look for callbacks potentially registered for third party code. |
202
|
|
|
jsondash.api.runCallbacks(container, config); |
203
|
|
|
jsondash.unload(container); |
204
|
|
|
}); |
205
|
|
|
}; |
206
|
|
|
|
207
|
|
|
jsondash.handlers.handleC3 = function(container, config) { |
208
|
|
|
var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
209
|
|
|
'use strict'; |
210
|
|
|
var init_config = { |
211
|
|
|
bindto: '[data-guid="' + config.guid + '"] .chart-container', |
212
|
|
|
legend: { |
213
|
|
|
show: true |
214
|
|
|
}, |
215
|
|
|
size: { |
216
|
|
|
height: config.height - jsondash.config.WIDGET_MARGIN_Y, |
217
|
|
|
width: _width - jsondash.config.WIDGET_MARGIN_X |
218
|
|
|
}, |
219
|
|
|
data: { |
220
|
|
|
type: config.type, |
221
|
|
|
url: config.dataSource, |
222
|
|
|
mimeType: 'json' |
223
|
|
|
}, |
224
|
|
|
onrendered: function(){ |
225
|
|
|
// Look for callbacks potentially registered for third party code. |
226
|
|
|
jsondash.api.runCallbacks(container, config); |
227
|
|
|
jsondash.unload(container); |
228
|
|
|
} |
229
|
|
|
}; |
230
|
|
|
if(jsondash.util.isOverride(config)) { |
231
|
|
|
// Just use the raw payload for this widgets' options. |
232
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, data){ |
233
|
|
|
// Keep existing options if not specified. |
234
|
|
|
config = $.extend(init_config, data); |
235
|
|
|
c3.generate(init_config); |
236
|
|
|
}); |
237
|
|
|
return; |
238
|
|
|
} |
239
|
|
|
if(config.type === 'timeseries') { |
240
|
|
|
init_config.axis = { |
241
|
|
|
x: {type: 'timeseries'} |
242
|
|
|
}; |
243
|
|
|
// Map the corresponding data key and list of dates |
244
|
|
|
// to the `x` property. |
245
|
|
|
init_config.data.x = 'dates'; |
246
|
|
|
} |
247
|
|
|
c3.generate(init_config); |
248
|
|
|
}; |
249
|
|
|
|
250
|
|
|
jsondash.handlers.handleD3 = function(container, config) { |
251
|
|
|
'use strict'; |
252
|
|
|
// Clean up all D3 charts in one step. |
253
|
|
|
container.selectAll('svg').remove(); |
254
|
|
|
// Handle specific types. |
255
|
|
|
if(config.type === 'radial-dendrogram') { return jsondash.handlers.handleRadialDendrogram(container, config); } |
256
|
|
|
if(config.type === 'dendrogram') { return jsondash.handlers.handleDendrogram(container, config); } |
257
|
|
|
if(config.type === 'voronoi') { return jsondash.handlers.handleVoronoi(container, config); } |
258
|
|
|
if(config.type === 'treemap') { return jsondash.handlers.handleTreemap(container, config); } |
259
|
|
|
if(config.type === 'circlepack') { return jsondash.handlers.handleCirclePack(container, config); } |
260
|
|
|
throw new Error('Unknown type: ' + config.type); |
261
|
|
|
}; |
262
|
|
|
|
263
|
|
|
jsondash.handlers.handleCirclePack = function(container, config) { |
264
|
|
|
'use strict'; |
265
|
|
|
// Adapted from https://bl.ocks.org/mbostock/4063530 |
266
|
|
|
var margin = jsondash.config.WIDGET_MARGIN_Y; |
267
|
|
|
var diameter = jsondash.getDiameter(container, config) - margin; |
268
|
|
|
var format = d3.format(',d'); |
269
|
|
|
var pack = d3.layout.pack() |
270
|
|
|
.size([diameter, diameter]) |
271
|
|
|
.value(function(d) { return d.size; }); |
272
|
|
|
|
273
|
|
|
var svg = container |
274
|
|
|
.append('svg') |
275
|
|
|
.attr('width', diameter) |
276
|
|
|
.attr('height', diameter) |
277
|
|
|
.append('g'); |
278
|
|
|
|
279
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, data) { |
280
|
|
|
var node = svg.datum(data).selectAll('.node') |
281
|
|
|
.data(pack.nodes) |
282
|
|
|
.enter().append('g') |
283
|
|
|
.attr('class', function(d) { return d.children ? 'node' : 'leaf node'; }) |
284
|
|
|
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; }); |
285
|
|
|
|
286
|
|
|
node.append('title') |
287
|
|
|
.text(function(d) { return d.name + (d.children ? '' : ': ' + format(d.size)); }); |
288
|
|
|
|
289
|
|
|
node.append('circle') |
290
|
|
|
.attr('r', function(d) { return d.r; }); |
291
|
|
|
|
292
|
|
|
node.filter(function(d) { return !d.children; }).append('text') |
293
|
|
|
.attr('dy', '.3em') |
294
|
|
|
.style('text-anchor', 'middle') |
295
|
|
|
.text(function(d) { return d.name.substring(0, d.r / 3); }); |
296
|
|
|
// Look for callbacks potentially registered for third party code. |
297
|
|
|
jsondash.api.runCallbacks(container, config); |
298
|
|
|
jsondash.unload(container); |
299
|
|
|
}); |
300
|
|
|
d3.select(self.frameElement).style("height", diameter + "px"); |
|
|
|
|
301
|
|
|
}; |
302
|
|
|
|
303
|
|
|
jsondash.handlers.handleTreemap = function(container, config) { |
304
|
|
|
'use strict'; |
305
|
|
|
// Adapted from http://bl.ocks.org/mbostock/4063582 |
306
|
|
|
var margin = { |
|
|
|
|
307
|
|
|
top: jsondash.config.WIDGET_MARGIN_Y / 2, |
308
|
|
|
bottom: jsondash.config.WIDGET_MARGIN_Y / 2, |
309
|
|
|
left: jsondash.config.WIDGET_MARGIN_X / 2, |
310
|
|
|
right: jsondash.config.WIDGET_MARGIN_X / 2 |
311
|
|
|
}; |
312
|
|
|
var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
313
|
|
|
var width = _width - jsondash.config.WIDGET_MARGIN_X; |
314
|
|
|
var height = config.height - jsondash.config.WIDGET_MARGIN_Y; |
315
|
|
|
var color = d3.scale.category20c(); |
316
|
|
|
var treemap = d3.layout.treemap() |
317
|
|
|
.size([width, height]) |
318
|
|
|
.sticky(true) |
319
|
|
|
.value(function(d) { return d.size; }); |
320
|
|
|
// Cleanup |
321
|
|
|
container.selectAll('.treemap').remove(); |
322
|
|
|
var div = container |
323
|
|
|
.append('div') |
324
|
|
|
.classed({treemap: true, 'chart-centered': true}) |
325
|
|
|
.style('position', 'relative') |
326
|
|
|
.style('width', width + 'px') |
327
|
|
|
.style('height', height + 'px'); |
328
|
|
|
|
329
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, root) { |
330
|
|
|
var node = div.datum(root).selectAll('.node') |
331
|
|
|
.data(treemap.nodes) |
332
|
|
|
.enter().append('div') |
333
|
|
|
.attr('class', 'node') |
334
|
|
|
.call(position) |
335
|
|
|
.style('border', '1px solid white') |
336
|
|
|
.style('font', '10px sans-serif') |
337
|
|
|
.style('line-height', '12px') |
338
|
|
|
.style('overflow', 'hidden') |
339
|
|
|
.style('position', 'absolute') |
340
|
|
|
.style('text-indent', '2px') |
341
|
|
|
.style('background', function(d) { |
342
|
|
|
return d.children ? color(d.name) : null; |
343
|
|
|
}) |
344
|
|
|
.text(function(d) { |
345
|
|
|
return d.children ? null : d.name; |
346
|
|
|
}); |
347
|
|
|
d3.selectAll('input').on('change', function change() { |
348
|
|
|
var value = this.value === 'count' |
349
|
|
|
? function() { return 1; } |
350
|
|
|
: function(d) { return d.size;}; |
351
|
|
|
node |
352
|
|
|
.data(treemap.value(value).nodes) |
353
|
|
|
.transition() |
354
|
|
|
.duration(1500) |
355
|
|
|
.call(position); |
356
|
|
|
}); |
357
|
|
|
// Look for callbacks potentially registered for third party code. |
358
|
|
|
jsondash.api.runCallbacks(container, config); |
359
|
|
|
jsondash.unload(container); |
360
|
|
|
}); |
361
|
|
|
|
362
|
|
|
function position() { |
363
|
|
|
this.style('left', function(d) { return d.x + 'px'; }) |
364
|
|
|
.style('top', function(d) { return d.y + 'px'; }) |
365
|
|
|
.style('width', function(d) { return Math.max(0, d.dx - 1) + 'px'; }) |
366
|
|
|
.style('height', function(d) { return Math.max(0, d.dy - 1) + 'px'; }); |
367
|
|
|
} |
368
|
|
|
}; |
369
|
|
|
|
370
|
|
|
jsondash.handlers.handleRadialDendrogram = function(container, config) { |
371
|
|
|
'use strict'; |
372
|
|
|
// Code taken (and refactored for use here) from: |
373
|
|
|
// https://bl.ocks.org/mbostock/4339607 |
374
|
|
|
var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
|
|
|
|
375
|
|
|
var radius = jsondash.getDiameter(container, config); |
376
|
|
|
var cluster = d3.layout.cluster() |
377
|
|
|
.size([360, radius / 2 - 150]); // reduce size relative to `radius` |
378
|
|
|
var diagonal = d3.svg.diagonal.radial() |
379
|
|
|
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); |
380
|
|
|
var svg = container.append('svg') |
381
|
|
|
.attr('width', radius) |
382
|
|
|
.attr('height', radius); |
383
|
|
|
var g = svg.append('g'); |
384
|
|
|
g.attr('transform', 'translate(' + radius / 2 + ',' + radius / 2 + ')'); |
385
|
|
|
|
386
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, root) { |
387
|
|
|
if (error) { throw error; } |
388
|
|
|
var nodes = cluster.nodes(root); |
389
|
|
|
var link = g.selectAll('path.link') |
|
|
|
|
390
|
|
|
.data(cluster.links(nodes)) |
391
|
|
|
.enter().append('path') |
392
|
|
|
.attr('class', 'link') |
393
|
|
|
.attr('d', diagonal); |
394
|
|
|
var node = g.selectAll('g.node') |
395
|
|
|
.data(nodes) |
396
|
|
|
.enter().append('g') |
397
|
|
|
.attr('class', 'node') |
398
|
|
|
.attr('transform', function(d) { return 'rotate(' + (d.x - 90) + ')translate(' + d.y + ')'; }); |
399
|
|
|
node.append('circle') |
400
|
|
|
.attr('r', 4.5); |
401
|
|
|
node.append('text') |
402
|
|
|
.attr('dy', '.31em') |
403
|
|
|
.attr('text-anchor', function(d) { return d.x < 180 ? 'start' : 'end'; }) |
404
|
|
|
.attr('transform', function(d) { return d.x < 180 ? 'translate(8)' : 'rotate(180)translate(-8)'; }) |
405
|
|
|
.text(function(d) { return d.name; }); |
406
|
|
|
// Look for callbacks potentially registered for third party code. |
407
|
|
|
jsondash.api.runCallbacks(container, config); |
408
|
|
|
jsondash.unload(container); |
409
|
|
|
}); |
410
|
|
|
d3.select(self.frameElement).style('height', radius * 2 + 'px'); |
|
|
|
|
411
|
|
|
}; |
412
|
|
|
|
413
|
|
|
jsondash.handlers.handleDendrogram = function(container, config) { |
414
|
|
|
'use strict'; |
415
|
|
|
var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
416
|
|
|
// A general padding for the svg inside of the widget. |
417
|
|
|
// The cluster dendrogram will also need to have padding itself, so |
418
|
|
|
// the bounds are not clipped in the svg. |
419
|
|
|
var svg_pad = 20; |
420
|
|
|
var width = _width - svg_pad; |
421
|
|
|
var height = config.height - svg_pad; |
422
|
|
|
var PADDING = width / 4; |
423
|
|
|
var cluster = d3.layout.cluster() |
424
|
|
|
.size([height * 0.85, width - PADDING]); |
425
|
|
|
var diagonal = d3.svg.diagonal() |
426
|
|
|
.projection(function(d) { return [d.y, d.x]; }); |
427
|
|
|
var svg = container |
428
|
|
|
.append('svg') |
429
|
|
|
.attr('width', width) |
430
|
|
|
.attr('height', height); |
431
|
|
|
var g = svg.append('g') |
432
|
|
|
.attr('transform', 'translate(40, 0)'); |
433
|
|
|
|
434
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, root) { |
435
|
|
|
var nodes = cluster.nodes(root); |
436
|
|
|
var links = cluster.links(nodes); |
437
|
|
|
var link = g.selectAll('.link') |
|
|
|
|
438
|
|
|
.data(links) |
439
|
|
|
.enter().append('path') |
440
|
|
|
.attr('class', 'link') |
441
|
|
|
.attr('d', diagonal); |
442
|
|
|
|
443
|
|
|
var node = g.selectAll('.node') |
444
|
|
|
.data(nodes) |
445
|
|
|
.enter().append('g') |
446
|
|
|
.attr('class', 'node') |
447
|
|
|
.attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; }); |
448
|
|
|
|
449
|
|
|
node.append('circle').attr('r', 4.5); |
450
|
|
|
node.append('text') |
451
|
|
|
.attr('dx', function(d) { return d.children ? -8 : 8; }) |
452
|
|
|
.attr('dy', 3) |
453
|
|
|
.style('text-anchor', function(d) { return d.children ? 'end' : 'start'; }) |
454
|
|
|
.text(function(d) { return d.name; }); |
455
|
|
|
|
456
|
|
|
// Look for callbacks potentially registered for third party code. |
457
|
|
|
jsondash.api.runCallbacks(container, config); |
458
|
|
|
jsondash.unload(container); |
459
|
|
|
}); |
460
|
|
|
}; |
461
|
|
|
|
462
|
|
|
jsondash.handlers.handleVoronoi = function(container, config) { |
463
|
|
|
'use strict'; |
464
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, data){ |
465
|
|
|
var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
466
|
|
|
var width = _width - jsondash.config.WIDGET_MARGIN_X; |
467
|
|
|
var height = config.height - jsondash.config.WIDGET_MARGIN_Y; |
468
|
|
|
var vertices = data; |
469
|
|
|
var voronoi = d3.geom.voronoi().clipExtent([[0, 0], [width, height]]); |
470
|
|
|
// Cleanup |
471
|
|
|
container.selectAll('svg').remove(); |
472
|
|
|
var svg = container |
473
|
|
|
.append('svg') |
474
|
|
|
.attr('width', width) |
475
|
|
|
.attr('height', height); |
476
|
|
|
var path = svg.append('g').selectAll('path'); |
477
|
|
|
svg.selectAll('circle') |
478
|
|
|
.data(vertices.slice(1)) |
479
|
|
|
.enter().append('circle') |
480
|
|
|
.attr('transform', function(d) { return 'translate(' + d + ')'; }) |
481
|
|
|
.attr('r', 1.5); |
482
|
|
|
redraw(); |
483
|
|
|
|
484
|
|
|
function redraw() { |
485
|
|
|
path = path.data(voronoi(vertices), jsondash.util.polygon); |
486
|
|
|
path.exit().remove(); |
487
|
|
|
path.enter().append('path') |
488
|
|
|
.attr('class', function(d, i) { return 'q' + (i % 9) + '-9'; }) |
489
|
|
|
.attr('d', jsondash.util.polygon); |
490
|
|
|
path.order(); |
491
|
|
|
} |
492
|
|
|
// Look for callbacks potentially registered for third party code. |
493
|
|
|
jsondash.api.runCallbacks(container, config); |
494
|
|
|
jsondash.unload(container); |
495
|
|
|
}); |
496
|
|
|
}; |
497
|
|
|
|
498
|
|
|
jsondash.handlers.handleSparkline = function(container, config) { |
499
|
|
|
'use strict'; |
500
|
|
|
// Clean up old canvas elements |
501
|
|
|
container.selectAll('.sparkline-container').remove(); |
502
|
|
|
var sparkline_type = config.type.split('-')[1]; |
503
|
|
|
var spark = container |
504
|
|
|
.append('div') |
505
|
|
|
.classed({ |
506
|
|
|
'sparkline-container': true, |
507
|
|
|
'text-center': true |
508
|
|
|
}); |
509
|
|
|
spark = $(spark[0]); |
510
|
|
|
jsondash.getJSON(container, config.dataSource, function(data){ |
511
|
|
|
var opts = { |
512
|
|
|
type: sparkline_type, |
513
|
|
|
width: config.width - jsondash.config.WIDGET_MARGIN_X, |
514
|
|
|
height: config.height - jsondash.config.WIDGET_MARGIN_Y |
515
|
|
|
}; |
516
|
|
|
spark.sparkline(data, opts); |
517
|
|
|
// Look for callbacks potentially registered for third party code. |
518
|
|
|
jsondash.api.runCallbacks(container, config); |
519
|
|
|
jsondash.unload(container); |
520
|
|
|
}); |
521
|
|
|
}; |
522
|
|
|
|
523
|
|
|
jsondash.handlers.handleDataTable = function(container, config) { |
524
|
|
|
'use strict'; |
525
|
|
|
// Clean up old tables if they exist, during reloading. |
526
|
|
|
container.selectAll('.dataTables_wrapper').remove(); |
527
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, res) { |
528
|
|
|
var keys = d3.keys(res[0]).map(function(d){ |
529
|
|
|
return {data: d, title: d}; |
530
|
|
|
}); |
531
|
|
|
container |
532
|
|
|
.append('table') |
533
|
|
|
.classed({ |
534
|
|
|
table: true, |
535
|
|
|
'table-striped': true, |
536
|
|
|
'table-bordered': true, |
537
|
|
|
'table-condensed': true |
538
|
|
|
}); |
539
|
|
|
var opts = config.override ? res : {data: res, columns: keys}; |
540
|
|
|
$(container.select('table')[0]).dataTable(opts).css({width: 'auto'}); |
541
|
|
|
// Look for callbacks potentially registered for third party code. |
542
|
|
|
jsondash.api.runCallbacks(container, config); |
543
|
|
|
jsondash.unload(container); |
544
|
|
|
}); |
545
|
|
|
}; |
546
|
|
|
|
547
|
|
|
jsondash.handlers.handleSingleNum = function(container, config) { |
548
|
|
|
'use strict'; |
549
|
|
|
container.selectAll('.singlenum').remove(); |
550
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, res){ |
551
|
|
|
var data = res.data.data ? res.data.data : res.data; |
552
|
|
|
var num = container.select('.chart-container').append('div') |
553
|
|
|
.classed({singlenum: true}) |
554
|
|
|
.text(data); |
555
|
|
|
data = String(data); |
556
|
|
|
// Add red or green, depending on if the number appears to be pos/neg. |
557
|
|
|
if(!res.noformat) { |
558
|
|
|
num.classed({ |
559
|
|
|
'text-danger': data.startsWith('-'), |
560
|
|
|
'text-success': !data.startsWith('-') |
561
|
|
|
}); |
562
|
|
|
} |
563
|
|
|
// Allow custom colors. |
564
|
|
|
if(res.color && res.noformat) { |
565
|
|
|
num.style('color', res.color); |
566
|
|
|
} |
567
|
|
|
// Get title height to offset box. |
568
|
|
|
var title_h = container |
|
|
|
|
569
|
|
|
.select('.widget-title') |
570
|
|
|
.node() |
571
|
|
|
.getBoundingClientRect() |
572
|
|
|
.height; |
573
|
|
|
var h = config.height - jsondash.config.WIDGET_MARGIN_Y; |
574
|
|
|
num.style({ |
575
|
|
|
'line-height': h + 'px', |
576
|
|
|
height: h + 'px', |
577
|
|
|
width: config.width - jsondash.config.WIDGET_MARGIN_X |
578
|
|
|
}); |
579
|
|
|
var digits = String(data).length; |
580
|
|
|
var size = jsondash.util.getDigitSize()(digits); |
581
|
|
|
num.style('font-size', size + 'px'); |
582
|
|
|
// Look for callbacks potentially registered for third party code. |
583
|
|
|
jsondash.api.runCallbacks(container, config); |
584
|
|
|
jsondash.unload(container); |
585
|
|
|
}); |
586
|
|
|
}; |
587
|
|
|
|
588
|
|
|
jsondash.handlers.handleTimeline = function(container, config) { |
589
|
|
|
'use strict'; |
590
|
|
|
jsondash.getJSON(container, config.dataSource, function(data){ |
591
|
|
|
container.append('div').attr('id', 'widget-' + config.guid); |
592
|
|
|
var timeline = new TL.Timeline('widget-' + config.guid, data); |
|
|
|
|
593
|
|
|
// Look for callbacks potentially registered for third party code. |
594
|
|
|
jsondash.api.runCallbacks(container, config); |
595
|
|
|
jsondash.unload(container); |
596
|
|
|
}); |
597
|
|
|
}; |
598
|
|
|
|
599
|
|
|
jsondash.handlers.handleIframe = function(container, config) { |
600
|
|
|
'use strict'; |
601
|
|
|
container.selectAll('iframe').remove(); |
602
|
|
|
var iframe = container.append('iframe'); |
603
|
|
|
iframe.attr({ |
604
|
|
|
border: 0, |
605
|
|
|
src: config.dataSource, |
606
|
|
|
height: config.height - jsondash.config.WIDGET_MARGIN_Y, |
607
|
|
|
width: isNaN(config.width) ? '100%' : config.width - jsondash.config.WIDGET_MARGIN_X |
608
|
|
|
}); |
609
|
|
|
// Look for callbacks potentially registered for third party code. |
610
|
|
|
jsondash.api.runCallbacks(container, config); |
611
|
|
|
jsondash.unload(container); |
612
|
|
|
}; |
613
|
|
|
|
614
|
|
|
jsondash.handlers.handleCustom = function(container, config) { |
615
|
|
|
'use strict'; |
616
|
|
|
container.selectAll('.custom-container').remove(); |
617
|
|
|
$.get(config.dataSource, function(html){ |
618
|
|
|
container.append('div').classed({'custom-container': true}).html(html); |
619
|
|
|
// Look for callbacks potentially registered for third party code. |
620
|
|
|
jsondash.api.runCallbacks(container, config); |
621
|
|
|
jsondash.unload(container); |
622
|
|
|
}); |
623
|
|
|
}; |
624
|
|
|
|
625
|
|
|
jsondash.handlers.handleVenn = function(container, config) { |
626
|
|
|
'use strict'; |
627
|
|
|
container.selectAll('.venn').remove(); |
628
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, data){ |
629
|
|
|
var chart = venn.VennDiagram(); |
630
|
|
|
var cont = container |
631
|
|
|
.append('div') |
632
|
|
|
.classed({venn: true}); |
633
|
|
|
cont.datum(data).call(chart); |
634
|
|
|
cont.select('svg') |
635
|
|
|
.attr('width', config.width - jsondash.config.WIDGET_MARGIN_X) |
636
|
|
|
.attr('height', config.height - jsondash.config.WIDGET_MARGIN_Y); |
637
|
|
|
// Look for callbacks potentially registered for third party code. |
638
|
|
|
jsondash.api.runCallbacks(container, config); |
639
|
|
|
jsondash.unload(container); |
640
|
|
|
}); |
641
|
|
|
}; |
642
|
|
|
|
643
|
|
|
jsondash.handlers.handlePlotly = function(container, config) { |
644
|
|
|
'use strict'; |
645
|
|
|
var id = 'plotly-' + config.guid; |
646
|
|
|
var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width; |
647
|
|
|
container.selectAll('.plotly-container').remove(); |
648
|
|
|
container.append('div') |
649
|
|
|
.classed({'plotly-container': true}) |
650
|
|
|
.attr('id', id); |
651
|
|
|
jsondash.getJSON(container, config.dataSource, function(error, data){ |
652
|
|
|
var plotly_wrapper = d3.select('#' + id); |
653
|
|
|
delete data.layout.height; |
654
|
|
|
delete data.layout.width; |
655
|
|
|
data.layout.margin = {l: 20, r: 20, b: 20, t: 50}; |
656
|
|
|
if(config.override) { |
657
|
|
|
Plotly.plot(id, data.data, data.layout || {}, data.options || {}); |
658
|
|
|
} else { |
659
|
|
|
Plotly.plot(id, data); |
660
|
|
|
} |
661
|
|
|
plotly_wrapper.select('.svg-container').style({ |
662
|
|
|
'margin': '0 auto', |
663
|
|
|
'width': isNaN(config.width) ? '100%' : config.width, |
664
|
|
|
'height': config.height |
665
|
|
|
}); |
666
|
|
|
plotly_wrapper.select('#scene').style({ |
667
|
|
|
'width': _width, |
668
|
|
|
'height': config.height |
669
|
|
|
}); |
670
|
|
|
// Look for callbacks potentially registered for third party code. |
671
|
|
|
jsondash.api.runCallbacks(container, config); |
672
|
|
|
jsondash.unload(container); |
673
|
|
|
}); |
674
|
|
|
}; |
675
|
|
|
|
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.
Consider:
If you or someone else later decides to put another statement in, only the first statement will be executed.
In this case the statement
b = 42
will always be executed, while the logging statement will be executed conditionally.ensures that the proper code will be executed conditionally no matter how many statements are added or removed.