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.

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