RGraph.gantt.js 37 KB

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