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.

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