- 25年過(guò)去了,Brooks博士著名的“沒(méi)有銀彈”的論斷依舊沒(méi)有被打破。HTML5也是一樣。但這并不妨礙HTML5是一個(gè)越來(lái)越有威力的“炸彈”:發(fā)展迅速、勢(shì)不可擋。隨著HTML5技術(shù)的普及,用HTML5做可視化呈現(xiàn)的項(xiàng)目越來(lái)越多了。HTML5的優(yōu)勢(shì)明顯:網(wǎng)頁(yè)上直接運(yùn)行無(wú)需插件、手機(jī)平板方便兼容、代碼開(kāi)發(fā)和維護(hù)相對(duì)容易,等等。一大波一大波的做Java、.NET甚至C++桌面的程序老手們都紛紛開(kāi)始研究javascript了,而初出茅廬的新一代程序猿更是義無(wú)反顧的直奔HTML5這個(gè)技術(shù)大熱點(diǎn)而來(lái)。
HTML5涵蓋的技術(shù)點(diǎn)很多,甚至延伸到了前端、后端、通訊等各個(gè)層面。前端的canvas繪圖這塊無(wú)疑是它的核心內(nèi)容。Canvas的API雖然不是很復(fù)雜很強(qiáng)大,但是做一般的2d繪圖基本都?jí)蛴昧恕;谶@些API,一大堆的2d繪圖組件紛紛出爐。Echarts、d3.js都是很不錯(cuò)的項(xiàng)目。Echarts主要是chart組件,而d3相對(duì)雜一些,很多呈現(xiàn)方式很有創(chuàng)意,值得研究。
研究d3的起因是最近有一個(gè)項(xiàng)目,用戶截了一張效果圖讓我們用HTML5做一下:

看著很眼熟,搜了一下,感覺(jué)就是d3例子中的sunburst效果,程序在這里:
http://bl.ocks.org/mbostock/4063423
看上去似乎也不難,就是一圈一圈的餅圖,把樹(shù)狀結(jié)構(gòu)數(shù)據(jù)按占比一層一層繪制上去就行了。所以引起了自己動(dòng)手做一個(gè)的興趣。“sunburst”英文里應(yīng)該是“云開(kāi)日出”的意思,類似強(qiáng)烈的光芒從云層背后透射出來(lái),不知為何中文里大多把它翻譯成“日落”。比如這把Fender Telecaster吉他型號(hào)是Brown Sunburst款,就會(huì)被大家翻譯成“日落色”。

這種圖最先是由布朗大學(xué)教授John T. Stasko設(shè)計(jì)。
http://www.cc.gatech.edu/~john.stasko/
經(jīng)過(guò)一天的折騰,終于做出了一個(gè)還算過(guò)得去的“彩虹爆炸圖”。先上個(gè)圖看看:

主要功能包括:
-可以通過(guò)json來(lái)定義數(shù)據(jù)和樣式(類似百度的echarts那樣);
-顏色可以固定,也可以自動(dòng)彩虹色;
-自動(dòng)計(jì)算數(shù)值及角度占比;
-動(dòng)態(tài)顯示導(dǎo)航路徑;
-鼠標(biāo)動(dòng)態(tài)高亮顯示路徑;
-動(dòng)畫(huà)飛入、展開(kāi)導(dǎo)航路徑;
-文字顯示及角度控制;
-全矢量,可鼠標(biāo)縮放、平移,不失真;
下面重點(diǎn)碼一下折騰過(guò)程中的幾個(gè)重點(diǎn):
一、定義節(jié)點(diǎn)對(duì)象
首先定義每一個(gè)小扇片節(jié)點(diǎn)。每個(gè)扇片可以用一段餅圖來(lái)繪制。為了簡(jiǎn)單方便,這里用了最簡(jiǎn)單高效偷懶的方法:用一個(gè)半徑很粗的線畫(huà)一段角度的arc,即可。如下圖:

另外還有文字等內(nèi)容。所以定義它的json結(jié)構(gòu)大概如下:
var item = {name: '名稱', color: 'red', angle: '45', …};
此外,下一圈的數(shù)據(jù),可直接定義為這個(gè)節(jié)點(diǎn)的“孩子節(jié)點(diǎn)”,直接在item中定義一個(gè)data的子節(jié)點(diǎn)數(shù)據(jù):
var item = {name: '名稱', color: 'red', angle: '45', data:[
{name:’孩子一’, color:’green’,…},
{name:’孩子二’, color:’yellow’,…},
]};
這樣就可以組成一個(gè)樹(shù)狀結(jié)構(gòu)。接下來(lái)要在canvas上繪制圖形了。為了方便,這里直接使用了矢量圖進(jìn)行定義:
twaver.Util.registerImage('node', {
v: [{
shape: 'circle',
r:
lineColor: function(data,view){return data.getClient("lineColor");},
lineWidth:
startAngle:
endAngle:
},{
shape: 'text',
textBaseline: 'middle',
textAlign:
text:
x:
y:
font:
fill:
rotate:
visible:
shadow:
}],
});矢量圖中定義了2個(gè)圖形元素:一個(gè)arc弧線、一個(gè)文字對(duì)象,分別用于畫(huà)node和繪制其文字。顏色、字體、是否可見(jiàn)、陰影、對(duì)齊、位置、線寬、角度…等等均在上面的定義中用一個(gè)function動(dòng)態(tài)獲取。例如,這個(gè)節(jié)點(diǎn)的半徑,通過(guò)下面的方法,就可以在圖形的lineColor屬性中保存并驅(qū)動(dòng),需要修改,直接修改lineColor這個(gè)client屬性即可,而不用去修改繪圖參數(shù),非常方便:r:function(data,view){return data.getClient("lineColor");}
二、文字控制文字控制也比較啰嗦。首先是對(duì)齊方式。最簡(jiǎn)單的方式當(dāng)然是讓文字在所在扇片處,直接居中、旋轉(zhuǎn)。這樣文字會(huì)在徑向的中間位置,如下圖:




shadow: {
offsetX: 2,
offsetY: 2,
blur: 4,
color: 'black',
},


三、生成彩虹顏色
關(guān)于顏色,是一個(gè)有趣的話題。對(duì)于廣大程序猿來(lái)說(shuō),顏色是一個(gè)既簡(jiǎn)單又困難的東西。我們隨手就能寫(xiě)下’red’, ‘green’, ‘orange’, ‘yellow’這樣的色彩斑斕的顏色,還能保證沒(méi)有語(yǔ)法錯(cuò)誤;我們還會(huì)寫(xiě)’#FF55AA’、’#0c0’、’RGB(0,204,0)’、’ RGB(0%,80%,0%)’這樣的各種顏色寫(xiě)法;我們也明白R(shí)GBA的含義和用途。但是,我們很少能把一個(gè)demo寫(xiě)的顏色很好看、很搭配。關(guān)于顏色和配色以后再專門(mén)討論。這里我們只想自動(dòng)生成一圈彩虹一樣的顏色。用我們熟悉的RGB方法好像比較困難了。于是想起了那個(gè)HSV的顏色定義方法,它貌似很適合解決這個(gè)問(wèn)題。

HSV顏色模型定義了色調(diào)H、飽和度S和亮度V,由A. R. Smith在1978年創(chuàng)建的一種顏色空間。其中H用一圈360度表示所有顏色,從紅色開(kāi)始按逆時(shí)針?lè)较蛴?jì)算,紅色為0度。飽和度S從0到1,越大越飽和。亮度V從0到255(也可以轉(zhuǎn)換為從0到1,方便使用),越大越明亮,越小越暗淡。
Js里面并沒(méi)有直接處理HSV顏色的函數(shù)。不過(guò)用下面的代碼很方便可以從hsv轉(zhuǎn)為rgb:

function getHSVColor(h, s, v) {
var r, g, b, i, f, p, q, t;
if (h && s === undefined && v === undefined) {
s = h.s, v = h.v, h = h.h;
}
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
var rgb='#'+toHex(r * 255)+toHex(g * 255)+toHex(b * 255);
return rgb;
}
var toAngle=node.getClient(‘toAngle’);
var level=node.getClient(‘level’);//節(jié)點(diǎn)在第幾圈
var h = (fromAngle+to)/2 % 360 /360; //中心角度,并轉(zhuǎn)換為弧度
var s = Math.max(0.2, 1-level*0.1);//每圈s遞減0.1,直到0.2為止
var v=1;
var color=getHSVColor(h, s, v);

如果相對(duì)某個(gè)節(jié)點(diǎn)的顏色做特殊處理,例如強(qiáng)制用橙色來(lái)凸顯,我們可以在數(shù)據(jù)中定義時(shí)加個(gè)標(biāo)記,設(shè)置顏色時(shí)候直接使用而不用計(jì)算即可。

接下來(lái)要實(shí)現(xiàn)鼠標(biāo)劃過(guò)節(jié)點(diǎn),自動(dòng)計(jì)算路徑、高亮路徑節(jié)點(diǎn)、暗淡非路徑節(jié)點(diǎn)。為了方便路徑尋找,程序把每個(gè)節(jié)點(diǎn)的下一圈子數(shù)據(jù)定義為子節(jié)點(diǎn),子節(jié)點(diǎn)通過(guò)getParent()函數(shù)可以直接獲得父對(duì)象。這樣,通過(guò)不斷getParent就可以獲得整個(gè)路徑上的節(jié)點(diǎn),并修改其顏色為預(yù)設(shè)顏色,實(shí)現(xiàn)高亮效果:
while(node){
node.setClient(‘color’, node.getClient(‘color.original’));
node=node.getParent();
}
對(duì)于非路徑節(jié)點(diǎn)的顏色,可以設(shè)置為預(yù)設(shè)顏色但飽和度為0.1的淡顏色 ,讓它變淡,以便突出高亮路徑:
node.setClient(‘color’, color);
from: 0,
to: 1,
dur: 3000+level*100,
easing: 'elasticOut',
onUpdate: function (value) {
node.setLocation('pie.location’, value);
},
}).play();
上面定義的動(dòng)畫(huà),用3秒鐘跑完,用’elasticOut’的easing方式。每一幀,修改node的位置信息。這樣就完成了橡皮筋一樣的環(huán)形彈出散開(kāi)效果。
另外,導(dǎo)航條的出來(lái)也比較突兀,這里也使用一下動(dòng)畫(huà),讓它從左到右慢慢伸出:
from: {x:x1, y:y1},
to: {x:x2, y:y2},
delay:50,
type: 'point',
dur: 1000,
easing: 'bounceOut',
onUpdate: function (value) {
node.setCenterLocation(value.x, value.y);
},
}).play();


