RGraph.rscatter.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  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.txt |
  12. * o------------------------------------------------------------------------------o
  13. */
  14. if (typeof(RGraph) == 'undefined') RGraph = {};
  15. /**
  16. * The chart constuctor
  17. *
  18. * @param object canvas
  19. * @param array data
  20. */
  21. RGraph.RScatter =
  22. RGraph.Rscatter = function (id)
  23. {
  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 = 'rscatter';
  29. this.hasTooltips = false;
  30. this.isRGraph = true;
  31. this.uid = RGraph.CreateUID();
  32. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  33. this.colorsParsed = false;
  34. this.coordsText = [];
  35. /**
  36. * Compatibility with older browsers
  37. */
  38. RGraph.OldBrowserCompat(this.context);
  39. this.centerx = 0;
  40. this.centery = 0;
  41. this.radius = 0;
  42. this.max = 0;
  43. this.properties = {
  44. 'chart.radius': null,
  45. 'chart.colors': [], // This is used internally for the key
  46. 'chart.colors.default': 'black',
  47. 'chart.gutter.left': 25,
  48. 'chart.gutter.right': 25,
  49. 'chart.gutter.top': 25,
  50. 'chart.gutter.bottom': 25,
  51. 'chart.title': '',
  52. 'chart.title.background': null,
  53. 'chart.title.hpos': null,
  54. 'chart.title.vpos': null,
  55. 'chart.title.bold': true,
  56. 'chart.title.font': null,
  57. 'chart.title.x': null,
  58. 'chart.title.y': null,
  59. 'chart.title.halign': null,
  60. 'chart.title.valign': null,
  61. 'chart.labels': null,
  62. 'chart.labels.position': 'center',
  63. 'chart.labels.axes': 'nsew',
  64. 'chart.text.color': 'black',
  65. 'chart.text.font': 'Arial',
  66. 'chart.text.size': 10,
  67. 'chart.key': null,
  68. 'chart.key.background': 'white',
  69. 'chart.key.position': 'graph',
  70. 'chart.key.halign': 'right',
  71. 'chart.key.shadow': false,
  72. 'chart.key.shadow.color': '#666',
  73. 'chart.key.shadow.blur': 3,
  74. 'chart.key.shadow.offsetx': 2,
  75. 'chart.key.shadow.offsety': 2,
  76. 'chart.key.position.gutter.boxed':false,
  77. 'chart.key.position.x': null,
  78. 'chart.key.position.y': null,
  79. 'chart.key.color.shape': 'square',
  80. 'chart.key.rounded': true,
  81. 'chart.key.linewidth': 1,
  82. 'chart.key.colors': null,
  83. 'chart.key.interactive': false,
  84. 'chart.key.interactive.highlight.chart.fill':'rgba(255,0,0,0.9)',
  85. 'chart.key.interactive.highlight.label':'rgba(255,0,0,0.2)',
  86. 'chart.key.text.color': 'black',
  87. 'chart.contextmenu': null,
  88. 'chart.tooltips': null,
  89. 'chart.tooltips.event': 'onmousemove',
  90. 'chart.tooltips.effect': 'fade',
  91. 'chart.tooltips.css.class': 'RGraph_tooltip',
  92. 'chart.tooltips.highlight': true,
  93. 'chart.tooltips.hotspot': 3,
  94. 'chart.tooltips.coords.page': false,
  95. 'chart.annotatable': false,
  96. 'chart.annotate.color': 'black',
  97. 'chart.zoom.factor': 1.5,
  98. 'chart.zoom.fade.in': true,
  99. 'chart.zoom.fade.out': true,
  100. 'chart.zoom.hdir': 'right',
  101. 'chart.zoom.vdir': 'down',
  102. 'chart.zoom.frames': 25,
  103. 'chart.zoom.delay': 16.666,
  104. 'chart.zoom.shadow': true,
  105. 'chart.zoom.background': true,
  106. 'chart.zoom.action': 'zoom',
  107. 'chart.resizable': false,
  108. 'chart.resize.handle.background': null,
  109. 'chart.ymax': null,
  110. 'chart.ymin': 0,
  111. 'chart.tickmarks': 'cross',
  112. 'chart.ticksize': 3,
  113. 'chart.scale.decimals': null,
  114. 'chart.scale.point': '.',
  115. 'chart.scale.thousand': ',',
  116. 'chart.scale.round': false,
  117. 'chart.units.pre': '',
  118. 'chart.units.post': '',
  119. 'chart.events.mousemove': null,
  120. 'chart.events.click': null,
  121. 'chart.highlight.stroke': 'transparent',
  122. 'chart.highlight.fill': 'rgba(255,255,255,0.7)',
  123. 'chart.highlight.point.radius': 3,
  124. 'chart.labels.count': 5
  125. }
  126. this.data = [];
  127. // Handle multiple datasets being given as one argument
  128. if (arguments[1][0] && arguments[1][0][0] && typeof(arguments[1][0][0]) == 'object') {
  129. // Store the data set(s)
  130. for (var i=0; i<arguments[1].length; ++i) {
  131. this.data[i] = arguments[1][i];
  132. }
  133. // Handle multiple data sets being supplied as seperate arguments
  134. } else {
  135. // Store the data set(s)
  136. for (var i=1; i<arguments.length; ++i) {
  137. this.data[i - 1] = RGraph.array_clone(arguments[i]);
  138. }
  139. }
  140. /**
  141. * Create the $ objects so that functions can be added to them
  142. */
  143. for (var i=0,idx=0; i<this.data.length; ++i) {
  144. for (var j=0,len=this.data[i].length; j<len; j+=1,idx+=1) {
  145. this['$' + idx] = {}
  146. }
  147. }
  148. /**
  149. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  150. * done already
  151. */
  152. if (!this.canvas.__rgraph_aa_translated__) {
  153. this.context.translate(0.5,0.5);
  154. this.canvas.__rgraph_aa_translated__ = true;
  155. }
  156. ///////////////////////////////// SHORT PROPERTIES /////////////////////////////////
  157. var RG = RGraph;
  158. var ca = this.canvas;
  159. var co = ca.getContext('2d');
  160. var prop = this.properties;
  161. //////////////////////////////////// METHODS ///////////////////////////////////////
  162. /**
  163. * A simple setter
  164. *
  165. * @param string name The name of the property to set
  166. * @param string value The value of the property
  167. */
  168. this.Set = function (name, value)
  169. {
  170. /**
  171. * This should be done first - prepend the property name with "chart." if necessary
  172. */
  173. if (name.substr(0,6) != 'chart.') {
  174. name = 'chart.' + name;
  175. }
  176. prop[name.toLowerCase()] = value;
  177. return this;
  178. }
  179. /**
  180. * A simple getter
  181. *
  182. * @param string name 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. return prop[name.toLowerCase()];
  193. }
  194. /**
  195. * This method draws the rose chart
  196. */
  197. this.Draw = function ()
  198. {
  199. /**
  200. * Fire the onbeforedraw event
  201. */
  202. RG.FireCustomEvent(this, 'onbeforedraw');
  203. /**
  204. * This doesn't affect the chart, but is used for compatibility
  205. */
  206. this.gutterLeft = prop['chart.gutter.left'];
  207. this.gutterRight = prop['chart.gutter.right'];
  208. this.gutterTop = prop['chart.gutter.top'];
  209. this.gutterBottom = prop['chart.gutter.bottom'];
  210. // Calculate the radius
  211. this.radius = (Math.min(ca.width - this.gutterLeft - this.gutterRight, ca.height - this.gutterTop - this.gutterBottom) / 2);
  212. this.centerx = ((ca.width - this.gutterLeft - this.gutterRight) / 2) + this.gutterLeft;
  213. this.centery = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop;
  214. this.coords = [];
  215. this.coords2 = []
  216. /**
  217. * If there's a user specified radius/centerx/centery, use them
  218. */
  219. if (typeof(prop['chart.centerx']) == 'number') this.centerx = prop['chart.centerx'];
  220. if (typeof(prop['chart.centery']) == 'number') this.centery = prop['chart.centery'];
  221. if (typeof(prop['chart.radius']) == 'number') this.radius = prop['chart.radius'];
  222. /**
  223. * Parse the colors for gradients. Its down here so that the center X/Y can be used
  224. */
  225. if (!this.colorsParsed) {
  226. this.parseColors();
  227. // Don't want to do this again
  228. this.colorsParsed = true;
  229. }
  230. /**
  231. * Work out the scale
  232. */
  233. var max = prop['chart.ymax'];
  234. var min = prop['chart.ymin'];
  235. if (typeof(max) == 'number') {
  236. this.max = max;
  237. this.scale2 = RG.getScale2(this, {'max':max,
  238. 'min':min,
  239. 'strict':true,
  240. 'scale.decimals':Number(prop['chart.scale.decimals']),
  241. 'scale.point':prop['chart.scale.point'],
  242. 'scale.thousand':prop['chart.scale.thousand'],
  243. 'scale.round':prop['chart.scale.round'],
  244. 'units.pre':prop['chart.units.pre'],
  245. 'units.post':prop['chart.units.post'],
  246. 'ylabels.count':prop['chart.labels.count']
  247. });
  248. } else {
  249. for (var i=0; i<this.data.length; i+=1) {
  250. for (var j=0,len=this.data[i].length; j<len; j+=1) {
  251. this.max = Math.max(this.max, this.data[i][j][1]);
  252. }
  253. }
  254. this.min = prop['chart.ymin'];
  255. this.scale2 = RG.getScale2(this, {'max':this.max,
  256. 'min':min,
  257. 'scale.decimals':Number(prop['chart.scale.decimals']),
  258. 'scale.point':prop['chart.scale.point'],
  259. 'scale.thousand':prop['chart.scale.thousand'],
  260. 'scale.round':prop['chart.scale.round'],
  261. 'units.pre':prop['chart.units.pre'],
  262. 'units.post':prop['chart.units.post'],
  263. 'ylabels.count':prop['chart.labels.count']
  264. });
  265. this.max = this.scale2.max;
  266. }
  267. /**
  268. * Change the centerx marginally if the key is defined
  269. */
  270. if (prop['chart.key'] && prop['chart.key'].length > 0 && prop['chart.key'].length >= 3) {
  271. this.centerx = this.centerx - prop['chart.gutter.right'] + 5;
  272. }
  273. /**
  274. * Populate the colors array for the purposes of generating the key
  275. */
  276. if (typeof(prop['chart.key']) == 'object' && RG.is_array(prop['chart.key']) && prop['chart.key'][0]) {
  277. // Reset the colors array
  278. prop['chart.colors'] = [];
  279. for (var i=0; i<this.data.length; i+=1) {
  280. for (var j=0,len=this.data[i].length; j<len; j+=1) {
  281. if (typeof this.data[i][j][2] == 'string') {
  282. prop['chart.colors'].push(this.data[i][j][2]);
  283. }
  284. }
  285. }
  286. }
  287. /**
  288. * Populate the chart.tooltips array
  289. */
  290. this.Set('chart.tooltips', []);
  291. for (var i=0; i<this.data.length; i+=1) {
  292. for (var j=0,len=this.data[i].length; j<len; j+=1) {
  293. if (typeof this.data[i][j][3] == 'string') {
  294. prop['chart.tooltips'].push(this.data[i][j][3]);
  295. }
  296. }
  297. }
  298. // This resets the chart drawing state
  299. co.beginPath();
  300. this.DrawBackground();
  301. this.DrawRscatter();
  302. this.DrawLabels();
  303. /**
  304. * Setup the context menu if required
  305. */
  306. if (prop['chart.contextmenu']) {
  307. RG.ShowContext(this);
  308. }
  309. // Draw the title if any has been set
  310. if (prop['chart.title']) {
  311. RG.DrawTitle(this,
  312. prop['chart.title'],
  313. this.centery - this.radius - 10,
  314. this.centerx,
  315. prop['chart.title.size'] ? prop['chart.title.size'] : prop['chart.text.size'] + 2);
  316. }
  317. /**
  318. * This function enables resizing
  319. */
  320. if (prop['chart.resizable']) {
  321. RG.AllowResizing(this);
  322. }
  323. /**
  324. * This installs the event listeners
  325. */
  326. RG.InstallEventListeners(this);
  327. /**
  328. * Fire the RGraph ondraw event
  329. */
  330. RG.FireCustomEvent(this, 'ondraw');
  331. return this;
  332. }
  333. /**
  334. * This method draws the rose charts background
  335. */
  336. this.DrawBackground = function ()
  337. {
  338. co.lineWidth = 1;
  339. // Draw the background grey circles
  340. co.strokeStyle = '#ccc';
  341. // Radius must be greater than 0 for Opera to work
  342. var r = this.radius / 10;
  343. for (var i=0,len=this.radius; i<=len; i+=r) {
  344. //co.moveTo(this.centerx + i, this.centery);
  345. // Radius must be greater than 0 for Opera to work
  346. co.arc(this.centerx, this.centery, i, 0, TWOPI, 0);
  347. }
  348. co.stroke();
  349. // Draw the background lines that go from the center outwards
  350. co.beginPath();
  351. for (var i=15; i<360; i+=15) {
  352. // Radius must be greater than 0 for Opera to work
  353. co.arc(this.centerx, this.centery, this.radius, i / (180 / PI), (i + 0.01) / (180 / PI), 0);
  354. co.lineTo(this.centerx, this.centery);
  355. }
  356. co.stroke();
  357. co.beginPath();
  358. co.strokeStyle = 'black';
  359. // Draw the X axis
  360. co.moveTo(this.centerx - this.radius, Math.round(this.centery));
  361. co.lineTo(this.centerx + this.radius, Math.round(this.centery));
  362. // Draw the X ends
  363. co.moveTo(Math.round(this.centerx - this.radius), this.centery - 5);
  364. co.lineTo(Math.round(this.centerx - this.radius), this.centery + 5);
  365. co.moveTo(Math.round(this.centerx + this.radius), this.centery - 5);
  366. co.lineTo(Math.round(this.centerx + this.radius), this.centery + 5);
  367. // Draw the X check marks
  368. for (var i=(this.centerx - this.radius); i<(this.centerx + this.radius); i+=(this.radius / 10)) {
  369. co.moveTo(Math.round(i), this.centery - 3);
  370. co.lineTo(Math.round(i), this.centery + 3);
  371. }
  372. // Draw the Y check marks
  373. for (var i=(this.centery - this.radius); i<(this.centery + this.radius); i+=(this.radius / 10)) {
  374. co.moveTo(this.centerx - 3, Math.round(i));
  375. co.lineTo(this.centerx + 3, Math.round(i));
  376. }
  377. // Draw the Y axis
  378. co.moveTo(Math.round(this.centerx), this.centery - this.radius);
  379. co.lineTo(Math.round(this.centerx), this.centery + this.radius);
  380. // Draw the Y ends
  381. co.moveTo(this.centerx - 5, Math.round(this.centery - this.radius));
  382. co.lineTo(this.centerx + 5, Math.round(this.centery - this.radius));
  383. co.moveTo(this.centerx - 5, Math.round(this.centery + this.radius));
  384. co.lineTo(this.centerx + 5, Math.round(this.centery + this.radius));
  385. // Stroke it
  386. co.closePath();
  387. co.stroke();
  388. }
  389. /**
  390. * This method draws a set of data on the graph
  391. */
  392. this.DrawRscatter = function ()
  393. {
  394. for (var dataset=0; dataset<this.data.length; dataset+=1) {
  395. var data = this.data[dataset];
  396. this.coords2[dataset] = [];
  397. for (var i=0; i<data.length; ++i) {
  398. var d1 = data[i][0];
  399. var d2 = data[i][1];
  400. var a = d1 / (180 / PI); // RADIANS
  401. var r = ( (d2 - prop['chart.ymin']) / (this.scale2.max - this.scale2.min) ) * this.radius;
  402. var x = Math.sin(a) * r;
  403. var y = Math.cos(a) * r;
  404. var color = data[i][2] ? data[i][2] : prop['chart.colors.default'];
  405. var tooltip = data[i][3] ? data[i][3] : null;
  406. if (tooltip && String(tooltip).length) {
  407. this.hasTooltips = true;
  408. }
  409. /**
  410. * Account for the correct quadrant
  411. */
  412. x = x + this.centerx;
  413. y = this.centery - y;
  414. this.DrawTick(x, y, color);
  415. // Populate the coords array with the coordinates and the tooltip
  416. this.coords.push([x, y, color, tooltip]);
  417. this.coords2[dataset].push([x, y, color, tooltip]);
  418. }
  419. }
  420. }
  421. /**
  422. * Unsuprisingly, draws the labels
  423. */
  424. this.DrawLabels = function ()
  425. {
  426. co.lineWidth = 1;
  427. // Set the color to black
  428. co.fillStyle = 'black';
  429. co.strokeStyle = 'black';
  430. var key = prop['chart.key'];
  431. var r = this.radius;
  432. var color = prop['chart.text.color'];
  433. var font = prop['chart.text.font'];
  434. var size = prop['chart.text.size'];
  435. var axes = prop['chart.labels.axes'].toLowerCase();
  436. var units_pre = prop['chart.units.pre'];
  437. var units_post = prop['chart.units.post'];
  438. var decimals = prop['chart.scale.decimals'];
  439. var centerx = this.centerx;
  440. var centery = this.centery;
  441. co.fillStyle = prop['chart.text.color'];
  442. // Draw any labels
  443. if (typeof(prop['chart.labels']) == 'object' && prop['chart.labels']) {
  444. this.DrawCircularLabels(co, prop['chart.labels'], font , size, r);
  445. }
  446. var color = 'rgba(255,255,255,0.8)';
  447. // Draw the axis labels
  448. for (var i=0,len=this.scale2.labels.length; i<len; ++i) {
  449. if (axes.indexOf('n') > -1) RG.Text2(this, {'tag': 'scale','font':font,'size':size,'x':centerx,'y':centery - (r * ((i+1) / len)),'text':this.scale2.labels[i],'valign':'center','halign':'center','bounding':true,'boundingFill':color});
  450. if (axes.indexOf('s') > -1) RG.Text2(this, {'tag': 'scale','font':font,'size':size,'x':centerx,'y':centery + (r * ((i+1) / len)),'text':this.scale2.labels[i],'valign':'center','halign':'center','bounding':true,'boundingFill':color});
  451. if (axes.indexOf('e') > -1) RG.Text2(this, {'tag': 'scale','font':font,'size':size,'x':centerx + (r * ((i+1) / len)),'y':centery,'text':this.scale2.labels[i],'valign':'center','halign':'center','bounding':true,'boundingFill':color});
  452. if (axes.indexOf('w') > -1) RG.Text2(this, {'tag': 'scale','font':font,'size':size,'x':centerx - (r * ((i+1) / len)),'y':centery,'text':this.scale2.labels[i],'valign':'center','halign':'center','bounding':true,'boundingFill':color});
  453. }
  454. // Draw the center minimum value (but only if there's at least one axes labels stipulated)
  455. if (prop['chart.labels.axes'].length > 0) {
  456. RG.Text2(this, {'font':font,
  457. 'size':size,
  458. 'x':centerx,
  459. 'y':centery,
  460. 'text':RG.number_format(this, Number(this.scale2.min).toFixed(this.scale2.decimals), this.scale2.units_pre, this.scale2.units_post),
  461. 'valign':'center',
  462. 'halign':'center',
  463. 'bounding':true,
  464. 'boundingFill':color,
  465. 'tag': 'scale'
  466. });
  467. }
  468. /**
  469. * Draw the key
  470. */
  471. if (key && key.length) {
  472. RG.DrawKey(this, key, prop['chart.colors']);
  473. }
  474. }
  475. /**
  476. * Draws the circular labels that go around the charts
  477. *
  478. * @param labels array The labels that go around the chart
  479. */
  480. this.DrawCircularLabels = function (context, labels, font_face, font_size, r)
  481. {
  482. var position = prop['chart.labels.position'];
  483. var r = r + 10;
  484. for (var i=0; i<labels.length; ++i) {
  485. var a = (360 / labels.length) * (i + 1) - (360 / (labels.length * 2));
  486. var a = a - 90 + (prop['chart.labels.position'] == 'edge' ? ((360 / labels.length) / 2) : 0);
  487. var x = Math.cos(a / (180/PI) ) * (r + 10);
  488. var y = Math.sin(a / (180/PI)) * (r + 10);
  489. RG.Text2(this, {'font':font_face,
  490. 'size':font_size,
  491. 'x':this.centerx + x,
  492. 'y':this.centery + y,
  493. 'text':String(labels[i]),
  494. 'valign':'center',
  495. 'halign':'center',
  496. 'tag': 'labels'
  497. });
  498. }
  499. }
  500. /**
  501. * Draws a single tickmark
  502. */
  503. this.DrawTick = function (x, y, color)
  504. {
  505. var tickmarks = prop['chart.tickmarks'];
  506. var ticksize = prop['chart.ticksize'];
  507. co.strokeStyle = color;
  508. co.fillStyle = color;
  509. // Cross
  510. if (tickmarks == 'cross') {
  511. co.beginPath();
  512. co.moveTo(x + ticksize, y + ticksize);
  513. co.lineTo(x - ticksize, y - ticksize);
  514. co.stroke();
  515. co.beginPath();
  516. co.moveTo(x - ticksize, y + ticksize);
  517. co.lineTo(x + ticksize, y - ticksize);
  518. co.stroke();
  519. // Circle
  520. } else if (tickmarks == 'circle') {
  521. co.beginPath();
  522. co.arc(x, y, ticksize, 0, 6.2830, false);
  523. co.fill();
  524. // Square
  525. } else if (tickmarks == 'square') {
  526. co.beginPath();
  527. co.fillRect(x - ticksize, y - ticksize, 2 * ticksize, 2 * ticksize);
  528. co.fill();
  529. // Diamond shape tickmarks
  530. } else if (tickmarks == 'diamond') {
  531. co.beginPath();
  532. co.moveTo(x, y - ticksize);
  533. co.lineTo(x + ticksize, y);
  534. co.lineTo(x, y + ticksize);
  535. co.lineTo(x - ticksize, y);
  536. co.closePath();
  537. co.fill();
  538. // Plus style tickmarks
  539. } else if (tickmarks == 'plus') {
  540. co.lineWidth = 1;
  541. co.beginPath();
  542. co.moveTo(x, y - ticksize);
  543. co.lineTo(x, y + ticksize);
  544. co.moveTo(x - ticksize, y);
  545. co.lineTo(x + ticksize, y);
  546. co.stroke();
  547. }
  548. }
  549. /**
  550. * This function makes it much easier to get the (if any) point that is currently being hovered over.
  551. *
  552. * @param object e The event object
  553. */
  554. this.getShape =
  555. this.getPoint = function (e)
  556. {
  557. var mouseXY = RG.getMouseXY(e);
  558. var mouseX = mouseXY[0];
  559. var mouseY = mouseXY[1];
  560. var overHotspot = false;
  561. var offset = prop['chart.tooltips.hotspot']; // This is how far the hotspot extends
  562. for (var i=0,len=this.coords.length; i<len; ++i) {
  563. var x = this.coords[i][0];
  564. var y = this.coords[i][1];
  565. var tooltip = this.coords[i][3];
  566. if (
  567. mouseX < (x + offset) &&
  568. mouseX > (x - offset) &&
  569. mouseY < (y + offset) &&
  570. mouseY > (y - offset)
  571. ) {
  572. var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);
  573. return {0:this,1:x,2:y,3:i,'object':this, 'x':x, 'y':y, 'index':i, 'tooltip': tooltip};
  574. }
  575. }
  576. }
  577. /**
  578. * This function facilitates the installation of tooltip event listeners if
  579. * tooltips are defined.
  580. */
  581. this.AllowTooltips = function ()
  582. {
  583. // Preload any tooltip images that are used in the tooltips
  584. RG.PreLoadTooltipImages(this);
  585. /**
  586. * This installs the window mousedown event listener that lears any
  587. * highlight that may be visible.
  588. */
  589. RG.InstallWindowMousedownTooltipListener(this);
  590. /**
  591. * This installs the canvas mousemove event listener. This function
  592. * controls the pointer shape.
  593. */
  594. RG.InstallCanvasMousemoveTooltipListener(this);
  595. /**
  596. * This installs the canvas mouseup event listener. This is the
  597. * function that actually shows the appropriate tooltip (if any).
  598. */
  599. RG.InstallCanvasMouseupTooltipListener(this);
  600. }
  601. /**
  602. * Each object type has its own Highlight() function which highlights the appropriate shape
  603. *
  604. * @param object shape The shape to highlight
  605. */
  606. this.Highlight = function (shape)
  607. {
  608. // Add the new highlight
  609. RG.Highlight.Point(this, shape);
  610. }
  611. /**
  612. * The getObjectByXY() worker method. Don't call this call:
  613. *
  614. * RGraph.ObjectRegistry.getObjectByXY(e)
  615. *
  616. * @param object e The event object
  617. */
  618. this.getObjectByXY = function (e)
  619. {
  620. var mouseXY = RG.getMouseXY(e);
  621. if (
  622. mouseXY[0] > (this.centerx - this.radius)
  623. && mouseXY[0] < (this.centerx + this.radius)
  624. && mouseXY[1] > (this.centery - this.radius)
  625. && mouseXY[1] < (this.centery + this.radius)
  626. ) {
  627. return this;
  628. }
  629. }
  630. /**
  631. * This function positions a tooltip when it is displayed
  632. *
  633. * @param obj object The chart object
  634. * @param int x The X coordinate specified for the tooltip
  635. * @param int y The Y coordinate specified for the tooltip
  636. * @param objec tooltip The tooltips DIV element
  637. */
  638. this.positionTooltip = function (obj, x, y, tooltip, idx)
  639. {
  640. var coordX = obj.coords[tooltip.__index__][0];
  641. var coordY = obj.coords[tooltip.__index__][1];
  642. var canvasXY = RG.getCanvasXY(obj.canvas);
  643. var gutterLeft = obj.gutterLeft;
  644. var gutterTop = obj.gutterTop;
  645. var width = tooltip.offsetWidth;
  646. // Set the top position
  647. tooltip.style.left = 0;
  648. tooltip.style.top = parseInt(tooltip.style.top) - 7 + 'px';
  649. // By default any overflow is hidden
  650. tooltip.style.overflow = '';
  651. // The arrow
  652. var img = new Image();
  653. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  654. img.style.position = 'absolute';
  655. img.id = '__rgraph_tooltip_pointer__';
  656. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  657. tooltip.appendChild(img);
  658. // Reposition the tooltip if at the edges:
  659. // LEFT edge
  660. if ((canvasXY[0] + coordX - (width / 2)) < 10) {
  661. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + 'px';
  662. img.style.left = ((width * 0.1) - 8.5) + 'px';
  663. // RIGHT edge
  664. } else if ((canvasXY[0] + coordX + (width / 2)) > document.body.offsetWidth) {
  665. tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + 'px';
  666. img.style.left = ((width * 0.9) - 8.5) + 'px';
  667. // Default positioning - CENTERED
  668. } else {
  669. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
  670. img.style.left = ((width * 0.5) - 8.5) + 'px';
  671. }
  672. }
  673. /**
  674. * This function returns the radius (ie the distance from the center) for a particular
  675. * value.
  676. *
  677. * @param number value The value you want the radius for
  678. */
  679. this.getRadius = function (value)
  680. {
  681. if (value < 0 || value > this.max) {
  682. return null;
  683. }
  684. var r = (value / this.max) * this.radius;
  685. return r;
  686. }
  687. /**
  688. * This allows for easy specification of gradients
  689. */
  690. this.parseColors = function ()
  691. {
  692. // Go through the data
  693. for (var i=0; i<this.data.length; i+=1) {
  694. for (var j=0,len=this.data[i].length; j<len; j+=1) {
  695. this.data[i][j][2] = this.parseSingleColorForGradient(this.data[i][j][2]);
  696. }
  697. }
  698. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  699. prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
  700. prop['chart.colors.default'] = this.parseSingleColorForGradient(prop['chart.colors.default']);
  701. }
  702. /**
  703. * This parses a single color value
  704. */
  705. this.parseSingleColorForGradient = function (color)
  706. {
  707. if (!color || typeof(color) != 'string') {
  708. return color;
  709. }
  710. if (color.match(/^gradient\((.*)\)$/i)) {
  711. var parts = RegExp.$1.split(':');
  712. // Create the gradient
  713. var grad = co.createRadialGradient(this.centerx, this.centery, 0, this.centerx, this.centery, this.radius);
  714. var diff = 1 / (parts.length - 1);
  715. grad.addColorStop(0, RG.trim(parts[0]));
  716. for (var j=1; j<parts.length; ++j) {
  717. grad.addColorStop(j * diff, RG.trim(parts[j]));
  718. }
  719. }
  720. return grad ? grad : color;
  721. }
  722. /**
  723. * This function handles highlighting an entire data-series for the interactive
  724. * key
  725. *
  726. * @param int index The index of the data series to be highlighted
  727. */
  728. this.interactiveKeyHighlight = function (index)
  729. {
  730. if (this.coords2 && this.coords2[index] && this.coords2[index].length) {
  731. this.coords2[index].forEach(function (value, idx, arr)
  732. {
  733. co.beginPath();
  734. co.fillStyle = prop['chart.key.interactive.highlight.chart.fill'];
  735. co.arc(value[0], value[1], prop['chart.ticksize'] + 2, 0, TWOPI, false);
  736. co.fill();
  737. });
  738. }
  739. }
  740. /**
  741. * Register the object
  742. */
  743. RG.Register(this);
  744. }