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.

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