Chart.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.charting.Chart"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.charting.Chart"] = true;
  8. dojo.provide("dojox.charting.Chart");
  9. dojo.require("dojox.gfx");
  10. dojo.require("dojox.lang.functional");
  11. dojo.require("dojox.lang.functional.fold");
  12. dojo.require("dojox.lang.functional.reversed");
  13. dojo.require("dojox.charting.Element");
  14. dojo.require("dojox.charting.Theme");
  15. dojo.require("dojox.charting.Series");
  16. dojo.require("dojox.charting.axis2d.common");
  17. /*=====
  18. dojox.charting.__ChartCtorArgs = function(margins, stroke, fill, delayInMs){
  19. // summary:
  20. // The keyword arguments that can be passed in a Chart constructor.
  21. //
  22. // margins: Object?
  23. // Optional margins for the chart, in the form of { l, t, r, b}.
  24. // stroke: dojox.gfx.Stroke?
  25. // An optional outline/stroke for the chart.
  26. // fill: dojox.gfx.Fill?
  27. // An optional fill for the chart.
  28. // delayInMs: Number
  29. // Delay in ms for delayedRender(). Default: 200.
  30. this.margins = margins;
  31. this.stroke = stroke;
  32. this.fill = fill;
  33. this.delayInMs = delayInMs;
  34. }
  35. =====*/
  36. (function(){
  37. var df = dojox.lang.functional, dc = dojox.charting, g = dojox.gfx,
  38. clear = df.lambda("item.clear()"),
  39. purge = df.lambda("item.purgeGroup()"),
  40. destroy = df.lambda("item.destroy()"),
  41. makeClean = df.lambda("item.dirty = false"),
  42. makeDirty = df.lambda("item.dirty = true"),
  43. getName = df.lambda("item.name");
  44. dojo.declare("dojox.charting.Chart", null, {
  45. // summary:
  46. // The main chart object in dojox.charting. This will create a two dimensional
  47. // chart based on dojox.gfx.
  48. //
  49. // description:
  50. // dojox.charting.Chart is the primary object used for any kind of charts. It
  51. // is simple to create--just pass it a node reference, which is used as the
  52. // container for the chart--and a set of optional keyword arguments and go.
  53. //
  54. // Note that like most of dojox.gfx, most of dojox.charting.Chart's methods are
  55. // designed to return a reference to the chart itself, to allow for functional
  56. // chaining. This makes defining everything on a Chart very easy to do.
  57. //
  58. // example:
  59. // Create an area chart, with smoothing.
  60. // | new dojox.charting.Chart(node))
  61. // | .addPlot("default", { type: "Areas", tension: "X" })
  62. // | .setTheme(dojox.charting.themes.Shrooms)
  63. // | .addSeries("Series A", [1, 2, 0.5, 1.5, 1, 2.8, 0.4])
  64. // | .addSeries("Series B", [2.6, 1.8, 2, 1, 1.4, 0.7, 2])
  65. // | .addSeries("Series C", [6.3, 1.8, 3, 0.5, 4.4, 2.7, 2])
  66. // | .render();
  67. //
  68. // example:
  69. // The form of data in a data series can take a number of forms: a simple array,
  70. // an array of objects {x,y}, or something custom (as determined by the plot).
  71. // Here's an example of a Candlestick chart, which expects an object of
  72. // { open, high, low, close }.
  73. // | new dojox.charting.Chart(node))
  74. // | .addPlot("default", {type: "Candlesticks", gap: 1})
  75. // | .addAxis("x", {fixLower: "major", fixUpper: "major", includeZero: true})
  76. // | .addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", natural: true})
  77. // | .addSeries("Series A", [
  78. // | { open: 20, close: 16, high: 22, low: 8 },
  79. // | { open: 16, close: 22, high: 26, low: 6, mid: 18 },
  80. // | { open: 22, close: 18, high: 22, low: 11, mid: 21 },
  81. // | { open: 18, close: 29, high: 32, low: 14, mid: 27 },
  82. // | { open: 29, close: 24, high: 29, low: 13, mid: 27 },
  83. // | { open: 24, close: 8, high: 24, low: 5 },
  84. // | { open: 8, close: 16, high: 22, low: 2 },
  85. // | { open: 16, close: 12, high: 19, low: 7 },
  86. // | { open: 12, close: 20, high: 22, low: 8 },
  87. // | { open: 20, close: 16, high: 22, low: 8 },
  88. // | { open: 16, close: 22, high: 26, low: 6, mid: 18 },
  89. // | { open: 22, close: 18, high: 22, low: 11, mid: 21 },
  90. // | { open: 18, close: 29, high: 32, low: 14, mid: 27 },
  91. // | { open: 29, close: 24, high: 29, low: 13, mid: 27 },
  92. // | { open: 24, close: 8, high: 24, low: 5 },
  93. // | { open: 8, close: 16, high: 22, low: 2 },
  94. // | { open: 16, close: 12, high: 19, low: 7 },
  95. // | { open: 12, close: 20, high: 22, low: 8 },
  96. // | { open: 20, close: 16, high: 22, low: 8 },
  97. // | { open: 16, close: 22, high: 26, low: 6 },
  98. // | { open: 22, close: 18, high: 22, low: 11 },
  99. // | { open: 18, close: 29, high: 32, low: 14 },
  100. // | { open: 29, close: 24, high: 29, low: 13 },
  101. // | { open: 24, close: 8, high: 24, low: 5 },
  102. // | { open: 8, close: 16, high: 22, low: 2 },
  103. // | { open: 16, close: 12, high: 19, low: 7 },
  104. // | { open: 12, close: 20, high: 22, low: 8 },
  105. // | { open: 20, close: 16, high: 22, low: 8 }
  106. // | ],
  107. // | { stroke: { color: "green" }, fill: "lightgreen" }
  108. // | )
  109. // | .render();
  110. //
  111. // theme: dojox.charting.Theme?
  112. // An optional theme to use for styling the chart.
  113. // axes: dojox.charting.Axis{}?
  114. // A map of axes for use in plotting a chart.
  115. // stack: dojox.charting.plot2d.Base[]
  116. // A stack of plotters.
  117. // plots: dojox.charting.plot2d.Base{}
  118. // A map of plotter indices
  119. // series: dojox.charting.Series[]
  120. // The stack of data runs used to create plots.
  121. // runs: dojox.charting.Series{}
  122. // A map of series indices
  123. // margins: Object?
  124. // The margins around the chart. Default is { l:10, t:10, r:10, b:10 }.
  125. // stroke: dojox.gfx.Stroke?
  126. // The outline of the chart (stroke in vector graphics terms).
  127. // fill: dojox.gfx.Fill?
  128. // The color for the chart.
  129. // node: DOMNode
  130. // The container node passed to the constructor.
  131. // surface: dojox.gfx.Surface
  132. // The main graphics surface upon which a chart is drawn.
  133. // dirty: Boolean
  134. // A boolean flag indicating whether or not the chart needs to be updated/re-rendered.
  135. // coords: Object
  136. // The coordinates on a page of the containing node, as returned from dojo.coords.
  137. constructor: function(/* DOMNode */node, /* dojox.charting.__ChartCtorArgs? */kwArgs){
  138. // summary:
  139. // The constructor for a new Chart. Initializes all parameters used for a chart.
  140. // returns: dojox.charting.Chart
  141. // The newly created chart.
  142. // initialize parameters
  143. if(!kwArgs){ kwArgs = {}; }
  144. this.margins = kwArgs.margins ? kwArgs.margins : {l: 10, t: 10, r: 10, b: 10};
  145. this.stroke = kwArgs.stroke;
  146. this.fill = kwArgs.fill;
  147. this.delayInMs = kwArgs.delayInMs || 200;
  148. this.title = kwArgs.title;
  149. this.titleGap = kwArgs.titleGap;
  150. this.titlePos = kwArgs.titlePos;
  151. this.titleFont = kwArgs.titleFont;
  152. this.titleFontColor = kwArgs.titleFontColor;
  153. this.chartTitle = null;
  154. // default initialization
  155. this.theme = null;
  156. this.axes = {}; // map of axes
  157. this.stack = []; // stack of plotters
  158. this.plots = {}; // map of plotter indices
  159. this.series = []; // stack of data runs
  160. this.runs = {}; // map of data run indices
  161. this.dirty = true;
  162. this.coords = null;
  163. // create a surface
  164. this.node = dojo.byId(node);
  165. var box = dojo.marginBox(node);
  166. this.surface = g.createSurface(this.node, box.w || 400, box.h || 300);
  167. },
  168. destroy: function(){
  169. // summary:
  170. // Cleanup when a chart is to be destroyed.
  171. // returns: void
  172. dojo.forEach(this.series, destroy);
  173. dojo.forEach(this.stack, destroy);
  174. df.forIn(this.axes, destroy);
  175. if(this.chartTitle && this.chartTitle.tagName){
  176. // destroy title if it is a DOM node
  177. dojo.destroy(this.chartTitle);
  178. }
  179. this.surface.destroy();
  180. },
  181. getCoords: function(){
  182. // summary:
  183. // Get the coordinates and dimensions of the containing DOMNode, as
  184. // returned by dojo.coords.
  185. // returns: Object
  186. // The resulting coordinates of the chart. See dojo.coords for details.
  187. if(!this.coords){
  188. this.coords = dojo.coords(this.node, true);
  189. }
  190. return this.coords; // Object
  191. },
  192. setTheme: function(theme){
  193. // summary:
  194. // Set a theme of the chart.
  195. // theme: dojox.charting.Theme
  196. // The theme to be used for visual rendering.
  197. // returns: dojox.charting.Chart
  198. // A reference to the current chart for functional chaining.
  199. this.theme = theme.clone();
  200. this.dirty = true;
  201. return this; // dojox.charting.Chart
  202. },
  203. addAxis: function(name, kwArgs){
  204. // summary:
  205. // Add an axis to the chart, for rendering.
  206. // name: String
  207. // The name of the axis.
  208. // kwArgs: dojox.charting.axis2d.__AxisCtorArgs?
  209. // An optional keyword arguments object for use in defining details of an axis.
  210. // returns: dojox.charting.Chart
  211. // A reference to the current chart for functional chaining.
  212. var axis, axisType = kwArgs && kwArgs.type || "Default";
  213. if(typeof axisType == "string"){
  214. if(!dc.axis2d || !dc.axis2d[axisType]){
  215. throw Error("Can't find axis: " + axisType + " - didn't you forget to dojo" + ".require() it?");
  216. }
  217. axis = new dc.axis2d[axisType](this, kwArgs);
  218. }else{
  219. axis = new axisType(this, kwArgs);
  220. }
  221. axis.name = name;
  222. axis.dirty = true;
  223. if(name in this.axes){
  224. this.axes[name].destroy();
  225. }
  226. this.axes[name] = axis;
  227. this.dirty = true;
  228. return this; // dojox.charting.Chart
  229. },
  230. getAxis: function(name){
  231. // summary:
  232. // Get the given axis, by name.
  233. // name: String
  234. // The name the axis was defined by.
  235. // returns: dojox.charting.axis2d.Default
  236. // The axis as stored in the chart's axis map.
  237. return this.axes[name]; // dojox.charting.axis2d.Default
  238. },
  239. removeAxis: function(name){
  240. // summary:
  241. // Remove the axis that was defined using name.
  242. // name: String
  243. // The axis name, as defined in addAxis.
  244. // returns: dojox.charting.Chart
  245. // A reference to the current chart for functional chaining.
  246. if(name in this.axes){
  247. // destroy the axis
  248. this.axes[name].destroy();
  249. delete this.axes[name];
  250. // mark the chart as dirty
  251. this.dirty = true;
  252. }
  253. return this; // dojox.charting.Chart
  254. },
  255. addPlot: function(name, kwArgs){
  256. // summary:
  257. // Add a new plot to the chart, defined by name and using the optional keyword arguments object.
  258. // Note that dojox.charting assumes the main plot to be called "default"; if you do not have
  259. // a plot called "default" and attempt to add data series to the chart without specifying the
  260. // plot to be rendered on, you WILL get errors.
  261. // name: String
  262. // The name of the plot to be added to the chart. If you only plan on using one plot, call it "default".
  263. // kwArgs: dojox.charting.plot2d.__PlotCtorArgs
  264. // An object with optional parameters for the plot in question.
  265. // returns: dojox.charting.Chart
  266. // A reference to the current chart for functional chaining.
  267. var plot, plotType = kwArgs && kwArgs.type || "Default";
  268. if(typeof plotType == "string"){
  269. if(!dc.plot2d || !dc.plot2d[plotType]){
  270. throw Error("Can't find plot: " + plotType + " - didn't you forget to dojo" + ".require() it?");
  271. }
  272. plot = new dc.plot2d[plotType](this, kwArgs);
  273. }else{
  274. plot = new plotType(this, kwArgs);
  275. }
  276. plot.name = name;
  277. plot.dirty = true;
  278. if(name in this.plots){
  279. this.stack[this.plots[name]].destroy();
  280. this.stack[this.plots[name]] = plot;
  281. }else{
  282. this.plots[name] = this.stack.length;
  283. this.stack.push(plot);
  284. }
  285. this.dirty = true;
  286. return this; // dojox.charting.Chart
  287. },
  288. removePlot: function(name){
  289. // summary:
  290. // Remove the plot defined using name from the chart's plot stack.
  291. // name: String
  292. // The name of the plot as defined using addPlot.
  293. // returns: dojox.charting.Chart
  294. // A reference to the current chart for functional chaining.
  295. if(name in this.plots){
  296. // get the index and remove the name
  297. var index = this.plots[name];
  298. delete this.plots[name];
  299. // destroy the plot
  300. this.stack[index].destroy();
  301. // remove the plot from the stack
  302. this.stack.splice(index, 1);
  303. // update indices to reflect the shift
  304. df.forIn(this.plots, function(idx, name, plots){
  305. if(idx > index){
  306. plots[name] = idx - 1;
  307. }
  308. });
  309. // remove all related series
  310. var ns = dojo.filter(this.series, function(run){ return run.plot != name; });
  311. if(ns.length < this.series.length){
  312. // kill all removed series
  313. dojo.forEach(this.series, function(run){
  314. if(run.plot == name){
  315. run.destroy();
  316. }
  317. });
  318. // rebuild all necessary data structures
  319. this.runs = {};
  320. dojo.forEach(ns, function(run, index){
  321. this.runs[run.plot] = index;
  322. }, this);
  323. this.series = ns;
  324. }
  325. // mark the chart as dirty
  326. this.dirty = true;
  327. }
  328. return this; // dojox.charting.Chart
  329. },
  330. getPlotOrder: function(){
  331. // summary:
  332. // Returns an array of plot names in the current order
  333. // (the top-most plot is the first).
  334. // returns: Array
  335. return df.map(this.stack, getName); // Array
  336. },
  337. setPlotOrder: function(newOrder){
  338. // summary:
  339. // Sets new order of plots. newOrder cannot add or remove
  340. // plots. Wrong names, or dups are ignored.
  341. // newOrder: Array:
  342. // Array of plot names compatible with getPlotOrder().
  343. // returns: dojox.charting.Chart
  344. // A reference to the current chart for functional chaining.
  345. var names = {},
  346. order = df.filter(newOrder, function(name){
  347. if(!(name in this.plots) || (name in names)){
  348. return false;
  349. }
  350. names[name] = 1;
  351. return true;
  352. }, this);
  353. if(order.length < this.stack.length){
  354. df.forEach(this.stack, function(plot){
  355. var name = plot.name;
  356. if(!(name in names)){
  357. order.push(name);
  358. }
  359. });
  360. }
  361. var newStack = df.map(order, function(name){
  362. return this.stack[this.plots[name]];
  363. }, this);
  364. df.forEach(newStack, function(plot, i){
  365. this.plots[plot.name] = i;
  366. }, this);
  367. this.stack = newStack;
  368. this.dirty = true;
  369. return this; // dojox.charting.Chart
  370. },
  371. movePlotToFront: function(name){
  372. // summary:
  373. // Moves a given plot to front.
  374. // name: String:
  375. // Plot's name to move.
  376. // returns: dojox.charting.Chart
  377. // A reference to the current chart for functional chaining.
  378. if(name in this.plots){
  379. var index = this.plots[name];
  380. if(index){
  381. var newOrder = this.getPlotOrder();
  382. newOrder.splice(index, 1);
  383. newOrder.unshift(name);
  384. return this.setPlotOrder(newOrder); // dojox.charting.Chart
  385. }
  386. }
  387. return this; // dojox.charting.Chart
  388. },
  389. movePlotToBack: function(name){
  390. // summary:
  391. // Moves a given plot to back.
  392. // name: String:
  393. // Plot's name to move.
  394. // returns: dojox.charting.Chart
  395. // A reference to the current chart for functional chaining.
  396. if(name in this.plots){
  397. var index = this.plots[name];
  398. if(index < this.stack.length - 1){
  399. var newOrder = this.getPlotOrder();
  400. newOrder.splice(index, 1);
  401. newOrder.push(name);
  402. return this.setPlotOrder(newOrder); // dojox.charting.Chart
  403. }
  404. }
  405. return this; // dojox.charting.Chart
  406. },
  407. addSeries: function(name, data, kwArgs){
  408. // summary:
  409. // Add a data series to the chart for rendering.
  410. // name: String:
  411. // The name of the data series to be plotted.
  412. // data: Array|Object:
  413. // The array of data points (either numbers or objects) that
  414. // represents the data to be drawn. Or it can be an object. In
  415. // the latter case, it should have a property "data" (an array),
  416. // destroy(), and setSeriesObject().
  417. // kwArgs: dojox.charting.__SeriesCtorArgs?:
  418. // An optional keyword arguments object that will be mixed into
  419. // the resultant series object.
  420. // returns: dojox.charting.Chart:
  421. // A reference to the current chart for functional chaining.
  422. var run = new dc.Series(this, data, kwArgs);
  423. run.name = name;
  424. if(name in this.runs){
  425. this.series[this.runs[name]].destroy();
  426. this.series[this.runs[name]] = run;
  427. }else{
  428. this.runs[name] = this.series.length;
  429. this.series.push(run);
  430. }
  431. this.dirty = true;
  432. // fix min/max
  433. if(!("ymin" in run) && "min" in run){ run.ymin = run.min; }
  434. if(!("ymax" in run) && "max" in run){ run.ymax = run.max; }
  435. return this; // dojox.charting.Chart
  436. },
  437. removeSeries: function(name){
  438. // summary:
  439. // Remove the series defined by name from the chart.
  440. // name: String
  441. // The name of the series as defined by addSeries.
  442. // returns: dojox.charting.Chart
  443. // A reference to the current chart for functional chaining.
  444. if(name in this.runs){
  445. // get the index and remove the name
  446. var index = this.runs[name];
  447. delete this.runs[name];
  448. // destroy the run
  449. this.series[index].destroy();
  450. // remove the run from the stack of series
  451. this.series.splice(index, 1);
  452. // update indices to reflect the shift
  453. df.forIn(this.runs, function(idx, name, runs){
  454. if(idx > index){
  455. runs[name] = idx - 1;
  456. }
  457. });
  458. this.dirty = true;
  459. }
  460. return this; // dojox.charting.Chart
  461. },
  462. updateSeries: function(name, data){
  463. // summary:
  464. // Update the given series with a new set of data points.
  465. // name: String
  466. // The name of the series as defined in addSeries.
  467. // data: Array|Object:
  468. // The array of data points (either numbers or objects) that
  469. // represents the data to be drawn. Or it can be an object. In
  470. // the latter case, it should have a property "data" (an array),
  471. // destroy(), and setSeriesObject().
  472. // returns: dojox.charting.Chart
  473. // A reference to the current chart for functional chaining.
  474. if(name in this.runs){
  475. var run = this.series[this.runs[name]];
  476. run.update(data);
  477. this._invalidateDependentPlots(run.plot, false);
  478. this._invalidateDependentPlots(run.plot, true);
  479. }
  480. return this; // dojox.charting.Chart
  481. },
  482. getSeriesOrder: function(plotName){
  483. // summary:
  484. // Returns an array of series names in the current order
  485. // (the top-most series is the first) within a plot.
  486. // plotName: String:
  487. // Plot's name.
  488. // returns: Array
  489. return df.map(df.filter(this.series, function(run){
  490. return run.plot == plotName;
  491. }), getName);
  492. },
  493. setSeriesOrder: function(newOrder){
  494. // summary:
  495. // Sets new order of series within a plot. newOrder cannot add
  496. // or remove series. Wrong names, or dups are ignored.
  497. // newOrder: Array:
  498. // Array of series names compatible with getPlotOrder(). All
  499. // series should belong to the same plot.
  500. // returns: dojox.charting.Chart
  501. // A reference to the current chart for functional chaining.
  502. var plotName, names = {},
  503. order = df.filter(newOrder, function(name){
  504. if(!(name in this.runs) || (name in names)){
  505. return false;
  506. }
  507. var run = this.series[this.runs[name]];
  508. if(plotName){
  509. if(run.plot != plotName){
  510. return false;
  511. }
  512. }else{
  513. plotName = run.plot;
  514. }
  515. names[name] = 1;
  516. return true;
  517. }, this);
  518. df.forEach(this.series, function(run){
  519. var name = run.name;
  520. if(!(name in names) && run.plot == plotName){
  521. order.push(name);
  522. }
  523. });
  524. var newSeries = df.map(order, function(name){
  525. return this.series[this.runs[name]];
  526. }, this);
  527. this.series = newSeries.concat(df.filter(this.series, function(run){
  528. return run.plot != plotName;
  529. }));
  530. df.forEach(this.series, function(run, i){
  531. this.runs[run.name] = i;
  532. }, this);
  533. this.dirty = true;
  534. return this; // dojox.charting.Chart
  535. },
  536. moveSeriesToFront: function(name){
  537. // summary:
  538. // Moves a given series to front of a plot.
  539. // name: String:
  540. // Series' name to move.
  541. // returns: dojox.charting.Chart
  542. // A reference to the current chart for functional chaining.
  543. if(name in this.runs){
  544. var index = this.runs[name],
  545. newOrder = this.getSeriesOrder(this.series[index].plot);
  546. if(name != newOrder[0]){
  547. newOrder.splice(index, 1);
  548. newOrder.unshift(name);
  549. return this.setSeriesOrder(newOrder); // dojox.charting.Chart
  550. }
  551. }
  552. return this; // dojox.charting.Chart
  553. },
  554. moveSeriesToBack: function(name){
  555. // summary:
  556. // Moves a given series to back of a plot.
  557. // name: String:
  558. // Series' name to move.
  559. // returns: dojox.charting.Chart
  560. // A reference to the current chart for functional chaining.
  561. if(name in this.runs){
  562. var index = this.runs[name],
  563. newOrder = this.getSeriesOrder(this.series[index].plot);
  564. if(name != newOrder[newOrder.length - 1]){
  565. newOrder.splice(index, 1);
  566. newOrder.push(name);
  567. return this.setSeriesOrder(newOrder); // dojox.charting.Chart
  568. }
  569. }
  570. return this; // dojox.charting.Chart
  571. },
  572. resize: function(width, height){
  573. // summary:
  574. // Resize the chart to the dimensions of width and height.
  575. // description:
  576. // Resize the chart and its surface to the width and height dimensions.
  577. // If no width/height or box is provided, resize the surface to the marginBox of the chart.
  578. // width: Number
  579. // The new width of the chart.
  580. // height: Number
  581. // The new height of the chart.
  582. // returns: dojox.charting.Chart
  583. // A reference to the current chart for functional chaining.
  584. var box;
  585. switch(arguments.length){
  586. // case 0, do not resize the div, just the surface
  587. case 1:
  588. // argument, override node box
  589. box = dojo.mixin({}, width);
  590. dojo.marginBox(this.node, box);
  591. break;
  592. case 2:
  593. box = {w: width, h: height};
  594. // argument, override node box
  595. dojo.marginBox(this.node, box);
  596. break;
  597. }
  598. // in all cases take back the computed box
  599. box = dojo.marginBox(this.node);
  600. // and set it on the surface
  601. this.surface.setDimensions(box.w, box.h);
  602. this.dirty = true;
  603. this.coords = null;
  604. return this.render(); // dojox.charting.Chart
  605. },
  606. getGeometry: function(){
  607. // summary:
  608. // Returns a map of information about all axes in a chart and what they represent
  609. // in terms of scaling (see dojox.charting.axis2d.Default.getScaler).
  610. // returns: Object
  611. // An map of geometry objects, a one-to-one mapping of axes.
  612. var ret = {};
  613. df.forIn(this.axes, function(axis){
  614. if(axis.initialized()){
  615. ret[axis.name] = {
  616. name: axis.name,
  617. vertical: axis.vertical,
  618. scaler: axis.scaler,
  619. ticks: axis.ticks
  620. };
  621. }
  622. });
  623. return ret; // Object
  624. },
  625. setAxisWindow: function(name, scale, offset, zoom){
  626. // summary:
  627. // Zooms an axis and all dependent plots. Can be used to zoom in 1D.
  628. // name: String
  629. // The name of the axis as defined by addAxis.
  630. // scale: Number
  631. // The scale on the target axis.
  632. // offset: Number
  633. // Any offest, as measured by axis tick
  634. // zoom: Boolean|Object?
  635. // The chart zooming animation trigger. This is null by default,
  636. // e.g. {duration: 1200}, or just set true.
  637. // returns: dojox.charting.Chart
  638. // A reference to the current chart for functional chaining.
  639. var axis = this.axes[name];
  640. if(axis){
  641. axis.setWindow(scale, offset);
  642. dojo.forEach(this.stack,function(plot){
  643. if(plot.hAxis == name || plot.vAxis == name){
  644. plot.zoom = zoom;
  645. }
  646. })
  647. }
  648. return this; // dojox.charting.Chart
  649. },
  650. setWindow: function(sx, sy, dx, dy, zoom){
  651. // summary:
  652. // Zooms in or out any plots in two dimensions.
  653. // sx: Number
  654. // The scale for the x axis.
  655. // sy: Number
  656. // The scale for the y axis.
  657. // dx: Number
  658. // The pixel offset on the x axis.
  659. // dy: Number
  660. // The pixel offset on the y axis.
  661. // zoom: Boolean|Object?
  662. // The chart zooming animation trigger. This is null by default,
  663. // e.g. {duration: 1200}, or just set true.
  664. // returns: dojox.charting.Chart
  665. // A reference to the current chart for functional chaining.
  666. if(!("plotArea" in this)){
  667. this.calculateGeometry();
  668. }
  669. df.forIn(this.axes, function(axis){
  670. var scale, offset, bounds = axis.getScaler().bounds,
  671. s = bounds.span / (bounds.upper - bounds.lower);
  672. if(axis.vertical){
  673. scale = sy;
  674. offset = dy / s / scale;
  675. }else{
  676. scale = sx;
  677. offset = dx / s / scale;
  678. }
  679. axis.setWindow(scale, offset);
  680. });
  681. dojo.forEach(this.stack, function(plot){ plot.zoom = zoom; });
  682. return this; // dojox.charting.Chart
  683. },
  684. zoomIn: function(name, range){
  685. // summary:
  686. // Zoom the chart to a specific range on one axis. This calls render()
  687. // directly as a convenience method.
  688. // name: String
  689. // The name of the axis as defined by addAxis.
  690. // range: Array
  691. // The end points of the zoom range, measured in axis ticks.
  692. var axis = this.axes[name];
  693. if(axis){
  694. var scale, offset, bounds = axis.getScaler().bounds;
  695. var lower = Math.min(range[0],range[1]);
  696. var upper = Math.max(range[0],range[1]);
  697. lower = range[0] < bounds.lower ? bounds.lower : lower;
  698. upper = range[1] > bounds.upper ? bounds.upper : upper;
  699. scale = (bounds.upper - bounds.lower) / (upper - lower);
  700. offset = lower - bounds.lower;
  701. this.setAxisWindow(name, scale, offset);
  702. this.render();
  703. }
  704. },
  705. calculateGeometry: function(){
  706. // summary:
  707. // Calculate the geometry of the chart based on the defined axes of
  708. // a chart.
  709. // returns: dojox.charting.Chart
  710. // A reference to the current chart for functional chaining.
  711. if(this.dirty){
  712. return this.fullGeometry();
  713. }
  714. // calculate geometry
  715. var dirty = dojo.filter(this.stack, function(plot){
  716. return plot.dirty ||
  717. (plot.hAxis && this.axes[plot.hAxis].dirty) ||
  718. (plot.vAxis && this.axes[plot.vAxis].dirty);
  719. }, this);
  720. calculateAxes(dirty, this.plotArea);
  721. return this; // dojox.charting.Chart
  722. },
  723. fullGeometry: function(){
  724. // summary:
  725. // Calculate the full geometry of the chart. This includes passing
  726. // over all major elements of a chart (plots, axes, series, container)
  727. // in order to ensure proper rendering.
  728. // returns: dojox.charting.Chart
  729. // A reference to the current chart for functional chaining.
  730. this._makeDirty();
  731. // clear old values
  732. dojo.forEach(this.stack, clear);
  733. // rebuild new connections, and add defaults
  734. // set up a theme
  735. if(!this.theme){
  736. this.setTheme(new dojox.charting.Theme(dojox.charting._def));
  737. }
  738. // assign series
  739. dojo.forEach(this.series, function(run){
  740. if(!(run.plot in this.plots)){
  741. if(!dc.plot2d || !dc.plot2d.Default){
  742. throw Error("Can't find plot: Default - didn't you forget to dojo" + ".require() it?");
  743. }
  744. var plot = new dc.plot2d.Default(this, {});
  745. plot.name = run.plot;
  746. this.plots[run.plot] = this.stack.length;
  747. this.stack.push(plot);
  748. }
  749. this.stack[this.plots[run.plot]].addSeries(run);
  750. }, this);
  751. // assign axes
  752. dojo.forEach(this.stack, function(plot){
  753. if(plot.hAxis){
  754. plot.setAxis(this.axes[plot.hAxis]);
  755. }
  756. if(plot.vAxis){
  757. plot.setAxis(this.axes[plot.vAxis]);
  758. }
  759. }, this);
  760. // calculate geometry
  761. // 1st pass
  762. var dim = this.dim = this.surface.getDimensions();
  763. dim.width = g.normalizedLength(dim.width);
  764. dim.height = g.normalizedLength(dim.height);
  765. df.forIn(this.axes, clear);
  766. calculateAxes(this.stack, dim);
  767. // assumption: we don't have stacked axes yet
  768. var offsets = this.offsets = { l: 0, r: 0, t: 0, b: 0 };
  769. df.forIn(this.axes, function(axis){
  770. df.forIn(axis.getOffsets(), function(o, i){ offsets[i] += o; });
  771. });
  772. // add title area
  773. if(this.title){
  774. this.titleGap = (this.titleGap==0) ? 0 : this.titleGap || this.theme.chart.titleGap || 20;
  775. this.titlePos = this.titlePos || this.theme.chart.titlePos || "top";
  776. this.titleFont = this.titleFont || this.theme.chart.titleFont;
  777. this.titleFontColor = this.titleFontColor || this.theme.chart.titleFontColor || "black";
  778. var tsize = g.normalizedLength(g.splitFontString(this.titleFont).size);
  779. offsets[this.titlePos=="top" ? "t":"b"] += (tsize + this.titleGap);
  780. }
  781. // add margins
  782. df.forIn(this.margins, function(o, i){ offsets[i] += o; });
  783. // 2nd pass with realistic dimensions
  784. this.plotArea = {
  785. width: dim.width - offsets.l - offsets.r,
  786. height: dim.height - offsets.t - offsets.b
  787. };
  788. df.forIn(this.axes, clear);
  789. calculateAxes(this.stack, this.plotArea);
  790. return this; // dojox.charting.Chart
  791. },
  792. render: function(){
  793. // summary:
  794. // Render the chart according to the current information defined. This should
  795. // be the last call made when defining/creating a chart, or if data within the
  796. // chart has been changed.
  797. // returns: dojox.charting.Chart
  798. // A reference to the current chart for functional chaining.
  799. if(this.theme){
  800. this.theme.clear();
  801. }
  802. if(this.dirty){
  803. return this.fullRender();
  804. }
  805. this.calculateGeometry();
  806. // go over the stack backwards
  807. df.forEachRev(this.stack, function(plot){ plot.render(this.dim, this.offsets); }, this);
  808. // go over axes
  809. df.forIn(this.axes, function(axis){ axis.render(this.dim, this.offsets); }, this);
  810. this._makeClean();
  811. // BEGIN FOR HTML CANVAS
  812. if(this.surface.render){ this.surface.render(); };
  813. // END FOR HTML CANVAS
  814. return this; // dojox.charting.Chart
  815. },
  816. fullRender: function(){
  817. // summary:
  818. // Force a full rendering of the chart, including full resets on the chart itself.
  819. // You should not call this method directly unless absolutely necessary.
  820. // returns: dojox.charting.Chart
  821. // A reference to the current chart for functional chaining.
  822. // calculate geometry
  823. this.fullGeometry();
  824. var offsets = this.offsets, dim = this.dim, rect;
  825. // get required colors
  826. //var requiredColors = df.foldl(this.stack, "z + plot.getRequiredColors()", 0);
  827. //this.theme.defineColors({num: requiredColors, cache: false});
  828. // clear old shapes
  829. dojo.forEach(this.series, purge);
  830. df.forIn(this.axes, purge);
  831. dojo.forEach(this.stack, purge);
  832. if(this.chartTitle && this.chartTitle.tagName){
  833. // destroy title if it is a DOM node
  834. dojo.destroy(this.chartTitle);
  835. }
  836. this.surface.clear();
  837. this.chartTitle = null;
  838. // generate shapes
  839. // draw a plot background
  840. var t = this.theme,
  841. fill = t.plotarea && t.plotarea.fill,
  842. stroke = t.plotarea && t.plotarea.stroke,
  843. rect = {
  844. x: offsets.l - 1, y: offsets.t - 1,
  845. width: dim.width - offsets.l - offsets.r + 2,
  846. height: dim.height - offsets.t - offsets.b + 2
  847. };
  848. if(fill){
  849. fill = dc.Element.prototype._shapeFill(dc.Element.prototype._plotFill(fill, dim, offsets), rect);
  850. this.surface.createRect(rect).setFill(fill);
  851. }
  852. if(stroke){
  853. this.surface.createRect({
  854. x: offsets.l, y: offsets.t,
  855. width: dim.width - offsets.l - offsets.r + 1,
  856. height: dim.height - offsets.t - offsets.b + 1
  857. }).setStroke(stroke);
  858. }
  859. // go over the stack backwards
  860. df.foldr(this.stack, function(z, plot){ return plot.render(dim, offsets), 0; }, 0);
  861. // pseudo-clipping: matting
  862. fill = this.fill !== undefined ? this.fill : (t.chart && t.chart.fill);
  863. stroke = this.stroke !== undefined ? this.stroke : (t.chart && t.chart.stroke);
  864. // TRT: support for "inherit" as a named value in a theme.
  865. if(fill == "inherit"){
  866. // find the background color of the nearest ancestor node, and use that explicitly.
  867. var node = this.node, fill = new dojo.Color(dojo.style(node, "backgroundColor"));
  868. while(fill.a==0 && node!=document.documentElement){
  869. fill = new dojo.Color(dojo.style(node, "backgroundColor"));
  870. node = node.parentNode;
  871. }
  872. }
  873. if(fill){
  874. fill = dc.Element.prototype._plotFill(fill, dim, offsets);
  875. if(offsets.l){ // left
  876. rect = {
  877. width: offsets.l,
  878. height: dim.height + 1
  879. };
  880. this.surface.createRect(rect).setFill(dc.Element.prototype._shapeFill(fill, rect));
  881. }
  882. if(offsets.r){ // right
  883. rect = {
  884. x: dim.width - offsets.r,
  885. width: offsets.r + 1,
  886. height: dim.height + 2
  887. };
  888. this.surface.createRect(rect).setFill(dc.Element.prototype._shapeFill(fill, rect));
  889. }
  890. if(offsets.t){ // top
  891. rect = {
  892. width: dim.width + 1,
  893. height: offsets.t
  894. };
  895. this.surface.createRect(rect).setFill(dc.Element.prototype._shapeFill(fill, rect));
  896. }
  897. if(offsets.b){ // bottom
  898. rect = {
  899. y: dim.height - offsets.b,
  900. width: dim.width + 1,
  901. height: offsets.b + 2
  902. };
  903. this.surface.createRect(rect).setFill(dc.Element.prototype._shapeFill(fill, rect));
  904. }
  905. }
  906. if(stroke){
  907. this.surface.createRect({
  908. width: dim.width - 1,
  909. height: dim.height - 1
  910. }).setStroke(stroke);
  911. }
  912. //create title: Whether to make chart title as a widget which extends dojox.charting.Element?
  913. if(this.title){
  914. var forceHtmlLabels = (g.renderer == "canvas"),
  915. labelType = forceHtmlLabels || !dojo.isIE && !dojo.isOpera ? "html" : "gfx",
  916. tsize = g.normalizedLength(g.splitFontString(this.titleFont).size);
  917. this.chartTitle = dc.axis2d.common.createText[labelType](
  918. this,
  919. this.surface,
  920. dim.width/2,
  921. this.titlePos=="top" ? tsize + this.margins.t : dim.height - this.margins.b,
  922. "middle",
  923. this.title,
  924. this.titleFont,
  925. this.titleFontColor
  926. );
  927. }
  928. // go over axes
  929. df.forIn(this.axes, function(axis){ axis.render(dim, offsets); });
  930. this._makeClean();
  931. // BEGIN FOR HTML CANVAS
  932. if(this.surface.render){ this.surface.render(); };
  933. // END FOR HTML CANVAS
  934. return this; // dojox.charting.Chart
  935. },
  936. delayedRender: function(){
  937. // summary:
  938. // Delayed render, which is used to collect multiple updates
  939. // within a delayInMs time window.
  940. // returns: dojox.charting.Chart
  941. // A reference to the current chart for functional chaining.
  942. if(!this._delayedRenderHandle){
  943. this._delayedRenderHandle = setTimeout(
  944. dojo.hitch(this, function(){
  945. clearTimeout(this._delayedRenderHandle);
  946. this._delayedRenderHandle = null;
  947. this.render();
  948. }),
  949. this.delayInMs
  950. );
  951. }
  952. return this; // dojox.charting.Chart
  953. },
  954. connectToPlot: function(name, object, method){
  955. // summary:
  956. // A convenience method to connect a function to a plot.
  957. // name: String
  958. // The name of the plot as defined by addPlot.
  959. // object: Object
  960. // The object to be connected.
  961. // method: Function
  962. // The function to be executed.
  963. // returns: Array
  964. // A handle to the connection, as defined by dojo.connect (see dojo.connect).
  965. return name in this.plots ? this.stack[this.plots[name]].connect(object, method) : null; // Array
  966. },
  967. fireEvent: function(seriesName, eventName, index){
  968. // summary:
  969. // Fires a synthetic event for a series item.
  970. // seriesName: String:
  971. // Series name.
  972. // eventName: String:
  973. // Event name to simulate: onmouseover, onmouseout, onclick.
  974. // index: Number:
  975. // Valid data value index for the event.
  976. // returns: dojox.charting.Chart
  977. // A reference to the current chart for functional chaining.
  978. if(seriesName in this.runs){
  979. var plotName = this.series[this.runs[seriesName]].plot;
  980. if(plotName in this.plots){
  981. var plot = this.stack[this.plots[plotName]];
  982. if(plot){
  983. plot.fireEvent(seriesName, eventName, index);
  984. }
  985. }
  986. }
  987. return this; // dojox.charting.Chart
  988. },
  989. _makeClean: function(){
  990. // reset dirty flags
  991. dojo.forEach(this.axes, makeClean);
  992. dojo.forEach(this.stack, makeClean);
  993. dojo.forEach(this.series, makeClean);
  994. this.dirty = false;
  995. },
  996. _makeDirty: function(){
  997. // reset dirty flags
  998. dojo.forEach(this.axes, makeDirty);
  999. dojo.forEach(this.stack, makeDirty);
  1000. dojo.forEach(this.series, makeDirty);
  1001. this.dirty = true;
  1002. },
  1003. _invalidateDependentPlots: function(plotName, /* Boolean */ verticalAxis){
  1004. if(plotName in this.plots){
  1005. var plot = this.stack[this.plots[plotName]], axis,
  1006. axisName = verticalAxis ? "vAxis" : "hAxis";
  1007. if(plot[axisName]){
  1008. axis = this.axes[plot[axisName]];
  1009. if(axis && axis.dependOnData()){
  1010. axis.dirty = true;
  1011. // find all plots and mark them dirty
  1012. dojo.forEach(this.stack, function(p){
  1013. if(p[axisName] && p[axisName] == plot[axisName]){
  1014. p.dirty = true;
  1015. }
  1016. });
  1017. }
  1018. }else{
  1019. plot.dirty = true;
  1020. }
  1021. }
  1022. }
  1023. });
  1024. function hSection(stats){
  1025. return {min: stats.hmin, max: stats.hmax};
  1026. }
  1027. function vSection(stats){
  1028. return {min: stats.vmin, max: stats.vmax};
  1029. }
  1030. function hReplace(stats, h){
  1031. stats.hmin = h.min;
  1032. stats.hmax = h.max;
  1033. }
  1034. function vReplace(stats, v){
  1035. stats.vmin = v.min;
  1036. stats.vmax = v.max;
  1037. }
  1038. function combineStats(target, source){
  1039. if(target && source){
  1040. target.min = Math.min(target.min, source.min);
  1041. target.max = Math.max(target.max, source.max);
  1042. }
  1043. return target || source;
  1044. }
  1045. function calculateAxes(stack, plotArea){
  1046. var plots = {}, axes = {};
  1047. dojo.forEach(stack, function(plot){
  1048. var stats = plots[plot.name] = plot.getSeriesStats();
  1049. if(plot.hAxis){
  1050. axes[plot.hAxis] = combineStats(axes[plot.hAxis], hSection(stats));
  1051. }
  1052. if(plot.vAxis){
  1053. axes[plot.vAxis] = combineStats(axes[plot.vAxis], vSection(stats));
  1054. }
  1055. });
  1056. dojo.forEach(stack, function(plot){
  1057. var stats = plots[plot.name];
  1058. if(plot.hAxis){
  1059. hReplace(stats, axes[plot.hAxis]);
  1060. }
  1061. if(plot.vAxis){
  1062. vReplace(stats, axes[plot.vAxis]);
  1063. }
  1064. plot.initializeScalers(plotArea, stats);
  1065. });
  1066. }
  1067. })();
  1068. }