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方法:
New Document <script></script>Insert title here<script type="text/javascript" src="../ext-all-debug.js"></script><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>
注意:
1. 以上並不是一個完整與完美的代碼, 只是說明可以使用這種方式進行的POC的代碼
2. 排列的位置, 需要自行去運算出來,多行, 還是多列?
效果與總結
最後,給出一下筆者實現的效果
當然, 如果LegendItem 比較少的話, 分行與分列並沒有什麼意義。
比較適用的情境是: legendItem 很多,超過20 甚至30, 大大影響了頁面的感官。
不過實際開發中,這種狀況應該比較少。
如果實在有這種狀況, 就要從需求開始就的謹慎考量了,盡量避免出現這種需求, 因為即使開發出來的, 針對如此多的Series , 速度自然快不了。