UNPKG

react-d3-graph

Version:

React component to build interactive and configurable graphs with d3 effortlessly

242 lines (206 loc) 7.45 kB
/** * @module utils * @description * Offers a series of generic methods for object manipulation, and other operations * that are common across rd3g such as error logging. */ // This variable assures that recursive methods such as merge and isDeepEqual do not fall on // circular JSON structure evaluation. const MAX_DEPTH = 20; /** * Checks whether a certain object property is from object type and is a non empty object. * @param {Object} o - the object. * @param {string} k - the object property. * @returns {boolean} returns true if o[k] is an non empty object. * @memberof utils */ function _isPropertyNestedObject(o, k) { return ( !!o && Object.prototype.hasOwnProperty.call(o, k) && typeof o[k] === "object" && o[k] !== null && !isEmptyObject(o[k]) ); } /** * Generic deep comparison between javascript simple or complex objects. * @param {Object} o1 - one of the objects to be compared. * @param {Object} o2 - second object to compare with first. * @param {number} [_depth=0] - this parameter serves only for internal usage. * @returns {boolean} returns true if o1 and o2 have exactly the same content, or are exactly the same object reference. * @memberof utils */ function isDeepEqual(o1, o2, _depth = 0) { let diffs = []; if (_depth === 0 && o1 === o2) { return true; } if ((isEmptyObject(o1) && !isEmptyObject(o2)) || (!isEmptyObject(o1) && isEmptyObject(o2))) { return false; } const o1Keys = Object.keys(o1); const o2Keys = Object.keys(o2); if (o1Keys.length !== o2Keys.length) { return false; } for (let k of o1Keys) { const nestedO = _isPropertyNestedObject(o1, k) && _isPropertyNestedObject(o2, k); if (nestedO && _depth < MAX_DEPTH) { diffs.push(isDeepEqual(o1[k], o2[k], _depth + 1)); } else { const r = (isEmptyObject(o1[k]) && isEmptyObject(o2[k])) || (Object.prototype.hasOwnProperty.call(o2, k) && o2[k] === o1[k]); diffs.push(r); if (!r) { break; } } } return diffs.indexOf(false) === -1; } /** * Checks whether or not a certain object is empty. * NOTE: If the passed parameter is not an object the method return false. * @param {Object} o - object whom emptiness we want to check. * @returns {boolean} true if the given object is n ft and object and is empty. * @memberof utils */ function isEmptyObject(o) { return !!o && typeof o === "object" && !Object.keys(o).length; } /** * Function to deep clone plain javascript objects. * @param {Object} o - the object to clone. * @param {Object} _clone - carries the cloned output throughout the * recursive calls. Parameter serves only for internal usage. * @param {number} _depth - this parameter serves only for internal usage. * @returns {Object} - the cloned object. * @memberof utils */ function deepClone(o, _clone = {}, _depth = 0) { const oKeys = Object.keys(o); for (let k of oKeys) { const nested = _isPropertyNestedObject(o, k); _clone[k] = nested && _depth < MAX_DEPTH ? deepClone(o[k], {}, _depth + 1) : o[k]; } return _clone; } /** * This function merges two objects o1 and o2, where o2 properties override existent o1 properties, and * if o2 doesn't posses some o1 property the fallback will be the o1 property. * @param {Object} o1 - object. * @param {Object} o2 - object that will override o1 properties. * @param {int} [_depth=0] - the depth at which we are merging the object. * @returns {Object} object that is the result of merging o1 and o2, being o2 properties priority overriding * existent o1 properties. * @memberof utils */ function merge(o1 = {}, o2 = {}, _depth = 0) { let o = {}; if (Object.keys(o1 || {}).length === 0) { return o2 && !isEmptyObject(o2) ? o2 : {}; } for (let k of Object.keys(o1)) { const nestedO = !!(o2[k] && typeof o2[k] === "object" && typeof o1[k] === "object" && _depth < MAX_DEPTH); if (nestedO) { const r = merge(o1[k], o2[k], _depth + 1); o[k] = Object.prototype.hasOwnProperty.call(o1[k], "length") && Object.prototype.hasOwnProperty.call(o2[k], "length") ? Object.keys(r).map(rk => r[rk]) : r; } else { o[k] = Object.prototype.hasOwnProperty.call(o2, k) ? o2[k] : o1[k]; } } return o; } /** * Create new object from the inputted one only with the props passed * in the props list. * @param {Object} o - the object to pick props from. * @param {Array.<string>} props - list of props that we want to pick from o. * @returns {Object} the object resultant from the picking operation. * @memberof utils */ function pick(o, props = []) { return props.reduce((acc, k) => { if (Object.prototype.hasOwnProperty.call(o, k)) { acc[k] = o[k]; } return acc; }, {}); } /** * Picks all props except the ones passed in the props array. * @param {Object} o - the object to pick props from. * @param {Array.<string>} props - list of props that we DON'T want to pick from o. * @returns {Object} the object resultant from the anti picking operation. * @memberof utils */ function antiPick(o, props = []) { const wanted = Object.keys(o).filter(k => !props.includes(k)); return pick(o, wanted); } /** * Given a function, returns a function that will only be called after it stops * being called for `time` seconds. * @param {function} fn Function to debounce * @param {number} time Milliseconds to wait before invoking the function if it is called repeatedly * @returns {function} Version of function that will only be called every `time` milliseconds * @memberof utils */ function debounce(fn, time) { let timer; return function exec(...args) { const later = () => { clearTimeout(timer); fn(...args); }; timer && clearTimeout(timer); timer = setTimeout(later, time); }; } /** * Formats an error message with fallbacks for the given parameters. * @param {string} component component name. * @param {string} msg message to log. * @returns {string} the error message. * @memberof utils */ function buildFormattedErrorMessage(component = "N/A", msg = "N/A") { return `react-d3-graph :: ${component} :: ${msg}`; } /** * Helper function for customized error logging. * @param {string} component - the name of the component where the error is to be thrown. * @param {string} msg - the message contain a more detailed explanation about the error. * @returns {Error} the thrown error. * @memberof utils */ function throwErr(component, msg) { throw Error(buildFormattedErrorMessage(component, msg)); } /** * Logs formatted `react-d3-graph` error with `console.error`. * @param {string} component component name. * @param {string} msg message to log. * @returns {undefined} * @memberof utils */ function logError(component, msg) { console.error(buildFormattedErrorMessage(component, msg)); } /** * Helper function for customized warning logging. * @param {string} component - the name of the component where the warning is to be thrown. * @param {string} msg - the message contain a more detailed explanation about the error. * @returns {Warning} the thrown warning. * @memberof utils */ function logWarning(component, msg) { const warning = `react-d3-graph :: ${component} :: ${msg}`; console.warn(warning); } export { isDeepEqual, isEmptyObject, deepClone, merge, pick, antiPick, debounce, throwErr, logError, logWarning };