UNPKG

17.6 kBJavaScriptView Raw
1/*
2 d3plus-common v0.6.34
3 Common functions and methods used across D3plus modules.
4 Copyright (c) 2018 D3plus - https://d3plus.org
5 @license MIT
6*/
7
8if (typeof Object.assign !== "function") {
9 Object.defineProperty(Object, "assign", {
10 value: function assign(target) {
11 "use strict";
12 if (target === null) {
13 throw new TypeError("Cannot convert undefined or null to object");
14 }
15
16 var to = Object(target);
17
18 for (var index = 1; index < arguments.length; index++) {
19 var nextSource = arguments[index];
20
21 if (nextSource !== null) {
22 for (var nextKey in nextSource) {
23 if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
24 to[nextKey] = nextSource[nextKey];
25 }
26 }
27 }
28 }
29 return to;
30 },
31 writable: true,
32 configurable: true
33 });
34}
35
36if (!Array.prototype.includes) {
37 Object.defineProperty(Array.prototype, "includes", {
38 value: function includes(searchElement, fromIndex) {
39
40 var o = Object(this);
41
42 var len = o.length >>> 0;
43
44 if (len === 0) return false;
45
46 var n = fromIndex | 0;
47
48 var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
49
50 function sameValueZero(x, y) {
51 return x === y || typeof x === "number" && typeof y === "number" && isNaN(x) && isNaN(y);
52 }
53
54 while (k < len) {
55 if (sameValueZero(o[k], searchElement)) {
56 return true;
57 }
58 k++;
59 }
60
61 return false;
62 }
63 });
64}
65
66(function (global, factory) {
67 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-transition'), require('d3-array'), require('d3-collection')) :
68 typeof define === 'function' && define.amd ? define('d3plus-common', ['exports', 'd3-selection', 'd3-transition', 'd3-array', 'd3-collection'], factory) :
69 (factory((global.d3plus = {}),global.d3Selection,global.d3Transition,global.d3Array,global.d3Collection));
70}(this, (function (exports,d3Selection,d3Transition,d3Array,d3Collection) { 'use strict';
71
72/**
73 @function accessor
74 @desc Wraps an object key in a simple accessor function.
75 @param {String} key The key to be returned from each Object passed to the function.
76 @param {*} [def] A default value to be returned if the key is not present.
77 @example <caption>this</caption>
78accessor("id");
79 @example <caption>returns this</caption>
80function(d) {
81 return d["id"];
82}
83*/
84function accessor(key, def) {
85 if (def === void 0) { return function (d) { return d[key]; }; }
86 return function (d) { return d[key] === void 0 ? def : d[key]; };
87}
88
89/**
90 @function isObject
91 @desc Detects if a variable is a javascript Object.
92 @param {*} item
93*/
94function isObject(item) {
95 return item && typeof item === "object" && !Array.isArray(item) && item !== void 0 ? true : false;
96}
97
98/**
99 @function assign
100 @desc A deeply recursive version of `Object.assign`.
101 @param {...Object} objects
102 @example <caption>this</caption>
103assign({id: "foo", deep: {group: "A"}}, {id: "bar", deep: {value: 20}}));
104 @example <caption>returns this</caption>
105{id: "bar", deep: {group: "A", value: 20}}
106*/
107function assign() {
108 var objects = [], len = arguments.length;
109 while ( len-- ) objects[ len ] = arguments[ len ];
110
111
112 var target = objects[0];
113 var loop = function ( i ) {
114
115 var source = objects[i];
116
117 Object.keys(source).forEach(function (prop) {
118
119 var value = source[prop];
120
121 if (isObject(value)) {
122
123 if (target.hasOwnProperty(prop) && isObject(target[prop])) { target[prop] = assign({}, target[prop], value); }
124 else { target[prop] = value; }
125
126 }
127 else if (Array.isArray(value)) {
128
129 if (target.hasOwnProperty(prop) && Array.isArray(target[prop])) {
130
131 var targetArray = target[prop];
132
133 value.forEach(function (sourceItem, itemIndex) {
134
135 if (itemIndex < targetArray.length) {
136 var targetItem = targetArray[itemIndex];
137
138 if (Object.is(targetItem, sourceItem)) { return; }
139
140 if (isObject(targetItem) && isObject(sourceItem) || Array.isArray(targetItem) && Array.isArray(sourceItem)) {
141 targetArray[itemIndex] = assign({}, targetItem, sourceItem);
142 }
143 else { targetArray[itemIndex] = sourceItem; }
144
145 }
146 else { targetArray.push(sourceItem); }
147
148 });
149 }
150 else { target[prop] = value; }
151
152 }
153 else { target[prop] = value; }
154
155 });
156 };
157
158 for (var i = 1; i < objects.length; i++) loop( i );
159
160 return target;
161
162}
163
164/**
165 @function attrize
166 @desc Applies each key/value in an object as an attr.
167 @param {D3selection} elem The D3 element to apply the styles to.
168 @param {Object} attrs An object of key/value attr pairs.
169*/
170function attrize(e, a) {
171 if ( a === void 0 ) a = {};
172
173 for (var k in a) { if ({}.hasOwnProperty.call(a, k)) { e.attr(k, a[k]); } }
174}
175
176/**
177 @function s
178 @desc Returns 4 random characters, used for constructing unique identifiers.
179 @private
180*/
181function s() {
182 return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
183}
184
185/**
186 @function uuid
187 @summary Returns a unique identifier.
188*/
189function uuid() {
190 return ("" + (s()) + (s()) + "-" + (s()) + "-" + (s()) + "-" + (s()) + "-" + (s()) + (s()) + (s()));
191}
192
193/**
194 @constant RESET
195 @desc String constant used to reset an individual config property.
196*/
197var RESET = "D3PLUS-COMMON-RESET";
198
199/**
200 @desc Recursive function that resets nested Object configs.
201 @param {Object} obj
202 @param {Object} defaults
203 @private
204*/
205function nestedReset(obj, defaults) {
206 if (isObject(obj)) {
207 for (var nestedKey in obj) {
208 if ({}.hasOwnProperty.call(obj, nestedKey) && !nestedKey.startsWith("_")) {
209 var defaultValue = defaults && isObject(defaults) ? defaults[nestedKey] : undefined;
210 if (obj[nestedKey] === RESET) {
211 obj[nestedKey] = defaultValue;
212 }
213 else if (isObject(obj[nestedKey])) {
214 nestedReset(obj[nestedKey], defaultValue);
215 }
216 }
217 }
218 }
219}
220
221/**
222 @class BaseClass
223 @summary An abstract class that contains some global methods and functionality.
224*/
225var BaseClass = function BaseClass() {
226 this._on = {};
227 this._uuid = uuid();
228};
229
230/**
231 @memberof BaseClass
232 @desc If *value* is specified, sets the methods that correspond to the key/value pairs and returns this class. If *value* is not specified, returns the current configuration.
233 @param {Object} [*value*]
234 @chainable
235*/
236BaseClass.prototype.config = function config (_) {
237 var this$1 = this;
238
239 if (!this._configDefault) {
240 var config = {};
241 for (var k in this$1.__proto__) {
242 if (k.indexOf("_") !== 0 && !["config", "constructor", "render"].includes(k)) {
243 var v = this$1[k]();
244 config[k] = isObject(v) ? assign({}, v) : v;
245 }
246 }
247 this._configDefault = config;
248 }
249 if (arguments.length) {
250 for (var k$1 in _) {
251 if ({}.hasOwnProperty.call(_, k$1) && k$1 in this$1) {
252 var v$1 = _[k$1];
253 if (v$1 === RESET) {
254 if (k$1 === "on") { this$1._on = this$1._configDefault[k$1]; }
255 else { this$1[k$1](this$1._configDefault[k$1]); }
256 }
257 else {
258 nestedReset(v$1, this$1._configDefault[k$1]);
259 this$1[k$1](v$1);
260 }
261 }
262 }
263 return this;
264 }
265 else {
266 var config$1 = {};
267 for (var k$2 in this$1.__proto__) { if (k$2.indexOf("_") !== 0 && !["config", "constructor", "render"].includes(k$2)) { config$1[k$2] = this$1[k$2](); } }
268 return config$1;
269 }
270};
271
272/**
273 @memberof BaseClass
274 @desc Adds or removes a *listener* to each object for the specified event *typenames*. If a *listener* is not specified, returns the currently assigned listener for the specified event *typename*. Mirrors the core [d3-selection](https://github.com/d3/d3-selection#selection_on) behavior.
275 @param {String} [*typenames*]
276 @param {Function} [*listener*]
277 @chainable
278 @example <caption>By default, listeners apply globally to all objects, however, passing a namespace with the class name gives control over specific elements:</caption>
279new Plot
280.on("click.Shape", function(d) {
281 console.log("data for shape clicked:", d);
282})
283.on("click.Legend", function(d) {
284 console.log("data for legend clicked:", d);
285})
286*/
287BaseClass.prototype.on = function on (_, f) {
288 return arguments.length === 2 ? (this._on[_] = f, this) : arguments.length ? typeof _ === "string" ? this._on[_] : (this._on = Object.assign({}, this._on, _), this) : this._on;
289};
290
291/**
292 @function closest
293 @desc Finds the closest numeric value in an array.
294 @param {Number} n The number value to use when searching the array.
295 @param {Array} arr The array of values to test against.
296*/
297function closest(n, arr) {
298 if ( arr === void 0 ) arr = [];
299
300 if (!arr || !(arr instanceof Array) || !arr.length) { return undefined; }
301 return arr.reduce(function (prev, curr) { return Math.abs(curr - n) < Math.abs(prev - n) ? curr : prev; });
302}
303
304/**
305 @function configPrep
306 @desc Preps a config object for d3plus data, and optionally bubbles up a specific nested type. When using this function, you must bind a d3plus class' `this` context.
307 @param {Object} [config = this._shapeConfig] The configuration object to parse.
308 @param {String} [type = "shape"] The event classifier to user for "on" events. For example, the default event type of "shape" will apply all events in the "on" config object with that key, like "click.shape" and "mouseleave.shape", in addition to any gloval events like "click" and "mouseleave".
309 @param {String} [nest] An optional nested key to bubble up to the parent config level.
310*/
311function configPrep(config, type, nest) {
312 if ( config === void 0 ) config = this._shapeConfig;
313 if ( type === void 0 ) type = "shape";
314 if ( nest === void 0 ) nest = false;
315
316
317 var newConfig = {duration: this._duration, on: {}};
318
319 var wrapFunction = function (func) { return function (d, i, s) {
320 while (d.__d3plus__) {
321 i = d.i;
322 d = d.data || d.feature;
323 }
324 return func(d, i, s);
325 }; };
326
327 var parseEvents = function (newObj, on) {
328
329 for (var event in on) {
330
331 if ({}.hasOwnProperty.call(on, event) && !event.includes(".") || event.includes(("." + type))) {
332 var eventName = event.replace("click", "click touchstart");
333 newObj.on[eventName] = wrapFunction(on[event]);
334 }
335
336 }
337
338 };
339
340 var keyEval = function (newObj, obj) {
341
342 for (var key in obj) {
343
344 if ({}.hasOwnProperty.call(obj, key)) {
345
346 if (key === "on") { parseEvents(newObj, obj[key]); }
347 else if (typeof obj[key] === "function") {
348 newObj[key] = wrapFunction(obj[key]);
349 }
350 else if (typeof obj[key] === "object" && !(obj instanceof Array)) {
351 newObj[key] = {on: {}};
352 keyEval(newObj[key], obj[key]);
353 }
354 else { newObj[key] = obj[key]; }
355
356 }
357
358 }
359
360 };
361
362 keyEval(newConfig, config);
363 if (this._on) { parseEvents(newConfig, this._on); }
364 if (nest && config[nest]) {
365 keyEval(newConfig, config[nest]);
366 if (config[nest].on) { parseEvents(newConfig, config[nest].on); }
367 }
368
369 return newConfig;
370
371}
372
373/**
374 @function constant
375 @desc Wraps non-function variables in a simple return function.
376 @param {Array|Number|Object|String} value The value to be returned from the function.
377 @example <caption>this</caption>
378constant(42);
379 @example <caption>returns this</caption>
380function() {
381 return 42;
382}
383*/
384function constant(value) {
385 return function constant() {
386 return value;
387 };
388}
389
390/**
391 @function elem
392 @desc Manages the enter/update/exit pattern for a single DOM element.
393 @param {String} selector A D3 selector, which must include the tagname and a class and/or ID.
394 @param {Object} params Additional parameters.
395 @param {Boolean} [params.condition = true] Whether or not the element should be rendered (or removed).
396 @param {Object} [params.enter = {}] A collection of key/value pairs that map to attributes to be given on enter.
397 @param {Object} [params.exit = {}] A collection of key/value pairs that map to attributes to be given on exit.
398 @param {D3Selection} [params.parent = d3.select("body")] The parent element for this new element to be appended to.
399 @param {D3Transition} [params.transition = d3.transition().duration(0)] The transition to use when animated the different life cycle stages.
400 @param {Object} [params.update = {}] A collection of key/value pairs that map to attributes to be given on update.
401*/
402function elem(selector, p) {
403
404 // overrides default params
405 p = Object.assign({}, {
406 condition: true,
407 enter: {},
408 exit: {},
409 parent: d3Selection.select("body"),
410 transition: d3Transition.transition().duration(0),
411 update: {}
412 }, p);
413
414 var className = (/\.([^#]+)/g).exec(selector),
415 id = (/#([^\.]+)/g).exec(selector),
416 tag = (/^([^.^#]+)/g).exec(selector)[1];
417
418 var elem = p.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector)
419 .data(p.condition ? [null] : []);
420
421 var enter = elem.enter().append(tag).call(attrize, p.enter);
422
423 if (id) { enter.attr("id", id[1]); }
424 if (className) { enter.attr("class", className[1]); }
425
426 elem.exit().transition(p.transition).call(attrize, p.exit).remove();
427
428 var update = enter.merge(elem);
429 update.transition(p.transition).call(attrize, p.update);
430
431 return update;
432
433}
434
435/**
436 @function merge
437 @desc Combines an Array of Objects together and returns a new Object.
438 @param {Array} objects The Array of objects to be merged together.
439 @param {Object} aggs An object containing specific aggregation methods (functions) for each key type. By default, numbers are summed and strings are returned as an array of unique values.
440 @example <caption>this</caption>
441merge([
442 {id: "foo", group: "A", value: 10, links: [1, 2]},
443 {id: "bar", group: "A", value: 20, links: [1, 3]}
444]);
445 @example <caption>returns this</caption>
446{id: ["bar", "foo"], group: "A", value: 30, links: [1, 2, 3]}
447*/
448function objectMerge(objects, aggs) {
449 if ( aggs === void 0 ) aggs = {};
450
451
452 var availableKeys = new Set(d3Array.merge(objects.map(function (o) { return d3Collection.keys(o); }))),
453 newObject = {};
454
455 availableKeys.forEach(function (k) {
456 var values = objects.map(function (o) { return o[k]; });
457 var value;
458 if (aggs[k]) { value = aggs[k](values); }
459 else {
460 var types = values.map(function (v) { return v || v === false ? v.constructor : v; }).filter(function (v) { return v !== void 0; });
461 if (!types.length) { value = undefined; }
462 else if (types.indexOf(Array) >= 0) {
463 value = d3Array.merge(values.map(function (v) { return v instanceof Array ? v : [v]; }));
464 value = Array.from(new Set(value));
465 if (value.length === 1) { value = value[0]; }
466 }
467 else if (types.indexOf(String) >= 0) {
468 value = Array.from(new Set(values));
469 if (value.length === 1) { value = value[0]; }
470 }
471 else if (types.indexOf(Number) >= 0) { value = d3Array.sum(values); }
472 else if (types.indexOf(Object) >= 0) { value = objectMerge(values.filter(function (v) { return v; })); }
473 else {
474 value = Array.from(new Set(values.filter(function (v) { return v !== void 0; })));
475 if (value.length === 1) { value = value[0]; }
476 }
477 }
478 newObject[k] = value;
479 });
480
481 return newObject;
482
483}
484
485/**
486 @function parseSides
487 @desc Converts a string of directional CSS shorthand values into an object with the values expanded.
488 @param {String|Number} sides The CSS shorthand string to expand.
489 */
490function parseSides(sides) {
491 var values;
492 if (typeof sides === "number") { values = [sides]; }
493 else { values = sides.split(/\s+/); }
494
495 if (values.length === 1) { values = [values[0], values[0], values[0], values[0]]; }
496 else if (values.length === 2) { values = values.concat(values); }
497 else if (values.length === 3) { values.push(values[1]); }
498
499 return [
500 "top",
501 "right",
502 "bottom",
503 "left"
504 ].reduce(function (acc, direction, i) {
505 var value = parseFloat(values[i]);
506 acc[direction] = value || 0;
507 return acc;
508 }, {});
509}
510
511/**
512 @function prefix
513 @desc Returns the appropriate CSS vendor prefix, given the current browser.
514*/
515function prefix() {
516 if ("-webkit-transform" in document.body.style) { return "-webkit-"; }
517 else if ("-moz-transform" in document.body.style) { return "-moz-"; }
518 else if ("-ms-transform" in document.body.style) { return "-ms-"; }
519 else if ("-o-transform" in document.body.style) { return "-o-"; }
520 else { return ""; }
521}
522
523/**
524 @function stylize
525 @desc Applies each key/value in an object as a style.
526 @param {D3selection} elem The D3 element to apply the styles to.
527 @param {Object} styles An object of key/value style pairs.
528*/
529function stylize(e, s) {
530 if ( s === void 0 ) s = {};
531
532 for (var k in s) { if ({}.hasOwnProperty.call(s, k)) { e.style(k, s[k]); } }
533}
534
535exports.accessor = accessor;
536exports.assign = assign;
537exports.attrize = attrize;
538exports.BaseClass = BaseClass;
539exports.closest = closest;
540exports.configPrep = configPrep;
541exports.constant = constant;
542exports.elem = elem;
543exports.isObject = isObject;
544exports.merge = objectMerge;
545exports.parseSides = parseSides;
546exports.prefix = prefix;
547exports.RESET = RESET;
548exports.stylize = stylize;
549exports.uuid = uuid;
550
551Object.defineProperty(exports, '__esModule', { value: true });
552
553})));
554//# sourceMappingURL=d3plus-common.js.map