RGraph.bar.js 123 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067
  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 bar chart constructor
  14. *
  15. * @param object canvas The canvas object
  16. * @param array data The chart data
  17. */
  18. RGraph.Bar = function (conf)
  19. {
  20. /**
  21. * Allow for object config style
  22. */
  23. if (typeof conf === 'object' && typeof conf.data === 'object') {
  24. var id = conf.id
  25. var canvas = document.getElementById(id);
  26. var data = conf.data;
  27. var parseConfObjectForOptions = true; // Set this so the config is parsed (at the end of the constructor)
  28. } else {
  29. var id = conf;
  30. var canvas = document.getElementById(id);
  31. var data = arguments[1];
  32. }
  33. // Get the canvas and context objects
  34. this.id = id;
  35. this.canvas = canvas;
  36. this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null;
  37. this.canvas.__object__ = this;
  38. this.type = 'bar';
  39. this.max = 0;
  40. this.stackedOrGrouped = false;
  41. this.isRGraph = true;
  42. this.uid = RGraph.CreateUID();
  43. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  44. this.colorsParsed = false;
  45. this.original_colors = [];
  46. this.cachedBackgroundCanvas = null;
  47. this.firstDraw = true; // After the first draw this will be false
  48. /**
  49. * Compatibility with older browsers
  50. */
  51. //RGraph.OldBrowserCompat(this.context);
  52. // Various config type stuff
  53. this.properties =
  54. {
  55. 'chart.background.barcolor1': 'rgba(0,0,0,0)',
  56. 'chart.background.barcolor2': 'rgba(0,0,0,0)',
  57. 'chart.background.grid': true,
  58. 'chart.background.grid.color': '#ddd',
  59. 'chart.background.grid.width': 1,
  60. 'chart.background.grid.hsize': 20,
  61. 'chart.background.grid.vsize': 20,
  62. 'chart.background.grid.vlines': true,
  63. 'chart.background.grid.hlines': true,
  64. 'chart.background.grid.border': true,
  65. 'chart.background.grid.autofit':true,
  66. 'chart.background.grid.autofit.numhlines': 5,
  67. 'chart.background.grid.autofit.numvlines': 20,
  68. 'chart.background.grid.dashed': false,
  69. 'chart.background.grid.dotted': false,
  70. 'chart.background.image.stretch': true,
  71. 'chart.background.image.x': null,
  72. 'chart.background.image.y': null,
  73. 'chart.background.image.w': null,
  74. 'chart.background.image.h': null,
  75. 'chart.background.image.align': null,
  76. 'chart.background.color': null,
  77. 'chart.numyticks': 10,
  78. 'chart.hmargin': 5,
  79. 'chart.hmargin.grouped': 1,
  80. 'chart.strokecolor': 'white',
  81. 'chart.axis.color': 'black',
  82. 'chart.axis.linewidth': 1,
  83. 'chart.gutter.top': 25,
  84. 'chart.gutter.bottom': 25,
  85. 'chart.gutter.left': 25,
  86. 'chart.gutter.right': 25,
  87. 'chart.labels': null,
  88. 'chart.labels.ingraph': null,
  89. 'chart.labels.above': false,
  90. 'chart.labels.above.decimals': 0,
  91. 'chart.labels.above.size': null,
  92. 'chart.labels.above.color': null,
  93. 'chart.labels.above.angle': null,
  94. 'chart.labels.above.offset': 4,
  95. 'chart.ylabels': true,
  96. 'chart.ylabels.count': 5,
  97. 'chart.ylabels.inside': false,
  98. 'chart.xlabels.offset': 0,
  99. 'chart.xaxispos': 'bottom',
  100. 'chart.yaxispos': 'left',
  101. 'chart.text.angle': 0,
  102. 'chart.text.color': 'black', // Gradients aren't supported for this color
  103. 'chart.text.size': 10,
  104. 'chart.text.font': 'Arial',
  105. 'chart.ymin': 0,
  106. 'chart.ymax': null,
  107. 'chart.title': '',
  108. 'chart.title.font': null,
  109. 'chart.title.background': null, // Gradients aren't supported for this color
  110. 'chart.title.hpos': null,
  111. 'chart.title.vpos': null,
  112. 'chart.title.bold': true,
  113. 'chart.title.xaxis': '',
  114. 'chart.title.xaxis.bold': true,
  115. 'chart.title.xaxis.size': null,
  116. 'chart.title.xaxis.font': null,
  117. 'chart.title.yaxis': '',
  118. 'chart.title.yaxis.bold': true,
  119. 'chart.title.yaxis.size': null,
  120. 'chart.title.yaxis.font': null,
  121. 'chart.title.yaxis.color': null, // Gradients aren't supported for this color
  122. 'chart.title.xaxis.pos': null,
  123. 'chart.title.yaxis.pos': null,
  124. 'chart.title.yaxis.x': null,
  125. 'chart.title.yaxis.y': null,
  126. 'chart.title.xaxis.x': null,
  127. 'chart.title.xaxis.y': null,
  128. 'chart.title.x': null,
  129. 'chart.title.y': null,
  130. 'chart.title.halign': null,
  131. 'chart.title.valign': null,
  132. 'chart.colors': [
  133. 'Gradient(#F9D5C9:#E65F2D:#E65F2D:#E65F2D)',
  134. 'Gradient(#F7DCD1:#D4592A:#D4592A:#D4592A)',
  135. 'Gradient(#DEE5EA:#B5C3CE:#B5C3CE:#B5C3CE)',
  136. 'Gradient(#E5E5E3:#545451:#545451:#545451)',
  137. 'Gradient(#F6E5D2:#E9C294:#E9C294:#E9C294)',
  138. 'Gradient(#F5EAD3:#D6AA4E:#D6AA4E:#D6AA4E)'
  139. ],
  140. 'chart.colors.sequential': false,
  141. 'chart.colors.reverse': false,
  142. 'chart.grouping': 'grouped',
  143. 'chart.variant': 'bar',
  144. 'chart.variant.sketch.verticals': true,
  145. 'chart.shadow': true,
  146. 'chart.shadow.color': '#aaa', // Gradients aren't supported for this color
  147. 'chart.shadow.offsetx': 0,
  148. 'chart.shadow.offsety': 0,
  149. 'chart.shadow.blur': 15,
  150. 'chart.tooltips': null,
  151. 'chart.tooltips.effect': 'fade',
  152. 'chart.tooltips.css.class': 'RGraph_tooltip',
  153. 'chart.tooltips.event': 'onclick',
  154. 'chart.tooltips.highlight': true,
  155. 'chart.highlight.stroke': 'rgba(0,0,0,0)',
  156. 'chart.highlight.fill': 'rgba(255,255,255,0.7)',
  157. 'chart.background.hbars': null,
  158. 'chart.key': null,
  159. 'chart.key.background': 'white',
  160. 'chart.key.position': 'graph',
  161. 'chart.key.shadow': false,
  162. 'chart.key.shadow.color': '#666',
  163. 'chart.key.shadow.blur': 3,
  164. 'chart.key.shadow.offsetx': 2,
  165. 'chart.key.shadow.offsety': 2,
  166. 'chart.key.position.gutter.boxed':false,
  167. 'chart.key.position.x': null,
  168. 'chart.key.position.y': null,
  169. 'chart.key.interactive': false,
  170. 'chart.key.interactive.highlight.chart.stroke':'black',
  171. 'chart.key.interactive.highlight.chart.fill':'rgba(255,255,255,0.7)',
  172. 'chart.key.interactive.highlight.label':'rgba(255,0,0,0.2)',
  173. 'chart.key.halign': 'right',
  174. 'chart.key.color.shape': 'square',
  175. 'chart.key.rounded': true,
  176. 'chart.key.text.size': 10,
  177. 'chart.key.linewidth': 1,
  178. 'chart.key.colors': null,
  179. 'chart.key.text.color': 'black',
  180. 'chart.contextmenu': null,
  181. 'chart.units.pre': '',
  182. 'chart.units.post': '',
  183. 'chart.scale.decimals': 0,
  184. 'chart.scale.point': '.',
  185. 'chart.scale.thousand': ',',
  186. 'chart.crosshairs': false,
  187. 'chart.crosshairs.color': '#333',
  188. 'chart.crosshairs.hline': true,
  189. 'chart.crosshairs.vline': true,
  190. 'chart.linewidth': 1,
  191. 'chart.annotatable': false,
  192. 'chart.annotate.color': 'black',
  193. 'chart.zoom.factor': 1.5,
  194. 'chart.zoom.fade.in': true,
  195. 'chart.zoom.fade.out': true,
  196. 'chart.zoom.hdir': 'right',
  197. 'chart.zoom.vdir': 'down',
  198. 'chart.zoom.frames': 25,
  199. 'chart.zoom.delay': 16.666,
  200. 'chart.zoom.shadow': true,
  201. 'chart.zoom.background': true,
  202. 'chart.resizable': false,
  203. 'chart.resize.handle.background': null,
  204. 'chart.adjustable': false,
  205. 'chart.noaxes': false,
  206. 'chart.noxaxis': false,
  207. 'chart.noyaxis': false,
  208. 'chart.events.click': null,
  209. 'chart.events.mousemove': null,
  210. 'chart.numxticks': null,
  211. 'chart.bevel': false
  212. }
  213. // Check for support
  214. if (!this.canvas) {
  215. alert('[BAR] No canvas support');
  216. return;
  217. }
  218. /**
  219. * Determine whether the chart will contain stacked or grouped bars
  220. */
  221. for (var i=0; i<data.length; ++i) {
  222. if (typeof data[i] === 'object' && !RGraph.is_null(data[i])) {
  223. this.stackedOrGrouped = true;
  224. }
  225. }
  226. /**
  227. * Create the dollar objects so that functions can be added to them
  228. */
  229. var linear_data = RGraph.array_linearize(data);
  230. for (var i=0; i<linear_data.length; ++i) {
  231. this['$' + i] = {};
  232. }
  233. // Store the data
  234. this.data = data;
  235. // Used to store the coords of the bars
  236. this.coords = [];
  237. this.coords2 = [];
  238. this.coordsText = [];
  239. /**
  240. * This linearises the data. Doing so can make it easier to pull
  241. * out the appropriate data from tooltips
  242. */
  243. this.data_arr = RGraph.array_linearize(this.data);
  244. /**
  245. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  246. * done already
  247. */
  248. if (!this.canvas.__rgraph_aa_translated__) {
  249. this.context.translate(0.5,0.5);
  250. this.canvas.__rgraph_aa_translated__ = true;
  251. }
  252. // Short variable names
  253. var RG = RGraph;
  254. var ca = this.canvas;
  255. var co = ca.getContext('2d');
  256. var prop = this.properties;
  257. var jq = jQuery;
  258. var pa = RG.Path;
  259. var win = window;
  260. var doc = document;
  261. var ma = Math;
  262. /**
  263. * "Decorate" the object with the generic effects if the effects library has been included
  264. */
  265. if (RG.Effects && typeof RG.Effects.decorate === 'function') {
  266. RG.Effects.decorate(this);
  267. }
  268. /**
  269. * A setter
  270. *
  271. * @param name string The name of the property to set
  272. * @param value mixed The value of the property
  273. */
  274. this.set =
  275. this.Set = function (name, value)
  276. {
  277. name = name.toLowerCase();
  278. /**
  279. * This should be done first - prepend the propertyy name with "chart." if necessary
  280. */
  281. if (name.substr(0,6) != 'chart.') {
  282. name = 'chart.' + name;
  283. }
  284. if (name == 'chart.labels.abovebar') {
  285. name = 'chart.labels.above';
  286. }
  287. if (name == 'chart.strokestyle') {
  288. name = 'chart.strokecolor';
  289. }
  290. /**
  291. * Check for xaxispos
  292. */
  293. if (name == 'chart.xaxispos' ) {
  294. if (value != 'bottom' && value != 'center' && value != 'top') {
  295. alert('[BAR] (' + this.id + ') chart.xaxispos should be top, center or bottom. Tried to set it to: ' + value + ' Changing it to center');
  296. value = 'center';
  297. }
  298. if (value == 'top') {
  299. for (var i=0; i<this.data.length; ++i) {
  300. if (typeof(this.data[i]) == 'number' && this.data[i] > 0) {
  301. alert('[BAR] The data element with index ' + i + ' should be negative');
  302. }
  303. }
  304. }
  305. }
  306. /**
  307. * lineWidth doesn't appear to like a zero setting
  308. */
  309. if (name.toLowerCase() == 'chart.linewidth' && value == 0) {
  310. value = 0.0001;
  311. }
  312. prop[name] = value;
  313. return this;
  314. };
  315. /**
  316. * A getter
  317. *
  318. * @param name string The name of the property to get
  319. */
  320. this.get =
  321. this.Get = function (name)
  322. {
  323. /**
  324. * This should be done first - prepend the property name with "chart." if necessary
  325. */
  326. if (name.substr(0,6) != 'chart.') {
  327. name = 'chart.' + name;
  328. }
  329. return prop[name];
  330. };
  331. /**
  332. * The function you call to draw the bar chart
  333. */
  334. this.draw =
  335. this.Draw = function ()
  336. {
  337. // MUST be the first thing done!
  338. if (typeof(prop['chart.background.image']) == 'string') {
  339. RG.DrawBackgroundImage(this);
  340. }
  341. /**
  342. * Fire the onbeforedraw event
  343. */
  344. RG.FireCustomEvent(this, 'onbeforedraw');
  345. /**
  346. * Parse the colors. This allows for simple gradient syntax
  347. */
  348. if (!this.colorsParsed) {
  349. this.parseColors();
  350. // Don't want to do this again
  351. this.colorsParsed = true;
  352. }
  353. /**
  354. * This is new in May 2011 and facilitates indiviual gutter settings,
  355. * eg chart.gutter.left
  356. */
  357. this.gutterLeft = prop['chart.gutter.left'];
  358. this.gutterRight = prop['chart.gutter.right'];
  359. this.gutterTop = prop['chart.gutter.top'];
  360. this.gutterBottom = prop['chart.gutter.bottom'];
  361. // Cache this in a class variable as it's used rather a lot
  362. /**
  363. * Check for tooltips and alert the user that they're not supported with pyramid charts
  364. */
  365. if ( (prop['chart.variant'] == 'pyramid' || prop['chart.variant'] == 'dot')
  366. && typeof(prop['chart.tooltips']) == 'object'
  367. && prop['chart.tooltips']
  368. && prop['chart.tooltips'].length > 0) {
  369. alert('[BAR] (' + this.id + ') Sorry, tooltips are not supported with dot or pyramid charts');
  370. }
  371. /**
  372. * Stop the coords arrays from growing uncontrollably
  373. */
  374. this.coords = [];
  375. this.coords2 = [];
  376. this.coordsText = [];
  377. /**
  378. * Work out a few things. They need to be here because they depend on things you can change before you
  379. * call Draw() but after you instantiate the object
  380. */
  381. this.max = 0;
  382. this.grapharea = ca.height - this.gutterTop - this.gutterBottom;
  383. this.halfgrapharea = this.grapharea / 2;
  384. this.halfTextHeight = prop['chart.text.size'] / 2;
  385. // Now draw the background on to the main canvas
  386. RG.background.Draw(this);
  387. //If it's a sketch chart variant, draw the axes first
  388. if (prop['chart.variant'] == 'sketch') {
  389. this.DrawAxes();
  390. this.Drawbars();
  391. } else {
  392. this.Drawbars();
  393. this.DrawAxes();
  394. }
  395. this.DrawLabels();
  396. /**
  397. * Draw the bevel if required
  398. */
  399. if (prop['chart.bevel'] || prop['chart.bevelled']) {
  400. this.DrawBevel();
  401. }
  402. // Draw the key if necessary
  403. if (prop['chart.key'] && prop['chart.key'].length) {
  404. RG.DrawKey(this, prop['chart.key'], prop['chart.colors']);
  405. }
  406. /**
  407. * Setup the context menu if required
  408. */
  409. if (prop['chart.contextmenu']) {
  410. RG.ShowContext(this);
  411. }
  412. /**
  413. * Draw "in graph" labels
  414. */
  415. if (prop['chart.labels.ingraph']) {
  416. RG.DrawInGraphLabels(this);
  417. }
  418. /**
  419. * This function enables resizing
  420. */
  421. if (prop['chart.resizable']) {
  422. RG.AllowResizing(this);
  423. }
  424. /**
  425. * This installs the event listeners
  426. */
  427. RG.InstallEventListeners(this);
  428. /**
  429. * Fire the onfirstdraw event
  430. */
  431. if (this.firstDraw) {
  432. RG.fireCustomEvent(this, 'onfirstdraw');
  433. this.firstDrawFunc();
  434. this.firstDraw = false;
  435. }
  436. /**
  437. * Fire the RGraph ondraw event
  438. */
  439. RG.fireCustomEvent(this, 'ondraw');
  440. return this;
  441. };
  442. /**
  443. * Draws the charts axes
  444. */
  445. this.drawAxes =
  446. this.DrawAxes = function ()
  447. {
  448. if (prop['chart.noaxes']) {
  449. return;
  450. }
  451. var xaxispos = prop['chart.xaxispos'];
  452. var yaxispos = prop['chart.yaxispos'];
  453. var isSketch = prop['chart.variant'] == 'sketch';
  454. co.beginPath();
  455. co.strokeStyle = prop['chart.axis.color'];
  456. co.lineWidth = prop['chart.axis.linewidth'] + 0.001;
  457. if (RG.ISSAFARI == -1) {
  458. co.lineCap = 'square';
  459. }
  460. // Draw the Y axis
  461. if (prop['chart.noyaxis'] == false) {
  462. if (yaxispos == 'right') {
  463. co.moveTo(ca.width - this.gutterRight + (isSketch ? 3 : 0), this.gutterTop - (isSketch ? 3 : 0));
  464. co.lineTo(ca.width - this.gutterRight - (isSketch ? 2 : 0), ca.height - this.gutterBottom + (isSketch ? 5 : 0));
  465. } else {
  466. co.moveTo(this.gutterLeft - (isSketch ? 2 : 0), this.gutterTop - (isSketch ? 5 : 0));
  467. co.lineTo(this.gutterLeft - (isSketch ? 1 : 0), ca.height - this.gutterBottom + (isSketch ? 5 : 0));
  468. }
  469. }
  470. // Draw the X axis
  471. if (prop['chart.noxaxis'] == false) {
  472. if (xaxispos == 'center') {
  473. co.moveTo(this.gutterLeft - (isSketch ? 5 : 0), Math.round(((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop + (isSketch ? 2 : 0)));
  474. co.lineTo(ca.width - this.gutterRight + (isSketch ? 5 : 0), Math.round(((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop - (isSketch ? 2 : 0)));
  475. } else if (xaxispos == 'top') {
  476. co.moveTo(this.gutterLeft - (isSketch ? 3 : 0), this.gutterTop - (isSketch ? 3 : 0));
  477. co.lineTo(ca.width - this.gutterRight + (isSketch ? 5 : 0), this.gutterTop + (isSketch ? 2 : 0));
  478. } else {
  479. co.moveTo(this.gutterLeft - (isSketch ? 5 : 0), ca.height - this.gutterBottom - (isSketch ? 2 : 0));
  480. co.lineTo(ca.width - this.gutterRight + (isSketch ? 8 : 0), ca.height - this.gutterBottom + (isSketch ? 2 : 0));
  481. }
  482. }
  483. var numYTicks = prop['chart.numyticks'];
  484. // Draw the Y tickmarks
  485. if (prop['chart.noyaxis'] == false && !isSketch) {
  486. var yTickGap = (ca.height - this.gutterTop - this.gutterBottom) / numYTicks;
  487. var xpos = yaxispos == 'left' ? this.gutterLeft : ca.width - this.gutterRight;
  488. if (this.properties['chart.numyticks'] > 0) {
  489. for (y=this.gutterTop;
  490. xaxispos == 'center' ? y <= (ca.height - this.gutterBottom) : y < (ca.height - this.gutterBottom + (xaxispos == 'top' ? 1 : 0));
  491. y += yTickGap) {
  492. if (xaxispos == 'center' && y == (this.gutterTop + (this.grapharea / 2))) continue;
  493. // X axis at the top
  494. if (xaxispos == 'top' && y == this.gutterTop) continue;
  495. co.moveTo(xpos + (yaxispos == 'left' ? 0 : 0), Math.round(y));
  496. co.lineTo(xpos + (yaxispos == 'left' ? -3 : 3), Math.round(y));
  497. }
  498. }
  499. /**
  500. * If the X axis is not being shown, draw an extra tick
  501. */
  502. if (prop['chart.noxaxis']) {
  503. if (xaxispos == 'center') {
  504. co.moveTo(xpos + (yaxispos == 'left' ? -3 : 3), Math.round(ca.height / 2));
  505. co.lineTo(xpos, Math.round(ca.height / 2));
  506. } else if (xaxispos == 'top') {
  507. co.moveTo(xpos + (yaxispos == 'left' ? -3 : 3), Math.round(this.gutterTop));
  508. co.lineTo(xpos, Math.round(this.gutterTop));
  509. } else {
  510. co.moveTo(xpos + (yaxispos == 'left' ? -3 : 3), Math.round(ca.height - this.gutterBottom));
  511. co.lineTo(xpos, Math.round(ca.height - this.gutterBottom));
  512. }
  513. }
  514. }
  515. // Draw the X tickmarks
  516. if (prop['chart.noxaxis'] == false && !isSketch) {
  517. if (typeof(prop['chart.numxticks']) == 'number') {
  518. var xTickGap = (ca.width - this.gutterLeft - this.gutterRight) / prop['chart.numxticks'];
  519. } else {
  520. var xTickGap = (ca.width - this.gutterLeft - this.gutterRight) / this.data.length;
  521. }
  522. if (xaxispos == 'bottom') {
  523. yStart = ca.height - this.gutterBottom;
  524. yEnd = (ca.height - this.gutterBottom) + 3;
  525. } else if (xaxispos == 'top') {
  526. yStart = this.gutterTop - 3;
  527. yEnd = this.gutterTop;
  528. } else if (xaxispos == 'center') {
  529. yStart = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop + 3;
  530. yEnd = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop - 3;
  531. }
  532. yStart = yStart;
  533. yEnd = yEnd;
  534. //////////////// X TICKS ////////////////
  535. var noEndXTick = prop['chart.noendxtick'];
  536. for (x=this.gutterLeft + (yaxispos == 'left' ? xTickGap : 0),len=(ca.width - this.gutterRight + (yaxispos == 'left' ? 5 : 0)); x<len; x+=xTickGap) {
  537. if (yaxispos == 'left' && !noEndXTick && x > this.gutterLeft) {
  538. co.moveTo(Math.round(x), yStart);
  539. co.lineTo(Math.round(x), yEnd);
  540. } else if (yaxispos == 'left' && noEndXTick && x > this.gutterLeft && x < (ca.width - this.gutterRight) ) {
  541. co.moveTo(Math.round(x), yStart);
  542. co.lineTo(Math.round(x), yEnd);
  543. } else if (yaxispos == 'right' && x < (ca.width - this.gutterRight) && !noEndXTick) {
  544. co.moveTo(Math.round(x), yStart);
  545. co.lineTo(Math.round(x), yEnd);
  546. } else if (yaxispos == 'right' && x < (ca.width - this.gutterRight) && x > (this.gutterLeft) && noEndXTick) {
  547. co.moveTo(Math.round(x), yStart);
  548. co.lineTo(Math.round(x), yEnd);
  549. }
  550. }
  551. if (prop['chart.noyaxis'] || prop['chart.numxticks'] == null) {
  552. if (typeof(prop['chart.numxticks']) == 'number' && prop['chart.numxticks'] > 0) {
  553. co.moveTo(Math.round(this.gutterLeft), yStart);
  554. co.lineTo(Math.round(this.gutterLeft), yEnd);
  555. }
  556. }
  557. //////////////// X TICKS ////////////////
  558. }
  559. /**
  560. * If the Y axis is not being shown, draw an extra tick
  561. */
  562. if (prop['chart.noyaxis'] && prop['chart.noxaxis'] == false && prop['chart.numxticks'] == null) {
  563. if (xaxispos == 'center') {
  564. co.moveTo(Math.round(this.gutterLeft), (ca.height / 2) - 3);
  565. co.lineTo(Math.round(this.gutterLeft), (ca.height / 2) + 3);
  566. } else {
  567. co.moveTo(Math.round(this.gutterLeft), ca.height - this.gutterBottom);
  568. co.lineTo(Math.round(this.gutterLeft), ca.height - this.gutterBottom + 3);
  569. }
  570. }
  571. co.stroke();
  572. };
  573. /**
  574. * Draws the bars
  575. */
  576. this.drawbars =
  577. this.Drawbars = function ()
  578. {
  579. // Variable "caching" so the context can be accessed as a local variable
  580. //var ca = this.canvas;
  581. //var co = this.context;
  582. //var prop = this.properties;
  583. co.lineWidth = prop['chart.linewidth'];
  584. co.strokeStyle = prop['chart.strokecolor'];
  585. co.fillStyle = prop['chart.colors'][0];
  586. var prevX = 0;
  587. var prevY = 0;
  588. var decimals = prop['chart.scale.decimals'];
  589. /**
  590. * Work out the max value
  591. */
  592. if (prop['chart.ymax']) {
  593. this.scale2 = RGraph.getScale2(this, {
  594. 'max':prop['chart.ymax'],
  595. 'strict': true,
  596. 'min':prop['chart.ymin'],
  597. 'scale.thousand':prop['chart.scale.thousand'],
  598. 'scale.point':prop['chart.scale.point'],
  599. 'scale.decimals':prop['chart.scale.decimals'],
  600. 'ylabels.count':prop['chart.ylabels.count'],
  601. 'scale.round':prop['chart.scale.round'],
  602. 'units.pre': prop['chart.units.pre'],
  603. 'units.post': prop['chart.units.post']
  604. });
  605. } else {
  606. for (i=0; i<this.data.length; ++i) {
  607. if (typeof(this.data[i]) == 'object') {
  608. var value = prop['chart.grouping'] == 'grouped' ? Number(RG.array_max(this.data[i], true)) : Number(RG.array_sum(this.data[i]));
  609. } else {
  610. var value = Number(this.data[i]);
  611. }
  612. this.max = Math.max(Math.abs(this.max), Math.abs(value));
  613. }
  614. this.scale2 = RGraph.getScale2(this, {
  615. 'max':this.max,
  616. 'min':prop['chart.ymin'],
  617. 'scale.thousand':prop['chart.scale.thousand'],
  618. 'scale.point':prop['chart.scale.point'],
  619. 'scale.decimals':prop['chart.scale.decimals'],
  620. 'ylabels.count':prop['chart.ylabels.count'],
  621. 'scale.round':prop['chart.scale.round'],
  622. 'units.pre': prop['chart.units.pre'],
  623. 'units.post': prop['chart.units.post']
  624. });
  625. this.max = this.scale2.max;
  626. }
  627. /**
  628. * if the chart is adjustable fix the scale so that it doesn't change.
  629. */
  630. if (prop['chart.adjustable'] && !prop['chart.ymax']) {
  631. this.Set('chart.ymax', this.scale2.max);
  632. }
  633. /**
  634. * Draw horizontal bars here
  635. */
  636. if (prop['chart.background.hbars'] && prop['chart.background.hbars'].length > 0) {
  637. RGraph.DrawBars(this);
  638. }
  639. var variant = prop['chart.variant'];
  640. /**
  641. * Draw the 3D axes is necessary
  642. */
  643. if (variant == '3d') {
  644. RG.Draw3DAxes(this);
  645. }
  646. /**
  647. * Get the variant once, and draw the bars, be they regular, stacked or grouped
  648. */
  649. // Get these variables outside of the loop
  650. var xaxispos = prop['chart.xaxispos'];
  651. var width = (ca.width - this.gutterLeft - this.gutterRight ) / this.data.length;
  652. var orig_height = height;
  653. var hmargin = prop['chart.hmargin'];
  654. var shadow = prop['chart.shadow'];
  655. var shadowColor = prop['chart.shadow.color'];
  656. var shadowBlur = prop['chart.shadow.blur'];
  657. var shadowOffsetX = prop['chart.shadow.offsetx'];
  658. var shadowOffsetY = prop['chart.shadow.offsety'];
  659. var strokeStyle = prop['chart.strokecolor'];
  660. var colors = prop['chart.colors'];
  661. var sequentialColorIndex = 0;
  662. for (i=0,len=this.data.length; i<len; i+=1) {
  663. // Work out the height
  664. //The width is up outside the loop
  665. var height = ((RGraph.array_sum(this.data[i]) < 0 ? RGraph.array_sum(this.data[i]) + this.scale2.min : RGraph.array_sum(this.data[i]) - this.scale2.min) / (this.scale2.max - this.scale2.min) ) * (ca.height - this.gutterTop - this.gutterBottom);
  666. // Half the height if the Y axis is at the center
  667. if (xaxispos == 'center') {
  668. height /= 2;
  669. }
  670. var x = (i * width) + this.gutterLeft;
  671. var y = xaxispos == 'center' ? ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop - height
  672. : ca.height - height - this.gutterBottom;
  673. // xaxispos is top
  674. if (xaxispos == 'top') {
  675. y = this.gutterTop + Math.abs(height);
  676. }
  677. // Account for negative lengths - Some browsers (eg Chrome) don't like a negative value
  678. if (height < 0) {
  679. y += height;
  680. height = Math.abs(height);
  681. }
  682. /**
  683. * Turn on the shadow if need be
  684. */
  685. if (shadow) {
  686. co.shadowColor = shadowColor;
  687. co.shadowBlur = shadowBlur;
  688. co.shadowOffsetX = shadowOffsetX;
  689. co.shadowOffsetY = shadowOffsetY;
  690. }
  691. /**
  692. * Draw the bar
  693. */
  694. co.beginPath();
  695. if (typeof(this.data[i]) == 'number') {
  696. var barWidth = width - (2 * hmargin);
  697. /**
  698. * Check for a negative bar width
  699. */
  700. if (barWidth < 0) {
  701. alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the chart.hmargin being too high or the width of the canvas not being sufficient.');
  702. }
  703. // Set the fill color
  704. co.strokeStyle = strokeStyle;
  705. co.fillStyle = colors[0];
  706. /**
  707. * Sequential colors
  708. */
  709. if (prop['chart.colors.sequential']) {
  710. co.fillStyle = colors[i];
  711. }
  712. if (variant == 'sketch') {
  713. co.lineCap = 'round';
  714. var sketchOffset = 3;
  715. co.beginPath();
  716. co.strokeStyle = colors[0];
  717. /**
  718. * Sequential colors
  719. */
  720. if (prop['chart.colors.sequential']) {
  721. co.strokeStyle = colors[i];
  722. }
  723. // Left side
  724. co.moveTo(x + hmargin + 2, y + height - 2);
  725. co.lineTo(x + hmargin - 1, y - 4);
  726. // The top
  727. co.moveTo(x + hmargin - 3, y + -2 + (this.data[i] < 0 ? height : 0));
  728. co.bezierCurveTo(
  729. x + ((hmargin + width) * 0.33),
  730. y + 15 + (this.data[i] < 0 ? height - 10: 0),
  731. x + ((hmargin + width) * 0.66),
  732. y + 5 + (this.data[i] < 0 ? height - 10 : 0),x + hmargin + width + -1, y + 0 + (this.data[i] < 0 ? height : 0)
  733. );
  734. // The right side
  735. co.moveTo(x + hmargin + width - 5, y - 5);
  736. co.lineTo(x + hmargin + width - 3, y + height - 3);
  737. if (prop['chart.variant.sketch.verticals']) {
  738. for (var r=0.2; r<=0.8; r+=0.2) {
  739. co.moveTo(x + hmargin + width + (r > 0.4 ? -1 : 3) - (r * width),y - 1);
  740. co.lineTo(x + hmargin + width - (r > 0.4 ? 1 : -1) - (r * width), y + height + (r == 0.2 ? 1 : -2));
  741. }
  742. }
  743. co.stroke();
  744. // Regular bar
  745. } else if (variant == 'bar' || variant == '3d' || variant == 'glass' || variant == 'bevel') {
  746. if (RGraph.ISOLD && shadow) {
  747. this.DrawIEShadow([x + hmargin, y, barWidth, height]);
  748. }
  749. if (variant == 'glass') {
  750. RGraph.filledCurvyRect(co, x + hmargin, y, barWidth, height, 3, this.data[i] > 0, this.data[i] > 0, this.data[i] < 0, this.data[i] < 0);
  751. RGraph.strokedCurvyRect(co, x + hmargin, y, barWidth, height, 3, this.data[i] > 0, this.data[i] > 0, this.data[i] < 0, this.data[i] < 0);
  752. } else {
  753. // On 9th April 2013 these two were swapped around so that the stroke happens SECOND so that any
  754. // shadow that is cast by the fill does not overwrite the stroke
  755. co.beginPath();
  756. co.rect(x + hmargin, y, barWidth, height);
  757. co.fill();
  758. // Turn the shadow off so that the stroke doesn't cast any "extra" shadow
  759. // that would show inside the bar
  760. RG.NoShadow(this);
  761. co.beginPath();
  762. co.rect(x + hmargin, y, barWidth, height);
  763. co.stroke();
  764. }
  765. // 3D effect
  766. if (variant == '3d') {
  767. var prevStrokeStyle = co.strokeStyle;
  768. var prevFillStyle = co.fillStyle;
  769. // Draw the top
  770. co.beginPath();
  771. co.moveTo(x + hmargin, y);
  772. co.lineTo(x + hmargin + 10, y - 5);
  773. co.lineTo(x + hmargin + 10 + barWidth, y - 5);
  774. co.lineTo(x + hmargin + barWidth, y);
  775. co.closePath();
  776. co.stroke();
  777. co.fill();
  778. // Draw the right hand side
  779. co.beginPath();
  780. co.moveTo(x + hmargin + barWidth, y);
  781. co.lineTo(x + hmargin + barWidth + 10, y - 5);
  782. co.lineTo(x + hmargin + barWidth + 10, y + height - 5);
  783. co.lineTo(x + hmargin + barWidth, y + height);
  784. co.closePath();
  785. co.stroke();
  786. co.fill();
  787. // Draw the darker top section
  788. co.beginPath();
  789. co.fillStyle = 'rgba(255,255,255,0.3)';
  790. co.moveTo(x + hmargin, y);
  791. co.lineTo(x + hmargin + 10, y - 5);
  792. co.lineTo(x + hmargin + 10 + barWidth, y - 5);
  793. co.lineTo(x + hmargin + barWidth, y);
  794. co.lineTo(x + hmargin, y);
  795. co.closePath();
  796. co.stroke();
  797. co.fill();
  798. // Draw the darker right side section
  799. co.beginPath();
  800. co.fillStyle = 'rgba(0,0,0,0.4)';
  801. co.moveTo(x + hmargin + barWidth, y);
  802. co.lineTo(x + hmargin + barWidth + 10, y - 5);
  803. co.lineTo(x + hmargin + barWidth + 10, y - 5 + height);
  804. co.lineTo(x + hmargin + barWidth, y + height);
  805. co.lineTo(x + hmargin + barWidth, y);
  806. co.closePath();
  807. co.stroke();
  808. co.fill();
  809. co.strokeStyle = prevStrokeStyle;
  810. co.fillStyle = prevFillStyle;
  811. // Glass variant
  812. } else if (variant == 'glass') {
  813. var grad = co.createLinearGradient(x + hmargin,y,x + hmargin + (barWidth / 2),y);
  814. grad.addColorStop(0, 'rgba(255,255,255,0.9)');
  815. grad.addColorStop(1, 'rgba(255,255,255,0.5)');
  816. co.beginPath();
  817. co.fillStyle = grad;
  818. co.fillRect(x + hmargin + 2,y + (this.data[i] > 0 ? 2 : 0),(barWidth / 2) - 2,height - 2);
  819. co.fill();
  820. }
  821. // Dot chart
  822. } else if (variant == 'dot') {
  823. co.beginPath();
  824. co.moveTo(x + (width / 2), y);
  825. co.lineTo(x + (width / 2), y + height);
  826. co.stroke();
  827. co.beginPath();
  828. co.fillStyle = this.properties['chart.colors'][i];
  829. co.arc(x + (width / 2), y + (this.data[i] > 0 ? 0 : height), 2, 0, 6.28, 0);
  830. // Set the colour for the dots
  831. co.fillStyle = prop['chart.colors'][0];
  832. /**
  833. * Sequential colors
  834. */
  835. if (prop['chart.colors.sequential']) {
  836. co.fillStyle = colors[i];
  837. }
  838. co.stroke();
  839. co.fill();
  840. // Unknown variant type
  841. } else {
  842. alert('[BAR] Warning! Unknown chart.variant: ' + variant);
  843. }
  844. this.coords.push([x + hmargin, y, width - (2 * hmargin), height]);
  845. if (typeof this.coords2[i] == 'undefined') {
  846. this.coords2[i] = [];
  847. }
  848. this.coords2[i].push([x + hmargin, y, width - (2 * hmargin), height]);
  849. /**
  850. * Stacked bar
  851. */
  852. } else if (this.data[i] && typeof(this.data[i]) == 'object' && prop['chart.grouping'] == 'stacked') {
  853. if (this.scale2.min) {
  854. alert("[ERROR] Stacked Bar charts with a Y min are not supported");
  855. }
  856. var barWidth = width - (2 * hmargin);
  857. var redrawCoords = [];// Necessary to draw if the shadow is enabled
  858. var startY = 0;
  859. var dataset = this.data[i];
  860. /**
  861. * Check for a negative bar width
  862. */
  863. if (barWidth < 0) {
  864. alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the chart.hmargin being too high or the width of the canvas not being sufficient.');
  865. }
  866. for (j=0; j<dataset.length; ++j) {
  867. // Stacked bar chart and X axis pos in the middle - poitless since negative values are not permitted
  868. if (xaxispos == 'center') {
  869. alert("[BAR] It's pointless having the X axis position at the center on a stacked bar chart.");
  870. return;
  871. }
  872. // Negative values not permitted for the stacked chart
  873. if (this.data[i][j] < 0) {
  874. alert('[BAR] Negative values are not permitted with a stacked bar chart. Try a grouped one instead.');
  875. return;
  876. }
  877. /**
  878. * Set the fill and stroke colors
  879. */
  880. co.strokeStyle = strokeStyle
  881. co.fillStyle = colors[j];
  882. if (prop['chart.colors.reverse']) {
  883. co.fillStyle = colors[this.data[i].length - j - 1];
  884. }
  885. if (prop['chart.colors.sequential'] && colors[sequentialColorIndex]) {
  886. co.fillStyle = colors[sequentialColorIndex++];
  887. } else if (prop['chart.colors.sequential']) {
  888. co.fillStyle = colors[sequentialColorIndex - 1];
  889. }
  890. var height = (dataset[j] / this.scale2.max) * (ca.height - this.gutterTop - this.gutterBottom );
  891. // If the X axis pos is in the center, we need to half the height
  892. if (xaxispos == 'center') {
  893. height /= 2;
  894. }
  895. var totalHeight = (RGraph.array_sum(dataset) / this.scale2.max) * (ca.height - hmargin - this.gutterTop - this.gutterBottom);
  896. /**
  897. * Store the coords for tooltips
  898. */
  899. this.coords.push([x + hmargin, y, width - (2 * hmargin), height]);
  900. if (typeof this.coords2[i] == 'undefined') {
  901. this.coords2[i] = [];
  902. }
  903. this.coords2[i].push([x + hmargin, y, width - (2 * hmargin), height]);
  904. // MSIE shadow
  905. if (RGraph.ISOLD && shadow) {
  906. this.DrawIEShadow([x + hmargin, y, width - (2 * hmargin), height + 1]);
  907. }
  908. if (height > 0) {
  909. co.strokeRect(x + hmargin, y, width - (2 * hmargin), height);
  910. co.fillRect(x + hmargin, y, width - (2 * hmargin), height);
  911. }
  912. if (j == 0) {
  913. var startY = y;
  914. var startX = x;
  915. }
  916. /**
  917. * Store the redraw coords if the shadow is enabled
  918. */
  919. if (shadow) {
  920. redrawCoords.push([x + hmargin, y, width - (2 * hmargin), height, co.fillStyle]);
  921. }
  922. /**
  923. * Stacked 3D effect
  924. */
  925. if (variant == '3d') {
  926. var prevFillStyle = co.fillStyle;
  927. var prevStrokeStyle = co.strokeStyle;
  928. // Draw the top side
  929. if (j == 0) {
  930. co.beginPath();
  931. co.moveTo(startX + hmargin, y);
  932. co.lineTo(startX + 10 + hmargin, y - 5);
  933. co.lineTo(startX + 10 + barWidth + hmargin, y - 5);
  934. co.lineTo(startX + barWidth + hmargin, y);
  935. co.closePath();
  936. co.fill();
  937. co.stroke();
  938. }
  939. // Draw the side section
  940. co.beginPath();
  941. co.moveTo(startX + barWidth + hmargin, y);
  942. co.lineTo(startX + barWidth + hmargin + 10, y - 5);
  943. co.lineTo(startX + barWidth + hmargin + 10, y - 5 + height);
  944. co.lineTo(startX + barWidth + hmargin , y + height);
  945. co.closePath();
  946. co.fill();
  947. co.stroke();
  948. // Draw the darker top side
  949. if (j == 0) {
  950. co.fillStyle = 'rgba(255,255,255,0.3)';
  951. co.beginPath();
  952. co.moveTo(startX + hmargin, y);
  953. co.lineTo(startX + 10 + hmargin, y - 5);
  954. co.lineTo(startX + 10 + barWidth + hmargin, y - 5);
  955. co.lineTo(startX + barWidth + hmargin, y);
  956. co.closePath();
  957. co.fill();
  958. co.stroke();
  959. }
  960. // Draw the darker side section
  961. co.fillStyle = 'rgba(0,0,0,0.4)';
  962. co.beginPath();
  963. co.moveTo(startX + barWidth + hmargin, y);
  964. co.lineTo(startX + barWidth + hmargin + 10, y - 5);
  965. co.lineTo(startX + barWidth + hmargin + 10, y - 5 + height);
  966. co.lineTo(startX + barWidth + hmargin , y + height);
  967. co.closePath();
  968. co.fill();
  969. co.stroke();
  970. co.strokeStyle = prevStrokeStyle;
  971. co.fillStyle = prevFillStyle;
  972. }
  973. y += height;
  974. }
  975. /**
  976. * Redraw the bars if the shadow is enabled due to hem being drawn from the bottom up, and the
  977. * shadow spilling over to higher up bars
  978. */
  979. if (shadow) {
  980. RGraph.NoShadow(this);
  981. for (k=0; k<redrawCoords.length; ++k) {
  982. co.strokeStyle = strokeStyle;
  983. co.fillStyle = redrawCoords[k][4];
  984. co.strokeRect(redrawCoords[k][0], redrawCoords[k][1], redrawCoords[k][2], redrawCoords[k][3]);
  985. co.fillRect(redrawCoords[k][0], redrawCoords[k][1], redrawCoords[k][2], redrawCoords[k][3]);
  986. co.stroke();
  987. co.fill();
  988. }
  989. // Reset the redraw coords to be empty
  990. redrawCoords = [];
  991. }
  992. /**
  993. * Grouped bar
  994. */
  995. } else if (this.data[i] && typeof(this.data[i]) == 'object' && prop['chart.grouping'] == 'grouped') {
  996. var redrawCoords = [];
  997. co.lineWidth = prop['chart.linewidth'];
  998. for (j=0; j<this.data[i].length; ++j) {
  999. // Set the fill and stroke colors
  1000. co.strokeStyle = strokeStyle;
  1001. co.fillStyle = colors[j];
  1002. /**
  1003. * Sequential colors
  1004. */
  1005. if (prop['chart.colors.sequential'] && colors[sequentialColorIndex]) {
  1006. co.fillStyle = colors[sequentialColorIndex++];
  1007. } else if (prop['chart.colors.sequential']) {
  1008. co.fillStyle = colors[sequentialColorIndex - 1];
  1009. }
  1010. var individualBarWidth = (width - (2 * hmargin)) / this.data[i].length;
  1011. var height = ((this.data[i][j] + (this.data[i][j] < 0 ? this.scale2.min : (-1 * this.scale2.min) )) / (this.scale2.max - this.scale2.min) ) * (ca.height - this.gutterTop - this.gutterBottom );
  1012. var groupedMargin = prop['chart.hmargin.grouped'];
  1013. var startX = x + hmargin + (j * individualBarWidth);
  1014. /**
  1015. * Check for a negative bar width
  1016. */
  1017. if (individualBarWidth < 0) {
  1018. alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the chart.hmargin being too high or the width of the canvas not being sufficient.');
  1019. }
  1020. // If the X axis pos is in the center, we need to half the height
  1021. if (xaxispos == 'center') {
  1022. height /= 2;
  1023. }
  1024. /**
  1025. * Determine the start positioning for the bar
  1026. */
  1027. if (xaxispos == 'top') {
  1028. var startY = this.gutterTop;
  1029. var height = Math.abs(height);
  1030. } else if (xaxispos == 'center') {
  1031. var startY = this.gutterTop + (this.grapharea / 2) - height;
  1032. } else {
  1033. var startY = ca.height - this.gutterBottom - height;
  1034. var height = Math.abs(height);
  1035. }
  1036. /**
  1037. * Draw MSIE shadow
  1038. */
  1039. if (RGraph.ISOLD && shadow) {
  1040. this.DrawIEShadow([startX, startY, individualBarWidth, height]);
  1041. }
  1042. co.strokeRect(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height);
  1043. co.fillRect(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height);
  1044. y += height;
  1045. /**
  1046. * Grouped 3D effect
  1047. */
  1048. if (variant == '3d') {
  1049. var prevFillStyle = co.fillStyle;
  1050. var prevStrokeStyle = co.strokeStyle;
  1051. // Draw the top side
  1052. co.beginPath();
  1053. co.moveTo(startX, startY);
  1054. co.lineTo(startX + 10, startY - 5);
  1055. co.lineTo(startX + 10 + individualBarWidth, startY - 5);
  1056. co.lineTo(startX + individualBarWidth, startY);
  1057. co.closePath();
  1058. co.fill();
  1059. co.stroke();
  1060. // Draw the side section
  1061. co.beginPath();
  1062. co.moveTo(startX + individualBarWidth, startY);
  1063. co.lineTo(startX + individualBarWidth + 10, startY - 5);
  1064. co.lineTo(startX + individualBarWidth + 10, startY - 5 + height);
  1065. co.lineTo(startX + individualBarWidth , startY + height);
  1066. co.closePath();
  1067. co.fill();
  1068. co.stroke();
  1069. // Draw the darker top side
  1070. co.fillStyle = 'rgba(255,255,255,0.3)';
  1071. co.beginPath();
  1072. co.moveTo(startX, startY);
  1073. co.lineTo(startX + 10, startY - 5);
  1074. co.lineTo(startX + 10 + individualBarWidth, startY - 5);
  1075. co.lineTo(startX + individualBarWidth, startY);
  1076. co.closePath();
  1077. co.fill();
  1078. co.stroke();
  1079. // Draw the darker side section
  1080. co.fillStyle = 'rgba(0,0,0,0.4)';
  1081. co.beginPath();
  1082. co.moveTo(startX + individualBarWidth, startY);
  1083. co.lineTo(startX + individualBarWidth + 10, startY - 5);
  1084. co.lineTo(startX + individualBarWidth + 10, startY - 5 + height);
  1085. co.lineTo(startX + individualBarWidth , startY + height);
  1086. co.closePath();
  1087. co.fill();
  1088. co.stroke();
  1089. co.strokeStyle = prevStrokeStyle;
  1090. co.fillStyle = prevFillStyle;
  1091. }
  1092. if (height < 0) {
  1093. height = Math.abs(height);
  1094. startY = startY - height;
  1095. }
  1096. this.coords.push([startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height]);
  1097. if (typeof this.coords2[i] == 'undefined') {
  1098. this.coords2[i] = [];
  1099. }
  1100. this.coords2[i].push([startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height]);
  1101. // Facilitate shadows going to the left
  1102. if (prop['chart.shadow']) {
  1103. redrawCoords.push([startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height, co.fillStyle]);
  1104. }
  1105. }
  1106. /**
  1107. * Redraw the bar if shadows are going to the left
  1108. */
  1109. if (redrawCoords.length) {
  1110. RGraph.NoShadow(this);
  1111. co.lineWidth = prop['chart.linewidth'];
  1112. co.beginPath();
  1113. for (var j=0; j<redrawCoords.length; ++j) {
  1114. co.fillStyle = redrawCoords[j][4];
  1115. co.strokeStyle = prop['chart.strokecolor'];
  1116. co.fillRect(redrawCoords[j][0], redrawCoords[j][1], redrawCoords[j][2], redrawCoords[j][3]);
  1117. co.strokeRect(redrawCoords[j][0], redrawCoords[j][1], redrawCoords[j][2], redrawCoords[j][3]);
  1118. }
  1119. co.fill();
  1120. co.stroke();
  1121. redrawCoords = [];
  1122. }
  1123. } else {
  1124. this.coords.push([]);
  1125. }
  1126. co.closePath();
  1127. }
  1128. /**
  1129. * Turn off any shadow
  1130. */
  1131. RGraph.NoShadow(this);
  1132. };
  1133. /**
  1134. * Draws the labels for the graph
  1135. */
  1136. this.drawLabels =
  1137. this.DrawLabels = function ()
  1138. {
  1139. // Variable "caching" so the context can be accessed as a local variable
  1140. // TODO Doesn't really need to be done as the variables come from the constructor
  1141. var ca = this.canvas;
  1142. var co = this.context;
  1143. var prop = this.properties;
  1144. var context = co;
  1145. var text_angle = prop['chart.text.angle'];
  1146. var text_size = prop['chart.text.size'];
  1147. var labels = prop['chart.labels'];
  1148. // Draw the Y axis labels:
  1149. if (prop['chart.ylabels']) {
  1150. if (prop['chart.xaxispos'] == 'top') this.Drawlabels_top();
  1151. if (prop['chart.xaxispos'] == 'center') this.Drawlabels_center();
  1152. if (prop['chart.xaxispos'] == 'bottom') this.Drawlabels_bottom();
  1153. }
  1154. /**
  1155. * The X axis labels
  1156. */
  1157. if (typeof(labels) == 'object' && labels) {
  1158. var yOffset = Number(prop['chart.xlabels.offset']);
  1159. /**
  1160. * Text angle
  1161. */
  1162. if (prop['chart.text.angle'] != 0) {
  1163. var valign = 'center';
  1164. var halign = 'right';
  1165. var angle = 0 - prop['chart.text.angle'];
  1166. } else {
  1167. var valign = 'top';
  1168. var halign = 'center';
  1169. var angle = 0;
  1170. }
  1171. // Draw the X axis labels
  1172. co.fillStyle = prop['chart.text.color'];
  1173. // How wide is each bar
  1174. var barWidth = (ca.width - this.gutterRight - this.gutterLeft) / labels.length;
  1175. // Reset the xTickGap
  1176. xTickGap = (ca.width - this.gutterRight - this.gutterLeft) / labels.length
  1177. // Draw the X tickmarks
  1178. var i=0;
  1179. var font = prop['chart.text.font'];
  1180. for (x=this.gutterLeft + (xTickGap / 2); x<=ca.width - this.gutterRight; x+=xTickGap) {
  1181. RGraph.Text2(this, {'font': font,
  1182. 'size': text_size,
  1183. 'x': x,
  1184. 'y': prop['chart.xaxispos'] == 'top' ? this.gutterTop - yOffset - 5: (ca.height - this.gutterBottom) + yOffset + 3,
  1185. 'text': String(labels[i++]),
  1186. 'valign': prop['chart.xaxispos'] == 'top' ? 'bottom' : valign,
  1187. 'halign': halign,
  1188. 'tag':'label',
  1189. 'marker':false,
  1190. 'angle':angle,
  1191. 'tag': 'labels'
  1192. });
  1193. }
  1194. }
  1195. /**
  1196. * Draw above labels
  1197. */
  1198. this.drawAboveLabels();
  1199. };
  1200. /**
  1201. * Draws the X axis at the top
  1202. */
  1203. this.drawlabels_top =
  1204. this.Drawlabels_top = function ()
  1205. {
  1206. var ca = this.canvas;
  1207. var co = this.context;
  1208. var prop = this.properties;
  1209. co.beginPath();
  1210. co.fillStyle = prop['chart.text.color'];
  1211. co.strokeStyle = 'black';
  1212. if (prop['chart.xaxispos'] == 'top') {
  1213. var context = co;
  1214. var text_size = prop['chart.text.size'];
  1215. var units_pre = prop['chart.units.pre'];
  1216. var units_post = prop['chart.units.post'];
  1217. var align = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
  1218. var font = prop['chart.text.font'];
  1219. var numYLabels = prop['chart.ylabels.count'];
  1220. var ymin = prop['chart.ymin'];
  1221. if (prop['chart.ylabels.inside'] == true) {
  1222. var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft + 5 : ca.width - this.gutterRight - 5;
  1223. var align = prop['chart.yaxispos'] == 'left' ? 'left' : 'right';
  1224. var boxed = true;
  1225. } else {
  1226. var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
  1227. var boxed = false;
  1228. }
  1229. /**
  1230. * Draw specific Y labels here so that the local variables can be reused
  1231. */
  1232. if (typeof(prop['chart.ylabels.specific']) == 'object' && prop['chart.ylabels.specific']) {
  1233. var labels = RGraph.array_reverse(prop['chart.ylabels.specific']);
  1234. var grapharea = ca.height - this.gutterTop - this.gutterBottom;
  1235. for (var i=0; i<labels.length; ++i) {
  1236. var y = this.gutterTop + (grapharea * (i / labels.length)) + (grapharea / labels.length);
  1237. RGraph.Text2(this, {'font': font,
  1238. 'size': text_size,
  1239. 'x': xpos,
  1240. 'y': y,
  1241. 'text': String(labels[i]),
  1242. 'valign': 'center',
  1243. 'halign': align,
  1244. 'bordered':boxed,
  1245. 'tag': 'scale'
  1246. });
  1247. }
  1248. return;
  1249. }
  1250. /**
  1251. * Draw the scale
  1252. */
  1253. var labels = this.scale2.labels;
  1254. for (var i=0; i<labels.length; ++i) {
  1255. RGraph.Text2(this, {'font': font,
  1256. 'size':text_size,
  1257. 'x':xpos,
  1258. 'y':this.gutterTop + ((this.grapharea / labels.length) * (i + 1)),
  1259. 'text': '-' + labels[i],
  1260. 'valign': 'center',
  1261. 'halign': align,
  1262. 'bordered': boxed,
  1263. 'tag':'scale'});
  1264. }
  1265. /**
  1266. * Show the minimum value if its not zero
  1267. */
  1268. if (prop['chart.ymin'] != 0 || prop['chart.noxaxis'] || prop['chart.scale.zerostart']) {
  1269. RGraph.Text2(this, {'font': font,
  1270. 'size': text_size,
  1271. 'x': xpos,
  1272. 'y': this.gutterTop,
  1273. 'text': (this.scale2.min != 0 ? '-' : '') + RGraph.number_format(this,(this.scale2.min.toFixed((prop['chart.scale.decimals']))), units_pre, units_post),
  1274. 'valign': 'center',
  1275. 'halign': align,
  1276. 'bordered': boxed,
  1277. 'tag': 'scale'});
  1278. }
  1279. }
  1280. co.fill();
  1281. };
  1282. /**
  1283. * Draws the X axis in the middle
  1284. */
  1285. this.drawlabels_center =
  1286. this.Drawlabels_center = function ()
  1287. {
  1288. var ca = this.canvas;
  1289. var co = this.context;
  1290. var prop = this.properties;
  1291. var font = prop['chart.text.font'];
  1292. var numYLabels = prop['chart.ylabels.count'];
  1293. co.fillStyle = prop['chart.text.color'];
  1294. if (prop['chart.xaxispos'] == 'center') {
  1295. /**
  1296. * Draw the top labels
  1297. */
  1298. var text_size = prop['chart.text.size'];
  1299. var units_pre = prop['chart.units.pre'];
  1300. var units_post = prop['chart.units.post'];
  1301. var context = co;
  1302. var align = '';
  1303. var xpos = 0;
  1304. var boxed = false;
  1305. var ymin = prop['chart.ymin'];
  1306. co.fillStyle = prop['chart.text.color'];
  1307. co.strokeStyle = 'black';
  1308. if (prop['chart.ylabels.inside'] == true) {
  1309. var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft + 5 : ca.width - this.gutterRight - 5;
  1310. var align = prop['chart.yaxispos'] == 'left' ? 'left' : 'right';
  1311. var boxed = true;
  1312. } else {
  1313. var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
  1314. var align = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
  1315. var boxed = false;
  1316. }
  1317. /**
  1318. * Draw specific Y labels here so that the local variables can be reused
  1319. */
  1320. if (typeof(prop['chart.ylabels.specific']) == 'object' && prop['chart.ylabels.specific']) {
  1321. var labels = prop['chart.ylabels.specific'];
  1322. var grapharea = ca.height - this.gutterTop - this.gutterBottom;
  1323. // Draw the top halves labels
  1324. for (var i=0; i<labels.length; ++i) {
  1325. var y = this.gutterTop + ((grapharea / 2) / (labels.length - 1)) * i;
  1326. RGraph.Text2(this, {'font':font,
  1327. 'size':text_size,
  1328. 'x':xpos,
  1329. 'y':y,
  1330. 'text':String(labels[i]),
  1331. 'valign':'center',
  1332. 'halign':align,
  1333. 'bordered':boxed,
  1334. 'tag': 'scale'
  1335. });
  1336. }
  1337. // Draw the bottom halves labels
  1338. for (var i=labels.length-1; i>=1; --i) {
  1339. var y = this.gutterTop + (grapharea * (i / ((labels.length - 1) * 2) )) + (grapharea / 2);
  1340. RG.Text2(this, {'font':font,
  1341. 'size':text_size,
  1342. 'x':xpos,
  1343. 'y':y,
  1344. 'text':String(labels[labels.length - i - 1]),
  1345. 'valign':'center',
  1346. 'halign':align,
  1347. 'bordered':boxed,
  1348. 'tag': 'scale'
  1349. });
  1350. }
  1351. return;
  1352. }
  1353. /**
  1354. * Draw the top halfs labels
  1355. */
  1356. for (var i=0; i<this.scale2.labels.length; ++i) {
  1357. var y = this.gutterTop + this.halfgrapharea - ((this.halfgrapharea / numYLabels) * (i + 1));
  1358. var text = this.scale2.labels[i];
  1359. RG.Text2(this, {'font':font, 'size':text_size, 'x':xpos, 'y':y, 'text': text, 'valign':'center', 'halign': align, 'bordered': boxed, 'tag':'scale'});
  1360. }
  1361. /**
  1362. * Draw the bottom halfs labels
  1363. */
  1364. for (var i=(this.scale2.labels.length - 1); i>=0; --i) {
  1365. var y = this.gutterTop + ((this.halfgrapharea / numYLabels) * (i + 1)) + this.halfgrapharea;
  1366. var text = this.scale2.labels[i];
  1367. RG.Text2(this, {'font':font, 'size':text_size,'x':xpos,'y':y,'text': '-' + text,'valign':'center','halign': align,'bordered': boxed,'tag':'scale'});
  1368. }
  1369. /**
  1370. * Show the minimum value if its not zero
  1371. */
  1372. if (this.scale2.min != 0 || prop['chart.scale.zerostart']) {
  1373. RG.Text2(this, {'font':font,'size':text_size, 'x':xpos, 'y':this.gutterTop + this.halfgrapharea,'text': RGraph.number_format(this,(this.scale2.min.toFixed((prop['chart.scale.decimals']))), units_pre, units_post),'valign':'center', 'valign':'center','halign': align, 'bordered': boxed, 'tag':'scale'});
  1374. }
  1375. }
  1376. };
  1377. /**
  1378. * Draws the X axdis at the bottom (the default)
  1379. */
  1380. this.drawlabels_bottom =
  1381. this.Drawlabels_bottom = function ()
  1382. {
  1383. var co = this.context;
  1384. var ca = this.canvas;
  1385. var prop = this.properties;
  1386. var text_size = prop['chart.text.size'];
  1387. var units_pre = prop['chart.units.pre'];
  1388. var units_post = prop['chart.units.post'];
  1389. var context = this.context;
  1390. var align = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
  1391. var font = prop['chart.text.font'];
  1392. var numYLabels = prop['chart.ylabels.count'];
  1393. var ymin = prop['chart.ymin'];
  1394. co.beginPath();
  1395. co.fillStyle = prop['chart.text.color'];
  1396. co.strokeStyle = 'black';
  1397. if (prop['chart.ylabels.inside'] == true) {
  1398. var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft + 5 : ca.width - this.gutterRight - 5;
  1399. var align = prop['chart.yaxispos'] == 'left' ? 'left' : 'right';
  1400. var boxed = true;
  1401. } else {
  1402. var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
  1403. var boxed = false;
  1404. }
  1405. /**
  1406. * Draw specific Y labels here so that the local variables can be reused
  1407. */
  1408. if (prop['chart.ylabels.specific'] && typeof(prop['chart.ylabels.specific']) == 'object') {
  1409. var labels = prop['chart.ylabels.specific'];
  1410. var grapharea = ca.height - this.gutterTop - this.gutterBottom;
  1411. for (var i=0; i<labels.length; ++i) {
  1412. var y = this.gutterTop + (grapharea * (i / (labels.length - 1)));
  1413. RGraph.Text2(this, {'font':font,
  1414. 'size':text_size,
  1415. 'x':xpos,
  1416. 'y':y,
  1417. 'text': labels[i],
  1418. 'valign':'center',
  1419. 'halign': align,
  1420. 'bordered': boxed,
  1421. 'tag':'scale'
  1422. });
  1423. }
  1424. return;
  1425. }
  1426. var gutterTop = this.gutterTop;
  1427. var halfTextHeight = this.halfTextHeight;
  1428. var scale = this.scale;
  1429. for (var i=0; i<numYLabels; ++i) {
  1430. var text = this.scale2.labels[i];
  1431. RGraph.Text2(this, {'font':font,
  1432. 'size':text_size,
  1433. 'x':xpos,
  1434. 'y':this.gutterTop + this.grapharea - ((this.grapharea / numYLabels) * (i+1)),
  1435. 'text': text,
  1436. 'valign':'center',
  1437. 'halign': align,
  1438. 'bordered': boxed,
  1439. 'tag':'scale'});
  1440. }
  1441. /**
  1442. * Show the minimum value if its not zero
  1443. */
  1444. if (prop['chart.ymin'] != 0 || prop['chart.noxaxis'] || prop['chart.scale.zerostart']) {
  1445. RG.Text2(this, {'font':font,
  1446. 'size':text_size,
  1447. 'x':xpos,
  1448. 'y':ca.height - this.gutterBottom,
  1449. 'text': RG.number_format(this,(this.scale2.min.toFixed((prop['chart.scale.decimals']))), units_pre, units_post),
  1450. 'valign':'center',
  1451. 'halign': align,
  1452. 'bordered': boxed,
  1453. 'tag':'scale'});
  1454. }
  1455. co.fill();
  1456. };
  1457. /**
  1458. * This function is used by MSIE only to manually draw the shadow
  1459. *
  1460. * @param array coords The coords for the bar
  1461. */
  1462. this.drawIEShadow =
  1463. this.DrawIEShadow = function (coords)
  1464. {
  1465. var co = this.context;
  1466. var ca = this.canvas;
  1467. var prop = this.properties;
  1468. var prevFillStyle = co.fillStyle;
  1469. var offsetx = prop['chart.shadow.offsetx'];
  1470. var offsety = prop['chart.shadow.offsety'];
  1471. co.lineWidth = prop['chart.linewidth'];
  1472. co.fillStyle = prop['chart.shadow.color'];
  1473. co.beginPath();
  1474. // Draw shadow here
  1475. co.fillRect(coords[0] + offsetx, coords[1] + offsety, coords[2], coords[3]);
  1476. co.fill();
  1477. // Change the fillstyle back to what it was
  1478. co.fillStyle = prevFillStyle;
  1479. };
  1480. /**
  1481. * Not used by the class during creating the graph, but is used by event handlers
  1482. * to get the coordinates (if any) of the selected bar
  1483. *
  1484. * @param object e The event object
  1485. * @param object OPTIONAL You can pass in the bar object instead of the
  1486. * function using "this"
  1487. */
  1488. this.getShape =
  1489. this.getBar = function (e)
  1490. {
  1491. // This facilitates you being able to pass in the bar object as a parameter instead of
  1492. // the function getting it from itself
  1493. var obj = arguments[1] ? arguments[1] : this;
  1494. var mouseXY = RGraph.getMouseXY(e);
  1495. var mouseX = mouseXY[0];
  1496. var mouseY = mouseXY[1];
  1497. var canvas = obj.canvas;
  1498. var context = obj.context;
  1499. var coords = obj.coords
  1500. for (var i=0,len=coords.length; i<len; i+=1) {
  1501. if (obj.coords[i].length == 0) {
  1502. continue;
  1503. }
  1504. var left = coords[i][0];
  1505. var top = coords[i][1];
  1506. var width = coords[i][2];
  1507. var height = coords[i][3];
  1508. var prop = obj.properties;
  1509. if (mouseX >= left && mouseX <= (left + width) && mouseY >= top && mouseY <= (top + height)) {
  1510. if (prop['chart.tooltips']) {
  1511. var tooltip = RGraph.parseTooltipText ? RGraph.parseTooltipText(prop['chart.tooltips'], i) : prop['chart.tooltips'][i];
  1512. }
  1513. // Work out the dataset
  1514. var dataset = 0;
  1515. var idx = i;
  1516. while (idx >= (typeof(obj.data[dataset]) == 'object' && obj.data[dataset] ? obj.data[dataset].length : 1)) {
  1517. if (typeof(obj.data[dataset]) == 'number') {
  1518. idx -= 1;
  1519. } else if (obj.data[dataset]) { // Accounts for null being an object
  1520. idx -= obj.data[dataset].length;
  1521. } else {
  1522. idx -= 1;
  1523. }
  1524. dataset++;
  1525. }
  1526. if (typeof(obj.data[dataset]) == 'number') {
  1527. idx = null;
  1528. }
  1529. return {
  1530. 0: obj, 1: left, 2: top, 3: width, 4: height, 5: i,
  1531. 'object': obj, 'x': left, 'y': top, 'width': width, 'height': height, 'index': i, 'tooltip': tooltip, 'index_adjusted': idx, 'dataset': dataset
  1532. };
  1533. }
  1534. }
  1535. return null;
  1536. };
  1537. /**
  1538. * This retrives the bar based on the X coordinate only.
  1539. *
  1540. * @param object e The event object
  1541. * @param object OPTIONAL You can pass in the bar object instead of the
  1542. * function using "this"
  1543. */
  1544. this.getShapeByX = function (e)
  1545. {
  1546. var canvas = e.target;
  1547. var mouseCoords = RGraph.getMouseXY(e);
  1548. // This facilitates you being able to pass in the bar object as a parameter instead of
  1549. // the function getting it from itself
  1550. var obj = arguments[1] ? arguments[1] : this;
  1551. /**
  1552. * Loop through the bars determining if the mouse is over a bar
  1553. */
  1554. for (var i=0,len=obj.coords.length; i<len; i++) {
  1555. if (obj.coords[i].length == 0) {
  1556. continue;
  1557. }
  1558. var mouseX = mouseCoords[0];
  1559. var mouseY = mouseCoords[1];
  1560. var left = obj.coords[i][0];
  1561. var top = obj.coords[i][1];
  1562. var width = obj.coords[i][2];
  1563. var height = obj.coords[i][3];
  1564. var prop = obj.properties;
  1565. if (mouseX >= left && mouseX <= (left + width)) {
  1566. if (prop['chart.tooltips']) {
  1567. var tooltip = RGraph.parseTooltipText ? RGraph.parseTooltipText(prop['chart.tooltips'], i) : prop['chart.tooltips'][i];
  1568. }
  1569. return {
  1570. 0: obj, 1: left, 2: top, 3: width, 4: height, 5: i,
  1571. 'object': obj, 'x': left, 'y': top, 'width': width, 'height': height, 'index': i, 'tooltip': tooltip
  1572. };
  1573. }
  1574. }
  1575. return null;
  1576. };
  1577. /**
  1578. * When you click on the chart, this method can return the Y value at that point. It works for any point on the
  1579. * chart (that is inside the gutters) - not just points within the Bars.
  1580. *
  1581. * EITHER:
  1582. *
  1583. * @param object arg The event object
  1584. *
  1585. * OR:
  1586. *
  1587. * @param object arg A two element array containing the X and Y coordinates
  1588. */
  1589. this.getValue = function (arg)
  1590. {
  1591. var co = this.context;
  1592. var ca = this.canvas;
  1593. var prop = this.properties;
  1594. if (arg.length == 2) {
  1595. var mouseX = arg[0];
  1596. var mouseY = arg[1];
  1597. } else {
  1598. var mouseCoords = RGraph.getMouseXY(arg);
  1599. var mouseX = mouseCoords[0];
  1600. var mouseY = mouseCoords[1];
  1601. }
  1602. if ( mouseY < prop['chart.gutter.top']
  1603. || mouseY > (ca.height - prop['chart.gutter.bottom'])
  1604. || mouseX < prop['chart.gutter.left']
  1605. || mouseX > (ca.width - prop['chart.gutter.right'])
  1606. ) {
  1607. return null;
  1608. }
  1609. if (prop['chart.xaxispos'] == 'center') {
  1610. var value = (((this.grapharea / 2) - (mouseY - prop['chart.gutter.top'])) / this.grapharea) * (this.scale2.max - this.scale2.min)
  1611. value *= 2;
  1612. if (value >= 0) {
  1613. value += this.scale2.min;
  1614. } else {
  1615. value -= this.scale2.min;
  1616. }
  1617. } else if (prop['chart.xaxispos'] == 'top') {
  1618. var value = ((this.grapharea - (mouseY - prop['chart.gutter.top'])) / this.grapharea) * (this.scale2.max - this.scale2.min)
  1619. value = this.scale2.max - value;
  1620. value = Math.abs(value) * -1;
  1621. } else {
  1622. var value = ((this.grapharea - (mouseY - prop['chart.gutter.top'])) / this.grapharea) * (this.scale2.max - this.scale2.min)
  1623. value += this.scale2.min;
  1624. }
  1625. return value;
  1626. };
  1627. /**
  1628. * This function can be used when the canvas is clicked on (or similar - depending on the event)
  1629. * to retrieve the relevant Y coordinate for a particular value.
  1630. *
  1631. * @param int value The value to get the Y coordinate for
  1632. */
  1633. this.getYCoord = function (value)
  1634. {
  1635. if (value > this.scale2.max) {
  1636. return null;
  1637. }
  1638. var co = this.context;
  1639. var ca = this.canvas;
  1640. var prop = this.properties;
  1641. var y;
  1642. var xaxispos = prop['chart.xaxispos'];
  1643. if (xaxispos == 'top') {
  1644. // Account for negative numbers
  1645. if (value < 0) {
  1646. value = Math.abs(value);
  1647. }
  1648. y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)) * this.grapharea;
  1649. y = y + this.gutterTop
  1650. } else if (xaxispos == 'center') {
  1651. y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)) * (this.grapharea / 2);
  1652. y = (this.grapharea / 2) - y;
  1653. y += this.gutterTop;
  1654. } else {
  1655. if (value < this.scale2.min) {
  1656. value = this.scale2.min;
  1657. }
  1658. y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)) * this.grapharea;
  1659. y = ca.height - this.gutterBottom - y;
  1660. }
  1661. return y;
  1662. };
  1663. /**
  1664. * Each object type has its own Highlight() function which highlights the appropriate shape
  1665. *
  1666. * @param object shape The shape to highlight
  1667. */
  1668. this.highlight =
  1669. this.Highlight = function (shape)
  1670. {
  1671. // Add the new highlight
  1672. RGraph.Highlight.Rect(this, shape);
  1673. };
  1674. /**
  1675. * The getObjectByXY() worker method
  1676. */
  1677. this.getObjectByXY = function (e)
  1678. {
  1679. var ca = this.canvas;
  1680. var prop = this.properties;
  1681. var mouseXY = RGraph.getMouseXY(e);
  1682. if (
  1683. mouseXY[0] >= prop['chart.gutter.left']
  1684. && mouseXY[0] <= (ca.width - prop['chart.gutter.right'])
  1685. && mouseXY[1] >= prop['chart.gutter.top']
  1686. && mouseXY[1] <= (ca.height - prop['chart.gutter.bottom'])
  1687. ) {
  1688. return this;
  1689. }
  1690. };
  1691. /**
  1692. * This method handles the adjusting calculation for when the mouse is moved
  1693. *
  1694. * @param object e The event object
  1695. */
  1696. this.adjusting_mousemove =
  1697. this.Adjusting_mousemove = function (e)
  1698. {
  1699. /**
  1700. * Handle adjusting for the Bar
  1701. */
  1702. if (prop['chart.adjustable'] && RG.Registry.Get('chart.adjusting') && RG.Registry.Get('chart.adjusting').uid == this.uid) {
  1703. // Rounding the value to the given number of decimals make the chart step
  1704. var value = Number(this.getValue(e));
  1705. var shape = this.getShapeByX(e);
  1706. if (shape) {
  1707. RG.Registry.Set('chart.adjusting.shape', shape);
  1708. if (this.stackedOrGrouped && prop['chart.grouping'] == 'grouped') {
  1709. var indexes = RG.sequentialIndexToGrouped(shape['index'], this.data);
  1710. if (typeof this.data[indexes[0]] == 'number') {
  1711. this.data[indexes[0]] = Number(value);
  1712. } else if (!RG.is_null(this.data[indexes[0]])) {
  1713. this.data[indexes[0]][indexes[1]] = Number(value);
  1714. }
  1715. } else if (typeof this.data[shape['index']] == 'number') {
  1716. this.data[shape['index']] = Number(value);
  1717. }
  1718. RG.redrawCanvas(e.target);
  1719. RG.fireCustomEvent(this, 'onadjust');
  1720. }
  1721. }
  1722. };
  1723. /**
  1724. * This function positions a tooltip when it is displayed
  1725. *
  1726. * @param obj object The chart object
  1727. * @param int x The X coordinate specified for the tooltip
  1728. * @param int y The Y coordinate specified for the tooltip
  1729. * @param objec tooltip The tooltips DIV element
  1730. */
  1731. this.positionTooltip = function (obj, x, y, tooltip, idx)
  1732. {
  1733. var prop = obj.properties;
  1734. var coordX = obj.coords[tooltip.__index__][0];
  1735. var coordY = obj.coords[tooltip.__index__][1];
  1736. var coordW = obj.coords[tooltip.__index__][2];
  1737. var coordH = obj.coords[tooltip.__index__][3];
  1738. var canvasXY = RGraph.getCanvasXY(obj.canvas);
  1739. var gutterLeft = prop['chart.gutter.left'];
  1740. var gutterTop = prop['chart.gutter.top'];
  1741. var width = tooltip.offsetWidth;
  1742. var height = tooltip.offsetHeight;
  1743. var value = obj.data_arr[tooltip.__index__];
  1744. // Set the top position
  1745. tooltip.style.left = 0;
  1746. tooltip.style.top = canvasXY[1] + coordY - height - 7 + 'px';
  1747. /**
  1748. * If the tooltip is for a negative value - position it underneath the bar
  1749. */
  1750. if (value < 0) {
  1751. tooltip.style.top = canvasXY[1] + coordY + coordH + 7 + 'px';
  1752. }
  1753. // By default any overflow is hidden
  1754. tooltip.style.overflow = '';
  1755. // Inverted arrow
  1756. // data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAFCAMAAACkeOZkAAAAK3RFWHRDcmVhdGlvbiBUaW1lAFNhdCA2IE9jdCAyMDEyIDEyOjQ5OjMyIC0wMDAw2S1RlgAAAAd0SU1FB9wKBgszM4Ed2k4AAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAEZ0FNQQAAsY8L/GEFAAAACVBMVEX/AAC9vb3//+92Pom0AAAAAXRSTlMAQObYZgAAAB1JREFUeNpjYAABRgY4YGRiRDCZYBwQE8qBMEEcAANCACqByy1sAAAAAElFTkSuQmCC
  1757. // The arrow
  1758. var img = new Image();
  1759. img.style.position = 'absolute';
  1760. img.id = '__rgraph_tooltip_pointer__';
  1761. if (value >= 0) {
  1762. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  1763. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  1764. } else {
  1765. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAFCAMAAACkeOZkAAAAK3RFWHRDcmVhdGlvbiBUaW1lAFNhdCA2IE9jdCAyMDEyIDEyOjQ5OjMyIC0wMDAw2S1RlgAAAAd0SU1FB9wKBgszM4Ed2k4AAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAEZ0FNQQAAsY8L/GEFAAAACVBMVEX/AAC9vb3//+92Pom0AAAAAXRSTlMAQObYZgAAAB1JREFUeNpjYAABRgY4YGRiRDCZYBwQE8qBMEEcAANCACqByy1sAAAAAElFTkSuQmCC';
  1766. img.style.top = '-5px';
  1767. }
  1768. tooltip.appendChild(img);
  1769. // Reposition the tooltip if at the edges:
  1770. // LEFT edge
  1771. if ((canvasXY[0] + coordX + (coordW / 2) - (width / 2)) < 10) {
  1772. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + (coordW / 2) + 'px';
  1773. img.style.left = ((width * 0.1) - 8.5) + 'px';
  1774. // RIGHT edge
  1775. } else if ((canvasXY[0] + coordX + (width / 2)) > doc.body.offsetWidth) {
  1776. tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + (coordW / 2) + 'px';
  1777. img.style.left = ((width * 0.9) - 8.5) + 'px';
  1778. // Default positioning - CENTERED
  1779. } else {
  1780. tooltip.style.left = (canvasXY[0] + coordX + (coordW / 2) - (width * 0.5)) + 'px';
  1781. img.style.left = ((width * 0.5) - 8.5) + 'px';
  1782. }
  1783. };
  1784. /**
  1785. * This allows for easy specification of gradients
  1786. */
  1787. this.parseColors = function ()
  1788. {
  1789. // Save the original colors so that they can be restored when the canvas is reset
  1790. if (this.original_colors.length === 0) {
  1791. this.original_colors['chart.colors'] = RGraph.array_clone(prop['chart.colors']);
  1792. this.original_colors['chart.key.colors'] = RGraph.array_clone(prop['chart.key.colors']);
  1793. this.original_colors['chart.crosshairs.color'] = prop['chart.crosshairs.color'];
  1794. this.original_colors['chart.highlight.stroke'] = prop['chart.highlight.stroke'];
  1795. this.original_colors['chart.highlight.fill'] = prop['chart.highlight.fill'];
  1796. this.original_colors['chart.text.color'] = prop['chart.text.color'];
  1797. this.original_colors['chart.background.barcolor1'] = prop['chart.background.barcolor1'];
  1798. this.original_colors['chart.background.barcolor2'] = prop['chart.background.barcolor2'];
  1799. this.original_colors['chart.background.grid.color'] = prop['chart.background.grid.color'];
  1800. this.original_colors['chart.background.color'] = prop['chart.background.color'];
  1801. this.original_colors['chart.strokecolor'] = prop['chart.strokecolor'];
  1802. this.original_colors['chart.axis.color'] = prop['chart.axis.color'];
  1803. }
  1804. // chart.colors
  1805. var colors = prop['chart.colors'];
  1806. if (colors) {
  1807. for (var i=0; i<colors.length; ++i) {
  1808. colors[i] = this.parseSingleColorForGradient(colors[i]);
  1809. }
  1810. }
  1811. // chart.key.colors
  1812. var colors = prop['chart.key.colors'];
  1813. if (colors) {
  1814. for (var i=0; i<colors.length; ++i) {
  1815. colors[i] = this.parseSingleColorForGradient(colors[i]);
  1816. }
  1817. }
  1818. prop['chart.crosshairs.color'] = this.parseSingleColorForGradient(prop['chart.crosshairs.color']);
  1819. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  1820. prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
  1821. prop['chart.text.color'] = this.parseSingleColorForGradient(prop['chart.text.color']);
  1822. prop['chart.background.barcolor1'] = this.parseSingleColorForGradient(prop['chart.background.barcolor1']);
  1823. prop['chart.background.barcolor2'] = this.parseSingleColorForGradient(prop['chart.background.barcolor2']);
  1824. prop['chart.background.grid.color'] = this.parseSingleColorForGradient(prop['chart.background.grid.color']);
  1825. prop['chart.background.color'] = this.parseSingleColorForGradient(prop['chart.background.color']);
  1826. prop['chart.strokecolor'] = this.parseSingleColorForGradient(prop['chart.strokecolor']);
  1827. prop['chart.axis.color'] = this.parseSingleColorForGradient(prop['chart.axis.color']);
  1828. };
  1829. /**
  1830. * This parses a single color value
  1831. */
  1832. this.parseSingleColorForGradient = function (color)
  1833. {
  1834. if (!color || typeof(color) != 'string') {
  1835. return color;
  1836. }
  1837. if (color.match(/^gradient\((.*)\)$/i)) {
  1838. var parts = RegExp.$1.split(':');
  1839. // Create the gradient
  1840. var grad = co.createLinearGradient(0,ca.height - prop['chart.gutter.bottom'], 0, prop['chart.gutter.top']);
  1841. var diff = 1 / (parts.length - 1);
  1842. grad.addColorStop(0, RG.trim(parts[0]));
  1843. for (var j=1,len=parts.length; j<len; ++j) {
  1844. grad.addColorStop(j * diff, RGraph.trim(parts[j]));
  1845. }
  1846. }
  1847. return grad ? grad : color;
  1848. };
  1849. this.drawBevel =
  1850. this.DrawBevel = function ()
  1851. {
  1852. var coords = this.coords;
  1853. var coords2 = this.coords2;
  1854. var prop = this.properties;
  1855. var co = this.context;
  1856. var ca = this.canvas;
  1857. if (prop['chart.grouping'] == 'stacked') {
  1858. for (var i=0; i<coords2.length; ++i) {
  1859. if (coords2[i] && coords2[i][0] && coords2[i][0][0]) {
  1860. var x = coords2[i][0][0];
  1861. var y = coords2[i][0][1];
  1862. var w = coords2[i][0][2];
  1863. var arr = [];
  1864. for (var j=0; j<coords2[i].length; ++j) {
  1865. arr.push(coords2[i][j][3]);
  1866. }
  1867. var h = RGraph.array_sum(arr);
  1868. co.save();
  1869. co.strokeStyle = 'black';
  1870. // Clip to the rect
  1871. co.beginPath();
  1872. co.rect(x, y, w, h);
  1873. co.clip();
  1874. // Add the shadow
  1875. co.shadowColor = 'black';
  1876. co.shadowOffsetX = 0;
  1877. co.shadowOffsetY = 0;
  1878. co.shadowBlur = 20;
  1879. co.beginPath();
  1880. co.rect(x - 3, y - 3, w + 6, h + 100);
  1881. co.lineWidth = 5;
  1882. co.stroke();
  1883. co.restore();
  1884. }
  1885. }
  1886. } else {
  1887. for (var i=0; i<coords.length; ++i) {
  1888. if (coords[i]) {
  1889. var x = coords[i][0];
  1890. var y = coords[i][1];
  1891. var w = coords[i][2];
  1892. var h = coords[i][3];
  1893. var xaxispos = prop['chart.xaxispos'];
  1894. var xaxis_ycoord = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop;
  1895. co.save();
  1896. co.strokeStyle = 'black';
  1897. // Clip to the rect
  1898. co.beginPath();
  1899. co.rect(x, y, w, h);
  1900. co.clip();
  1901. // Add the shadow
  1902. co.shadowColor = 'black';
  1903. co.shadowOffsetX = 0;
  1904. co.shadowOffsetY = 0;
  1905. co.shadowBlur = 20;
  1906. if (xaxispos == 'top' || (xaxispos == 'center' && (y + h) > xaxis_ycoord)) {
  1907. y = y - 100;
  1908. h = h + 100;
  1909. } else {
  1910. y = y;
  1911. h = h + 100;
  1912. }
  1913. co.beginPath();
  1914. co.rect(x - 3, y - 3, w + 6, h + 6);
  1915. co.lineWidth = 5;
  1916. co.stroke();
  1917. co.restore();
  1918. }
  1919. }
  1920. }
  1921. };
  1922. /**
  1923. * This function handles highlighting an entire data-series for the interactive
  1924. * key
  1925. *
  1926. * @param int index The index of the data series to be highlighted
  1927. */
  1928. this.interactiveKeyHighlight = function (index)
  1929. {
  1930. this.coords2.forEach(function (value, idx, arr)
  1931. {
  1932. if (typeof value[index] == 'object' && value[index]) {
  1933. var x = value[index][0]
  1934. var y = value[index][1]
  1935. var w = value[index][2]
  1936. var h = value[index][3]
  1937. co.fillStyle = prop['chart.key.interactive.highlight.chart.fill'];
  1938. co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];
  1939. co.lineWidth = 2;
  1940. co.strokeRect(x, y, w, h);
  1941. co.fillRect(x, y, w, h);
  1942. }
  1943. });
  1944. };
  1945. /**
  1946. * Using a function to add events makes it easier to facilitate method chaining
  1947. *
  1948. * @param string type The type of even to add
  1949. * @param function func
  1950. */
  1951. this.on = function (type, func)
  1952. {
  1953. if (type.substr(0,2) !== 'on') {
  1954. type = 'on' + type;
  1955. }
  1956. this[type] = func;
  1957. return this;
  1958. };
  1959. /**
  1960. * Draws the above labels
  1961. */
  1962. this.drawAboveLabels = function ()
  1963. {
  1964. var labels = prop['chart.labels.above'];
  1965. var specific = prop['chart.labels.above.specific'];
  1966. var color = prop['chart.labels.above.color'];
  1967. var decimals = prop['chart.labels.above.decimals'];
  1968. var size = prop['chart.labels.above.size'];
  1969. var angle = -1 * prop['chart.labels.above.angle'];
  1970. var coords = this.coords;
  1971. var coords2 = this.coords2;
  1972. var data = this.data;
  1973. var ldata = RG.arrayLinearize(this.data);
  1974. var offset = prop['chart.labels.above.offset'];
  1975. var text_font = prop['chart.text.font'];
  1976. var text_size = prop['chart.text.size'];
  1977. var grouping = prop['chart.grouping']
  1978. // Turn off any shadow
  1979. RG.noShadow(this);
  1980. // Color
  1981. co.fillStyle = typeof color === 'string' ? color : prop['chart.text.color'];
  1982. // This bit draws the text labels that appear above the bars if requested
  1983. if (labels && grouping === 'grouped') {
  1984. for (var i=0,len=data.length,sequentialIndex=0; i<len; i+=1) {
  1985. // Alignment for regular, positive bars
  1986. if (typeof data[i] === 'number' && data[i] >= 0) {
  1987. var angle = angle;
  1988. var halign = (angle ? 'left' : 'center');
  1989. var valign = angle !== 0 ? 'center' : 'bottom';
  1990. RG.text2(this, {'font': text_font,
  1991. 'size': typeof size === 'number' ? size : text_size - 3,
  1992. 'x': coords2[i][0][0] + (coords2[i][0][2] / 2),
  1993. 'y': coords2[i][0][1] - offset,
  1994. 'text': specific ? (specific[sequentialIndex] || '') : RG.numberFormat(this, Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals)), // TODO Units?
  1995. 'halign': halign,
  1996. 'valign': valign,
  1997. 'angle': angle,
  1998. 'marker': false,
  1999. 'bounding': false,
  2000. 'tag': 'labels.above'
  2001. });
  2002. sequentialIndex++;
  2003. // Alignment for regular, negative bars
  2004. } else if (typeof data[i] === 'number' && data[i] < 0) {
  2005. var angle = angle;
  2006. var halign = angle ? 'right' : 'center';
  2007. var valign = angle !== 0 ? 'center' : 'top';
  2008. RG.text2(this, {'font': text_font,
  2009. 'size': typeof size === 'number' ? size : text_size - 3,
  2010. 'x': coords2[i][0][0] + (coords2[i][0][2] / 2),
  2011. 'y': coords2[i][0][1] + coords2[i][0][3] + offset,
  2012. 'text': specific ? (specific[sequentialIndex] || '') : RG.numberFormat(this, Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals)), // TODO Units?
  2013. 'halign': halign,
  2014. 'valign': valign,
  2015. 'angle': angle,
  2016. 'bounding': false,
  2017. 'marker': false,
  2018. 'tag': 'labels.above'
  2019. });
  2020. sequentialIndex++;
  2021. // Alignment for grouped bars
  2022. } else if (typeof data[i] === 'object') {
  2023. for (var j=0,len2=data[i].length; j<len2; j+=1) {
  2024. var angle = angle;
  2025. var halign = data[i][j] < 0 ? 'right' : 'left';
  2026. halign = angle === 0 ? 'center' : halign;
  2027. var valign = data[i][j] < 0 ? 'top' : 'bottom';
  2028. valign = angle != 0 ? 'center' : valign;
  2029. RG.text2(this, {'font': text_font,
  2030. 'size': typeof size === 'number' ? size : text_size - 3,
  2031. 'x': coords2[i][j][0] + (coords2[i][j][2] / 2),
  2032. 'y': coords2[i][j][1] + (data[i][j] < 0 ? coords2[i][j][3] + offset: -offset),
  2033. 'text': specific ? (specific[sequentialIndex] || '') : RG.numberFormat(this, Number(data[i][j]).toFixed(decimals)), // TODO Units?
  2034. 'halign': halign,
  2035. 'valign': valign,
  2036. 'angle': angle,
  2037. 'bounding': false,
  2038. 'marker': false,
  2039. 'tag': 'labels.above'
  2040. });
  2041. sequentialIndex++;
  2042. }
  2043. }
  2044. }
  2045. /**
  2046. * STACKED bars
  2047. */
  2048. } else if (labels && grouping === 'stacked') {
  2049. for (var i=0,len=data.length,sequentialIndex=0; i<len; i+=1) {
  2050. if (typeof data[i] === 'object') {
  2051. var angle = angle;
  2052. var halign = angle != 0 ? 'left' : 'center';
  2053. var valign = angle != 0 ? 'center' : 'bottom';
  2054. RG.text2(this, {'font': text_font,
  2055. 'size': typeof size === 'number' ? size : text_size - 3,
  2056. 'x': coords2[i][0][0] + (coords2[i][0][2] / 2),
  2057. 'y': coords2[i][0][1] + (data[i][0] < 0 ? coords2[i][0][3] : 0) - offset,
  2058. 'text': specific ? (specific[sequentialIndex] || '') : RG.numberFormat(this, Number(RG.arraySum(data[i])).toFixed(decimals)), // TODO Units?
  2059. 'halign': halign,
  2060. 'valign': valign,
  2061. 'angle': angle,
  2062. 'bounding': false,
  2063. 'marker': false,
  2064. 'tag': 'labels.above'
  2065. });
  2066. sequentialIndex += data[i].length;
  2067. /**
  2068. * Regular numbers but in a stacked grouping
  2069. */
  2070. } else {
  2071. var angle = angle;
  2072. var halign = angle != 0 ? 'left' : 'center';
  2073. var valign = angle != 0 ? 'center' : 'bottom';
  2074. RG.text2(this, {'font': text_font,
  2075. 'size': typeof size === 'number' ? size : text_size - 3,
  2076. 'x': coords2[i][0][0] + (coords2[i][0][2] / 2),
  2077. 'y': coords2[i][0][1] + (data[i][0] < 0 ? coords2[i][0][3] : 0) - offset,
  2078. 'text': specific ? (specific[sequentialIndex] || '') : RG.numberFormat(this, Number(data[i]).toFixed(decimals)), // TODO Units?
  2079. 'halign': halign,
  2080. 'valign': valign,
  2081. 'angle': angle,
  2082. 'bounding': false,
  2083. 'marker': false,
  2084. 'tag': 'labels.above'
  2085. });
  2086. sequentialIndex++;
  2087. }
  2088. }
  2089. }
  2090. };
  2091. /**
  2092. * This function runs once only
  2093. */
  2094. this.firstDrawFunc = function ()
  2095. {
  2096. };
  2097. /**
  2098. * Bar chart Wave effect This effect defaults to 30 frames - which is
  2099. * approximately half a second. This the prior, older implementation
  2100. * of the Wave effect. It can be slower due to the many timers set
  2101. *
  2102. * @param object obj The chart object
  2103. */
  2104. this.waveOld = function ()
  2105. {
  2106. var obj = this;
  2107. var opt = arguments[0] ? arguments[0] : {};
  2108. opt.frames = opt.frames ? opt.frames : 15;
  2109. opt.delay = opt.delay || 50;
  2110. var callback = arguments[1] ? arguments[1] : function () {};
  2111. var original_data = [];
  2112. var frame = [];
  2113. var length = obj.data.length;
  2114. obj.draw();
  2115. //var scale = RGraph.getScale2(obj, {'max':obj.max});
  2116. obj.Set('chart.ymax', obj.scale2.max);
  2117. RG.clear(obj.canvas);
  2118. for (var i=0,len=length; i<len; ++i) {
  2119. (function (idx)
  2120. {
  2121. original_data[idx] = obj.data[idx];
  2122. obj.data[idx] = typeof obj.data[idx] === 'object' ? [] : 0;
  2123. frame[idx] = typeof obj.data[idx] === 'object' ? [] : 0;
  2124. setTimeout(function () {iterator(idx, opt.frames);}, opt.delay * idx)
  2125. })(i);
  2126. }
  2127. return this;
  2128. function iterator (idx, frames)
  2129. {
  2130. if (frame[idx] <= frames) {
  2131. // Update the data point
  2132. if (typeof obj.data[idx] === 'number') {
  2133. obj.data[idx] = (frame[idx] / frames) * original_data[idx]
  2134. } else if (typeof obj.data[idx] === 'object') {
  2135. for (var k=0,len=original_data[idx].length; k<len; ++k) {
  2136. obj.data[idx][k] = (frame[idx] / frames) * original_data[idx][k];
  2137. }
  2138. }
  2139. RG.clear(obj.canvas);
  2140. RG.redrawCanvas(obj.canvas);
  2141. ++frame[idx];
  2142. RG.Effects.updateCanvas(function () {iterator(idx, frames);});
  2143. } else if (idx === (length - 1) ) {
  2144. callback(obj);
  2145. }
  2146. }
  2147. };
  2148. /**
  2149. * (new) Bar chart Wave effect. This is a rewrite that should be smoother
  2150. * because it just uses a single loop and not setTimeout
  2151. *
  2152. * @param object OPTIONAL An object map of options. You specify 'frames' here to give the number of frames in the effect
  2153. * @param function OPTIONAL A function that will be called when the effect is complete
  2154. */
  2155. this.wave = function ()
  2156. {
  2157. var obj = this;
  2158. var opt = arguments[0] || {};
  2159. opt.frames = opt.frames || 60;
  2160. opt.startFrames = [];
  2161. opt.counters = [];
  2162. var framesperbar = opt.frames / 3;
  2163. var frame = -1;
  2164. var callback = arguments[1] || function () {};
  2165. var original = RG.arrayClone(obj.data);
  2166. for (var i=0,len=obj.data.length; i<len; i+=1) {
  2167. opt.startFrames[i] = ((opt.frames / 2) / (obj.data.length - 1)) * i;
  2168. if (typeof obj.data[i] === 'object' && obj.data[i]) {
  2169. opt.counters[i] = [];
  2170. for (var j=0; j<obj.data[i].length; j++) {
  2171. opt.counters[i][j] = 0;
  2172. }
  2173. } else {
  2174. opt.counters[i] = 0;
  2175. }
  2176. }
  2177. /**
  2178. * This stops the chart from jumping
  2179. */
  2180. obj.draw();
  2181. obj.Set('ymax', obj.scale2.max);
  2182. RG.clear(obj.canvas);
  2183. function iterator ()
  2184. {
  2185. ++frame;
  2186. for (var i=0,len=obj.data.length; i<len; i+=1) {
  2187. if (frame > opt.startFrames[i]) {
  2188. if (typeof obj.data[i] === 'number') {
  2189. obj.data[i] = ma.min(
  2190. ma.abs(original[i]),
  2191. ma.abs(original[i] * ( (opt.counters[i]++) / framesperbar))
  2192. );
  2193. // Make the number negative if the original was
  2194. if (original[i] < 0) {
  2195. obj.data[i] *= -1;
  2196. }
  2197. } else if (!RG.isNull(obj.data[i])) {
  2198. for (var j=0,len2=obj.data[i].length; j<len2; j+=1) {
  2199. obj.data[i][j] = ma.min(
  2200. ma.abs(original[i][j]),
  2201. ma.abs(original[i][j] * ( (opt.counters[i][j]++) / framesperbar))
  2202. );
  2203. // Make the number negative if the original was
  2204. if (original[i][j] < 0) {
  2205. obj.data[i][j] *= -1;
  2206. }
  2207. }
  2208. }
  2209. } else {
  2210. obj.data[i] = typeof obj.data[i] === 'object' && obj.data[i] ? RG.arrayPad([], obj.data[i].length, 0) : (RG.isNull(obj.data[i]) ? null : 0);
  2211. }
  2212. }
  2213. if (frame >= opt.frames) {
  2214. callback(obj);
  2215. } else {
  2216. RG.redrawCanvas(obj.canvas);
  2217. RG.Effects.updateCanvas(iterator);
  2218. }
  2219. }
  2220. iterator();
  2221. return this;
  2222. };
  2223. /**
  2224. * Grow
  2225. *
  2226. * The Bar chart Grow effect gradually increases the values of the bars
  2227. *
  2228. * @param object An object of options - eg: {frames: 30}
  2229. * @param function A function to call when the effect is complete
  2230. */
  2231. this.grow = function ()
  2232. {
  2233. // Callback
  2234. var opt = arguments[0] || {};
  2235. var frames = opt.frames || 30;
  2236. var frame = 0;
  2237. var callback = arguments[1] || function () {};
  2238. var obj = this;
  2239. // Save the data
  2240. obj.original_data = RGraph.array_clone(obj.data);
  2241. // Stop the scale from changing by setting chart.ymax (if it's not already set)
  2242. if (obj.Get('chart.ymax') == null) {
  2243. var ymax = 0;
  2244. for (var i=0; i<obj.data.length; ++i) {
  2245. if (RG.is_array(obj.data[i]) && obj.Get('chart.grouping') == 'stacked') {
  2246. ymax = ma.max(ymax, ma.abs(RG.array_sum(obj.data[i])));
  2247. } else if (RG.is_array(obj.data[i]) && obj.Get('chart.grouping') == 'grouped') {
  2248. ymax = ma.max(ymax, ma.abs(RG.array_max(obj.data[i])));
  2249. } else {
  2250. ymax = ma.max(ymax, ma.abs(obj.data[i]));
  2251. }
  2252. }
  2253. var scale = RGraph.getScale2(obj, {'max':ymax});
  2254. obj.Set('chart.ymax', scale.max);
  2255. }
  2256. var iterator = function ()
  2257. {
  2258. var easingMultiplier = RG.Effects.getEasingMultiplier(frames, frame);
  2259. // Alter the Bar chart data depending on the frame
  2260. for (var j=0,len=obj.original_data.length; j<len; ++j) {
  2261. if (typeof obj.data[j] === 'object') {
  2262. for (var k=0,len2=obj.data[j].length; k<len2; ++k) {
  2263. obj.data[j][k] = easingMultiplier * obj.original_data[j][k];
  2264. }
  2265. } else {
  2266. obj.data[j] = easingMultiplier * obj.original_data[j];
  2267. }
  2268. }
  2269. //RGraph.clear(obj.canvas);
  2270. RGraph.redrawCanvas(obj.canvas);
  2271. if (frame < frames) {
  2272. frame += 1;
  2273. RG.Effects.updateCanvas(iterator);
  2274. // Call the callback function
  2275. } else {
  2276. callback(obj);
  2277. }
  2278. };
  2279. iterator();
  2280. return this;
  2281. };
  2282. /**
  2283. * Register the object
  2284. */
  2285. RG.register(this);
  2286. /**
  2287. * This the 'end' of the constructor so if the first argument actually
  2288. * contains configuration dta as well - handle that.
  2289. */
  2290. if (parseConfObjectForOptions) {
  2291. RG.parseObjectStyleConfig(this, conf);
  2292. return this;
  2293. }
  2294. };
  2295. /*********************************************************************************************************
  2296. * This is the combined bar and Line class which makes creating bar/line combo charts a little bit easier *
  2297. /*********************************************************************************************************/
  2298. RGraph.CombinedChart = function ()
  2299. {
  2300. /**
  2301. * Create a default empty array for the objects
  2302. */
  2303. this.objects = [];
  2304. var objects = arguments;
  2305. if (RGraph.is_array(arguments[0])) {
  2306. objects = arguments[0];
  2307. }
  2308. for (var i=0; i<objects.length; ++i) {
  2309. this.objects[i] = objects[i];
  2310. /**
  2311. * Set the Line chart gutters to match the Bar chart gutters
  2312. */
  2313. this.objects[i].Set('chart.gutter.left', this.objects[0].Get('chart.gutter.left'));
  2314. this.objects[i].Set('chart.gutter.right', this.objects[0].Get('chart.gutter.right'));
  2315. this.objects[i].Set('chart.gutter.top', this.objects[0].Get('chart.gutter.top'));
  2316. this.objects[i].Set('chart.gutter.bottom', this.objects[0].Get('chart.gutter.bottom'));
  2317. if (this.objects[i].type == 'line') {
  2318. /**
  2319. * Set the line chart hmargin
  2320. */
  2321. this.objects[i].Set('chart.hmargin', ((this.objects[0].canvas.width - this.objects[0].Get('chart.gutter.right') - this.objects[0].Get('chart.gutter.left')) / this.objects[0].data.length) / 2 );
  2322. /**
  2323. * No labels, axes or grid on the Line chart
  2324. */
  2325. this.objects[i].Set('chart.noaxes', true);
  2326. this.objects[i].Set('chart.background.grid', false);
  2327. this.objects[i].Set('chart.ylabels', false);
  2328. }
  2329. /**
  2330. * Resizing
  2331. */
  2332. if (this.objects[i].Get('chart.resizable')) {
  2333. var resizable_object = this.objects[i];
  2334. }
  2335. }
  2336. /**
  2337. * Resizing
  2338. */
  2339. if (resizable_object) {
  2340. /**
  2341. * This recalculates the Line chart hmargin when the chart is resized
  2342. */
  2343. function myOnresizebeforedraw (obj)
  2344. {
  2345. var gutterLeft = obj.Get('chart.gutter.left');
  2346. var gutterRight = obj.Get('chart.gutter.right');
  2347. obj.Set('chart.hmargin', (obj.canvas.width - gutterLeft - gutterRight) / (obj.original_data[0].length * 2));
  2348. }
  2349. RGraph.AddCustomEventListener(resizable_object,
  2350. 'onresizebeforedraw',
  2351. myOnresizebeforedraw);
  2352. }
  2353. };
  2354. /**
  2355. * The Add method can be used to add methods to the CombinedChart object.
  2356. */
  2357. RGraph.CombinedChart.prototype.add =
  2358. RGraph.CombinedChart.prototype.Add = function (obj)
  2359. {
  2360. this.objects.push(obj);
  2361. };
  2362. /**
  2363. * The Draw method goes through all of the objects drawing them (sequentially)
  2364. */
  2365. RGraph.CombinedChart.prototype.draw =
  2366. RGraph.CombinedChart.prototype.Draw = function ()
  2367. {
  2368. for (var i=0; i<this.objects.length; ++i) {
  2369. this.objects[i].Draw();
  2370. }
  2371. };