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.

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