PDA

View Full Version : Ext.ux.chart.Histogram(non-flash version)



cdj
1 Aug 2009, 4:50 AM
hi ~my friends
i'd like to share my extension,
there was a Ext.chart already available,but it goes along with the adobe flash.my solution is totally pure javascript(thanks to google iecanvas).so ~if you don't want use flash in application,you can use my extension instead.:>
wish you have interesting with it.:D
我希望与大家分享我的成果,Ext.ux.chart.Histogram,这是我的第一个extension,希望大家对它感点兴趣。
below is the constructor code:
以下是我的构造器代码:


Ext.ns("Ext.ux.chart");

/**
* @class Ext.ux.chart 图表控件基类
* @author cdj
* @version beta1.4.4
*/

Ext.ux.chart = Ext.extend(Ext.Component, {
/**
* @cfg 内框的高度
*/
width:300,
/**
* @cfg 内框的高度
*/
height:160,
/**
* @cfg 内框的间距,上右下左的顺序
*/
offsets:'5,40,5,5',
/**
* @cfg 基础css类
*/
baseCls:'ux-chart',
/**
* @cfg 边框的厚度
*/
borderWidth:1,
/**
* @cfg store
*/
store:{},
initComponent:function(){
Ext.ux.chart.superclass.initComponent.call(this);
this.addEvents(
//supported event list here
);
this.ds = this.store;
},

onRender:function(ct,pos){
this.preCreateSkeleton();
this.createSkeleton(ct,pos);
this.createXHandler();//behavior

this.romCVX.init();
this.ramCVX.init();
this.ds.on('load',this.onLoad,this);
},

/**
* 计算出用于构建图表结构的一些数据
*/
preCreateSkeleton:function(){
var _off = this.offsets.split(',');
Ext.applyIf(this,{
nOffset:Ext.isGecko?new Number(_off[0])+1:new Number(_off[0]),//火狐上有一点点偏差
eOffset:new Number(_off[1]),
sOffset:new Number(_off[2]),
wOffset:Ext.isGecko?new Number(_off[3])+7:new Number(_off[3])
});
this.chartHeight = this.height + this.sOffset + this.nOffset;//整个
this.chartWidth = this.width + this.wOffset + this.eOffset;

},

/**
* 创建图表文档元素的骨架,将几个文档元素都创建好
* @param ct 图表文档结构的容器
* @param pos
*/
createSkeleton:function(ct,pos){
var ramLayerId = Ext.id("","ui2-chart-ram-cvs-");
var romLayerId = Ext.id("","ui2-chart-rom-cvs-");
var labelLayerId = Ext.id("","ui2-chart-label-");//标签区
var evLayerId = Ext.id("","ui2-chart-ev-");//标签区
var tl = "position:absolute;left:0px;top:0px;";
this.style += "position:relative;";
var tpl = new Ext.XTemplate(
'<div>',
'<canvas id ='+romLayerId+' width = "'+this.chartWidth+'" height = "'+this.chartHeight+'" style='+tl+'background:'+this.bgColor+'></canvas>',//things that wouldn't be changed
'<div id = "'+labelLayerId+'" style = "position:absolute;left:0px;top:0px;"></div>',//标签文字区
'<canvas id ='+ramLayerId+' width = "'+this.chartWidth+'" height = "'+this.chartHeight+'" style='+tl+'></canvas>',//things that would be changed in run-time
'<div id = "'+evLayerId+'" style = "position:absolute;left:0px;top:0px;"></div>',//事件侦听区
'</div>'
);
this.el = tpl.append(ct,"",true);
if(this.baseCls){
this.el.addClass(this.baseCls);
}
if(this.cls){
this.el.addClass(this.cls);
}
if(this.id){
this.el.dom.id = this.el.id = this.id;
}
this.evct = Ext.get(evLayerId);
var ramCV = this.el.child("#"+ramLayerId,true);
this.lbct = Ext.get(labelLayerId);
var romCV = this.el.child("#"+romLayerId,true);
if(Ext.isIE){
ramCV = Ext.get(window.G_vmlCanvasManager.initElement(ramCV)).dom;
romCV = Ext.get(window.G_vmlCanvasManager.initElement(romCV)).dom;
}
this.ramCVX = ramCV.getContext('2d');
this.romCVX = romCV.getContext('2d');

},

/**
* rom层初始化方法 override it
*/
romLayerInitial:Ext.emptyFn,

/**
* ram层初始化方法 override it
*/
ramLayerInitial:Ext.emptyFn,

/**
* ram层画图方法 override it
*/
draw:Ext.emptyFn,

/**
* ram层重画方法 override it
*/
redraw:Ext.emptyFn,

createXHandler:function(){
Ext.applyIf(this,{
gridBgColor:this.colors.gridbg,
gridColor:this.colors.grid,
frameColor:this.colors.frame,
lineColor:this.colors.line
});

this.romCVX.init = this.romLayerInitial.createDelegate(this.romCVX,[this]);
delete this.romLayerInitial;

Ext.apply(this.ramCVX,{
init :this.ramLayerInitial.createDelegate(this.ramCVX,[this]),
draw :this.draw.createDelegate(this.ramCVX,[this]),
redraw:this.redraw.createDelegate(this.ramCVX,[this])
});

delete this.romLayerInitial;
delete this.draw;
delete this.redraw;
},

addLabel:function(conf){
var lbf = {};
Ext.apply(lbf,conf,{
id:Ext.id('','ui2-monitor-label-'),
position:[0,0],
tag:'span'
});
if(typeof lbf.style != "undefined"){
lbf.style += ";font-size:12px;white-space:nowrap";
}else {
lbf.style = "font-size:12px;white-space:nowrap";
}
if(typeof lbf.p == "undefined"){
lbf.p = lbf.position;
}
//下面的这个font-family害人啊,设置成‘宋体’ie上就不能竖排了
lbf.style += (";font-family:tahoma,arial,helvetica,sans-serif;position:absolute;left:"+lbf.p[0]+"px;top:"+lbf.p[1]+"px");
delete lbf.p;
delete lbf.position;
if(lbf.vmode){
lbf.style += ';writing-mode:tb-rl;';
delete lbf.vmode;
}

if(typeof lbf.background != "undefined"){
lbf.style += (";background:"+lbf.background);
}
var label = this.lbct.createChild(lbf,'',false);
return label;
},
getStore:function(){
return this.store;
},
/**
* override it
*/
onLoad:Ext.emptyFn
})

/**
* 直方图构造器
* @author cdj
* @usage:
* var histogram = new Ext.ux.chart.Histogram({
//renderTo:cdjel,
width:300,
height:200,
colSize:20,
gapSize:0.9,
_coloffset:30,
offsets:'10,0,80,10',
valueField:'value',
nameField:'name',
store:new Ext.data.SimpleStore({
fields:["name","value"],
autoLoad:true,
url:'res.json'
}),
colors:{
gridbg:'rgb(241,233,196)',
grid:'rgb(196,196,196)',
frame:'rgb(0,0,0)',
line:'rgb(0,0,0)'
},
yAxis:[{
text:'10K',
value:10*1024
},{
text:'1K',
value:1*1024
},{
text:'500bytes',
value:500
},{
text:'100bytes',
value:100
},{
text:'10bytes',
value:10
}]
});
*/
Ext.ux.chart.Histogram = Ext.extend(Ext.ux.chart, {
xfontSize:12,
gapScale:0.2,
colsLeftOffset:20,//初始位移
xTickTpl:"",
/**
* @cfg 颜色
*/
colors:{
gridbg:'rgb(241,233,196)',
grid:'rgb(196,196,196)',
frame:'rgb(0,0,0)',
line:'rgb(0,0,0)'
},

/**
* rom层初始化方法
*/
romLayerInitial:function(chart){
chart.xScale = (chart.width - 2*chart.borderWidth)/chart.xAxisN;
chart.yScale = (chart.height - 2*chart.borderWidth)/chart.yAxis.length;
this.save();
this.translate(chart.wOffset,chart.nOffset);
this.fillStyle = chart.gridBgColor;
this.fillRect(0,0,chart.width,chart.height);
this.fill();
this.strokeStyle = chart.frameColor;
this.moveTo(0,0);
this.lineTo(0,chart.height);
this.lineTo(chart.width,chart.height);
this.lineTo(chart.width,0);
this.lineTo(0,0);
this.stroke();
// draw the grid and ticks

this.strokeWidth = chart.borderWidth;//add by cj

this.strokeStyle = chart.gridColor;
var _p = [];// position for label
var _s;// scale for each tick
for(var i = 0;i < chart.yAxis.length;i++){
_s = i*chart.yScale;
this.moveTo(0,_s);
this.lineTo(chart.width+5,_s);// this '5' is the small line for tick
_p[0] = chart.chartWidth - chart.eOffset + 6; // this '4' is just for adjust the tick's position
_p[1] = _s +3;
chart.addLabel({
position:_p,
//vmode:true,
html:chart.yAxis[i].text
})
}
this.stroke();
//eof draw
},
ramLayerInitial:function(chart){
this.save();
this.translate(chart.wOffset,chart.nOffset);
this.lineStyle = chart.lineColor;
},
/**
* @override 重写onload
*/
onLoad:function(){
this.doDataConvert();
this.ramCVX.clearRect(0,0,10000,10000);
if(this.evct.first()){
var id = this.evct.id;
Ext.destroy(this.evct);//reconstruct evct layer
this.evct = Ext.DomHelper.append(this.el,{
id:id
},true);
}else {
//do nothing
}
this.ramCVX.draw();
Ext.ux.chart.Histogram.superclass.onLoad.call(this);
},
/**
* @private 数据转化
* 这里只是将column modal里的数据转化为用于渲染的数据
* 这个数据里不包含store数据衍生出的数据,比如每个列的高度(像素值)
* 因为出于效率的考虑,各列的像素值的计算还是放在draw方法的store遍历里比较好
*/
doDataConvert:function(){
this.pd = {};//用于构图的数据,data for paint
var sow = (this.width-this.colsLeftOffset)/this.ds.getCount();//self owned width

//calculate gapWidth
var gw = sow*this.gapScale;

//calculate every ColumnWidth
var cw = sow - gw;
var cols = [];
for(var i=0,cm = this.columnModal;i<cm.length;i++){
//Ext.log(cm[i].width)
var _tpl = new Ext.XTemplate(cm[i].tipTpl||"{"+cm[i].dataIndex+"}");
_tpl.compile();
var c = {
width:cm[i].width*cw,
tipTpl:_tpl,
dataIndex:cm[i].dataIndex,
color:cm[i].color || "#000"
//,color:"rgba()"
};
cols.push(c);
}
var _xpl = new Ext.XTemplate(this.xTickTpl);
_xpl.compile();
Ext.apply(this.pd,{
gapWidth:gw,
xTpl:_xpl,
selfOwnedWidth:sow,
columnsWidth:cw,
colCfgs:cols
})
},
/**
* @override 重写了画图方法
*/
draw:function(chart){
var pd = chart.pd;
var cf = pd.colCfgs;
this.beginPath();
this.save();
this.translate(chart.colsLeftOffset+chart.borderWidth,chart.borderWidth);
var parseColor = Ext.ux.chart.ColorManager.parseColor.createDelegate(Ext.ux.chart.ColorManager);
chart.ds.each(function(r,i){
var _off = 0;//前面的柱子的宽度总和
for(var ii = 0;ii<cf.length;ii++){

var _x = i*pd.selfOwnedWidth+_off;
var _y = chart.getYPixelByValue(r.get(cf[ii].dataIndex));
var _w = cf[ii].width;
var _h = chart.height-2*chart.borderWidth-_y;
var _clor = parseColor(cf[ii].color);
//begin render column
var gradient = this.createLinearGradient(_x,_y,_x+_w,_y);
gradient.addColorStop(0, _clor.s);
gradient.addColorStop(1, _clor.e);
this.fillStyle = gradient;
this.fillRect(_x,_y,_w,_h);
this.fill();

//发光区
var m = parseInt(cf[ii].width*0.5);
var gradient2 = this.createLinearGradient(_x+m,_y,_x+_w,_y);
gradient2.addColorStop(0, 'rgba(235,245,235,0.2)');
gradient2.addColorStop(1, 'rgba(235,245,235,0.6)');
this.fillStyle = gradient2;
this.fillRect(_x+m,_y,_w-m,_h);
this.fill();
//eof render column

//add tip
var _ev = chart.addBlankImage({
p:[chart.wOffset+chart.colsLeftOffset+_x,_y+chart.nOffset],
height:_h,
width:_w,
background:''
});
new Ext.ToolTip({
target: _ev,
title: cf[ii].title || pd.xTpl.apply(r.data),
width:200,
html: cf[ii].tipTpl.apply(r.data),
trackMouse:true
});
//eof add tip

_off += _w;
}//eof pd cols

var tx= i*pd.selfOwnedWidth+0.5*pd.columnsWidth;
//TODO 画标签
if(Ext.isGecko){
this.save();
this.fillStyle = chart.lineColor;
this.translate(tx-parseInt(chart.xfontSize/6),chart.height+5);
this.rotate(90 * Math.PI / 180);
this.mozTextStyle = chart.xfontSize+"px simsun";
this.mozDrawText(pd.xTpl.apply(r.data));
this.restore();
}else {
//ie
chart.addLabel({
html:pd.xTpl.apply(r.data),
p:[chart.colsLeftOffset + tx-parseInt(chart.xfontSize/6)+chart.wOffset,chart.height+chart.nOffset+5],
vmode:true
})
}
},this);
this.closePath();
this.stroke();
this.restore();
},
/**
* 清空所有重建
*/
reconstruct:function(config){
Ext.apply(this,config);
//clear up rom and ram layer
//this.ramCVX.restore();
this.ramCVX.restore();
//this.ramCVX.restore();
this.ramCVX.clearRect(0,0,10000,10000);

if(this.evct.first()){
var id = this.evct.id;
Ext.destroy(this.evct);//reconstruct evct layer
this.evct = Ext.DomHelper.append(this.el,{
id:id
},true);
}else {
//do nothing
}
this.romCVX.restore();
this.romCVX.clearRect(0,0,10000,10000);

if(this.lbct.first()){
var id = this.lbct.id;
Ext.destroy(this.lbct);//reconstruct evct layer
this.lbct = Ext.DomHelper.append(this.el,{
id:id
},true);
}else {
//do nothing
}
this.romCVX.init();
this.ramCVX.init();


},
/**
* 清空重画
*/
redraw:function(){
this.ramCVX.clearRect(0,0,10000,10000);
if(this.evct.first()){
var id = this.evct.id;
Ext.destroy(this.evct);//reconstruct evct layer
this.evct = Ext.DomHelper.append(this.el,{
id:id
},true);
}else {
//do nothing
}
this.ramCVX.draw();
},
/**
* @private
* @describe addBlankImage
*/
addBlankImage:function(conf){
var lbf = {};
Ext.apply(lbf,conf,{
position:[0,0],
src:Ext.BLANK_IMAGE_URL,
tag:'img',
width:10,
height:10
});
if(lbf.style){
lbf.style += ';font-size:12px;white-space:nowrap';
}else {
lbf.style = 'font-size:12px;white-space:nowrap';
}
if(!lbf.p){
lbf.p = lbf.position;
}
lbf.style += (";position:absolute;left:"+lbf.p[0]+"px;top:"+lbf.p[1]+"px;height:"+lbf.height+"px;width:"+lbf.width);
delete lbf.p;
delete lbf.position;
if(lbf.vmode){
lbf.style += ';writing-mode:tb-rl';
delete lbf.vmode;
}
if(lbf.background){
lbf.style += (";background:"+lbf.background);
}
var label = this.evct.createChild(lbf,'',false);
return label;
},
/**
* @private
* @describe 根据Y轴坐标的配置以及所传的值算出坐标位置
* @returns Y坐标值
*/
getYPixelByValue:function(v){
var max,min;
for(var i = 0;i < this.yAxis.length;i++){
if(v >= this.yAxis[i].value){
max = i==0?v:this.yAxis[i-1].value;
min = this.yAxis[i].value;
break;
}else {
continue;
}
}
var i = i-1;
if(typeof min == 'undefined'){
min = 0;
max = this.yAxis[i].value;
}
var result;
if(max == min){
result = 0;
}else{
result = (1-(v - min)/(max - min) + i)*this.yScale;
}
return result;
}
});

Ext.ux.chart.ColorManager = {
parseColor: function(str){

var result;

// rgb(num,num,num)
if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)))
return this.getColor(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));

// rgba(num,num,num,num)
if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))
return this.getColor(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4]));

// rgb(num%,num%,num%)
if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)))
return this.getColor(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);

// rgba(num%,num%,num%,num)
if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))
return this.getColor(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));

// #a0b1c2
if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)))
return this.getColor(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));

// #fff
if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)))
return this.getColor(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));

// Otherwise, we're most likely dealing with a named color.
var name = str.trim().toLowerCase();
if(name == 'transparent'){
return this.getColor(255, 255, 255, 0);
}
return ((result = this.colors[name])) ? this.getColor(result[0], result[1], result[2]) : false;
},
getColor: function(r, g, b, a){
this.rgba = ['r','g','b','a'];
var x = 4;
while(-1<--x){
this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
}
return this.normalize();
},
normalize: function(){
var limit = this.limit;
this.r = limit(parseInt(this.r), 0, 255);
this.g = limit(parseInt(this.g), 0, 255);
this.b = limit(parseInt(this.b), 0, 255);
this.a = limit(this.a, 0, 1);
return {s:"rgba("+this.r+","+this.g+","+this.b+","+this.a+")",e:"rgba("+this.r+","+this.g+","+this.b+","+(this.a-0.5)+")"};
//return "rgba("+this.r+","+this.g+","+this.b+","+this.a+")";
},
limit: function(val,minVal,maxVal){
return Math.max(Math.min(val, maxVal), minVal);
},
colors : {
aqua:[0,255,255],
azure:[240,255,255],
beige:[245,245,220],
black:[0,0,0],
blue:[0,0,255],
brown:[165,42,42],
cyan:[0,255,255],
darkblue:[0,0,139],
darkcyan:[0,139,139],
darkgrey:[169,169,169],
darkgreen:[0,100,0],
darkkhaki:[189,183,107],
darkmagenta:[139,0,139],
darkolivegreen:[85,107,47],
darkorange:[255,140,0],
darkorchid:[153,50,204],
darkred:[139,0,0],
darksalmon:[233,150,122],
darkviolet:[148,0,211],
fuchsia:[255,0,255],
gold:[255,215,0],
green:[0,128,0],
indigo:[75,0,130],
khaki:[240,230,140],
lightblue:[173,216,230],
lightcyan:[224,255,255],
lightgreen:[144,238,144],
lightgrey:[211,211,211],
lightpink:[255,182,193],
lightyellow:[255,255,224],
lime:[0,255,0],
magenta:[255,0,255],
maroon:[128,0,0],
navy:[0,0,128],
olive:[128,128,0],
orange:[255,165,0],
pink:[255,192,203],
purple:[128,0,128],
violet:[128,0,128],
red:[255,0,0],
silver:[192,192,192],
white:[255,255,255],
yellow:[255,255,0]
}
};
Ext.reg('ux-chart-histogram', Ext.ux.chart.Histogram);
and below is the usage example:



Ext.BLANK_IMAGE_URL = 'lib/ext/resources/images/default/s.gif';

Ext.onReady(function(){
//报表实验
//new a chart
var histogram = new Ext.ux.chart.Histogram({
width:700,
height:300,
//renderTo:cdjel,
offsets:'10,40,80,10',
store:new Ext.data.SimpleStore({
fields:["name","value1","value2"],
autoLoad:true,
url:'res.json'
}),
xTickTpl:"2009 年{name}",
columnModal:[{
dataIndex:'value1',
width:0.5,
tipTpl:"链路一流量是{value1}",
color:'olive'
//color:"rgba(0,0,0,1)"
},{
dataIndex:'value2',
//tipTpl:"链路二流量是{value1}",
//color:'purple',
width:0.2
},{
dataIndex:'value2',
width:0.3,
tipTpl:"链路三流量是{value2}",
color:'navy'
}],
colors:{
gridbg:'rgb(241,233,196)',
grid:'rgb(196,196,196)',
frame:'rgb(0,0,0)',
line:'rgb(0,0,0)'
},
yAxis:[{
text:'10K',
value:10*1024
},{
text:'1K',
value:1*1024
},{
text:'500bytes',
value:500
},{
text:'100bytes',
value:100
},{
text:'10bytes',
value:10
}],
listeners:{
render:function(){
//Ext.log("my chart rendered");
}
}
});


var win = new Ext.Window({
width:800,
height:460,
title:'测试图表',
items:[histogram]
})
win.show();

})

in this example,your data from the server should looked like this :


[
['一月','1020','100'],
['二月','300','500'],
['三月','500','200'],
['四月','200','400'],
['一月','200','700'],
['二月','300','100'],
['三月','500','600'],
['四月','1000','100']
]
there is a screenshot and a demo in the attachment.
the demo which is without ext lib in it,please copy a ext lib file into the right folder when you run it,it's compatible with Ext2.0 & 3.0

notice that :
if you wanna use this extension in ie,you must include the "google iecanvas" in your page


<!--[if IE]><script type="text/javascript" src="lib/mylib/excanvas.js"></script><![endif]-->
.its size is just 27k.

I'd appreciate comments and/or suggestions.

sorry for my broken english,i hope you could understand what i wrote:">

moussetique
17 Dec 2009, 3:09 AM
Very great code. :)
Thx