Source: core/utils.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
import {timeDay, timeHour, timeMinute, timeMonth, timeSecond, timeWeek, timeYear} from 'd3-time';
import {format} from 'd3-format';

import {constants} from './constants';
import {config} from './config';

/**
 * Returns a function that given a string property name, can be used to pluck the property off an object.  A function
 * can be passed as the second argument to also alter the data being returned.
 *
 * This can be a useful shorthand method to create accessor functions.
 * @example
 * var xPluck = pluck('x');
 * var objA = {x: 1};
 * xPluck(objA) // 1
 * @example
 * var xPosition = pluck('x', function (x, i) {
 *     // `this` is the original datum,
 *     // `x` is the x property of the datum,
 *     // `i` is the position in the array
 *     return this.radius + x;
 * });
 * selectAll('.circle').data(...).x(xPosition);
 * @function pluck
 * @param {String} n
 * @param {Function} [f]
 * @returns {Function}
 */
export const pluck = function (n, f) {
    if (!f) {
        return function (d) { return d[n]; };
    }
    return function (d, i) { return f.call(d, d[n], i); };
};

/**
 * @namespace utils
 * @type {{}}
 */
export const utils = {};

/**
 * Print a single value filter.
 * @method printSingleValue
 * @memberof utils
 * @param {any} filter
 * @returns {String}
 */
utils.printSingleValue = function (filter) {
    let s = `${filter}`;

    if (filter instanceof Date) {
        s = config.dateFormat(filter);
    } else if (typeof (filter) === 'string') {
        s = filter;
    } else if (utils.isFloat(filter)) {
        s = utils.printSingleValue.fformat(filter);
    } else if (utils.isInteger(filter)) {
        s = Math.round(filter);
    }

    return s;
};
utils.printSingleValue.fformat = format('.2f');

// convert 'day' to d3.timeDay and similar
utils._toTimeFunc = function (t) {
    const mappings = {
        'second': timeSecond,
        'minute': timeMinute,
        'hour': timeHour,
        'day': timeDay,
        'week': timeWeek,
        'month': timeMonth,
        'year': timeYear
    };
    return mappings[t];
};

/**
 * Arbitrary add one value to another.
 *
 * If the value l is of type Date, adds r units to it. t becomes the unit.
 * For example utils.add(dt, 3, 'week') will add 3 (r = 3) weeks (t= 'week') to dt.
 *
 * If l is of type numeric, t is ignored. In this case if r is of type string,
 * it is assumed to be percentage (whether or not it includes %). For example
 * utils.add(30, 10) will give 40 and utils.add(30, '10') will give 33.
 *
 * They also generate strange results if l is a string.
 * @method add
 * @memberof utils
 * @param {Date|Number} l the value to modify
 * @param {String|Number} r the amount by which to modify the value
 * @param {Function|String} [t=d3.timeDay] if `l` is a `Date`, then this should be a
 * [d3 time interval](https://github.com/d3/d3-time/blob/master/README.md#_interval).
 * For backward compatibility with dc.js 2.0, it can also be the name of an interval, i.e.
 * 'millis', 'second', 'minute', 'hour', 'day', 'week', 'month', or 'year'
 * @returns {Date|Number}
 */
utils.add = function (l, r, t) {
    if (typeof r === 'string') {
        r = r.replace('%', '');
    }

    if (l instanceof Date) {
        if (typeof r === 'string') {
            r = +r;
        }
        if (t === 'millis') {
            return new Date(l.getTime() + r);
        }
        t = t || timeDay;
        if (typeof t !== 'function') {
            t = utils._toTimeFunc(t);
        }
        return t.offset(l, r);
    } else if (typeof r === 'string') {
        const percentage = (+r / 100);
        return l > 0 ? l * (1 + percentage) : l * (1 - percentage);
    } else {
        return l + r;
    }
};

/**
 * Arbitrary subtract one value from another.
 *
 * If the value l is of type Date, subtracts r units from it. t becomes the unit.
 * For example utils.subtract(dt, 3, 'week') will subtract 3 (r = 3) weeks (t= 'week') from dt.
 *
 * If l is of type numeric, t is ignored. In this case if r is of type string,
 * it is assumed to be percentage (whether or not it includes %). For example
 * utils.subtract(30, 10) will give 20 and utils.subtract(30, '10') will give 27.
 *
 * They also generate strange results if l is a string.
 * @method subtract
 * @memberof utils
 * @param {Date|Number} l the value to modify
 * @param {String|Number} r the amount by which to modify the value
 * @param {Function|String} [t=d3.timeDay] if `l` is a `Date`, then this should be a
 * [d3 time interval](https://github.com/d3/d3-time/blob/master/README.md#_interval).
 * For backward compatibility with dc.js 2.0, it can also be the name of an interval, i.e.
 * 'millis', 'second', 'minute', 'hour', 'day', 'week', 'month', or 'year'
 * @returns {Date|Number}
 */
utils.subtract = function (l, r, t) {
    if (typeof r === 'string') {
        r = r.replace('%', '');
    }

    if (l instanceof Date) {
        if (typeof r === 'string') {
            r = +r;
        }
        if (t === 'millis') {
            return new Date(l.getTime() - r);
        }
        t = t || timeDay;
        if (typeof t !== 'function') {
            t = utils._toTimeFunc(t);
        }
        return t.offset(l, -r);
    } else if (typeof r === 'string') {
        const percentage = (+r / 100);
        return l < 0 ? l * (1 + percentage) : l * (1 - percentage);
    } else {
        return l - r;
    }
};

/**
 * Is the value a number?
 * @method isNumber
 * @memberof utils
 * @param {any} n
 * @returns {Boolean}
 */
utils.isNumber = function (n) {
    return n === +n;
};

/**
 * Is the value a float?
 * @method isFloat
 * @memberof utils
 * @param {any} n
 * @returns {Boolean}
 */
utils.isFloat = function (n) {
    return n === +n && n !== (n | 0);
};

/**
 * Is the value an integer?
 * @method isInteger
 * @memberof utils
 * @param {any} n
 * @returns {Boolean}
 */
utils.isInteger = function (n) {
    return n === +n && n === (n | 0);
};

/**
 * Is the value very close to zero?
 * @method isNegligible
 * @memberof utils
 * @param {any} n
 * @returns {Boolean}
 */
utils.isNegligible = function (n) {
    return !utils.isNumber(n) || (n < constants.NEGLIGIBLE_NUMBER && n > -constants.NEGLIGIBLE_NUMBER);
};

/**
 * Ensure the value is no greater or less than the min/max values.  If it is return the boundary value.
 * @method clamp
 * @memberof utils
 * @param {any} val
 * @param {any} min
 * @param {any} max
 * @returns {any}
 */
utils.clamp = function (val, min, max) {
    return val < min ? min : (val > max ? max : val);
};

/**
 * Given `x`, return a function that always returns `x`.
 *
 * {@link https://github.com/d3/d3/blob/master/CHANGES.md#internals `d3.functor` was removed in d3 version 4}.
 * This function helps to implement the replacement,
 * `typeof x === "function" ? x : utils.constant(x)`
 * @method constant
 * @memberof utils
 * @param {any} x
 * @returns {Function}
 */
utils.constant = function (x) {
    return function () {
        return x;
    };
};

/**
 * Using a simple static counter, provide a unique integer id.
 * @method uniqueId
 * @memberof utils
 * @returns {Number}
 */
let _idCounter = 0;
utils.uniqueId = function () {
    return ++_idCounter;
};

/**
 * Convert a name to an ID.
 * @method nameToId
 * @memberof utils
 * @param {String} name
 * @returns {String}
 */
utils.nameToId = function (name) {
    return name.toLowerCase().replace(/[\s]/g, '_').replace(/[\.']/g, '');
};

/**
 * Append or select an item on a parent element.
 * @method appendOrSelect
 * @memberof utils
 * @param {d3.selection} parent
 * @param {String} selector
 * @param {String} tag
 * @returns {d3.selection}
 */
utils.appendOrSelect = function (parent, selector, tag) {
    tag = tag || selector;
    let element = parent.select(selector);
    if (element.empty()) {
        element = parent.append(tag);
    }
    return element;
};

/**
 * Return the number if the value is a number; else 0.
 * @method safeNumber
 * @memberof utils
 * @param {Number|any} n
 * @returns {Number}
 */
utils.safeNumber = function (n) { return utils.isNumber(+n) ? +n : 0;};

/**
 * Return true if both arrays are equal, if both array are null these are considered equal
 * @method arraysEqual
 * @memberof utils
 * @param {Array|null} a1
 * @param {Array|null} a2
 * @returns {Boolean}
 */
utils.arraysEqual = function (a1, a2) {
    if (!a1 && !a2) {
        return true;
    }

    if (!a1 || !a2) {
        return false;
    }

    return a1.length === a2.length &&
        // If elements are not integers/strings, we hope that it will match because of toString
        // Test cases cover dates as well.
        a1.every((elem, i) => elem.valueOf() === a2[i].valueOf());
};

// ******** Sunburst Chart ********
utils.allChildren = function (node) {
    let paths = [];
    paths.push(node.path);
    console.log('currentNode', node);
    if (node.children) {
        for (let i = 0; i < node.children.length; i++) {
            paths = paths.concat(utils.allChildren(node.children[i]));
        }
    }
    return paths;
};

// builds a d3 Hierarchy from a collection
// TODO: turn this monster method something better.
utils.toHierarchy = function (list, accessor) {
    const root = {'key': 'root', 'children': []};
    for (let i = 0; i < list.length; i++) {
        const data = list[i];
        const parts = data.key;
        const value = accessor(data);
        let currentNode = root;
        for (let j = 0; j < parts.length; j++) {
            const currentPath = parts.slice(0, j + 1);
            const children = currentNode.children;
            const nodeName = parts[j];
            let childNode;
            if (j + 1 < parts.length) {
                // Not yet at the end of the sequence; move down the tree.
                childNode = findChild(children, nodeName);

                // If we don't already have a child node for this branch, create it.
                if (childNode === void 0) {
                    childNode = {'key': nodeName, 'children': [], 'path': currentPath};
                    children.push(childNode);
                }
                currentNode = childNode;
            } else {
                // Reached the end of the sequence; create a leaf node.
                childNode = {'key': nodeName, 'value': value, 'data': data, 'path': currentPath};
                children.push(childNode);
            }
        }
    }
    return root;
};

function findChild (children, nodeName) {
    for (let k = 0; k < children.length; k++) {
        if (children[k].key === nodeName) {
            return children[k];
        }
    }
}

utils.getAncestors = function (node) {
    const path = [];
    let current = node;
    while (current.parent) {
        path.unshift(current.name);
        current = current.parent;
    }
    return path;
};

utils.arraysIdentical = function (a, b) {
    let i = a.length;
    if (i !== b.length) {
        return false;
    }
    while (i--) {
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
};