UNPKG

dojox

Version:

Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.

501 lines (457 loc) 16.9 kB
define(["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./CartesianBase", "./_PlotEvents", "./common", "../axis2d/common", "dojox/gfx", "dojox/lang/utils", "dojox/gfx/fx", "dojo/has"], function(lang, array, declare, CartesianBase, _PlotEvents, dcpc, dcac, gfx, du, fx, has){ // all the code below should be removed when http://trac.dojotoolkit.org/ticket/11299 will be available var getBoundingBox = function(shape){ return getTextBBox(shape, shape.getShape().text); }; var getTextBBox = function(s, t){ var c = s.declaredClass; var w, h; if(c.indexOf("svg")!=-1){ // try/catch the FF native getBBox error. cheaper than walking up in the DOM // hierarchy to check the conditions (bench show /10 ) try { return lang.mixin({}, s.rawNode.getBBox()); }catch (e){ return null; } }else if(c.indexOf("vml")!=-1){ var rawNode = s.rawNode, _display = rawNode.style.display; rawNode.style.display = "inline"; w = gfx.pt2px(parseFloat(rawNode.currentStyle.width)); h = gfx.pt2px(parseFloat(rawNode.currentStyle.height)); var sz = {x: 0, y: 0, width: w, height: h}; // in VML, the width/height we get are in view coordinates // in our case we don't zoom the view so that is ok // It's impossible to get the x/y from the currentStyle.left/top, // because all negative coordinates are 'clipped' to 0. // (x:0 + translate(-100) -> x=0 computeLocation(s, sz); rawNode.style.display = _display; return sz; }else if(c.indexOf("silverlight")!=-1){ var bb = {width: s.rawNode.actualWidth, height: s.rawNode.actualHeight}; return computeLocation(s, bb, 0.75); }else if(s.getTextWidth){ // canvas w = s.getTextWidth(); var font = s.getFont(); var fz = font ? font.size : gfx.defaultFont.size; h = gfx.normalizedLength(fz); sz = {width: w, height: h}; computeLocation(s, sz, 0.75); return sz; } return null; }; var computeLocation = function(s, sz, coef){ var width = sz.width, height = sz.height, sh = s.getShape(), align = sh.align; switch (align) { case "end": sz.x = sh.x - width; break; case "middle": sz.x = sh.x - width / 2; break; case "start": default: sz.x = sh.x; break; } coef = coef || 1; sz.y = sh.y - height*coef; // rough approximation of the ascent!... return sz; }; /*===== declare("dojox.charting.plot2d.__IndicatorCtorArgs", dojox.charting.plot2d.__CartesianCtorArgs, { // summary: // A special keyword arguments object that is specific to a indicator "plot". // lines: Boolean? // Whether the indicator lines are visible or not. The lines are displayed for each of the // 'values' of the indicator. Default is true. lines: true, // labels: String? // Describes how the indicator labels are displayed. // Possible values are: // `"line"` for displaying the value of each indicator line, // `"marker"` for displaying the values of the markers of each indicator line, // `"trend"` for displaying the percentage variation from the first to the last indicator line, // `"none"` to prevent any label to display. // Default is "line". labels: "line", // markers: Boolean? // Whether the markers on the indicator lines are visible or not. The markers are displayed for each // of the indicator lines using the series attached to the indicator plot. Default is true. markers: true, // values: Boolean // The data values at which each of the indicator line is drawn. For a single value a Number can be provided // otherwise an Array of Number is required. fault is []. values: [], // offset: {x, y}? // A pair of (x, y) pixel coordinate to specifiy the offset between the end of the indicator line and the // position at which the labels are rendered. Default is no offset. offset: {}, // start: Boolean? // Whether the label is rendered at the start or end of the indicator line. Default is false meaning end of // the line. start: false, // animate: Boolean?|Number? // Whether or not to animate the chart to place. When a Number it specifies the duration of the animation. // Default is false. animate: false, // vertical: Boolean? // Whether the indicator is vertical or not. Default is true. vertical: true, // fixed: Boolean? // Whether a fixed precision must be applied to data values for display. Default is true. fixed: true, // precision: Number? // The precision at which to round data values for display. Default is 0. precision: 0, // lineStroke: dojo/gfx/Stroke? // An optional stroke to use for indicator line. lineStroke: {}, // lineOutline: dojo/gfx/Stroke? // An optional outline to use for indicator line. lineOutline: {}, // lineShadow: dojo/gfx/Stroke? // An optional shadow to use for indicator line. lineShadow: {}, // stroke: dojo.gfx.Stroke? // An optional stroke to use for indicator label background. stroke: {}, // outline: dojo.gfx.Stroke? // An optional outline to use for indicator label background. outline: {}, // shadow: dojo.gfx.Stroke? // An optional shadow to use for indicator label background. shadow: {}, // fill: dojo.gfx.Fill? // An optional fill to use for indicator label background. fill: {}, // fillFunc: Function? // An optional function to use to compute label background fill. It takes precedence over // fill property when available. // | function fillFunc(index, values, data) {} // `index` is the index in the values array of the label being drawn. // `values` is the entire array of values. // `data` is the entire array of marker values. fillFunc: null, // labelFunc: Function? // An optional function to use to compute label text. It takes precedence over // the default text when available. // | function labelFunc(index, values, data, fixed, precision, labels) {} // `index` is the index in the values array of the label being drawn. // `values` is the entire array of values. // `data` is the entire array of marker values. // `fixed` is true if fixed precision must be applied. // `precision` is the requested precision to be applied. // `labels` is the labels mode of the indicator. labelFunc: null, // font: String? // A font definition to use for indicator label background. font: "", // fontColor: String|dojo.Color? // The color to use for indicator label background. fontColor: "", // markerStroke: dojo.gfx.Stroke? // An optional stroke to use for indicator marker. markerStroke: {}, // markerOutline: dojo.gfx.Stroke? // An optional outline to use for indicator marker. markerOutline: {}, // markerShadow: dojo.gfx.Stroke? // An optional shadow to use for indicator marker. markerShadow: {}, // markerFill: dojo.gfx.Fill? // An optional fill to use for indicator marker. markerFill: {}, // markerSymbol: String? // An optional symbol string to use for indicator marker. markerSymbol: "", }); =====*/ var Indicator = declare("dojox.charting.plot2d.Indicator", [CartesianBase, _PlotEvents], { // summary: // A "faux" plot that can be placed behind or above other plots to represent a line or multi-line // threshold on the chart. defaultParams: { vertical: true, fixed: true, precision: 0, lines: true, labels: "line", // "line" | "trend" | "markers" | "none" markers: true }, optionalParams: { lineStroke: {}, outlineStroke: {}, shadowStroke: {}, lineFill: {}, stroke: {}, outline: {}, shadow: {}, fill: {}, fillFunc: null, labelFunc: null, font: "", fontColor: "", markerStroke: {}, markerOutline: {}, markerShadow: {}, markerFill: {}, markerSymbol: "", values: [], offset: {}, start: false, animate: false }, 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); if(typeof kwArgs.values == "number"){ kwArgs.values = [ kwArgs.values ]; } du.updateWithPattern(this.opt, kwArgs, this.optionalParams); this.animate = this.opt.animate; }, render: function(dim, offsets){ if(this.zoom){ return this.performZoom(dim, offsets); } if(!this.isDirty()){ return this; } this.cleanGroup(null, true); if(!this.opt.values){ return this; } this._updateIndicator(); return this; }, _updateIndicator: function(){ var t = this.chart.theme; var hn = this._hAxis.name, vn = this._vAxis.name, hb = this._hAxis.getScaler().bounds, vb = this._vAxis.getScaler().bounds; var o = {}; o[hn] = hb.from; o[vn] = vb.from; var min = this.toPage(o); o[hn] = hb.to; o[vn] = vb.to; var max = this.toPage(o); var events = this.events(); var results = array.map(this.opt.values, function(value, index){ return this._renderIndicator(value, index, hn, vn, min, max, events, this.animate); }, this); var length = results.length; if(this.opt.labels == "trend"){ var v = this.opt.vertical; var first = this._data[0][0]; var last = this._data[length - 1][0]; var delta = last-first; var text = this.opt.labelFunc?this.opt.labelFunc(-1, this.values, this._data, this.opt.fixed, this.opt.precision): (dcpc.getLabel(delta, this.opt.fixed, this.opt.precision)+" ("+dcpc.getLabel(100*delta/first, true, 2)+"%)"); this._renderText(this.getGroup(), text, this.chart.theme, v?(results[0].x+results[length - 1].x)/2:results[1].x, v?results[0].y:(results[1].y+results[length - 1].y)/2, -1, this.opt.values, this._data); } var lineFill = this.opt.lineFill!=undefined?this.opt.lineFill:t.indicator.lineFill; if(lineFill && length > 1){ var x0 = Math.min(results[0].x1, results[length - 1].x1); var y0 = Math.min(results[0].y1, results[length - 1].y1); var r = this.getGroup().createRect({x: x0, y: y0, width: Math.max(results[0].x2, results[length - 1].x2) - x0, height: Math.max(results[0].y2, results[length - 1].y2) - y0}). setFill(lineFill); r.moveToBack(); } }, _renderIndicator: function(coord, index, hn, vn, min, max, events, animate){ var t = this.chart.theme, c = this.chart.getCoords(), v = this.opt.vertical; var g = this.getGroup().createGroup(); var mark = {}; mark[hn] = v?coord:0; mark[vn] = v?0:coord; if(has("dojo-bidi")){ mark.x = this._getMarkX(mark.x); } mark = this.toPage(mark); var visible = v?mark.x >= min.x && mark.x <= max.x:mark.y >= max.y && mark.y <= min.y; var cx = mark.x - c.x, cy = mark.y - c.y; var x1 = v?cx:min.x - c.x, y1 = v?min.y - c.y:cy, x2 = v?x1:max.x - c.x, y2 = v?max.y - c.y:y1; if(this.opt.lines && visible){ var sh = this.opt.hasOwnProperty("lineShadow")?this.opt.lineShadow:t.indicator.lineShadow, ls = this.opt.hasOwnProperty("lineStroke")?this.opt.lineStroke:t.indicator.lineStroke, ol = this.opt.hasOwnProperty("lineOutline")?this.opt.lineOutline:t.indicator.lineOutline; if(sh){ g.createLine({x1: x1 + sh.dx, y1: y1 + sh.dy, x2: x2 + sh.dx, y2: y2 + sh.dy}).setStroke(sh); } if(ol){ ol = dcpc.makeStroke(ol); ol.width = 2 * ol.width + (ls?ls.width:0); g.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ol); } g.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ls); } // series items represent markers on the indicator var data; if(this.opt.markers && visible){ var d = this._data[index]; var self = this; if(d){ data = array.map(d, function(value, index){ mark[hn] = v?coord:value; mark[vn] = v?value:coord; if(has("dojo-bidi")){ mark.x = self._getMarkX(mark.x); } mark = this.toPage(mark); if(v?mark.y <= min.y && mark.y >= max.y:mark.x >= min.x && mark.x <= max.x){ cx = mark.x - c.x cy = mark.y - c.y; var ms = this.opt.markerSymbol?this.opt.markerSymbol:t.indicator.markerSymbol, path = "M" + cx + " " + cy + " " + ms; sh = this.opt.markerShadow!=undefined?this.opt.markerShadow:t.indicator.markerShadow; ls = this.opt.markerStroke!=undefined?this.opt.markerStroke:t.indicator.markerStroke; ol = this.opt.markerOutline!=undefined?this.opt.markerOutline:t.indicator.markerOutline; if(sh){ var sp = "M" + (cx + sh.dx) + " " + (cy + sh.dy) + " " + ms; g.createPath(sp).setFill(sh.color).setStroke(sh); } if(ol){ ol = dcpc.makeStroke(ol); ol.width = 2 * ol.width + (ls?ls.width:0); g.createPath(path).setStroke(ol); } var shape = g.createPath(path); var sf = this._shapeFill(this.opt.markerFill != undefined?this.opt.markerFill:t.indicator.markerFill, shape.getBoundingBox()); shape.setFill(sf).setStroke(ls); } return value; }, this); } } var ctext; if(this.opt.start){ ctext = { x: v?x1:x1, y: v?y1:y2 }; }else{ ctext = { x: v?x1:x2, y: v?y2:y1 }; } if(this.opt.labels && this.opt.labels != "trend" && visible){ var text; if(this.opt.labelFunc){ text = this.opt.labelFunc(index, this.opt.values, this._data, this.opt.fixed, this.opt.precision, this.opt.labels); }else{ if(this.opt.labels == "markers"){ text = array.map(data, function(value){ return dcpc.getLabel(value, this.opt.fixed, this.opt.precision); }, this); text = text.length != 1 ? "[ "+text.join(", ")+" ]" : text[0]; }else{ text = dcpc.getLabel(coord, this.opt.fixed, this.opt.precision); } } this._renderText(g, text, t, ctext.x, ctext.y, index, this.opt.values, this._data); } if(events){ this._connectEvents({ element: "indicator", run: this.run?this.run[index]:undefined, shape: g, value: coord }); } if(animate){ this._animateIndicator(g, v, v?y1:x1, v?(y1 + y2):(x1 + x2), animate); } return lang.mixin(ctext, {x1: x1, y1: y1, x2: x2, y2: y2}); }, _animateIndicator: function(shape, vertical, offset, size, animate){ var transStart = vertical ? [0, offset] : [offset, 0]; var scaleStart = vertical ? [1, 1 / size] : [1 / size, 1]; 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"} ] }, animate)).play(); }, clear: function(){ this.inherited(arguments); this._data = []; }, addSeries: function(run){ this.inherited(arguments); this._data.push(run.data); }, _renderText: function(g, text, t, x, y, index, values, data){ if(this.opt.offset){ x += this.opt.offset.x; y += this.opt.offset.y; } var label = dcac.createText.gfx( this.chart, g, x, y, this.opt.vertical? "middle" : (this.opt.start ? "start":"end"), text, this.opt.font?this.opt.font:t.indicator.font, this.opt.fontColor?this.opt.fontColor:t.indicator.fontColor); var b = getBoundingBox(label); if(this.opt.vertical && !this.opt.start) { b.y += b.height/2; label.setShape({y: y+b.height/2}); } b.x-=2; b.y-=1; b.width+=4; b.height+=2; b.r = this.opt.radius?this.opt.radius:t.indicator.radius; var sh = this.opt.shadow!=undefined?this.opt.shadow:t.indicator.shadow, ls = this.opt.stroke!=undefined?this.opt.stroke:t.indicator.stroke, ol = this.opt.outline!=undefined?this.opt.outline:t.indicator.outline; if(sh){ g.createRect(b).setFill(sh.color).setStroke(sh); } if(ol){ ol = dcpc.makeStroke(ol); ol.width = 2 * ol.width + (ls?ls.width:0); g.createRect(b).setStroke(ol); } var f = this.opt.fillFunc?this.opt.fillFunc(index, values, data):(this.opt.fill!=undefined?this.opt.fill:t.indicator.fill); g.createRect(b).setFill(this._shapeFill(f, b)).setStroke(ls); label.moveToFront(); }, 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(dcpc.defaultStats); } }); if(has("dojo-bidi")){ Indicator.extend({ _getMarkX: function(x){ if(this.chart.isRightToLeft()){ return this.chart.axes.x.scaler.bounds.to + this.chart.axes.x.scaler.bounds.from - x; } return x; } }); } return Indicator; });