source: Dev/trunk/src/client/dojox/widget/DataPresentation.js @ 485

Last change on this file since 485 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 29.4 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                //              A widget that connects to a data store in a simple manner,
362                //              and also provides some additional convenience mechanisms
363                //              for connecting to common data sources without needing to
364                //              explicitly construct a Dojo data store. The widget can then
365                //              present the data in several forms: as a graphical chart,
366                //              as a tabular grid, or as display panels presenting meta-data
367                //              (title, creation information, etc) from the data. The
368                //              widget can also create and manage several of these forms
369                //              in one simple construction.
370                //
371                //              Note: this is a first experimental draft and any/all details
372                //              are subject to substantial change in later drafts.
373                // example:
374                // |    var pres = new dojox.data.DataPresentation("myChartNode", {
375                // |            type: "chart",
376                // |            url: "/data/mydata",
377                // |            gridNode: "myGridNode"
378                // |    });
379
380                // store: Object
381                //              Dojo data store used to supply data to be presented. This may
382                //              be supplied on construction or created implicitly based on
383                //              other construction parameters ('data', 'url').
384
385                // query: String
386                //              Query to apply to the Dojo data store used to supply data to
387                //              be presented.
388
389                // queryOptions: String
390                //              Query options to apply to the Dojo data store used to supply
391                //              data to be presented.
392
393                // data: Object
394                //              Data to be presented. If supplied on construction this property
395                //              will override any value supplied for the 'store' property.
396
397                // url: String
398                //              URL to fetch data from in JSON format. If supplied on
399                //              construction this property will override any values supplied
400                //              for the 'store' and/or 'data' properties. Note that the data
401                //              can also be comment-filtered JSON, although this will trigger
402                //              a warning message in the console unless djConfig.useCommentedJson
403                //              has been set to true.
404
405                // urlContent: Object
406                //              Content to be passed to the URL when fetching data. If a URL has
407                //              not been supplied, this value is ignored.
408
409                // urlError: function
410                //              A function to be called if an error is encountered when fetching
411                //              data from the supplied URL. This function will be supplied with
412                //              two parameters exactly as the error function supplied to the
413                //              dojo.xhrGet function. This function may be called multiple times
414                //              if a refresh interval has been supplied.
415
416                // refreshInterval: Number
417                //              the time interval in milliseconds after which the data supplied
418                //              via the 'data' property or fetched from a URL via the 'url'
419                //              property should be regularly refreshed. This property is
420                //              ignored if neither the 'data' nor 'url' property has been
421                //              supplied. If the refresh interval is zero, no regular refresh is done.
422
423                // refreshIntervalPending:
424                //              the JavaScript set interval currently in progress, if any
425
426                // series: Array
427                //              an array of objects describing the data series to be included
428                //              in the data presentation. Each object may contain the
429                //              following fields:
430                //
431                //              - datapoints: the name of the field from the source data which
432                //                      contains an array of the data points for this data series.
433                //                      If not supplied, the source data is assumed to be an array
434                //                      of data points to be used.
435                //              - field: the name of the field within each data point which
436                //                      contains the data for this data series. If not supplied,
437                //                      each data point is assumed to be the value for the series.
438                //              - name: a name for the series, used in the legend and grid headings
439                //              - namefield:
440                //                      the name of the field from the source data which
441                //                      contains the name the series, used in the legend and grid
442                //                      headings. If both name and namefield are supplied, name takes
443                //                  precedence. If neither are supplied, a default name is used.
444                //              - chart: true if the series should be included in a chart presentation (default: true)
445                //              - charttype: the type of presentation of the series in the chart, which can be
446                //                      "range", "line", "bar" (default: "bar")
447                //              - linestyle: the stroke style for lines (if applicable) (default: "Solid")
448                //              - axis: the dependant axis to which the series will be attached in the chart,
449                //                  which can be "primary" or "secondary"
450                //              - grid: true if the series should be included in a data grid presentation (default: true)
451                //              - gridformatter: an optional formatter to use for this series in the data grid
452                //
453                //              a call-back function may alternatively be supplied. The function takes
454                //              a single parameter, which will be the data (from the 'data' field or
455                //              loaded from the value in the 'url' field), and should return the array
456                //              of objects describing the data series to be included in the data
457                //              presentation. This enables the series structures to be built dynamically
458                //              after data load, and rebuilt if necessary on data refresh. The call-back
459                //              function will be called each time new data is set, loaded or refreshed.
460                //              A call-back function cannot be used if the data is supplied directly
461                //              from a Dojo data store.
462
463                // type: String
464                //              the type of presentation to be applied at the DOM attach point.
465                //              This can be 'chart', 'legend', 'grid', 'title', 'footer'. The
466                //              default type is 'chart'.
467                type: "chart",
468
469                // chartType: String
470                //              the type of chart to display. This can be 'clusteredbars',
471                //              'areas', 'stackedcolumns', 'stackedbars', 'stackedareas',
472                //              'lines', 'hybrid'. The default type is 'bar'.
473                chartType: "clusteredBars",
474
475                // reverse: Boolean
476                //              true if the chart independent axis should be reversed.
477                reverse: false,
478
479                // animate: Object
480                //              if an object is supplied, then the chart bars or columns will animate
481                //              into place. If the object contains a field 'duration' then the value
482                //              supplied is the duration of the animation in milliseconds, otherwise
483                //              a default duration is used. A boolean value true can alternatively be
484                //              supplied to enable animation with the default duration.
485                //              The default is null (no animation).
486                animate: null,
487
488                // labelMod: Integer
489                //              the frequency of label annotations to be included on the
490                //              independent axis. 1=every label. 0=no labels. The default is 1.
491                labelMod: 1,
492
493                // tooltip: String|Function
494                //              a string pattern defining the tooltip text to be applied to chart
495                //              data points, or a function which takes a single parameter and returns
496                //              the tooltip text to be applied to chart data points. The string pattern
497                //              will have the following substitutions applied:
498                //
499                //              - {0} - the type of chart element ('bar', 'surface', etc)
500                //              - {1} - the name of the data series
501                //              - {2} - the independent axis value at the tooltip data point
502                //              - {3} - the series value at the tooltip data point point
503                //
504                //              The function, if supplied, will receive a single parameter exactly
505                //              as per the dojox.charting.action2D.Tooltip class. The default value
506                //              is to apply the default tooltip as defined by the
507                //              dojox.charting.action2D.Tooltip class.
508
509                // legendHorizontal: Boolean|Number
510                //              true if the legend should be rendered horizontally, or a number if
511                //              the legend should be rendered as horizontal rows with that number of
512                //              items in each row, or false if the legend should be rendered
513                //              vertically (same as specifying 1). The default is true (legend
514                //              rendered horizontally).
515                legendHorizontal: true,
516
517                // theme: String|Theme
518                //              a theme to use for the chart, or the name of a theme.
519
520                // chartNode: String|DomNode
521                //              an optional DOM node or the id of a DOM node to receive a
522                //              chart presentation of the data. Supply only when a chart is
523                //              required and the type is not 'chart'; when the type is
524                //              'chart' this property will be set to the widget attach point.
525
526                // legendNode: String|DomNode
527                //              an optional DOM node or the id of a DOM node to receive a
528                //              chart legend for the data. Supply only when a legend is
529                //              required and the type is not 'legend'; when the type is
530                //              'legend' this property will be set to the widget attach point.
531
532                // gridNode: String|DomNode
533                //              an optional DOM node or the id of a DOM node to receive a
534                //              grid presentation of the data. Supply only when a grid is
535                //              required and the type is not 'grid'; when the type is
536                //              'grid' this property will be set to the widget attach point.
537
538                // titleNode: String|DomNode
539                //              an optional DOM node or the id of a DOM node to receive a
540                //              title for the data. Supply only when a title is
541                //              required and the type is not 'title'; when the type is
542                //              'title' this property will be set to the widget attach point.
543
544                // footerNode: String|DomNode
545                //              an optional DOM node or the id of a DOM node to receive a
546                //              footer presentation of the data. Supply only when a footer is
547                //              required and the type is not 'footer'; when the type is
548                //              'footer' this property will be set to the widget attach point.
549
550                // chartWidget: Object
551                //              the chart widget, if any
552
553                // legendWidget: Object
554                //              the legend widget, if any
555
556                // gridWidget: Object
557                //              the grid widget, if any
558               
559                constructor: function(node, args){
560                        // summary:
561                        //              Set up properties and initialize.
562                        // node: DomNode
563                        //              The node to attach the data presentation to.
564                        // args: Object
565                        //              (see above)
566                       
567                        // apply arguments directly
568                        dojo.mixin(this, args);
569
570                        // store our DOM attach point
571                        this.domNode = dojo.byId(node);
572                       
573                        // also apply the DOM attach point as the node for the presentation type
574                        this[this.type + "Node"] = this.domNode;
575                       
576                        // load the theme if provided by name
577                        if(typeof this.theme == 'string'){
578                                this.theme = dojo.getObject(this.theme);
579                        }
580                       
581                        // resolve any the nodes that were supplied as ids
582                        this.chartNode = dojo.byId(this.chartNode);
583                        this.legendNode = dojo.byId(this.legendNode);
584                        this.gridNode = dojo.byId(this.gridNode);
585                        this.titleNode = dojo.byId(this.titleNode);
586                        this.footerNode = dojo.byId(this.footerNode);
587                       
588                        // we used to support a 'legendVertical' so for now
589                        // at least maintain backward compatibility
590                        if(this.legendVertical){
591                                this.legendHorizontal = !this.legendVertical;
592                        }
593                       
594                        if(this.url){
595                                this.setURL(null, null, this.refreshInterval);
596                        }
597                        else{
598                                if(this.data){
599                                        this.setData(null, this.refreshInterval);
600                                }
601                                else{
602                                        this.setStore();
603                                }
604                        }
605                },
606               
607                setURL: function(/*String?*/url, /*Object?*/ urlContent, /*Number?*/refreshInterval){
608                        // summary:
609                        //              Sets the URL to fetch data from, with optional content
610                        //              supplied with the request, and an optional
611                        //              refresh interval in milliseconds (0=no refresh)
612
613                        // if a refresh interval is supplied we will start a fresh
614                        // refresh after storing the supplied url
615                        if(refreshInterval){
616                                this.cancelRefresh();
617                        }
618                       
619                        this.url = url || this.url;
620                        this.urlContent = urlContent || this.urlContent;
621                        this.refreshInterval = refreshInterval || this.refreshInterval;
622                       
623                        var me = this;
624                       
625                        dojo.xhrGet({
626                                url: this.url,
627                                content: this.urlContent,
628                                handleAs: 'json-comment-optional',
629                                load: function(response, ioArgs){
630                                        me.setData(response);
631                                },
632                                error: function(xhr, ioArgs){
633                                        if(me.urlError && (typeof me.urlError == "function")){
634                                                me.urlError(xhr, ioArgs);
635                                        }
636                                }
637                        });
638                       
639                        if(refreshInterval && (this.refreshInterval > 0)){
640                                this.refreshIntervalPending = setInterval(function(){
641                                        me.setURL();
642                                }, this.refreshInterval);
643                        }
644                },
645               
646                setData: function(/*Object?*/data, /*Number?*/refreshInterval){
647                        // summary:
648                        //              Sets the data to be presented, and an optional
649                        //              refresh interval in milliseconds (0=no refresh)
650                       
651                        // if a refresh interval is supplied we will start a fresh
652                        // refresh after storing the supplied data reference
653                        if(refreshInterval){
654                                this.cancelRefresh();
655                        }
656                       
657                        this.data = data || this.data;
658                        this.refreshInterval = refreshInterval || this.refreshInterval;
659                       
660                        // TODO if no 'series' property was provided, build one intelligently here
661                        // (until that is done, a 'series' property must be supplied)
662                       
663                        var _series = (typeof this.series == 'function') ? this.series(this.data) : this.series;
664
665                        var datasets = [],
666                                series_data = [],
667                                series_name = [],
668                                series_chart = [],
669                                series_charttype = [],
670                                series_linestyle = [],
671                                series_axis = [],
672                                series_grid = [],
673                                series_gridformatter = [],
674                                maxlen = 0;
675                       
676                        // identify the dataset arrays in which series values can be found
677                        for(var ser = 0; ser < _series.length; ser++){
678                                datasets[ser] = getSubfield(this.data, _series[ser].datapoints);
679                                if(datasets[ser] && (datasets[ser].length > maxlen)){
680                                        maxlen = datasets[ser].length;
681                                }
682                               
683                                series_data[ser] = [];
684                                // name can be specified in series structure, or by field in series structure, otherwise use a default
685                                series_name[ser] = _series[ser].name || (_series[ser].namefield ? getSubfield(this.data, _series[ser].namefield) : null) || ("series " + ser);
686                                series_chart[ser] = (_series[ser].chart !== false);
687                                series_charttype[ser] = _series[ser].charttype || "bar";
688                                series_linestyle[ser] = _series[ser].linestyle;
689                                series_axis[ser] = _series[ser].axis || "primary";
690                                series_grid[ser] = (_series[ser].grid !== false);
691                                series_gridformatter[ser] = _series[ser].gridformatter;
692                        }
693               
694                        // create an array of data points by sampling the series
695                        // and an array of series arrays by collecting the series
696                        // each data point has an 'index' item containing a sequence number
697                        // and items named "data.0", "data.1", ... containing the series samples
698                        // and the first data point also has items named "name.0", "name.1", ... containing the series names
699                        // and items named "series.0", "series.1", ... containing arrays with the complete series in
700                        var point, datapoint, datavalue, fdatavalue;
701                        var datapoints = [];
702                       
703                        for(point = 0; point < maxlen; point++){
704                                datapoint = { index: point };
705                                for(ser = 0; ser < _series.length; ser++){
706                                        if(datasets[ser] && (datasets[ser].length > point)){
707                                                datavalue = getSubfield(datasets[ser][point], _series[ser].field);
708                                               
709                                                if(series_chart[ser]){
710                                                        // convert the data value to a float if possible
711                                                        fdatavalue = parseFloat(datavalue);
712                                                        if(!isNaN(fdatavalue)){
713                                                                datavalue = fdatavalue;
714                                                        }
715                                                }
716                                               
717                                                datapoint["data." + ser] = datavalue;
718                                                series_data[ser].push(datavalue);
719                                        }
720                                }
721                                datapoints.push(datapoint);
722                        }
723
724                        if(maxlen <= 0){
725                                datapoints.push({index: 0});
726                        }
727               
728                        // now build a prepared store from the data points we've constructed
729                        var store = new dojo.data.ItemFileWriteStore({ data: { identifier: 'index', items: datapoints }});
730                        if(this.data.title){
731                                store.title = this.data.title;
732                        }
733                        if(this.data.footer){
734                                store.footer = this.data.footer;
735                        }
736                       
737                        store.series_data = series_data;
738                        store.series_name = series_name;
739                        store.series_chart = series_chart;
740                        store.series_charttype = series_charttype;
741                        store.series_linestyle = series_linestyle;
742                        store.series_axis = series_axis;
743                        store.series_grid = series_grid;
744                        store.series_gridformatter = series_gridformatter;
745                       
746                        this.setPreparedStore(store);
747                       
748                        if(refreshInterval && (this.refreshInterval > 0)){
749                                var me = this;
750                                this.refreshIntervalPending = setInterval(function(){
751                                        me.setData();
752                                }, this.refreshInterval);
753                        }
754                },
755               
756                refresh: function(){
757                        // summary:
758                        //              If a URL or data has been supplied, refreshes the
759                        //              presented data from the URL or data. If a refresh
760                        //              interval is also set, the periodic refresh is
761                        //              restarted. If a URL or data was not supplied, this
762                        //              method has no effect.
763                        if(this.url){
764                                this.setURL(this.url, this.urlContent, this.refreshInterval);
765                        }else if(this.data){
766                                this.setData(this.data, this.refreshInterval);
767                        }
768                },
769               
770                cancelRefresh: function(){
771                        // summary:
772                        //              Cancels any and all outstanding data refreshes
773                        if(this.refreshIntervalPending){
774                                // cancel existing refresh
775                                clearInterval(this.refreshIntervalPending);
776                                this.refreshIntervalPending = undefined;
777                        }
778                },
779       
780                setStore: function(/*Object?*/store, /*String?*/query, /*Object?*/queryOptions){
781                        // FIXME build a prepared store properly -- this requires too tight a convention to be followed to be useful
782                        this.setPreparedStore(store, query, queryOptions);
783                },
784               
785                setPreparedStore: function(/*Object?*/store, /*String?*/query, /*Object?*/queryOptions){
786                        // summary:
787                        //              Sets the store and query.
788
789                        this.preparedstore = store || this.store;
790                        this.query = query || this.query;
791                        this.queryOptions = queryOptions || this.queryOptions;
792                       
793                        if(this.preparedstore){
794                                if(this.chartNode){
795                                        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);
796                                        this.renderChartWidget();
797                                }
798                                if(this.legendNode){
799                                        this.legendWidget = setupLegend(this.legendNode, this.legendWidget, this.chartWidget, this.legendHorizontal);
800                                }
801                                if(this.gridNode){
802                                        this.gridWidget = setupGrid(this.gridNode, this.gridWidget, this.preparedstore, this.query, this.queryOptions);
803                                        this.renderGridWidget();
804                                }
805                                if(this.titleNode){
806                                        setupTitle(this.titleNode, this.preparedstore);
807                                }
808                                if(this.footerNode){
809                                        setupFooter(this.footerNode, this.preparedstore);
810                                }
811                        }
812                },
813               
814                renderChartWidget: function(){
815                        // summary:
816                        //              Renders the chart widget (if any). This method is
817                        //              called whenever a chart widget is created or
818                        //              configured, and may be connected to.
819                        if(this.chartWidget){
820                                this.chartWidget.render();
821                        }
822                },
823               
824                renderGridWidget: function(){
825                        // summary:
826                        //              Renders the grid widget (if any). This method is
827                        //              called whenever a grid widget is created or
828                        //              configured, and may be connected to.
829                        if(this.gridWidget){
830                                this.gridWidget.render();
831                        }
832                },
833               
834                getChartWidget: function(){
835                        // summary:
836                        //              Returns the chart widget (if any) created if the type
837                        //              is "chart" or the "chartNode" property was supplied.
838                        return this.chartWidget;
839                },
840               
841                getGridWidget: function(){
842                        // summary:
843                        //              Returns the grid widget (if any) created if the type
844                        //              is "grid" or the "gridNode" property was supplied.
845                        return this.gridWidget;
846                },
847               
848                destroy: function(){
849                        // summary:
850                        //              Destroys the widget and all components and resources.
851
852                        // cancel any outstanding refresh requests
853                        this.cancelRefresh();
854                       
855                        if(this.chartWidget){
856                                this.chartWidget.destroy();
857                                delete this.chartWidget;
858                        }
859                       
860                        if(this.legendWidget){
861                                // no legend.destroy()
862                                delete this.legendWidget;
863                        }
864
865                        if(this.gridWidget){
866                                // no grid.destroy()
867                                delete this.gridWidget;
868                        }
869                       
870                        if(this.chartNode){
871                                this.chartNode.innerHTML = "";
872                        }
873                       
874                        if(this.legendNode){
875                                this.legendNode.innerHTML = "";
876                        }
877                       
878                        if(this.gridNode){
879                                this.gridNode.innerHTML = "";
880                        }
881                       
882                        if(this.titleNode){
883                                this.titleNode.innerHTML = "";
884                        }
885                       
886                        if(this.footerNode){
887                                this.footerNode.innerHTML = "";
888                        }
889                }
890               
891        });
892               
893})();
Note: See TracBrowser for help on using the repository browser.