RGraph.drawing.yaxis.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  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. /**
  15. * Having this here means that the RGraph libraries can be included in any order, instead of you having
  16. * to include the common core library first.
  17. */
  18. if (typeof(RGraph) == 'undefined') RGraph = {};
  19. if (typeof(RGraph.Drawing) == 'undefined') RGraph.Drawing = {};
  20. /**
  21. * The constructor. This function sets up the object. It takes the ID (the HTML attribute) of the canvas as the
  22. * first argument and the X coordinate of the axis as the second
  23. *
  24. * @param string id The canvas tag ID
  25. * @param number x The X coordinate of the Y axis
  26. */
  27. RGraph.Drawing.YAxis = function (id, x)
  28. {
  29. this.id = id;
  30. this.canvas = document.getElementById(typeof id === 'object' ? id.id : id);
  31. this.context = this.canvas.getContext('2d');
  32. this.canvas.__object__ = this;
  33. this.x = x;
  34. this.coords = [];
  35. this.coordsText = [];
  36. /**
  37. * This defines the type of this shape
  38. */
  39. this.type = 'drawing.yaxis';
  40. /**
  41. * This facilitates easy object identification, and should always be true
  42. */
  43. this.isRGraph = true;
  44. /**
  45. * This adds a uid to the object that you can use for identification purposes
  46. */
  47. this.uid = RGraph.CreateUID();
  48. /**
  49. * This adds a UID to the canvas for identification purposes
  50. */
  51. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  52. /**
  53. * This does a few things, for example adding the .fillText() method to the canvas 2D context when
  54. * it doesn't exist. This facilitates the graphs to be still shown in older browser (though without
  55. * text obviously). You'll find the function in RGraph.common.core.js
  56. */
  57. RGraph.OldBrowserCompat(this.context);
  58. /**
  59. * Some example background properties
  60. */
  61. this.properties =
  62. {
  63. 'chart.gutter.top': 25,
  64. 'chart.gutter.bottom': 25,
  65. 'chart.min': 0,
  66. 'chart.max': null,
  67. 'chart.colors': ['black'],
  68. 'chart.title': '',
  69. 'chart.title.color': null,
  70. 'chart.text.color': null,
  71. 'chart.numticks': 5,
  72. 'chart.numlabels': 5,
  73. 'chart.labels.specific': null,
  74. 'chart.text.font': 'Arial',
  75. 'chart.text.size': 10,
  76. 'chart.align': 'left',
  77. 'hart.scale.formatter': null,
  78. 'chart.scale.point': '.',
  79. 'chart.scale.decimals': 0,
  80. 'chart.scale.invert': false,
  81. 'chart.scale.zerostart': true,
  82. 'chart.scale.visible': true,
  83. 'chart.units.pre': '',
  84. 'chart.units.post': '',
  85. 'chart.linewidth': 1,
  86. 'chart.noendtick.top': false,
  87. 'chart.noendtick.bottom': false,
  88. 'chart.noyaxis': false,
  89. 'chart.tooltips': null,
  90. 'chart.tooltips.effect': 'fade',
  91. 'chart.tooltips.css.class':'RGraph_tooltip',
  92. 'chart.tooltips.event': 'onclick',
  93. 'chart.xaxispos': 'bottom',
  94. 'chart.events.click': null,
  95. 'chart.events.mousemove': null
  96. }
  97. /**
  98. * A simple check that the browser has canvas support
  99. */
  100. if (!this.canvas) {
  101. alert('[DRAWING.YAXIS] No canvas support');
  102. return;
  103. }
  104. /**
  105. * Create the dollar object so that functions can be added to them
  106. */
  107. this.$0 = {};
  108. /**
  109. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  110. * done already
  111. *
  112. * ** Could use setTransform() here instead ?
  113. */
  114. if (!this.canvas.__rgraph_aa_translated__) {
  115. this.context.translate(0.5,0.5);
  116. this.canvas.__rgraph_aa_translated__ = true;
  117. }
  118. ///////////////////////////////// SHORT PROPERTIES /////////////////////////////////
  119. var RG = RGraph;
  120. var ca = this.canvas;
  121. var co = ca.getContext('2d');
  122. var prop = this.properties;
  123. //////////////////////////////////// METHODS ///////////////////////////////////////
  124. /**
  125. * A setter method for setting graph properties. It can be used like this: obj.Set('chart.strokestyle', '#666');
  126. *
  127. * @param name string The name of the property to set
  128. * @param value mixed The value of the property
  129. */
  130. this.Set = function (name, value)
  131. {
  132. name = name.toLowerCase();
  133. /**
  134. * This should be done first - prepend the property name with "chart." if necessary
  135. */
  136. if (name.substr(0,6) != 'chart.') {
  137. name = 'chart.' + name;
  138. }
  139. prop[name] = value;
  140. return this;
  141. }
  142. /**
  143. * A getter method for retrieving graph properties. It can be used like this: obj.Get('chart.strokestyle');
  144. *
  145. * @param name string The name of the property to get
  146. */
  147. this.Get = function (name)
  148. {
  149. /**
  150. * This should be done first - prepend the property name with "chart." if necessary
  151. */
  152. if (name.substr(0,6) != 'chart.') {
  153. name = 'chart.' + name;
  154. }
  155. return prop[name.toLowerCase()];
  156. }
  157. /**
  158. * Draws the axes
  159. */
  160. this.Draw = function ()
  161. {
  162. /**
  163. * Fire the onbeforedraw event
  164. */
  165. RG.FireCustomEvent(this, 'onbeforedraw');
  166. /**
  167. * Some defaults
  168. */
  169. this.gutterTop = prop['chart.gutter.top'];
  170. this.gutterBottom = prop['chart.gutter.bottom'];
  171. if (!prop['chart.text.color']) prop['chart.text.color'] = prop['chart.colors'][0];
  172. if (!prop['chart.title.color']) prop['chart.title.color'] = prop['chart.colors'][0];
  173. /**
  174. * Parse the colors. This allows for simple gradient syntax
  175. */
  176. if (!this.colorsParsed) {
  177. this.parseColors();
  178. // Don't want to do this again
  179. this.colorsParsed = true;
  180. }
  181. // DRAW Y AXIS HERE
  182. this.DrawYAxis();
  183. /**
  184. * This installs the event listeners
  185. */
  186. RG.InstallEventListeners(this);
  187. /**
  188. * Fire the ondraw event
  189. */
  190. RG.FireCustomEvent(this, 'ondraw');
  191. return this;
  192. }
  193. /**
  194. * The getObjectByXY() worker method
  195. */
  196. this.getObjectByXY = function (e)
  197. {
  198. if (this.getShape(e)) {
  199. return this;
  200. }
  201. }
  202. /**
  203. * Not used by the class during creating the axis, but is used by event handlers
  204. * to get the coordinates (if any) of the selected shape
  205. *
  206. * @param object e The event object
  207. */
  208. this.getShape = function (e)
  209. {
  210. var mouseXY = RG.getMouseXY(e);
  211. var mouseX = mouseXY[0];
  212. var mouseY = mouseXY[1];
  213. if ( mouseX >= this.x - (prop['chart.align'] == 'left' ? this.getWidth() : 0)
  214. && mouseX <= this.x + (prop['chart.align'] == 'left' ? 0 : this.getWidth())
  215. && mouseY >= this.gutterTop
  216. && mouseY <= (ca.height - this.gutterBottom)
  217. ) {
  218. var x = this.x;
  219. var y = this.gutterTop;
  220. var w = 15;;
  221. var h = ca.height - this.gutterTop - this.gutterBottom;
  222. return {
  223. 0: this, 1: x, 2: y, 3: w, 4: h, 5: 0,
  224. 'object': this, 'x': x, 'y': y, 'width': w, 'height': h, 'index': 0, 'tooltip': prop['chart.tooltips'] ? prop['chart.tooltips'][0] : null
  225. };
  226. }
  227. return null;
  228. }
  229. /**
  230. * This function positions a tooltip when it is displayed
  231. *
  232. * @param obj object The chart object
  233. * @param int x The X coordinate specified for the tooltip
  234. * @param int y The Y coordinate specified for the tooltip
  235. * @param objec tooltip The tooltips DIV element
  236. */
  237. this.positionTooltip = function (obj, x, y, tooltip, idx)
  238. {
  239. var coordW = prop['chart.text.size'] * 1.5;
  240. var coordX = obj.x - coordW;
  241. var coordY = obj.gutterTop;
  242. var coordH = ca.height - obj.gutterTop - obj.gutterBottom;
  243. var canvasXY = RG.getCanvasXY(ca);
  244. var width = tooltip.offsetWidth;
  245. var height = tooltip.offsetHeight;
  246. // Set the top position
  247. tooltip.style.left = 0;
  248. tooltip.style.top = canvasXY[1] + ((ca.height - this.gutterTop - this.gutterBottom) / 2) + 'px';
  249. // By default any overflow is hidden
  250. tooltip.style.overflow = '';
  251. // The arrow
  252. var img = new Image();
  253. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  254. img.style.position = 'absolute';
  255. img.id = '__rgraph_tooltip_pointer__';
  256. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  257. tooltip.appendChild(img);
  258. // Reposition the tooltip if at the edges:
  259. // LEFT edge
  260. if ((canvasXY[0] + coordX + (coordW / 2) - (width / 2)) < 10) {
  261. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + (coordW / 2) + 'px';
  262. img.style.left = ((width * 0.1) - 8.5) + 'px';
  263. // RIGHT edge
  264. } else if ((canvasXY[0] + coordX + (width / 2)) > document.body.offsetWidth) {
  265. tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + (coordW / 2) + 'px';
  266. img.style.left = ((width * 0.9) - 8.5) + 'px';
  267. // Default positioning - CENTERED
  268. } else {
  269. tooltip.style.left = (canvasXY[0] + coordX + (coordW / 2) - (width * 0.5)) + 'px';
  270. img.style.left = ((width * 0.5) - 8.5) + 'px';
  271. }
  272. }
  273. /**
  274. * Each object type has its own Highlight() function which highlights the appropriate shape
  275. *
  276. * @param object shape The shape to highlight
  277. */
  278. this.Highlight = function (shape)
  279. {
  280. // When showing tooltips, this method can be used to highlight the X axis
  281. }
  282. /**
  283. * This allows for easy specification of gradients
  284. */
  285. this.parseColors = function ()
  286. {
  287. /**
  288. * Parse various properties for colors
  289. */
  290. //prop['chart.title.color'] = this.parseSingleColorForGradient(prop['chart.title.color']);
  291. //prop['chart.text.color'] = this.parseSingleColorForGradient(prop['chart.text.color']);
  292. prop['chart.colors'][0] = this.parseSingleColorForGradient(prop['chart.colors'][0]);
  293. }
  294. /**
  295. * This parses a single color value
  296. */
  297. this.parseSingleColorForGradient = function (color)
  298. {
  299. if (!color) {
  300. return color;
  301. }
  302. if (color.match(/^gradient\((.*)\)$/i)) {
  303. var parts = RegExp.$1.split(':');
  304. // Create the gradient
  305. var grad = co.createLinearGradient(0,prop['chart.gutter.top'],0,ca.height - this.gutterBottom);
  306. var diff = 1 / (parts.length - 1);
  307. grad.addColorStop(0, RG.trim(parts[0]));
  308. for (var j=1; j<parts.length; ++j) {
  309. grad.addColorStop(j * diff, RG.trim(parts[j]));
  310. }
  311. }
  312. return grad ? grad : color;
  313. }
  314. /**
  315. * The function that draws the Y axis
  316. */
  317. this.DrawYAxis = function ()
  318. {
  319. /**
  320. * Allow both axis.xxx and chart.xxx to prevent any confusion that may arise
  321. */
  322. for (i in prop) {
  323. if (typeof(i) == 'string') {
  324. var key = i.replace(/^chart\./, 'axis.');
  325. prop[key] = prop[i];
  326. }
  327. }
  328. var x = this.x;
  329. var y = this.gutterTop;
  330. var height = ca.height - this.gutterBottom - this.gutterTop;
  331. var min = prop['chart.min'] ? prop['chart.min'] : 0;
  332. var max = prop['chart.max'];
  333. var title = prop['chart.title'] ? prop['chart.title'] : '';
  334. var color = prop['chart.colors'] ? prop['chart.colors'][0] : 'black';
  335. var title_color = prop['chart.title.color'] ? prop['chart.title.color'] : color;
  336. var label_color = prop['chart.text.color'] ? prop['chart.text.color'] : color;
  337. var numticks = typeof(prop['chart.numticks']) == 'number' ? prop['chart.numticks'] : 10;
  338. var labels_specific = prop['chart.labels.specific'];
  339. var numlabels = prop['chart.numlabels'] ? prop['chart.numlabels'] : 5;
  340. var font = prop['chart.text.font'] ? prop['chart.text.font'] : 'Arial';
  341. var size = prop['chart.text.size'] ? prop['chart.text.size'] : 10;
  342. var align = typeof(prop['chart.align']) == 'string'? prop['chart.align'] : 'left';
  343. var formatter = prop['chart.scale.formatter'];
  344. var decimals = prop['chart.scale.decimals'];
  345. var invert = prop['chart.scale.invert'];
  346. var scale_visible = prop['chart.scale.visible'];
  347. var units_pre = prop['chart.units.pre'];
  348. var units_post = prop['chart.units.post'];
  349. var linewidth = prop['chart.linewidth'] ? prop['chart.linewidth'] : 1;
  350. var notopendtick = prop['chart.noendtick.top'];
  351. var nobottomendtick = prop['chart.noendtick.bottom'];
  352. var noyaxis = prop['chart.noyaxis'];
  353. var xaxispos = prop['chart.xaxispos'];
  354. // This fixes missing corner pixels in Chrome
  355. co.lineWidth = linewidth + 0.001;
  356. /**
  357. * Set the color
  358. */
  359. co.strokeStyle = color;
  360. if (!noyaxis) {
  361. /**
  362. * Draw the main vertical line
  363. */
  364. co.beginPath();
  365. co.moveTo(Math.round(x), y);
  366. co.lineTo(Math.round(x), y + height);
  367. co.stroke();
  368. /**
  369. * Draw the axes tickmarks
  370. */
  371. if (numticks) {
  372. var gap = (xaxispos == 'center' ? height / 2 : height) / numticks;
  373. var halfheight = height / 2;
  374. co.beginPath();
  375. for (var i=(notopendtick ? 1 : 0); i<=(numticks - (nobottomendtick || xaxispos == 'center'? 1 : 0)); ++i) {
  376. co.moveTo(align == 'right' ? x + 3 : x - 3, Math.round(y + (gap *i)));
  377. co.lineTo(x, Math.round(y + (gap *i)));
  378. }
  379. // Draw the bottom halves ticks if the X axis is in the center
  380. if (xaxispos == 'center') {
  381. for (var i=1; i<=numticks; ++i) {
  382. co.moveTo(align == 'right' ? x + 3 : x - 3, Math.round(y + halfheight + (gap *i)));
  383. co.lineTo(x, Math.round(y + halfheight + (gap *i)));
  384. }
  385. }
  386. co.stroke();
  387. }
  388. }
  389. /**
  390. * Draw the scale for the axes
  391. */
  392. co.fillStyle = label_color;
  393. //co.beginPath();
  394. var text_len = 0;
  395. if (scale_visible) {
  396. if (labels_specific && labels_specific.length) {
  397. var text_len = 0;
  398. // First - gp through the labels to find the longest
  399. for (var i=0,len=labels_specific.length; i<len; i+=1) {
  400. text_len = Math.max(text_len, co.measureText(labels_specific[i]).width);
  401. }
  402. for (var i=0,len=labels_specific.length; i<len; ++i) {
  403. var gap = (len-1) > 0 ? (height / (len-1)) : 0;
  404. if (xaxispos == 'center') {
  405. gap /= 2;
  406. }
  407. RG.Text2(this, {'font':font,
  408. 'size':size,
  409. 'x':x - (align == 'right' ? -5 : 5),
  410. 'y':(i * gap) + this.gutterTop,
  411. 'text':labels_specific[i],
  412. 'valign':'center',
  413. 'halign':align == 'right' ? 'left' : 'right',
  414. 'tag': 'scale'
  415. });
  416. }
  417. if (xaxispos == 'center') {
  418. // It's "-2" so that the center label isn't added twice
  419. for (var i=(labels_specific.length-2); i>=0; --i) {
  420. RG.Text2(this, {'font':font,
  421. 'size':size,
  422. 'x':x - (align == 'right' ? -5 : 5),
  423. 'y':ca.height - this.gutterBottom - (i * gap),
  424. 'text':labels_specific[i],
  425. 'valign':'center',
  426. 'halign':align == 'right' ? 'left' : 'right',
  427. 'tag': 'scale'
  428. });
  429. }
  430. }
  431. } else {
  432. for (var i=0; i<=numlabels; ++i) {
  433. var original = ((max - min) * ((numlabels-i) / numlabels)) + min;
  434. if (original == 0 && prop['chart.scale.zerostart'] == false) {
  435. continue;
  436. }
  437. var text = RG.number_format(this, original.toFixed(decimals), units_pre, units_post);
  438. var text = String(typeof(formatter) == 'function' ? formatter(this, original) : text);
  439. // text_len is used below for positioning the title
  440. var text_len = Math.max(text_len, co.measureText(text).width);
  441. if (invert) {
  442. var y = height - ((height / numlabels)*i);
  443. } else {
  444. var y = (height / numlabels)*i;
  445. }
  446. if (prop['chart.xaxispos'] == 'center') {
  447. y = y / 2;
  448. }
  449. /**
  450. * Now - draw the labels
  451. */
  452. RG.Text2(this, {'font':font,
  453. 'size':size,
  454. 'x':x - (align == 'right' ? -5 : 5),
  455. 'y':y + this.gutterTop,
  456. 'text':text,
  457. 'valign':'center',
  458. 'halign':align == 'right' ? 'left' : 'right',
  459. 'tag': 'scale'
  460. });
  461. /**
  462. * Draw the bottom half of the labels if the X axis is in the center
  463. */
  464. if (prop['chart.xaxispos'] == 'center' && i < numlabels) {
  465. RG.Text2(this, {'font':font,
  466. 'size':size,
  467. 'x':x - (align == 'right' ? -5 : 5),
  468. 'y':ca.height - this.gutterBottom - y,
  469. 'text':'-' + text,
  470. 'valign':'center',
  471. 'halign':align == 'right' ? 'left' : 'right',
  472. 'tag': 'scale'
  473. });
  474. }
  475. }
  476. }
  477. }
  478. //co.stroke();
  479. /**
  480. * Draw the title for the axes
  481. */
  482. if (title) {
  483. co.beginPath();
  484. co.fillStyle = title_color;
  485. if (labels_specific) {
  486. var width = 0;
  487. for (var i=0,len=labels_specific.length; i<len; i+=1) {
  488. width = Math.max(width, co.measureText(labels_specific[i]).width);
  489. }
  490. } else {
  491. var width = co.measureText(prop['chart.units.pre'] + prop['chart.max'].toFixed(prop['chart.scale.decimals']) + prop['chart.units.post']).width;
  492. }
  493. RG.Text2(this, {'font':font,
  494. 'size':size + 2,
  495. 'x':x - align == 'right' ? x + width + 5 : x - width - 5,
  496. 'y':height / 2 + this.gutterTop,
  497. 'text':title,
  498. 'valign':'bottom',
  499. 'halign':'center',
  500. 'angle':align == 'right' ? 90 : -90});
  501. co.stroke();
  502. }
  503. }
  504. /**
  505. * This detemines the maximum text width of either the scale or text
  506. * labels - whichever is given
  507. *
  508. * @return number The maximum text width
  509. */
  510. this.getWidth = function ()
  511. {
  512. var width = co.measureText(prop['chart.max']).width
  513. // Add the title width if it's specified
  514. if (prop['chart.title'] && prop['chart.title'].length) {
  515. width += (prop['chart.text.size'] * 1.5);
  516. }
  517. this.width = width;
  518. return width;
  519. }
  520. /**
  521. * Objects are now always registered so that the chart is redrawn if need be.
  522. */
  523. RG.Register(this);
  524. }