//>>built define("dojox/charting/Chart", ["dojo/_base/lang", "dojo/_base/array","dojo/_base/declare", "dojo/_base/html", "dojo/dom", "dojo/dom-geometry", "dojo/dom-construct","dojo/_base/Color", "dojo/_base/sniff", "./Element", "./Theme", "./Series", "./axis2d/common", "dojox/gfx", "dojox/lang/functional", "dojox/lang/functional/fold", "dojox/lang/functional/reversed"], function(lang, arr, declare, html, dom, domGeom, domConstruct, Color, has, Element, Theme, Series, common, g, func, funcFold, funcReversed){ /*===== dojox.charting.__ChartCtorArgs = function(margins, stroke, fill, delayInMs){ // summary: // The keyword arguments that can be passed in a Chart constructor. // // margins: Object? // Optional margins for the chart, in the form of { l, t, r, b}. // stroke: dojox.gfx.Stroke? // An optional outline/stroke for the chart. // fill: dojox.gfx.Fill? // An optional fill for the chart. // delayInMs: Number // Delay in ms for delayedRender(). Default: 200. this.margins = margins; this.stroke = stroke; this.fill = fill; this.delayInMs = delayInMs; } =====*/ var dc = dojox.charting, clear = func.lambda("item.clear()"), purge = func.lambda("item.purgeGroup()"), destroy = func.lambda("item.destroy()"), makeClean = func.lambda("item.dirty = false"), makeDirty = func.lambda("item.dirty = true"), getName = func.lambda("item.name"); declare("dojox.charting.Chart", null, { // summary: // The main chart object in dojox.charting. This will create a two dimensional // chart based on dojox.gfx. // // description: // dojox.charting.Chart is the primary object used for any kind of charts. It // is simple to create--just pass it a node reference, which is used as the // container for the chart--and a set of optional keyword arguments and go. // // Note that like most of dojox.gfx, most of dojox.charting.Chart's methods are // designed to return a reference to the chart itself, to allow for functional // chaining. This makes defining everything on a Chart very easy to do. // // example: // Create an area chart, with smoothing. // | new dojox.charting.Chart(node)) // | .addPlot("default", { type: "Areas", tension: "X" }) // | .setTheme(dojox.charting.themes.Shrooms) // | .addSeries("Series A", [1, 2, 0.5, 1.5, 1, 2.8, 0.4]) // | .addSeries("Series B", [2.6, 1.8, 2, 1, 1.4, 0.7, 2]) // | .addSeries("Series C", [6.3, 1.8, 3, 0.5, 4.4, 2.7, 2]) // | .render(); // // example: // The form of data in a data series can take a number of forms: a simple array, // an array of objects {x,y}, or something custom (as determined by the plot). // Here's an example of a Candlestick chart, which expects an object of // { open, high, low, close }. // | new dojox.charting.Chart(node)) // | .addPlot("default", {type: "Candlesticks", gap: 1}) // | .addAxis("x", {fixLower: "major", fixUpper: "major", includeZero: true}) // | .addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", natural: true}) // | .addSeries("Series A", [ // | { open: 20, close: 16, high: 22, low: 8 }, // | { open: 16, close: 22, high: 26, low: 6, mid: 18 }, // | { open: 22, close: 18, high: 22, low: 11, mid: 21 }, // | { open: 18, close: 29, high: 32, low: 14, mid: 27 }, // | { open: 29, close: 24, high: 29, low: 13, mid: 27 }, // | { open: 24, close: 8, high: 24, low: 5 }, // | { open: 8, close: 16, high: 22, low: 2 }, // | { open: 16, close: 12, high: 19, low: 7 }, // | { open: 12, close: 20, high: 22, low: 8 }, // | { open: 20, close: 16, high: 22, low: 8 }, // | { open: 16, close: 22, high: 26, low: 6, mid: 18 }, // | { open: 22, close: 18, high: 22, low: 11, mid: 21 }, // | { open: 18, close: 29, high: 32, low: 14, mid: 27 }, // | { open: 29, close: 24, high: 29, low: 13, mid: 27 }, // | { open: 24, close: 8, high: 24, low: 5 }, // | { open: 8, close: 16, high: 22, low: 2 }, // | { open: 16, close: 12, high: 19, low: 7 }, // | { open: 12, close: 20, high: 22, low: 8 }, // | { open: 20, close: 16, high: 22, low: 8 }, // | { open: 16, close: 22, high: 26, low: 6 }, // | { open: 22, close: 18, high: 22, low: 11 }, // | { open: 18, close: 29, high: 32, low: 14 }, // | { open: 29, close: 24, high: 29, low: 13 }, // | { open: 24, close: 8, high: 24, low: 5 }, // | { open: 8, close: 16, high: 22, low: 2 }, // | { open: 16, close: 12, high: 19, low: 7 }, // | { open: 12, close: 20, high: 22, low: 8 }, // | { open: 20, close: 16, high: 22, low: 8 } // | ], // | { stroke: { color: "green" }, fill: "lightgreen" } // | ) // | .render(); // theme: dojox.charting.Theme? // An optional theme to use for styling the chart. // axes: dojox.charting.Axis{}? // A map of axes for use in plotting a chart. // stack: dojox.charting.plot2d.Base[] // A stack of plotters. // plots: dojox.charting.plot2d.Base{} // A map of plotter indices // series: dojox.charting.Series[] // The stack of data runs used to create plots. // runs: dojox.charting.Series{} // A map of series indices // margins: Object? // The margins around the chart. Default is { l:10, t:10, r:10, b:10 }. // stroke: dojox.gfx.Stroke? // The outline of the chart (stroke in vector graphics terms). // fill: dojox.gfx.Fill? // The color for the chart. // node: DOMNode // The container node passed to the constructor. // surface: dojox.gfx.Surface // The main graphics surface upon which a chart is drawn. // dirty: Boolean // A boolean flag indicating whether or not the chart needs to be updated/re-rendered. // coords: Object // The coordinates on a page of the containing node, as returned from dojo.coords. constructor: function(/* DOMNode */node, /* dojox.charting.__ChartCtorArgs? */kwArgs){ // summary: // The constructor for a new Chart. Initializes all parameters used for a chart. // returns: dojox.charting.Chart // The newly created chart. // initialize parameters if(!kwArgs){ kwArgs = {}; } this.margins = kwArgs.margins ? kwArgs.margins : {l: 10, t: 10, r: 10, b: 10}; this.stroke = kwArgs.stroke; this.fill = kwArgs.fill; this.delayInMs = kwArgs.delayInMs || 200; this.title = kwArgs.title; this.titleGap = kwArgs.titleGap; this.titlePos = kwArgs.titlePos; this.titleFont = kwArgs.titleFont; this.titleFontColor = kwArgs.titleFontColor; this.chartTitle = null; // default initialization this.theme = null; this.axes = {}; // map of axes this.stack = []; // stack of plotters this.plots = {}; // map of plotter indices this.series = []; // stack of data runs this.runs = {}; // map of data run indices this.dirty = true; this.coords = null; // create a surface this.node = dom.byId(node); var box = domGeom.getMarginBox(node); this.surface = g.createSurface(this.node, box.w || 400, box.h || 300); }, destroy: function(){ // summary: // Cleanup when a chart is to be destroyed. // returns: void arr.forEach(this.series, destroy); arr.forEach(this.stack, destroy); func.forIn(this.axes, destroy); if(this.chartTitle && this.chartTitle.tagName){ // destroy title if it is a DOM node domConstruct.destroy(this.chartTitle); } this.surface.destroy(); }, getCoords: function(){ // summary: // Get the coordinates and dimensions of the containing DOMNode, as // returned by dojo.coords. // returns: Object // The resulting coordinates of the chart. See dojo.coords for details. return html.coords(this.node, true); // Object }, setTheme: function(theme){ // summary: // Set a theme of the chart. // theme: dojox.charting.Theme // The theme to be used for visual rendering. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. this.theme = theme.clone(); this.dirty = true; return this; // dojox.charting.Chart }, addAxis: function(name, kwArgs){ // summary: // Add an axis to the chart, for rendering. // name: String // The name of the axis. // kwArgs: dojox.charting.axis2d.__AxisCtorArgs? // An optional keyword arguments object for use in defining details of an axis. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. var axis, axisType = kwArgs && kwArgs.type || "Default"; if(typeof axisType == "string"){ if(!dc.axis2d || !dc.axis2d[axisType]){ throw Error("Can't find axis: " + axisType + " - Check " + "require() dependencies."); } axis = new dc.axis2d[axisType](this, kwArgs); }else{ axis = new axisType(this, kwArgs); } axis.name = name; axis.dirty = true; if(name in this.axes){ this.axes[name].destroy(); } this.axes[name] = axis; this.dirty = true; return this; // dojox.charting.Chart }, getAxis: function(name){ // summary: // Get the given axis, by name. // name: String // The name the axis was defined by. // returns: dojox.charting.axis2d.Default // The axis as stored in the chart's axis map. return this.axes[name]; // dojox.charting.axis2d.Default }, removeAxis: function(name){ // summary: // Remove the axis that was defined using name. // name: String // The axis name, as defined in addAxis. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.axes){ // destroy the axis this.axes[name].destroy(); delete this.axes[name]; // mark the chart as dirty this.dirty = true; } return this; // dojox.charting.Chart }, addPlot: function(name, kwArgs){ // summary: // Add a new plot to the chart, defined by name and using the optional keyword arguments object. // Note that dojox.charting assumes the main plot to be called "default"; if you do not have // a plot called "default" and attempt to add data series to the chart without specifying the // plot to be rendered on, you WILL get errors. // name: String // The name of the plot to be added to the chart. If you only plan on using one plot, call it "default". // kwArgs: dojox.charting.plot2d.__PlotCtorArgs // An object with optional parameters for the plot in question. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. var plot, plotType = kwArgs && kwArgs.type || "Default"; if(typeof plotType == "string"){ if(!dc.plot2d || !dc.plot2d[plotType]){ throw Error("Can't find plot: " + plotType + " - didn't you forget to dojo" + ".require() it?"); } plot = new dc.plot2d[plotType](this, kwArgs); }else{ plot = new plotType(this, kwArgs); } plot.name = name; plot.dirty = true; if(name in this.plots){ this.stack[this.plots[name]].destroy(); this.stack[this.plots[name]] = plot; }else{ this.plots[name] = this.stack.length; this.stack.push(plot); } this.dirty = true; return this; // dojox.charting.Chart }, getPlot: function(name){ // summary: // Get the given plot, by name. // name: String // The name the plot was defined by. // returns: dojox.charting.plot2d.Base // The plot. return this.stack[this.plots[name]]; }, removePlot: function(name){ // summary: // Remove the plot defined using name from the chart's plot stack. // name: String // The name of the plot as defined using addPlot. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.plots){ // get the index and remove the name var index = this.plots[name]; delete this.plots[name]; // destroy the plot this.stack[index].destroy(); // remove the plot from the stack this.stack.splice(index, 1); // update indices to reflect the shift func.forIn(this.plots, function(idx, name, plots){ if(idx > index){ plots[name] = idx - 1; } }); // remove all related series var ns = arr.filter(this.series, function(run){ return run.plot != name; }); if(ns.length < this.series.length){ // kill all removed series arr.forEach(this.series, function(run){ if(run.plot == name){ run.destroy(); } }); // rebuild all necessary data structures this.runs = {}; arr.forEach(ns, function(run, index){ this.runs[run.plot] = index; }, this); this.series = ns; } // mark the chart as dirty this.dirty = true; } return this; // dojox.charting.Chart }, getPlotOrder: function(){ // summary: // Returns an array of plot names in the current order // (the top-most plot is the first). // returns: Array return func.map(this.stack, getName); // Array }, setPlotOrder: function(newOrder){ // summary: // Sets new order of plots. newOrder cannot add or remove // plots. Wrong names, or dups are ignored. // newOrder: Array: // Array of plot names compatible with getPlotOrder(). // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. var names = {}, order = func.filter(newOrder, function(name){ if(!(name in this.plots) || (name in names)){ return false; } names[name] = 1; return true; }, this); if(order.length < this.stack.length){ func.forEach(this.stack, function(plot){ var name = plot.name; if(!(name in names)){ order.push(name); } }); } var newStack = func.map(order, function(name){ return this.stack[this.plots[name]]; }, this); func.forEach(newStack, function(plot, i){ this.plots[plot.name] = i; }, this); this.stack = newStack; this.dirty = true; return this; // dojox.charting.Chart }, movePlotToFront: function(name){ // summary: // Moves a given plot to front. // name: String: // Plot's name to move. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.plots){ var index = this.plots[name]; if(index){ var newOrder = this.getPlotOrder(); newOrder.splice(index, 1); newOrder.unshift(name); return this.setPlotOrder(newOrder); // dojox.charting.Chart } } return this; // dojox.charting.Chart }, movePlotToBack: function(name){ // summary: // Moves a given plot to back. // name: String: // Plot's name to move. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.plots){ var index = this.plots[name]; if(index < this.stack.length - 1){ var newOrder = this.getPlotOrder(); newOrder.splice(index, 1); newOrder.push(name); return this.setPlotOrder(newOrder); // dojox.charting.Chart } } return this; // dojox.charting.Chart }, addSeries: function(name, data, kwArgs){ // summary: // Add a data series to the chart for rendering. // name: String: // The name of the data series to be plotted. // data: Array|Object: // The array of data points (either numbers or objects) that // represents the data to be drawn. Or it can be an object. In // the latter case, it should have a property "data" (an array), // destroy(), and setSeriesObject(). // kwArgs: dojox.charting.__SeriesCtorArgs?: // An optional keyword arguments object that will be mixed into // the resultant series object. // returns: dojox.charting.Chart: // A reference to the current chart for functional chaining. var run = new Series(this, data, kwArgs); run.name = name; if(name in this.runs){ this.series[this.runs[name]].destroy(); this.series[this.runs[name]] = run; }else{ this.runs[name] = this.series.length; this.series.push(run); } this.dirty = true; // fix min/max if(!("ymin" in run) && "min" in run){ run.ymin = run.min; } if(!("ymax" in run) && "max" in run){ run.ymax = run.max; } return this; // dojox.charting.Chart }, getSeries: function(name){ // summary: // Get the given series, by name. // name: String // The name the series was defined by. // returns: dojox.charting.Series // The series. return this.series[this.runs[name]]; }, removeSeries: function(name){ // summary: // Remove the series defined by name from the chart. // name: String // The name of the series as defined by addSeries. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.runs){ // get the index and remove the name var index = this.runs[name]; delete this.runs[name]; // destroy the run this.series[index].destroy(); // remove the run from the stack of series this.series.splice(index, 1); // update indices to reflect the shift func.forIn(this.runs, function(idx, name, runs){ if(idx > index){ runs[name] = idx - 1; } }); this.dirty = true; } return this; // dojox.charting.Chart }, updateSeries: function(name, data){ // summary: // Update the given series with a new set of data points. // name: String // The name of the series as defined in addSeries. // data: Array|Object: // The array of data points (either numbers or objects) that // represents the data to be drawn. Or it can be an object. In // the latter case, it should have a property "data" (an array), // destroy(), and setSeriesObject(). // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.runs){ var run = this.series[this.runs[name]]; run.update(data); this._invalidateDependentPlots(run.plot, false); this._invalidateDependentPlots(run.plot, true); } return this; // dojox.charting.Chart }, getSeriesOrder: function(plotName){ // summary: // Returns an array of series names in the current order // (the top-most series is the first) within a plot. // plotName: String: // Plot's name. // returns: Array return func.map(func.filter(this.series, function(run){ return run.plot == plotName; }), getName); }, setSeriesOrder: function(newOrder){ // summary: // Sets new order of series within a plot. newOrder cannot add // or remove series. Wrong names, or dups are ignored. // newOrder: Array: // Array of series names compatible with getPlotOrder(). All // series should belong to the same plot. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. var plotName, names = {}, order = func.filter(newOrder, function(name){ if(!(name in this.runs) || (name in names)){ return false; } var run = this.series[this.runs[name]]; if(plotName){ if(run.plot != plotName){ return false; } }else{ plotName = run.plot; } names[name] = 1; return true; }, this); func.forEach(this.series, function(run){ var name = run.name; if(!(name in names) && run.plot == plotName){ order.push(name); } }); var newSeries = func.map(order, function(name){ return this.series[this.runs[name]]; }, this); this.series = newSeries.concat(func.filter(this.series, function(run){ return run.plot != plotName; })); func.forEach(this.series, function(run, i){ this.runs[run.name] = i; }, this); this.dirty = true; return this; // dojox.charting.Chart }, moveSeriesToFront: function(name){ // summary: // Moves a given series to front of a plot. // name: String: // Series' name to move. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.runs){ var index = this.runs[name], newOrder = this.getSeriesOrder(this.series[index].plot); if(name != newOrder[0]){ newOrder.splice(index, 1); newOrder.unshift(name); return this.setSeriesOrder(newOrder); // dojox.charting.Chart } } return this; // dojox.charting.Chart }, moveSeriesToBack: function(name){ // summary: // Moves a given series to back of a plot. // name: String: // Series' name to move. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(name in this.runs){ var index = this.runs[name], newOrder = this.getSeriesOrder(this.series[index].plot); if(name != newOrder[newOrder.length - 1]){ newOrder.splice(index, 1); newOrder.push(name); return this.setSeriesOrder(newOrder); // dojox.charting.Chart } } return this; // dojox.charting.Chart }, resize: function(width, height){ // summary: // Resize the chart to the dimensions of width and height. // description: // Resize the chart and its surface to the width and height dimensions. // If no width/height or box is provided, resize the surface to the marginBox of the chart. // width: Number // The new width of the chart. // height: Number // The new height of the chart. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. var box; switch(arguments.length){ // case 0, do not resize the div, just the surface case 1: // argument, override node box box = lang.mixin({}, width); domGeom.setMarginBox(this.node, box); break; case 2: box = {w: width, h: height}; // argument, override node box domGeom.setMarginBox(this.node, box); break; } // in all cases take back the computed box box = domGeom.getMarginBox(this.node); var d = this.surface.getDimensions(); if(d.width != box.w || d.height != box.h){ // and set it on the surface this.surface.setDimensions(box.w, box.h); this.dirty = true; return this.render(); // dojox.charting.Chart }else{ return this; } }, getGeometry: function(){ // summary: // Returns a map of information about all axes in a chart and what they represent // in terms of scaling (see dojox.charting.axis2d.Default.getScaler). // returns: Object // An map of geometry objects, a one-to-one mapping of axes. var ret = {}; func.forIn(this.axes, function(axis){ if(axis.initialized()){ ret[axis.name] = { name: axis.name, vertical: axis.vertical, scaler: axis.scaler, ticks: axis.ticks }; } }); return ret; // Object }, setAxisWindow: function(name, scale, offset, zoom){ // summary: // Zooms an axis and all dependent plots. Can be used to zoom in 1D. // name: String // The name of the axis as defined by addAxis. // scale: Number // The scale on the target axis. // offset: Number // Any offest, as measured by axis tick // zoom: Boolean|Object? // The chart zooming animation trigger. This is null by default, // e.g. {duration: 1200}, or just set true. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. var axis = this.axes[name]; if(axis){ axis.setWindow(scale, offset); arr.forEach(this.stack,function(plot){ if(plot.hAxis == name || plot.vAxis == name){ plot.zoom = zoom; } }); } return this; // dojox.charting.Chart }, setWindow: function(sx, sy, dx, dy, zoom){ // summary: // Zooms in or out any plots in two dimensions. // sx: Number // The scale for the x axis. // sy: Number // The scale for the y axis. // dx: Number // The pixel offset on the x axis. // dy: Number // The pixel offset on the y axis. // zoom: Boolean|Object? // The chart zooming animation trigger. This is null by default, // e.g. {duration: 1200}, or just set true. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(!("plotArea" in this)){ this.calculateGeometry(); } func.forIn(this.axes, function(axis){ var scale, offset, bounds = axis.getScaler().bounds, s = bounds.span / (bounds.upper - bounds.lower); if(axis.vertical){ scale = sy; offset = dy / s / scale; }else{ scale = sx; offset = dx / s / scale; } axis.setWindow(scale, offset); }); arr.forEach(this.stack, function(plot){ plot.zoom = zoom; }); return this; // dojox.charting.Chart }, zoomIn: function(name, range){ // summary: // Zoom the chart to a specific range on one axis. This calls render() // directly as a convenience method. // name: String // The name of the axis as defined by addAxis. // range: Array // The end points of the zoom range, measured in axis ticks. var axis = this.axes[name]; if(axis){ var scale, offset, bounds = axis.getScaler().bounds; var lower = Math.min(range[0],range[1]); var upper = Math.max(range[0],range[1]); lower = range[0] < bounds.lower ? bounds.lower : lower; upper = range[1] > bounds.upper ? bounds.upper : upper; scale = (bounds.upper - bounds.lower) / (upper - lower); offset = lower - bounds.lower; this.setAxisWindow(name, scale, offset); this.render(); } }, calculateGeometry: function(){ // summary: // Calculate the geometry of the chart based on the defined axes of // a chart. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(this.dirty){ return this.fullGeometry(); } // calculate geometry var dirty = arr.filter(this.stack, function(plot){ return plot.dirty || (plot.hAxis && this.axes[plot.hAxis].dirty) || (plot.vAxis && this.axes[plot.vAxis].dirty); }, this); calculateAxes(dirty, this.plotArea); return this; // dojox.charting.Chart }, fullGeometry: function(){ // summary: // Calculate the full geometry of the chart. This includes passing // over all major elements of a chart (plots, axes, series, container) // in order to ensure proper rendering. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. this._makeDirty(); // clear old values arr.forEach(this.stack, clear); // rebuild new connections, and add defaults // set up a theme if(!this.theme){ this.setTheme(new Theme(dojox.charting._def)); } // assign series arr.forEach(this.series, function(run){ if(!(run.plot in this.plots)){ if(!dc.plot2d || !dc.plot2d.Default){ throw Error("Can't find plot: Default - didn't you forget to dojo" + ".require() it?"); } var plot = new dc.plot2d.Default(this, {}); plot.name = run.plot; this.plots[run.plot] = this.stack.length; this.stack.push(plot); } this.stack[this.plots[run.plot]].addSeries(run); }, this); // assign axes arr.forEach(this.stack, function(plot){ if(plot.hAxis){ plot.setAxis(this.axes[plot.hAxis]); } if(plot.vAxis){ plot.setAxis(this.axes[plot.vAxis]); } }, this); // calculate geometry // 1st pass var dim = this.dim = this.surface.getDimensions(); dim.width = g.normalizedLength(dim.width); dim.height = g.normalizedLength(dim.height); func.forIn(this.axes, clear); calculateAxes(this.stack, dim); // assumption: we don't have stacked axes yet var offsets = this.offsets = { l: 0, r: 0, t: 0, b: 0 }; func.forIn(this.axes, function(axis){ func.forIn(axis.getOffsets(), function(o, i){ offsets[i] += o; }); }); // add title area if(this.title){ this.titleGap = (this.titleGap==0) ? 0 : this.titleGap || this.theme.chart.titleGap || 20; this.titlePos = this.titlePos || this.theme.chart.titlePos || "top"; this.titleFont = this.titleFont || this.theme.chart.titleFont; this.titleFontColor = this.titleFontColor || this.theme.chart.titleFontColor || "black"; var tsize = g.normalizedLength(g.splitFontString(this.titleFont).size); offsets[this.titlePos=="top" ? "t":"b"] += (tsize + this.titleGap); } // add margins func.forIn(this.margins, function(o, i){ offsets[i] += o; }); // 2nd pass with realistic dimensions this.plotArea = { width: dim.width - offsets.l - offsets.r, height: dim.height - offsets.t - offsets.b }; func.forIn(this.axes, clear); calculateAxes(this.stack, this.plotArea); return this; // dojox.charting.Chart }, render: function(){ // summary: // Render the chart according to the current information defined. This should // be the last call made when defining/creating a chart, or if data within the // chart has been changed. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(this.theme){ this.theme.clear(); } if(this.dirty){ return this.fullRender(); } this.calculateGeometry(); // go over the stack backwards func.forEachRev(this.stack, function(plot){ plot.render(this.dim, this.offsets); }, this); // go over axes func.forIn(this.axes, function(axis){ axis.render(this.dim, this.offsets); }, this); this._makeClean(); // BEGIN FOR HTML CANVAS if(this.surface.render){ this.surface.render(); }; // END FOR HTML CANVAS return this; // dojox.charting.Chart }, fullRender: function(){ // summary: // Force a full rendering of the chart, including full resets on the chart itself. // You should not call this method directly unless absolutely necessary. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. // calculate geometry this.fullGeometry(); var offsets = this.offsets, dim = this.dim, rect; // get required colors //var requiredColors = func.foldl(this.stack, "z + plot.getRequiredColors()", 0); //this.theme.defineColors({num: requiredColors, cache: false}); // clear old shapes arr.forEach(this.series, purge); func.forIn(this.axes, purge); arr.forEach(this.stack, purge); if(this.chartTitle && this.chartTitle.tagName){ // destroy title if it is a DOM node domConstruct.destroy(this.chartTitle); } this.surface.clear(); this.chartTitle = null; // generate shapes // draw a plot background var t = this.theme, fill = t.plotarea && t.plotarea.fill, stroke = t.plotarea && t.plotarea.stroke, // size might be neg if offsets are bigger that chart size this happens quite often at // initialization time if the chart widget is used in a BorderContainer // this will fail on IE/VML w = Math.max(0, dim.width - offsets.l - offsets.r), h = Math.max(0, dim.height - offsets.t - offsets.b), rect = { x: offsets.l - 1, y: offsets.t - 1, width: w + 2, height: h + 2 }; if(fill){ fill = Element.prototype._shapeFill(Element.prototype._plotFill(fill, dim, offsets), rect); this.surface.createRect(rect).setFill(fill); } if(stroke){ this.surface.createRect({ x: offsets.l, y: offsets.t, width: w + 1, height: h + 1 }).setStroke(stroke); } // go over the stack backwards func.foldr(this.stack, function(z, plot){ return plot.render(dim, offsets), 0; }, 0); // pseudo-clipping: matting fill = this.fill !== undefined ? this.fill : (t.chart && t.chart.fill); stroke = this.stroke !== undefined ? this.stroke : (t.chart && t.chart.stroke); // TRT: support for "inherit" as a named value in a theme. if(fill == "inherit"){ // find the background color of the nearest ancestor node, and use that explicitly. var node = this.node, fill = new Color(html.style(node, "backgroundColor")); while(fill.a==0 && node!=document.documentElement){ fill = new Color(html.style(node, "backgroundColor")); node = node.parentNode; } } if(fill){ fill = Element.prototype._plotFill(fill, dim, offsets); if(offsets.l){ // left rect = { width: offsets.l, height: dim.height + 1 }; this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect)); } if(offsets.r){ // right rect = { x: dim.width - offsets.r, width: offsets.r + 1, height: dim.height + 2 }; this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect)); } if(offsets.t){ // top rect = { width: dim.width + 1, height: offsets.t }; this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect)); } if(offsets.b){ // bottom rect = { y: dim.height - offsets.b, width: dim.width + 1, height: offsets.b + 2 }; this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect)); } } if(stroke){ this.surface.createRect({ width: dim.width - 1, height: dim.height - 1 }).setStroke(stroke); } //create title: Whether to make chart title as a widget which extends dojox.charting.Element? if(this.title){ var forceHtmlLabels = (g.renderer == "canvas"), labelType = forceHtmlLabels || !has("ie") && !has("opera") ? "html" : "gfx", tsize = g.normalizedLength(g.splitFontString(this.titleFont).size); this.chartTitle = common.createText[labelType]( this, this.surface, dim.width/2, this.titlePos=="top" ? tsize + this.margins.t : dim.height - this.margins.b, "middle", this.title, this.titleFont, this.titleFontColor ); } // go over axes func.forIn(this.axes, function(axis){ axis.render(dim, offsets); }); this._makeClean(); // BEGIN FOR HTML CANVAS if(this.surface.render){ this.surface.render(); }; // END FOR HTML CANVAS return this; // dojox.charting.Chart }, delayedRender: function(){ // summary: // Delayed render, which is used to collect multiple updates // within a delayInMs time window. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(!this._delayedRenderHandle){ this._delayedRenderHandle = setTimeout( lang.hitch(this, function(){ clearTimeout(this._delayedRenderHandle); this._delayedRenderHandle = null; this.render(); }), this.delayInMs ); } return this; // dojox.charting.Chart }, connectToPlot: function(name, object, method){ // summary: // A convenience method to connect a function to a plot. // name: String // The name of the plot as defined by addPlot. // object: Object // The object to be connected. // method: Function // The function to be executed. // returns: Array // A handle to the connection, as defined by dojo.connect (see dojo.connect). return name in this.plots ? this.stack[this.plots[name]].connect(object, method) : null; // Array }, fireEvent: function(seriesName, eventName, index){ // summary: // Fires a synthetic event for a series item. // seriesName: String: // Series name. // eventName: String: // Event name to simulate: onmouseover, onmouseout, onclick. // index: Number: // Valid data value index for the event. // returns: dojox.charting.Chart // A reference to the current chart for functional chaining. if(seriesName in this.runs){ var plotName = this.series[this.runs[seriesName]].plot; if(plotName in this.plots){ var plot = this.stack[this.plots[plotName]]; if(plot){ plot.fireEvent(seriesName, eventName, index); } } } return this; // dojox.charting.Chart }, _makeClean: function(){ // reset dirty flags arr.forEach(this.axes, makeClean); arr.forEach(this.stack, makeClean); arr.forEach(this.series, makeClean); this.dirty = false; }, _makeDirty: function(){ // reset dirty flags arr.forEach(this.axes, makeDirty); arr.forEach(this.stack, makeDirty); arr.forEach(this.series, makeDirty); this.dirty = true; }, _invalidateDependentPlots: function(plotName, /* Boolean */ verticalAxis){ if(plotName in this.plots){ var plot = this.stack[this.plots[plotName]], axis, axisName = verticalAxis ? "vAxis" : "hAxis"; if(plot[axisName]){ axis = this.axes[plot[axisName]]; if(axis && axis.dependOnData()){ axis.dirty = true; // find all plots and mark them dirty arr.forEach(this.stack, function(p){ if(p[axisName] && p[axisName] == plot[axisName]){ p.dirty = true; } }); } }else{ plot.dirty = true; } } } }); function hSection(stats){ return {min: stats.hmin, max: stats.hmax}; } function vSection(stats){ return {min: stats.vmin, max: stats.vmax}; } function hReplace(stats, h){ stats.hmin = h.min; stats.hmax = h.max; } function vReplace(stats, v){ stats.vmin = v.min; stats.vmax = v.max; } function combineStats(target, source){ if(target && source){ target.min = Math.min(target.min, source.min); target.max = Math.max(target.max, source.max); } return target || source; } function calculateAxes(stack, plotArea){ var plots = {}, axes = {}; arr.forEach(stack, function(plot){ var stats = plots[plot.name] = plot.getSeriesStats(); if(plot.hAxis){ axes[plot.hAxis] = combineStats(axes[plot.hAxis], hSection(stats)); } if(plot.vAxis){ axes[plot.vAxis] = combineStats(axes[plot.vAxis], vSection(stats)); } }); arr.forEach(stack, function(plot){ var stats = plots[plot.name]; if(plot.hAxis){ hReplace(stats, axes[plot.hAxis]); } if(plot.vAxis){ vReplace(stats, axes[plot.vAxis]); } plot.initializeScalers(plotArea, stats); }); } return dojox.charting.Chart; });