RGraph.gantt.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  1. /**
  2. * o------------------------------------------------------------------------------o
  3. * | This file is part of the RGraph package - you can learn more at: |
  4. * | |
  5. * | http://www.rgraph.net |
  6. * | |
  7. * | This package is licensed under the RGraph license. For all kinds of business |
  8. * | purposes there is a small one-time licensing fee to pay and for non |
  9. * | commercial purposes it is free to use. You can read the full license here: |
  10. * | |
  11. * | http://www.rgraph.net/license |
  12. * o------------------------------------------------------------------------------o
  13. */
  14. if (typeof(RGraph) == 'undefined') RGraph = {};
  15. /**
  16. * The gantt chart constructor
  17. *
  18. * @param object canvas The cxanvas object
  19. * @param array data The chart data
  20. */
  21. RGraph.Gantt = function (id, data)
  22. {
  23. // Get the canvas and context objects
  24. this.id = id;
  25. this.canvas = document.getElementById(typeof id === 'object' ? id.id : id);
  26. this.context = this.canvas.getContext("2d");
  27. this.canvas.__object__ = this;
  28. this.type = 'gantt';
  29. this.isRGraph = true;
  30. this.uid = RGraph.CreateUID();
  31. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  32. this.data = data;
  33. this.colorsParsed = false;
  34. this.coordsText = [];
  35. /**
  36. * Compatibility with older browsers
  37. */
  38. RGraph.OldBrowserCompat(this.context);
  39. // Set some defaults
  40. this.properties = {
  41. 'chart.background.barcolor1': 'rgba(0,0,0,0)',
  42. 'chart.background.barcolor2': 'rgba(0,0,0,0)',
  43. 'chart.background.grid': true,
  44. 'chart.background.grid.width': 1,
  45. 'chart.background.grid.color': '#ddd',
  46. 'chart.background.grid.hsize': 20,
  47. 'chart.background.grid.vsize': 20,
  48. 'chart.background.grid.hlines': true,
  49. 'chart.background.grid.vlines': true,
  50. 'chart.background.grid.border': true,
  51. 'chart.background.grid.autofit':true,
  52. 'chart.background.grid.autofit.numhlines': 7,
  53. 'chart.background.grid.autofit.numvlines': 20,
  54. 'chart.background.vbars': [],
  55. 'chart.text.size': 10,
  56. 'chart.text.font': 'Arial',
  57. 'chart.text.color': 'black',
  58. 'chart.gutter.left': 75,
  59. 'chart.gutter.right': 25,
  60. 'chart.gutter.top': 35,
  61. 'chart.gutter.bottom': 25,
  62. 'chart.labels': [],
  63. 'chart.labels.align': 'bottom',
  64. 'chart.labels.inbar': null,
  65. 'chart.labels.inbar.color': 'black',
  66. 'chart.labels.inbar.bgcolor': null,
  67. 'chart.labels.inbar.align': 'left',
  68. 'chart.labels.inbar.size': 10,
  69. 'chart.labels.inbar.font': 'Arial',
  70. 'chart.labels.inbar.above': false,
  71. 'chart.vmargin': 2,
  72. 'chart.title': '',
  73. 'chart.title.background': null,
  74. 'chart.title.x': null,
  75. 'chart.title.y': null,
  76. 'chart.title.bold': true,
  77. 'chart.title.font': null,
  78. 'chart.title.yaxis': '',
  79. 'chart.title.yaxis.bold': true,
  80. 'chart.title.yaxis.pos': null,
  81. 'chart.title.yaxis.color': null,
  82. 'chart.title.yaxis.position': 'right',
  83. 'chart.title.yaxis.x': null,
  84. 'chart.title.yaxis.y': null,
  85. 'chart.title.xaxis.x': null,
  86. 'chart.title.xaxis.y': null,
  87. 'chart.title.xaxis.bold': true,
  88. 'chart.title.x': null,
  89. 'chart.title.y': null,
  90. 'chart.title.halign': null,
  91. 'chart.title.valign': null,
  92. 'chart.borders': true,
  93. 'chart.defaultcolor': 'white',
  94. 'chart.coords': [],
  95. 'chart.tooltips': null,
  96. 'chart.tooltips.effect': 'fade',
  97. 'chart.tooltips.css.class': 'RGraph_tooltip',
  98. 'chart.tooltips.highlight': true,
  99. 'chart.tooltips.event': 'onclick',
  100. 'chart.highlight.stroke': 'rgba(0,0,0,0)',
  101. 'chart.highlight.fill': 'rgba(255,255,255,0.7)',
  102. 'chart.xmin': 0,
  103. 'chart.xmax': 0,
  104. 'chart.contextmenu': null,
  105. 'chart.annotatable': false,
  106. 'chart.annotate.color': 'black',
  107. 'chart.zoom.factor': 1.5,
  108. 'chart.zoom.fade.in': true,
  109. 'chart.zoom.fade.out': true,
  110. 'chart.zoom.hdir': 'right',
  111. 'chart.zoom.vdir': 'down',
  112. 'chart.zoom.frames': 25,
  113. 'chart.zoom.delay': 16.666,
  114. 'chart.zoom.shadow': true,
  115. 'chart.zoom.background': true,
  116. 'chart.zoom.action': 'zoom',
  117. 'chart.resizable': false,
  118. 'chart.resize.handle.adjust': [0,0],
  119. 'chart.resize.handle.background': null,
  120. 'chart.adjustable': false,
  121. 'chart.events.click': null,
  122. 'chart.events.mousemove': null
  123. }
  124. /**
  125. * Create the dollar objects so that functions can be added to them
  126. */
  127. if (!data) {
  128. alert('[GANTT] The Gantt chart event data is now supplied as the second argument to the constructor - please update your code');
  129. }
  130. // Linearize the data (DON'T use RGraph.array_linearize() here)
  131. for (var i=0,idx=0; i<data.length; ++i) {
  132. if (RGraph.is_array(this.data[i][0])) {
  133. for (var j=0; j<this.data[i].length; ++j) {
  134. this['$' + (idx++)] = {};
  135. }
  136. } else {
  137. this['$' + (idx++)] = {};
  138. }
  139. }
  140. /*
  141. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  142. * done already
  143. */
  144. if (!this.canvas.__rgraph_aa_translated__) {
  145. this.context.translate(0.5,0.5);
  146. this.canvas.__rgraph_aa_translated__ = true;
  147. }
  148. ///////////////////////////////// SHORT PROPERTIES /////////////////////////////////
  149. var RG = RGraph;
  150. var ca = this.canvas;
  151. var co = ca.getContext('2d');
  152. var prop = this.properties;
  153. //////////////////////////////////// METHODS ///////////////////////////////////////
  154. /**
  155. * A peudo setter
  156. *
  157. * @param name string The name of the property to set
  158. * @param value mixed The value of the property
  159. */
  160. this.Set = function (name, value)
  161. {
  162. name = name.toLowerCase();
  163. /**
  164. * This should be done first - prepend the propertyy name with "chart." if necessary
  165. */
  166. if (name.substr(0,6) != 'chart.') {
  167. name = 'chart.' + name;
  168. }
  169. if (name == 'chart.margin') {
  170. name = 'chart.vmargin'
  171. }
  172. if (name == 'chart.events') {
  173. alert('[GANTT] The chart.events property is deprecated - supply the events data as an argument to the constructor instead');
  174. this.data = value;
  175. }
  176. prop[name] = value;
  177. return this;
  178. }
  179. /**
  180. * A peudo getter
  181. *
  182. * @param name string The name of the property to get
  183. */
  184. this.Get = function (name)
  185. {
  186. /**
  187. * This should be done first - prepend the property name with "chart." if necessary
  188. */
  189. if (name.substr(0,6) != 'chart.') {
  190. name = 'chart.' + name;
  191. }
  192. if (name == 'chart.margin') {
  193. name = 'chart.vmargin'
  194. }
  195. return prop[name.toLowerCase()];
  196. }
  197. /**
  198. * Draws the chart
  199. */
  200. this.Draw = function ()
  201. {
  202. /**
  203. * Fire the onbeforedraw event
  204. */
  205. RG.FireCustomEvent(this, 'onbeforedraw');
  206. /**
  207. * This is new in May 2011 and facilitates indiviual gutter settings,
  208. * eg chart.gutter.left
  209. */
  210. this.gutterLeft = prop['chart.gutter.left'];
  211. this.gutterRight = prop['chart.gutter.right'];
  212. this.gutterTop = prop['chart.gutter.top'];
  213. this.gutterBottom = prop['chart.gutter.bottom'];
  214. /**
  215. * Parse the colors. This allows for simple gradient syntax
  216. */
  217. if (!this.colorsParsed) {
  218. this.parseColors();
  219. // Don't want to do this again
  220. this.colorsParsed = true;
  221. }
  222. /**
  223. * Work out the graphArea
  224. */
  225. this.graphArea = ca.width - this.gutterLeft - this.gutterRight;
  226. this.graphHeight = ca.height - this.gutterTop - this.gutterBottom;
  227. this.numEvents = this.data.length
  228. this.barHeight = this.graphHeight / this.numEvents;
  229. this.halfBarHeight = this.barHeight / 2;
  230. /**
  231. * Draw the background
  232. */
  233. RG.background.Draw(this);
  234. /**
  235. * Draw the labels at the top
  236. */
  237. this.DrawLabels();
  238. /**
  239. * Draw the events
  240. */
  241. this.DrawEvents();
  242. /**
  243. * Setup the context menu if required
  244. */
  245. if (prop['chart.contextmenu']) {
  246. RG.ShowContext(this);
  247. }
  248. /**
  249. * This function enables resizing
  250. */
  251. if (prop['chart.resizable']) {
  252. RG.AllowResizing(this);
  253. }
  254. /**
  255. * This installs the event listeners
  256. */
  257. RG.InstallEventListeners(this);
  258. /**
  259. * Fire the RGraph ondraw event
  260. */
  261. RG.FireCustomEvent(this, 'ondraw');
  262. return this;
  263. }
  264. /**
  265. * Draws the labels at the top and the left of the chart
  266. */
  267. this.DrawLabels = function ()
  268. {
  269. co.beginPath();
  270. co.fillStyle = prop['chart.text.color'];
  271. /**
  272. * Draw the X labels at the top of the chart.
  273. */
  274. var labels = prop['chart.labels'];
  275. var labelSpace = (this.graphArea) / labels.length;
  276. var x = this.gutterLeft + (labelSpace / 2);
  277. var y = this.gutterTop - (prop['chart.text.size'] / 2) - 5;
  278. var font = prop['chart.text.font'];
  279. var size = prop['chart.text.size'];
  280. co.strokeStyle = 'black'
  281. /**
  282. * This facilitates chart.labels.align
  283. */
  284. if (prop['chart.labels.align'] == 'bottom') {
  285. y = ca.height - this.gutterBottom + size + 2;
  286. }
  287. /**
  288. * Draw the horizontal labels
  289. */
  290. for (i=0; i<labels.length; ++i) {
  291. RG.Text2(this,{'font': font,
  292. 'size':size,
  293. 'x': x + (i * labelSpace),
  294. 'y': y,
  295. 'text': String(labels[i]),
  296. 'halign':'center',
  297. 'valign':'center',
  298. 'tag': 'labels.horizontal'
  299. });
  300. }
  301. /**
  302. * Draw the vertical labels
  303. */
  304. for (var i=0,len=this.data.length; i<len; ++i) {
  305. var ev = this.data[i];
  306. var x = this.gutterLeft;
  307. var y = this.gutterTop + this.halfBarHeight + (i * this.barHeight);
  308. RG.Text2(this,{'font': font,
  309. 'size':size,
  310. 'x': x - 5,
  311. 'y': y,
  312. 'text': RG.is_array(ev[0]) ? (ev[0][3] ? String(ev[0][3]) : '') : (typeof ev[3] == 'string' ? ev[3] : ''),
  313. 'halign':'right',
  314. 'valign':'center',
  315. 'tag': 'labels.vertical'
  316. });
  317. }
  318. }
  319. /**
  320. * Draws the events to the canvas
  321. */
  322. this.DrawEvents = function ()
  323. {
  324. var events = this.data;
  325. /**
  326. * Reset the coords array to prevent it growing
  327. */
  328. this.coords = [];
  329. /**
  330. * First draw the vertical bars that have been added
  331. */
  332. if (prop['chart.vbars']) {
  333. for (i=0,len=prop['chart.vbars'].length; i<len; ++i) {
  334. // Boundary checking
  335. if (prop['chart.vbars'][i][0] + prop['chart.vbars'][i][1] > prop['chart.xmax']) {
  336. prop['chart.vbars'][i][1] = 364 - prop['chart.vbars'][i][0];
  337. }
  338. var barX = this.gutterLeft + (( (prop['chart.vbars'][i][0] - prop['chart.xmin']) / (prop['chart.xmax'] - prop['chart.xmin']) ) * this.graphArea);
  339. var barY = this.gutterTop;
  340. var width = (this.graphArea / (prop['chart.xmax'] - prop['chart.xmin']) ) * prop['chart.vbars'][i][1];
  341. var height = ca.height - this.gutterTop - this.gutterBottom;
  342. // Right hand bounds checking
  343. if ( (barX + width) > (ca.width - this.gutterRight) ) {
  344. width = ca.width - this.gutterRight - barX;
  345. }
  346. co.fillStyle = prop['chart.vbars'][i][2];
  347. co.fillRect(barX, barY, width, height);
  348. }
  349. }
  350. /**
  351. * Draw the events
  352. */
  353. var sequentialIndex = 0;
  354. for (i=0; i<events.length; ++i) {
  355. if (typeof(events[i][0]) == 'number') {
  356. this.DrawSingleEvent(events[i], i, null);
  357. } else {
  358. for (var j=0; j<events[i].length; ++j) {
  359. this.DrawSingleEvent(events[i][j], i, sequentialIndex++);
  360. }
  361. }
  362. }
  363. }
  364. /**
  365. * Retrieves the bar (if any) that has been click on or is hovered over
  366. *
  367. * @param object e The event object
  368. */
  369. this.getShape =
  370. this.getBar = function (e)
  371. {
  372. e = RG.FixEventObject(e);
  373. //var canvas = e.target;
  374. //var context = canvas.getContext('2d');
  375. var mouseCoords = RGraph.getMouseXY(e);
  376. var mouseX = mouseCoords[0];
  377. var mouseY = mouseCoords[1];
  378. /**
  379. * Loop through the bars determining if the mouse is over a bar
  380. */
  381. for (var i=0,len=this.coords.length; i<len; i++) {
  382. var left = this.coords[i][0];
  383. var top = this.coords[i][1];
  384. var width = this.coords[i][2];
  385. var height = this.coords[i][3];
  386. if ( mouseX >= left
  387. && mouseX <= (left + width)
  388. && mouseY >= top
  389. && mouseY <= (top + height)
  390. ) {
  391. var tooltip = RGraph.parseTooltipText(prop['chart.tooltips'], i);
  392. return {0: this, 'object': this,
  393. 1: left, 'x': left,
  394. 2: top, 'y': top,
  395. 3: width, 'width': width,
  396. 4: height, 'height': height,
  397. 5: i, 'index': i,
  398. 'tooltip': tooltip};
  399. }
  400. }
  401. }
  402. /**
  403. * Draws a single event
  404. */
  405. this.DrawSingleEvent = function (ev, index, sequentialIndex)
  406. {
  407. var min = prop['chart.xmin'];
  408. co.beginPath();
  409. co.strokeStyle = 'black';
  410. co.fillStyle = ev[4] ? ev[4] : prop['chart.defaultcolor'];
  411. var barStartX = this.gutterLeft + (((ev[0] - min) / (prop['chart.xmax'] - min)) * this.graphArea);
  412. var barStartY = this.gutterTop + (index * this.barHeight);
  413. var barWidth = (ev[1] / (prop['chart.xmax'] - min) ) * this.graphArea;
  414. /**
  415. * If the width is greater than the graph atrea, curtail it
  416. */
  417. if ( (barStartX + barWidth) > (ca.width - this.gutterRight) ) {
  418. barWidth = ca.width - this.gutterRight - barStartX;
  419. }
  420. /**
  421. * Draw the actual bar storing store the coordinates
  422. */
  423. this.coords.push([barStartX, barStartY + prop['chart.vmargin'], barWidth, this.barHeight - (2 * prop['chart.vmargin'])]);
  424. // draw the border around the bar
  425. if (prop['chart.borders'] || ev[6]) {
  426. co.strokeStyle = typeof(ev[6]) == 'string' ? ev[6] : 'black';
  427. co.lineWidth = (typeof(ev[7]) == 'number' ? ev[7] : 1);
  428. co.beginPath();
  429. co.strokeRect(barStartX, barStartY + prop['chart.vmargin'], barWidth, this.barHeight - (2 * prop['chart.vmargin']) );
  430. }
  431. co.beginPath();
  432. co.fillRect(barStartX, barStartY + prop['chart.vmargin'], barWidth, this.barHeight - (2 * prop['chart.vmargin']) );
  433. co.fill();
  434. // Work out the completeage indicator
  435. var complete = (ev[2] / 100) * barWidth;
  436. // Draw the % complete indicator. If it's greater than 0
  437. if (typeof(ev[2]) == 'number') {
  438. co.beginPath();
  439. co.fillStyle = ev[5] ? ev[5] : '#0c0';
  440. co.fillRect(barStartX,
  441. barStartY + prop['chart.vmargin'],
  442. (ev[2] / 100) * barWidth,
  443. this.barHeight - (2 * prop['chart.vmargin']) );
  444. co.beginPath();
  445. co.fillStyle = prop['chart.text.color'];
  446. RG.Text2(this,{'font': prop['chart.text.font'],
  447. 'size': prop['chart.text.size'],
  448. 'x': barStartX + barWidth + 5,
  449. 'y': barStartY + this.halfBarHeight,
  450. 'text': String(ev[2]) + '%',
  451. 'valign':'center',
  452. 'tag': 'labels.complete'
  453. });
  454. }
  455. /**
  456. * Draw the inbar label if it's defined
  457. */
  458. if (prop['chart.labels.inbar'] && (prop['chart.labels.inbar'][sequentialIndex] || prop['chart.labels.inbar'][index])) {
  459. var label = String(prop['chart.labels.inbar'][sequentialIndex] || prop['chart.labels.inbar'][index]);
  460. var halign = prop['chart.labels.inbar.align'] == 'left' ? 'left' : 'center';
  461. halign = prop['chart.labels.inbar.align'] == 'right' ? 'right' : halign;
  462. // Work out the position of the text
  463. if (halign == 'right') {
  464. var x = (barStartX + barWidth) - 5;
  465. } else if (halign == 'center') {
  466. var x = barStartX + (barWidth / 2);
  467. } else {
  468. var x = barStartX + 5;
  469. }
  470. // Draw the labels "above" the bar
  471. if (prop['chart.labels.inbar.above']) {
  472. x = barStartX + barWidth + 5;
  473. halign = 'left';
  474. }
  475. // Set the color
  476. co.fillStyle = prop['chart.labels.inbar.color'];
  477. RGraph.Text2(this,{'font':prop['chart.labels.inbar.font'],
  478. 'size':prop['chart.labels.inbar.size'],
  479. 'x': x,
  480. 'y': barStartY + this.halfBarHeight,
  481. 'text': label,
  482. 'valign':'center',
  483. 'halign':halign,
  484. 'bounding': typeof(prop['chart.labels.inbar.bgcolor']) == 'string',
  485. 'boundingFill':typeof(prop['chart.labels.inbar.bgcolor']) == 'string' ? prop['chart.labels.inbar.bgcolor'] : null,
  486. 'tag': 'labels.inbar'
  487. });
  488. }
  489. }
  490. /**
  491. * Each object type has its own Highlight() function which highlights the appropriate shape
  492. *
  493. * @param object shape The shape to highlight
  494. */
  495. this.Highlight = function (shape)
  496. {
  497. // Add the new highlight
  498. RG.Highlight.Rect(this, shape);
  499. }
  500. /**
  501. * The getObjectByXY() worker method. Don't call this call:
  502. *
  503. * RGraph.ObjectRegistry.getObjectByXY(e)
  504. *
  505. * @param object e The event object
  506. */
  507. this.getObjectByXY = function (e)
  508. {
  509. var mouseXY = RG.getMouseXY(e);
  510. if (
  511. mouseXY[0] > this.gutterLeft
  512. && mouseXY[0] < (ca.width - this.gutterRight)
  513. && mouseXY[1] > this.gutterTop
  514. && mouseXY[1] < (ca.height - this.gutterBottom)
  515. ) {
  516. return this;
  517. }
  518. }
  519. /**
  520. * This method handles the adjusting calculation for when the mouse is moved
  521. *
  522. * @param object e The event object
  523. */
  524. this.Adjusting_mousemove = function (e)
  525. {
  526. /**
  527. * Handle adjusting for the Bar
  528. */
  529. if (prop['chart.adjustable'] && RG.Registry.Get('chart.adjusting') && RG.Registry.Get('chart.adjusting').uid == this.uid) {
  530. var bar = RG.Registry.Get('chart.adjusting.gantt');
  531. if (bar) {
  532. var mouseXY = RG.getMouseXY(e);
  533. var obj = RG.Registry.Get('chart.adjusting.gantt')['object'];
  534. var index = bar['index'];
  535. var diff = ((mouseXY[0] - RG.Registry.Get('chart.adjusting.gantt')['mousex']) / (ca.width - obj.gutterLeft - obj.gutterRight)) * prop['chart.xmax'];
  536. var eventStart = RG.Registry.Get('chart.adjusting.gantt')['event_start'];
  537. var duration = RG.Registry.Get('chart.adjusting.gantt')['event_duration'];
  538. if (bar['mode'] == 'move') {
  539. diff = Math.round(diff);
  540. if ( eventStart + diff >= 0
  541. && (eventStart + diff + obj.data[index][1]) < prop['chart.xmax']) {
  542. obj.data[index][0] = eventStart + diff;
  543. } else if (eventStart + diff < 0) {
  544. obj.data[index][0] = 0;
  545. //
  546. } else if ((eventStart + diff + obj.data[index][1]) > prop['chart.xmax']) {
  547. obj.data[index][0] = prop['chart.xmax'] - obj.data[index][1];
  548. }
  549. } else if (bar['mode'] == 'resize') {
  550. /*
  551. * Account for the right hand gutter. Appears to be a FF bug
  552. */
  553. if (mouseXY[0] > (ca.width - obj.gutterRight)) {
  554. mouseXY[0] = ca.width - obj.gutterRight;
  555. }
  556. var diff = ((mouseXY[0] - RG.Registry.Get('chart.adjusting.gantt')['mousex']) / (ca.width - obj.gutterLeft - obj.gutterRight)) * prop['chart.xmax'];
  557. diff = Math.round(diff);
  558. obj.data[index][1] = duration + diff;
  559. if (obj.data[index][1] < 0) {
  560. obj.data[index][1] = 1;
  561. }
  562. }
  563. RG.FireCustomEvent(obj, 'onadjust')
  564. RG.Clear(ca);
  565. RG.RedrawCanvas(ca);
  566. }
  567. }
  568. }
  569. /**
  570. * This function positions a tooltip when it is displayed
  571. *
  572. * @param obj object The chart object
  573. * @param int x The X coordinate specified for the tooltip
  574. * @param int y The Y coordinate specified for the tooltip
  575. * @param objec tooltip The tooltips DIV element
  576. */
  577. this.positionTooltip = function (obj, x, y, tooltip, idx)
  578. {
  579. var coordX = obj.coords[tooltip.__index__][0];
  580. var coordY = obj.coords[tooltip.__index__][1];
  581. var coordW = obj.coords[tooltip.__index__][2];
  582. var coordH = obj.coords[tooltip.__index__][3];
  583. var canvasXY = RG.getCanvasXY(obj.canvas);
  584. var gutterLeft = obj.gutterLeft;
  585. var gutterTop = obj.gutterTop;
  586. var width = tooltip.offsetWidth;
  587. var height = tooltip.offsetHeight;
  588. // Set the top position
  589. tooltip.style.left = 0;
  590. tooltip.style.top = canvasXY[1] + coordY - height - 7 + 'px';
  591. // By default any overflow is hidden
  592. tooltip.style.overflow = '';
  593. // The arrow
  594. var img = new Image();
  595. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  596. img.style.position = 'absolute';
  597. img.id = '__rgraph_tooltip_pointer__';
  598. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  599. tooltip.appendChild(img);
  600. // Reposition the tooltip if at the edges:
  601. // LEFT edge
  602. if ((canvasXY[0] + coordX - (width / 2)) < 10) {
  603. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + (coordW / 2) + 'px';
  604. img.style.left = ((width * 0.1) - 8.5) + 'px';
  605. // RIGHT edge
  606. } else if ((canvasXY[0] + coordX + (width / 2)) > document.body.offsetWidth) {
  607. tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + (coordW / 2) + 'px';
  608. img.style.left = ((width * 0.9) - 8.5) + 'px';
  609. // Default positioning - CENTERED
  610. } else {
  611. tooltip.style.left = (canvasXY[0] + coordX + (coordW / 2) - (width * 0.5)) + 'px';
  612. img.style.left = ((width * 0.5) - 8.5) + 'px';
  613. }
  614. }
  615. /**
  616. * Returns the X coordinate for the given value
  617. *
  618. * @param number value The desired value (eg minute/hour/day etc)
  619. */
  620. this.getXCoord = function (value)
  621. {
  622. var min = prop['chart.xmin'];
  623. var max = prop['chart.xmax'];
  624. if (value > max || value < min) {
  625. return null;
  626. }
  627. var x = (((value - min) / (max - min)) * this.graphArea) + this.gutterLeft;
  628. return x;
  629. }
  630. /**
  631. * Returns the value given EITHER the event object OR a two element array containing the X/Y coords
  632. */
  633. this.getValue = function (arg)
  634. {
  635. if (arg.length == 2) {
  636. var mouseXY = arg;
  637. } else {
  638. var mouseXY = RGraph.getMouseXY(arg);
  639. }
  640. var mouseX = mouseXY[0];
  641. var mouseY = mouseXY[1];
  642. var value = (mouseX - this.gutterLeft) / (ca.width - this.gutterLeft - this.gutterRight);
  643. value *= (prop['chart.xmax'] - prop['chart.xmin']);
  644. // Bounds checking
  645. if (value < prop['chart.xmin'] || value > prop['chart.xmax']) {
  646. value = null;
  647. }
  648. return value;
  649. }
  650. /**
  651. * This allows for easy specification of gradients. Could optimise this not to repeatedly call parseSingleColors()
  652. */
  653. this.parseColors = function ()
  654. {
  655. for (var i=0; i<this.data.length; ++i) {
  656. if (typeof(this.data[i][4]) == 'string') this.data[i][4] = this.parseSingleColorForGradient(this.data[i][4]);
  657. if (typeof(this.data[i][5]) == 'string') this.data[i][5] = this.parseSingleColorForGradient(this.data[i][5]);
  658. if (typeof this.data[i][0] == 'object' && typeof this.data[i][0][0] == 'number') {
  659. for (var j=0,len=this.data[i].length; j<len; j+=1) {
  660. this.data[i][j][4] = this.parseSingleColorForGradient(this.data[i][j][4]);
  661. this.data[i][j][5] = this.parseSingleColorForGradient(this.data[i][j][5]);
  662. }
  663. }
  664. }
  665. prop['chart.background.barcolor1'] = this.parseSingleColorForGradient(prop['chart.background.barcolor1']);
  666. prop['chart.background.barcolor2'] = this.parseSingleColorForGradient(prop['chart.background.barcolor2']);
  667. prop['chart.background.grid.color'] = this.parseSingleColorForGradient(prop['chart.background.grid.color']);
  668. prop['chart.defaultcolor'] = this.parseSingleColorForGradient(prop['chart.defaultcolor']);
  669. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  670. prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
  671. }
  672. /**
  673. * This parses a single color value
  674. */
  675. this.parseSingleColorForGradient = function (color)
  676. {
  677. if (!color || typeof(color) != 'string') {
  678. return color;
  679. }
  680. if (color.match(/^gradient\((.*)\)$/i)) {
  681. var parts = RegExp.$1.split(':');
  682. // Create the gradient
  683. var grad = co.createLinearGradient(this.gutterLeft,0,ca.width - this.gutterRight,0);
  684. var diff = 1 / (parts.length - 1);
  685. grad.addColorStop(0, RG.trim(parts[0]));
  686. for (var j=1; j<parts.length; ++j) {
  687. grad.addColorStop(j * diff, RG.trim(parts[j]));
  688. }
  689. }
  690. return grad ? grad : color;
  691. }
  692. /**
  693. * Register the object
  694. */
  695. RG.Register(this);
  696. }