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.

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