在ext3.0中,增加了flash图表的部分,可以让报表更加的赏心悦目。但是其中也有些缺憾,比如stackbar,columnbar 等,里面的值不能直接显示在相应的位置,必须鼠标移动到上面才行。本以为有什么机关咱不知道,但是在yahoo的论坛上,yui的开发人员明确的回答,yui-chart没有这样的功能,咱就只能自己动手了。
ext3.0中的charts.swf用的是yui-chart,我试验了两个版本2.6和2.8,2.8无法显示。2.6比较好的兼容了。到这里下载2.6的源码
http://yuilibrary.com/downloads/。解压缩后,取出其中的chart目录。
再说说编译工具的问题吧,为了解决能调试的问题,找了很多的工具,但是现在还是没有能解决,目前只能是黑盒修改,也就是说无法跟踪调试,只能自己在里面写点信息,然后显示出来,看看是否达到了目的。我用的是flex3.0。其实用flashdevelop也可以的。
用flex3.0新建一个as的工程,然后将chart目录下的文件拷贝到该目录一下,或者应用chart目录。修改build.xml的输出路径(output)和编译工具的路径(flexbin),其他的可以不管.
对于stackbar 和columnbar,显示值的位置是不同的,因为一个是横向的柱子,一个是纵向的柱子,对于横向的,值是显示在bar里面的,纵向的,值是显示在bar上面的。下面分别就针对这两种类型的bar,进行说明,原理差不多。带中文备注的是增加的.
打开单元com.yahoo.astra.fl.charts.series.ColumnSeries.as找到draw方法
override protected function draw():void
{
super.draw();
//if we don't have data, let's get out of here
if(!this.dataProvider)
{
return;
}
//grab the axes
var cartesianChart:CartesianChart = this.chart as CartesianChart;
var valueAxis:IOriginAxis = cartesianChart.verticalAxis as IOriginAxis;
var otherAxis:IAxis = cartesianChart.horizontalAxis;
var verticalAxisTitle:IAxis=cartesianChart.verticalAxis;//获取X轴的相关信息
if(!valueAxis)
{
throw new Error("To use a ColumnSeries object, the vertical axis of the chart it appears within must be an IOriginAxis.");
return;
}
var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, cartesianChart);
var markerSizes:Array = [];
var totalMarkerSize:Number = this.calculateTotalMarkerSize(otherAxis, markerSizes);
var seriesIndex:int = allSeriesOfType.indexOf(this);
var markerSize:Number = markerSizes[seriesIndex] as Number;
var xOffset:Number = this.calculateXOffset(valueAxis, otherAxis, markerSizes, totalMarkerSize, allSeriesOfType);
var startValues:Array = [];
var endValues:Array = [];
var itemCount:int = this.length;
for(var i:int = 0; i < itemCount; i++)
{
var originValue:Object = this.calculateOriginValue(i, valueAxis, allSeriesOfType);
var originPosition:Number = valueAxis.valueToLocal(originValue);
var position:Point = IChart(this.chart).itemToPosition(this, i);
var value:Object=IChart(this.chart).itemToAxisValue(this,i,verticalAxisTitle,false);//根据X轴来获取当前柱子的值
var marker:DisplayObject = this.markers[i] as DisplayObject;
marker.x = position.x + xOffset;
marker.width = markerSize;
var caption:TextField=new TextField ();//创建一个TextField,也可以用Label等代替
caption.y=-20;//这里的x,y是相对于柱子来说的,所以要显示在上面,必须为负值
caption.x=-10;
caption.text=String(value);//显示值
UIComponent(marker).addChild(caption);//增加这个对象
//if we have a bad position, don't display the marker
if(isNaN(position.x) || isNaN(position.y))
{
this.invalidateMarker(ISeriesItemRenderer(marker));
}
else if(this.isMarkerInvalid(ISeriesItemRenderer(marker)))
{
//initialize the marker to the origin
marker.y = originPosition;
marker.height = 0;
if(marker is UIComponent)
{
(marker as UIComponent).drawNow();
}
this.validateMarker(ISeriesItemRenderer(marker));
}
//stupid Flash UIComponent rounding!
position.y = Math.round(position.y);
originPosition = Math.round(originPosition);
var calculatedHeight:Number = originPosition - position.y;
if(calculatedHeight < 0)
{
calculatedHeight = Math.abs(calculatedHeight);
position.y = Math.round(originPosition);
//always put the marker on the origin
marker.y = position.y;
}
startValues.push(marker.y, marker.height);
endValues.push(position.y, calculatedHeight);
}
//handle animating all the markers in one fell swoop.
if(this._animation)
{
this._animation.removeEventListener(AnimationEvent.UPDATE, tweenUpdateHandler);
this._animation.removeEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler);
this._animation = null;
}
//don't animate on livepreview!
if(this.isLivePreview || !this.getStyleValue("animationEnabled"))
{
this.drawMarkers(endValues);
}
else
{
var animationDuration:int = this.getStyleValue("animationDuration") as int;
var animationEasingFunction:Function = this.getStyleValue("animationEasingFunction") as Function;
this._animation = new Animation(animationDuration, startValues, endValues);
this._animation.addEventListener(AnimationEvent.UPDATE, tweenUpdateHandler);
this._animation.addEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler);
this._animation.easingFunction = animationEasingFunction;
}
}
打开单元com.yahoo.astra.fl.charts.series.BarSeries.as找到draw方法
override protected function draw():void
{
super.draw();
this.graphics.clear();
//if we don't have data, let's get out of here
if(!this.dataProvider)
{
return;
}
this.graphics.lineStyle(1, 0x0000ff);
//grab the axes
var cartesianChart:CartesianChart = this.chart as CartesianChart;
var valueAxis:IOriginAxis = cartesianChart.horizontalAxis as IOriginAxis;
var otherAxis:IAxis = cartesianChart.verticalAxis;
var horizontalAxis:IAxis=cartesianChart.horizontalAxis;//获取Y轴的信息
if(!valueAxis)
{
throw new Error("To use a BarSeries object, the horizontal axis of the chart it appears within must be an IOriginAxis.");
return;
}
var markerSizes:Array = [];
var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart);
var totalMarkerSize:Number = this.calculateTotalMarkerSize(otherAxis, markerSizes);
var seriesIndex:int = allSeriesOfType.indexOf(this);
var markerSize:Number = markerSizes[seriesIndex] as Number;
var yOffset:Number = this.calculateYOffset(valueAxis, otherAxis, markerSizes, totalMarkerSize, allSeriesOfType);
var startValues:Array = [];
var endValues:Array = [];
var itemCount:int = this.length;
for(var i:int = 0; i < itemCount; i++)
{
var originValue:Object = this.calculateOriginValue(i, valueAxis, allSeriesOfType);
var originPosition:Number = valueAxis.valueToLocal(originValue);
var value:Object=IChart(this.chart).itemToAxisValue(this,i,horizontalAxis,false);//根据Y轴信息来获取值
var position:Point = IChart(this.chart).itemToPosition(this, i);
var marker:DisplayObject = this.markers[i] as DisplayObject;
marker.y = position.y + yOffset;
marker.height = markerSize;
var caption:TextField=new TextField ();//创建一个对象
caption.y=1;
caption.x=position.x/2;//显示在柱子的中间
caption.text=String(value);//显示值
UIComponent(marker).addChild(caption);//加入对象
//if we have a bad position, don't display the marker
if(isNaN(position.x) || isNaN(position.y))
{
this.invalidateMarker(ISeriesItemRenderer(marker));
}
else if(this.isMarkerInvalid(ISeriesItemRenderer(marker)))
{
//initialize the marker to the origin
marker.x = originPosition;
marker.width = 0;
if(marker is UIComponent)
{
(marker as UIComponent).drawNow();
}
this.validateMarker(ISeriesItemRenderer(marker));
}
//stupid Flash UIComponent rounding!
position.x = Math.round(position.x);
originPosition = Math.round(originPosition);
var calculatedWidth:Number = originPosition - position.x;
if(calculatedWidth < 0)
{
calculatedWidth = Math.abs(calculatedWidth);
position.x = Math.round(originPosition);
//always put the marker on the origin
marker.x = position.x;
}
startValues.push(marker.x, marker.width);
endValues.push(position.x, calculatedWidth);
}
//handle animating all the markers in one fell swoop.
if(this._animation)
{
this._animation.removeEventListener(AnimationEvent.UPDATE, tweenUpdateHandler);
this._animation.removeEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler);
this._animation = null;
}
//don't animate on livepreview!
if(this.isLivePreview || !this.getStyleValue("animationEnabled"))
{
this.drawMarkers(endValues);
}
else
{
var animationDuration:int = this.getStyleValue("animationDuration") as int;
var animationEasingFunction:Function = this.getStyleValue("animationEasingFunction") as Function;
this._animation = new Animation(animationDuration, startValues, endValues);
this._animation.addEventListener(AnimationEvent.UPDATE, tweenUpdateHandler);
this._animation.addEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler);
this._animation.easingFunction = animationEasingFunction;
}
}
增加右键菜单的功能是因为我想在鼠标移动到某个柱子上面,可以有相应的交互。其实用EXT的menu也可以实现,但是会有响应的问题,也就是说会发生点击不准,响应失败的问题,而用flash的右键,则不会产生这种问题,而且更加的好看。响应的修改方法如下:
打开Charts.as.
首先在flash中增加方法,可以接受JS传来的右键菜单名:找到方法initializeComponent,在其中加入
//custom ExternalInterface.addCallback("setMenuInfo",setMenuInfo)
实现setMenuInfo方法
public function setMenuInfo(value:String):void{
var info:Object = JSON.decode(value as String);
_menuInfo=info;
}
增加Charts的本地变量_menuInfo
protected var _menuInfo:Object;
public function get MenuInfo():Object{
return this._menuInfo;
}
这样flash就能接受菜单信息了。接受到菜单信息后,就可以建立菜单了。建立菜单我是安排在数据显示的时候,因为想这种只是针对series的右键菜单,必须在Series中生成,而且是针对每个Series创建。
com.yahoo.astra.fl.series.Series.as是ColumnSeries和BarSeries的基类,所以在这里就可以解决掉问题。
加入菜单对象
private var _menuInfo:Object;
public function get menuInfo():Object{
return this._menuInfo;
}
public function set menuInfo(value:Object):void{
this._menuInfo=value;
build_right_click_menu();
}
private function onContextMenuHandler(event:ContextMenuEvent):void
{
}
private function build_right_click_menu(): void {
//本函数主要的难点在于鼠标对象的获取
var cm:ContextMenu = new ContextMenu();
cm.addEventListener(ContextMenuEvent.MENU_SELECT, onContextMenuHandler);
cm.hideBuiltInItems();
var items:Array=this._menuInfo.items as Array;
for (var i:int=0;i<items.length;i++){
var item:String=items[i];
var fs:ContextMenuItem = new ContextMenuItem(item);
fs.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,function doSomething(e:ContextMenuEvent):void {
//获取当前Series不能用this,用如下方法,可以获取当前的Series
var ser:Series=Series(e.contextMenuOwner);
//获取当前菜单序号
var index:int=-1;
for (var j:int=0;j<(ser._menuInfo.items as Array).length;j++){
if (ser._menuInfo.items[j]==ContextMenuItem(e.currentTarget).caption)
index=j;
}
if(ExternalInterface.available){
var param:String=JSON.encode(_lastItem);//将对象编码为字符串
ExternalInterface.call("executeFlashMenu",param,index);
}////执行JS函数
});
cm.customItems.push( fs );
}
// OFC CREDITS
this.contextMenu = cm;
}
private var _lastItem:Object;//鼠标当前停留的item对象,利用以下方法进行获取
protected function markerRollOverHandler(event:MouseEvent):void
{
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
var index:int = this.itemRendererToIndex(itemRenderer);
var item:Object = this.dataProvider[index];
_lastItem=item;
var rollOver:ChartEvent = new ChartEvent(ChartEvent.ITEM_ROLL_OVER, index, item, itemRenderer, this);
this.dispatchEvent(rollOver);
}
在Charts的setDataProvider方法中加入
public function setDataProvider(value:Array):void
{
var dataProvider:Array = [];
var seriesCount:int = value.length;
var seriesStyles:Array = [];
for(var i:int = 0; i < seriesCount; i++)
{
var dataFromJavaScript:Object = value[i];
var currentData:ISeries = this.chart.dataProvider[i] as ISeries;
var seriesType:Class = SeriesSerializer.shortNameToSeriesType(dataFromJavaScript.type ? dataFromJavaScript.type : this.type);
var series:ISeries;
if(currentData && getDefinitionByName(getQualifiedClassName(currentData)) == seriesType)
{
//reuse the series if possible because we want animation
series = SeriesSerializer.readSeries(dataFromJavaScript, currentData);
}
else
{
series = SeriesSerializer.readSeries(dataFromJavaScript);
}
dataProvider[i] = series;
series.menuInfo=this._menuInfo;//将菜单信息赋值给每个柱子对象
//this is where we parse the individual series styles, and we convert them
//to the format the chart actually expects.
//we fill in with defaults for styles that have not been specified
if(dataFromJavaScript.style)
{
seriesStyles.push(dataFromJavaScript.style);
}
else seriesStyles.push(null);
}
EXT的JS修改如下
重载Ext.chart.StackedBarChart 的refresh方法,
refresh : function(){
if (this.store==undefined)
{
return ;
}
var styleChanged = false;
// convert the store data into something YUI charts can understand
var data = [], rs = this.store.data.items;
for(var j = 0, len = rs.length; j < len; j++){
data[j] = rs[j].data;
}
var Number=40;
//this.setHeight(Number*rs.length);
//make a copy of the series definitions so that we aren't
//editing them directly.
var dataProvider = [];
var seriesCount = 0;
var currentSeries = null;
var i = 0;
if(this.series){
seriesCount = this.series.length;
for(i = 0; i < seriesCount; i++){
currentSeries = this.series[i];
var clonedSeries = {};
for(var prop in currentSeries){
if(prop == "style" && currentSeries.style !== null){
clonedSeries.style = Ext.encode(currentSeries.style);
styleChanged = true;
//we don't want to modify the styles again next time
//so null out the style property.
// this causes issues
// currentSeries.style = null;
} else{
clonedSeries[prop] = currentSeries[prop];
}
}
dataProvider.push(clonedSeries);
}
}else {
}
if(seriesCount > 0){
for(i = 0; i < seriesCount; i++){
currentSeries = dataProvider[i];
if(!currentSeries.type){
currentSeries.type = this.type;
}
currentSeries.dataProvider = data;
}
} else{
dataProvider.push({type: this.type, dataProvider: data});
}
if (this.menuTitles!='')//增加menuTitles属性
{
this.swf.setMenuInfo(this.menuTitles);//调用flash的setMenuInfo方法
}
try{
this.swf.setDataProvider(dataProvider);
}catch(e){
}
}
//增加属性
menuTitles:'',
setMenuInfo:function(infoStr){ //设置菜单值
this.menuTitles=infoStr;
}
增加函数 ,可以由点中flash右键菜单的时候调用
function executeFlashMenu(dataItem,itemIndex){}
组织菜单对象,然后传入至flash
var items=new Array();
for (var i=0;i<menuInfo.items.length ;i++ )
{
items.push(menuInfo.items[i].text);
}
var menus=new Object();
menus.items=items;
stackedBarChart.setMenuInfo(Ext.util.JSON.encode(menus));