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

图片描述

图片描述

相关文章
相关标签/搜索