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.

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