You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
893 lines
29 KiB
893 lines
29 KiB
dojo.provide("dojox.widget.DataPresentation"); |
|
dojo.experimental("dojox.widget.DataPresentation"); |
|
|
|
dojo.require("dojox.grid.DataGrid"); |
|
dojo.require("dojox.charting.Chart2D"); |
|
dojo.require("dojox.charting.widget.Legend"); |
|
dojo.require("dojox.charting.action2d.Tooltip"); |
|
dojo.require("dojox.charting.action2d.Highlight"); |
|
dojo.require("dojo.colors"); |
|
dojo.require("dojo.data.ItemFileWriteStore"); |
|
|
|
(function(){ |
|
|
|
// sort out the labels for the independent axis of the chart |
|
var getLabels = function(range, labelMod, charttype, domNode){ |
|
|
|
// prepare labels for the independent axis |
|
var labels = []; |
|
// add empty label, hack |
|
labels[0] = {value: 0, text: ''}; |
|
|
|
var nlabels = range.length; |
|
|
|
// auto-set labelMod for horizontal charts if the labels will otherwise collide |
|
if((charttype !== "ClusteredBars") && (charttype !== "StackedBars")){ |
|
var cwid = domNode.offsetWidth; |
|
var tmp = ("" + range[0]).length * range.length * 7; // *assume* 7 pixels width per character ( was 9 ) |
|
|
|
if(labelMod == 1){ |
|
for(var z = 1; z < 500; ++z){ |
|
if((tmp / z) < cwid){ |
|
break; |
|
} |
|
++labelMod; |
|
} |
|
} |
|
} |
|
|
|
// now set the labels |
|
for(var i = 0; i < nlabels; i++){ |
|
//sparse labels |
|
labels.push({ |
|
value: i + 1, |
|
text: (!labelMod || i % labelMod) ? "" : range[i] |
|
}); |
|
} |
|
|
|
// add empty label again, hack |
|
labels.push({value: nlabels + 1, text:''}); |
|
|
|
return labels; |
|
}; |
|
|
|
// get the configuration of an independent axis for the chart |
|
var getIndependentAxisArgs = function(charttype, labels){ |
|
|
|
var args = { vertical: false, labels: labels, min: 0, max: labels.length-1, majorTickStep: 1, minorTickStep: 1 }; |
|
|
|
// clustered or stacked bars have a vertical independent axis |
|
if((charttype === "ClusteredBars") || (charttype === "StackedBars")){ |
|
args.vertical = true; |
|
} |
|
|
|
// lines, areas and stacked areas don't need the extra slots at each end |
|
if((charttype === "Lines") || (charttype === "Areas") || (charttype === "StackedAreas")){ |
|
args.min++; |
|
args.max--; |
|
} |
|
|
|
return args; |
|
}; |
|
|
|
// get the configuration of a dependent axis for the chart |
|
var getDependentAxisArgs = function(charttype, axistype, minval, maxval){ |
|
|
|
var args = { vertical: true, fixLower: "major", fixUpper: "major", natural: true }; |
|
|
|
// secondary dependent axis is not left-bottom |
|
if(axistype === "secondary"){ |
|
args.leftBottom = false; |
|
} |
|
|
|
// clustered or stacked bars have horizontal dependent axes |
|
if((charttype === "ClusteredBars") || (charttype === "StackedBars")){ |
|
args.vertical = false; |
|
} |
|
|
|
// ensure axis does not "collapse" for flat series |
|
if(minval == maxval){ |
|
args.min = minval - 1; |
|
args.max = maxval + 1; |
|
} |
|
|
|
return args; |
|
}; |
|
|
|
// get the configuration of a plot for the chart |
|
var getPlotArgs = function(charttype, axistype, animate){ |
|
|
|
var args = { type: charttype, hAxis: "independent", vAxis: "dependent-" + axistype, gap: 4, lines: false, areas: false, markers: false }; |
|
|
|
// clustered or stacked bars have horizontal dependent axes |
|
if((charttype === "ClusteredBars") || (charttype === "StackedBars")){ |
|
args.hAxis = args.vAxis; |
|
args.vAxis = "independent"; |
|
} |
|
|
|
// turn on lines for Lines, Areas and StackedAreas |
|
if((charttype === "Lines") || (charttype === "Hybrid-Lines") || (charttype === "Areas") || (charttype === "StackedAreas")){ |
|
args.lines = true; |
|
} |
|
|
|
// turn on areas for Areas and StackedAreas |
|
if((charttype === "Areas") || (charttype === "StackedAreas")){ |
|
args.areas = true; |
|
} |
|
|
|
// turn on markers and shadow for Lines |
|
if(charttype === "Lines"){ |
|
args.markers = true; |
|
} |
|
|
|
// turn on shadow for Hybrid-Lines |
|
// also, Hybrid-Lines is not a true chart type: use Lines for the actual plot |
|
if(charttype === "Hybrid-Lines"){ |
|
args.shadows = {dx: 2, dy: 2, dw: 2}; |
|
args.type = "Lines"; |
|
} |
|
|
|
// also, Hybrid-ClusteredColumns is not a true chart type: use ClusteredColumns for the actual plot |
|
if(charttype === "Hybrid-ClusteredColumns"){ |
|
args.type = "ClusteredColumns"; |
|
} |
|
|
|
// enable animation on the plot if animation is requested |
|
if(animate){ |
|
args.animate = animate; |
|
} |
|
|
|
return args; |
|
}; |
|
|
|
// set up a chart presentation |
|
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){ |
|
var _chart = chart; |
|
|
|
if(!_chart){ |
|
domNode.innerHTML = ""; // any other content in the node disrupts the chart rendering |
|
_chart = new dojox.charting.Chart2D(domNode); |
|
} |
|
|
|
// set the theme |
|
if(theme){ |
|
|
|
// workaround for a theme bug: its _clone method |
|
// does not transfer the markers, so we repair |
|
// that omission here |
|
// FIXME this should be removed once the theme bug is fixed |
|
theme._clone = function(){ |
|
var result = new dojox.charting.Theme({ |
|
chart: this.chart, |
|
plotarea: this.plotarea, |
|
axis: this.axis, |
|
series: this.series, |
|
marker: this.marker, |
|
antiAlias: this.antiAlias, |
|
assignColors: this.assignColors, |
|
assignMarkers: this.assigneMarkers, |
|
colors: dojo.delegate(this.colors) |
|
}); |
|
|
|
result.markers = this.markers; |
|
result._buildMarkerArray(); |
|
|
|
return result; |
|
}; |
|
|
|
_chart.setTheme(theme); |
|
} |
|
|
|
var range = store.series_data[0].slice(0); |
|
|
|
// reverse the labels if requested |
|
if(reverse){ |
|
range.reverse(); |
|
} |
|
|
|
var labels = getLabels(range, labelMod, type, domNode); |
|
|
|
// collect details of whether primary and/or secondary axes are required |
|
// and what plots we have instantiated using each type of axis |
|
var plots = {}; |
|
|
|
// collect maximum and minimum data values |
|
var maxval = null; |
|
var minval = null; |
|
|
|
var seriestoremove = {}; |
|
for(var sname in _chart.runs){ |
|
seriestoremove[sname] = true; |
|
} |
|
|
|
// set x values & max data value |
|
var nseries = store.series_name.length; |
|
for(var i = 0; i < nseries; i++){ |
|
// only include series with chart=true and with some data values in |
|
if(store.series_chart[i] && (store.series_data[i].length > 0)){ |
|
|
|
var charttype = type; |
|
var axistype = store.series_axis[i]; |
|
|
|
if(charttype == "Hybrid"){ |
|
if(store.series_charttype[i] == 'line'){ |
|
charttype = "Hybrid-Lines"; |
|
}else{ |
|
charttype = "Hybrid-ClusteredColumns"; |
|
} |
|
} |
|
|
|
// ensure we have recorded that we are using this axis type |
|
if(!plots[axistype]){ |
|
plots[axistype] = {}; |
|
} |
|
|
|
// ensure we have the correct type of plot for this series |
|
if(!plots[axistype][charttype]){ |
|
var axisname = axistype + "-" + charttype; |
|
|
|
// create the plot and enable tooltips |
|
_chart.addPlot(axisname, getPlotArgs(charttype, axistype, animate)); |
|
|
|
var tooltipArgs = {}; |
|
if(typeof tooltip == 'string'){ |
|
tooltipArgs.text = function(o){ |
|
var substitutions = [o.element, o.run.name, range[o.index], ((charttype === "ClusteredBars") || (charttype === "StackedBars")) ? o.x : o.y]; |
|
return dojo.replace(tooltip, substitutions); // from Dojo 1.4 onward |
|
//return tooltip.replace(/\{([^\}]+)\}/g, function(_, token){ return dojo.getObject(token, false, substitutions); }); // prior to Dojo 1.4 |
|
} |
|
}else if(typeof tooltip == 'function'){ |
|
tooltipArgs.text = tooltip; |
|
} |
|
new dojox.charting.action2d.Tooltip(_chart, axisname, tooltipArgs); |
|
|
|
// add highlighting, except for lines |
|
if(charttype !== "Lines" && charttype !== "Hybrid-Lines"){ |
|
new dojox.charting.action2d.Highlight(_chart, axisname); |
|
} |
|
|
|
// record that this plot type is now created |
|
plots[axistype][charttype] = true; |
|
} |
|
|
|
// extract the series values |
|
var xvals = []; |
|
var valen = store.series_data[i].length; |
|
for(var j = 0; j < valen; j++){ |
|
var val = store.series_data[i][j]; |
|
xvals.push(val); |
|
if(maxval === null || val > maxval){ |
|
maxval = val; |
|
} |
|
if(minval === null || val < minval){ |
|
minval = val; |
|
} |
|
} |
|
|
|
// reverse the values if requested |
|
if(reverse){ |
|
xvals.reverse(); |
|
} |
|
|
|
var seriesargs = { plot: axistype + "-" + charttype }; |
|
if(store.series_linestyle[i]){ |
|
seriesargs.stroke = { style: store.series_linestyle[i] }; |
|
} |
|
|
|
_chart.addSeries(store.series_name[i], xvals, seriesargs); |
|
delete seriestoremove[store.series_name[i]]; |
|
} |
|
} |
|
|
|
// remove any series that are no longer needed |
|
for(sname in seriestoremove){ |
|
_chart.removeSeries(sname); |
|
} |
|
|
|
// create axes |
|
_chart.addAxis("independent", getIndependentAxisArgs(type, labels)); |
|
_chart.addAxis("dependent-primary", getDependentAxisArgs(type, "primary", minval, maxval)); |
|
_chart.addAxis("dependent-secondary", getDependentAxisArgs(type, "secondary", minval, maxval)); |
|
|
|
return _chart; |
|
}; |
|
|
|
// set up a legend presentation |
|
var setupLegend = function(/*DomNode*/domNode, /*Legend*/legend, /*Chart2D*/chart, /*Boolean*/horizontal){ |
|
// destroy any existing legend and recreate |
|
var _legend = legend; |
|
|
|
if(!_legend){ |
|
_legend = new dojox.charting.widget.Legend({ chart: chart, horizontal: horizontal }, domNode); |
|
}else{ |
|
_legend.refresh(); |
|
} |
|
|
|
return _legend; |
|
}; |
|
|
|
// set up a grid presentation |
|
var setupGrid = function(/*DomNode*/domNode, /*Object?*/grid, /*Object?*/store, /*String?*/query, /*String?*/queryOptions){ |
|
var _grid = grid || new dojox.grid.DataGrid({}, domNode); |
|
_grid.startup(); |
|
_grid.setStore(store, query, queryOptions); |
|
|
|
var structure = []; |
|
for(var ser = 0; ser < store.series_name.length; ser++){ |
|
// only include series with grid=true and with some data values in |
|
if(store.series_grid[ser] && (store.series_data[ser].length > 0)){ |
|
structure.push({ field: "data." + ser, name: store.series_name[ser], width: "auto", formatter: store.series_gridformatter[ser] }); |
|
} |
|
} |
|
|
|
_grid.setStructure(structure); |
|
|
|
return _grid; |
|
}; |
|
|
|
// set up a title presentation |
|
var setupTitle = function(/*DomNode*/domNode, /*object*/store){ |
|
if(store.title){ |
|
domNode.innerHTML = store.title; |
|
} |
|
}; |
|
|
|
// set up a footer presentation |
|
var setupFooter = function(/*DomNode*/domNode, /*object*/store){ |
|
if(store.footer){ |
|
domNode.innerHTML = store.footer; |
|
} |
|
}; |
|
|
|
// obtain a subfield from a field specifier which may contain |
|
// multiple levels (eg, "child.foo[36].manacle") |
|
var getSubfield = function(/*Object*/object, /*String*/field){ |
|
var result = object; |
|
|
|
if(field){ |
|
var fragments = field.split(/[.\[\]]+/); |
|
for(var frag = 0, l = fragments.length; frag < l; frag++){ |
|
if(result){ |
|
result = result[fragments[frag]]; |
|
} |
|
} |
|
} |
|
|
|
return result; |
|
}; |
|
|
|
dojo.declare("dojox.widget.DataPresentation", null, { |
|
// summary: |
|
// A widget that connects to a data store in a simple manner, |
|
// and also provides some additional convenience mechanisms |
|
// for connecting to common data sources without needing to |
|
// explicitly construct a Dojo data store. The widget can then |
|
// present the data in several forms: as a graphical chart, |
|
// as a tabular grid, or as display panels presenting meta-data |
|
// (title, creation information, etc) from the data. The |
|
// widget can also create and manage several of these forms |
|
// in one simple construction. |
|
// |
|
// Note: this is a first experimental draft and any/all details |
|
// are subject to substantial change in later drafts. |
|
// example: |
|
// | var pres = new dojox.data.DataPresentation("myChartNode", { |
|
// | type: "chart", |
|
// | url: "/data/mydata", |
|
// | gridNode: "myGridNode" |
|
// | }); |
|
|
|
// store: Object |
|
// Dojo data store used to supply data to be presented. This may |
|
// be supplied on construction or created implicitly based on |
|
// other construction parameters ('data', 'url'). |
|
|
|
// query: String |
|
// Query to apply to the Dojo data store used to supply data to |
|
// be presented. |
|
|
|
// queryOptions: String |
|
// Query options to apply to the Dojo data store used to supply |
|
// data to be presented. |
|
|
|
// data: Object |
|
// Data to be presented. If supplied on construction this property |
|
// will override any value supplied for the 'store' property. |
|
|
|
// url: String |
|
// URL to fetch data from in JSON format. If supplied on |
|
// construction this property will override any values supplied |
|
// for the 'store' and/or 'data' properties. Note that the data |
|
// can also be comment-filtered JSON, although this will trigger |
|
// a warning message in the console unless djConfig.useCommentedJson |
|
// has been set to true. |
|
|
|
// urlContent: Object |
|
// Content to be passed to the URL when fetching data. If a URL has |
|
// not been supplied, this value is ignored. |
|
|
|
// urlError: function |
|
// A function to be called if an error is encountered when fetching |
|
// data from the supplied URL. This function will be supplied with |
|
// two parameters exactly as the error function supplied to the |
|
// dojo.xhrGet function. This function may be called multiple times |
|
// if a refresh interval has been supplied. |
|
|
|
// refreshInterval: Number |
|
// the time interval in milliseconds after which the data supplied |
|
// via the 'data' property or fetched from a URL via the 'url' |
|
// property should be regularly refreshed. This property is |
|
// ignored if neither the 'data' nor 'url' property has been |
|
// supplied. If the refresh interval is zero, no regular refresh is done. |
|
|
|
// refreshIntervalPending: |
|
// the JavaScript set interval currently in progress, if any |
|
|
|
// series: Array |
|
// an array of objects describing the data series to be included |
|
// in the data presentation. Each object may contain the |
|
// following fields: |
|
// |
|
// - datapoints: the name of the field from the source data which |
|
// contains an array of the data points for this data series. |
|
// If not supplied, the source data is assumed to be an array |
|
// of data points to be used. |
|
// - field: the name of the field within each data point which |
|
// contains the data for this data series. If not supplied, |
|
// each data point is assumed to be the value for the series. |
|
// - name: a name for the series, used in the legend and grid headings |
|
// - namefield: |
|
// the name of the field from the source data which |
|
// contains the name the series, used in the legend and grid |
|
// headings. If both name and namefield are supplied, name takes |
|
// precedence. If neither are supplied, a default name is used. |
|
// - chart: true if the series should be included in a chart presentation (default: true) |
|
// - charttype: the type of presentation of the series in the chart, which can be |
|
// "range", "line", "bar" (default: "bar") |
|
// - linestyle: the stroke style for lines (if applicable) (default: "Solid") |
|
// - axis: the dependant axis to which the series will be attached in the chart, |
|
// which can be "primary" or "secondary" |
|
// - grid: true if the series should be included in a data grid presentation (default: true) |
|
// - gridformatter: an optional formatter to use for this series in the data grid |
|
// |
|
// a call-back function may alternatively be supplied. The function takes |
|
// a single parameter, which will be the data (from the 'data' field or |
|
// loaded from the value in the 'url' field), and should return the array |
|
// of objects describing the data series to be included in the data |
|
// presentation. This enables the series structures to be built dynamically |
|
// after data load, and rebuilt if necessary on data refresh. The call-back |
|
// function will be called each time new data is set, loaded or refreshed. |
|
// A call-back function cannot be used if the data is supplied directly |
|
// from a Dojo data store. |
|
|
|
// type: String |
|
// the type of presentation to be applied at the DOM attach point. |
|
// This can be 'chart', 'legend', 'grid', 'title', 'footer'. The |
|
// default type is 'chart'. |
|
type: "chart", |
|
|
|
// chartType: String |
|
// the type of chart to display. This can be 'clusteredbars', |
|
// 'areas', 'stackedcolumns', 'stackedbars', 'stackedareas', |
|
// 'lines', 'hybrid'. The default type is 'bar'. |
|
chartType: "clusteredBars", |
|
|
|
// reverse: Boolean |
|
// true if the chart independent axis should be reversed. |
|
reverse: false, |
|
|
|
// animate: Object |
|
// if an object is supplied, then the chart bars or columns will animate |
|
// into place. If the object contains a field 'duration' then the value |
|
// supplied is the duration of the animation in milliseconds, otherwise |
|
// a default duration is used. A boolean value true can alternatively be |
|
// supplied to enable animation with the default duration. |
|
// The default is null (no animation). |
|
animate: null, |
|
|
|
// labelMod: Integer |
|
// the frequency of label annotations to be included on the |
|
// independent axis. 1=every label. 0=no labels. The default is 1. |
|
labelMod: 1, |
|
|
|
// tooltip: String|Function |
|
// a string pattern defining the tooltip text to be applied to chart |
|
// data points, or a function which takes a single parameter and returns |
|
// the tooltip text to be applied to chart data points. The string pattern |
|
// will have the following substitutions applied: |
|
// |
|
// - {0} - the type of chart element ('bar', 'surface', etc) |
|
// - {1} - the name of the data series |
|
// - {2} - the independent axis value at the tooltip data point |
|
// - {3} - the series value at the tooltip data point point |
|
// |
|
// The function, if supplied, will receive a single parameter exactly |
|
// as per the dojox.charting.action2D.Tooltip class. The default value |
|
// is to apply the default tooltip as defined by the |
|
// dojox.charting.action2D.Tooltip class. |
|
|
|
// legendHorizontal: Boolean|Number |
|
// true if the legend should be rendered horizontally, or a number if |
|
// the legend should be rendered as horizontal rows with that number of |
|
// items in each row, or false if the legend should be rendered |
|
// vertically (same as specifying 1). The default is true (legend |
|
// rendered horizontally). |
|
legendHorizontal: true, |
|
|
|
// theme: String|Theme |
|
// a theme to use for the chart, or the name of a theme. |
|
|
|
// chartNode: String|DomNode |
|
// an optional DOM node or the id of a DOM node to receive a |
|
// chart presentation of the data. Supply only when a chart is |
|
// required and the type is not 'chart'; when the type is |
|
// 'chart' this property will be set to the widget attach point. |
|
|
|
// legendNode: String|DomNode |
|
// an optional DOM node or the id of a DOM node to receive a |
|
// chart legend for the data. Supply only when a legend is |
|
// required and the type is not 'legend'; when the type is |
|
// 'legend' this property will be set to the widget attach point. |
|
|
|
// gridNode: String|DomNode |
|
// an optional DOM node or the id of a DOM node to receive a |
|
// grid presentation of the data. Supply only when a grid is |
|
// required and the type is not 'grid'; when the type is |
|
// 'grid' this property will be set to the widget attach point. |
|
|
|
// titleNode: String|DomNode |
|
// an optional DOM node or the id of a DOM node to receive a |
|
// title for the data. Supply only when a title is |
|
// required and the type is not 'title'; when the type is |
|
// 'title' this property will be set to the widget attach point. |
|
|
|
// footerNode: String|DomNode |
|
// an optional DOM node or the id of a DOM node to receive a |
|
// footer presentation of the data. Supply only when a footer is |
|
// required and the type is not 'footer'; when the type is |
|
// 'footer' this property will be set to the widget attach point. |
|
|
|
// chartWidget: Object |
|
// the chart widget, if any |
|
|
|
// legendWidget: Object |
|
// the legend widget, if any |
|
|
|
// gridWidget: Object |
|
// the grid widget, if any |
|
|
|
constructor: function(node, args){ |
|
// summary: |
|
// Set up properties and initialize. |
|
// node: DomNode |
|
// The node to attach the data presentation to. |
|
// args: Object |
|
// (see above) |
|
|
|
// apply arguments directly |
|
dojo.mixin(this, args); |
|
|
|
// store our DOM attach point |
|
this.domNode = dojo.byId(node); |
|
|
|
// also apply the DOM attach point as the node for the presentation type |
|
this[this.type + "Node"] = this.domNode; |
|
|
|
// load the theme if provided by name |
|
if(typeof this.theme == 'string'){ |
|
this.theme = dojo.getObject(this.theme); |
|
} |
|
|
|
// resolve any the nodes that were supplied as ids |
|
this.chartNode = dojo.byId(this.chartNode); |
|
this.legendNode = dojo.byId(this.legendNode); |
|
this.gridNode = dojo.byId(this.gridNode); |
|
this.titleNode = dojo.byId(this.titleNode); |
|
this.footerNode = dojo.byId(this.footerNode); |
|
|
|
// we used to support a 'legendVertical' so for now |
|
// at least maintain backward compatibility |
|
if(this.legendVertical){ |
|
this.legendHorizontal = !this.legendVertical; |
|
} |
|
|
|
if(this.url){ |
|
this.setURL(null, null, this.refreshInterval); |
|
} |
|
else{ |
|
if(this.data){ |
|
this.setData(null, this.refreshInterval); |
|
} |
|
else{ |
|
this.setStore(); |
|
} |
|
} |
|
}, |
|
|
|
setURL: function(/*String?*/url, /*Object?*/ urlContent, /*Number?*/refreshInterval){ |
|
// summary: |
|
// Sets the URL to fetch data from, with optional content |
|
// supplied with the request, and an optional |
|
// refresh interval in milliseconds (0=no refresh) |
|
|
|
// if a refresh interval is supplied we will start a fresh |
|
// refresh after storing the supplied url |
|
if(refreshInterval){ |
|
this.cancelRefresh(); |
|
} |
|
|
|
this.url = url || this.url; |
|
this.urlContent = urlContent || this.urlContent; |
|
this.refreshInterval = refreshInterval || this.refreshInterval; |
|
|
|
var me = this; |
|
|
|
dojo.xhrGet({ |
|
url: this.url, |
|
content: this.urlContent, |
|
handleAs: 'json-comment-optional', |
|
load: function(response, ioArgs){ |
|
me.setData(response); |
|
}, |
|
error: function(xhr, ioArgs){ |
|
if(me.urlError && (typeof me.urlError == "function")){ |
|
me.urlError(xhr, ioArgs); |
|
} |
|
} |
|
}); |
|
|
|
if(refreshInterval && (this.refreshInterval > 0)){ |
|
this.refreshIntervalPending = setInterval(function(){ |
|
me.setURL(); |
|
}, this.refreshInterval); |
|
} |
|
}, |
|
|
|
setData: function(/*Object?*/data, /*Number?*/refreshInterval){ |
|
// summary: |
|
// Sets the data to be presented, and an optional |
|
// refresh interval in milliseconds (0=no refresh) |
|
|
|
// if a refresh interval is supplied we will start a fresh |
|
// refresh after storing the supplied data reference |
|
if(refreshInterval){ |
|
this.cancelRefresh(); |
|
} |
|
|
|
this.data = data || this.data; |
|
this.refreshInterval = refreshInterval || this.refreshInterval; |
|
|
|
// TODO if no 'series' property was provided, build one intelligently here |
|
// (until that is done, a 'series' property must be supplied) |
|
|
|
var _series = (typeof this.series == 'function') ? this.series(this.data) : this.series; |
|
|
|
var datasets = [], |
|
series_data = [], |
|
series_name = [], |
|
series_chart = [], |
|
series_charttype = [], |
|
series_linestyle = [], |
|
series_axis = [], |
|
series_grid = [], |
|
series_gridformatter = [], |
|
maxlen = 0; |
|
|
|
// identify the dataset arrays in which series values can be found |
|
for(var ser = 0; ser < _series.length; ser++){ |
|
datasets[ser] = getSubfield(this.data, _series[ser].datapoints); |
|
if(datasets[ser] && (datasets[ser].length > maxlen)){ |
|
maxlen = datasets[ser].length; |
|
} |
|
|
|
series_data[ser] = []; |
|
// name can be specified in series structure, or by field in series structure, otherwise use a default |
|
series_name[ser] = _series[ser].name || (_series[ser].namefield ? getSubfield(this.data, _series[ser].namefield) : null) || ("series " + ser); |
|
series_chart[ser] = (_series[ser].chart !== false); |
|
series_charttype[ser] = _series[ser].charttype || "bar"; |
|
series_linestyle[ser] = _series[ser].linestyle; |
|
series_axis[ser] = _series[ser].axis || "primary"; |
|
series_grid[ser] = (_series[ser].grid !== false); |
|
series_gridformatter[ser] = _series[ser].gridformatter; |
|
} |
|
|
|
// create an array of data points by sampling the series |
|
// and an array of series arrays by collecting the series |
|
// each data point has an 'index' item containing a sequence number |
|
// and items named "data.0", "data.1", ... containing the series samples |
|
// and the first data point also has items named "name.0", "name.1", ... containing the series names |
|
// and items named "series.0", "series.1", ... containing arrays with the complete series in |
|
var point, datapoint, datavalue, fdatavalue; |
|
var datapoints = []; |
|
|
|
for(point = 0; point < maxlen; point++){ |
|
datapoint = { index: point }; |
|
for(ser = 0; ser < _series.length; ser++){ |
|
if(datasets[ser] && (datasets[ser].length > point)){ |
|
datavalue = getSubfield(datasets[ser][point], _series[ser].field); |
|
|
|
if(series_chart[ser]){ |
|
// convert the data value to a float if possible |
|
fdatavalue = parseFloat(datavalue); |
|
if(!isNaN(fdatavalue)){ |
|
datavalue = fdatavalue; |
|
} |
|
} |
|
|
|
datapoint["data." + ser] = datavalue; |
|
series_data[ser].push(datavalue); |
|
} |
|
} |
|
datapoints.push(datapoint); |
|
} |
|
|
|
if(maxlen <= 0){ |
|
datapoints.push({index: 0}); |
|
} |
|
|
|
// now build a prepared store from the data points we've constructed |
|
var store = new dojo.data.ItemFileWriteStore({ data: { identifier: 'index', items: datapoints }}); |
|
if(this.data.title){ |
|
store.title = this.data.title; |
|
} |
|
if(this.data.footer){ |
|
store.footer = this.data.footer; |
|
} |
|
|
|
store.series_data = series_data; |
|
store.series_name = series_name; |
|
store.series_chart = series_chart; |
|
store.series_charttype = series_charttype; |
|
store.series_linestyle = series_linestyle; |
|
store.series_axis = series_axis; |
|
store.series_grid = series_grid; |
|
store.series_gridformatter = series_gridformatter; |
|
|
|
this.setPreparedStore(store); |
|
|
|
if(refreshInterval && (this.refreshInterval > 0)){ |
|
var me = this; |
|
this.refreshIntervalPending = setInterval(function(){ |
|
me.setData(); |
|
}, this.refreshInterval); |
|
} |
|
}, |
|
|
|
refresh: function(){ |
|
// summary: |
|
// If a URL or data has been supplied, refreshes the |
|
// presented data from the URL or data. If a refresh |
|
// interval is also set, the periodic refresh is |
|
// restarted. If a URL or data was not supplied, this |
|
// method has no effect. |
|
if(this.url){ |
|
this.setURL(this.url, this.urlContent, this.refreshInterval); |
|
}else if(this.data){ |
|
this.setData(this.data, this.refreshInterval); |
|
} |
|
}, |
|
|
|
cancelRefresh: function(){ |
|
// summary: |
|
// Cancels any and all outstanding data refreshes |
|
if(this.refreshIntervalPending){ |
|
// cancel existing refresh |
|
clearInterval(this.refreshIntervalPending); |
|
this.refreshIntervalPending = undefined; |
|
} |
|
}, |
|
|
|
setStore: function(/*Object?*/store, /*String?*/query, /*Object?*/queryOptions){ |
|
// FIXME build a prepared store properly -- this requires too tight a convention to be followed to be useful |
|
this.setPreparedStore(store, query, queryOptions); |
|
}, |
|
|
|
setPreparedStore: function(/*Object?*/store, /*String?*/query, /*Object?*/queryOptions){ |
|
// summary: |
|
// Sets the store and query. |
|
|
|
this.preparedstore = store || this.store; |
|
this.query = query || this.query; |
|
this.queryOptions = queryOptions || this.queryOptions; |
|
|
|
if(this.preparedstore){ |
|
if(this.chartNode){ |
|
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); |
|
this.renderChartWidget(); |
|
} |
|
if(this.legendNode){ |
|
this.legendWidget = setupLegend(this.legendNode, this.legendWidget, this.chartWidget, this.legendHorizontal); |
|
} |
|
if(this.gridNode){ |
|
this.gridWidget = setupGrid(this.gridNode, this.gridWidget, this.preparedstore, this.query, this.queryOptions); |
|
this.renderGridWidget(); |
|
} |
|
if(this.titleNode){ |
|
setupTitle(this.titleNode, this.preparedstore); |
|
} |
|
if(this.footerNode){ |
|
setupFooter(this.footerNode, this.preparedstore); |
|
} |
|
} |
|
}, |
|
|
|
renderChartWidget: function(){ |
|
// summary: |
|
// Renders the chart widget (if any). This method is |
|
// called whenever a chart widget is created or |
|
// configured, and may be connected to. |
|
if(this.chartWidget){ |
|
this.chartWidget.render(); |
|
} |
|
}, |
|
|
|
renderGridWidget: function(){ |
|
// summary: |
|
// Renders the grid widget (if any). This method is |
|
// called whenever a grid widget is created or |
|
// configured, and may be connected to. |
|
if(this.gridWidget){ |
|
this.gridWidget.render(); |
|
} |
|
}, |
|
|
|
getChartWidget: function(){ |
|
// summary: |
|
// Returns the chart widget (if any) created if the type |
|
// is "chart" or the "chartNode" property was supplied. |
|
return this.chartWidget; |
|
}, |
|
|
|
getGridWidget: function(){ |
|
// summary: |
|
// Returns the grid widget (if any) created if the type |
|
// is "grid" or the "gridNode" property was supplied. |
|
return this.gridWidget; |
|
}, |
|
|
|
destroy: function(){ |
|
// summary: |
|
// Destroys the widget and all components and resources. |
|
|
|
// cancel any outstanding refresh requests |
|
this.cancelRefresh(); |
|
|
|
if(this.chartWidget){ |
|
this.chartWidget.destroy(); |
|
delete this.chartWidget; |
|
} |
|
|
|
if(this.legendWidget){ |
|
// no legend.destroy() |
|
delete this.legendWidget; |
|
} |
|
|
|
if(this.gridWidget){ |
|
// no grid.destroy() |
|
delete this.gridWidget; |
|
} |
|
|
|
if(this.chartNode){ |
|
this.chartNode.innerHTML = ""; |
|
} |
|
|
|
if(this.legendNode){ |
|
this.legendNode.innerHTML = ""; |
|
} |
|
|
|
if(this.gridNode){ |
|
this.gridNode.innerHTML = ""; |
|
} |
|
|
|
if(this.titleNode){ |
|
this.titleNode.innerHTML = ""; |
|
} |
|
|
|
if(this.footerNode){ |
|
this.footerNode.innerHTML = ""; |
|
} |
|
} |
|
|
|
}); |
|
|
|
})();
|
|
|