diff options
| author | Tristan Zur <tzur@web.web.ccwn.org> | 2014-03-27 22:27:47 +0100 |
|---|---|---|
| committer | Tristan Zur <tzur@web.web.ccwn.org> | 2014-03-27 22:27:47 +0100 |
| commit | b62676ca5d3d6f6ba3f019ea3f99722e165a98d8 (patch) | |
| tree | 86722cb80f07d4569f90088eeaea2fc2f6e2ef94 /js/dojo/dojox/charting/Chart.js | |
Diffstat (limited to 'js/dojo/dojox/charting/Chart.js')
| -rw-r--r-- | js/dojo/dojox/charting/Chart.js | 1130 |
1 files changed, 1130 insertions, 0 deletions
diff --git a/js/dojo/dojox/charting/Chart.js b/js/dojo/dojox/charting/Chart.js new file mode 100644 index 0000000..084b156 --- /dev/null +++ b/js/dojo/dojox/charting/Chart.js @@ -0,0 +1,1130 @@ +//>>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; +}); |
