RGraph.drawing.marker1.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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 data as the second. If you need to change this, you can.
  23. *
  24. * @param string id The canvas tag ID
  25. * @param number x The X position of the label
  26. * @param number y The Y position of the label
  27. * @param number text The text used - should be a single character (unless you've significantly increased
  28. * the size of the marker.
  29. */
  30. RGraph.Drawing.Marker1 = function (id, x, y, radius, text)
  31. {
  32. this.id = id;
  33. this.canvas = document.getElementById(typeof id === 'object' ? id.id : id);
  34. this.context = this.canvas.getContext ? this.canvas.getContext("2d") : null;
  35. this.colorsParsed = false;
  36. this.canvas.__object__ = this;
  37. /**
  38. * Store the properties
  39. */
  40. this.centerx = x;
  41. this.centery = y;
  42. this.radius = radius;
  43. this.text = text;
  44. /**
  45. * This defines the type of this shape
  46. */
  47. this.type = 'drawing.marker1';
  48. /**
  49. * This facilitates easy object identification, and should always be true
  50. */
  51. this.isRGraph = true;
  52. /**
  53. * This adds a uid to the object that you can use for identification purposes
  54. */
  55. this.uid = RGraph.CreateUID();
  56. /**
  57. * This adds a UID to the canvas for identification purposes
  58. */
  59. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  60. /**
  61. * This does a few things, for example adding the .fillText() method to the canvas 2D context when
  62. * it doesn't exist. This facilitates the graphs to be still shown in older browser (though without
  63. * text obviously). You'll find the function in RGraph.common.core.js
  64. */
  65. RGraph.OldBrowserCompat(this.context);
  66. /**
  67. * Some example background properties
  68. */
  69. this.properties =
  70. {
  71. 'chart.strokestyle': 'black',
  72. 'chart.fillstyle': 'white',
  73. 'chart.linewidth': 2,
  74. 'chart.text.color': 'black',
  75. 'chart.text.size': 12,
  76. 'chart.text.font': 'Arial',
  77. 'chart.events.click': null,
  78. 'chart.events.mousemove': null,
  79. 'chart.shadow': true,
  80. 'chart.shadow.color': '#aaa',
  81. 'chart.shadow.offsetx': 0,
  82. 'chart.shadow.offsety': 0,
  83. 'chart.shadow.blur': 15,
  84. 'chart.highlight.stroke': 'rgba(0,0,0,0)',
  85. 'chart.highlight.fill': 'rgba(255,255,255,0.7)',
  86. 'chart.tooltips': null,
  87. 'chart.tooltips.highlight': true,
  88. 'chart.tooltips.event': 'onclick',
  89. 'chart.align': 'center'
  90. }
  91. /**
  92. * A simple check that the browser has canvas support
  93. */
  94. if (!this.canvas) {
  95. alert('[DRAWING.MARKER1] No canvas support');
  96. return;
  97. }
  98. /**
  99. * Create the dollar object so that functions can be added to them
  100. */
  101. this.$0 = {};
  102. /**
  103. * Arrays that store the coordinates
  104. */
  105. this.coords = [];
  106. this.coordsText = [];
  107. /**
  108. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  109. * done already
  110. */
  111. if (!this.canvas.__rgraph_aa_translated__) {
  112. this.context.translate(0.5,0.5);
  113. this.canvas.__rgraph_aa_translated__ = true;
  114. }
  115. ///////////////////////////////// SHORT PROPERTIES /////////////////////////////////
  116. var RG = RGraph;
  117. var ca = this.canvas;
  118. var co = ca.getContext('2d');
  119. var prop = this.properties;
  120. //////////////////////////////////// METHODS ///////////////////////////////////////
  121. /**
  122. * A setter method for setting graph properties. It can be used like this: obj.Set('chart.strokestyle', '#666');
  123. *
  124. * @param name string The name of the property to set
  125. * @param value mixed The value of the property
  126. */
  127. this.Set = function (name, value)
  128. {
  129. name = name.toLowerCase();
  130. /**
  131. * This should be done first - prepend the propertyy name with "chart." if necessary
  132. */
  133. if (name.substr(0,6) != 'chart.') {
  134. name = 'chart.' + name;
  135. }
  136. prop[name] = value;
  137. return this;
  138. }
  139. /**
  140. * A getter method for retrieving graph properties. It can be used like this: obj.Get('chart.strokestyle');
  141. *
  142. * @param name string The name of the property to get
  143. */
  144. this.Get = function (name)
  145. {
  146. /**
  147. * This should be done first - prepend the property name with "chart." if necessary
  148. */
  149. if (name.substr(0,6) != 'chart.') {
  150. name = 'chart.' + name;
  151. }
  152. return prop[name.toLowerCase()];
  153. }
  154. /**
  155. * Draws the circle
  156. */
  157. this.Draw = function ()
  158. {
  159. /**
  160. * Fire the onbeforedraw event
  161. */
  162. RG.FireCustomEvent(this, 'onbeforedraw');
  163. var r = this.radius;
  164. if (prop['chart.align'] == 'left') {
  165. this.markerCenterx = this.centerx - r - r - 3;
  166. this.markerCentery = this.centery - r - r - 3;
  167. } else if (prop['chart.align'] == 'right') {
  168. this.markerCenterx = this.centerx + r + r + 3;
  169. this.markerCentery = this.centery - r - r - 3;
  170. } else {
  171. this.markerCenterx = this.centerx;
  172. this.markerCentery = this.centery - r - r - 3;
  173. }
  174. /**
  175. * Parse the colors. This allows for simple gradient syntax
  176. */
  177. if (!this.colorsParsed) {
  178. this.parseColors();
  179. // Don't want to do this again
  180. this.colorsParsed = true;
  181. }
  182. /**
  183. * DRAW THE MARKER HERE
  184. */
  185. co.beginPath();
  186. if (prop['chart.shadow']) {
  187. RG.SetShadow(this, prop['chart.shadow.color'], prop['chart.shadow.offsetx'], prop['chart.shadow.offsety'], prop['chart.shadow.blur']);
  188. }
  189. co.lineWidth = prop['chart.linewidth'];
  190. co.strokeStyle = prop['chart.strokestyle'];
  191. co.fillStyle = prop['chart.fillstyle'];
  192. // This function draws the actual marker
  193. this.DrawMarker();
  194. co.stroke();
  195. co.fill();
  196. // Turn the shadow off
  197. RG.NoShadow(this);
  198. // Now draw the text on the marker
  199. co.fillStyle = prop['chart.text.color'];
  200. // Draw the text on the marker
  201. RG.Text2(this, {'font':prop['chart.text.font'],
  202. 'size':prop['chart.text.size'],
  203. 'x':this.coords[0][0] - 1,
  204. 'y':this.coords[0][1] - 1,
  205. 'text':this.text,
  206. 'valign':'center',
  207. 'halign':'center',
  208. 'tag': 'labels'
  209. });
  210. /**
  211. * This installs the event listeners
  212. */
  213. RG.InstallEventListeners(this);
  214. /**
  215. * Fire the ondraw event
  216. */
  217. RG.FireCustomEvent(this, 'ondraw');
  218. return this;
  219. }
  220. /**
  221. * The getObjectByXY() worker method
  222. */
  223. this.getObjectByXY = function (e)
  224. {
  225. if (this.getShape(e)) {
  226. return this;
  227. }
  228. }
  229. /**
  230. * Not used by the class during creating the shape, but is used by event handlers
  231. * to get the coordinates (if any) of the selected bar
  232. *
  233. * @param object e The event object
  234. * @param object OPTIONAL You can pass in the bar object instead of the
  235. * function using "this"
  236. */
  237. this.getShape = function (e)
  238. {
  239. var mouseXY = RG.getMouseXY(e);
  240. var mouseX = mouseXY[0];
  241. var mouseY = mouseXY[1];
  242. /**
  243. * Path the marker but DON'T STROKE OR FILL it
  244. */
  245. co.beginPath();
  246. this.DrawMarker();
  247. if (co.isPointInPath(mouseXY[0], mouseXY[1])) {
  248. return {
  249. 0: this, 1: this.coords[0][0], 2: this.coords[0][1], 3: this.coords[0][2], 4: 0,
  250. 'object': this, 'x': this.coords[0][0], 'y': this.coords[0][1], 'radius': this.coords[0][2], 'index': 0, 'tooltip': prop['chart.tooltips'] ? prop['chart.tooltips'][0] : null
  251. };
  252. }
  253. return null;
  254. }
  255. /**
  256. * This function positions a tooltip when it is displayed
  257. *
  258. * @param obj object The chart object
  259. * @param int x The X coordinate specified for the tooltip
  260. * @param int y The Y coordinate specified for the tooltip
  261. * @param object tooltip The tooltips DIV element
  262. * @param number idx The index of the tooltip
  263. */
  264. this.positionTooltip = function (obj, x, y, tooltip, idx)
  265. {
  266. var canvasXY = RG.getCanvasXY(obj.canvas);
  267. var width = tooltip.offsetWidth;
  268. var height = tooltip.offsetHeight;
  269. // Set the top position
  270. tooltip.style.left = 0;
  271. tooltip.style.top = canvasXY[1] + this.coords[0][1] - height - 7 - this.radius + 'px';
  272. // By default any overflow is hidden
  273. tooltip.style.overflow = '';
  274. // The arrow
  275. var img = new Image();
  276. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  277. img.style.position = 'absolute';
  278. img.id = '__rgraph_tooltip_pointer__';
  279. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  280. tooltip.appendChild(img);
  281. // Reposition the tooltip if at the edges:
  282. // LEFT edge
  283. if ((canvasXY[0] + obj.coords[0][0] + (obj.coords[0][2] / 2) - (width / 2)) < 10) {
  284. tooltip.style.left = canvasXY[0] + this.markerCenterx - (width * 0.1) + 'px';
  285. img.style.left = ((width * 0.1) - 8.5) + 'px';
  286. // RIGHT edge
  287. } else if ((canvasXY[0] + this.coords[0][0] + (this.coords[0][2] / 2) + (width / 2)) > document.body.offsetWidth) {
  288. tooltip.style.left = canvasXY[0] + this.markerCenterx - (width * 0.9) + 'px';
  289. img.style.left = ((width * 0.9) - 8.5) + 'px';
  290. // Default positioning - CENTERED
  291. } else {
  292. tooltip.style.left = (canvasXY[0] + this.markerCenterx - (width * 0.5)) + 'px';
  293. img.style.left = ((width * 0.5) - 8.5) + 'px';
  294. }
  295. }
  296. /**
  297. * Each object type has its own Highlight() function which highlights the appropriate shape
  298. *
  299. * @param object shape The shape to highlight
  300. */
  301. this.Highlight = function (shape)
  302. {
  303. if (prop['chart.tooltips.highlight']) {
  304. co.beginPath();
  305. co.strokeStyle = prop['chart.highlight.stroke'];
  306. co.fillStyle = prop['chart.highlight.fill'];
  307. this.DrawMarker();
  308. co.closePath();
  309. co.stroke();
  310. co.fill();
  311. }
  312. }
  313. /**
  314. * This function is used to encapsulate the actual drawing of the marker. It
  315. * intentional does not start a path or set colors.
  316. */
  317. this.DrawMarker = function ()
  318. {
  319. var r = this.radius;
  320. if (prop['chart.align'] == 'left') {
  321. var x = this.markerCenterx;
  322. var y = this.markerCentery;
  323. co.arc(x, y, r, HALFPI, TWOPI, false);
  324. // special case for MSIE 7/8
  325. if (ISOLD) {
  326. co.moveTo(x + r + r, y+r+r);
  327. co.quadraticCurveTo(
  328. x + r,
  329. y + r,
  330. x + r + 1,
  331. y
  332. );
  333. co.moveTo(x + r + r, y+r+r);
  334. } else {
  335. co.quadraticCurveTo(
  336. x + r,
  337. y + r,
  338. x + r + r,
  339. y + r + r
  340. );
  341. }
  342. co.quadraticCurveTo(
  343. x + r,
  344. y + r,
  345. x,
  346. y + r + (ISOLD ? 1 : 0)
  347. );
  348. } else if (prop['chart.align'] == 'right') {
  349. var x = this.markerCenterx;
  350. var y = this.markerCentery;
  351. co.arc(x, y, r, HALFPI, PI, true);
  352. // special case for MSIE 7/8
  353. if (ISOLD) {
  354. co.moveTo(x - r - r, y+r+r);
  355. co.quadraticCurveTo(
  356. x - r,
  357. y + r,
  358. x - r - 1,
  359. y
  360. );
  361. co.moveTo(x - r - r, y+r+r);
  362. } else {
  363. co.quadraticCurveTo(
  364. x - r,
  365. y + r,
  366. x - r - r,
  367. y + r + r
  368. );
  369. }
  370. co.quadraticCurveTo(
  371. x - r,
  372. y + r,
  373. x,
  374. y + r + (ISOLD ? 1 : 0)
  375. );
  376. // Default is center
  377. } else {
  378. var x = this.markerCenterx;
  379. var y = this.markerCentery;
  380. co.arc(x, y, r, HALFPI / 2, PI - (HALFPI / 2), true);
  381. // special case for MSIE 7/8
  382. if (ISOLD) {
  383. co.moveTo(x, y+r+r-2);
  384. co.quadraticCurveTo(
  385. x,
  386. y + r + (r / 4),
  387. x - (Math.cos(HALFPI / 2) * r),
  388. y + (Math.sin(HALFPI / 2) * r)
  389. );
  390. co.moveTo(x, y+r+r-2);
  391. } else {
  392. co.quadraticCurveTo(
  393. x,
  394. y + r + (r / 4),
  395. x,
  396. y + r + r - 2 // The two is so that the marker is not touching the point
  397. );
  398. }
  399. co.quadraticCurveTo(
  400. x,
  401. y + r + (r / 4),
  402. x + (Math.cos(HALFPI / 2) * r),
  403. y + (Math.sin(HALFPI / 2) * r)
  404. );
  405. }
  406. this.coords[0] = [x, y, r];
  407. }
  408. /**
  409. * This allows for easy specification of gradients
  410. */
  411. this.parseColors = function ()
  412. {
  413. /**
  414. * Parse various properties for colors
  415. */
  416. prop['chart.fillstyle'] = this.parseSingleColorForGradient(prop['chart.fillstyle']);
  417. prop['chart.strokestyle'] = this.parseSingleColorForGradient(prop['chart.strokestyle']);
  418. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  419. prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
  420. prop['chart.text.color'] = this.parseSingleColorForGradient(prop['chart.text.color']);
  421. }
  422. /**
  423. * This parses a single color value
  424. */
  425. this.parseSingleColorForGradient = function (color)
  426. {
  427. if (!color || typeof(color) != 'string') {
  428. return color;
  429. }
  430. if (color.match(/^gradient\((.*)\)$/i)) {
  431. var parts = RegExp.$1.split(':');
  432. // Create the gradient
  433. var grad = co.createRadialGradient(this.markerCenterx, this.markerCentery, 0, this.markerCenterx, this.markerCentery, this.radius);
  434. var diff = 1 / (parts.length - 1);
  435. grad.addColorStop(0, RG.trim(parts[0]));
  436. for (var j=1; j<parts.length; ++j) {
  437. grad.addColorStop(j * diff, RG.trim(parts[j]));
  438. }
  439. }
  440. return grad ? grad : color;
  441. }
  442. /**
  443. * Objects are now always registered so that the chart is redrawn if need be.
  444. */
  445. RG.Register(this);
  446. }