flask_jsondash/static/js/handlers.js   F
last analyzed

Complexity

Total Complexity 178
Complexity/F 1.71

Size

Lines of Code 918
Function Count 104

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 24
Bugs 3 Features 1
Metric Value
cc 0
dl 0
loc 918
rs 1.682
c 24
b 3
f 1
nc 0
wmc 178
mnd 2
bc 136
fnc 104
bpm 1.3076
cpm 1.7115
noi 38

29 Functions

Rating   Name   Duplication   Size   Complexity  
A jsondash.getDiameter 0 4 2
A jsondash.handlers.handleSigma 0 30 2
A jsondash.handlers.handleYoutube 0 35 2
B jsondash.handlers.handleWordCloud 0 49 1
A jsondash.handlers.handleVenn 0 18 1
B jsondash.handlers.handleTreemap 0 66 2
B jsondash.handlers.handleNumbersGroup 0 52 1
A jsondash.handlers.handlePlotly 0 34 2
A jsondash.handlers.handleSparkline 0 24 1
B jsondash.handlers.handleBasic 0 9 7
A jsondash.handlers.handleCustom 0 14 1
A jsondash.handlers.handleDataTable 0 23 1
B jsondash.handlers.handleDendrogram 0 50 2
A jsondash.handlers.handleImage 0 14 1
A jsondash.getTitleBarHeight 0 6 1
A jsondash.handlers.handleCytoscape 0 33 2
B jsondash.handlers.handleRadialDendrogram 0 45 2
A jsondash.handlers.handleVegaLite 0 33 1
B jsondash.handlers.handleD3 0 9 6
A jsondash.handlers.handleGraph 0 24 1
B jsondash.handlers.handleC3 0 78 2
A jsondash.getDynamicWidth 0 6 2
A jsondash.handlers.handleVoronoi 0 35 1
B jsondash.handlers.handleSingleNum 0 42 1
A jsondash.handlers.handleTimeline 0 13 1
B jsondash.handlers.handleCirclePack 0 40 1
A jsondash.handlers.handleFlameGraph 0 13 1
A jsondash.getJSON 0 18 5
A jsondash.handlers.handleIframe 0 16 2

How to fix   Complexity   

Complexity

Complex classes like flask_jsondash/static/js/handlers.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/** global: jsondash */
2
/** global: c3 */
3
/** global: d3 */
4
/** global: venn */
5
/** global: Plotly */
6
7
jsondash.handleRes = function(error, data, container) {
8
    var err_msg = '';
9
    if(error) {
10
        err_msg = 'Error: ' + error.status + ' ' + error.statusText;
11
    }
12
    else if(!data) {
13
        err_msg = 'No data was found (invalid response).';
14
    }
15
    if(error || !data) {
16
        container.classed({error: true});
17
        container.select('.error-overlay')
18
            .classed({hidden: false})
19
            .select('.alert')
20
            .text(err_msg);
21
        jsondash.unload(container);
22
    }
23
};
24
jsondash.getJSON = function(container, config, callback) {
25
    var url     = config.dataSource;
26
    var cached  = config.cachedData;
27
    var err_msg = null;
0 ignored issues
show
Unused Code introduced by
The variable err_msg seems to be never used. Consider removing it.
Loading history...
28
    if(!url) throw new Error('Invalid URL: ' + url);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

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 (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
29
    if(cached && cached !== null && cached !== undefined) {
30
        // Ensure this is not re-used for this cycle. It's somewhat of a pseudo-cache in that sense.
31
        config.cachedData = null;
32
        return callback(null, cached);
33
    }
34
    d3.json(url, function(error, data){
35
        jsondash.handleRes(error, data, container);
36
        if(error || !data) {
37
            return;
38
        }
39
        callback(error, config.key && data.multicharts[config.key] ? data.multicharts[config.key] : data);
40
    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
41
};
42
43
44
/**
45
 * [getTitleBarHeight Return the height for a chart containers titlebar,
46
 *     plus any other computed box model properties.
47
 */
48
jsondash.getTitleBarHeight = function(container) {
49
    var titlebar = container.select('.widget-title');
50
    var titlebar_height = titlebar.node().getBoundingClientRect().height;
51
    var titlebar_padding = parseInt(titlebar.style('padding-bottom').replace('px', ''), 10);
52
    return titlebar_height + titlebar_padding;
53
};
54
55
/**
56
 * [getDynamicWidth Return the width for a container that has no specified width
57
 * (e.g. grid mode)]
58
 */
59
jsondash.getDynamicWidth = function(container, config) {
60
    if(isNaN(config.width)) {
61
        return d3.round(container.node().getBoundingClientRect().width);
62
    }
63
    return parseInt(config.width, 10);
64
};
65
66
67
/**
68
 * [getDiameter Calculate a valid diameter for a circular widget,
69
 * based on width/height to ensure the size never goes out of the container bounds.]
70
 */
71
jsondash.getDiameter = function(container, config) {
72
    var width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
73
    return d3.min([d3.round(width), config.height]);
74
};
75
76
/**
77
 * Handler for all sigma.js specifications
78
 */
79
jsondash.handlers.handleSigma = function(container, config) {
80
    'use strict';
81
    var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
82
    // Titlebar + padding + a bit extra to account for the bottom.
83
    var titlebar_offset = jsondash.getTitleBarHeight(container) * 1.2;
84
    // Sigmajs just assumes an ID for the querySelector, so we need to add one
85
    // to the child container.
86
    var new_id = 'sigma-' + jsondash.util.guid();
87
    var width = (_width - 10) + 'px';
88
    var height = (config.height - titlebar_offset) + 'px';
89
    container
90
        .select('.chart-container')
91
        .attr('id', new_id)
92
        .classed(jsondash.util.getCSSClasses(config))
93
        .style({
94
        width: width,
95
        height: height
96
    });
97
    jsondash.getJSON(container, config, function(error, data){
98
        var sig = new sigma({
0 ignored issues
show
Bug introduced by
The variable sigma seems to be never declared. If this is a global, consider adding a /** global: sigma */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Unused Code introduced by
The variable sig seems to be never used. Consider removing it.
Loading history...
Coding Style Best Practice introduced by
By convention, constructors like sigma should be capitalized.
Loading history...
99
          graph: data,
100
          width: width,
101
          height: height,
102
          container: new_id
103
        });
104
        // Look for callbacks potentially registered for third party code.
105
        jsondash.api.runCallbacks(container, config);
106
        jsondash.unload(container);
107
    });
108
};
109
110
/**
111
 * [handleFlameGraph handler for flameGraph plugin]
112
 */
113
jsondash.handlers.handleFlameGraph = function(container, config) {
114
    jsondash.getJSON(container, config, function(_, data){
115
        var padding = 60;
116
        var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
117
        var flamegraph = d3.flameGraph()
118
            .width(_width - padding)
119
            .height(config.height - padding);
120
        container.datum(data).call(flamegraph);
121
        // Look for callbacks potentially registered for third party code.
122
        jsondash.api.runCallbacks(container, config);
123
        jsondash.unload(container);
124
    });
125
};
126
127
/**
128
 * Handler for all cytoscape specifications
129
 */
130
jsondash.handlers.handleCytoscape = function(container, config) {
131
    'use strict';
132
    var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
133
    // Titlebar + padding + a bit extra to account for the bottom.
134
    var titlebar_offset = jsondash.getTitleBarHeight(container) * 1.2;
135
    container
136
        .select('.chart-container')
137
        .classed(jsondash.util.getCSSClasses(config))
138
        .style({
139
        width: (_width - 10) + 'px',
140
        height: (config.height - titlebar_offset) + 'px'
141
    });
142
    jsondash.getJSON(container, config, function(error, cyspec){
143
        // the `document.getElementByID` declaration in the cytoscape
144
        // spec is not serializable so we will ignore anything user
145
        // sent and just drop our selector in place of it.
146
        var override = {
147
            container: document.querySelector('[data-guid="' + config.guid + '"] .chart-container'),
148
            // We intentionally override w/h with null values,
149
            // so the graph is forced to be
150
            // constrained to the parent dimensions.
151
            layout: {
152
                width: null,
153
                height: null
154
            },
155
        };
156
        var spec = $.extend(cyspec, override);
157
        var cy = cytoscape(spec);
0 ignored issues
show
Unused Code introduced by
The variable cy seems to be never used. Consider removing it.
Loading history...
158
        // Look for callbacks potentially registered for third party code.
159
        jsondash.api.runCallbacks(container, config);
160
        jsondash.unload(container);
161
    });
162
};
163
164
/**
165
 * Handler for all vega-lite specifications
166
 */
167
jsondash.handlers.handleVegaLite = function(container, config) {
168
    'use strict';
169
    jsondash.getJSON(container, config, function(error, vlspec){
170
        var SCALE_FACTOR = 0.7; // very important to get sizing jusst right.
171
        var selector = '[data-guid="' + config.guid + '"] .chart-container';
172
        var width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
173
        var size = d3.max([config.height, width]);
174
        var overrides = {
175
            width: ~~(size * SCALE_FACTOR),
176
            height: ~~(config.height * SCALE_FACTOR)
177
        };
178
        var embedSpec = {
179
            mode: 'vega-lite',
180
            spec: $.extend({}, vlspec, overrides)
181
        };
182
        d3.select(selector).classed(jsondash.util.getCSSClasses(config))
183
        vg.embed(selector, embedSpec, function(error, result) {
0 ignored issues
show
Unused Code introduced by
The parameter result is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Bug introduced by
The variable vg seems to be never declared. If this is a global, consider adding a /** global: vg */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
184
            // Callback receiving the View instance and parsed Vega spec
185
            // result.view is the View, which resides under the '#vis' element
186
            if(error) {
187
                throw new Error('Error loading chart: ' + error);
188
            }
189
            // Change look of default buttons
190
            container.select('.vega-actions')
191
                .classed({'btn-group': true})
192
                .selectAll('a')
193
                .classed({'btn btn-xs btn-default': true});
194
            // Look for callbacks potentially registered for third party code.
195
            jsondash.api.runCallbacks(container, config);
196
            jsondash.unload(container);
197
        });
198
    });
199
};
200
201
/**
202
 * Handlers for various widget types. The method signatures are always the same,
203
 * but each handler can handle them differently.
204
 */
205
jsondash.handlers.handleYoutube = function(container, config) {
206
    // Clean up all previous.
207
    'use strict';
208
    function getAttr(prop, props) {
209
        // Return the propery from a list of properties for the iframe.
210
        // e.g. getAttr('width', ["width="900""]) --> "900"
211
        return props.filter(function(k, v){
0 ignored issues
show
Unused Code introduced by
The parameter v is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
212
            return k.startsWith(prop);
213
        })[0];
214
    }
215
216
217
    var url = config.dataSource;
0 ignored issues
show
Unused Code introduced by
The assignment to variable url seems to be never used. Consider removing it.
Loading history...
218
    var parts = config.dataSource.split(' ');
219
    var yt_width = parseInt(getAttr('width', parts).split('=')[1].replace(/"/gi, ''), 10);
220
    var height = parseInt(getAttr('height', parts).split('=')[1].replace(/"/gi, ''), 10);
221
    var width = isNaN(config.width) ? '100%' : yt_width;
222
    var url = getAttr('src', parts).replace('src=', '').replace(/"/gi, '');
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable url already seems to be declared on line 217. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
223
224
    // In the case of YouTube, we have to override the config dimensions
225
    // as this will be wonky when the aspect ratio is calculated. We will
226
    // defer to YouTube calculations instead.
227
    container
228
        .select('.chart-container')
229
        .append('iframe')
230
        .classed(jsondash.util.getCSSClasses(config))
231
        .attr('width', width)
232
        .attr('height', height)
233
        .attr('src', url)
234
        .attr('allowfullscreen', true)
235
        .attr('frameborder', 0);
236
    // Look for callbacks potentially registered for third party code.
237
    jsondash.api.runCallbacks(container, config);
238
    jsondash.unload(container);
239
};
240
241
/**
242
 * [handleGraph creates graphs using the dot format
243
 * spec with d3 and dagre-d3]
244
 */
245
jsondash.handlers.handleGraph = function(container, config) {
246
    'use strict';
247
    jsondash.getJSON(container, config, function(error, data){
248
        var h = config.height - jsondash.config.WIDGET_MARGIN_Y;
0 ignored issues
show
Unused Code introduced by
The variable h seems to be never used. Consider removing it.
Loading history...
249
        var w = config.width - jsondash.config.WIDGET_MARGIN_X;
0 ignored issues
show
Unused Code introduced by
The variable w seems to be never used. Consider removing it.
Loading history...
250
        var svg = container
251
            .select('.chart-container')
252
            .append('svg')
253
            .classed(jsondash.util.getCSSClasses(config))
254
            .classed({'chart-graph': true});
255
        var svg_group = svg.append('g');
256
        var g = graphlibDot.read(data.graph);
0 ignored issues
show
Bug introduced by
The variable graphlibDot seems to be never declared. If this is a global, consider adding a /** global: graphlibDot */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
257
        var bbox = null;
0 ignored issues
show
Unused Code introduced by
The assignment to bbox seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
258
        // Create the renderer
259
        var render = new dagreD3.render();
0 ignored issues
show
Bug introduced by
The variable dagreD3 seems to be never declared. If this is a global, consider adding a /** global: dagreD3 */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
260
        render(svg_group, g);
261
        bbox = svg.node().getBBox();
262
        svg.attr('width', bbox.width)
263
            .attr('height', bbox.height);
264
        // Look for callbacks potentially registered for third party code.
265
        jsondash.api.runCallbacks(container, config);
266
        jsondash.unload(container);
267
    });
268
};
269
270
/**
271
 * [handleWordCloud create word clouds using the d3-cloud extension.]
272
 */
273
jsondash.handlers.handleWordCloud = function(container, config) {
274
    'use strict';
275
    jsondash.getJSON(container, config, function(error, data){
276
        var h     = config.height - jsondash.config.WIDGET_MARGIN_Y;
277
        var w     = config.width - jsondash.config.WIDGET_MARGIN_X;
278
        var svg   = container
279
            .select('.chart-container')
280
            .append('svg')
281
            .classed(jsondash.util.getCSSClasses(config))
282
            .classed({'wordcloud': true});
283
        var fill  = d3.scale.category20();
0 ignored issues
show
Unused Code introduced by
The variable fill seems to be never used. Consider removing it.
Loading history...
284
        var cloud = d3.layout.cloud;
285
        var words = data.map(function(d) {
286
            return {text: d.text, size: d.size};
287
        });
288
        var layout = cloud()
289
            .size([w, h])
290
            .words(words)
291
            .padding(4)
292
            .rotate(function() {return ~~(Math.random() * 1) * 90;})
293
            .font('Arial')
294
            .fontSize(function(d) {return d.size;})
295
            .on('end', draw);
296
297
        layout.start();
298
299
        function draw(words) {
300
          svg
301
              .attr('width', layout.size()[0])
302
              .attr('height', layout.size()[1])
303
            .append('g')
304
              .attr('transform', 'translate(' + layout.size()[0] / 2 + ',' + layout.size()[1] / 2 + ')')
305
            .selectAll('text').data(words)
306
            .enter().append('text')
307
              .style('font-size', function(d) { return d.size + 'px'; })
308
              .style('font-family', 'arial')
309
              .style('fill', function(d, i) { return "#000"; })
0 ignored issues
show
Unused Code introduced by
The parameter i is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter d is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
310
              .attr('text-anchor', 'middle')
311
              .attr('transform', function(d) {
312
                return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')';
313
              })
314
              .text(function(d) { return d.text; });
315
        }
316
317
        // Look for callbacks potentially registered for third party code.
318
        jsondash.api.runCallbacks(container, config);
319
        jsondash.unload(container);
320
    });
321
};
322
323
jsondash.handlers.handleC3 = function(container, config) {
324
    var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
325
    'use strict';
326
    var init_config = {
327
        bindto: '[data-guid="' + config.guid + '"] .chart-container',
328
        legend: {
329
            show: true
330
        },
331
        size: {
332
            height: config.height - jsondash.config.WIDGET_MARGIN_Y,
333
            width: _width - jsondash.config.WIDGET_MARGIN_X
334
        },
335
        data: {},
336
        onrendered: function(){
337
            // Look for callbacks potentially registered for third party code.
338
            jsondash.api.runCallbacks(container, config);
339
            jsondash.unload(container);
340
        }
341
    };
342
343
    container
344
        .select('.chart-container')
345
        .classed(jsondash.util.getCSSClasses(config))
346
347
    /**
348
     * [normalizeData Transform data from a standardized jsondash
349
     *     format into one suitable for c3.]
350
     */
351
    function normalizeData(data, type) {
352
        // For most cases, we build out N columns into ['label', 0, 1, 2, 3] format
353
        // from data in format: {'foo': [1, 2]} or format {'foo': 1}
354
        var cols = [];
355
        if(type === 'donut' || type === 'gauge' || type === 'pie') {
356
            $.each(data, function(label, val){
357
                cols.push([label, val]);
358
            });
359
            return cols;
360
        }
361
        if(type === 'timeseries') {
362
            var dates = ['x'];
363
            data.dates.map(function(date, _){
0 ignored issues
show
Unused Code introduced by
The parameter _ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
364
                dates.push(date);
365
            });
366
            cols.push(dates);
367
        }
368
        $.each(data, function(label, vals){
369
            if(label !== 'dates') {
370
                var newarr = [label];
371
                vals.map(function(val, _){
0 ignored issues
show
Unused Code introduced by
The parameter _ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
372
                    newarr.push(val);
373
                });
374
                cols.push(newarr);
375
            }
376
        });
377
        return cols;
378
    }
379
380
    jsondash.getJSON(container, config, function(error, data){
381
        if(jsondash.util.isOverride(config)) {
382
            // Just use the raw payload for this widgets' options.
383
            // Keep existing options if not specified.
384
            init_config = $.extend(init_config, data);
385
        } else {
386
            if(config.type === 'timeseries') {
387
                // Map the corresponding data key and list of dates
388
                // to the `x` property.
389
                init_config.axis = {
390
                    x: {type: 'timeseries'}
391
                };
392
                init_config.data.x = 'x';
393
            } else {
394
                init_config.data.type = config.type;
395
            }
396
            init_config.data.columns = normalizeData(data, config.type);
397
        }
398
        c3.generate(init_config);
399
    });
400
};
401
402
jsondash.handlers.handleBasic = function(container, config) {
403
    'use strict';
404
    if(config.type === 'numbergroup') { return jsondash.handlers.handleNumbersGroup(container, config); }
405
    if(config.type === 'number') { return jsondash.handlers.handleSingleNum(container, config); }
406
    if(config.type === 'iframe') { return jsondash.handlers.handleIframe(container, config); }
407
    if(config.type === 'image') { return jsondash.handlers.handleImage(container, config); }
408
    if(config.type === 'youtube') { return jsondash.handlers.handleYoutube(container, config); }
409
    if(config.type === 'custom') { return jsondash.handlers.handleCustom(container, config); }
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if config.type === "custom" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
410
};
411
412
jsondash.handlers.handleD3 = function(container, config) {
413
    'use strict';
414
    // Handle specific types.
415
    if(config.type === 'radial-dendrogram') { return jsondash.handlers.handleRadialDendrogram(container, config); }
416
    if(config.type === 'dendrogram') { return jsondash.handlers.handleDendrogram(container, config); }
417
    if(config.type === 'voronoi') { return jsondash.handlers.handleVoronoi(container, config); }
418
    if(config.type === 'treemap') { return jsondash.handlers.handleTreemap(container, config); }
419
    if(config.type === 'circlepack') { return jsondash.handlers.handleCirclePack(container, config); }
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if config.type === "circlepack" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
420
};
421
422
jsondash.handlers.handleCirclePack = function(container, config) {
423
    'use strict';
424
    // Adapted from https://bl.ocks.org/mbostock/4063530
425
    var margin = jsondash.config.WIDGET_MARGIN_Y;
426
    var diameter = jsondash.getDiameter(container, config) - margin;
427
    var format = d3.format(',d');
428
    var pack = d3.layout.pack()
429
        .size([diameter, diameter])
430
        .value(function(d) { return d.size; });
431
    var svg = container
432
        .select('.chart-container')
433
        .append('svg')
434
        .classed(jsondash.util.getCSSClasses(config))
435
        .attr('width', diameter)
436
        .attr('height', diameter)
437
        .append('g');
438
439
    jsondash.getJSON(container, config, function(error, data) {
440
        var node = svg.datum(data).selectAll('.node')
441
        .data(pack.nodes)
442
        .enter().append('g')
443
        .attr('class', function(d) { return d.children ? 'node' : 'leaf node'; })
444
        .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });
445
446
        node.append('title')
447
        .text(function(d) { return d.name + (d.children ? '' : ': ' + format(d.size)); });
448
449
        node.append('circle')
450
        .attr('r', function(d) { return d.r; });
451
452
        node.filter(function(d) { return !d.children; }).append('text')
453
        .attr('dy', '.3em')
454
        .style('text-anchor', 'middle')
455
        .text(function(d) { return d.name.substring(0, d.r / 3); });
456
        // Look for callbacks potentially registered for third party code.
457
        jsondash.api.runCallbacks(container, config);
458
        jsondash.unload(container);
459
    });
460
    d3.select(self.frameElement).style("height", diameter + "px");
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
461
};
462
463
jsondash.handlers.handleTreemap = function(container, config) {
464
    'use strict';
465
    // Adapted from http://bl.ocks.org/mbostock/4063582
466
    var margin = {
0 ignored issues
show
Unused Code introduced by
The variable margin seems to be never used. Consider removing it.
Loading history...
467
        top: jsondash.config.WIDGET_MARGIN_Y / 2,
468
        bottom: jsondash.config.WIDGET_MARGIN_Y / 2,
469
        left: jsondash.config.WIDGET_MARGIN_X / 2,
470
        right: jsondash.config.WIDGET_MARGIN_X / 2
471
    };
472
    var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
473
    var width = _width - jsondash.config.WIDGET_MARGIN_X;
474
    var height = config.height - jsondash.config.WIDGET_MARGIN_Y;
475
    var color = d3.scale.category20c();
476
    var treemap = d3.layout.treemap()
477
        .size([width, height])
478
        .sticky(true)
479
        .value(function(d) { return d.size; });
480
    var div = container
481
        .select('.chart-container')
482
        .append('div')
483
        .classed(jsondash.util.getCSSClasses(config))
484
        .classed({treemap: true, 'chart-centered': true})
485
        .style('position', 'relative')
486
        .style('width', width + 'px')
487
        .style('height', height + 'px');
488
489
    jsondash.getJSON(container, config, function(error, root) {
490
        var node = div.datum(root).selectAll('.node')
491
            .data(treemap.nodes)
492
            .enter().append('div')
493
            .attr('class', 'node')
494
            .call(position)
495
            .style('border', '1px solid white')
496
            .style('font', '10px sans-serif')
497
            .style('line-height', '12px')
498
            .style('overflow', 'hidden')
499
            .style('position', 'absolute')
500
            .style('text-indent', '2px')
501
            .style('background', function(d) {
502
                return d.children ? color(d.name) : null;
503
            })
504
            .text(function(d) {
505
                return d.children ? null : d.name;
506
            });
507
        d3.selectAll('input').on('change', function change() {
508
            var value = this.value === 'count'
509
            ? function() { return 1; }
510
            : function(d) { return d.size;};
511
            node
512
            .data(treemap.value(value).nodes)
513
            .transition()
514
            .duration(1500)
515
            .call(position);
516
        });
517
        // Look for callbacks potentially registered for third party code.
518
        jsondash.api.runCallbacks(container, config);
519
        jsondash.unload(container);
520
    });
521
522
    function position() {
523
        this.style('left', function(d) { return d.x + 'px'; })
524
            .style('top', function(d) { return d.y + 'px'; })
525
            .style('width', function(d) { return Math.max(0, d.dx - 1) + 'px'; })
526
            .style('height', function(d) { return Math.max(0, d.dy - 1) + 'px'; });
527
    }
528
};
529
530
jsondash.handlers.handleRadialDendrogram = function(container, config) {
531
    'use strict';
532
    // Code taken (and refactored for use here) from:
533
    // https://bl.ocks.org/mbostock/4339607
534
    var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
0 ignored issues
show
Unused Code introduced by
The variable _width seems to be never used. Consider removing it.
Loading history...
535
    var radius = jsondash.getDiameter(container, config);
536
    var cluster = d3.layout.cluster()
537
        .size([360, radius / 2 - 150]); // reduce size relative to `radius`
538
    var diagonal = d3.svg.diagonal.radial()
539
        .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
540
    var svg = container
541
        .select('.chart-container')
542
        .append('svg')
543
        .classed(jsondash.util.getCSSClasses(config))
544
        .attr('width', radius)
545
        .attr('height', radius);
546
    var g = svg.append('g');
547
    g.attr('transform', 'translate(' + radius / 2 + ',' + radius / 2 + ')');
548
549
    jsondash.getJSON(container, config, function(error, root) {
550
        if (error) { throw error; }
551
        var nodes = cluster.nodes(root);
552
        var link = g.selectAll('path.link')
0 ignored issues
show
Unused Code introduced by
The variable link seems to be never used. Consider removing it.
Loading history...
553
            .data(cluster.links(nodes))
554
            .enter().append('path')
555
            .attr('class', 'link')
556
            .attr('d', diagonal);
557
        var node = g.selectAll('g.node')
558
            .data(nodes)
559
            .enter().append('g')
560
            .attr('class', 'node')
561
            .attr('transform', function(d) { return 'rotate(' + (d.x - 90) + ')translate(' + d.y + ')'; });
562
        node.append('circle')
563
            .attr('r', 4.5);
564
        node.append('text')
565
            .attr('dy', '.31em')
566
            .attr('text-anchor', function(d) { return d.x < 180 ? 'start' : 'end'; })
567
            .attr('transform', function(d) { return d.x < 180 ? 'translate(8)' : 'rotate(180)translate(-8)'; })
568
            .text(function(d) { return d.name; });
569
        // Look for callbacks potentially registered for third party code.
570
        jsondash.api.runCallbacks(container, config);
571
        jsondash.unload(container);
572
    });
573
    d3.select(self.frameElement).style('height', radius * 2 + 'px');
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
574
};
575
576
jsondash.handlers.handleDendrogram = function(container, config) {
577
    'use strict';
578
    var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
579
    // A general padding for the svg inside of the widget.
580
    // The cluster dendrogram will also need to have padding itself, so
581
    // the bounds are not clipped in the svg.
582
    var svg_pad = 20;
583
    var width = _width - svg_pad;
584
    var height = config.height - svg_pad;
585
    var PADDING = width / 4;
586
    var cluster = d3.layout.cluster()
587
        .size([height * 0.85, width - PADDING]);
588
    var diagonal = d3.svg.diagonal()
589
        .projection(function(d) { return [d.y, d.x]; });
590
    var svg = container
591
        .select('.chart-container')
592
        .append('svg')
593
        .classed(jsondash.util.getCSSClasses(config))
594
        .attr('width', width)
595
        .attr('height', height);
596
    var g = svg.append('g')
597
        .attr('transform', 'translate(40, 0)');
598
599
    jsondash.getJSON(container, config, function(error, root) {
600
        var nodes = cluster.nodes(root);
601
        var links = cluster.links(nodes);
602
        var link = g.selectAll('.link')
0 ignored issues
show
Unused Code introduced by
The variable link seems to be never used. Consider removing it.
Loading history...
603
        .data(links)
604
        .enter().append('path')
605
        .attr('class', 'link')
606
        .attr('d', diagonal);
607
608
        var node = g.selectAll('.node')
609
        .data(nodes)
610
        .enter().append('g')
611
        .attr('class', 'node')
612
        .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });
613
614
        node.append('circle').attr('r', 4.5);
615
        node.append('text')
616
        .attr('dx', function(d) { return d.children ? -8 : 8; })
617
        .attr('dy', 3)
618
        .style('text-anchor', function(d) { return d.children ? 'end' : 'start'; })
619
        .text(function(d) { return d.name; });
620
621
        // Look for callbacks potentially registered for third party code.
622
        jsondash.api.runCallbacks(container, config);
623
        jsondash.unload(container);
624
    });
625
};
626
627
jsondash.handlers.handleVoronoi = function(container, config) {
628
    'use strict';
629
    jsondash.getJSON(container, config, function(error, data){
630
        var _width   = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
631
        var width    = _width - jsondash.config.WIDGET_MARGIN_X;
632
        var height   = config.height - jsondash.config.WIDGET_MARGIN_Y;
633
        var vertices = data;
634
        var voronoi  = d3.geom.voronoi().clipExtent([[0, 0], [width, height]]);
635
        var svg = container
636
            .select('.chart-container')
637
            .append('svg')
638
            .classed(jsondash.util.getCSSClasses(config))
639
            .attr('width', width)
640
            .attr('height', height);
641
        var path = svg.append('g').selectAll('path');
642
        svg.selectAll('circle')
643
            .data(vertices.slice(1))
644
            .enter().append('circle')
645
            .attr('transform', function(d) { return 'translate(' + d + ')'; })
646
            .attr('r', 1.5);
647
            redraw();
648
649
        function redraw() {
650
            path = path.data(voronoi(vertices), jsondash.util.polygon);
651
            path.exit().remove();
652
            path.enter().append('path')
653
            .attr('class', function(d, i) { return 'q' + (i % 9) + '-9'; })
654
            .attr('d', jsondash.util.polygon);
655
            path.order();
656
        }
657
        // Look for callbacks potentially registered for third party code.
658
        jsondash.api.runCallbacks(container, config);
659
        jsondash.unload(container);
660
    });
661
};
662
663
jsondash.handlers.handleSparkline = function(container, config) {
664
    'use strict';
665
    var sparkline_type = config.type.split('-')[1];
666
    var spark = container
667
        .select('.chart-container')
668
        .append('div')
669
        .classed(jsondash.util.getCSSClasses(config))
670
        .classed({
671
            'sparkline-container': true,
672
            'text-center': true
673
        });
674
    spark = $(spark[0]);
675
    jsondash.getJSON(container, config, function(data){
676
        var opts = {
677
            type: sparkline_type,
678
            width: config.width - jsondash.config.WIDGET_MARGIN_X,
679
            height: config.height - jsondash.config.WIDGET_MARGIN_Y
680
        };
681
        spark.sparkline(data, opts);
682
        // Look for callbacks potentially registered for third party code.
683
        jsondash.api.runCallbacks(container, config);
684
        jsondash.unload(container);
685
    });
686
};
687
688
jsondash.handlers.handleDataTable = function(container, config) {
689
    'use strict';
690
    jsondash.getJSON(container, config, function(error, res) {
691
        var keys = d3.keys(res[0]).map(function(d){
692
            return {data: d, title: d};
693
        });
694
        var titlebar_offset = jsondash.getTitleBarHeight(container) * 2.5;
0 ignored issues
show
Unused Code introduced by
The variable titlebar_offset seems to be never used. Consider removing it.
Loading history...
695
        var table_class_defaults = ['table', 'table-bordered', 'table-striped'];
696
        var classes = jsondash.util.getCSSClasses(config, table_class_defaults);
697
        container
698
            .select('.chart-container')
699
            .append('table')
700
            .classed(classes);
701
        var opts = config.override ? res : {data: res, columns: keys};
702
        $(container.select('table')[0])
703
            .dataTable(opts).css({
704
                width: 'auto',
705
        });
706
        // Look for callbacks potentially registered for third party code.
707
        jsondash.api.runCallbacks(container, config);
708
        jsondash.unload(container);
709
    });
710
};
711
712
jsondash.handlers.handleNumbersGroup = function(container, config) {
713
    'use strict';
714
    var scale = d3.scale.linear()
715
        .clamp(true)
716
        .domain([1, 20]) // min/max digits length
717
        .range([80, 30]); // max/min font-size
718
719
    function getStylesForColumn(d) {
720
        var digits = String(d.data).length + String(d.units ? d.units : '').length;
721
        var size = ~~scale(digits);
722
        var styles = 'font-size: ' + size + 'px;';
723
        if(d.color && d.noformat !== false) {
724
            styles += 'color: ' + d.color + ';';
725
        }
726
        return styles;
727
    }
728
729
    jsondash.getJSON(container, config, function(error, data){
730
        container
731
        .select('.chart-container')
732
        .append('table')
733
        .classed({numgroup: true, 'table': true})
734
        .classed(jsondash.util.getCSSClasses(config))
735
        .append('tr')
736
        .selectAll('td')
737
        .data(data)
738
        .enter()
739
        .append('td')
740
        .attr('width', function(d, i){
0 ignored issues
show
Unused Code introduced by
The parameter i is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
741
            return d.width !== null && d.width !== undefined ? d.width : null;
742
        })
743
        .attr('class', function(d, i){
0 ignored issues
show
Unused Code introduced by
The parameter i is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
744
            if(!d.noformat) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !d.noformat is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
745
                var classes = '';
746
                classes += typeof d === 'string' && d.startsWith('-') ? 'text-danger' : '';
747
                classes += typeof d === 'string' && !d.startsWith('-') ? 'text-success' : '';
748
                return classes;
749
            }
750
        })
751
        .html(function(d, i){
0 ignored issues
show
Unused Code introduced by
The parameter i is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
752
            var styles = getStylesForColumn(d);
753
            var title = '<p class="numgroup-title">' + d.title + '</p>';
754
            var units = d.units ? '<span class="numgroup-val-units"> ' + d.units + '</span>' : '';
755
            var num = '<h4 class="numgroup-val" style="' + styles + '">' + d.data + units + '</h4>';
756
            var desc = '<small class="numgroup-desc">' + (d.description ? d.description : '') + '</small>';
757
            return title + desc + num;
758
        });
759
        // Look for callbacks potentially registered for third party code.
760
        jsondash.api.runCallbacks(container, config);
761
        jsondash.unload(container);
762
    });
763
};
764
765
jsondash.handlers.handleSingleNum = function(container, config) {
766
    'use strict';
767
    jsondash.getJSON(container, config, function(error, res){
768
        var data = res.data.data ? res.data.data : res.data;
769
        var num = container
770
            .select('.chart-container')
771
            .append('div')
772
            .classed({singlenum: true})
773
            .classed(jsondash.util.getCSSClasses(config))
774
            .text(data);
775
        data = String(data);
776
        // Add red or green, depending on if the number appears to be pos/neg.
777
        if(!res.noformat) {
778
            num.classed({
779
                'text-danger': data.startsWith('-'),
780
                'text-success': !data.startsWith('-')
781
            });
782
        }
783
        // Allow custom colors.
784
        if(res.color && res.noformat) {
785
            num.style('color', res.color);
786
        }
787
        // Get title height to offset box.
788
        var title_h = container
0 ignored issues
show
Unused Code introduced by
The variable title_h seems to be never used. Consider removing it.
Loading history...
789
            .select('.widget-title')
790
            .node()
791
            .getBoundingClientRect()
792
            .height;
793
        var h = config.height - jsondash.config.WIDGET_MARGIN_Y;
794
        num.style({
795
            'line-height': h + 'px',
796
            height: h + 'px',
797
            width: config.width - jsondash.config.WIDGET_MARGIN_X
798
        });
799
        var digits = String(data).length;
800
        var size = jsondash.util.getDigitSize()(digits);
801
        num.style('font-size', size + 'px');
802
        // Look for callbacks potentially registered for third party code.
803
        jsondash.api.runCallbacks(container, config);
804
        jsondash.unload(container);
805
    });
806
};
807
808
jsondash.handlers.handleTimeline = function(container, config) {
809
    'use strict';
810
    jsondash.getJSON(container, config, function(data){
811
        container
812
            .append('div')
813
            .classed(jsondash.util.getCSSClasses(config))
814
            .attr('id', 'widget-' + config.guid);
815
        var timeline = new TL.Timeline('widget-' + config.guid, data);
0 ignored issues
show
Bug introduced by
The variable TL seems to be never declared. If this is a global, consider adding a /** global: TL */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Unused Code introduced by
The variable timeline seems to be never used. Consider removing it.
Loading history...
816
        // Look for callbacks potentially registered for third party code.
817
        jsondash.api.runCallbacks(container, config);
818
        jsondash.unload(container);
819
    });
820
};
821
822
/**
823
 * [handleImage Embed an image src directly.]
824
 */
825
jsondash.handlers.handleImage = function(container, config) {
826
    'use strict';
827
    var img = container.select('.chart-container').append('img');
828
    var height = (config.height - jsondash.config.WIDGET_MARGIN_Y) + 'px';
829
    img.attr({
830
        src: config.dataSource,
831
        height: height
832
    });
833
    img.classed({img: true});
834
    img.classed(jsondash.util.getCSSClasses(config));
835
    // Look for callbacks potentially registered for third party code.
836
    jsondash.api.runCallbacks(container, config);
837
    jsondash.unload(container);
838
};
839
840
jsondash.handlers.handleIframe = function(container, config) {
841
    'use strict';
842
    var iframe = container
843
        .select('.chart-container')
844
        .append('iframe');
845
    iframe.attr({
846
        border: 0,
847
        src: config.dataSource,
848
        height: config.height - jsondash.config.WIDGET_MARGIN_Y,
849
        width: isNaN(config.width) ? '100%' : config.width - jsondash.config.WIDGET_MARGIN_X
850
    });
851
    iframe.classed(jsondash.util.getCSSClasses(config));
852
    // Look for callbacks potentially registered for third party code.
853
    jsondash.api.runCallbacks(container, config);
854
    jsondash.unload(container);
855
};
856
857
jsondash.handlers.handleCustom = function(container, config) {
858
    'use strict';
859
    $.get(config.dataSource, function(html){
860
        container
861
            .select('.chart-container')
862
            .append('div')
863
            .classed({'custom-container': true})
864
            .classed(jsondash.util.getCSSClasses(config))
865
            .html(html);
866
        // Look for callbacks potentially registered for third party code.
867
        jsondash.api.runCallbacks(container, config);
868
        jsondash.unload(container);
869
    });
870
};
871
872
jsondash.handlers.handleVenn = function(container, config) {
873
    'use strict';
874
    jsondash.getJSON(container, config, function(error, data){
875
        var chart = venn.VennDiagram();
876
        var cont = container
877
            .select('.chart-container')
878
            .append('div')
879
            .classed(jsondash.util.getCSSClasses(config))
880
            .classed({venn: true});
881
        cont.datum(data).call(chart);
882
        cont.select('svg')
883
            .attr('width', config.width - jsondash.config.WIDGET_MARGIN_X)
884
            .attr('height', config.height - jsondash.config.WIDGET_MARGIN_Y);
885
        // Look for callbacks potentially registered for third party code.
886
        jsondash.api.runCallbacks(container, config);
887
        jsondash.unload(container);
888
    });
889
};
890
891
jsondash.handlers.handlePlotly = function(container, config) {
892
    'use strict';
893
    var id = 'plotly-' + config.guid;
894
    var _width = isNaN(config.width) ? jsondash.getDynamicWidth(container, config) : config.width;
895
    container
896
        .select('.chart-container')
897
        .append('div')
898
        .classed(jsondash.util.getCSSClasses(config))
899
        .classed({'plotly-container': true})
900
        .attr('id', id);
901
    jsondash.getJSON(container, config, function(error, data){
902
        var plotly_wrapper =  d3.select('#' + id);
903
        delete data.layout.height;
904
        delete data.layout.width;
905
        data.layout.margin = {l: 20, r: 20, b: 20, t: 50};
906
        if(config.override) {
907
            Plotly.plot(id, data.data, data.layout || {}, data.options || {});
908
        } else {
909
            Plotly.plot(id, data);
910
        }
911
       plotly_wrapper.select('.svg-container').style({
912
            'margin': '0 auto',
913
            'width': isNaN(config.width) ? '100%' : config.width,
914
            'height': config.height
915
        });
916
        plotly_wrapper.select('#scene').style({
917
            'width': _width,
918
            'height': config.height
919
        });
920
        // Look for callbacks potentially registered for third party code.
921
        jsondash.api.runCallbacks(container, config);
922
        jsondash.unload(container);
923
    });
924
};
925