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.
300 lines (290 loc) • 8.75 kB
JavaScript
define(["dojo/_base/array",
"dojo/_base/declare",
"dojo/query",
"dojo/_base/connect",
"dojo/_base/Color",
"./Legend",
"dijit/form/CheckBox",
"../action2d/Highlight",
"dojox/lang/functional",
"dojox/gfx/fx",
"dojo/keys",
"dojo/dom-construct",
"dojo/dom-prop",
"dijit/registry"
], function(arrayUtil, declare, query, hub, Color, Legend, CheckBox, Highlight, df, fx, keys, dom, domProp, registry){
var FocusManager = declare(null, {
// summary:
// It will take legend as a tab stop, and using
// cursor keys to navigate labels within the legend.
// tags:
// private
constructor: function(legend){
this.legend = legend;
this.index = 0;
this.horizontalLength = this._getHrizontalLength();
arrayUtil.forEach(legend.legends, function(item, i){
if(i > 0){
query("input", item).attr("tabindex", -1);
}
});
this.firstLabel = query("input", legend.legends[0])[0];
hub.connect(this.firstLabel, "focus", this, function(){this.legend.active = true;});
hub.connect(this.legend.domNode, "keydown", this, "_onKeyEvent");
},
_getHrizontalLength: function(){
var horizontal = this.legend.horizontal;
if(typeof horizontal == "number"){
return Math.min(horizontal, this.legend.legends.length);
}else if(!horizontal){
return 1;
}else{
return this.legend.legends.length;
}
},
_onKeyEvent: function(e){
// if not focused
if(!this.legend.active){
return;
}
// lose focus
if(e.keyCode == keys.TAB){
this.legend.active = false;
return;
}
// handle with arrow keys
var max = this.legend.legends.length;
switch(e.keyCode){
case keys.LEFT_ARROW:
this.index--;
if(this.index < 0){
this.index += max;
}
break;
case keys.RIGHT_ARROW:
this.index++;
if(this.index >= max){
this.index -= max;
}
break;
case keys.UP_ARROW:
if(this.index - this.horizontalLength >= 0){
this.index -= this.horizontalLength;
}
break;
case keys.DOWN_ARROW:
if(this.index + this.horizontalLength < max){
this.index += this.horizontalLength;
}
break;
default:
return;
}
this._moveToFocus();
Event.stop(e);
},
_moveToFocus: function(){
query("input", this.legend.legends[this.index])[0].focus();
}
});
var FakeHighlight = declare(Highlight, {
connect: function(){}
});
var SelectableLegend = declare("dojox.charting.widget.SelectableLegend", Legend, {
// summary:
// An enhanced chart legend supporting interactive events on data series
// theme component
outline: false, // outline of vanished data series
transitionFill: null, // fill of deselected data series
transitionStroke: null, // stroke of deselected data series
// autoScale: Boolean
// Whether the scales of the chart are recomputed when selecting/unselecting a series in the legend. Default is false.
autoScale: false,
postCreate: function(){
this.legends = [];
this.legendAnim = {};
this._cbs = [];
this.inherited(arguments);
},
refresh: function(){
this.legends = [];
this._clearLabels();
this.inherited(arguments);
this._applyEvents();
new FocusManager(this);
},
_clearLabels: function(){
var cbs = this._cbs;
while(cbs.length){
cbs.pop().destroyRecursive();
}
},
_addLabel: function(dyn, label){
this.inherited(arguments);
// create checkbox
var legendNodes = query("td", this.legendBody);
var currentLegendNode = legendNodes[legendNodes.length - 1];
this.legends.push(currentLegendNode);
var checkbox = new CheckBox({checked: true});
this._cbs.push(checkbox);
dom.place(checkbox.domNode, currentLegendNode, "first");
// connect checkbox and existed label
var clabel = query("label", currentLegendNode)[0];
domProp.set(clabel, "for", checkbox.id);
},
_applyEvents: function(){
// summary:
// Apply click-event on checkbox and hover-event on legend icon,
// highlight data series or toggle it.
// if the chart has not yet been refreshed it will crash here (targetData.group == null)
if(this.chart.dirty){
return;
}
arrayUtil.forEach(this.legends, function(legend, i){
var targetData, plotName, seriesName;
if(this._isPie()){
targetData = this.chart.stack[0];
plotName = targetData.name;
seriesName = this.chart.series[0].name;
}else{
targetData = this.chart.series[i];
plotName = targetData.plot;
seriesName = targetData.name;
}
// toggle action
var legendCheckBox = registry.byNode(query(".dijitCheckBox", legend)[0]);
legendCheckBox.set("checked", !this._isHidden(plotName, i));
hub.connect(legendCheckBox, "onClick", this, function(e){
this.toogle(plotName, i, !legendCheckBox.get("checked"));
e.stopPropagation();
});
// highlight action
var legendIcon = query(".dojoxLegendIcon", legend)[0],
iconShape = this._getFilledShape(this._surfaces[i].children);
arrayUtil.forEach(["onmouseenter", "onmouseleave"], function(event){
hub.connect(legendIcon, event, this, function(e){
this._highlight(e, iconShape, i, !legendCheckBox.get("checked"), seriesName, plotName);
});
}, this);
},this);
},
_isHidden: function(plotName, index){
if(this._isPie()){
return arrayUtil.indexOf(this.chart.getPlot(plotName).runFilter, index) != -1;
}else{
return this.chart.series[index].hidden
}
},
toogle: function(plotName, index, hide){
var plot = this.chart.getPlot(plotName);
if(this._isPie()){
if(arrayUtil.indexOf(plot.runFilter, index) != -1){
if(!hide){
plot.runFilter = arrayUtil.filter(plot.runFilter, function(item){
return item != index;
});
}
}else{
if(hide){
plot.runFilter.push(index);
}
}
}else{
this.chart.series[index].hidden = hide;
}
this.autoScale ? this.chart.dirty = true: plot.dirty = true;
this.chart.render();
},
_highlight: function(e, iconShape, index, isOff, seriesName, plotName){
if(!isOff){
var anim = this._getAnim(plotName),
isPie = this._isPie(),
type = formatEventType(e.type);
// highlight the label icon,
var label = {
shape: iconShape,
index: isPie ? "legend" + index : "legend",
run: {name: seriesName},
type: type
};
anim.process(label);
// highlight the data items
arrayUtil.forEach(this._getShapes(index, plotName), function(shape, i){
var o = {
shape: shape,
index: isPie ? index : i,
run: {name: seriesName},
type: type
};
anim.duration = 100;
anim.process(o);
});
}
},
_getShapes: function(i, plotName){
var shapes = [];
if(this._isPie()){
var decrease = 0;
arrayUtil.forEach(this.chart.getPlot(plotName).runFilter, function(item){
if(i > item){
decrease++;
}
});
shapes.push(this.chart.stack[0].group.children[i-decrease]);
}else if(this._isCandleStick(plotName)){
arrayUtil.forEach(this.chart.series[i].group.children, function(group){
arrayUtil.forEach(group.children, function(candle){
arrayUtil.forEach(candle.children, function(shape){
if(shape.shape.type !="line"){
shapes.push(shape);
}
});
});
});
}else{
shapes = this.chart.series[i].group.children;
}
return shapes;
},
_getAnim: function(plotName){
if(!this.legendAnim[plotName]){
this.legendAnim[plotName] = new FakeHighlight(this.chart, plotName);
}
return this.legendAnim[plotName];
},
_getTransitionFill: function(plotName){
// Since series of stacked charts all start from the base line,
// fill the "front" series with plotarea color to make it disappear .
if(this.chart.stack[this.chart.plots[plotName]].declaredClass.indexOf("dojox.charting.plot2d.Stacked") != -1){
return this.chart.theme.plotarea.fill;
}
return null;
},
_getFilledShape: function(shapes){
// summary:
// Get filled shape in legend icon which would be highlighted when hovered
var i = 0;
while(shapes[i]){
if(shapes[i].getFill())return shapes[i];
i++;
}
return null;
},
_isPie: function(){
return this.chart.stack[0].declaredClass == "dojox.charting.plot2d.Pie";
},
_isCandleStick: function(plotName){
return this.chart.stack[this.chart.plots[plotName]].declaredClass == "dojox.charting.plot2d.Candlesticks";
},
destroy: function(){
this._clearLabels();
this.inherited(arguments);
}
});
function formatEventType(type){
if(type == "mouseenter")return "onmouseover";
if(type == "mouseleave")return "onmouseout";
return "on" + type;
}
return SelectableLegend;
});