HTML5,不僅是看上去很美 (第一彈:彩虹爆炸圖)

前言

25年過去了,Brooks博士著名的「沒有銀彈」的論斷依舊沒有被打破。HTML5也是同樣。但這並不妨礙HTML5是一個愈來愈有威力的「炸彈」:發展迅速、勢不可擋。隨着HTML5技術的普及,用HTML5作可視化呈現的項目愈來愈多了。HTML5的優點明顯:網頁上直接運行無需插件、手機平板方便兼容、代碼開發和維護相對容易,等等。一大波一大波的作Java、.NET甚至C++桌面的程序老手們都紛紛開始研究javascript了,而初出茅廬的新一代程序猿更是義無反顧的直奔HTML5這個技術大熱點而來。javascript

HTML5涵蓋的技術點不少,甚至延伸到了前端、後端、通信等各個層面。前端的canvas繪圖這塊無疑是它的核心內容。Canvas的API雖然不是很複雜很強大,可是作通常的2d繪圖基本都夠用了。基於這些API,一大堆的2d繪圖組件紛紛出爐。Echarts、d3.js都是很不錯的項目。Echarts主要是chart組件,而d3相對雜一些,不少呈現方式頗有創意,值得研究。前端

概述

研究d3的原由是最近有一個項目,用戶截了一張效果圖讓咱們用HTML5作一下:java

圖片描述

看着很眼熟,搜了一下,感受就是d3例子中的sunburst效果,程序在這裏:node

http://bl.ocks.org/mbostock/4063423json

看上去彷佛也不難,就是一圈一圈的餅圖,把樹狀結構數據按佔比一層一層繪製上去就好了。因此引發了本身動手作一個的興趣。「sunburst」英文裏應該是「雲開日出」的意思,相似強烈的光芒從雲層背後透射出來,不知爲什麼中文裏大多把它翻譯成「日落」。好比這把Fender Telecaster吉他型號是Brown Sunburst款,就會被你們翻譯成「日落色」。canvas

圖片描述

關於日出和日落更喜歡哪個的問題,網上還真有這樣的調查。有意思的是,選擇喜歡日落的遠多於選擇日出的。日出表明但願,日落表明成熟,都是一種美,哪一個更美要看你我的的心境,由於它的美麗是由心生。爲了避免在這個問題上站錯對,咱們仍是給他從新起一個更加響亮霸氣的中文名字:「彩虹爆炸圖」,怎麼樣?後端

仔細研究一下彩虹爆炸圖的結構,無非就是一個樹形結構,並採用發射狀的佈局。根節點在中間(也能夠認爲沒有惟一的根,而是一堆根節點圍繞在第一圈),一次向外發散排列。每個節點有名稱、數值。節點能夠按照自身數值在扇區所佔比例進行繪製,這樣就不用管節點具體數值有多大多小了。echarts

這種圖最早是由布朗大學教授John T. Stasko設計。
http://www.cc.gatech.edu/~john.stasko/函數

通過一天的折騰,終於作出了一個還算過得去的「彩虹爆炸圖」。先上個圖看看:
圖片描述佈局

主要功能包括了:
• 能夠經過json來定義數據和樣式(相似百度的echarts那樣);
• 顏色能夠固定,也能夠自動彩虹色;
• 自動計算數值及角度佔比;
• 動態顯示導航路徑;
• 鼠標動態高亮顯示路徑;
• 動畫飛入、展開導航路徑;
• 文字顯示及角度控制;
• 全矢量,可鼠標縮放、平移,不失真;

下面重點碼一下折騰過程當中的幾個重點:

1、定義節點對象

首先定義每個小扇片節點。每一個扇片能夠用一段餅圖來繪製。爲了簡單方便,這裏用了最簡單高效偷懶的方法:用一個半徑很粗的線畫一段角度的arc,便可。以下圖:

圖片描述

另外還有文字等內容。因此定義它的json結構大概以下:

javascriptvar  item = {name: '名稱', color: 'red', angle: '45', …};

此外,下一圈的數據,可直接定義爲這個節點的「孩子節點」,直接在item中定義一個data的子節點數據:

javascriptvar  item = {name: '名稱', color: 'red', angle: '45', data:[
    {name:’孩子一’, color:’green’,…},
    {name:’孩子二’, color:’yellow’,…},
]};

這樣就能夠組成一個樹狀結構。接下來要在canvas上繪製圖形了。爲了方便,這裏直接使用了矢量圖進行定義:

javascripttwaver.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個圖形元素:一個arc弧線、一個文字對象,分別用於畫node和繪製其文字。顏色、字體、是否可見、陰影、對齊、位置、線寬、角度…等等均在上面的定義中用一個function動態獲取。例如,這個節點的半徑,經過下面的方法,就能夠在圖形的lineColor屬性中保存並驅動,須要修改,直接修改lineColor這個client屬性便可,而不用去修改繪圖參數,很是方便:

javascriptr:function(data,view){return data.getClient("lineColor");}

這裏有一個比較囉嗦的地方是:每一個扇片的角度須要根據每一個item定義的原始值進行計算角度佔比。並且,對於過小的扇片,能夠給必定的最小值(例如1度),保證能視覺上看到它。不然,顯示10000和1兩個數值,因爲對比過大,可能就杯具了,由於1連1度都佔不到,顯示效果會很是差。還有,每一個扇片之間應該儘可能留有必定的空隙。若是連續繪製,就會連成一片,沒有「分片」感。這些能夠在代碼中進行簡單控制。

2、文字控制

文字控制也比較囉嗦。首先是對齊方式。最簡單的方式固然是讓文字在所在扇片處,直接居中、旋轉。這樣文字會在徑向的中間位置,以下圖。

圖片描述

但這樣顯示感受並非很完美。對於中文來講,若是能統一靠近圓心方向的位置對齊,會更好看一些。這樣,即便文字過長,也會向外延伸,不會和裏面的重疊。以下圖:

圖片描述

還有,當文字在左半圓時,若是不作特殊處理,文字旋轉會致使文字大頭朝下,閱讀起來有把脖子歪斷的風險。因此應該動態判斷,若是文字在左側,應該文字再增長旋轉180度。同時左側的文字對齊也要特殊考慮,應該變成右對齊,才能保持徑向的整齊一致。

圖片描述

文字還有一個細節就是顏色和陰影的問題。不使用陰影,單純的使用顏色(例如白色),則在一些方向上的節點的文字會看不清楚,由於咱們作的是彩虹爆炸圖,各個方向顏色都不同,並且還會隨着圈數增長而變淡顏色,因此幾乎不可能用一個固定的顏色(例如白色或黑色)能保證文字在全部地方都能和node顏色搭配並看清楚。因此思來想去仍是使用了陰影效果。

聯想了一下咱們看美劇時候的字幕,彷佛也是一樣的問題。視頻字幕要顯示在變幻無窮的視頻場景裏面,視頻場景的顏色徹底隨機出現無從知曉,要想讓字幕看清楚,必然也會想一些辦法解決。咱們仔細觀察一下視頻字幕:

圖片描述

仔細觀察,字幕是白色文字加了一圈黑色外框,這樣就不怕任何場景了。咱們在文字定義時也模擬一下,設置陰影和陰影偏移試一試:

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

看一下使用前和使用後的效果對比:

圖片描述

使用陰影后不但文字更清晰了,並且也增長了立體感,效果仍是不錯的。下面圖顯示在應用在節點上的效果:

圖片描述

可見不論什麼顏色,都能比較好的勾勒出文字輪廓,保持清晰可讀。

3、生成彩虹顏色

關於顏色,是一個有趣的話題。對於廣大程序猿來講,顏色是一個既簡單又困難的東西。咱們隨手就能寫下’red’, ‘green’, ‘orange’, ‘yellow’這樣的色彩斑斕的顏色,還能保證沒有語法錯誤;咱們還會寫’#FF55AA’、’#0c0’、’RGB(0,204,0)’、’ RGB(0%,80%,0%)’這樣的各類顏色寫法;咱們也明白RGBA的含義和用途。可是,咱們不多能把一個demo寫的顏色很好看、很搭配。關於顏色和配色之後再專門討論。這裏咱們只想自動生成一圈彩虹同樣的顏色。用咱們熟悉的RGB方法好像比較困難了。因而想起了那個HSV的顏色定義方法,它貌似很適合解決這個問題。

HSV顏色模型定義了色調H、飽和度S和亮度V,由A. R. Smith在1978年建立的一種顏色空間。其中H用一圈360度表示全部顏色,從紅色開始按逆時針方向計算,紅色爲0度。飽和度S從0到1,越大越飽和。亮度V從0到255(也能夠轉換爲從0到1,方便使用),越大越明亮,越小越暗淡。

圖片描述

Js裏面並無直接處理HSV顏色的函數。不過用下面的代碼很方即可以從hsv轉爲rgb:

圖片描述

寫一個對應的js函數也很簡單:

javascript/* h, s, v (0 ~ 1) */
    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;
    }

再回到咱們的彩虹爆炸圖。每個節點對應的所在角度(中心角度)決定了它本身的顏色值。因此,咱們能夠直接根據這個角度獲得顏色的h。而後,爲了讓彩虹逐漸一圈一圈變淡,再把s飽和度從1逐圈遞減(例如0.1),產生變淡的效果。爲了防止圈太多最後看不清,減到0.2到0.3左右能夠中止遞減。

javascriptvar fromAngle=node.getClient(‘fromAngle’);
var toAngle=node.getClient(‘toAngle’);
var level=node.getClient(‘level’);//節點在第幾圈
var h = (fromAngle+to)/2 % 360 /360; //中心角度,並轉換爲弧度
var s = Math.max(0.2, 1-level*0.1);//每圈s遞減0.1,直到0.2爲止
var v=1;
var color=getHSVColor(h, s, v);

這樣就得到了一圈顏色。實驗效果以下:

圖片描述

若是相對某個節點的顏色作特殊處理,例如強制爲橙色來凸顯,咱們能夠在數據中定義時加個標記,設置顏色時候直接使用而不用計算便可。

javascript{name:'浦東新區', value: 2600, color: '#FE9A2E'}

接下來要實現鼠標劃過節點,自動計算路徑、高亮路徑節點、暗淡非路徑節點。爲了方便路徑尋找,程序把每一個節點的下一圈子數據定義爲子節點,子節點經過getParent()函數能夠直接得到父對象。這樣,經過不斷getParent就能夠得到整個路徑上的節點,並修改其顏色爲預設顏色,實現高亮效果:

javascriptvar node=highlightedNode;
        while(node){            
            node.setClient(‘color’, node.getClient(‘color.original’));
            node=node.getParent();
        }

對於非路徑節點的顏色,能夠設置爲預設顏色但飽和度爲0.1的淡顏色 ,讓它變淡,以便突出高亮路徑:

javascriptvar color = getHSVColor(h, 0.1, v);
node.setClient(‘color’, color);

4、動畫效果

最後,爲了圖形更生動,使用了一些動畫效果。首先想到的就是圖形出來時候,用動畫從小到大發散開來,會很動感。這樣作須要用動畫函數來驅動每個節點的半徑位置,從0增長到所在的半徑位置,若是你們一塊兒設置,整個圖就會動起來。這裏用了一個動畫函數來驅動,並使用了網上經常使用的easing函數來控制,避免線性的動畫太死板:

javascriptnew Animate({
    from: 0,
    to: 1,
    dur: 3000+level*100,
    easing: 'elasticOut',
    onUpdate: function (value) {
        node.setLocation('pie.location’, value);
    },
}).play();

上面定義的動畫,用3秒鐘跑完,用'elasticOut'的easing方式。每一幀,修改node的位置信息。這樣就完成了橡皮筋同樣的環形彈出散開效果。

另外,導航條的出來也比較突兀,這裏也使用一下動畫,讓它從左到右慢慢伸出:

javascriptnew Animate({
    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();

和上一個動畫的不一樣之處在於這裏使用了{x、y}的point結構,每一幀直接更新節點位置。同時設置了50毫秒的delay,讓動畫有一點點粘性停滯,不至於太突兀。效果不錯。

圖片描述

至此,彩虹爆炸圖基本上就作的差很少了。使用起來也很簡單,只要準備一些json數據就能夠了,下面是一些有趣的數據作出來的效果。感興趣的同窗能夠郵件info@servasoft.com索取代碼。

圖片描述

實際應用在項目中的示意圖。若是你也但願項目中用一下彩虹爆炸圖,歡迎給我私信索取:info@servasoft.com

圖片描述

圖片描述

相關文章
相關標籤/搜索