cdj
2 Aug 2009, 7:21 PM
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
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.
to run this demo,you just need to take four steps.
1.download it.
2.decompress all files to the web application deployment folder(suppose you named"app").
your folder structure under the "app" should be :
/lib/mylib/excanvas.js
/lib/mylib/UI2.chart.Histogram.js
histogram.js
histogram.html
3.mkdir /lib/ext ,and copy the ext lib files to it
/lib/ext/resources/* (css and image files)
/lib/ext/ext-base.js
/lib/ext/ext-all.js
4.launch your server --> view the histogram.html.
you can modify my code as you wish, and use the modified code in your products.
notice that :
you must include the "google iecanvas" (which named excanvas.js) in your page,otherwise it won't work.
you can download it from my attachment or http://excanvas.sourceforge.net/
<!--[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 couldn't express my thinking very well,hope you could understand what i wrote:">
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
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.
to run this demo,you just need to take four steps.
1.download it.
2.decompress all files to the web application deployment folder(suppose you named"app").
your folder structure under the "app" should be :
/lib/mylib/excanvas.js
/lib/mylib/UI2.chart.Histogram.js
histogram.js
histogram.html
3.mkdir /lib/ext ,and copy the ext lib files to it
/lib/ext/resources/* (css and image files)
/lib/ext/ext-base.js
/lib/ext/ext-all.js
4.launch your server --> view the histogram.html.
you can modify my code as you wish, and use the modified code in your products.
notice that :
you must include the "google iecanvas" (which named excanvas.js) in your page,otherwise it won't work.
you can download it from my attachment or http://excanvas.sourceforge.net/
<!--[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 couldn't express my thinking very well,hope you could understand what i wrote:">