UNPKG

17.5 kBJavaScriptView Raw
1/*
2 d3plus-common v0.6.33
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
333 newObj.on[event] = wrapFunction(on[event]);
334
335 }
336
337 }
338
339 };
340
341 var keyEval = function (newObj, obj) {
342
343 for (var key in obj) {
344
345 if ({}.hasOwnProperty.call(obj, key)) {
346
347 if (key === "on") { parseEvents(newObj, obj[key]); }
348 else if (typeof obj[key] === "function") {
349 newObj[key] = wrapFunction(obj[key]);
350 }
351 else if (typeof obj[key] === "object" && !(obj instanceof Array)) {
352 newObj[key] = {on: {}};
353 keyEval(newObj[key], obj[key]);
354 }
355 else { newObj[key] = obj[key]; }
356
357 }
358
359 }
360
361 };
362
363 keyEval(newConfig, config);
364 if (this._on) { parseEvents(newConfig, this._on); }
365 if (nest && config[nest]) {
366 keyEval(newConfig, config[nest]);
367 if (config[nest].on) { parseEvents(newConfig, config[nest].on); }
368 }
369
370 return newConfig;
371
372}
373
374/**
375 @function constant
376 @desc Wraps non-function variables in a simple return function.
377 @param {Array|Number|Object|String} value The value to be returned from the function.
378 @example <caption>this</caption>
379constant(42);
380 @example <caption>returns this</caption>
381function() {
382 return 42;
383}
384*/
385function constant(value) {
386 return function constant() {
387 return value;
388 };
389}
390
391/**
392 @function elem
393 @desc Manages the enter/update/exit pattern for a single DOM element.
394 @param {String} selector A D3 selector, which must include the tagname and a class and/or ID.
395 @param {Object} params Additional parameters.
396 @param {Boolean} [params.condition = true] Whether or not the element should be rendered (or removed).
397 @param {Object} [params.enter = {}] A collection of key/value pairs that map to attributes to be given on enter.
398 @param {Object} [params.exit = {}] A collection of key/value pairs that map to attributes to be given on exit.
399 @param {D3Selection} [params.parent = d3.select("body")] The parent element for this new element to be appended to.
400 @param {D3Transition} [params.transition = d3.transition().duration(0)] The transition to use when animated the different life cycle stages.
401 @param {Object} [params.update = {}] A collection of key/value pairs that map to attributes to be given on update.
402*/
403function elem(selector, p) {
404
405 // overrides default params
406 p = Object.assign({}, {
407 condition: true,
408 enter: {},
409 exit: {},
410 parent: d3Selection.select("body"),
411 transition: d3Transition.transition().duration(0),
412 update: {}
413 }, p);
414
415 var className = (/\.([^#]+)/g).exec(selector),
416 id = (/#([^\.]+)/g).exec(selector),
417 tag = (/^([^.^#]+)/g).exec(selector)[1];
418
419 var elem = p.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector)
420 .data(p.condition ? [null] : []);
421
422 var enter = elem.enter().append(tag).call(attrize, p.enter);
423
424 if (id) { enter.attr("id", id[1]); }
425 if (className) { enter.attr("class", className[1]); }
426
427 elem.exit().transition(p.transition).call(attrize, p.exit).remove();
428
429 var update = enter.merge(elem);
430 update.transition(p.transition).call(attrize, p.update);
431
432 return update;
433
434}
435
436/**
437 @function merge
438 @desc Combines an Array of Objects together and returns a new Object.
439 @param {Array} objects The Array of objects to be merged together.
440 @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.
441 @example <caption>this</caption>
442merge([
443 {id: "foo", group: "A", value: 10, links: [1, 2]},
444 {id: "bar", group: "A", value: 20, links: [1, 3]}
445]);
446 @example <caption>returns this</caption>
447{id: ["bar", "foo"], group: "A", value: 30, links: [1, 2, 3]}
448*/
449function objectMerge(objects, aggs) {
450 if ( aggs === void 0 ) aggs = {};
451
452
453 var availableKeys = new Set(d3Array.merge(objects.map(function (o) { return d3Collection.keys(o); }))),
454 newObject = {};
455
456 availableKeys.forEach(function (k) {
457 var values = objects.map(function (o) { return o[k]; });
458 var value;
459 if (aggs[k]) { value = aggs[k](values); }
460 else {
461 var types = values.map(function (v) { return v || v === false ? v.constructor : v; }).filter(function (v) { return v !== void 0; });
462 if (!types.length) { value = undefined; }
463 else if (types.indexOf(Array) >= 0) {
464 value = d3Array.merge(values.map(function (v) { return v instanceof Array ? v : [v]; }));
465 value = Array.from(new Set(value));
466 if (value.length === 1) { value = value[0]; }
467 }
468 else if (types.indexOf(String) >= 0) {
469 value = Array.from(new Set(values));
470 if (value.length === 1) { value = value[0]; }
471 }
472 else if (types.indexOf(Number) >= 0) { value = d3Array.sum(values); }
473 else if (types.indexOf(Object) >= 0) { value = objectMerge(values.filter(function (v) { return v; })); }
474 else {
475 value = Array.from(new Set(values.filter(function (v) { return v !== void 0; })));
476 if (value.length === 1) { value = value[0]; }
477 }
478 }
479 newObject[k] = value;
480 });
481
482 return newObject;
483
484}
485
486/**
487 @function parseSides
488 @desc Converts a string of directional CSS shorthand values into an object with the values expanded.
489 @param {String|Number} sides The CSS shorthand string to expand.
490 */
491function parseSides(sides) {
492 var values;
493 if (typeof sides === "number") { values = [sides]; }
494 else { values = sides.split(/\s+/); }
495
496 if (values.length === 1) { values = [values[0], values[0], values[0], values[0]]; }
497 else if (values.length === 2) { values = values.concat(values); }
498 else if (values.length === 3) { values.push(values[1]); }
499
500 return [
501 "top",
502 "right",
503 "bottom",
504 "left"
505 ].reduce(function (acc, direction, i) {
506 var value = parseFloat(values[i]);
507 acc[direction] = value || 0;
508 return acc;
509 }, {});
510}
511
512/**
513 @function prefix
514 @desc Returns the appropriate CSS vendor prefix, given the current browser.
515*/
516function prefix() {
517 if ("-webkit-transform" in document.body.style) { return "-webkit-"; }
518 else if ("-moz-transform" in document.body.style) { return "-moz-"; }
519 else if ("-ms-transform" in document.body.style) { return "-ms-"; }
520 else if ("-o-transform" in document.body.style) { return "-o-"; }
521 else { return ""; }
522}
523
524/**
525 @function stylize
526 @desc Applies each key/value in an object as a style.
527 @param {D3selection} elem The D3 element to apply the styles to.
528 @param {Object} styles An object of key/value style pairs.
529*/
530function stylize(e, s) {
531 if ( s === void 0 ) s = {};
532
533 for (var k in s) { if ({}.hasOwnProperty.call(s, k)) { e.style(k, s[k]); } }
534}
535
536exports.accessor = accessor;
537exports.assign = assign;
538exports.attrize = attrize;
539exports.BaseClass = BaseClass;
540exports.closest = closest;
541exports.configPrep = configPrep;
542exports.constant = constant;
543exports.elem = elem;
544exports.isObject = isObject;
545exports.merge = objectMerge;
546exports.parseSides = parseSides;
547exports.prefix = prefix;
548exports.RESET = RESET;
549exports.stylize = stylize;
550exports.uuid = uuid;
551
552Object.defineProperty(exports, '__esModule', { value: true });
553
554})));
555//# sourceMappingURL=d3plus-common.js.map