vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1398 lines
36 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. // utility functions
  2. // first check if moment.js is already loaded in the browser window, if so,
  3. // use this instance. Else, load via commonjs.
  4. var moment = require('./module/moment');
  5. var uuid = require('./module/uuid');
  6. /**
  7. * Test whether given object is a number
  8. * @param {*} object
  9. * @return {Boolean} isNumber
  10. */
  11. exports.isNumber = function(object) {
  12. return (object instanceof Number || typeof object == 'number');
  13. };
  14. exports.recursiveDOMDelete = function(DOMobject) {
  15. while (DOMobject.hasChildNodes() == true) {
  16. exports.recursiveDOMDelete(DOMobject.firstChild);
  17. DOMobject.removeChild(DOMobject.firstChild);
  18. }
  19. };
  20. /**
  21. * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value.
  22. *
  23. * @param min
  24. * @param max
  25. * @param total
  26. * @param value
  27. * @returns {number}
  28. */
  29. exports.giveRange = function(min,max,total,value) {
  30. if (max == min) {
  31. return 0.5;
  32. }
  33. else {
  34. var scale = 1 / (max - min);
  35. return Math.max(0,(value - min)*scale);
  36. }
  37. }
  38. /**
  39. * Test whether given object is a string
  40. * @param {*} object
  41. * @return {Boolean} isString
  42. */
  43. exports.isString = function(object) {
  44. return (object instanceof String || typeof object == 'string');
  45. };
  46. /**
  47. * Test whether given object is a Date, or a String containing a Date
  48. * @param {Date | String} object
  49. * @return {Boolean} isDate
  50. */
  51. exports.isDate = function(object) {
  52. if (object instanceof Date) {
  53. return true;
  54. }
  55. else if (exports.isString(object)) {
  56. // test whether this string contains a date
  57. var match = ASPDateRegex.exec(object);
  58. if (match) {
  59. return true;
  60. }
  61. else if (!isNaN(Date.parse(object))) {
  62. return true;
  63. }
  64. }
  65. return false;
  66. };
  67. /**
  68. * Test whether given object is an instance of google.visualization.DataTable
  69. * @param {*} object
  70. * @return {Boolean} isDataTable
  71. */
  72. exports.isDataTable = function(object) {
  73. return (typeof (google) !== 'undefined') &&
  74. (google.visualization) &&
  75. (google.visualization.DataTable) &&
  76. (object instanceof google.visualization.DataTable);
  77. };
  78. /**
  79. * Create a semi UUID
  80. * source: http://stackoverflow.com/a/105074/1262753
  81. * @return {String} uuid
  82. */
  83. exports.randomUUID = function() {
  84. return uuid.v4();
  85. };
  86. /**
  87. * assign all keys of an object that are not nested objects to a certain value (used for color objects).
  88. * @param obj
  89. * @param value
  90. */
  91. exports.assignAllKeys = function (obj, value) {
  92. for (var prop in obj) {
  93. if (obj.hasOwnProperty(prop)) {
  94. if (typeof obj[prop] !== 'object') {
  95. obj[prop] = value;
  96. }
  97. }
  98. }
  99. }
  100. /**
  101. * Fill an object with a possibly partially defined other object. Only copies values.
  102. * @param obj
  103. * @param value
  104. */
  105. exports.fillIfDefined = function (a, b, allowDeletion = false) {
  106. for (var prop in a) {
  107. if (b[prop] !== undefined) {
  108. if (typeof b[prop] !== 'object') {
  109. if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  110. delete a[prop];
  111. }
  112. else {
  113. a[prop] = b[prop];
  114. }
  115. }
  116. }
  117. }
  118. }
  119. /**
  120. * Extend object a with the properties of object b or a series of objects
  121. * Only properties with defined values are copied
  122. * @param {Object} a
  123. * @param {... Object} b
  124. * @return {Object} a
  125. */
  126. exports.protoExtend = function (a, b) {
  127. for (var i = 1; i < arguments.length; i++) {
  128. var other = arguments[i];
  129. for (var prop in other) {
  130. a[prop] = other[prop];
  131. }
  132. }
  133. return a;
  134. };
  135. /**
  136. * Extend object a with the properties of object b or a series of objects
  137. * Only properties with defined values are copied
  138. * @param {Object} a
  139. * @param {... Object} b
  140. * @return {Object} a
  141. */
  142. exports.extend = function (a, b) {
  143. for (var i = 1; i < arguments.length; i++) {
  144. var other = arguments[i];
  145. for (var prop in other) {
  146. if (other.hasOwnProperty(prop)) {
  147. a[prop] = other[prop];
  148. }
  149. }
  150. }
  151. return a;
  152. };
  153. /**
  154. * Extend object a with selected properties of object b or a series of objects
  155. * Only properties with defined values are copied
  156. * @param {Array.<String>} props
  157. * @param {Object} a
  158. * @param {Object} b
  159. * @return {Object} a
  160. */
  161. exports.selectiveExtend = function (props, a, b) {
  162. if (!Array.isArray(props)) {
  163. throw new Error('Array with property names expected as first argument');
  164. }
  165. for (var i = 2; i < arguments.length; i++) {
  166. var other = arguments[i];
  167. for (var p = 0; p < props.length; p++) {
  168. var prop = props[p];
  169. if (other.hasOwnProperty(prop)) {
  170. a[prop] = other[prop];
  171. }
  172. }
  173. }
  174. return a;
  175. };
  176. /**
  177. * Extend object a with selected properties of object b or a series of objects
  178. * Only properties with defined values are copied
  179. * @param {Array.<String>} props
  180. * @param {Object} a
  181. * @param {Object} b
  182. * @return {Object} a
  183. */
  184. exports.selectiveDeepExtend = function (props, a, b, allowDeletion = false) {
  185. // TODO: add support for Arrays to deepExtend
  186. if (Array.isArray(b)) {
  187. throw new TypeError('Arrays are not supported by deepExtend');
  188. }
  189. for (var i = 2; i < arguments.length; i++) {
  190. var other = arguments[i];
  191. for (var p = 0; p < props.length; p++) {
  192. var prop = props[p];
  193. if (other.hasOwnProperty(prop)) {
  194. if (b[prop] && b[prop].constructor === Object) {
  195. if (a[prop] === undefined) {
  196. a[prop] = {};
  197. }
  198. if (a[prop].constructor === Object) {
  199. exports.deepExtend(a[prop], b[prop], false, allowDeletion);
  200. }
  201. else {
  202. if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  203. delete a[prop];
  204. }
  205. else {
  206. a[prop] = b[prop];
  207. }
  208. }
  209. } else if (Array.isArray(b[prop])) {
  210. throw new TypeError('Arrays are not supported by deepExtend');
  211. } else {
  212. a[prop] = b[prop];
  213. }
  214. }
  215. }
  216. }
  217. return a;
  218. };
  219. /**
  220. * Extend object a with selected properties of object b or a series of objects
  221. * Only properties with defined values are copied
  222. * @param {Array.<String>} props
  223. * @param {Object} a
  224. * @param {Object} b
  225. * @return {Object} a
  226. */
  227. exports.selectiveNotDeepExtend = function (props, a, b, allowDeletion = false) {
  228. // TODO: add support for Arrays to deepExtend
  229. if (Array.isArray(b)) {
  230. throw new TypeError('Arrays are not supported by deepExtend');
  231. }
  232. for (var prop in b) {
  233. if (b.hasOwnProperty(prop)) {
  234. if (props.indexOf(prop) == -1) {
  235. if (b[prop] && b[prop].constructor === Object) {
  236. if (a[prop] === undefined) {
  237. a[prop] = {};
  238. }
  239. if (a[prop].constructor === Object) {
  240. exports.deepExtend(a[prop], b[prop]);
  241. }
  242. else {
  243. if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  244. delete a[prop];
  245. }
  246. else {
  247. a[prop] = b[prop];
  248. }
  249. }
  250. } else if (Array.isArray(b[prop])) {
  251. throw new TypeError('Arrays are not supported by deepExtend');
  252. } else {
  253. a[prop] = b[prop];
  254. }
  255. }
  256. }
  257. }
  258. return a;
  259. };
  260. /**
  261. * Deep extend an object a with the properties of object b
  262. * @param {Object} a
  263. * @param {Object} b
  264. * @param [Boolean] protoExtend --> optional parameter. If true, the prototype values will also be extended.
  265. * (ie. the options objects that inherit from others will also get the inherited options)
  266. * @param [Boolean] global --> optional parameter. If true, the values of fields that are undefined or null will not deleted
  267. * @returns {Object}
  268. */
  269. exports.deepExtend = function(a, b, protoExtend, allowDeletion) {
  270. for (var prop in b) {
  271. if (b.hasOwnProperty(prop) || protoExtend === true) {
  272. if (b[prop] && b[prop].constructor === Object) {
  273. if (a[prop] === undefined) {
  274. a[prop] = {};
  275. }
  276. if (a[prop].constructor === Object) {
  277. exports.deepExtend(a[prop], b[prop], protoExtend);
  278. }
  279. else {
  280. if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  281. delete a[prop];
  282. }
  283. else {
  284. a[prop] = b[prop];
  285. }
  286. }
  287. } else if (Array.isArray(b[prop])) {
  288. a[prop] = [];
  289. for (let i = 0; i < b[prop].length; i++) {
  290. a[prop].push(b[prop][i]);
  291. }
  292. } else {
  293. a[prop] = b[prop];
  294. }
  295. }
  296. }
  297. return a;
  298. };
  299. /**
  300. * Test whether all elements in two arrays are equal.
  301. * @param {Array} a
  302. * @param {Array} b
  303. * @return {boolean} Returns true if both arrays have the same length and same
  304. * elements.
  305. */
  306. exports.equalArray = function (a, b) {
  307. if (a.length != b.length) return false;
  308. for (var i = 0, len = a.length; i < len; i++) {
  309. if (a[i] != b[i]) return false;
  310. }
  311. return true;
  312. };
  313. /**
  314. * Convert an object to another type
  315. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  316. * @param {String | undefined} type Name of the type. Available types:
  317. * 'Boolean', 'Number', 'String',
  318. * 'Date', 'Moment', ISODate', 'ASPDate'.
  319. * @return {*} object
  320. * @throws Error
  321. */
  322. exports.convert = function(object, type) {
  323. var match;
  324. if (object === undefined) {
  325. return undefined;
  326. }
  327. if (object === null) {
  328. return null;
  329. }
  330. if (!type) {
  331. return object;
  332. }
  333. if (!(typeof type === 'string') && !(type instanceof String)) {
  334. throw new Error('Type must be a string');
  335. }
  336. //noinspection FallthroughInSwitchStatementJS
  337. switch (type) {
  338. case 'boolean':
  339. case 'Boolean':
  340. return Boolean(object);
  341. case 'number':
  342. case 'Number':
  343. return Number(object.valueOf());
  344. case 'string':
  345. case 'String':
  346. return String(object);
  347. case 'Date':
  348. if (exports.isNumber(object)) {
  349. return new Date(object);
  350. }
  351. if (object instanceof Date) {
  352. return new Date(object.valueOf());
  353. }
  354. else if (moment.isMoment(object)) {
  355. return new Date(object.valueOf());
  356. }
  357. if (exports.isString(object)) {
  358. match = ASPDateRegex.exec(object);
  359. if (match) {
  360. // object is an ASP date
  361. return new Date(Number(match[1])); // parse number
  362. }
  363. else {
  364. return moment(object).toDate(); // parse string
  365. }
  366. }
  367. else {
  368. throw new Error(
  369. 'Cannot convert object of type ' + exports.getType(object) +
  370. ' to type Date');
  371. }
  372. case 'Moment':
  373. if (exports.isNumber(object)) {
  374. return moment(object);
  375. }
  376. if (object instanceof Date) {
  377. return moment(object.valueOf());
  378. }
  379. else if (moment.isMoment(object)) {
  380. return moment(object);
  381. }
  382. if (exports.isString(object)) {
  383. match = ASPDateRegex.exec(object);
  384. if (match) {
  385. // object is an ASP date
  386. return moment(Number(match[1])); // parse number
  387. }
  388. else {
  389. return moment(object); // parse string
  390. }
  391. }
  392. else {
  393. throw new Error(
  394. 'Cannot convert object of type ' + exports.getType(object) +
  395. ' to type Date');
  396. }
  397. case 'ISODate':
  398. if (exports.isNumber(object)) {
  399. return new Date(object);
  400. }
  401. else if (object instanceof Date) {
  402. return object.toISOString();
  403. }
  404. else if (moment.isMoment(object)) {
  405. return object.toDate().toISOString();
  406. }
  407. else if (exports.isString(object)) {
  408. match = ASPDateRegex.exec(object);
  409. if (match) {
  410. // object is an ASP date
  411. return new Date(Number(match[1])).toISOString(); // parse number
  412. }
  413. else {
  414. return new Date(object).toISOString(); // parse string
  415. }
  416. }
  417. else {
  418. throw new Error(
  419. 'Cannot convert object of type ' + exports.getType(object) +
  420. ' to type ISODate');
  421. }
  422. case 'ASPDate':
  423. if (exports.isNumber(object)) {
  424. return '/Date(' + object + ')/';
  425. }
  426. else if (object instanceof Date) {
  427. return '/Date(' + object.valueOf() + ')/';
  428. }
  429. else if (exports.isString(object)) {
  430. match = ASPDateRegex.exec(object);
  431. var value;
  432. if (match) {
  433. // object is an ASP date
  434. value = new Date(Number(match[1])).valueOf(); // parse number
  435. }
  436. else {
  437. value = new Date(object).valueOf(); // parse string
  438. }
  439. return '/Date(' + value + ')/';
  440. }
  441. else {
  442. throw new Error(
  443. 'Cannot convert object of type ' + exports.getType(object) +
  444. ' to type ASPDate');
  445. }
  446. default:
  447. throw new Error('Unknown type "' + type + '"');
  448. }
  449. };
  450. // parse ASP.Net Date pattern,
  451. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  452. // code from http://momentjs.com/
  453. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  454. /**
  455. * Get the type of an object, for example exports.getType([]) returns 'Array'
  456. * @param {*} object
  457. * @return {String} type
  458. */
  459. exports.getType = function(object) {
  460. var type = typeof object;
  461. if (type == 'object') {
  462. if (object === null) {
  463. return 'null';
  464. }
  465. if (object instanceof Boolean) {
  466. return 'Boolean';
  467. }
  468. if (object instanceof Number) {
  469. return 'Number';
  470. }
  471. if (object instanceof String) {
  472. return 'String';
  473. }
  474. if (Array.isArray(object)) {
  475. return 'Array';
  476. }
  477. if (object instanceof Date) {
  478. return 'Date';
  479. }
  480. return 'Object';
  481. }
  482. else if (type == 'number') {
  483. return 'Number';
  484. }
  485. else if (type == 'boolean') {
  486. return 'Boolean';
  487. }
  488. else if (type == 'string') {
  489. return 'String';
  490. }
  491. else if (type === undefined) {
  492. return 'undefined';
  493. }
  494. return type;
  495. };
  496. /**
  497. * Used to extend an array and copy it. This is used to propagate paths recursively.
  498. *
  499. * @param arr
  500. * @param newValue
  501. * @returns {Array}
  502. */
  503. exports.copyAndExtendArray = function(arr,newValue) {
  504. let newArr = [];
  505. for (let i = 0; i < arr.length; i++) {
  506. newArr.push(arr[i]);
  507. }
  508. newArr.push(newValue);
  509. return newArr;
  510. }
  511. /**
  512. * Used to extend an array and copy it. This is used to propagate paths recursively.
  513. *
  514. * @param arr
  515. * @param newValue
  516. * @returns {Array}
  517. */
  518. exports.copyArray = function(arr) {
  519. let newArr = [];
  520. for (let i = 0; i < arr.length; i++) {
  521. newArr.push(arr[i]);
  522. }
  523. return newArr;
  524. }
  525. /**
  526. * Retrieve the absolute left value of a DOM element
  527. * @param {Element} elem A dom element, for example a div
  528. * @return {number} left The absolute left position of this element
  529. * in the browser page.
  530. */
  531. exports.getAbsoluteLeft = function(elem) {
  532. return elem.getBoundingClientRect().left;
  533. };
  534. /**
  535. * Retrieve the absolute top value of a DOM element
  536. * @param {Element} elem A dom element, for example a div
  537. * @return {number} top The absolute top position of this element
  538. * in the browser page.
  539. */
  540. exports.getAbsoluteTop = function(elem) {
  541. return elem.getBoundingClientRect().top;
  542. };
  543. /**
  544. * add a className to the given elements style
  545. * @param {Element} elem
  546. * @param {String} className
  547. */
  548. exports.addClassName = function(elem, className) {
  549. var classes = elem.className.split(' ');
  550. if (classes.indexOf(className) == -1) {
  551. classes.push(className); // add the class to the array
  552. elem.className = classes.join(' ');
  553. }
  554. };
  555. /**
  556. * add a className to the given elements style
  557. * @param {Element} elem
  558. * @param {String} className
  559. */
  560. exports.removeClassName = function(elem, className) {
  561. var classes = elem.className.split(' ');
  562. var index = classes.indexOf(className);
  563. if (index != -1) {
  564. classes.splice(index, 1); // remove the class from the array
  565. elem.className = classes.join(' ');
  566. }
  567. };
  568. /**
  569. * For each method for both arrays and objects.
  570. * In case of an array, the built-in Array.forEach() is applied.
  571. * In case of an Object, the method loops over all properties of the object.
  572. * @param {Object | Array} object An Object or Array
  573. * @param {function} callback Callback method, called for each item in
  574. * the object or array with three parameters:
  575. * callback(value, index, object)
  576. */
  577. exports.forEach = function(object, callback) {
  578. var i,
  579. len;
  580. if (Array.isArray(object)) {
  581. // array
  582. for (i = 0, len = object.length; i < len; i++) {
  583. callback(object[i], i, object);
  584. }
  585. }
  586. else {
  587. // object
  588. for (i in object) {
  589. if (object.hasOwnProperty(i)) {
  590. callback(object[i], i, object);
  591. }
  592. }
  593. }
  594. };
  595. /**
  596. * Convert an object into an array: all objects properties are put into the
  597. * array. The resulting array is unordered.
  598. * @param {Object} object
  599. * @param {Array} array
  600. */
  601. exports.toArray = function(object) {
  602. var array = [];
  603. for (var prop in object) {
  604. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  605. }
  606. return array;
  607. }
  608. /**
  609. * Update a property in an object
  610. * @param {Object} object
  611. * @param {String} key
  612. * @param {*} value
  613. * @return {Boolean} changed
  614. */
  615. exports.updateProperty = function(object, key, value) {
  616. if (object[key] !== value) {
  617. object[key] = value;
  618. return true;
  619. }
  620. else {
  621. return false;
  622. }
  623. };
  624. /**
  625. * Add and event listener. Works for all browsers
  626. * @param {Element} element An html element
  627. * @param {string} action The action, for example "click",
  628. * without the prefix "on"
  629. * @param {function} listener The callback function to be executed
  630. * @param {boolean} [useCapture]
  631. */
  632. exports.addEventListener = function(element, action, listener, useCapture) {
  633. if (element.addEventListener) {
  634. if (useCapture === undefined)
  635. useCapture = false;
  636. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  637. action = "DOMMouseScroll"; // For Firefox
  638. }
  639. element.addEventListener(action, listener, useCapture);
  640. } else {
  641. element.attachEvent("on" + action, listener); // IE browsers
  642. }
  643. };
  644. /**
  645. * Remove an event listener from an element
  646. * @param {Element} element An html dom element
  647. * @param {string} action The name of the event, for example "mousedown"
  648. * @param {function} listener The listener function
  649. * @param {boolean} [useCapture]
  650. */
  651. exports.removeEventListener = function(element, action, listener, useCapture) {
  652. if (element.removeEventListener) {
  653. // non-IE browsers
  654. if (useCapture === undefined)
  655. useCapture = false;
  656. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  657. action = "DOMMouseScroll"; // For Firefox
  658. }
  659. element.removeEventListener(action, listener, useCapture);
  660. } else {
  661. // IE browsers
  662. element.detachEvent("on" + action, listener);
  663. }
  664. };
  665. /**
  666. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  667. */
  668. exports.preventDefault = function (event) {
  669. if (!event)
  670. event = window.event;
  671. if (event.preventDefault) {
  672. event.preventDefault(); // non-IE browsers
  673. }
  674. else {
  675. event.returnValue = false; // IE browsers
  676. }
  677. };
  678. /**
  679. * Get HTML element which is the target of the event
  680. * @param {Event} event
  681. * @return {Element} target element
  682. */
  683. exports.getTarget = function(event) {
  684. // code from http://www.quirksmode.org/js/events_properties.html
  685. if (!event) {
  686. event = window.event;
  687. }
  688. var target;
  689. if (event.target) {
  690. target = event.target;
  691. }
  692. else if (event.srcElement) {
  693. target = event.srcElement;
  694. }
  695. if (target.nodeType != undefined && target.nodeType == 3) {
  696. // defeat Safari bug
  697. target = target.parentNode;
  698. }
  699. return target;
  700. };
  701. /**
  702. * Check if given element contains given parent somewhere in the DOM tree
  703. * @param {Element} element
  704. * @param {Element} parent
  705. */
  706. exports.hasParent = function (element, parent) {
  707. var e = element;
  708. while (e) {
  709. if (e === parent) {
  710. return true;
  711. }
  712. e = e.parentNode;
  713. }
  714. return false;
  715. };
  716. exports.option = {};
  717. /**
  718. * Convert a value into a boolean
  719. * @param {Boolean | function | undefined} value
  720. * @param {Boolean} [defaultValue]
  721. * @returns {Boolean} bool
  722. */
  723. exports.option.asBoolean = function (value, defaultValue) {
  724. if (typeof value == 'function') {
  725. value = value();
  726. }
  727. if (value != null) {
  728. return (value != false);
  729. }
  730. return defaultValue || null;
  731. };
  732. /**
  733. * Convert a value into a number
  734. * @param {Boolean | function | undefined} value
  735. * @param {Number} [defaultValue]
  736. * @returns {Number} number
  737. */
  738. exports.option.asNumber = function (value, defaultValue) {
  739. if (typeof value == 'function') {
  740. value = value();
  741. }
  742. if (value != null) {
  743. return Number(value) || defaultValue || null;
  744. }
  745. return defaultValue || null;
  746. };
  747. /**
  748. * Convert a value into a string
  749. * @param {String | function | undefined} value
  750. * @param {String} [defaultValue]
  751. * @returns {String} str
  752. */
  753. exports.option.asString = function (value, defaultValue) {
  754. if (typeof value == 'function') {
  755. value = value();
  756. }
  757. if (value != null) {
  758. return String(value);
  759. }
  760. return defaultValue || null;
  761. };
  762. /**
  763. * Convert a size or location into a string with pixels or a percentage
  764. * @param {String | Number | function | undefined} value
  765. * @param {String} [defaultValue]
  766. * @returns {String} size
  767. */
  768. exports.option.asSize = function (value, defaultValue) {
  769. if (typeof value == 'function') {
  770. value = value();
  771. }
  772. if (exports.isString(value)) {
  773. return value;
  774. }
  775. else if (exports.isNumber(value)) {
  776. return value + 'px';
  777. }
  778. else {
  779. return defaultValue || null;
  780. }
  781. };
  782. /**
  783. * Convert a value into a DOM element
  784. * @param {HTMLElement | function | undefined} value
  785. * @param {HTMLElement} [defaultValue]
  786. * @returns {HTMLElement | null} dom
  787. */
  788. exports.option.asElement = function (value, defaultValue) {
  789. if (typeof value == 'function') {
  790. value = value();
  791. }
  792. return value || defaultValue || null;
  793. };
  794. /**
  795. * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
  796. *
  797. * @param {String} hex
  798. * @returns {{r: *, g: *, b: *}} | 255 range
  799. */
  800. exports.hexToRGB = function(hex) {
  801. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  802. var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  803. hex = hex.replace(shorthandRegex, function(m, r, g, b) {
  804. return r + r + g + g + b + b;
  805. });
  806. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  807. return result ? {
  808. r: parseInt(result[1], 16),
  809. g: parseInt(result[2], 16),
  810. b: parseInt(result[3], 16)
  811. } : null;
  812. };
  813. /**
  814. * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string.
  815. * @param color
  816. * @param opacity
  817. * @returns {*}
  818. */
  819. exports.overrideOpacity = function(color,opacity) {
  820. if (color.indexOf("rgb") != -1) {
  821. var rgb = color.substr(color.indexOf("(")+1).replace(")","").split(",");
  822. return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + opacity + ")"
  823. }
  824. else {
  825. var rgb = exports.hexToRGB(color);
  826. if (rgb == null) {
  827. return color;
  828. }
  829. else {
  830. return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + "," + opacity + ")"
  831. }
  832. }
  833. }
  834. /**
  835. *
  836. * @param red 0 -- 255
  837. * @param green 0 -- 255
  838. * @param blue 0 -- 255
  839. * @returns {string}
  840. * @constructor
  841. */
  842. exports.RGBToHex = function(red,green,blue) {
  843. return "#" + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
  844. };
  845. /**
  846. * Parse a color property into an object with border, background, and
  847. * highlight colors
  848. * @param {Object | String} color
  849. * @return {Object} colorObject
  850. */
  851. exports.parseColor = function(color) {
  852. var c;
  853. if (exports.isString(color)) {
  854. if (exports.isValidRGB(color)) {
  855. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  856. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  857. }
  858. if (exports.isValidHex(color)) {
  859. var hsv = exports.hexToHSV(color);
  860. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  861. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  862. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  863. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  864. c = {
  865. background: color,
  866. border:darkerColorHex,
  867. highlight: {
  868. background:lighterColorHex,
  869. border:darkerColorHex
  870. },
  871. hover: {
  872. background:lighterColorHex,
  873. border:darkerColorHex
  874. }
  875. };
  876. }
  877. else {
  878. c = {
  879. background:color,
  880. border:color,
  881. highlight: {
  882. background:color,
  883. border:color
  884. },
  885. hover: {
  886. background:color,
  887. border:color
  888. }
  889. };
  890. }
  891. }
  892. else {
  893. c = {};
  894. c.background = color.background || undefined;
  895. c.border = color.border || undefined;
  896. if (exports.isString(color.highlight)) {
  897. c.highlight = {
  898. border: color.highlight,
  899. background: color.highlight
  900. }
  901. }
  902. else {
  903. c.highlight = {};
  904. c.highlight.background = color.highlight && color.highlight.background || undefined;
  905. c.highlight.border = color.highlight && color.highlight.border || undefined;
  906. }
  907. if (exports.isString(color.hover)) {
  908. c.hover = {
  909. border: color.hover,
  910. background: color.hover
  911. }
  912. }
  913. else {
  914. c.hover = {};
  915. c.hover.background = color.hover && color.hover.background || undefined;
  916. c.hover.border = color.hover && color.hover.border || undefined;
  917. }
  918. }
  919. return c;
  920. };
  921. /**
  922. * http://www.javascripter.net/faq/rgb2hsv.htm
  923. *
  924. * @param red
  925. * @param green
  926. * @param blue
  927. * @returns {*}
  928. * @constructor
  929. */
  930. exports.RGBToHSV = function(red,green,blue) {
  931. red=red/255; green=green/255; blue=blue/255;
  932. var minRGB = Math.min(red,Math.min(green,blue));
  933. var maxRGB = Math.max(red,Math.max(green,blue));
  934. // Black-gray-white
  935. if (minRGB == maxRGB) {
  936. return {h:0,s:0,v:minRGB};
  937. }
  938. // Colors other than black-gray-white:
  939. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  940. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  941. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  942. var saturation = (maxRGB - minRGB)/maxRGB;
  943. var value = maxRGB;
  944. return {h:hue,s:saturation,v:value};
  945. };
  946. var cssUtil = {
  947. // split a string with css styles into an object with key/values
  948. split: function (cssText) {
  949. var styles = {};
  950. cssText.split(';').forEach(function (style) {
  951. if (style.trim() != '') {
  952. var parts = style.split(':');
  953. var key = parts[0].trim();
  954. var value = parts[1].trim();
  955. styles[key] = value;
  956. }
  957. });
  958. return styles;
  959. },
  960. // build a css text string from an object with key/values
  961. join: function (styles) {
  962. return Object.keys(styles)
  963. .map(function (key) {
  964. return key + ': ' + styles[key];
  965. })
  966. .join('; ');
  967. }
  968. };
  969. /**
  970. * Append a string with css styles to an element
  971. * @param {Element} element
  972. * @param {String} cssText
  973. */
  974. exports.addCssText = function (element, cssText) {
  975. var currentStyles = cssUtil.split(element.style.cssText);
  976. var newStyles = cssUtil.split(cssText);
  977. var styles = exports.extend(currentStyles, newStyles);
  978. element.style.cssText = cssUtil.join(styles);
  979. };
  980. /**
  981. * Remove a string with css styles from an element
  982. * @param {Element} element
  983. * @param {String} cssText
  984. */
  985. exports.removeCssText = function (element, cssText) {
  986. var styles = cssUtil.split(element.style.cssText);
  987. var removeStyles = cssUtil.split(cssText);
  988. for (var key in removeStyles) {
  989. if (removeStyles.hasOwnProperty(key)) {
  990. delete styles[key];
  991. }
  992. }
  993. element.style.cssText = cssUtil.join(styles);
  994. };
  995. /**
  996. * https://gist.github.com/mjijackson/5311256
  997. * @param h
  998. * @param s
  999. * @param v
  1000. * @returns {{r: number, g: number, b: number}}
  1001. * @constructor
  1002. */
  1003. exports.HSVToRGB = function(h, s, v) {
  1004. var r, g, b;
  1005. var i = Math.floor(h * 6);
  1006. var f = h * 6 - i;
  1007. var p = v * (1 - s);
  1008. var q = v * (1 - f * s);
  1009. var t = v * (1 - (1 - f) * s);
  1010. switch (i % 6) {
  1011. case 0: r = v, g = t, b = p; break;
  1012. case 1: r = q, g = v, b = p; break;
  1013. case 2: r = p, g = v, b = t; break;
  1014. case 3: r = p, g = q, b = v; break;
  1015. case 4: r = t, g = p, b = v; break;
  1016. case 5: r = v, g = p, b = q; break;
  1017. }
  1018. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  1019. };
  1020. exports.HSVToHex = function(h, s, v) {
  1021. var rgb = exports.HSVToRGB(h, s, v);
  1022. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1023. };
  1024. exports.hexToHSV = function(hex) {
  1025. var rgb = exports.hexToRGB(hex);
  1026. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1027. };
  1028. exports.isValidHex = function(hex) {
  1029. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1030. return isOk;
  1031. };
  1032. exports.isValidRGB = function(rgb) {
  1033. rgb = rgb.replace(" ","");
  1034. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1035. return isOk;
  1036. }
  1037. exports.isValidRGBA = function(rgba) {
  1038. rgba = rgba.replace(" ","");
  1039. var isOk = /rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(rgba);
  1040. return isOk;
  1041. }
  1042. /**
  1043. * This recursively redirects the prototype of JSON objects to the referenceObject
  1044. * This is used for default options.
  1045. *
  1046. * @param referenceObject
  1047. * @returns {*}
  1048. */
  1049. exports.selectiveBridgeObject = function(fields, referenceObject) {
  1050. if (typeof referenceObject == "object") {
  1051. var objectTo = Object.create(referenceObject);
  1052. for (var i = 0; i < fields.length; i++) {
  1053. if (referenceObject.hasOwnProperty(fields[i])) {
  1054. if (typeof referenceObject[fields[i]] == "object") {
  1055. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1056. }
  1057. }
  1058. }
  1059. return objectTo;
  1060. }
  1061. else {
  1062. return null;
  1063. }
  1064. };
  1065. /**
  1066. * This recursively redirects the prototype of JSON objects to the referenceObject
  1067. * This is used for default options.
  1068. *
  1069. * @param referenceObject
  1070. * @returns {*}
  1071. */
  1072. exports.bridgeObject = function(referenceObject) {
  1073. if (typeof referenceObject == "object") {
  1074. var objectTo = Object.create(referenceObject);
  1075. for (var i in referenceObject) {
  1076. if (referenceObject.hasOwnProperty(i)) {
  1077. if (typeof referenceObject[i] == "object") {
  1078. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1079. }
  1080. }
  1081. }
  1082. return objectTo;
  1083. }
  1084. else {
  1085. return null;
  1086. }
  1087. };
  1088. /**
  1089. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1090. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1091. *
  1092. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1093. * @param [object] options | options
  1094. * @param [String] option | this is the option key in the options argument
  1095. * @private
  1096. */
  1097. exports.mergeOptions = function (mergeTarget, options, option) {
  1098. if (options[option] !== undefined) {
  1099. if (typeof options[option] == 'boolean') {
  1100. mergeTarget[option].enabled = options[option];
  1101. }
  1102. else {
  1103. mergeTarget[option].enabled = true;
  1104. for (var prop in options[option]) {
  1105. if (options[option].hasOwnProperty(prop)) {
  1106. mergeTarget[option][prop] = options[option][prop];
  1107. }
  1108. }
  1109. }
  1110. }
  1111. }
  1112. /**
  1113. * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
  1114. * this function will then iterate in both directions over this sorted list to find all visible items.
  1115. *
  1116. * @param {Item[]} orderedItems | Items ordered by start
  1117. * @param {function} searchFunction | -1 is lower, 0 is found, 1 is higher
  1118. * @param {String} field
  1119. * @param {String} field2
  1120. * @returns {number}
  1121. * @private
  1122. */
  1123. exports.binarySearchCustom = function(orderedItems, searchFunction, field, field2) {
  1124. var maxIterations = 10000;
  1125. var iteration = 0;
  1126. var low = 0;
  1127. var high = orderedItems.length - 1;
  1128. while (low <= high && iteration < maxIterations) {
  1129. var middle = Math.floor((low + high) / 2);
  1130. var item = orderedItems[middle];
  1131. var value = (field2 === undefined) ? item[field] : item[field][field2];
  1132. var searchResult = searchFunction(value);
  1133. if (searchResult == 0) { // jihaa, found a visible item!
  1134. return middle;
  1135. }
  1136. else if (searchResult == -1) { // it is too small --> increase low
  1137. low = middle + 1;
  1138. }
  1139. else { // it is too big --> decrease high
  1140. high = middle - 1;
  1141. }
  1142. iteration++;
  1143. }
  1144. return -1;
  1145. };
  1146. /**
  1147. * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
  1148. * two values, we return either the one before or the one after, depending on user input
  1149. * If it is found, we return the index, else -1.
  1150. *
  1151. * @param {Array} orderedItems
  1152. * @param {{start: number, end: number}} target
  1153. * @param {String} field
  1154. * @param {String} sidePreference 'before' or 'after'
  1155. * @returns {number}
  1156. * @private
  1157. */
  1158. exports.binarySearchValue = function(orderedItems, target, field, sidePreference) {
  1159. var maxIterations = 10000;
  1160. var iteration = 0;
  1161. var low = 0;
  1162. var high = orderedItems.length - 1;
  1163. var prevValue, value, nextValue, middle;
  1164. while (low <= high && iteration < maxIterations) {
  1165. // get a new guess
  1166. middle = Math.floor(0.5*(high+low));
  1167. prevValue = orderedItems[Math.max(0,middle - 1)][field];
  1168. value = orderedItems[middle][field];
  1169. nextValue = orderedItems[Math.min(orderedItems.length-1,middle + 1)][field];
  1170. if (value == target) { // we found the target
  1171. return middle;
  1172. }
  1173. else if (prevValue < target && value > target) { // target is in between of the previous and the current
  1174. return sidePreference == 'before' ? Math.max(0,middle - 1) : middle;
  1175. }
  1176. else if (value < target && nextValue > target) { // target is in between of the current and the next
  1177. return sidePreference == 'before' ? middle : Math.min(orderedItems.length-1,middle + 1);
  1178. }
  1179. else { // didnt find the target, we need to change our boundaries.
  1180. if (value < target) { // it is too small --> increase low
  1181. low = middle + 1;
  1182. }
  1183. else { // it is too big --> decrease high
  1184. high = middle - 1;
  1185. }
  1186. }
  1187. iteration++;
  1188. }
  1189. // didnt find anything. Return -1.
  1190. return -1;
  1191. };
  1192. /**
  1193. * Quadratic ease-in-out
  1194. * http://gizma.com/easing/
  1195. * @param {number} t Current time
  1196. * @param {number} start Start value
  1197. * @param {number} end End value
  1198. * @param {number} duration Duration
  1199. * @returns {number} Value corresponding with current time
  1200. */
  1201. exports.easeInOutQuad = function (t, start, end, duration) {
  1202. var change = end - start;
  1203. t /= duration/2;
  1204. if (t < 1) return change/2*t*t + start;
  1205. t--;
  1206. return -change/2 * (t*(t-2) - 1) + start;
  1207. };
  1208. /*
  1209. * Easing Functions - inspired from http://gizma.com/easing/
  1210. * only considering the t value for the range [0, 1] => [0, 1]
  1211. * https://gist.github.com/gre/1650294
  1212. */
  1213. exports.easingFunctions = {
  1214. // no easing, no acceleration
  1215. linear: function (t) {
  1216. return t
  1217. },
  1218. // accelerating from zero velocity
  1219. easeInQuad: function (t) {
  1220. return t * t
  1221. },
  1222. // decelerating to zero velocity
  1223. easeOutQuad: function (t) {
  1224. return t * (2 - t)
  1225. },
  1226. // acceleration until halfway, then deceleration
  1227. easeInOutQuad: function (t) {
  1228. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  1229. },
  1230. // accelerating from zero velocity
  1231. easeInCubic: function (t) {
  1232. return t * t * t
  1233. },
  1234. // decelerating to zero velocity
  1235. easeOutCubic: function (t) {
  1236. return (--t) * t * t + 1
  1237. },
  1238. // acceleration until halfway, then deceleration
  1239. easeInOutCubic: function (t) {
  1240. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  1241. },
  1242. // accelerating from zero velocity
  1243. easeInQuart: function (t) {
  1244. return t * t * t * t
  1245. },
  1246. // decelerating to zero velocity
  1247. easeOutQuart: function (t) {
  1248. return 1 - (--t) * t * t * t
  1249. },
  1250. // acceleration until halfway, then deceleration
  1251. easeInOutQuart: function (t) {
  1252. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  1253. },
  1254. // accelerating from zero velocity
  1255. easeInQuint: function (t) {
  1256. return t * t * t * t * t
  1257. },
  1258. // decelerating to zero velocity
  1259. easeOutQuint: function (t) {
  1260. return 1 + (--t) * t * t * t * t
  1261. },
  1262. // acceleration until halfway, then deceleration
  1263. easeInOutQuint: function (t) {
  1264. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  1265. }
  1266. };