903 lines
30 KiB
JavaScript
903 lines
30 KiB
JavaScript
//>>built
|
|
// wrapped by build app
|
|
define("dojox/widget/DataPresentation", ["dijit","dojo","dojox","dojo/require!dojox/grid/DataGrid,dojox/charting/Chart2D,dojox/charting/widget/Legend,dojox/charting/action2d/Tooltip,dojox/charting/action2d/Highlight,dojo/colors,dojo/data/ItemFileWriteStore"], function(dijit,dojo,dojox){
|
|
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:
|
|
//
|
|
// DataPresentation
|
|
//
|
|
// 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"
|
|
// });
|
|
//
|
|
// properties:
|
|
//
|
|
// 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 independant 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.
|
|
//
|
|
// arguments:
|
|
// node: DomNode
|
|
// The node to attach the data presentation to.
|
|
// kwArgs: 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 = "";
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
});
|