Extjs 中的Chart 的legend.
Legend, 翻译过来的意思是图例。
在Extjs 的Chart 中, 到底代表什么呢? 直接看这张图:
右边红色框起来的部分就是Legend 了。
在 Extjs Chart 的定义中, 可以通过配置 legend 的配置值(configs)来设置Legend 显示的位置和样式:
position 配置显示的位置:可以设置的值有 "top","bottom", "left", "right", or "float"。
其他还可以设置图例显示的文字、图的样式等等, 详细可以参见 Ext.chart.Legend 的参考文档。
具体的设置类似:
Legend 如何分列或者分行 -- 历程
不管居于什么位置,Extjs Legend 默认的显示方式都是单行或是当列。(根据位置配置的差异)
查阅Legend 的参考文档的所有配置,属性和方法都找不到, 如何将Legend 可以分行或分列显示的方法。
倒是在 legendItem 的参考文档中,找到了 updatePosition( [relativeTo] )的方法。
Legend是整个图例, LegendItem 应该就是里面的每一项图例了。
于是产生了一种解法:
1. 是否可以在合适的时候, 设置legendItem 的位置
首先第一个问题, 什么是合适的时候? 是否可以在 afterRender 事件中,
但是实际测试发现, 在Chart 的afterRender 中, 图例并没有产生出来。 这个Form , Grid 的机制看上去不太一样。
没办法,只能先整一个按钮, 通过点击来看看这种解法是否可行。
1) 得到Chart 的legend
var chart = Ext.getCmp("testChart"); var legend = chart.legend;我的Chart Id 是: testChart
2) 得到ChartItems
var legendItems = legend.items;
3) 循环以上数组,设置各Item 的位置, 位置是自己计算出来的(x,y 坐标)
legendItem.updatePosition({x:xPos,y:yPos});位置的确是改变了。
到此, 问题并没有结束,
legend 上的Item 是可以进行点击的, 而且点击之后,相应的图是可以进行显示和隐藏的切换的。
使用以上方式, 发现点击之后, 又恢复原状了。
于是又产生了一种想法,
2. 是否可以给LegendItem 添加, click 事件, 在click 中重新计算位置并更新/
legendItem.on("click",function(){ legendItem.updatePosition({x:xPos,y:yPos}); });
这样的写法是否繁杂不说, 会发现一个问题:
就是legendItem 必须要点两次以上才能触发上面的function.
而且Dubug 发现, 此时Legend item 有两个click 事件,一个是原生的,一个是后来加的。
这样的方式肯定会存在问题。
所以以上的方式只能宣告失败。
Legend分列或者分行
看来只能从Extjs 的源码层级考虑了。
Extjs Chart 的最底层的技术无非是 SVG, VML.
关于此的详细介绍可以参考:
[Web Chart系列之一]Web端图形绘制SVG,VML, HTML5 Canvas 技术比较
而Extjs 的Chart 又是基于raphael 框架之上。 关于raphael 的详细,可以参考官方网站:
http://raphaeljs.com/
思考的历程就统统省去,直接给出结论就是: 重写 Ext.chart.LegendItem 和 Ext.chart.Legend 的 updatePosition方法:
<!--Add by oscar999--> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE> New Document </TITLE> <META NAME="Author" CONTENT="oscar999"> <script> </script> </HEAD> <BODY> </BODY> </HTML> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <script type="text/javascript" src="../ext-all-debug.js"></script> <link href="../resources/ext-theme-neptune/ext-theme-neptune-all.css" rel="stylesheet" type="text/css" /> <script> Ext.chart.LegendItem.override({ updateSpecPosition: function(positionTo) { var me = this, items = me.items, ln = items.length, i = 0, item; var posX = positionTo.x; var posY = positionTo.y; for (; i < ln; i++) { item = items[i]; switch (item.type) { case ‘text‘: item.setAttributes({ x: 20 + posX, y: posY }, true); break; case ‘rect‘: item.setAttributes({ translate: { x: posX, y: posY - 6 } }, true); break; default: item.setAttributes({ translate: { x: posX, y: posY } }, true); } } } }); Ext.chart.Legend.override({ updatePosition: function() { var me = this, items = me.items, pos, i, l, bbox; if (me.isDisplayed()) { pos = me.calcPosition(); me.x = pos.x; me.y = pos.y; //items[i].updatePosition({x:100,y:100}); var posX = me.x; var posY = me.y; for (i = 0, l = items.length; i < l; i++) { posX = me.x; posY = me.y; //items[i].updatePosition(); if(i%2>0) { posX += 80; } posY += parseInt(i/2)*30; items[i].updateSpecPosition({x:posX,y:posY}); } bbox = me.getBBox(); if (isNaN(bbox.width) || isNaN(bbox.height)) { if (me.boxSprite) { me.boxSprite.hide(true); } } else { if (!me.boxSprite) { me.createBox(); } me.boxSprite.setAttributes(bbox, true); me.boxSprite.show(true); } } } }); </script> <script> Ext.onReady(function(){ var store = Ext.create(‘Ext.data.JsonStore‘, { fields: [‘year‘, ‘comedy‘, ‘action‘, ‘drama‘, ‘thriller‘], data: [ {year: 2005, comedy: 34000000, action: 23890000, drama: 18450000, thriller: 20060000}, {year: 2006, comedy: 56703000, action: 38900000, drama: 12650000, thriller: 21000000}, {year: 2007, comedy: 42100000, action: 50410000, drama: 25780000, thriller: 23040000}, {year: 2008, comedy: 38910000, action: 56070000, drama: 24810000, thriller: 26940000} ] }); var chart = Ext.create(‘Ext.chart.Chart‘,{ id:‘testChart‘, animate: true, shadow: true, store: store, legend: { //position: ‘right‘, //position:‘float‘, position: ‘bottom‘, boxStroke: ‘#FFF‘, legendCol:2, update:false }, axes: [{ type: ‘Numeric‘, position: ‘bottom‘, fields: [‘comedy‘, ‘action‘, ‘drama‘, ‘thriller‘], title: false, grid: true, label: { renderer: function(v) { return String(v).replace(/(.)00000$/, ‘.$1M‘); } } }, { type: ‘Category‘, position: ‘left‘, fields: [‘year‘], title: false }], series: [{ type: ‘bar‘, axis: ‘bottom‘, gutter: 80, xField: ‘year‘, yField: [‘comedy‘, ‘action‘, ‘drama‘, ‘thriller‘], stacked: true, tips: { trackMouse: true, width: 65, height: 28, renderer: function(storeItem, item) { this.setTitle(String(item.value[1] / 1000000) + ‘M‘); } } }] }); var panel1 = Ext.create(‘widget.panel‘, { width: 800, height: 400, title: ‘Stacked Bar Chart - Movies by Genre‘, renderTo: Ext.getBody(), layout: ‘fit‘, tbar: [{ text: ‘Save Chart‘, handler: function() { Ext.MessageBox.confirm(‘Confirm Download‘, ‘Would you like to download the chart as an image?‘, function(choice){ if(choice == ‘yes‘){ chart.save({ type: ‘image/png‘ }); } }); } }], items: chart }); }); </script> </head> <body> <div id="my-div"></div> <div id="user-div" class="applicantItem" style="display:none"></div> </body> </html>
注意:
1. 以上并不是一个完整与完美的代码, 只是说明可以使用这种方式进行的POC的代码
2. 排列的位置, 需要自行去运算出来,多行, 还是多列?
效果与总结
最后,给出一下笔者实现的效果
当然, 如果LegendItem 比较少的话, 分行与分列并没有什么意义。
比较适用的场景是: legendItem 很多,超过20 甚至30, 大大影响了页面的感官。
不过实际开发中,这种状况应该比较少。
如果实在有这种状况, 就要从需求开始就的慎重考量了,尽量避免出现这种需求, 因为即使开发出来的, 针对如此多的Series , 速度自然快不了。