diff options
Diffstat (limited to 'js/dojo/dojox/charting/plot2d')
24 files changed, 4388 insertions, 0 deletions
diff --git a/js/dojo/dojox/charting/plot2d/Areas.js b/js/dojo/dojox/charting/plot2d/Areas.js new file mode 100644 index 0000000..a99fa7f --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Areas.js @@ -0,0 +1,15 @@ +//>>built +define("dojox/charting/plot2d/Areas", ["dojo/_base/declare", "./Default"], + function(declare, Default){ +/*===== +var Default = dojox.charting.plot2d.Default; +=====*/ + return declare("dojox.charting.plot2d.Areas", Default, { + // summary: + // Represents an area chart. See dojox.charting.plot2d.Default for details. + constructor: function(){ + this.opt.lines = true; + this.opt.areas = true; + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Bars.js b/js/dojo/dojox/charting/plot2d/Bars.js new file mode 100644 index 0000000..375b5d4 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Bars.js @@ -0,0 +1,196 @@ +//>>built +define("dojox/charting/plot2d/Bars", ["dojo/_base/kernel", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Base", "./common", + "dojox/gfx/fx", "dojox/lang/utils", "dojox/lang/functional", "dojox/lang/functional/reversed"], + function(dojo, lang, arr, declare, Base, dc, fx, du, df, dfr){ + + /*===== + dojo.declare("dojox.charting.plot2d.__BarCtorArgs", dojox.charting.plot2d.__DefaultCtorArgs, { + // summary: + // Additional keyword arguments for bar charts. + + // minBarSize: Number? + // The minimum size for a bar in pixels. Default is 1. + minBarSize: 1, + + // maxBarSize: Number? + // The maximum size for a bar in pixels. Default is 1. + maxBarSize: 1, + + // enableCache: Boolean? + // Whether the bars rect are cached from one rendering to another. This improves the rendering performance of + // successive rendering but penalize the first rendering. Default false. + enableCache: false + }); + var Base = dojox.charting.plot2d.Base; + =====*/ + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + return declare("dojox.charting.plot2d.Bars", Base, { + // summary: + // The plot object representing a bar chart (horizontal bars). + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + gap: 0, // gap between columns in pixels + animate: null, // animate bars into place + enableCache: false + }, + optionalParams: { + minBarSize: 1, // minimal bar width in pixels + maxBarSize: 1, // maximal bar width in pixels + // theme component + stroke: {}, + outline: {}, + shadow: {}, + fill: {}, + font: "", + fontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // The constructor for a bar chart. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__BarCtorArgs? + // An optional keyword arguments object to help define the plot. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + this.animate = this.opt.animate; + }, + + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + var stats = dc.collectSimpleStats(this.series), t; + stats.hmin -= 0.5; + stats.hmax += 0.5; + t = stats.hmin, stats.hmin = stats.vmin, stats.vmin = t; + t = stats.hmax, stats.hmax = stats.vmax, stats.vmax = t; + return stats; + }, + + createRect: function(run, creator, params){ + var rect; + if(this.opt.enableCache && run._rectFreePool.length > 0){ + rect = run._rectFreePool.pop(); + rect.setShape(params); + // was cleared, add it back + creator.add(rect); + }else{ + rect = creator.createRect(params); + } + if(this.opt.enableCache){ + run._rectUsePool.push(rect); + } + return rect; + }, + + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.Bars + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.dirty = this.isDirty(); + this.resetEvents(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, height, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + baseline = Math.max(0, this._hScaler.bounds.lower), + baselineWidth = ht(baseline), + events = this.events(); + f = dc.calculateBarSize(this._vScaler.bounds.scale, this.opt); + gap = f.gap; + height = f.size; + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + if(this.opt.enableCache){ + run._rectFreePool = (run._rectFreePool?run._rectFreePool:[]).concat(run._rectUsePool?run._rectUsePool:[]); + run._rectUsePool = []; + } + var theme = t.next("bar", [this.opt, run]), s = run.group, + eventSeries = new Array(run.data.length); + for(var j = 0; j < run.data.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y, + hv = ht(v), + width = hv - baselineWidth, + w = Math.abs(width), + finalTheme = typeof value != "number" ? + t.addMixin(theme, "bar", value, true) : + t.post(theme, "bar"); + if(w >= 0 && height >= 1){ + var rect = { + x: offsets.l + (v < baseline ? hv : baselineWidth), + y: dim.height - offsets.b - vt(j + 1.5) + gap, + width: w, height: height + }; + var specialFill = this._plotFill(finalTheme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, rect); + var shape = this.createRect(run, s, rect).setFill(specialFill).setStroke(finalTheme.series.stroke); + run.dyn.fill = shape.getFill(); + run.dyn.stroke = shape.getStroke(); + if(events){ + var o = { + element: "bar", + index: j, + run: run, + shape: shape, + x: v, + y: j + 1.5 + }; + this._connectEvents(o); + eventSeries[j] = o; + } + if(this.animate){ + this._animateBar(shape, offsets.l + baselineWidth, -w); + } + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.Bars + }, + _animateBar: function(shape, hoffset, hsize){ + fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform: [ + {name: "translate", start: [hoffset - (hoffset/hsize), 0], end: [0, 0]}, + {name: "scale", start: [1/hsize, 1], end: [1, 1]}, + {name: "original"} + ] + }, this.animate)).play(); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Base.js b/js/dojo/dojox/charting/plot2d/Base.js new file mode 100644 index 0000000..2dee3ac --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Base.js @@ -0,0 +1,245 @@ +//>>built +define("dojox/charting/plot2d/Base", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/connect", + "../Element", "./_PlotEvents", "dojo/_base/array", + "../scaler/primitive", "./common", "dojox/gfx/fx"], + function(lang, declare, hub, Element, PlotEvents, arr, primitive, common, fx){ +/*===== +var Element = dojox.charting.Element; +var PlotEvents = dojox.charting.plot2d._PlotEvents; +dojox.charting.plot2d.__PlotCtorArgs = function(){ + // summary: + // The base keyword arguments object for plot constructors. + // Note that the parameters for this may change based on the + // specific plot type (see the corresponding plot type for + // details). +} +=====*/ +return declare("dojox.charting.plot2d.Base", [Element, PlotEvents], { + constructor: function(chart, kwArgs){ + // summary: + // Create a base plot for charting. + // chart: dojox.chart.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__PlotCtorArgs? + // An optional arguments object to help define the plot. + this.zoom = null, + this.zoomQueue = []; // zooming action task queue + this.lastWindow = {vscale: 1, hscale: 1, xoffset: 0, yoffset: 0}; + }, + clear: function(){ + // summary: + // Clear out all of the information tied to this plot. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + this.series = []; + this._hAxis = null; + this._vAxis = null; + this.dirty = true; + return this; // dojox.charting.plot2d.Base + }, + setAxis: function(axis){ + // summary: + // Set an axis for this plot. + // axis: dojox.charting.axis2d.Base + // The axis to set. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + if(axis){ + this[axis.vertical ? "_vAxis" : "_hAxis"] = axis; + } + return this; // dojox.charting.plot2d.Base + }, + toPage: function(coord){ + // summary: + // Compute page coordinates from plot axis data coordinates. + // coord: Object? + // The coordinates in plot axis data coordinate space. For cartesian charts that is of the following form: + // `{ hAxisName: 50, vAxisName: 200 }` + // If not provided return the tranform method instead of the result of the transformation. + // returns: Object + // The resulting page pixel coordinates. That is of the following form: + // `{ x: 50, y: 200 }` + var ah = this._hAxis, av = this._vAxis, + sh = ah.getScaler(), sv = av.getScaler(), + th = sh.scaler.getTransformerFromModel(sh), + tv = sv.scaler.getTransformerFromModel(sv), + c = this.chart.getCoords(), + o = this.chart.offsets, dim = this.chart.dim; + var t = function(coord){ + var r = {}; + r.x = th(coord[ah.name]) + c.x + o.l; + r.y = c.y + dim.height - o.b - tv(coord[av.name]); + return r; + }; + // if no coord return the function so that we can capture the current transforms + // and reuse them later on + return coord?t(coord):t; + }, + toData: function(coord){ + // summary: + // Compute plot axis data coordinates from page coordinates. + // coord: Object + // The pixel coordinate in page coordinate space. That is of the following form: + // `{ x: 50, y: 200 }` + // If not provided return the tranform method instead of the result of the transformation. + // returns: Object + // The resulting plot axis data coordinates. For cartesian charts that is of the following form: + // `{ hAxisName: 50, vAxisName: 200 }` + var ah = this._hAxis, av = this._vAxis, + sh = ah.getScaler(), sv = av.getScaler(), + th = sh.scaler.getTransformerFromPlot(sh), + tv = sv.scaler.getTransformerFromPlot(sv), + c = this.chart.getCoords(), + o = this.chart.offsets, dim = this.chart.dim; + var t = function(coord){ + var r = {}; + r[ah.name] = th(coord.x - c.x - o.l); + r[av.name] = tv(c.y + dim.height - coord.y - o.b); + return r; + }; + // if no coord return the function so that we can capture the current transforms + // and reuse them later on + return coord?t(coord):t; + }, + addSeries: function(run){ + // summary: + // Add a data series to this plot. + // run: dojox.charting.Series + // The series to be added. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + this.series.push(run); + return this; // dojox.charting.plot2d.Base + }, + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + return common.collectSimpleStats(this.series); + }, + calculateAxes: function(dim){ + // summary: + // Stub function for running the axis calculations (depricated). + // dim: Object + // An object of the form { width, height } + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + this.initializeScalers(dim, this.getSeriesStats()); + return this; // dojox.charting.plot2d.Base + }, + isDirty: function(){ + // summary: + // Returns whether or not this plot needs to be rendered. + // returns: Boolean + // The state of the plot. + return this.dirty || this._hAxis && this._hAxis.dirty || this._vAxis && this._vAxis.dirty; // Boolean + }, + isDataDirty: function(){ + // summary: + // Returns whether or not any of this plot's data series need to be rendered. + // returns: Boolean + // Flag indicating if any of this plot's series are invalid and need rendering. + return arr.some(this.series, function(item){ return item.dirty; }); // Boolean + }, + performZoom: function(dim, offsets){ + // summary: + // Create/alter any zooming windows on this plot. + // dim: Object + // An object of the form { width, height }. + // offsets: Object + // An object of the form { l, r, t, b }. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + + // get current zooming various + var vs = this._vAxis.scale || 1, + hs = this._hAxis.scale || 1, + vOffset = dim.height - offsets.b, + hBounds = this._hScaler.bounds, + xOffset = (hBounds.from - hBounds.lower) * hBounds.scale, + vBounds = this._vScaler.bounds, + yOffset = (vBounds.from - vBounds.lower) * vBounds.scale, + // get incremental zooming various + rVScale = vs / this.lastWindow.vscale, + rHScale = hs / this.lastWindow.hscale, + rXOffset = (this.lastWindow.xoffset - xOffset)/ + ((this.lastWindow.hscale == 1)? hs : this.lastWindow.hscale), + rYOffset = (yOffset - this.lastWindow.yoffset)/ + ((this.lastWindow.vscale == 1)? vs : this.lastWindow.vscale), + + shape = this.group, + anim = fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform:[ + {name:"translate", start:[0, 0], end: [offsets.l * (1 - rHScale), vOffset * (1 - rVScale)]}, + {name:"scale", start:[1, 1], end: [rHScale, rVScale]}, + {name:"original"}, + {name:"translate", start: [0, 0], end: [rXOffset, rYOffset]} + ]}, this.zoom)); + + lang.mixin(this.lastWindow, {vscale: vs, hscale: hs, xoffset: xOffset, yoffset: yOffset}); + //add anim to zooming action queue, + //in order to avoid several zooming action happened at the same time + this.zoomQueue.push(anim); + //perform each anim one by one in zoomQueue + hub.connect(anim, "onEnd", this, function(){ + this.zoom = null; + this.zoomQueue.shift(); + if(this.zoomQueue.length > 0){ + this.zoomQueue[0].play(); + } + }); + if(this.zoomQueue.length == 1){ + this.zoomQueue[0].play(); + } + return this; // dojox.charting.plot2d.Base + }, + render: function(dim, offsets){ + // summary: + // Render the plot on the chart. + // dim: Object + // An object of the form { width, height }. + // offsets: Object + // An object of the form { l, r, t, b }. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + return this; // dojox.charting.plot2d.Base + }, + getRequiredColors: function(){ + // summary: + // Get how many data series we have, so we know how many colors to use. + // returns: Number + // The number of colors needed. + return this.series.length; // Number + }, + initializeScalers: function(dim, stats){ + // summary: + // Initializes scalers using attached axes. + // dim: Object: + // Size of a plot area in pixels as {width, height}. + // stats: Object: + // Min/max of data in both directions as {hmin, hmax, vmin, vmax}. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + if(this._hAxis){ + if(!this._hAxis.initialized()){ + this._hAxis.calculate(stats.hmin, stats.hmax, dim.width); + } + this._hScaler = this._hAxis.getScaler(); + }else{ + this._hScaler = primitive.buildScaler(stats.hmin, stats.hmax, dim.width); + } + if(this._vAxis){ + if(!this._vAxis.initialized()){ + this._vAxis.calculate(stats.vmin, stats.vmax, dim.height); + } + this._vScaler = this._vAxis.getScaler(); + }else{ + this._vScaler = primitive.buildScaler(stats.vmin, stats.vmax, dim.height); + } + return this; // dojox.charting.plot2d.Base + } +}); +}); diff --git a/js/dojo/dojox/charting/plot2d/Bubble.js b/js/dojo/dojox/charting/plot2d/Bubble.js new file mode 100644 index 0000000..4de7a06 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Bubble.js @@ -0,0 +1,219 @@ +//>>built +define("dojox/charting/plot2d/Bubble", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", + "./Base", "./common", "dojox/lang/functional", "dojox/lang/functional/reversed", + "dojox/lang/utils", "dojox/gfx/fx"], + function(lang, declare, arr, Base, dc, df, dfr, du, fx){ +/*===== +var Base = dojox.charting.plot2d.Base; +=====*/ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + return declare("dojox.charting.plot2d.Bubble", Base, { + // summary: + // A plot representing bubbles. Note that data for Bubbles requires 3 parameters, + // in the form of: { x, y, size }, where size determines the size of the bubble. + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + animate: null // animate bars into place + }, + optionalParams: { + // theme component + stroke: {}, + outline: {}, + shadow: {}, + fill: {}, + font: "", + fontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // Create a plot of bubbles. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__DefaultCtorArgs? + // Optional keyword arguments object to help define plot parameters. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + this.animate = this.opt.animate; + }, + + // override the render so that we are plotting only circles. + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.Bubble + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + + var t = this.chart.theme, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + events = this.events(); + + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + if(!run.data.length){ + run.dirty = false; + t.skip(); + continue; + } + + if(typeof run.data[0] == "number"){ + console.warn("dojox.charting.plot2d.Bubble: the data in the following series cannot be rendered as a bubble chart; ", run); + continue; + } + + var theme = t.next("circle", [this.opt, run]), s = run.group, + points = arr.map(run.data, function(v, i){ + return v ? { + x: ht(v.x) + offsets.l, + y: dim.height - offsets.b - vt(v.y), + radius: this._vScaler.bounds.scale * (v.size / 2) + } : null; + }, this); + + var frontCircles = null, outlineCircles = null, shadowCircles = null; + + // make shadows if needed + if(theme.series.shadow){ + shadowCircles = arr.map(points, function(item){ + if(item !== null){ + var finalTheme = t.addMixin(theme, "circle", item, true), + shadow = finalTheme.series.shadow; + var shape = s.createCircle({ + cx: item.x + shadow.dx, cy: item.y + shadow.dy, r: item.radius + }).setStroke(shadow).setFill(shadow.color); + if(this.animate){ + this._animateBubble(shape, dim.height - offsets.b, item.radius); + } + return shape; + } + return null; + }, this); + if(shadowCircles.length){ + run.dyn.shadow = shadowCircles[shadowCircles.length - 1].getStroke(); + } + } + + // make outlines if needed + if(theme.series.outline){ + outlineCircles = arr.map(points, function(item){ + if(item !== null){ + var finalTheme = t.addMixin(theme, "circle", item, true), + outline = dc.makeStroke(finalTheme.series.outline); + outline.width = 2 * outline.width + theme.series.stroke.width; + var shape = s.createCircle({ + cx: item.x, cy: item.y, r: item.radius + }).setStroke(outline); + if(this.animate){ + this._animateBubble(shape, dim.height - offsets.b, item.radius); + } + return shape; + } + return null; + }, this); + if(outlineCircles.length){ + run.dyn.outline = outlineCircles[outlineCircles.length - 1].getStroke(); + } + } + + // run through the data and add the circles. + frontCircles = arr.map(points, function(item){ + if(item !== null){ + var finalTheme = t.addMixin(theme, "circle", item, true), + rect = { + x: item.x - item.radius, + y: item.y - item.radius, + width: 2 * item.radius, + height: 2 * item.radius + }; + var specialFill = this._plotFill(finalTheme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, rect); + var shape = s.createCircle({ + cx: item.x, cy: item.y, r: item.radius + }).setFill(specialFill).setStroke(finalTheme.series.stroke); + if(this.animate){ + this._animateBubble(shape, dim.height - offsets.b, item.radius); + } + return shape; + } + return null; + }, this); + if(frontCircles.length){ + run.dyn.fill = frontCircles[frontCircles.length - 1].getFill(); + run.dyn.stroke = frontCircles[frontCircles.length - 1].getStroke(); + } + + if(events){ + var eventSeries = new Array(frontCircles.length); + arr.forEach(frontCircles, function(s, i){ + if(s !== null){ + var o = { + element: "circle", + index: i, + run: run, + shape: s, + outline: outlineCircles && outlineCircles[i] || null, + shadow: shadowCircles && shadowCircles[i] || null, + x: run.data[i].x, + y: run.data[i].y, + r: run.data[i].size / 2, + cx: points[i].x, + cy: points[i].y, + cr: points[i].radius + }; + this._connectEvents(o); + eventSeries[i] = o; + } + }, this); + this._eventSeries[run.name] = eventSeries; + }else{ + delete this._eventSeries[run.name]; + } + + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.Bubble + }, + _animateBubble: function(shape, offset, size){ + fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform: [ + {name: "translate", start: [0, offset], end: [0, 0]}, + {name: "scale", start: [0, 1/size], end: [1, 1]}, + {name: "original"} + ] + }, this.animate)).play(); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Candlesticks.js b/js/dojo/dojox/charting/plot2d/Candlesticks.js new file mode 100644 index 0000000..a30d39b --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Candlesticks.js @@ -0,0 +1,229 @@ +//>>built +define("dojox/charting/plot2d/Candlesticks", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", "./Base", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils", "dojox/gfx/fx"], + function(lang, declare, arr, Base, dc, df, dfr, du, fx){ +/*===== +var Base = dojox.charting.plot2d.Base; +=====*/ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + // Candlesticks are based on the Bars plot type; we expect the following passed + // as values in a series: + // { x?, open, close, high, low, mid? } + // if x is not provided, the array index is used. + // failing to provide the OHLC values will throw an error. + return declare("dojox.charting.plot2d.Candlesticks", Base, { + // summary: + // A plot that represents typical candlesticks (financial reporting, primarily). + // Unlike most charts, the Candlestick expects data points to be represented by + // an object of the form { x?, open, close, high, low, mid? }, where both + // x and mid are optional parameters. If x is not provided, the index of the + // data array is used. + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + gap: 2, // gap between columns in pixels + animate: null // animate bars into place + }, + optionalParams: { + minBarSize: 1, // minimal candle width in pixels + maxBarSize: 1, // maximal candle width in pixels + // theme component + stroke: {}, + outline: {}, + shadow: {}, + fill: {}, + font: "", + fontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // The constructor for a candlestick chart. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__BarCtorArgs? + // An optional keyword arguments object to help define the plot. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + this.animate = this.opt.animate; + }, + + collectStats: function(series){ + // summary: + // Collect all statistics for drawing this chart. Since the common + // functionality only assumes x and y, Candlesticks must create it's own + // stats (since data has no y value, but open/close/high/low instead). + // series: dojox.charting.Series[] + // The data series array to be drawn on this plot. + // returns: Object + // Returns an object in the form of { hmin, hmax, vmin, vmax }. + + // we have to roll our own, since we need to use all four passed + // values to figure out our stats, and common only assumes x and y. + var stats = lang.delegate(dc.defaultStats); + for(var i=0; i<series.length; i++){ + var run = series[i]; + if(!run.data.length){ continue; } + var old_vmin = stats.vmin, old_vmax = stats.vmax; + if(!("ymin" in run) || !("ymax" in run)){ + arr.forEach(run.data, function(val, idx){ + if(val !== null){ + var x = val.x || idx + 1; + stats.hmin = Math.min(stats.hmin, x); + stats.hmax = Math.max(stats.hmax, x); + stats.vmin = Math.min(stats.vmin, val.open, val.close, val.high, val.low); + stats.vmax = Math.max(stats.vmax, val.open, val.close, val.high, val.low); + } + }); + } + if("ymin" in run){ stats.vmin = Math.min(old_vmin, run.ymin); } + if("ymax" in run){ stats.vmax = Math.max(old_vmax, run.ymax); } + } + return stats; // Object + }, + + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + var stats = this.collectStats(this.series); + stats.hmin -= 0.5; + stats.hmax += 0.5; + return stats; + }, + + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.Candlesticks + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, width, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + baseline = Math.max(0, this._vScaler.bounds.lower), + baselineHeight = vt(baseline), + events = this.events(); + f = dc.calculateBarSize(this._hScaler.bounds.scale, this.opt); + gap = f.gap; + width = f.size; + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + var theme = t.next("candlestick", [this.opt, run]), s = run.group, + eventSeries = new Array(run.data.length); + for(var j = 0; j < run.data.length; ++j){ + var v = run.data[j]; + if(v !== null){ + var finalTheme = t.addMixin(theme, "candlestick", v, true); + + // calculate the points we need for OHLC + var x = ht(v.x || (j+0.5)) + offsets.l + gap, + y = dim.height - offsets.b, + open = vt(v.open), + close = vt(v.close), + high = vt(v.high), + low = vt(v.low); + if("mid" in v){ + var mid = vt(v.mid); + } + if(low > high){ + var tmp = high; + high = low; + low = tmp; + } + + if(width >= 1){ + // draw the line and rect, set up as a group and pass that to the events. + var doFill = open > close; + var line = { x1: width/2, x2: width/2, y1: y - high, y2: y - low }, + rect = { + x: 0, y: y-Math.max(open, close), + width: width, height: Math.max(doFill ? open-close : close-open, 1) + }; + var shape = s.createGroup(); + shape.setTransform({dx: x, dy: 0 }); + var inner = shape.createGroup(); + inner.createLine(line).setStroke(finalTheme.series.stroke); + inner.createRect(rect).setStroke(finalTheme.series.stroke). + setFill(doFill ? finalTheme.series.fill : "white"); + if("mid" in v){ + // add the mid line. + inner.createLine({ + x1: (finalTheme.series.stroke.width||1), x2: width - (finalTheme.series.stroke.width || 1), + y1: y - mid, y2: y - mid + }).setStroke(doFill ? "white" : finalTheme.series.stroke); + } + + // TODO: double check this. + run.dyn.fill = finalTheme.series.fill; + run.dyn.stroke = finalTheme.series.stroke; + if(events){ + var o = { + element: "candlestick", + index: j, + run: run, + shape: inner, + x: x, + y: y-Math.max(open, close), + cx: width/2, + cy: (y-Math.max(open, close)) + (Math.max(doFill ? open-close : close-open, 1)/2), + width: width, + height: Math.max(doFill ? open-close : close-open, 1), + data: v + }; + this._connectEvents(o); + eventSeries[j] = o; + } + } + if(this.animate){ + this._animateCandlesticks(shape, y - low, high - low); + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.Candlesticks + }, + _animateCandlesticks: function(shape, voffset, vsize){ + fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform: [ + {name: "translate", start: [0, voffset - (voffset/vsize)], end: [0, 0]}, + {name: "scale", start: [1, 1/vsize], end: [1, 1]}, + {name: "original"} + ] + }, this.animate)).play(); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/ClusteredBars.js b/js/dojo/dojox/charting/plot2d/ClusteredBars.js new file mode 100644 index 0000000..afac291 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/ClusteredBars.js @@ -0,0 +1,100 @@ +//>>built +define("dojox/charting/plot2d/ClusteredBars", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Bars", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils"], + function(lang, arr, declare, Bars, dc, df, dfr, du){ +/*===== +var Bars = dojox.charting.plot2d.Bars; +=====*/ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + return declare("dojox.charting.plot2d.ClusteredBars", Bars, { + // summary: + // A plot representing grouped or clustered bars (horizontal bars) + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.ClusteredBars + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, height, thickness, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + baseline = Math.max(0, this._hScaler.bounds.lower), + baselineWidth = ht(baseline), + events = this.events(); + f = dc.calculateBarSize(this._vScaler.bounds.scale, this.opt, this.series.length); + gap = f.gap; + height = thickness = f.size; + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i], shift = thickness * (this.series.length - i - 1); + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + var theme = t.next("bar", [this.opt, run]), s = run.group, + eventSeries = new Array(run.data.length); + for(var j = 0; j < run.data.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y, + hv = ht(v), + width = hv - baselineWidth, + w = Math.abs(width), + finalTheme = typeof value != "number" ? + t.addMixin(theme, "bar", value, true) : + t.post(theme, "bar"); + if(w >= 0 && height >= 1){ + var rect = { + x: offsets.l + (v < baseline ? hv : baselineWidth), + y: dim.height - offsets.b - vt(j + 1.5) + gap + shift, + width: w, height: height + }; + var specialFill = this._plotFill(finalTheme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, rect); + var shape = s.createRect(rect).setFill(specialFill).setStroke(finalTheme.series.stroke); + run.dyn.fill = shape.getFill(); + run.dyn.stroke = shape.getStroke(); + if(events){ + var o = { + element: "bar", + index: j, + run: run, + shape: shape, + x: v, + y: j + 1.5 + }; + this._connectEvents(o); + eventSeries[j] = o; + } + if(this.animate){ + this._animateBar(shape, offsets.l + baselineWidth, -width); + } + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.ClusteredBars + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/ClusteredColumns.js b/js/dojo/dojox/charting/plot2d/ClusteredColumns.js new file mode 100644 index 0000000..b3e7d09 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/ClusteredColumns.js @@ -0,0 +1,100 @@ +//>>built +define("dojox/charting/plot2d/ClusteredColumns", ["dojo/_base/array", "dojo/_base/declare", "./Columns", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils"], + function(arr, declare, Columns, dc, df, dfr, du){ +/*===== +var Columns = dojox.charting.plot2d.Columns; +=====*/ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + return declare("dojox.charting.plot2d.ClusteredColumns", Columns, { + // summary: + // A plot representing grouped or clustered columns (vertical bars). + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.ClusteredColumns + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, width, thickness, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + baseline = Math.max(0, this._vScaler.bounds.lower), + baselineHeight = vt(baseline), + events = this.events(); + f = dc.calculateBarSize(this._hScaler.bounds.scale, this.opt, this.series.length); + gap = f.gap; + width = thickness = f.size; + for(var i = 0; i < this.series.length; ++i){ + var run = this.series[i], shift = thickness * i; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + var theme = t.next("column", [this.opt, run]), s = run.group, + eventSeries = new Array(run.data.length); + for(var j = 0; j < run.data.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y, + vv = vt(v), + height = vv - baselineHeight, + h = Math.abs(height), + finalTheme = typeof value != "number" ? + t.addMixin(theme, "column", value, true) : + t.post(theme, "column"); + if(width >= 1 && h >= 0){ + var rect = { + x: offsets.l + ht(j + 0.5) + gap + shift, + y: dim.height - offsets.b - (v > baseline ? vv : baselineHeight), + width: width, height: h + }; + var specialFill = this._plotFill(finalTheme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, rect); + var shape = s.createRect(rect).setFill(specialFill).setStroke(finalTheme.series.stroke); + run.dyn.fill = shape.getFill(); + run.dyn.stroke = shape.getStroke(); + if(events){ + var o = { + element: "column", + index: j, + run: run, + shape: shape, + x: j + 0.5, + y: v + }; + this._connectEvents(o); + eventSeries[j] = o; + } + if(this.animate){ + this._animateColumn(shape, dim.height - offsets.b - baselineHeight, h); + } + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.ClusteredColumns + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Columns.js b/js/dojo/dojox/charting/plot2d/Columns.js new file mode 100644 index 0000000..7e25091 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Columns.js @@ -0,0 +1,180 @@ +//>>built +define("dojox/charting/plot2d/Columns", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Base", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils", "dojox/gfx/fx"], + function(lang, arr, declare, Base, dc, df, dfr, du, fx){ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); +/*===== +var Base = dojox.charting.plot2d.Base; +=====*/ + + return declare("dojox.charting.plot2d.Columns", Base, { + // summary: + // The plot object representing a column chart (vertical bars). + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + gap: 0, // gap between columns in pixels + animate: null, // animate bars into place + enableCache: false + }, + optionalParams: { + minBarSize: 1, // minimal column width in pixels + maxBarSize: 1, // maximal column width in pixels + // theme component + stroke: {}, + outline: {}, + shadow: {}, + fill: {}, + font: "", + fontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // The constructor for a columns chart. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__BarCtorArgs? + // An optional keyword arguments object to help define the plot. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + this.animate = this.opt.animate; + }, + + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + var stats = dc.collectSimpleStats(this.series); + stats.hmin -= 0.5; + stats.hmax += 0.5; + return stats; + }, + + createRect: function(run, creator, params){ + var rect; + if(this.opt.enableCache && run._rectFreePool.length > 0){ + rect = run._rectFreePool.pop(); + rect.setShape(params); + // was cleared, add it back + creator.add(rect); + }else{ + rect = creator.createRect(params); + } + if(this.opt.enableCache){ + run._rectUsePool.push(rect); + } + return rect; + }, + + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.Columns + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + var t = this.getSeriesStats(); + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, width, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + baseline = Math.max(0, this._vScaler.bounds.lower), + baselineHeight = vt(baseline), + min = Math.max(0, Math.floor(this._hScaler.bounds.from - 1)), max = Math.ceil(this._hScaler.bounds.to), + events = this.events(); + f = dc.calculateBarSize(this._hScaler.bounds.scale, this.opt); + gap = f.gap; + width = f.size; + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + if(this.opt.enableCache){ + run._rectFreePool = (run._rectFreePool?run._rectFreePool:[]).concat(run._rectUsePool?run._rectUsePool:[]); + run._rectUsePool = []; + } + var theme = t.next("column", [this.opt, run]), s = run.group, + eventSeries = new Array(run.data.length); + var l = Math.min(run.data.length, max); + for(var j = min; j < l; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y, + vv = vt(v), + height = vv - baselineHeight, + h = Math.abs(height), + finalTheme = typeof value != "number" ? + t.addMixin(theme, "column", value, true) : + t.post(theme, "column"); + if(width >= 1 && h >= 0){ + var rect = { + x: offsets.l + ht(j + 0.5) + gap, + y: dim.height - offsets.b - (v > baseline ? vv : baselineHeight), + width: width, height: h + }; + var specialFill = this._plotFill(finalTheme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, rect); + var shape = this.createRect(run, s, rect).setFill(specialFill).setStroke(finalTheme.series.stroke); + run.dyn.fill = shape.getFill(); + run.dyn.stroke = shape.getStroke(); + if(events){ + var o = { + element: "column", + index: j, + run: run, + shape: shape, + x: j + 0.5, + y: v + }; + this._connectEvents(o); + eventSeries[j] = o; + } + if(this.animate){ + this._animateColumn(shape, dim.height - offsets.b - baselineHeight, h); + } + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.Columns + }, + _animateColumn: function(shape, voffset, vsize){ + fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform: [ + {name: "translate", start: [0, voffset - (voffset/vsize)], end: [0, 0]}, + {name: "scale", start: [1, 1/vsize], end: [1, 1]}, + {name: "original"} + ] + }, this.animate)).play(); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Default.js b/js/dojo/dojox/charting/plot2d/Default.js new file mode 100644 index 0000000..50434e3 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Default.js @@ -0,0 +1,377 @@ +//>>built +define("dojox/charting/plot2d/Default", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", + "./Base", "./common", "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils", "dojox/gfx/fx"], + function(lang, declare, arr, Base, dc, df, dfr, du, fx){ + + /*===== + dojo.declare("dojox.charting.plot2d.__DefaultCtorArgs", dojox.charting.plot2d.__PlotCtorArgs, { + // summary: + // The arguments used for any/most plots. + + // hAxis: String? + // The horizontal axis name. + hAxis: "x", + + // vAxis: String? + // The vertical axis name + vAxis: "y", + + // lines: Boolean? + // Whether or not to draw lines on this plot. Defaults to true. + lines: true, + + // areas: Boolean? + // Whether or not to draw areas on this plot. Defaults to false. + areas: false, + + // markers: Boolean? + // Whether or not to draw markers at data points on this plot. Default is false. + markers: false, + + // tension: Number|String? + // Whether or not to apply 'tensioning' to the lines on this chart. + // Options include a number, "X", "x", or "S"; if a number is used, the + // simpler bezier curve calculations are used to draw the lines. If X, x or S + // is used, the more accurate smoothing algorithm is used. + tension: "", + + // animate: Boolean? + // Whether or not to animate the chart to place. + animate: false, + + // stroke: dojox.gfx.Stroke? + // An optional stroke to use for any series on the plot. + stroke: {}, + + // outline: dojox.gfx.Stroke? + // An optional stroke used to outline any series on the plot. + outline: {}, + + // shadow: dojox.gfx.Stroke? + // An optional stroke to use to draw any shadows for a series on a plot. + shadow: {}, + + // fill: dojox.gfx.Fill? + // Any fill to be used for elements on the plot (such as areas). + fill: {}, + + // font: String? + // A font definition to be used for labels and other text-based elements on the plot. + font: "", + + // fontColor: String|dojo.Color? + // The color to be used for any text-based elements on the plot. + fontColor: "", + + // markerStroke: dojo.gfx.Stroke? + // An optional stroke to use for any markers on the plot. + markerStroke: {}, + + // markerOutline: dojo.gfx.Stroke? + // An optional outline to use for any markers on the plot. + markerOutline: {}, + + // markerShadow: dojo.gfx.Stroke? + // An optional shadow to use for any markers on the plot. + markerShadow: {}, + + // markerFill: dojo.gfx.Fill? + // An optional fill to use for any markers on the plot. + markerFill: {}, + + // markerFont: String? + // An optional font definition to use for any markers on the plot. + markerFont: "", + + // markerFontColor: String|dojo.Color? + // An optional color to use for any marker text on the plot. + markerFontColor: "", + + // enableCache: Boolean? + // Whether the markers are cached from one rendering to another. This improves the rendering performance of + // successive rendering but penalize the first rendering. Default false. + enableCache: false + }); + + var Base = dojox.charting.plot2d.Base; +=====*/ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + var DEFAULT_ANIMATION_LENGTH = 1200; // in ms + + return declare("dojox.charting.plot2d.Default", Base, { + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + lines: true, // draw lines + areas: false, // draw areas + markers: false, // draw markers + tension: "", // draw curved lines (tension is "X", "x", or "S") + animate: false, // animate chart to place + enableCache: false + }, + optionalParams: { + // theme component + stroke: {}, + outline: {}, + shadow: {}, + fill: {}, + font: "", + fontColor: "", + markerStroke: {}, + markerOutline: {}, + markerShadow: {}, + markerFill: {}, + markerFont: "", + markerFontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // Return a new plot. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__DefaultCtorArgs? + // An optional arguments object to help define this plot. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + + // animation properties + this.animate = this.opt.animate; + }, + + createPath: function(run, creator, params){ + var path; + if(this.opt.enableCache && run._pathFreePool.length > 0){ + path = run._pathFreePool.pop(); + path.setShape(params); + // was cleared, add it back + creator.add(path); + }else{ + path = creator.createPath(params); + } + if(this.opt.enableCache){ + run._pathUsePool.push(path); + } + return path; + }, + + render: function(dim, offsets){ + // summary: + // Render/draw everything on this plot. + // dim: Object + // An object of the form { width, height } + // offsets: Object + // An object of the form { l, r, t, b } + // returns: dojox.charting.plot2d.Default + // A reference to this plot for functional chaining. + + // make sure all the series is not modified + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + this.group.setTransform(null); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, stroke, outline, marker, events = this.events(); + + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + if(this.opt.enableCache){ + run._pathFreePool = (run._pathFreePool?run._pathFreePool:[]).concat(run._pathUsePool?run._pathUsePool:[]); + run._pathUsePool = []; + } + if(!run.data.length){ + run.dirty = false; + t.skip(); + continue; + } + + var theme = t.next(this.opt.areas ? "area" : "line", [this.opt, run], true), + s = run.group, rsegments = [], startindexes = [], rseg = null, lpoly, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + eventSeries = this._eventSeries[run.name] = new Array(run.data.length); + + // optim works only for index based case + var indexed = typeof run.data[0] == "number"; + var min = indexed?Math.max(0, Math.floor(this._hScaler.bounds.from - 1)):0, + max = indexed?Math.min(run.data.length, Math.ceil(this._hScaler.bounds.to)):run.data.length; + + // split the run data into dense segments (each containing no nulls) + for(var j = min; j < max; j++){ + if(run.data[j] != null){ + if(!rseg){ + rseg = []; + startindexes.push(j); + rsegments.push(rseg); + } + rseg.push(run.data[j]); + }else{ + rseg = null; + } + } + + for(var seg = 0; seg < rsegments.length; seg++){ + if(typeof rsegments[seg][0] == "number"){ + lpoly = arr.map(rsegments[seg], function(v, i){ + return { + x: ht(i + startindexes[seg] + 1) + offsets.l, + y: dim.height - offsets.b - vt(v) + }; + }, this); + }else{ + lpoly = arr.map(rsegments[seg], function(v, i){ + return { + x: ht(v.x) + offsets.l, + y: dim.height - offsets.b - vt(v.y) + }; + }, this); + } + + var lpath = this.opt.tension ? dc.curve(lpoly, this.opt.tension) : ""; + + if(this.opt.areas && lpoly.length > 1){ + var fill = theme.series.fill; + var apoly = lang.clone(lpoly); + if(this.opt.tension){ + var apath = "L" + apoly[apoly.length-1].x + "," + (dim.height - offsets.b) + + " L" + apoly[0].x + "," + (dim.height - offsets.b) + + " L" + apoly[0].x + "," + apoly[0].y; + run.dyn.fill = s.createPath(lpath + " " + apath).setFill(fill).getFill(); + } else { + apoly.push({x: lpoly[lpoly.length - 1].x, y: dim.height - offsets.b}); + apoly.push({x: lpoly[0].x, y: dim.height - offsets.b}); + apoly.push(lpoly[0]); + run.dyn.fill = s.createPolyline(apoly).setFill(fill).getFill(); + } + } + if(this.opt.lines || this.opt.markers){ + // need a stroke + stroke = theme.series.stroke; + if(theme.series.outline){ + outline = run.dyn.outline = dc.makeStroke(theme.series.outline); + outline.width = 2 * outline.width + stroke.width; + } + } + if(this.opt.markers){ + run.dyn.marker = theme.symbol; + } + var frontMarkers = null, outlineMarkers = null, shadowMarkers = null; + if(stroke && theme.series.shadow && lpoly.length > 1){ + var shadow = theme.series.shadow, + spoly = arr.map(lpoly, function(c){ + return {x: c.x + shadow.dx, y: c.y + shadow.dy}; + }); + if(this.opt.lines){ + if(this.opt.tension){ + run.dyn.shadow = s.createPath(dc.curve(spoly, this.opt.tension)).setStroke(shadow).getStroke(); + } else { + run.dyn.shadow = s.createPolyline(spoly).setStroke(shadow).getStroke(); + } + } + if(this.opt.markers && theme.marker.shadow){ + shadow = theme.marker.shadow; + shadowMarkers = arr.map(spoly, function(c){ + return this.createPath(run, s, "M" + c.x + " " + c.y + " " + theme.symbol). + setStroke(shadow).setFill(shadow.color); + }, this); + } + } + if(this.opt.lines && lpoly.length > 1){ + if(outline){ + if(this.opt.tension){ + run.dyn.outline = s.createPath(lpath).setStroke(outline).getStroke(); + } else { + run.dyn.outline = s.createPolyline(lpoly).setStroke(outline).getStroke(); + } + } + if(this.opt.tension){ + run.dyn.stroke = s.createPath(lpath).setStroke(stroke).getStroke(); + } else { + run.dyn.stroke = s.createPolyline(lpoly).setStroke(stroke).getStroke(); + } + } + if(this.opt.markers){ + frontMarkers = new Array(lpoly.length); + outlineMarkers = new Array(lpoly.length); + outline = null; + if(theme.marker.outline){ + outline = dc.makeStroke(theme.marker.outline); + outline.width = 2 * outline.width + (theme.marker.stroke ? theme.marker.stroke.width : 0); + } + arr.forEach(lpoly, function(c, i){ + var path = "M" + c.x + " " + c.y + " " + theme.symbol; + if(outline){ + outlineMarkers[i] = this.createPath(run, s, path).setStroke(outline); + } + frontMarkers[i] = this.createPath(run, s, path).setStroke(theme.marker.stroke).setFill(theme.marker.fill); + }, this); + run.dyn.markerFill = theme.marker.fill; + run.dyn.markerStroke = theme.marker.stroke; + if(events){ + arr.forEach(frontMarkers, function(s, i){ + var o = { + element: "marker", + index: i + startindexes[seg], + run: run, + shape: s, + outline: outlineMarkers[i] || null, + shadow: shadowMarkers && shadowMarkers[i] || null, + cx: lpoly[i].x, + cy: lpoly[i].y + }; + if(typeof rsegments[seg][0] == "number"){ + o.x = i + startindexes[seg] + 1; + o.y = rsegments[seg][i]; + }else{ + o.x = rsegments[seg][i].x; + o.y = rsegments[seg][i].y; + } + this._connectEvents(o); + eventSeries[i + startindexes[seg]] = o; + }, this); + }else{ + delete this._eventSeries[run.name]; + } + } + } + run.dirty = false; + } + if(this.animate){ + // grow from the bottom + var plotGroup = this.group; + fx.animateTransform(lang.delegate({ + shape: plotGroup, + duration: DEFAULT_ANIMATION_LENGTH, + transform:[ + {name:"translate", start: [0, dim.height - offsets.b], end: [0, 0]}, + {name:"scale", start: [1, 0], end:[1, 1]}, + {name:"original"} + ] + }, this.animate)).play(); + } + this.dirty = false; + return this; // dojox.charting.plot2d.Default + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Grid.js b/js/dojo/dojox/charting/plot2d/Grid.js new file mode 100644 index 0000000..c7631c1 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Grid.js @@ -0,0 +1,320 @@ +//>>built +define("dojox/charting/plot2d/Grid", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/connect", "dojo/_base/array", + "../Element", "./common", "dojox/lang/utils", "dojox/gfx/fx"], + function(lang, declare, hub, arr, Element, dc, du, fx){ + + /*===== + dojo.declare("dojox.charting.plot2d.__GridCtorArgs", dojox.charting.plot2d.__DefaultCtorArgs, { + // summary: + // A special keyword arguments object that is specific to a grid "plot". + + // hMajorLines: Boolean? + // Whether to show lines at the major ticks along the horizontal axis. Default is true. + hMajorLines: true, + + // hMinorLines: Boolean? + // Whether to show lines at the minor ticks along the horizontal axis. Default is false. + hMinorLines: false, + + // vMajorLines: Boolean? + // Whether to show lines at the major ticks along the vertical axis. Default is true. + vMajorLines: true, + + // vMinorLines: Boolean? + // Whether to show lines at the major ticks along the vertical axis. Default is false. + vMinorLines: false, + + // hStripes: String? + // Whether or not to show stripes (alternating fills) along the horizontal axis. Default is "none". + hStripes: "none", + + // vStripes: String? + // Whether or not to show stripes (alternating fills) along the vertical axis. Default is "none". + vStripes: "none", + + // enableCache: Boolean? + // Whether the grid lines are cached from one rendering to another. This improves the rendering performance of + // successive rendering but penalize the first rendering. Default false. + enableCache: false + }); + var Element = dojox.charting.plot2d.Element; + =====*/ + + return declare("dojox.charting.plot2d.Grid", Element, { + // summary: + // A "faux" plot that can be placed behind other plots to represent + // a grid against which other plots can be easily measured. + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + hMajorLines: true, // draw horizontal major lines + hMinorLines: false, // draw horizontal minor lines + vMajorLines: true, // draw vertical major lines + vMinorLines: false, // draw vertical minor lines + hStripes: "none", // TBD + vStripes: "none", // TBD + animate: null, // animate bars into place + enableCache: false + }, + optionalParams: {}, // no optional parameters + + constructor: function(chart, kwArgs){ + // summary: + // Create the faux Grid plot. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__GridCtorArgs? + // An optional keyword arguments object to help define the parameters of the underlying grid. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + this.dirty = true; + this.animate = this.opt.animate; + this.zoom = null, + this.zoomQueue = []; // zooming action task queue + this.lastWindow = {vscale: 1, hscale: 1, xoffset: 0, yoffset: 0}; + if(this.opt.enableCache){ + this._lineFreePool = []; + this._lineUsePool = []; + } + }, + clear: function(){ + // summary: + // Clear out any parameters set on this plot. + // returns: dojox.charting.plot2d.Grid + // The reference to this plot for functional chaining. + this._hAxis = null; + this._vAxis = null; + this.dirty = true; + return this; // dojox.charting.plot2d.Grid + }, + setAxis: function(axis){ + // summary: + // Set an axis for this plot. + // returns: dojox.charting.plot2d.Grid + // The reference to this plot for functional chaining. + if(axis){ + this[axis.vertical ? "_vAxis" : "_hAxis"] = axis; + } + return this; // dojox.charting.plot2d.Grid + }, + addSeries: function(run){ + // summary: + // Ignored but included as a dummy method. + // returns: dojox.charting.plot2d.Grid + // The reference to this plot for functional chaining. + return this; // dojox.charting.plot2d.Grid + }, + getSeriesStats: function(){ + // summary: + // Returns default stats (irrelevant for this type of plot). + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + return lang.delegate(dc.defaultStats); + }, + initializeScalers: function(){ + // summary: + // Does nothing (irrelevant for this type of plot). + return this; + }, + isDirty: function(){ + // summary: + // Return whether or not this plot needs to be redrawn. + // returns: Boolean + // If this plot needs to be rendered, this will return true. + return this.dirty || this._hAxis && this._hAxis.dirty || this._vAxis && this._vAxis.dirty; // Boolean + }, + performZoom: function(dim, offsets){ + // summary: + // Create/alter any zooming windows on this plot. + // dim: Object + // An object of the form { width, height }. + // offsets: Object + // An object of the form { l, r, t, b }. + // returns: dojox.charting.plot2d.Grid + // A reference to this plot for functional chaining. + + // get current zooming various + var vs = this._vAxis.scale || 1, + hs = this._hAxis.scale || 1, + vOffset = dim.height - offsets.b, + hBounds = this._hAxis.getScaler().bounds, + xOffset = (hBounds.from - hBounds.lower) * hBounds.scale, + vBounds = this._vAxis.getScaler().bounds, + yOffset = (vBounds.from - vBounds.lower) * vBounds.scale, + // get incremental zooming various + rVScale = vs / this.lastWindow.vscale, + rHScale = hs / this.lastWindow.hscale, + rXOffset = (this.lastWindow.xoffset - xOffset)/ + ((this.lastWindow.hscale == 1)? hs : this.lastWindow.hscale), + rYOffset = (yOffset - this.lastWindow.yoffset)/ + ((this.lastWindow.vscale == 1)? vs : this.lastWindow.vscale), + + shape = this.group, + anim = fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform:[ + {name:"translate", start:[0, 0], end: [offsets.l * (1 - rHScale), vOffset * (1 - rVScale)]}, + {name:"scale", start:[1, 1], end: [rHScale, rVScale]}, + {name:"original"}, + {name:"translate", start: [0, 0], end: [rXOffset, rYOffset]} + ]}, this.zoom)); + + lang.mixin(this.lastWindow, {vscale: vs, hscale: hs, xoffset: xOffset, yoffset: yOffset}); + //add anim to zooming action queue, + //in order to avoid several zooming action happened at the same time + this.zoomQueue.push(anim); + //perform each anim one by one in zoomQueue + hub.connect(anim, "onEnd", this, function(){ + this.zoom = null; + this.zoomQueue.shift(); + if(this.zoomQueue.length > 0){ + this.zoomQueue[0].play(); + } + }); + if(this.zoomQueue.length == 1){ + this.zoomQueue[0].play(); + } + return this; // dojox.charting.plot2d.Grid + }, + getRequiredColors: function(){ + // summary: + // Ignored but included as a dummy method. + // returns: Number + // Returns 0, since there are no series associated with this plot type. + return 0; // Number + }, + cleanGroup: function(){ + this.inherited(arguments); + if(this.opt.enableCache){ + this._lineFreePool = this._lineFreePool.concat(this._lineUsePool); + this._lineUsePool = []; + } + }, + createLine: function(creator, params){ + var line; + if(this.opt.enableCache && this._lineFreePool.length > 0){ + line = this._lineFreePool.pop(); + line.setShape(params); + // was cleared, add it back + creator.add(line); + }else{ + line = creator.createLine(params); + } + if(this.opt.enableCache){ + this._lineUsePool.push(line); + } + return line; + }, + render: function(dim, offsets){ + // summary: + // Render the plot on the chart. + // dim: Object + // An object of the form { width, height }. + // offsets: Object + // An object of the form { l, r, t, b }. + // returns: dojox.charting.plot2d.Grid + // A reference to this plot for functional chaining. + if(this.zoom){ + return this.performZoom(dim, offsets); + } + this.dirty = this.isDirty(); + if(!this.dirty){ return this; } + this.cleanGroup(); + var s = this.group, ta = this.chart.theme.axis; + // draw horizontal stripes and lines + try{ + var vScaler = this._vAxis.getScaler(), + vt = vScaler.scaler.getTransformerFromModel(vScaler), + ticks = this._vAxis.getTicks(); + if(ticks != null){ + if(this.opt.hMinorLines){ + arr.forEach(ticks.minor, function(tick){ + var y = dim.height - offsets.b - vt(tick.value); + var hMinorLine = this.createLine(s, { + x1: offsets.l, + y1: y, + x2: dim.width - offsets.r, + y2: y + }).setStroke(ta.minorTick); + if(this.animate){ + this._animateGrid(hMinorLine, "h", offsets.l, offsets.r + offsets.l - dim.width); + } + }, this); + } + if(this.opt.hMajorLines){ + arr.forEach(ticks.major, function(tick){ + var y = dim.height - offsets.b - vt(tick.value); + var hMajorLine = this.createLine(s, { + x1: offsets.l, + y1: y, + x2: dim.width - offsets.r, + y2: y + }).setStroke(ta.majorTick); + if(this.animate){ + this._animateGrid(hMajorLine, "h", offsets.l, offsets.r + offsets.l - dim.width); + } + }, this); + } + } + }catch(e){ + // squelch + } + // draw vertical stripes and lines + try{ + var hScaler = this._hAxis.getScaler(), + ht = hScaler.scaler.getTransformerFromModel(hScaler), + ticks = this._hAxis.getTicks(); + if(this != null){ + if(ticks && this.opt.vMinorLines){ + arr.forEach(ticks.minor, function(tick){ + var x = offsets.l + ht(tick.value); + var vMinorLine = this.createLine(s, { + x1: x, + y1: offsets.t, + x2: x, + y2: dim.height - offsets.b + }).setStroke(ta.minorTick); + if(this.animate){ + this._animateGrid(vMinorLine, "v", dim.height - offsets.b, dim.height - offsets.b - offsets.t); + } + }, this); + } + if(ticks && this.opt.vMajorLines){ + arr.forEach(ticks.major, function(tick){ + var x = offsets.l + ht(tick.value); + var vMajorLine = this.createLine(s, { + x1: x, + y1: offsets.t, + x2: x, + y2: dim.height - offsets.b + }).setStroke(ta.majorTick); + if(this.animate){ + this._animateGrid(vMajorLine, "v", dim.height - offsets.b, dim.height - offsets.b - offsets.t); + } + }, this); + } + } + }catch(e){ + // squelch + } + this.dirty = false; + return this; // dojox.charting.plot2d.Grid + }, + _animateGrid: function(shape, type, offset, size){ + var transStart = type == "h" ? [offset, 0] : [0, offset]; + var scaleStart = type == "h" ? [1/size, 1] : [1, 1/size]; + fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform: [ + {name: "translate", start: transStart, end: [0, 0]}, + {name: "scale", start: scaleStart, end: [1, 1]}, + {name: "original"} + ] + }, this.animate)).play(); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Lines.js b/js/dojo/dojox/charting/plot2d/Lines.js new file mode 100644 index 0000000..3791acf --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Lines.js @@ -0,0 +1,15 @@ +//>>built +define("dojox/charting/plot2d/Lines", ["dojo/_base/declare", "./Default"], function(declare, Default){ +/*===== +var Default = dojox.charting.plot2d.Default; +=====*/ + return declare("dojox.charting.plot2d.Lines", Default, { + // summary: + // A convenience constructor to create a typical line chart. + constructor: function(){ + // summary: + // Preset our default plot to be line-based. + this.opt.lines = true; + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Markers.js b/js/dojo/dojox/charting/plot2d/Markers.js new file mode 100644 index 0000000..e06d0f6 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Markers.js @@ -0,0 +1,15 @@ +//>>built +define("dojox/charting/plot2d/Markers", ["dojo/_base/declare", "./Default"], function(declare, Default){ +/*===== +var Default = dojox.charting.plot2d.Default +=====*/ + return declare("dojox.charting.plot2d.Markers", Default, { + // summary: + // A convenience plot to draw a line chart with markers. + constructor: function(){ + // summary: + // Set up the plot for lines and markers. + this.opt.markers = true; + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/MarkersOnly.js b/js/dojo/dojox/charting/plot2d/MarkersOnly.js new file mode 100644 index 0000000..c7fe493 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/MarkersOnly.js @@ -0,0 +1,16 @@ +//>>built +define("dojox/charting/plot2d/MarkersOnly", ["dojo/_base/declare", "./Default"], function(declare, Default){ +/*===== +var Default = dojox.charting.plot2d.Default; +=====*/ + return declare("dojox.charting.plot2d.MarkersOnly", Default, { + // summary: + // A convenience object to draw only markers (like a scatter but not quite). + constructor: function(){ + // summary: + // Set up our default plot to only have markers and no lines. + this.opt.lines = false; + this.opt.markers = true; + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/OHLC.js b/js/dojo/dojox/charting/plot2d/OHLC.js new file mode 100644 index 0000000..264ea29 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/OHLC.js @@ -0,0 +1,214 @@ +//>>built +define("dojox/charting/plot2d/OHLC", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Base", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils", "dojox/gfx/fx"], + function(lang, arr, declare, Base, dc, df, dfr, du, fx){ +/*===== +var Base = dojox.charting.plot2d.Base; +=====*/ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + // Candlesticks are based on the Bars plot type; we expect the following passed + // as values in a series: + // { x?, open, close, high, low } + // if x is not provided, the array index is used. + // failing to provide the OHLC values will throw an error. + return declare("dojox.charting.plot2d.OHLC", Base, { + // summary: + // A plot that represents typical open/high/low/close (financial reporting, primarily). + // Unlike most charts, the Candlestick expects data points to be represented by + // an object of the form { x?, open, close, high, low, mid? }, where both + // x and mid are optional parameters. If x is not provided, the index of the + // data array is used. + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + gap: 2, // gap between columns in pixels + animate: null // animate chart to place + }, + optionalParams: { + minBarSize: 1, // minimal bar size in pixels + maxBarSize: 1, // maximal bar size in pixels + // theme component + stroke: {}, + outline: {}, + shadow: {}, + fill: {}, + font: "", + fontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // The constructor for a candlestick chart. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__BarCtorArgs? + // An optional keyword arguments object to help define the plot. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + this.animate = this.opt.animate; + }, + + collectStats: function(series){ + // summary: + // Collect all statistics for drawing this chart. Since the common + // functionality only assumes x and y, OHLC must create it's own + // stats (since data has no y value, but open/close/high/low instead). + // series: dojox.charting.Series[] + // The data series array to be drawn on this plot. + // returns: Object + // Returns an object in the form of { hmin, hmax, vmin, vmax }. + + // we have to roll our own, since we need to use all four passed + // values to figure out our stats, and common only assumes x and y. + var stats = lang.delegate(dc.defaultStats); + for(var i=0; i<series.length; i++){ + var run = series[i]; + if(!run.data.length){ continue; } + var old_vmin = stats.vmin, old_vmax = stats.vmax; + if(!("ymin" in run) || !("ymax" in run)){ + arr.forEach(run.data, function(val, idx){ + if(val !== null){ + var x = val.x || idx + 1; + stats.hmin = Math.min(stats.hmin, x); + stats.hmax = Math.max(stats.hmax, x); + stats.vmin = Math.min(stats.vmin, val.open, val.close, val.high, val.low); + stats.vmax = Math.max(stats.vmax, val.open, val.close, val.high, val.low); + } + }); + } + if("ymin" in run){ stats.vmin = Math.min(old_vmin, run.ymin); } + if("ymax" in run){ stats.vmax = Math.max(old_vmax, run.ymax); } + } + return stats; + }, + + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + var stats = this.collectStats(this.series); + stats.hmin -= 0.5; + stats.hmax += 0.5; + return stats; + }, + + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.OHLC + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, width, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + baseline = Math.max(0, this._vScaler.bounds.lower), + baselineHeight = vt(baseline), + events = this.events(); + f = dc.calculateBarSize(this._hScaler.bounds.scale, this.opt); + gap = f.gap; + width = f.size; + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + var theme = t.next("candlestick", [this.opt, run]), s = run.group, + eventSeries = new Array(run.data.length); + for(var j = 0; j < run.data.length; ++j){ + var v = run.data[j]; + if(v !== null){ + var finalTheme = t.addMixin(theme, "candlestick", v, true); + + // calculate the points we need for OHLC + var x = ht(v.x || (j+0.5)) + offsets.l + gap, + y = dim.height - offsets.b, + open = vt(v.open), + close = vt(v.close), + high = vt(v.high), + low = vt(v.low); + if(low > high){ + var tmp = high; + high = low; + low = tmp; + } + + if(width >= 1){ + var hl = {x1: width/2, x2: width/2, y1: y - high, y2: y - low}, + op = {x1: 0, x2: ((width/2) + ((finalTheme.series.stroke.width||1)/2)), y1: y-open, y2: y-open}, + cl = {x1: ((width/2) - ((finalTheme.series.stroke.width||1)/2)), x2: width, y1: y-close, y2: y-close}; + var shape = s.createGroup(); + shape.setTransform({dx: x, dy: 0}); + var inner = shape.createGroup(); + inner.createLine(hl).setStroke(finalTheme.series.stroke); + inner.createLine(op).setStroke(finalTheme.series.stroke); + inner.createLine(cl).setStroke(finalTheme.series.stroke); + + // TODO: double check this. + run.dyn.stroke = finalTheme.series.stroke; + if(events){ + var o = { + element: "candlestick", + index: j, + run: run, + shape: inner, + x: x, + y: y-Math.max(open, close), + cx: width/2, + cy: (y-Math.max(open, close)) + (Math.max(open > close ? open-close : close-open, 1)/2), + width: width, + height: Math.max(open > close ? open-close : close-open, 1), + data: v + }; + this._connectEvents(o); + eventSeries[j] = o; + } + } + if(this.animate){ + this._animateOHLC(shape, y - low, high - low); + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.OHLC + }, + _animateOHLC: function(shape, voffset, vsize){ + fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform: [ + {name: "translate", start: [0, voffset - (voffset/vsize)], end: [0, 0]}, + {name: "scale", start: [1, 1/vsize], end: [1, 1]}, + {name: "original"} + ] + }, this.animate)).play(); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Pie.js b/js/dojo/dojox/charting/plot2d/Pie.js new file mode 100644 index 0000000..01e07b1 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Pie.js @@ -0,0 +1,491 @@ +//>>built +define("dojox/charting/plot2d/Pie", ["dojo/_base/lang", "dojo/_base/array" ,"dojo/_base/declare", + "../Element", "./_PlotEvents", "./common", "../axis2d/common", + "dojox/gfx", "dojox/gfx/matrix", "dojox/lang/functional", "dojox/lang/utils"], + function(lang, arr, declare, Element, PlotEvents, dc, da, g, m, df, du){ + + /*===== + var Element = dojox.charting.Element; + var PlotEvents = dojox.charting.plot2d._PlotEvents; + dojo.declare("dojox.charting.plot2d.__PieCtorArgs", dojox.charting.plot2d.__DefaultCtorArgs, { + // summary: + // Specialized keyword arguments object for use in defining parameters on a Pie chart. + + // labels: Boolean? + // Whether or not to draw labels for each pie slice. Default is true. + labels: true, + + // ticks: Boolean? + // Whether or not to draw ticks to labels within each slice. Default is false. + ticks: false, + + // fixed: Boolean? + // TODO + fixed: true, + + // precision: Number? + // The precision at which to sum/add data values. Default is 1. + precision: 1, + + // labelOffset: Number? + // The amount in pixels by which to offset labels. Default is 20. + labelOffset: 20, + + // labelStyle: String? + // Options as to where to draw labels. Values include "default", and "columns". Default is "default". + labelStyle: "default", // default/columns + + // htmlLabels: Boolean? + // Whether or not to use HTML to render slice labels. Default is true. + htmlLabels: true, + + // radGrad: String? + // The type of radial gradient to use in rendering. Default is "native". + radGrad: "native", + + // fanSize: Number? + // The amount for a radial gradient. Default is 5. + fanSize: 5, + + // startAngle: Number? + // Where to being rendering gradients in slices, in degrees. Default is 0. + startAngle: 0, + + // radius: Number? + // The size of the radial gradient. Default is 0. + radius: 0 + }); + =====*/ + + var FUDGE_FACTOR = 0.2; // use to overlap fans + + return declare("dojox.charting.plot2d.Pie", [Element, PlotEvents], { + // summary: + // The plot that represents a typical pie chart. + defaultParams: { + labels: true, + ticks: false, + fixed: true, + precision: 1, + labelOffset: 20, + labelStyle: "default", // default/columns + htmlLabels: true, // use HTML to draw labels + radGrad: "native", // or "linear", or "fan" + fanSize: 5, // maximum fan size in degrees + startAngle: 0 // start angle for slices in degrees + }, + optionalParams: { + radius: 0, + // theme components + stroke: {}, + outline: {}, + shadow: {}, + fill: {}, + font: "", + fontColor: "", + labelWiring: {} + }, + + constructor: function(chart, kwArgs){ + // summary: + // Create a pie plot. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.run = null; + this.dyn = []; + }, + clear: function(){ + // summary: + // Clear out all of the information tied to this plot. + // returns: dojox.charting.plot2d.Pie + // A reference to this plot for functional chaining. + this.dirty = true; + this.dyn = []; + this.run = null; + return this; // dojox.charting.plot2d.Pie + }, + setAxis: function(axis){ + // summary: + // Dummy method, since axes are irrelevant with a Pie chart. + // returns: dojox.charting.plot2d.Pie + // The reference to this plot for functional chaining. + return this; // dojox.charting.plot2d.Pie + }, + addSeries: function(run){ + // summary: + // Add a series of data to this plot. + // returns: dojox.charting.plot2d.Pie + // The reference to this plot for functional chaining. + this.run = run; + return this; // dojox.charting.plot2d.Pie + }, + getSeriesStats: function(){ + // summary: + // Returns default stats (irrelevant for this type of plot). + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + return lang.delegate(dc.defaultStats); + }, + initializeScalers: function(){ + // summary: + // Does nothing (irrelevant for this type of plot). + return this; + }, + getRequiredColors: function(){ + // summary: + // Return the number of colors needed to draw this plot. + return this.run ? this.run.data.length : 0; + }, + + render: function(dim, offsets){ + // summary: + // Render the plot on the chart. + // dim: Object + // An object of the form { width, height }. + // offsets: Object + // An object of the form { l, r, t, b }. + // returns: dojox.charting.plot2d.Pie + // A reference to this plot for functional chaining. + if(!this.dirty){ return this; } + this.resetEvents(); + this.dirty = false; + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group, t = this.chart.theme; + + if(!this.run || !this.run.data.length){ + return this; + } + + // calculate the geometry + var rx = (dim.width - offsets.l - offsets.r) / 2, + ry = (dim.height - offsets.t - offsets.b) / 2, + r = Math.min(rx, ry), + taFont = "font" in this.opt ? this.opt.font : t.axis.font, + size = taFont ? g.normalizedLength(g.splitFontString(taFont).size) : 0, + taFontColor = "fontColor" in this.opt ? this.opt.fontColor : t.axis.fontColor, + startAngle = m._degToRad(this.opt.startAngle), + start = startAngle, step, filteredRun, slices, labels, shift, labelR, + run = this.run.data, + events = this.events(); + if(typeof run[0] == "number"){ + filteredRun = df.map(run, "x ? Math.max(x, 0) : 0"); + if(df.every(filteredRun, "<= 0")){ + return this; + } + slices = df.map(filteredRun, "/this", df.foldl(filteredRun, "+", 0)); + if(this.opt.labels){ + labels = arr.map(slices, function(x){ + return x > 0 ? this._getLabel(x * 100) + "%" : ""; + }, this); + } + }else{ + filteredRun = df.map(run, "x ? Math.max(x.y, 0) : 0"); + if(df.every(filteredRun, "<= 0")){ + return this; + } + slices = df.map(filteredRun, "/this", df.foldl(filteredRun, "+", 0)); + if(this.opt.labels){ + labels = arr.map(slices, function(x, i){ + if(x <= 0){ return ""; } + var v = run[i]; + return "text" in v ? v.text : this._getLabel(x * 100) + "%"; + }, this); + } + } + var themes = df.map(run, function(v, i){ + if(v === null || typeof v == "number"){ + return t.next("slice", [this.opt, this.run], true); + } + return t.next("slice", [this.opt, this.run, v], true); + }, this); + if(this.opt.labels){ + shift = df.foldl1(df.map(labels, function(label, i){ + var font = themes[i].series.font; + return g._base._getTextBox(label, {font: font}).w; + }, this), "Math.max(a, b)") / 2; + if(this.opt.labelOffset < 0){ + r = Math.min(rx - 2 * shift, ry - size) + this.opt.labelOffset; + } + labelR = r - this.opt.labelOffset; + } + if("radius" in this.opt){ + r = this.opt.radius; + labelR = r - this.opt.labelOffset; + } + var circle = { + cx: offsets.l + rx, + cy: offsets.t + ry, + r: r + }; + + this.dyn = []; + // draw slices + var eventSeries = new Array(slices.length); + arr.some(slices, function(slice, i){ + if(slice < 0){ + // degenerated slice + return false; // continue + } + if(slice == 0){ + this.dyn.push({fill: null, stroke: null}); + return false; + } + var v = run[i], theme = themes[i], specialFill; + if(slice >= 1){ + // whole pie + specialFill = this._plotFill(theme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, + { + x: circle.cx - circle.r, y: circle.cy - circle.r, + width: 2 * circle.r, height: 2 * circle.r + }); + specialFill = this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, circle.r); + var shape = s.createCircle(circle).setFill(specialFill).setStroke(theme.series.stroke); + this.dyn.push({fill: specialFill, stroke: theme.series.stroke}); + + if(events){ + var o = { + element: "slice", + index: i, + run: this.run, + shape: shape, + x: i, + y: typeof v == "number" ? v : v.y, + cx: circle.cx, + cy: circle.cy, + cr: r + }; + this._connectEvents(o); + eventSeries[i] = o; + } + + return true; // stop iteration + } + // calculate the geometry of the slice + var end = start + slice * 2 * Math.PI; + if(i + 1 == slices.length){ + end = startAngle + 2 * Math.PI; + } + var step = end - start, + x1 = circle.cx + r * Math.cos(start), + y1 = circle.cy + r * Math.sin(start), + x2 = circle.cx + r * Math.cos(end), + y2 = circle.cy + r * Math.sin(end); + // draw the slice + var fanSize = m._degToRad(this.opt.fanSize); + if(theme.series.fill && theme.series.fill.type === "radial" && this.opt.radGrad === "fan" && step > fanSize){ + var group = s.createGroup(), nfans = Math.ceil(step / fanSize), delta = step / nfans; + specialFill = this._shapeFill(theme.series.fill, + {x: circle.cx - circle.r, y: circle.cy - circle.r, width: 2 * circle.r, height: 2 * circle.r}); + for(var j = 0; j < nfans; ++j){ + var fansx = j == 0 ? x1 : circle.cx + r * Math.cos(start + (j - FUDGE_FACTOR) * delta), + fansy = j == 0 ? y1 : circle.cy + r * Math.sin(start + (j - FUDGE_FACTOR) * delta), + fanex = j == nfans - 1 ? x2 : circle.cx + r * Math.cos(start + (j + 1 + FUDGE_FACTOR) * delta), + faney = j == nfans - 1 ? y2 : circle.cy + r * Math.sin(start + (j + 1 + FUDGE_FACTOR) * delta), + fan = group.createPath(). + moveTo(circle.cx, circle.cy). + lineTo(fansx, fansy). + arcTo(r, r, 0, delta > Math.PI, true, fanex, faney). + lineTo(circle.cx, circle.cy). + closePath(). + setFill(this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, r, start + (j + 0.5) * delta, start + (j + 0.5) * delta)); + } + group.createPath(). + moveTo(circle.cx, circle.cy). + lineTo(x1, y1). + arcTo(r, r, 0, step > Math.PI, true, x2, y2). + lineTo(circle.cx, circle.cy). + closePath(). + setStroke(theme.series.stroke); + shape = group; + }else{ + shape = s.createPath(). + moveTo(circle.cx, circle.cy). + lineTo(x1, y1). + arcTo(r, r, 0, step > Math.PI, true, x2, y2). + lineTo(circle.cx, circle.cy). + closePath(). + setStroke(theme.series.stroke); + var specialFill = theme.series.fill; + if(specialFill && specialFill.type === "radial"){ + specialFill = this._shapeFill(specialFill, {x: circle.cx - circle.r, y: circle.cy - circle.r, width: 2 * circle.r, height: 2 * circle.r}); + if(this.opt.radGrad === "linear"){ + specialFill = this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, r, start, end); + } + }else if(specialFill && specialFill.type === "linear"){ + specialFill = this._plotFill(specialFill, dim, offsets); + specialFill = this._shapeFill(specialFill, shape.getBoundingBox()); + } + shape.setFill(specialFill); + } + this.dyn.push({fill: specialFill, stroke: theme.series.stroke}); + + if(events){ + var o = { + element: "slice", + index: i, + run: this.run, + shape: shape, + x: i, + y: typeof v == "number" ? v : v.y, + cx: circle.cx, + cy: circle.cy, + cr: r + }; + this._connectEvents(o); + eventSeries[i] = o; + } + + start = end; + + return false; // continue + }, this); + // draw labels + if(this.opt.labels){ + if(this.opt.labelStyle == "default"){ + start = startAngle; + arr.some(slices, function(slice, i){ + if(slice <= 0){ + // degenerated slice + return false; // continue + } + var theme = themes[i]; + if(slice >= 1){ + // whole pie + var v = run[i], elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"]( + this.chart, s, circle.cx, circle.cy + size / 2, "middle", labels[i], + theme.series.font, theme.series.fontColor); + if(this.opt.htmlLabels){ + this.htmlElements.push(elem); + } + return true; // stop iteration + } + // calculate the geometry of the slice + var end = start + slice * 2 * Math.PI, v = run[i]; + if(i + 1 == slices.length){ + end = startAngle + 2 * Math.PI; + } + var labelAngle = (start + end) / 2, + x = circle.cx + labelR * Math.cos(labelAngle), + y = circle.cy + labelR * Math.sin(labelAngle) + size / 2; + // draw the label + var elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"] + (this.chart, s, x, y, "middle", labels[i], theme.series.font, theme.series.fontColor); + if(this.opt.htmlLabels){ + this.htmlElements.push(elem); + } + start = end; + return false; // continue + }, this); + }else if(this.opt.labelStyle == "columns"){ + start = startAngle; + //calculate label angles + var labeledSlices = []; + arr.forEach(slices, function(slice, i){ + var end = start + slice * 2 * Math.PI; + if(i + 1 == slices.length){ + end = startAngle + 2 * Math.PI; + } + var labelAngle = (start + end) / 2; + labeledSlices.push({ + angle: labelAngle, + left: Math.cos(labelAngle) < 0, + theme: themes[i], + index: i, + omit: end - start < 0.001 + }); + start = end; + }); + //calculate label radius to each slice + var labelHeight = g._base._getTextBox("a",{font:taFont}).h; + this._getProperLabelRadius(labeledSlices, labelHeight, circle.r * 1.1); + //draw label and wiring + arr.forEach(labeledSlices, function(slice, i){ + if (!slice.omit) { + var leftColumn = circle.cx - circle.r * 2, + rightColumn = circle.cx + circle.r * 2, + labelWidth = g._base._getTextBox(labels[i], {font: taFont}).w, + x = circle.cx + slice.labelR * Math.cos(slice.angle), + y = circle.cy + slice.labelR * Math.sin(slice.angle), + jointX = (slice.left) ? (leftColumn + labelWidth) : (rightColumn - labelWidth), + labelX = (slice.left) ? leftColumn : jointX; + var wiring = s.createPath().moveTo(circle.cx + circle.r * Math.cos(slice.angle), circle.cy + circle.r * Math.sin(slice.angle)) + if (Math.abs(slice.labelR * Math.cos(slice.angle)) < circle.r * 2 - labelWidth) { + wiring.lineTo(x, y); + } + wiring.lineTo(jointX, y).setStroke(slice.theme.series.labelWiring); + var elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"]( + this.chart, s, labelX, y, "left", labels[i], slice.theme.series.font, slice.theme.series.fontColor); + if (this.opt.htmlLabels) { + this.htmlElements.push(elem); + } + } + },this); + } + } + // post-process events to restore the original indexing + var esi = 0; + this._eventSeries[this.run.name] = df.map(run, function(v){ + return v <= 0 ? null : eventSeries[esi++]; + }); + return this; // dojox.charting.plot2d.Pie + }, + + _getProperLabelRadius: function(slices, labelHeight, minRidius){ + var leftCenterSlice = {},rightCenterSlice = {},leftMinSIN = 1, rightMinSIN = 1; + if (slices.length == 1) { + slices[0].labelR = minRidius; + return; + } + for(var i = 0;i<slices.length;i++){ + var tempSIN = Math.abs(Math.sin(slices[i].angle)); + if(slices[i].left){ + if(leftMinSIN > tempSIN){ + leftMinSIN = tempSIN; + leftCenterSlice = slices[i]; + } + }else{ + if(rightMinSIN > tempSIN){ + rightMinSIN = tempSIN; + rightCenterSlice = slices[i]; + } + } + } + leftCenterSlice.labelR = rightCenterSlice.labelR = minRidius; + this._calculateLabelR(leftCenterSlice,slices,labelHeight); + this._calculateLabelR(rightCenterSlice,slices,labelHeight); + }, + _calculateLabelR: function(firstSlice,slices,labelHeight){ + var i = firstSlice.index,length = slices.length, + currentLabelR = firstSlice.labelR; + while(!(slices[i%length].left ^ slices[(i+1)%length].left)){ + if (!slices[(i + 1) % length].omit) { + var nextLabelR = (Math.sin(slices[i % length].angle) * currentLabelR + ((slices[i % length].left) ? (-labelHeight) : labelHeight)) / + Math.sin(slices[(i + 1) % length].angle); + currentLabelR = (nextLabelR < firstSlice.labelR) ? firstSlice.labelR : nextLabelR; + slices[(i + 1) % length].labelR = currentLabelR; + } + i++; + } + i = firstSlice.index; + var j = (i == 0)?length-1 : i - 1; + while(!(slices[i].left ^ slices[j].left)){ + if (!slices[j].omit) { + var nextLabelR = (Math.sin(slices[i].angle) * currentLabelR + ((slices[i].left) ? labelHeight : (-labelHeight))) / + Math.sin(slices[j].angle); + currentLabelR = (nextLabelR < firstSlice.labelR) ? firstSlice.labelR : nextLabelR; + slices[j].labelR = currentLabelR; + } + i--;j--; + i = (i < 0)?i+slices.length:i; + j = (j < 0)?j+slices.length:j; + } + }, + // utilities + _getLabel: function(number){ + return dc.getLabel(number, this.opt.fixed, this.opt.precision); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Scatter.js b/js/dojo/dojox/charting/plot2d/Scatter.js new file mode 100644 index 0000000..7f6f68c --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Scatter.js @@ -0,0 +1,189 @@ +//>>built +define("dojox/charting/plot2d/Scatter", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Base", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils", "dojox/gfx/fx", "dojox/gfx/gradutils"], + function(lang, arr, declare, Base, dc, df, dfr, du, fx, gradutils){ +/*===== +var Base = dojox.charting.plot2d.Base; +=====*/ + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + return declare("dojox.charting.plot2d.Scatter", Base, { + // summary: + // A plot object representing a typical scatter chart. + defaultParams: { + hAxis: "x", // use a horizontal axis named "x" + vAxis: "y", // use a vertical axis named "y" + shadows: null, // draw shadows + animate: null // animate chart to place + }, + optionalParams: { + // theme component + markerStroke: {}, + markerOutline: {}, + markerShadow: {}, + markerFill: {}, + markerFont: "", + markerFontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // Create the scatter plot. + // chart: dojox.charting.Chart + // The chart this plot belongs to. + // kwArgs: dojox.charting.plot2d.__DefaultCtorArgs? + // An optional keyword arguments object to help define this plot's parameters. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.hAxis = this.opt.hAxis; + this.vAxis = this.opt.vAxis; + this.animate = this.opt.animate; + }, + + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.Scatter + // A reference to this plot for functional chaining. + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, events = this.events(); + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + if(!run.data.length){ + run.dirty = false; + t.skip(); + continue; + } + + var theme = t.next("marker", [this.opt, run]), s = run.group, lpoly, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler); + if(typeof run.data[0] == "number"){ + lpoly = arr.map(run.data, function(v, i){ + return { + x: ht(i + 1) + offsets.l, + y: dim.height - offsets.b - vt(v) + }; + }, this); + }else{ + lpoly = arr.map(run.data, function(v, i){ + return { + x: ht(v.x) + offsets.l, + y: dim.height - offsets.b - vt(v.y) + }; + }, this); + } + + var shadowMarkers = new Array(lpoly.length), + frontMarkers = new Array(lpoly.length), + outlineMarkers = new Array(lpoly.length); + + arr.forEach(lpoly, function(c, i){ + var finalTheme = typeof run.data[i] == "number" ? + t.post(theme, "marker") : + t.addMixin(theme, "marker", run.data[i], true), + path = "M" + c.x + " " + c.y + " " + finalTheme.symbol; + if(finalTheme.marker.shadow){ + shadowMarkers[i] = s.createPath("M" + (c.x + finalTheme.marker.shadow.dx) + " " + + (c.y + finalTheme.marker.shadow.dy) + " " + finalTheme.symbol). + setStroke(finalTheme.marker.shadow).setFill(finalTheme.marker.shadow.color); + if(this.animate){ + this._animateScatter(shadowMarkers[i], dim.height - offsets.b); + } + } + if(finalTheme.marker.outline){ + var outline = dc.makeStroke(finalTheme.marker.outline); + outline.width = 2 * outline.width + finalTheme.marker.stroke.width; + outlineMarkers[i] = s.createPath(path).setStroke(outline); + if(this.animate){ + this._animateScatter(outlineMarkers[i], dim.height - offsets.b); + } + } + var stroke = dc.makeStroke(finalTheme.marker.stroke), + fill = this._plotFill(finalTheme.marker.fill, dim, offsets); + if(fill && (fill.type === "linear" || fill.type == "radial")){ + var color = gradutils.getColor(fill, {x: c.x, y: c.y}); + if(stroke){ + stroke.color = color; + } + frontMarkers[i] = s.createPath(path).setStroke(stroke).setFill(color); + }else{ + frontMarkers[i] = s.createPath(path).setStroke(stroke).setFill(fill); + } + if(this.animate){ + this._animateScatter(frontMarkers[i], dim.height - offsets.b); + } + }, this); + if(frontMarkers.length){ + run.dyn.stroke = frontMarkers[frontMarkers.length - 1].getStroke(); + run.dyn.fill = frontMarkers[frontMarkers.length - 1].getFill(); + } + + if(events){ + var eventSeries = new Array(frontMarkers.length); + arr.forEach(frontMarkers, function(s, i){ + var o = { + element: "marker", + index: i, + run: run, + shape: s, + outline: outlineMarkers && outlineMarkers[i] || null, + shadow: shadowMarkers && shadowMarkers[i] || null, + cx: lpoly[i].x, + cy: lpoly[i].y + }; + if(typeof run.data[0] == "number"){ + o.x = i + 1; + o.y = run.data[i]; + }else{ + o.x = run.data[i].x; + o.y = run.data[i].y; + } + this._connectEvents(o); + eventSeries[i] = o; + }, this); + this._eventSeries[run.name] = eventSeries; + }else{ + delete this._eventSeries[run.name]; + } + run.dirty = false; + } + this.dirty = false; + return this; // dojox.charting.plot2d.Scatter + }, + _animateScatter: function(shape, offset){ + fx.animateTransform(lang.delegate({ + shape: shape, + duration: 1200, + transform: [ + {name: "translate", start: [0, offset], end: [0, 0]}, + {name: "scale", start: [0, 0], end: [1, 1]}, + {name: "original"} + ] + }, this.animate)).play(); + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/Spider.js b/js/dojo/dojox/charting/plot2d/Spider.js new file mode 100644 index 0000000..a5a67b0 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Spider.js @@ -0,0 +1,632 @@ +//>>built +define("dojox/charting/plot2d/Spider", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/connect", "dojo/_base/html", "dojo/_base/array", + "dojo/dom-geometry", "dojo/_base/fx", "dojo/fx", "dojo/_base/sniff", + "../Element", "./_PlotEvents", "dojo/_base/Color", "dojox/color/_base", "./common", "../axis2d/common", + "../scaler/primitive", "dojox/gfx", "dojox/gfx/matrix", "dojox/gfx/fx", "dojox/lang/functional", + "dojox/lang/utils", "dojo/fx/easing"], + function(lang, declare, hub, html, arr, domGeom, baseFx, coreFx, has, + Element, PlotEvents, Color, dxcolor, dc, da, primitive, + g, m, gfxfx, df, du, easing){ +/*===== +var Element = dojox.charting.Element; +var PlotEvents = dojox.charting.plot2d._PlotEvents; +=====*/ + var FUDGE_FACTOR = 0.2; // use to overlap fans + + var Spider = declare("dojox.charting.plot2d.Spider", [Element, PlotEvents], { + // summary: + // The plot that represents a typical Spider chart. + defaultParams: { + labels: true, + ticks: false, + fixed: true, + precision: 1, + labelOffset: -10, + labelStyle: "default", // default/rows/auto + htmlLabels: true, // use HTML to draw labels + startAngle: -90, // start angle for slices in degrees + divisions: 3, // radius tick count + axisColor: "", // spider axis color + axisWidth: 0, // spider axis stroke width + spiderColor: "", // spider web color + spiderWidth: 0, // spider web stroke width + seriesWidth: 0, // plot border with + seriesFillAlpha: 0.2, // plot fill alpha + spiderOrigin: 0.16, + markerSize: 3, // radius of plot vertex (px) + spiderType: "polygon", //"circle" + animationType: easing.backOut, + axisTickFont: "", + axisTickFontColor: "", + axisFont: "", + axisFontColor: "" + }, + optionalParams: { + radius: 0, + font: "", + fontColor: "" + }, + + constructor: function(chart, kwArgs){ + // summary: + // Create a Spider plot. + this.opt = lang.clone(this.defaultParams); + du.updateWithObject(this.opt, kwArgs); + du.updateWithPattern(this.opt, kwArgs, this.optionalParams); + this.series = []; + this.dyn = []; + this.datas = {}; + this.labelKey = []; + this.oldSeriePoints = {}; + this.animations = {}; + }, + clear: function(){ + // summary: + // Clear out all of the information tied to this plot. + // returns: dojox.charting.plot2d.Spider + // A reference to this plot for functional chaining. + this.dirty = true; + this.dyn = []; + this.series = []; + this.datas = {}; + this.labelKey = []; + this.oldSeriePoints = {}; + this.animations = {}; + return this; // dojox.charting.plot2d.Spider + }, + setAxis: function(axis){ + // summary: + // Dummy method, since axes are irrelevant with a Spider chart. + // returns: dojox.charting.plot2d.Spider + // The reference to this plot for functional chaining. + return this; // dojox.charting.plot2d.Spider + }, + addSeries: function(run){ + // summary: + // Add a data series to this plot. + // run: dojox.charting.Series + // The series to be added. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + var matched = false; + this.series.push(run); + for(var key in run.data){ + var val = run.data[key], + data = this.datas[key]; + if(data){ + data.vlist.push(val); + data.min = Math.min(data.min, val); + data.max = Math.max(data.max, val); + }else{ + this.datas[key] = {min: val, max: val, vlist: [val]}; + } + } + if (this.labelKey.length <= 0) { + for (var key in run.data) { + this.labelKey.push(key); + } + } + return this; // dojox.charting.plot2d.Base + }, + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + return dc.collectSimpleStats(this.series); + }, + calculateAxes: function(dim){ + // summary: + // Stub function for running the axis calculations (depricated). + // dim: Object + // An object of the form { width, height } + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + this.initializeScalers(dim, this.getSeriesStats()); + return this; // dojox.charting.plot2d.Base + }, + getRequiredColors: function(){ + // summary: + // Get how many data series we have, so we know how many colors to use. + // returns: Number + // The number of colors needed. + return this.series.length; // Number + }, + initializeScalers: function(dim, stats){ + // summary: + // Initializes scalers using attached axes. + // dim: Object: + // Size of a plot area in pixels as {width, height}. + // stats: Object: + // Min/max of data in both directions as {hmin, hmax, vmin, vmax}. + // returns: dojox.charting.plot2d.Base + // A reference to this plot for functional chaining. + if(this._hAxis){ + if(!this._hAxis.initialized()){ + this._hAxis.calculate(stats.hmin, stats.hmax, dim.width); + } + this._hScaler = this._hAxis.getScaler(); + }else{ + this._hScaler = primitive.buildScaler(stats.hmin, stats.hmax, dim.width); + } + if(this._vAxis){ + if(!this._vAxis.initialized()){ + this._vAxis.calculate(stats.vmin, stats.vmax, dim.height); + } + this._vScaler = this._vAxis.getScaler(); + }else{ + this._vScaler = primitive.buildScaler(stats.vmin, stats.vmax, dim.height); + } + return this; // dojox.charting.plot2d.Base + }, + render: function(dim, offsets){ + // summary: + // Render the plot on the chart. + // dim: Object + // An object of the form { width, height }. + // offsets: Object + // An object of the form { l, r, t, b }. + // returns: dojox.charting.plot2d.Spider + // A reference to this plot for functional chaining. + if(!this.dirty){ return this; } + this.dirty = false; + this.cleanGroup(); + var s = this.group, t = this.chart.theme; + this.resetEvents(); + + if(!this.series || !this.series.length){ + return this; + } + + // calculate the geometry + var o = this.opt, ta = t.axis, + rx = (dim.width - offsets.l - offsets.r) / 2, + ry = (dim.height - offsets.t - offsets.b) / 2, + r = Math.min(rx, ry), + axisTickFont = o.font || (ta.majorTick && ta.majorTick.font) || (ta.tick && ta.tick.font) || "normal normal normal 7pt Tahoma", + axisFont = o.axisFont || (ta.tick && ta.tick.titleFont) || "normal normal normal 11pt Tahoma", + axisTickFontColor = o.axisTickFontColor || (ta.majorTick && ta.majorTick.fontColor) || (ta.tick && ta.tick.fontColor) || "silver", + axisFontColor = o.axisFontColor || (ta.tick && ta.tick.titleFontColor) || "black", + axisColor = o.axisColor || (ta.tick && ta.tick.axisColor) || "silver", + spiderColor = o.spiderColor || (ta.tick && ta.tick.spiderColor) || "silver", + axisWidth = o.axisWidth || (ta.stroke && ta.stroke.width) || 2, + spiderWidth = o.spiderWidth || (ta.stroke && ta.stroke.width) || 2, + seriesWidth = o.seriesWidth || (ta.stroke && ta.stroke.width) || 2, + asize = g.normalizedLength(g.splitFontString(axisFont).size), + startAngle = m._degToRad(o.startAngle), + start = startAngle, step, filteredRun, slices, labels, shift, labelR, + outerPoints, innerPoints, divisionPoints, divisionRadius, labelPoints, + ro = o.spiderOrigin, dv = o.divisions >= 3 ? o.divisions : 3, ms = o.markerSize, + spt = o.spiderType, at = o.animationType, lboffset = o.labelOffset < -10 ? o.labelOffset : -10, + axisExtra = 0.2; + + if(o.labels){ + labels = arr.map(this.series, function(s){ + return s.name; + }, this); + shift = df.foldl1(df.map(labels, function(label, i){ + var font = t.series.font; + return g._base._getTextBox(label, { + font: font + }).w; + }, this), "Math.max(a, b)") / 2; + r = Math.min(rx - 2 * shift, ry - asize) + lboffset; + labelR = r - lboffset; + } + if ("radius" in o) { + r = o.radius; + labelR = r - lboffset; + } + r /= (1+axisExtra); + var circle = { + cx: offsets.l + rx, + cy: offsets.t + ry, + r: r + }; + + for (var i = this.series.length - 1; i >= 0; i--) { + var serieEntry = this.series[i]; + if (!this.dirty && !serieEntry.dirty) { + t.skip(); + continue; + } + serieEntry.cleanGroup(); + var run = serieEntry.data; + if (run !== null) { + var len = this._getObjectLength(run); + //construct connect points + if (!outerPoints || outerPoints.length <= 0) { + outerPoints = [], innerPoints = [], labelPoints = []; + this._buildPoints(outerPoints, len, circle, r, start, true); + this._buildPoints(innerPoints, len, circle, r*ro, start, true); + this._buildPoints(labelPoints, len, circle, labelR, start); + if(dv > 2){ + divisionPoints = [], divisionRadius = []; + for (var j = 0; j < dv - 2; j++) { + divisionPoints[j] = []; + this._buildPoints(divisionPoints[j], len, circle, r*(ro + (1-ro)*(j+1)/(dv-1)), start, true); + divisionRadius[j] = r*(ro + (1-ro)*(j+1)/(dv-1)); + } + } + } + } + } + + //draw Spider + //axis + var axisGroup = s.createGroup(), axisStroke = {color: axisColor, width: axisWidth}, + spiderStroke = {color: spiderColor, width: spiderWidth}; + for (var j = outerPoints.length - 1; j >= 0; --j) { + var point = outerPoints[j], + st = { + x: point.x + (point.x - circle.cx) * axisExtra, + y: point.y + (point.y - circle.cy) * axisExtra + }, + nd = { + x: point.x + (point.x - circle.cx) * axisExtra / 2, + y: point.y + (point.y - circle.cy) * axisExtra / 2 + }; + axisGroup.createLine({ + x1: circle.cx, + y1: circle.cy, + x2: st.x, + y2: st.y + }).setStroke(axisStroke); + //arrow + this._drawArrow(axisGroup, st, nd, axisStroke); + } + + // draw the label + var labelGroup = s.createGroup(); + for (var j = labelPoints.length - 1; j >= 0; --j) { + var point = labelPoints[j], + fontWidth = g._base._getTextBox(this.labelKey[j], {font: axisFont}).w || 0, + render = this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx", + elem = da.createText[render](this.chart, labelGroup, (!domGeom.isBodyLtr() && render == "html") ? (point.x + fontWidth - dim.width) : point.x, point.y, + "middle", this.labelKey[j], axisFont, axisFontColor); + if (this.opt.htmlLabels) { + this.htmlElements.push(elem); + } + } + + //spider web: polygon or circle + var spiderGroup = s.createGroup(); + if(spt == "polygon"){ + spiderGroup.createPolyline(outerPoints).setStroke(spiderStroke); + spiderGroup.createPolyline(innerPoints).setStroke(spiderStroke); + if (divisionPoints.length > 0) { + for (var j = divisionPoints.length - 1; j >= 0; --j) { + spiderGroup.createPolyline(divisionPoints[j]).setStroke(spiderStroke); + } + } + }else{//circle + var ccount = this._getObjectLength(this.datas); + spiderGroup.createCircle({cx: circle.cx, cy: circle.cy, r: r}).setStroke(spiderStroke); + spiderGroup.createCircle({cx: circle.cx, cy: circle.cy, r: r*ro}).setStroke(spiderStroke); + if (divisionRadius.length > 0) { + for (var j = divisionRadius.length - 1; j >= 0; --j) { + spiderGroup.createCircle({cx: circle.cx, cy: circle.cy, r: divisionRadius[j]}).setStroke(spiderStroke); + } + } + } + //text + var textGroup = s.createGroup(), len = this._getObjectLength(this.datas), k = 0; + for(var key in this.datas){ + var data = this.datas[key], min = data.min, max = data.max, distance = max - min, + end = start + 2 * Math.PI * k / len; + for (var i = 0; i < dv; i++) { + var text = min + distance*i/(dv-1), point = this._getCoordinate(circle, r*(ro + (1-ro)*i/(dv-1)), end); + text = this._getLabel(text); + var fontWidth = g._base._getTextBox(text, {font: axisTickFont}).w || 0, + render = this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"; + if (this.opt.htmlLabels) { + this.htmlElements.push(da.createText[render] + (this.chart, textGroup, (!domGeom.isBodyLtr() && render == "html") ? (point.x + fontWidth - dim.width) : point.x, point.y, + "start", text, axisTickFont, axisTickFontColor)); + } + } + k++; + } + + //draw series (animation) + this.chart.seriesShapes = {}; + var animationConnections = []; + for (var i = this.series.length - 1; i >= 0; i--) { + var serieEntry = this.series[i], run = serieEntry.data; + if (run !== null) { + //series polygon + var seriePoints = [], k = 0, tipData = []; + for(var key in run){ + var data = this.datas[key], min = data.min, max = data.max, distance = max - min, + entry = run[key], end = start + 2 * Math.PI * k / len, + point = this._getCoordinate(circle, r*(ro + (1-ro)*(entry-min)/distance), end); + seriePoints.push(point); + tipData.push({sname: serieEntry.name, key: key, data: entry}); + k++; + } + seriePoints[seriePoints.length] = seriePoints[0]; + tipData[tipData.length] = tipData[0]; + var polygonBoundRect = this._getBoundary(seriePoints), + theme = t.next("spider", [o, serieEntry]), ts = serieEntry.group, + f = g.normalizeColor(theme.series.fill), sk = {color: theme.series.fill, width: seriesWidth}; + f.a = o.seriesFillAlpha; + serieEntry.dyn = {fill: f, stroke: sk}; + + var osps = this.oldSeriePoints[serieEntry.name]; + var cs = this._createSeriesEntry(ts, (osps || innerPoints), seriePoints, f, sk, r, ro, ms, at); + this.chart.seriesShapes[serieEntry.name] = cs; + this.oldSeriePoints[serieEntry.name] = seriePoints; + + var po = { + element: "spider_poly", + index: i, + id: "spider_poly_"+serieEntry.name, + run: serieEntry, + plot: this, + shape: cs.poly, + parent: ts, + brect: polygonBoundRect, + cx: circle.cx, + cy: circle.cy, + cr: r, + f: f, + s: s + }; + this._connectEvents(po); + + var so = { + element: "spider_plot", + index: i, + id: "spider_plot_"+serieEntry.name, + run: serieEntry, + plot: this, + shape: serieEntry.group + }; + this._connectEvents(so); + + arr.forEach(cs.circles, function(c, i){ + var shape = c.getShape(), + co = { + element: "spider_circle", + index: i, + id: "spider_circle_"+serieEntry.name+i, + run: serieEntry, + plot: this, + shape: c, + parent: ts, + tdata: tipData[i], + cx: seriePoints[i].x, + cy: seriePoints[i].y, + f: f, + s: s + }; + this._connectEvents(co); + }, this); + } + } + return this; // dojox.charting.plot2d.Spider + }, + _createSeriesEntry: function(ts, osps, sps, f, sk, r, ro, ms, at){ + //polygon + var spoly = ts.createPolyline(osps).setFill(f).setStroke(sk), scircle = []; + for (var j = 0; j < osps.length; j++) { + var point = osps[j], cr = ms; + var circle = ts.createCircle({cx: point.x, cy: point.y, r: cr}).setFill(f).setStroke(sk); + scircle.push(circle); + } + + var anims = arr.map(sps, function(np, j){ + // create animation + var sp = osps[j], + anim = new baseFx.Animation({ + duration: 1000, + easing: at, + curve: [sp.y, np.y] + }); + var spl = spoly, sc = scircle[j]; + hub.connect(anim, "onAnimate", function(y){ + //apply poly + var pshape = spl.getShape(); + pshape.points[j].y = y; + spl.setShape(pshape); + //apply circle + var cshape = sc.getShape(); + cshape.cy = y; + sc.setShape(cshape); + }); + return anim; + }); + + var anims1 = arr.map(sps, function(np, j){ + // create animation + var sp = osps[j], + anim = new baseFx.Animation({ + duration: 1000, + easing: at, + curve: [sp.x, np.x] + }); + var spl = spoly, sc = scircle[j]; + hub.connect(anim, "onAnimate", function(x){ + //apply poly + var pshape = spl.getShape(); + pshape.points[j].x = x; + spl.setShape(pshape); + //apply circle + var cshape = sc.getShape(); + cshape.cx = x; + sc.setShape(cshape); + }); + return anim; + }); + var masterAnimation = coreFx.combine(anims.concat(anims1)); //dojo.fx.chain(anims); + masterAnimation.play(); + return {group :ts, poly: spoly, circles: scircle}; + }, + plotEvent: function(o){ + // summary: + // Stub function for use by specific plots. + // o: Object + // An object intended to represent event parameters. + var runName = o.id ? o.id : "default", a; + if (runName in this.animations) { + a = this.animations[runName]; + a.anim && a.anim.stop(true); + } else { + a = this.animations[runName] = {}; + } + if(o.element == "spider_poly"){ + if(!a.color){ + var color = o.shape.getFill(); + if(!color || !(color instanceof Color)){ + return; + } + a.color = { + start: color, + end: transColor(color) + }; + } + var start = a.color.start, end = a.color.end; + if(o.type == "onmouseout"){ + // swap colors + var t = start; start = end; end = t; + } + a.anim = gfxfx.animateFill({ + shape: o.shape, + duration: 800, + easing: easing.backOut, + color: {start: start, end: end} + }); + a.anim.play(); + }else if(o.element == "spider_circle"){ + var init, scale, defaultScale = 1.5; + if(o.type == "onmouseover"){ + init = m.identity; + scale = defaultScale; + //show tooltip + var aroundRect = {type: "rect"}; + aroundRect.x = o.cx; + aroundRect.y = o.cy; + aroundRect.width = aroundRect.height = 1; + var lt = html.coords(this.chart.node, true); + aroundRect.x += lt.x; + aroundRect.y += lt.y; + aroundRect.x = Math.round(aroundRect.x); + aroundRect.y = Math.round(aroundRect.y); + aroundRect.width = Math.ceil(aroundRect.width); + aroundRect.height = Math.ceil(aroundRect.height); + this.aroundRect = aroundRect; + var position = ["after", "before"]; + dc.doIfLoaded("dijit/Tooltip", dojo.hitch(this, function(Tooltip){ + Tooltip.show(o.tdata.sname + "<br/>" + o.tdata.key + "<br/>" + o.tdata.data, this.aroundRect, position); + })); + }else{ + init = m.scaleAt(defaultScale, o.cx, o.cy); + scale = 1/defaultScale; + dc.doIfLoaded("dijit/Tooltip", dojo.hitch(this, function(Tooltip){ + this.aroundRect && Tooltip.hide(this.aroundRect); + })); + } + var cs = o.shape.getShape(), + init = m.scaleAt(defaultScale, cs.cx, cs.cy), + kwArgs = { + shape: o.shape, + duration: 200, + easing: easing.backOut, + transform: [ + {name: "scaleAt", start: [1, cs.cx, cs.cy], end: [scale, cs.cx, cs.cy]}, + init + ] + }; + a.anim = gfxfx.animateTransform(kwArgs); + a.anim.play(); + }else if(o.element == "spider_plot"){ + //dojo gfx function "moveToFront" not work in IE + if (o.type == "onmouseover" && !has("ie")) { + o.shape.moveToFront(); + } + } + }, + _getBoundary: function(points){ + var xmax = points[0].x, + xmin = points[0].x, + ymax = points[0].y, + ymin = points[0].y; + for(var i = 0; i < points.length; i++){ + var point = points[i]; + xmax = Math.max(point.x, xmax); + ymax = Math.max(point.y, ymax); + xmin = Math.min(point.x, xmin); + ymin = Math.min(point.y, ymin); + } + return { + x: xmin, + y: ymin, + width: xmax - xmin, + height: ymax - ymin + }; + }, + + _drawArrow: function(s, start, end, stroke){ + var len = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)), + sin = (end.y - start.y)/len, cos = (end.x - start.x)/len, + point2 = {x: end.x + (len/3)*(-sin), y: end.y + (len/3)*cos}, + point3 = {x: end.x + (len/3)*sin, y: end.y + (len/3)*(-cos)}; + s.createPolyline([start, point2, point3]).setFill(stroke.color).setStroke(stroke); + }, + + _buildPoints: function(points, count, circle, radius, angle, recursive){ + for (var i = 0; i < count; i++) { + var end = angle + 2 * Math.PI * i / count; + points.push(this._getCoordinate(circle, radius, end)); + } + if(recursive){ + points.push(this._getCoordinate(circle, radius, angle + 2 * Math.PI)); + } + }, + + _getCoordinate: function(circle, radius, angle){ + return { + x: circle.cx + radius * Math.cos(angle), + y: circle.cy + radius * Math.sin(angle) + } + }, + + _getObjectLength: function(obj){ + var count = 0; + if(lang.isObject(obj)){ + for(var key in obj){ + count++; + } + } + return count; + }, + + // utilities + _getLabel: function(number){ + return dc.getLabel(number, this.opt.fixed, this.opt.precision); + } + }); + + function transColor(color){ + var a = new dxcolor.Color(color), + x = a.toHsl(); + if(x.s == 0){ + x.l = x.l < 50 ? 100 : 0; + }else{ + x.s = 100; + if(x.l < 50){ + x.l = 75; + }else if(x.l > 75){ + x.l = 50; + }else{ + x.l = x.l - 50 > 75 - x.l ? + 50 : 75; + } + } + var color = dxcolor.fromHsl(x); + color.a = 0.7; + return color; + } + + return Spider; // dojox.plot2d.Spider +}); diff --git a/js/dojo/dojox/charting/plot2d/Stacked.js b/js/dojo/dojox/charting/plot2d/Stacked.js new file mode 100644 index 0000000..9a39f6b --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/Stacked.js @@ -0,0 +1,197 @@ +//>>built +define("dojox/charting/plot2d/Stacked", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", "./Default", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/functional/sequence"], + function(lang, declare, arr, Default, dc, df, dfr, dfs){ +/*===== +var Default = dojox.charting.plot2d.Default; +=====*/ + var purgeGroup = dfr.lambda("item.purgeGroup()"); + + return declare("dojox.charting.plot2d.Stacked", Default, { + // summary: + // Like the default plot, Stacked sets up lines, areas and markers + // in a stacked fashion (values on the y axis added to each other) + // as opposed to a direct one. + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + var stats = dc.collectStackedStats(this.series); + this._maxRunLength = stats.hmax; + return stats; + }, + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.Stacked + // A reference to this plot for functional chaining. + if(this._maxRunLength <= 0){ + return this; + } + + // stack all values + var acc = df.repeat(this._maxRunLength, "-> 0", 0); + for(var i = 0; i < this.series.length; ++i){ + var run = this.series[i]; + for(var j = 0; j < run.data.length; ++j){ + var v = run.data[j]; + if(v !== null){ + if(isNaN(v)){ v = 0; } + acc[j] += v; + } + } + } + // draw runs in backwards + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + + var t = this.chart.theme, events = this.events(), + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler); + + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + var theme = t.next(this.opt.areas ? "area" : "line", [this.opt, run], true), + s = run.group, outline, + lpoly = arr.map(acc, function(v, i){ + return { + x: ht(i + 1) + offsets.l, + y: dim.height - offsets.b - vt(v) + }; + }, this); + + var lpath = this.opt.tension ? dc.curve(lpoly, this.opt.tension) : ""; + + if(this.opt.areas){ + var apoly = lang.clone(lpoly); + if(this.opt.tension){ + var p=dc.curve(apoly, this.opt.tension); + p += " L" + lpoly[lpoly.length - 1].x + "," + (dim.height - offsets.b) + + " L" + lpoly[0].x + "," + (dim.height - offsets.b) + + " L" + lpoly[0].x + "," + lpoly[0].y; + run.dyn.fill = s.createPath(p).setFill(theme.series.fill).getFill(); + } else { + apoly.push({x: lpoly[lpoly.length - 1].x, y: dim.height - offsets.b}); + apoly.push({x: lpoly[0].x, y: dim.height - offsets.b}); + apoly.push(lpoly[0]); + run.dyn.fill = s.createPolyline(apoly).setFill(theme.series.fill).getFill(); + } + } + if(this.opt.lines || this.opt.markers){ + if(theme.series.outline){ + outline = dc.makeStroke(theme.series.outline); + outline.width = 2 * outline.width + theme.series.stroke.width; + } + } + if(this.opt.markers){ + run.dyn.marker = theme.symbol; + } + var frontMarkers, outlineMarkers, shadowMarkers; + if(theme.series.shadow && theme.series.stroke){ + var shadow = theme.series.shadow, + spoly = arr.map(lpoly, function(c){ + return {x: c.x + shadow.dx, y: c.y + shadow.dy}; + }); + if(this.opt.lines){ + if(this.opt.tension){ + run.dyn.shadow = s.createPath(dc.curve(spoly, this.opt.tension)).setStroke(shadow).getStroke(); + } else { + run.dyn.shadow = s.createPolyline(spoly).setStroke(shadow).getStroke(); + } + } + if(this.opt.markers){ + shadow = theme.marker.shadow; + shadowMarkers = arr.map(spoly, function(c){ + return s.createPath("M" + c.x + " " + c.y + " " + theme.symbol). + setStroke(shadow).setFill(shadow.color); + }, this); + } + } + if(this.opt.lines){ + if(outline){ + if(this.opt.tension){ + run.dyn.outline = s.createPath(lpath).setStroke(outline).getStroke(); + } else { + run.dyn.outline = s.createPolyline(lpoly).setStroke(outline).getStroke(); + } + } + if(this.opt.tension){ + run.dyn.stroke = s.createPath(lpath).setStroke(theme.series.stroke).getStroke(); + } else { + run.dyn.stroke = s.createPolyline(lpoly).setStroke(theme.series.stroke).getStroke(); + } + } + if(this.opt.markers){ + frontMarkers = new Array(lpoly.length); + outlineMarkers = new Array(lpoly.length); + outline = null; + if(theme.marker.outline){ + outline = dc.makeStroke(theme.marker.outline); + outline.width = 2 * outline.width + (theme.marker.stroke ? theme.marker.stroke.width : 0); + } + arr.forEach(lpoly, function(c, i){ + var path = "M" + c.x + " " + c.y + " " + theme.symbol; + if(outline){ + outlineMarkers[i] = s.createPath(path).setStroke(outline); + } + frontMarkers[i] = s.createPath(path).setStroke(theme.marker.stroke).setFill(theme.marker.fill); + }, this); + if(events){ + var eventSeries = new Array(frontMarkers.length); + arr.forEach(frontMarkers, function(s, i){ + var o = { + element: "marker", + index: i, + run: run, + shape: s, + outline: outlineMarkers[i] || null, + shadow: shadowMarkers && shadowMarkers[i] || null, + cx: lpoly[i].x, + cy: lpoly[i].y, + x: i + 1, + y: run.data[i] + }; + this._connectEvents(o); + eventSeries[i] = o; + }, this); + this._eventSeries[run.name] = eventSeries; + }else{ + delete this._eventSeries[run.name]; + } + } + run.dirty = false; + // update the accumulator + for(var j = 0; j < run.data.length; ++j){ + var v = run.data[j]; + if(v !== null){ + if(isNaN(v)){ v = 0; } + acc[j] -= v; + } + } + } + this.dirty = false; + return this; // dojox.charting.plot2d.Stacked + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/StackedAreas.js b/js/dojo/dojox/charting/plot2d/StackedAreas.js new file mode 100644 index 0000000..2bc3f6b --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/StackedAreas.js @@ -0,0 +1,17 @@ +//>>built +define("dojox/charting/plot2d/StackedAreas", ["dojo/_base/declare", "./Stacked"], function(declare, Stacked){ +/*===== +var Stacked = dojox.charting.plot2d.Stacked; +=====*/ + return declare("dojox.charting.plot2d.StackedAreas", Stacked, { + // summary: + // A convenience object to set up a stacked area plot. + constructor: function(){ + // summary: + // Force our Stacked plotter to include both lines and areas. + this.opt.lines = true; + this.opt.areas = true; + } + }); +}); + diff --git a/js/dojo/dojox/charting/plot2d/StackedBars.js b/js/dojo/dojox/charting/plot2d/StackedBars.js new file mode 100644 index 0000000..3019c90 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/StackedBars.js @@ -0,0 +1,135 @@ +//>>built +define("dojox/charting/plot2d/StackedBars", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Bars", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/functional/sequence"], + function(lang, arr, declare, Bars, dc, df, dfr, dfs){ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); +/*===== +var bars = dojox.charting.plot2d.Bars; +=====*/ + return declare("dojox.charting.plot2d.StackedBars", Bars, { + // summary: + // The plot object representing a stacked bar chart (horizontal bars). + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + var stats = dc.collectStackedStats(this.series), t; + this._maxRunLength = stats.hmax; + stats.hmin -= 0.5; + stats.hmax += 0.5; + t = stats.hmin, stats.hmin = stats.vmin, stats.vmin = t; + t = stats.hmax, stats.hmax = stats.vmax, stats.vmax = t; + return stats; + }, + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.StackedBars + // A reference to this plot for functional chaining. + if(this._maxRunLength <= 0){ + return this; + } + + // stack all values + var acc = df.repeat(this._maxRunLength, "-> 0", 0); + for(var i = 0; i < this.series.length; ++i){ + var run = this.series[i]; + for(var j = 0; j < run.data.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y; + if(isNaN(v)){ v = 0; } + acc[j] += v; + } + } + } + // draw runs in backwards + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, height, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + events = this.events(); + f = dc.calculateBarSize(this._vScaler.bounds.scale, this.opt); + gap = f.gap; + height = f.size; + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + var theme = t.next("bar", [this.opt, run]), s = run.group, + eventSeries = new Array(acc.length); + for(var j = 0; j < acc.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = acc[j], + width = ht(v), + finalTheme = typeof value != "number" ? + t.addMixin(theme, "bar", value, true) : + t.post(theme, "bar"); + if(width >= 0 && height >= 1){ + var rect = { + x: offsets.l, + y: dim.height - offsets.b - vt(j + 1.5) + gap, + width: width, height: height + }; + var specialFill = this._plotFill(finalTheme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, rect); + var shape = s.createRect(rect).setFill(specialFill).setStroke(finalTheme.series.stroke); + run.dyn.fill = shape.getFill(); + run.dyn.stroke = shape.getStroke(); + if(events){ + var o = { + element: "bar", + index: j, + run: run, + shape: shape, + x: v, + y: j + 1.5 + }; + this._connectEvents(o); + eventSeries[j] = o; + } + if(this.animate){ + this._animateBar(shape, offsets.l, -width); + } + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + // update the accumulator + for(var j = 0; j < run.data.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y; + if(isNaN(v)){ v = 0; } + acc[j] -= v; + } + } + } + this.dirty = false; + return this; // dojox.charting.plot2d.StackedBars + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/StackedColumns.js b/js/dojo/dojox/charting/plot2d/StackedColumns.js new file mode 100644 index 0000000..4f21e31 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/StackedColumns.js @@ -0,0 +1,133 @@ +//>>built +define("dojox/charting/plot2d/StackedColumns", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Columns", "./common", + "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/functional/sequence"], + function(lang, arr, declare, Columns, dc, df, dfr, dfs){ + + var purgeGroup = dfr.lambda("item.purgeGroup()"); +/*===== +var Columns = dojox.charting.plot2d.Columns; +=====*/ + return declare("dojox.charting.plot2d.StackedColumns", Columns, { + // summary: + // The plot object representing a stacked column chart (vertical bars). + getSeriesStats: function(){ + // summary: + // Calculate the min/max on all attached series in both directions. + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + var stats = dc.collectStackedStats(this.series); + this._maxRunLength = stats.hmax; + stats.hmin -= 0.5; + stats.hmax += 0.5; + return stats; + }, + render: function(dim, offsets){ + // summary: + // Run the calculations for any axes for this plot. + // dim: Object + // An object in the form of { width, height } + // offsets: Object + // An object of the form { l, r, t, b}. + // returns: dojox.charting.plot2d.StackedColumns + // A reference to this plot for functional chaining. + if(this._maxRunLength <= 0){ + return this; + } + + // stack all values + var acc = df.repeat(this._maxRunLength, "-> 0", 0); + for(var i = 0; i < this.series.length; ++i){ + var run = this.series[i]; + for(var j = 0; j < run.data.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y; + if(isNaN(v)){ v = 0; } + acc[j] += v; + } + } + } + // draw runs in backwards + if(this.zoom && !this.isDataDirty()){ + return this.performZoom(dim, offsets); + } + this.resetEvents(); + this.dirty = this.isDirty(); + if(this.dirty){ + arr.forEach(this.series, purgeGroup); + this._eventSeries = {}; + this.cleanGroup(); + var s = this.group; + df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); + } + var t = this.chart.theme, f, gap, width, + ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), + vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), + events = this.events(); + f = dc.calculateBarSize(this._hScaler.bounds.scale, this.opt); + gap = f.gap; + width = f.size; + for(var i = this.series.length - 1; i >= 0; --i){ + var run = this.series[i]; + if(!this.dirty && !run.dirty){ + t.skip(); + this._reconnectEvents(run.name); + continue; + } + run.cleanGroup(); + var theme = t.next("column", [this.opt, run]), s = run.group, + eventSeries = new Array(acc.length); + for(var j = 0; j < acc.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = acc[j], + height = vt(v), + finalTheme = typeof value != "number" ? + t.addMixin(theme, "column", value, true) : + t.post(theme, "column"); + if(width >= 1 && height >= 0){ + var rect = { + x: offsets.l + ht(j + 0.5) + gap, + y: dim.height - offsets.b - vt(v), + width: width, height: height + }; + var specialFill = this._plotFill(finalTheme.series.fill, dim, offsets); + specialFill = this._shapeFill(specialFill, rect); + var shape = s.createRect(rect).setFill(specialFill).setStroke(finalTheme.series.stroke); + run.dyn.fill = shape.getFill(); + run.dyn.stroke = shape.getStroke(); + if(events){ + var o = { + element: "column", + index: j, + run: run, + shape: shape, + x: j + 0.5, + y: v + }; + this._connectEvents(o); + eventSeries[j] = o; + } + if(this.animate){ + this._animateColumn(shape, dim.height - offsets.b, height); + } + } + } + } + this._eventSeries[run.name] = eventSeries; + run.dirty = false; + // update the accumulator + for(var j = 0; j < run.data.length; ++j){ + var value = run.data[j]; + if(value !== null){ + var v = typeof value == "number" ? value : value.y; + if(isNaN(v)){ v = 0; } + acc[j] -= v; + } + } + } + this.dirty = false; + return this; // dojox.charting.plot2d.StackedColumns + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/StackedLines.js b/js/dojo/dojox/charting/plot2d/StackedLines.js new file mode 100644 index 0000000..9751d85 --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/StackedLines.js @@ -0,0 +1,15 @@ +//>>built +define("dojox/charting/plot2d/StackedLines", ["dojo/_base/declare", "./Stacked"], function(declare, Stacked){ +/*===== +var Stacked = dojox.charting.plot2d.Stacked; +=====*/ + return declare("dojox.charting.plot2d.StackedLines", Stacked, { + // summary: + // A convenience object to create a stacked line chart. + constructor: function(){ + // summary: + // Force our Stacked base to be lines only. + this.opt.lines = true; + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/_PlotEvents.js b/js/dojo/dojox/charting/plot2d/_PlotEvents.js new file mode 100644 index 0000000..c01a28b --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/_PlotEvents.js @@ -0,0 +1,121 @@ +//>>built +define("dojox/charting/plot2d/_PlotEvents", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "dojo/_base/connect"], + function(lang, arr, declare, hub){ + + return declare("dojox.charting.plot2d._PlotEvents", null, { + constructor: function(){ + this._shapeEvents = []; + this._eventSeries = {}; + }, + destroy: function(){ + // summary: + // Destroy any internal elements and event handlers. + this.resetEvents(); + this.inherited(arguments); + }, + plotEvent: function(o){ + // summary: + // Stub function for use by specific plots. + // o: Object + // An object intended to represent event parameters. + }, + raiseEvent: function(o){ + // summary: + // Raises events in predefined order + // o: Object + // An object intended to represent event parameters. + this.plotEvent(o); + var t = lang.delegate(o); + t.originalEvent = o.type; + t.originalPlot = o.plot; + t.type = "onindirect"; + arr.forEach(this.chart.stack, function(plot){ + if(plot !== this && plot.plotEvent){ + t.plot = plot; + plot.plotEvent(t); + } + }, this); + }, + connect: function(object, method){ + // summary: + // Helper function to connect any object's method to our plotEvent. + // object: Object + // The object to connect to. + // method: String|Function + // The method to fire when our plotEvent is fired. + // returns: Array + // The handle as returned from dojo.connect (see dojo.connect). + this.dirty = true; + return hub.connect(this, "plotEvent", object, method); // Array + }, + events: function(){ + // summary: + // Find out if any event handlers have been connected to our plotEvent. + // returns: Boolean + // A flag indicating that there are handlers attached. + return !!this.plotEvent.after; + }, + resetEvents: function(){ + // summary: + // Reset all events attached to our plotEvent (i.e. disconnect). + if(this._shapeEvents.length){ + arr.forEach(this._shapeEvents, function(item){ + item.shape.disconnect(item.handle); + }); + this._shapeEvents = []; + } + this.raiseEvent({type: "onplotreset", plot: this}); + }, + _connectSingleEvent: function(o, eventName){ + this._shapeEvents.push({ + shape: o.eventMask, + handle: o.eventMask.connect(eventName, this, function(e){ + o.type = eventName; + o.event = e; + this.raiseEvent(o); + o.event = null; + }) + }); + }, + _connectEvents: function(o){ + if(o){ + o.chart = this.chart; + o.plot = this; + o.hAxis = this.hAxis || null; + o.vAxis = this.vAxis || null; + o.eventMask = o.eventMask || o.shape; + this._connectSingleEvent(o, "onmouseover"); + this._connectSingleEvent(o, "onmouseout"); + this._connectSingleEvent(o, "onclick"); + } + }, + _reconnectEvents: function(seriesName){ + var a = this._eventSeries[seriesName]; + if(a){ + arr.forEach(a, this._connectEvents, this); + } + }, + fireEvent: function(seriesName, eventName, index, eventObject){ + // summary: + // Emulates firing an event for a given data value (specified by + // an index) of a given series. + // seriesName: String: + // Series name. + // eventName: String: + // Event name to emulate. + // index: Number: + // Valid data value index used to raise an event. + // eventObject: Object?: + // Optional event object. Especially useful for synthetic events. + // Default: null. + var s = this._eventSeries[seriesName]; + if(s && s.length && index < s.length){ + var o = s[index]; + o.type = eventName; + o.event = eventObject || null; + this.raiseEvent(o); + o.event = null; + } + } + }); +}); diff --git a/js/dojo/dojox/charting/plot2d/common.js b/js/dojo/dojox/charting/plot2d/common.js new file mode 100644 index 0000000..441ca7a --- /dev/null +++ b/js/dojo/dojox/charting/plot2d/common.js @@ -0,0 +1,217 @@ +//>>built +define("dojox/charting/plot2d/common", ["dojo/_base/lang", "dojo/_base/array", "dojo/_base/Color", + "dojox/gfx", "dojox/lang/functional", "../scaler/common"], + function(lang, arr, Color, g, df, sc){ + + var common = lang.getObject("dojox.charting.plot2d.common", true); + + return lang.mixin(common, { + doIfLoaded: sc.doIfLoaded, + makeStroke: function(stroke){ + if(!stroke){ return stroke; } + if(typeof stroke == "string" || stroke instanceof Color){ + stroke = {color: stroke}; + } + return g.makeParameters(g.defaultStroke, stroke); + }, + augmentColor: function(target, color){ + var t = new Color(target), + c = new Color(color); + c.a = t.a; + return c; + }, + augmentStroke: function(stroke, color){ + var s = common.makeStroke(stroke); + if(s){ + s.color = common.augmentColor(s.color, color); + } + return s; + }, + augmentFill: function(fill, color){ + var fc, c = new Color(color); + if(typeof fill == "string" || fill instanceof Color){ + return common.augmentColor(fill, color); + } + return fill; + }, + + defaultStats: { + vmin: Number.POSITIVE_INFINITY, vmax: Number.NEGATIVE_INFINITY, + hmin: Number.POSITIVE_INFINITY, hmax: Number.NEGATIVE_INFINITY + }, + + collectSimpleStats: function(series){ + var stats = lang.delegate(common.defaultStats); + for(var i = 0; i < series.length; ++i){ + var run = series[i]; + for(var j = 0; j < run.data.length; j++){ + if(run.data[j] !== null){ + if(typeof run.data[j] == "number"){ + // 1D case + var old_vmin = stats.vmin, old_vmax = stats.vmax; + if(!("ymin" in run) || !("ymax" in run)){ + arr.forEach(run.data, function(val, i){ + if(val !== null){ + var x = i + 1, y = val; + if(isNaN(y)){ y = 0; } + stats.hmin = Math.min(stats.hmin, x); + stats.hmax = Math.max(stats.hmax, x); + stats.vmin = Math.min(stats.vmin, y); + stats.vmax = Math.max(stats.vmax, y); + } + }); + } + if("ymin" in run){ stats.vmin = Math.min(old_vmin, run.ymin); } + if("ymax" in run){ stats.vmax = Math.max(old_vmax, run.ymax); } + }else{ + // 2D case + var old_hmin = stats.hmin, old_hmax = stats.hmax, + old_vmin = stats.vmin, old_vmax = stats.vmax; + if(!("xmin" in run) || !("xmax" in run) || !("ymin" in run) || !("ymax" in run)){ + arr.forEach(run.data, function(val, i){ + if(val !== null){ + var x = "x" in val ? val.x : i + 1, y = val.y; + if(isNaN(x)){ x = 0; } + if(isNaN(y)){ y = 0; } + stats.hmin = Math.min(stats.hmin, x); + stats.hmax = Math.max(stats.hmax, x); + stats.vmin = Math.min(stats.vmin, y); + stats.vmax = Math.max(stats.vmax, y); + } + }); + } + if("xmin" in run){ stats.hmin = Math.min(old_hmin, run.xmin); } + if("xmax" in run){ stats.hmax = Math.max(old_hmax, run.xmax); } + if("ymin" in run){ stats.vmin = Math.min(old_vmin, run.ymin); } + if("ymax" in run){ stats.vmax = Math.max(old_vmax, run.ymax); } + } + + break; + } + } + } + return stats; + }, + + calculateBarSize: function(/* Number */ availableSize, /* Object */ opt, /* Number? */ clusterSize){ + if(!clusterSize){ + clusterSize = 1; + } + var gap = opt.gap, size = (availableSize - 2 * gap) / clusterSize; + if("minBarSize" in opt){ + size = Math.max(size, opt.minBarSize); + } + if("maxBarSize" in opt){ + size = Math.min(size, opt.maxBarSize); + } + size = Math.max(size, 1); + gap = (availableSize - size * clusterSize) / 2; + return {size: size, gap: gap}; // Object + }, + + collectStackedStats: function(series){ + // collect statistics + var stats = lang.clone(common.defaultStats); + if(series.length){ + // 1st pass: find the maximal length of runs + stats.hmin = Math.min(stats.hmin, 1); + stats.hmax = df.foldl(series, "seed, run -> Math.max(seed, run.data.length)", stats.hmax); + // 2nd pass: stack values + for(var i = 0; i < stats.hmax; ++i){ + var v = series[0].data[i]; + v = v && (typeof v == "number" ? v : v.y); + if(isNaN(v)){ v = 0; } + stats.vmin = Math.min(stats.vmin, v); + for(var j = 1; j < series.length; ++j){ + var t = series[j].data[i]; + t = t && (typeof t == "number" ? t : t.y); + if(isNaN(t)){ t = 0; } + v += t; + } + stats.vmax = Math.max(stats.vmax, v); + } + } + return stats; + }, + + curve: function(/* Number[] */a, /* Number|String */tension){ + // FIX for #7235, submitted by Enzo Michelangeli. + // Emulates the smoothing algorithms used in a famous, unnamed spreadsheet + // program ;) + var array = a.slice(0); + if(tension == "x") { + array[array.length] = arr[0]; // add a last element equal to the first, closing the loop + } + var p=arr.map(array, function(item, i){ + if(i==0){ return "M" + item.x + "," + item.y; } + if(!isNaN(tension)) { // use standard Dojo smoothing in tension is numeric + var dx=item.x-array[i-1].x, dy=array[i-1].y; + return "C"+(item.x-(tension-1)*(dx/tension))+","+dy+" "+(item.x-(dx/tension))+","+item.y+" "+item.x+","+item.y; + } else if(tension == "X" || tension == "x" || tension == "S") { + // use Excel "line smoothing" algorithm (http://xlrotor.com/resources/files.shtml) + var p0, p1 = array[i-1], p2 = array[i], p3; + var bz1x, bz1y, bz2x, bz2y; + var f = 1/6; + if(i==1) { + if(tension == "x") { + p0 = array[array.length-2]; + } else { // "tension == X || tension == "S" + p0 = p1; + } + f = 1/3; + } else { + p0 = array[i-2]; + } + if(i==(array.length-1)) { + if(tension == "x") { + p3 = array[1]; + } else { // "tension == X || tension == "S" + p3 = p2; + } + f = 1/3; + } else { + p3 = array[i+1]; + } + var p1p2 = Math.sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y)); + var p0p2 = Math.sqrt((p2.x-p0.x)*(p2.x-p0.x)+(p2.y-p0.y)*(p2.y-p0.y)); + var p1p3 = Math.sqrt((p3.x-p1.x)*(p3.x-p1.x)+(p3.y-p1.y)*(p3.y-p1.y)); + + var p0p2f = p0p2 * f; + var p1p3f = p1p3 * f; + + if(p0p2f > p1p2/2 && p1p3f > p1p2/2) { + p0p2f = p1p2/2; + p1p3f = p1p2/2; + } else if(p0p2f > p1p2/2) { + p0p2f = p1p2/2; + p1p3f = p1p2/2 * p1p3/p0p2; + } else if(p1p3f > p1p2/2) { + p1p3f = p1p2/2; + p0p2f = p1p2/2 * p0p2/p1p3; + } + + if(tension == "S") { + if(p0 == p1) { p0p2f = 0; } + if(p2 == p3) { p1p3f = 0; } + } + + bz1x = p1.x + p0p2f*(p2.x - p0.x)/p0p2; + bz1y = p1.y + p0p2f*(p2.y - p0.y)/p0p2; + bz2x = p2.x - p1p3f*(p3.x - p1.x)/p1p3; + bz2y = p2.y - p1p3f*(p3.y - p1.y)/p1p3; + } + return "C"+(bz1x+","+bz1y+" "+bz2x+","+bz2y+" "+p2.x+","+p2.y); + }); + return p.join(" "); + }, + + getLabel: function(/*Number*/number, /*Boolean*/fixed, /*Number*/precision){ + return sc.doIfLoaded("dojo/number", function(numberLib){ + return (fixed ? numberLib.format(number, {places : precision}) : + numberLib.format(number)) || ""; + }, function(){ + return fixed ? number.toFixed(precision) : number.toString(); + }); + } + }); +}); |
