UNPKG

18.4 kBJavaScriptView Raw
1/*
2 d3plus-common v0.6.37
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>
78 accessor("id");
79 @example <caption>returns this</caption>
80 function(d) {
81 return d["id"];
82 }
83 */
84 function 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 */
94 function 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>
103 assign({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 */
107 function 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 if (target.hasOwnProperty(prop) && isObject(target[prop])) { target[prop] = assign({}, target[prop], value); }
123 else { target[prop] = assign({}, value); }
124 }
125 else if (Array.isArray(value)) {
126
127 if (target.hasOwnProperty(prop) && Array.isArray(target[prop])) {
128
129 var targetArray = target[prop];
130
131 value.forEach(function (sourceItem, itemIndex) {
132
133 if (itemIndex < targetArray.length) {
134 var targetItem = targetArray[itemIndex];
135
136 if (Object.is(targetItem, sourceItem)) { return; }
137
138 if (isObject(targetItem) && isObject(sourceItem) || Array.isArray(targetItem) && Array.isArray(sourceItem)) {
139 targetArray[itemIndex] = assign({}, targetItem, sourceItem);
140 }
141 else { targetArray[itemIndex] = sourceItem; }
142
143 }
144 else { targetArray.push(sourceItem); }
145
146 });
147 }
148 else { target[prop] = value; }
149
150 }
151 else { target[prop] = value; }
152
153 });
154 };
155
156 for (var i = 1; i < objects.length; i++) loop( i );
157
158 return target;
159
160 }
161
162 /**
163 @function attrize
164 @desc Applies each key/value in an object as an attr.
165 @param {D3selection} elem The D3 element to apply the styles to.
166 @param {Object} attrs An object of key/value attr pairs.
167 */
168 function attrize(e, a) {
169 if ( a === void 0 ) a = {};
170
171 for (var k in a) { if ({}.hasOwnProperty.call(a, k)) { e.attr(k, a[k]); } }
172 }
173
174 /**
175 @function s
176 @desc Returns 4 random characters, used for constructing unique identifiers.
177 @private
178 */
179 function s() {
180 return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
181 }
182
183 /**
184 @function uuid
185 @summary Returns a unique identifier.
186 */
187 function uuid() {
188 return ("" + (s()) + (s()) + "-" + (s()) + "-" + (s()) + "-" + (s()) + "-" + (s()) + (s()) + (s()));
189 }
190
191 /**
192 @constant RESET
193 @desc String constant used to reset an individual config property.
194 */
195 var RESET = "D3PLUS-COMMON-RESET";
196
197 /**
198 @desc Recursive function that resets nested Object configs.
199 @param {Object} obj
200 @param {Object} defaults
201 @private
202 */
203 function nestedReset(obj, defaults) {
204 if (isObject(obj)) {
205 for (var nestedKey in obj) {
206 if ({}.hasOwnProperty.call(obj, nestedKey) && !nestedKey.startsWith("_")) {
207 var defaultValue = defaults && isObject(defaults) ? defaults[nestedKey] : undefined;
208 if (obj[nestedKey] === RESET) {
209 obj[nestedKey] = defaultValue;
210 }
211 else if (isObject(obj[nestedKey])) {
212 nestedReset(obj[nestedKey], defaultValue);
213 }
214 }
215 }
216 }
217 }
218
219 /**
220 @class BaseClass
221 @summary An abstract class that contains some global methods and functionality.
222 */
223 var BaseClass = function BaseClass() {
224 this._on = {};
225 this._uuid = uuid();
226 };
227
228 /**
229 @memberof BaseClass
230 @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.
231 @param {Object} [*value*]
232 @chainable
233 */
234 BaseClass.prototype.config = function config (_) {
235 var this$1 = this;
236
237 if (!this._configDefault) {
238 var config = {};
239 for (var k in this$1.__proto__) {
240 if (k.indexOf("_") !== 0 && !["config", "constructor", "render"].includes(k)) {
241 var v = this$1[k]();
242 config[k] = isObject(v) ? assign({}, v) : v;
243 }
244 }
245 this._configDefault = config;
246 }
247 if (arguments.length) {
248 for (var k$1 in _) {
249 if ({}.hasOwnProperty.call(_, k$1) && k$1 in this$1) {
250 var v$1 = _[k$1];
251 if (v$1 === RESET) {
252 if (k$1 === "on") { this$1._on = this$1._configDefault[k$1]; }
253 else { this$1[k$1](this$1._configDefault[k$1]); }
254 }
255 else {
256 nestedReset(v$1, this$1._configDefault[k$1]);
257 this$1[k$1](v$1);
258 }
259 }
260 }
261 return this;
262 }
263 else {
264 var config$1 = {};
265 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](); } }
266 return config$1;
267 }
268 };
269
270 /**
271 @memberof BaseClass
272 @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.
273 @param {String} [*typenames*]
274 @param {Function} [*listener*]
275 @chainable
276 @example <caption>By default, listeners apply globally to all objects, however, passing a namespace with the class name gives control over specific elements:</caption>
277 new Plot
278 .on("click.Shape", function(d) {
279 console.log("data for shape clicked:", d);
280 })
281 .on("click.Legend", function(d) {
282 console.log("data for legend clicked:", d);
283 })
284 */
285 BaseClass.prototype.on = function on (_, f) {
286 return arguments.length === 2 ? (this._on[_] = f, this) : arguments.length ? typeof _ === "string" ? this._on[_] : (this._on = Object.assign({}, this._on, _), this) : this._on;
287 };
288
289 /**
290 @function closest
291 @desc Finds the closest numeric value in an array.
292 @param {Number} n The number value to use when searching the array.
293 @param {Array} arr The array of values to test against.
294 */
295 function closest(n, arr) {
296 if ( arr === void 0 ) arr = [];
297
298 if (!arr || !(arr instanceof Array) || !arr.length) { return undefined; }
299 return arr.reduce(function (prev, curr) { return Math.abs(curr - n) < Math.abs(prev - n) ? curr : prev; });
300 }
301
302 /**
303 @function configPrep
304 @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.
305 @param {Object} [config = this._shapeConfig] The configuration object to parse.
306 @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".
307 @param {String} [nest] An optional nested key to bubble up to the parent config level.
308 */
309 function configPrep(config, type, nest) {
310 var this$1 = this;
311 if ( config === void 0 ) config = this._shapeConfig;
312 if ( type === void 0 ) type = "shape";
313 if ( nest === void 0 ) nest = false;
314
315
316 var newConfig = {duration: this._duration, on: {}};
317
318 var wrapFunction = function (func) { return function (d, i, s) {
319 while (d.__d3plus__) {
320 i = d.i;
321 d = d.data || d.feature;
322 }
323 return func.bind(this$1)(d, i, s);
324 }; };
325
326 var parseEvents = function (newObj, on) {
327
328 for (var event in on) {
329
330 if ({}.hasOwnProperty.call(on, event) && !event.includes(".") || event.includes(("." + type))) {
331 newObj.on[event] = wrapFunction(on[event]);
332 }
333
334 }
335
336 };
337
338 var keyEval = function (newObj, obj) {
339
340 for (var key in obj) {
341
342 if ({}.hasOwnProperty.call(obj, key)) {
343
344 if (key === "on") { parseEvents(newObj, obj[key]); }
345 else if (typeof obj[key] === "function") {
346 newObj[key] = wrapFunction(obj[key]);
347 }
348 else if (typeof obj[key] === "object" && !(obj instanceof Array)) {
349 newObj[key] = {on: {}};
350 keyEval(newObj[key], obj[key]);
351 }
352 else { newObj[key] = obj[key]; }
353
354 }
355
356 }
357
358 };
359
360 keyEval(newConfig, config);
361 if (this._on) { parseEvents(newConfig, this._on); }
362 if (nest && config[nest]) {
363 keyEval(newConfig, config[nest]);
364 if (config[nest].on) { parseEvents(newConfig, config[nest].on); }
365 }
366
367 return newConfig;
368
369 }
370
371 /**
372 @function constant
373 @desc Wraps non-function variables in a simple return function.
374 @param {Array|Number|Object|String} value The value to be returned from the function.
375 @example <caption>this</caption>
376 constant(42);
377 @example <caption>returns this</caption>
378 function() {
379 return 42;
380 }
381 */
382 function constant(value) {
383 return function constant() {
384 return value;
385 };
386 }
387
388 /**
389 @function elem
390 @desc Manages the enter/update/exit pattern for a single DOM element.
391 @param {String} selector A D3 selector, which must include the tagname and a class and/or ID.
392 @param {Object} params Additional parameters.
393 @param {Boolean} [params.condition = true] Whether or not the element should be rendered (or removed).
394 @param {Object} [params.enter = {}] A collection of key/value pairs that map to attributes to be given on enter.
395 @param {Object} [params.exit = {}] A collection of key/value pairs that map to attributes to be given on exit.
396 @param {D3Selection} [params.parent = d3.select("body")] The parent element for this new element to be appended to.
397 @param {D3Transition} [params.transition = d3.transition().duration(0)] The transition to use when animated the different life cycle stages.
398 @param {Object} [params.update = {}] A collection of key/value pairs that map to attributes to be given on update.
399 */
400 function elem(selector, p) {
401
402 // overrides default params
403 p = Object.assign({}, {
404 condition: true,
405 enter: {},
406 exit: {},
407 parent: d3Selection.select("body"),
408 transition: d3Transition.transition().duration(0),
409 update: {}
410 }, p);
411
412 var className = (/\.([^#]+)/g).exec(selector),
413 id = (/#([^\.]+)/g).exec(selector),
414 tag = (/^([^.^#]+)/g).exec(selector)[1];
415
416 var elem = p.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector)
417 .data(p.condition ? [null] : []);
418
419 var enter = elem.enter().append(tag).call(attrize, p.enter);
420
421 if (id) { enter.attr("id", id[1]); }
422 if (className) { enter.attr("class", className[1]); }
423
424 elem.exit().transition(p.transition).call(attrize, p.exit).remove();
425
426 var update = enter.merge(elem);
427 update.transition(p.transition).call(attrize, p.update);
428
429 return update;
430
431 }
432
433 /**
434 @function merge
435 @desc Combines an Array of Objects together and returns a new Object.
436 @param {Array} objects The Array of objects to be merged together.
437 @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.
438 @example <caption>this</caption>
439 merge([
440 {id: "foo", group: "A", value: 10, links: [1, 2]},
441 {id: "bar", group: "A", value: 20, links: [1, 3]}
442 ]);
443 @example <caption>returns this</caption>
444 {id: ["bar", "foo"], group: "A", value: 30, links: [1, 2, 3]}
445 */
446 function objectMerge(objects, aggs) {
447 if ( aggs === void 0 ) aggs = {};
448
449
450 var availableKeys = new Set(d3Array.merge(objects.map(function (o) { return d3Collection.keys(o); }))),
451 newObject = {};
452
453 availableKeys.forEach(function (k) {
454 var values = objects.map(function (o) { return o[k]; });
455 var value;
456 if (aggs[k]) { value = aggs[k](values); }
457 else {
458 var types = values.map(function (v) { return v || v === false ? v.constructor : v; }).filter(function (v) { return v !== void 0; });
459 if (!types.length) { value = undefined; }
460 else if (types.indexOf(Array) >= 0) {
461 value = d3Array.merge(values.map(function (v) { return v instanceof Array ? v : [v]; }));
462 value = Array.from(new Set(value));
463 if (value.length === 1) { value = value[0]; }
464 }
465 else if (types.indexOf(String) >= 0) {
466 value = Array.from(new Set(values));
467 if (value.length === 1) { value = value[0]; }
468 }
469 else if (types.indexOf(Number) >= 0) { value = d3Array.sum(values); }
470 else if (types.indexOf(Object) >= 0) { value = objectMerge(values.filter(function (v) { return v; })); }
471 else {
472 value = Array.from(new Set(values.filter(function (v) { return v !== void 0; })));
473 if (value.length === 1) { value = value[0]; }
474 }
475 }
476 newObject[k] = value;
477 });
478
479 return newObject;
480
481 }
482
483 /**
484 @function parseSides
485 @desc Converts a string of directional CSS shorthand values into an object with the values expanded.
486 @param {String|Number} sides The CSS shorthand string to expand.
487 */
488 function parseSides(sides) {
489 var values;
490 if (typeof sides === "number") { values = [sides]; }
491 else { values = sides.split(/\s+/); }
492
493 if (values.length === 1) { values = [values[0], values[0], values[0], values[0]]; }
494 else if (values.length === 2) { values = values.concat(values); }
495 else if (values.length === 3) { values.push(values[1]); }
496
497 return [
498 "top",
499 "right",
500 "bottom",
501 "left"
502 ].reduce(function (acc, direction, i) {
503 var value = parseFloat(values[i]);
504 acc[direction] = value || 0;
505 return acc;
506 }, {});
507 }
508
509 /**
510 @function prefix
511 @desc Returns the appropriate CSS vendor prefix, given the current browser.
512 */
513 function prefix() {
514 if ("-webkit-transform" in document.body.style) { return "-webkit-"; }
515 else if ("-moz-transform" in document.body.style) { return "-moz-"; }
516 else if ("-ms-transform" in document.body.style) { return "-ms-"; }
517 else if ("-o-transform" in document.body.style) { return "-o-"; }
518 else { return ""; }
519 }
520
521 /**
522 @function stylize
523 @desc Applies each key/value in an object as a style.
524 @param {D3selection} elem The D3 element to apply the styles to.
525 @param {Object} styles An object of key/value style pairs.
526 */
527 function stylize(e, s) {
528 if ( s === void 0 ) s = {};
529
530 for (var k in s) { if ({}.hasOwnProperty.call(s, k)) { e.style(k, s[k]); } }
531 }
532
533 exports.accessor = accessor;
534 exports.assign = assign;
535 exports.attrize = attrize;
536 exports.BaseClass = BaseClass;
537 exports.closest = closest;
538 exports.configPrep = configPrep;
539 exports.constant = constant;
540 exports.elem = elem;
541 exports.isObject = isObject;
542 exports.merge = objectMerge;
543 exports.parseSides = parseSides;
544 exports.prefix = prefix;
545 exports.RESET = RESET;
546 exports.stylize = stylize;
547 exports.uuid = uuid;
548
549 Object.defineProperty(exports, '__esModule', { value: true });
550
551})));
552//# sourceMappingURL=d3plus-common.js.map