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.

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