Chart.js 38 KB

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