DataPresentation.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  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.widget.DataPresentation"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.widget.DataPresentation"] = true;
  8. dojo.provide("dojox.widget.DataPresentation");
  9. dojo.experimental("dojox.widget.DataPresentation");
  10. dojo.require("dojox.grid.DataGrid");
  11. dojo.require("dojox.charting.Chart2D");
  12. dojo.require("dojox.charting.widget.Legend");
  13. dojo.require("dojox.charting.action2d.Tooltip");
  14. dojo.require("dojox.charting.action2d.Highlight");
  15. dojo.require("dojo.colors");
  16. dojo.require("dojo.data.ItemFileWriteStore");
  17. (function(){
  18. // sort out the labels for the independent axis of the chart
  19. var getLabels = function(range, labelMod, charttype, domNode){
  20. // prepare labels for the independent axis
  21. var labels = [];
  22. // add empty label, hack
  23. labels[0] = {value: 0, text: ''};
  24. var nlabels = range.length;
  25. // auto-set labelMod for horizontal charts if the labels will otherwise collide
  26. if((charttype !== "ClusteredBars") && (charttype !== "StackedBars")){
  27. var cwid = domNode.offsetWidth;
  28. var tmp = ("" + range[0]).length * range.length * 7; // *assume* 7 pixels width per character ( was 9 )
  29. if(labelMod == 1){
  30. for(var z = 1; z < 500; ++z){
  31. if((tmp / z) < cwid){
  32. break;
  33. }
  34. ++labelMod;
  35. }
  36. }
  37. }
  38. // now set the labels
  39. for(var i = 0; i < nlabels; i++){
  40. //sparse labels
  41. labels.push({
  42. value: i + 1,
  43. text: (!labelMod || i % labelMod) ? "" : range[i]
  44. });
  45. }
  46. // add empty label again, hack
  47. labels.push({value: nlabels + 1, text:''});
  48. return labels;
  49. };
  50. // get the configuration of an independent axis for the chart
  51. var getIndependentAxisArgs = function(charttype, labels){
  52. var args = { vertical: false, labels: labels, min: 0, max: labels.length-1, majorTickStep: 1, minorTickStep: 1 };
  53. // clustered or stacked bars have a vertical independent axis
  54. if((charttype === "ClusteredBars") || (charttype === "StackedBars")){
  55. args.vertical = true;
  56. }
  57. // lines, areas and stacked areas don't need the extra slots at each end
  58. if((charttype === "Lines") || (charttype === "Areas") || (charttype === "StackedAreas")){
  59. args.min++;
  60. args.max--;
  61. }
  62. return args;
  63. };
  64. // get the configuration of a dependent axis for the chart
  65. var getDependentAxisArgs = function(charttype, axistype, minval, maxval){
  66. var args = { vertical: true, fixLower: "major", fixUpper: "major", natural: true };
  67. // secondary dependent axis is not left-bottom
  68. if(axistype === "secondary"){
  69. args.leftBottom = false;
  70. }
  71. // clustered or stacked bars have horizontal dependent axes
  72. if((charttype === "ClusteredBars") || (charttype === "StackedBars")){
  73. args.vertical = false;
  74. }
  75. // ensure axis does not "collapse" for flat series
  76. if(minval == maxval){
  77. args.min = minval - 1;
  78. args.max = maxval + 1;
  79. }
  80. return args;
  81. };
  82. // get the configuration of a plot for the chart
  83. var getPlotArgs = function(charttype, axistype, animate){
  84. var args = { type: charttype, hAxis: "independent", vAxis: "dependent-" + axistype, gap: 4, lines: false, areas: false, markers: false };
  85. // clustered or stacked bars have horizontal dependent axes
  86. if((charttype === "ClusteredBars") || (charttype === "StackedBars")){
  87. args.hAxis = args.vAxis;
  88. args.vAxis = "independent";
  89. }
  90. // turn on lines for Lines, Areas and StackedAreas
  91. if((charttype === "Lines") || (charttype === "Hybrid-Lines") || (charttype === "Areas") || (charttype === "StackedAreas")){
  92. args.lines = true;
  93. }
  94. // turn on areas for Areas and StackedAreas
  95. if((charttype === "Areas") || (charttype === "StackedAreas")){
  96. args.areas = true;
  97. }
  98. // turn on markers and shadow for Lines
  99. if(charttype === "Lines"){
  100. args.markers = true;
  101. }
  102. // turn on shadow for Hybrid-Lines
  103. // also, Hybrid-Lines is not a true chart type: use Lines for the actual plot
  104. if(charttype === "Hybrid-Lines"){
  105. args.shadows = {dx: 2, dy: 2, dw: 2};
  106. args.type = "Lines";
  107. }
  108. // also, Hybrid-ClusteredColumns is not a true chart type: use ClusteredColumns for the actual plot
  109. if(charttype === "Hybrid-ClusteredColumns"){
  110. args.type = "ClusteredColumns";
  111. }
  112. // enable animation on the plot if animation is requested
  113. if(animate){
  114. args.animate = animate;
  115. }
  116. return args;
  117. };
  118. // set up a chart presentation
  119. var setupChart = function(/*DomNode*/domNode, /*Object?*/chart, /*String*/type, /*Boolean*/reverse, /*Object*/animate, /*Integer*/labelMod, /*String*/theme, /*String*/tooltip, /*Object?*/store, /*String?*/query, /*String?*/queryOptions){
  120. var _chart = chart;
  121. if(!_chart){
  122. domNode.innerHTML = ""; // any other content in the node disrupts the chart rendering
  123. _chart = new dojox.charting.Chart2D(domNode);
  124. }
  125. // set the theme
  126. if(theme){
  127. // workaround for a theme bug: its _clone method
  128. // does not transfer the markers, so we repair
  129. // that omission here
  130. // FIXME this should be removed once the theme bug is fixed
  131. theme._clone = function(){
  132. var result = new dojox.charting.Theme({
  133. chart: this.chart,
  134. plotarea: this.plotarea,
  135. axis: this.axis,
  136. series: this.series,
  137. marker: this.marker,
  138. antiAlias: this.antiAlias,
  139. assignColors: this.assignColors,
  140. assignMarkers: this.assigneMarkers,
  141. colors: dojo.delegate(this.colors)
  142. });
  143. result.markers = this.markers;
  144. result._buildMarkerArray();
  145. return result;
  146. };
  147. _chart.setTheme(theme);
  148. }
  149. var range = store.series_data[0].slice(0);
  150. // reverse the labels if requested
  151. if(reverse){
  152. range.reverse();
  153. }
  154. var labels = getLabels(range, labelMod, type, domNode);
  155. // collect details of whether primary and/or secondary axes are required
  156. // and what plots we have instantiated using each type of axis
  157. var plots = {};
  158. // collect maximum and minimum data values
  159. var maxval = null;
  160. var minval = null;
  161. var seriestoremove = {};
  162. for(var sname in _chart.runs){
  163. seriestoremove[sname] = true;
  164. }
  165. // set x values & max data value
  166. var nseries = store.series_name.length;
  167. for(var i = 0; i < nseries; i++){
  168. // only include series with chart=true and with some data values in
  169. if(store.series_chart[i] && (store.series_data[i].length > 0)){
  170. var charttype = type;
  171. var axistype = store.series_axis[i];
  172. if(charttype == "Hybrid"){
  173. if(store.series_charttype[i] == 'line'){
  174. charttype = "Hybrid-Lines";
  175. }else{
  176. charttype = "Hybrid-ClusteredColumns";
  177. }
  178. }
  179. // ensure we have recorded that we are using this axis type
  180. if(!plots[axistype]){
  181. plots[axistype] = {};
  182. }
  183. // ensure we have the correct type of plot for this series
  184. if(!plots[axistype][charttype]){
  185. var axisname = axistype + "-" + charttype;
  186. // create the plot and enable tooltips
  187. _chart.addPlot(axisname, getPlotArgs(charttype, axistype, animate));
  188. var tooltipArgs = {};
  189. if(typeof tooltip == 'string'){
  190. tooltipArgs.text = function(o){
  191. var substitutions = [o.element, o.run.name, range[o.index], ((charttype === "ClusteredBars") || (charttype === "StackedBars")) ? o.x : o.y];
  192. return dojo.replace(tooltip, substitutions); // from Dojo 1.4 onward
  193. //return tooltip.replace(/\{([^\}]+)\}/g, function(_, token){ return dojo.getObject(token, false, substitutions); }); // prior to Dojo 1.4
  194. }
  195. }else if(typeof tooltip == 'function'){
  196. tooltipArgs.text = tooltip;
  197. }
  198. new dojox.charting.action2d.Tooltip(_chart, axisname, tooltipArgs);
  199. // add highlighting, except for lines
  200. if(charttype !== "Lines" && charttype !== "Hybrid-Lines"){
  201. new dojox.charting.action2d.Highlight(_chart, axisname);
  202. }
  203. // record that this plot type is now created
  204. plots[axistype][charttype] = true;
  205. }
  206. // extract the series values
  207. var xvals = [];
  208. var valen = store.series_data[i].length;
  209. for(var j = 0; j < valen; j++){
  210. var val = store.series_data[i][j];
  211. xvals.push(val);
  212. if(maxval === null || val > maxval){
  213. maxval = val;
  214. }
  215. if(minval === null || val < minval){
  216. minval = val;
  217. }
  218. }
  219. // reverse the values if requested
  220. if(reverse){
  221. xvals.reverse();
  222. }
  223. var seriesargs = { plot: axistype + "-" + charttype };
  224. if(store.series_linestyle[i]){
  225. seriesargs.stroke = { style: store.series_linestyle[i] };
  226. }
  227. _chart.addSeries(store.series_name[i], xvals, seriesargs);
  228. delete seriestoremove[store.series_name[i]];
  229. }
  230. }
  231. // remove any series that are no longer needed
  232. for(sname in seriestoremove){
  233. _chart.removeSeries(sname);
  234. }
  235. // create axes
  236. _chart.addAxis("independent", getIndependentAxisArgs(type, labels));
  237. _chart.addAxis("dependent-primary", getDependentAxisArgs(type, "primary", minval, maxval));
  238. _chart.addAxis("dependent-secondary", getDependentAxisArgs(type, "secondary", minval, maxval));
  239. return _chart;
  240. };
  241. // set up a legend presentation
  242. var setupLegend = function(/*DomNode*/domNode, /*Legend*/legend, /*Chart2D*/chart, /*Boolean*/horizontal){
  243. // destroy any existing legend and recreate
  244. var _legend = legend;
  245. if(!_legend){
  246. _legend = new dojox.charting.widget.Legend({ chart: chart, horizontal: horizontal }, domNode);
  247. }else{
  248. _legend.refresh();
  249. }
  250. return _legend;
  251. };
  252. // set up a grid presentation
  253. var setupGrid = function(/*DomNode*/domNode, /*Object?*/grid, /*Object?*/store, /*String?*/query, /*String?*/queryOptions){
  254. var _grid = grid || new dojox.grid.DataGrid({}, domNode);
  255. _grid.startup();
  256. _grid.setStore(store, query, queryOptions);
  257. var structure = [];
  258. for(var ser = 0; ser < store.series_name.length; ser++){
  259. // only include series with grid=true and with some data values in
  260. if(store.series_grid[ser] && (store.series_data[ser].length > 0)){
  261. structure.push({ field: "data." + ser, name: store.series_name[ser], width: "auto", formatter: store.series_gridformatter[ser] });
  262. }
  263. }
  264. _grid.setStructure(structure);
  265. return _grid;
  266. };
  267. // set up a title presentation
  268. var setupTitle = function(/*DomNode*/domNode, /*object*/store){
  269. if(store.title){
  270. domNode.innerHTML = store.title;
  271. }
  272. };
  273. // set up a footer presentation
  274. var setupFooter = function(/*DomNode*/domNode, /*object*/store){
  275. if(store.footer){
  276. domNode.innerHTML = store.footer;
  277. }
  278. };
  279. // obtain a subfield from a field specifier which may contain
  280. // multiple levels (eg, "child.foo[36].manacle")
  281. var getSubfield = function(/*Object*/object, /*String*/field){
  282. var result = object;
  283. if(field){
  284. var fragments = field.split(/[.\[\]]+/);
  285. for(var frag = 0, l = fragments.length; frag < l; frag++){
  286. if(result){
  287. result = result[fragments[frag]];
  288. }
  289. }
  290. }
  291. return result;
  292. };
  293. dojo.declare("dojox.widget.DataPresentation", null, {
  294. // summary:
  295. //
  296. // DataPresentation
  297. //
  298. // A widget that connects to a data store in a simple manner,
  299. // and also provides some additional convenience mechanisms
  300. // for connecting to common data sources without needing to
  301. // explicitly construct a Dojo data store. The widget can then
  302. // present the data in several forms: as a graphical chart,
  303. // as a tabular grid, or as display panels presenting meta-data
  304. // (title, creation information, etc) from the data. The
  305. // widget can also create and manage several of these forms
  306. // in one simple construction.
  307. //
  308. // Note: this is a first experimental draft and any/all details
  309. // are subject to substantial change in later drafts.
  310. //
  311. // example:
  312. //
  313. // var pres = new dojox.data.DataPresentation("myChartNode", {
  314. // type: "chart",
  315. // url: "/data/mydata",
  316. // gridNode: "myGridNode"
  317. // });
  318. //
  319. // properties:
  320. //
  321. // store: Object
  322. // Dojo data store used to supply data to be presented. This may
  323. // be supplied on construction or created implicitly based on
  324. // other construction parameters ('data', 'url').
  325. //
  326. // query: String
  327. // Query to apply to the Dojo data store used to supply data to
  328. // be presented.
  329. //
  330. // queryOptions: String
  331. // Query options to apply to the Dojo data store used to supply
  332. // data to be presented.
  333. //
  334. // data: Object
  335. // Data to be presented. If supplied on construction this property
  336. // will override any value supplied for the 'store' property.
  337. //
  338. // url: String
  339. // URL to fetch data from in JSON format. If supplied on
  340. // construction this property will override any values supplied
  341. // for the 'store' and/or 'data' properties. Note that the data
  342. // can also be comment-filtered JSON, although this will trigger
  343. // a warning message in the console unless djConfig.useCommentedJson
  344. // has been set to true.
  345. //
  346. // urlContent: Object
  347. // Content to be passed to the URL when fetching data. If a URL has
  348. // not been supplied, this value is ignored.
  349. //
  350. // urlError: function
  351. // A function to be called if an error is encountered when fetching
  352. // data from the supplied URL. This function will be supplied with
  353. // two parameters exactly as the error function supplied to the
  354. // dojo.xhrGet function. This function may be called multiple times
  355. // if a refresh interval has been supplied.
  356. //
  357. // refreshInterval: Number
  358. // the time interval in milliseconds after which the data supplied
  359. // via the 'data' property or fetched from a URL via the 'url'
  360. // property should be regularly refreshed. This property is
  361. // ignored if neither the 'data' nor 'url' property has been
  362. // supplied. If the refresh interval is zero, no regular refresh is done.
  363. //
  364. // refreshIntervalPending:
  365. // the JavaScript set interval currently in progress, if any
  366. //
  367. // series: Array
  368. // an array of objects describing the data series to be included
  369. // in the data presentation. Each object may contain the
  370. // following fields:
  371. // datapoints: the name of the field from the source data which
  372. // contains an array of the data points for this data series.
  373. // If not supplied, the source data is assumed to be an array
  374. // of data points to be used.
  375. // field: the name of the field within each data point which
  376. // contains the data for this data series. If not supplied,
  377. // each data point is assumed to be the value for the series.
  378. // name: a name for the series, used in the legend and grid headings
  379. // namefield: the name of the field from the source data which
  380. // contains the name the series, used in the legend and grid
  381. // headings. If both name and namefield are supplied, name takes
  382. // precedence. If neither are supplied, a default name is used.
  383. // chart: true if the series should be included in a chart presentation (default: true)
  384. // charttype: the type of presentation of the series in the chart, which can be
  385. // "range", "line", "bar" (default: "bar")
  386. // linestyle: the stroke style for lines (if applicable) (default: "Solid")
  387. // axis: the dependant axis to which the series will be attached in the chart,
  388. // which can be "primary" or "secondary"
  389. // grid: true if the series should be included in a data grid presentation (default: true)
  390. // gridformatter: an optional formatter to use for this series in the data grid
  391. //
  392. // a call-back function may alternatively be supplied. The function takes
  393. // a single parameter, which will be the data (from the 'data' field or
  394. // loaded from the value in the 'url' field), and should return the array
  395. // of objects describing the data series to be included in the data
  396. // presentation. This enables the series structures to be built dynamically
  397. // after data load, and rebuilt if necessary on data refresh. The call-back
  398. // function will be called each time new data is set, loaded or refreshed.
  399. // A call-back function cannot be used if the data is supplied directly
  400. // from a Dojo data store.
  401. //
  402. // type: String
  403. // the type of presentation to be applied at the DOM attach point.
  404. // This can be 'chart', 'legend', 'grid', 'title', 'footer'. The
  405. // default type is 'chart'.
  406. type: "chart",
  407. //
  408. // chartType: String
  409. // the type of chart to display. This can be 'clusteredbars',
  410. // 'areas', 'stackedcolumns', 'stackedbars', 'stackedareas',
  411. // 'lines', 'hybrid'. The default type is 'bar'.
  412. chartType: "clusteredBars",
  413. //
  414. // reverse: Boolean
  415. // true if the chart independant axis should be reversed.
  416. reverse: false,
  417. //
  418. // animate: Object
  419. // if an object is supplied, then the chart bars or columns will animate
  420. // into place. If the object contains a field 'duration' then the value
  421. // supplied is the duration of the animation in milliseconds, otherwise
  422. // a default duration is used. A boolean value true can alternatively be
  423. // supplied to enable animation with the default duration.
  424. // The default is null (no animation).
  425. animate: null,
  426. //
  427. // labelMod: Integer
  428. // the frequency of label annotations to be included on the
  429. // independent axis. 1=every label. 0=no labels. The default is 1.
  430. labelMod: 1,
  431. //
  432. // tooltip: String | Function
  433. // a string pattern defining the tooltip text to be applied to chart
  434. // data points, or a function which takes a single parameter and returns
  435. // the tooltip text to be applied to chart data points. The string pattern
  436. // will have the following substitutions applied:
  437. // {0} - the type of chart element ('bar', 'surface', etc)
  438. // {1} - the name of the data series
  439. // {2} - the independent axis value at the tooltip data point
  440. // {3} - the series value at the tooltip data point point
  441. // The function, if supplied, will receive a single parameter exactly
  442. // as per the dojox.charting.action2D.Tooltip class. The default value
  443. // is to apply the default tooltip as defined by the
  444. // dojox.charting.action2D.Tooltip class.
  445. //
  446. // legendHorizontal: Boolean | Number
  447. // true if the legend should be rendered horizontally, or a number if
  448. // the legend should be rendered as horizontal rows with that number of
  449. // items in each row, or false if the legend should be rendered
  450. // vertically (same as specifying 1). The default is true (legend
  451. // rendered horizontally).
  452. legendHorizontal: true,
  453. //
  454. // theme: String|Theme
  455. // a theme to use for the chart, or the name of a theme.
  456. //
  457. // chartNode: String|DomNode
  458. // an optional DOM node or the id of a DOM node to receive a
  459. // chart presentation of the data. Supply only when a chart is
  460. // required and the type is not 'chart'; when the type is
  461. // 'chart' this property will be set to the widget attach point.
  462. //
  463. // legendNode: String|DomNode
  464. // an optional DOM node or the id of a DOM node to receive a
  465. // chart legend for the data. Supply only when a legend is
  466. // required and the type is not 'legend'; when the type is
  467. // 'legend' this property will be set to the widget attach point.
  468. //
  469. // gridNode: String|DomNode
  470. // an optional DOM node or the id of a DOM node to receive a
  471. // grid presentation of the data. Supply only when a grid is
  472. // required and the type is not 'grid'; when the type is
  473. // 'grid' this property will be set to the widget attach point.
  474. //
  475. // titleNode: String|DomNode
  476. // an optional DOM node or the id of a DOM node to receive a
  477. // title for the data. Supply only when a title is
  478. // required and the type is not 'title'; when the type is
  479. // 'title' this property will be set to the widget attach point.
  480. //
  481. // footerNode: String|DomNode
  482. // an optional DOM node or the id of a DOM node to receive a
  483. // footer presentation of the data. Supply only when a footer is
  484. // required and the type is not 'footer'; when the type is
  485. // 'footer' this property will be set to the widget attach point.
  486. //
  487. // chartWidget: Object
  488. // the chart widget, if any
  489. //
  490. // legendWidget: Object
  491. // the legend widget, if any
  492. //
  493. // gridWidget: Object
  494. // the grid widget, if any
  495. constructor: function(node, args){
  496. // summary:
  497. // Set up properties and initialize.
  498. //
  499. // arguments:
  500. // node: DomNode
  501. // The node to attach the data presentation to.
  502. // kwArgs: Object (see above)
  503. // apply arguments directly
  504. dojo.mixin(this, args);
  505. // store our DOM attach point
  506. this.domNode = dojo.byId(node);
  507. // also apply the DOM attach point as the node for the presentation type
  508. this[this.type + "Node"] = this.domNode;
  509. // load the theme if provided by name
  510. if(typeof this.theme == 'string'){
  511. this.theme = dojo.getObject(this.theme);
  512. }
  513. // resolve any the nodes that were supplied as ids
  514. this.chartNode = dojo.byId(this.chartNode);
  515. this.legendNode = dojo.byId(this.legendNode);
  516. this.gridNode = dojo.byId(this.gridNode);
  517. this.titleNode = dojo.byId(this.titleNode);
  518. this.footerNode = dojo.byId(this.footerNode);
  519. // we used to support a 'legendVertical' so for now
  520. // at least maintain backward compatibility
  521. if(this.legendVertical){
  522. this.legendHorizontal = !this.legendVertical;
  523. }
  524. if(this.url){
  525. this.setURL(null, null, this.refreshInterval);
  526. }
  527. else{
  528. if(this.data){
  529. this.setData(null, this.refreshInterval);
  530. }
  531. else{
  532. this.setStore();
  533. }
  534. }
  535. },
  536. setURL: function(/*String?*/url, /*Object?*/ urlContent, /*Number?*/refreshInterval){
  537. // summary:
  538. // Sets the URL to fetch data from, with optional content
  539. // supplied with the request, and an optional
  540. // refresh interval in milliseconds (0=no refresh)
  541. // if a refresh interval is supplied we will start a fresh
  542. // refresh after storing the supplied url
  543. if(refreshInterval){
  544. this.cancelRefresh();
  545. }
  546. this.url = url || this.url;
  547. this.urlContent = urlContent || this.urlContent;
  548. this.refreshInterval = refreshInterval || this.refreshInterval;
  549. var me = this;
  550. dojo.xhrGet({
  551. url: this.url,
  552. content: this.urlContent,
  553. handleAs: 'json-comment-optional',
  554. load: function(response, ioArgs){
  555. me.setData(response);
  556. },
  557. error: function(xhr, ioArgs){
  558. if(me.urlError && (typeof me.urlError == "function")){
  559. me.urlError(xhr, ioArgs);
  560. }
  561. }
  562. });
  563. if(refreshInterval && (this.refreshInterval > 0)){
  564. this.refreshIntervalPending = setInterval(function(){
  565. me.setURL();
  566. }, this.refreshInterval);
  567. }
  568. },
  569. setData: function(/*Object?*/data, /*Number?*/refreshInterval){
  570. // summary:
  571. // Sets the data to be presented, and an optional
  572. // refresh interval in milliseconds (0=no refresh)
  573. // if a refresh interval is supplied we will start a fresh
  574. // refresh after storing the supplied data reference
  575. if(refreshInterval){
  576. this.cancelRefresh();
  577. }
  578. this.data = data || this.data;
  579. this.refreshInterval = refreshInterval || this.refreshInterval;
  580. // TODO if no 'series' property was provided, build one intelligently here
  581. // (until that is done, a 'series' property must be supplied)
  582. var _series = (typeof this.series == 'function') ? this.series(this.data) : this.series;
  583. var datasets = [],
  584. series_data = [],
  585. series_name = [],
  586. series_chart = [],
  587. series_charttype = [],
  588. series_linestyle = [],
  589. series_axis = [],
  590. series_grid = [],
  591. series_gridformatter = [],
  592. maxlen = 0;
  593. // identify the dataset arrays in which series values can be found
  594. for(var ser = 0; ser < _series.length; ser++){
  595. datasets[ser] = getSubfield(this.data, _series[ser].datapoints);
  596. if(datasets[ser] && (datasets[ser].length > maxlen)){
  597. maxlen = datasets[ser].length;
  598. }
  599. series_data[ser] = [];
  600. // name can be specified in series structure, or by field in series structure, otherwise use a default
  601. series_name[ser] = _series[ser].name || (_series[ser].namefield ? getSubfield(this.data, _series[ser].namefield) : null) || ("series " + ser);
  602. series_chart[ser] = (_series[ser].chart !== false);
  603. series_charttype[ser] = _series[ser].charttype || "bar";
  604. series_linestyle[ser] = _series[ser].linestyle;
  605. series_axis[ser] = _series[ser].axis || "primary";
  606. series_grid[ser] = (_series[ser].grid !== false);
  607. series_gridformatter[ser] = _series[ser].gridformatter;
  608. }
  609. // create an array of data points by sampling the series
  610. // and an array of series arrays by collecting the series
  611. // each data point has an 'index' item containing a sequence number
  612. // and items named "data.0", "data.1", ... containing the series samples
  613. // and the first data point also has items named "name.0", "name.1", ... containing the series names
  614. // and items named "series.0", "series.1", ... containing arrays with the complete series in
  615. var point, datapoint, datavalue, fdatavalue;
  616. var datapoints = [];
  617. for(point = 0; point < maxlen; point++){
  618. datapoint = { index: point };
  619. for(ser = 0; ser < _series.length; ser++){
  620. if(datasets[ser] && (datasets[ser].length > point)){
  621. datavalue = getSubfield(datasets[ser][point], _series[ser].field);
  622. if(series_chart[ser]){
  623. // convert the data value to a float if possible
  624. fdatavalue = parseFloat(datavalue);
  625. if(!isNaN(fdatavalue)){
  626. datavalue = fdatavalue;
  627. }
  628. }
  629. datapoint["data." + ser] = datavalue;
  630. series_data[ser].push(datavalue);
  631. }
  632. }
  633. datapoints.push(datapoint);
  634. }
  635. if(maxlen <= 0){
  636. datapoints.push({index: 0});
  637. }
  638. // now build a prepared store from the data points we've constructed
  639. var store = new dojo.data.ItemFileWriteStore({ data: { identifier: 'index', items: datapoints }});
  640. if(this.data.title){
  641. store.title = this.data.title;
  642. }
  643. if(this.data.footer){
  644. store.footer = this.data.footer;
  645. }
  646. store.series_data = series_data;
  647. store.series_name = series_name;
  648. store.series_chart = series_chart;
  649. store.series_charttype = series_charttype;
  650. store.series_linestyle = series_linestyle;
  651. store.series_axis = series_axis;
  652. store.series_grid = series_grid;
  653. store.series_gridformatter = series_gridformatter;
  654. this.setPreparedStore(store);
  655. if(refreshInterval && (this.refreshInterval > 0)){
  656. var me = this;
  657. this.refreshIntervalPending = setInterval(function(){
  658. me.setData();
  659. }, this.refreshInterval);
  660. }
  661. },
  662. refresh: function(){
  663. // summary:
  664. // If a URL or data has been supplied, refreshes the
  665. // presented data from the URL or data. If a refresh
  666. // interval is also set, the periodic refresh is
  667. // restarted. If a URL or data was not supplied, this
  668. // method has no effect.
  669. if(this.url){
  670. this.setURL(this.url, this.urlContent, this.refreshInterval);
  671. }else if(this.data){
  672. this.setData(this.data, this.refreshInterval);
  673. }
  674. },
  675. cancelRefresh: function(){
  676. // summary:
  677. // Cancels any and all outstanding data refreshes
  678. if(this.refreshIntervalPending){
  679. // cancel existing refresh
  680. clearInterval(this.refreshIntervalPending);
  681. this.refreshIntervalPending = undefined;
  682. }
  683. },
  684. setStore: function(/*Object?*/store, /*String?*/query, /*Object?*/queryOptions){
  685. // FIXME build a prepared store properly -- this requires too tight a convention to be followed to be useful
  686. this.setPreparedStore(store, query, queryOptions);
  687. },
  688. setPreparedStore: function(/*Object?*/store, /*String?*/query, /*Object?*/queryOptions){
  689. // summary:
  690. // Sets the store and query.
  691. //
  692. this.preparedstore = store || this.store;
  693. this.query = query || this.query;
  694. this.queryOptions = queryOptions || this.queryOptions;
  695. if(this.preparedstore){
  696. if(this.chartNode){
  697. this.chartWidget = setupChart(this.chartNode, this.chartWidget, this.chartType, this.reverse, this.animate, this.labelMod, this.theme, this.tooltip, this.preparedstore, this.query, this,queryOptions);
  698. this.renderChartWidget();
  699. }
  700. if(this.legendNode){
  701. this.legendWidget = setupLegend(this.legendNode, this.legendWidget, this.chartWidget, this.legendHorizontal);
  702. }
  703. if(this.gridNode){
  704. this.gridWidget = setupGrid(this.gridNode, this.gridWidget, this.preparedstore, this.query, this.queryOptions);
  705. this.renderGridWidget();
  706. }
  707. if(this.titleNode){
  708. setupTitle(this.titleNode, this.preparedstore);
  709. }
  710. if(this.footerNode){
  711. setupFooter(this.footerNode, this.preparedstore);
  712. }
  713. }
  714. },
  715. renderChartWidget: function(){
  716. // summary:
  717. // Renders the chart widget (if any). This method is
  718. // called whenever a chart widget is created or
  719. // configured, and may be connected to.
  720. if(this.chartWidget){
  721. this.chartWidget.render();
  722. }
  723. },
  724. renderGridWidget: function(){
  725. // summary:
  726. // Renders the grid widget (if any). This method is
  727. // called whenever a grid widget is created or
  728. // configured, and may be connected to.
  729. if(this.gridWidget){
  730. this.gridWidget.render();
  731. }
  732. },
  733. getChartWidget: function(){
  734. // summary:
  735. // Returns the chart widget (if any) created if the type
  736. // is "chart" or the "chartNode" property was supplied.
  737. return this.chartWidget;
  738. },
  739. getGridWidget: function(){
  740. // summary:
  741. // Returns the grid widget (if any) created if the type
  742. // is "grid" or the "gridNode" property was supplied.
  743. return this.gridWidget;
  744. },
  745. destroy: function(){
  746. // summary:
  747. // Destroys the widget and all components and resources.
  748. // cancel any outstanding refresh requests
  749. this.cancelRefresh();
  750. if(this.chartWidget){
  751. this.chartWidget.destroy();
  752. delete this.chartWidget;
  753. }
  754. if(this.legendWidget){
  755. // no legend.destroy()
  756. delete this.legendWidget;
  757. }
  758. if(this.gridWidget){
  759. // no grid.destroy()
  760. delete this.gridWidget;
  761. }
  762. if(this.chartNode){
  763. this.chartNode.innerHTML = "";
  764. }
  765. if(this.legendNode){
  766. this.legendNode.innerHTML = "";
  767. }
  768. if(this.gridNode){
  769. this.gridNode.innerHTML = "";
  770. }
  771. if(this.titleNode){
  772. this.titleNode.innerHTML = "";
  773. }
  774. if(this.footerNode){
  775. this.footerNode.innerHTML = "";
  776. }
  777. }
  778. });
  779. })();
  780. }