source: Dev/branches/rest-dojo-ui/client/dojox/widget/DataPresentation.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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