利用HTML5 Canvas创建交互式Bubble Chart

摘要: 众所周知资讯图像一种使数据易于理解和充满娱乐性的方式。不同的人采用不同的方式使数据易于理解充满娱乐性,一些人通过充满创意的平面设计实现这个目标如打印资讯信息,另外也有一些人通过增加动画和交互性实现目标 …

 来源:HTML5中国

众所周知资讯图像一种使数据易于理解和充满娱乐性的方式。不同的人采用不同的方式使数据易于理解充满娱乐性,一些人通过充满创意的平面设计实现这个目标如打印资讯信息,另外也有一些人通过增加动画和交互性实现目标。软件工程师、研究者、业余设计师Josh Marinacci强烈推荐读者看Hans Rosling教授和一群富有想象力的人的TED会谈。这个会谈揭露了关于发展中国家的共同的神话。会谈中Rosling教授成功的关键是他采用一种非专业人士不能理解的方式渲染数据。他作品的核心是一个令人惊奇的Bubble Chart,可以随着时间的推移比较世界上的各个地区的数据信息。

Josh Marinacci也在其博客中详细地介绍了“如何利用HTML5 Canvas创建可以在移动桌面上运行的交互式图表以及如何利用真实数据填充图表。”下面让我们跟随作者学习如何创建我们自己的Bubble Chart。

准备工作:

需要知识:媒介HTML和JavaScript

需求:文本编辑器和现代浏览器

关于Bubble Chart

Bubble Chart就和它听起来一样是由气泡组成的图表,气泡实际上就是一些圆。但是Bubble Chart却能可视化五维数据,这也是其强大之处。每个气泡代表由 X和Y坐标定位的数据点,这和任何其他的线状图和散点图一样。可是每个气泡的大小和颜色属性可以表示另外两维的数据。如果我们动态显示图表,那么我们可以增加时间作为第五维数据。

尽管Bubble Chart的五个属性都可以代表任何类型的数据,但是实践中我们通常用X和Y坐标代表数字型数据,而气泡颜色这个属性用于区分两个不同的数据集。例如利用X和Y分表代表儿童的“死亡数”和“受教育比例”,而气泡颜色代表不同的国家。气泡大小一般用来代表某种事物的数量如某个国家的人口。当动态显示图标时,第五维的坐标用来代表时间如表示从1960年到现在。仔细地创造性地使用这些气泡属性是创造出一个与众不同的资讯图标的关键。

利用模拟数据创建简单的图表

数据本身应该代表按照一定标准分组随着时间变化的数据点。假设数据由5格国家,20个时间点(从1980到2000年)的数据组成。每个数据点由x、y和大小三个属性组成。初始化这些模拟数据的代码如下:

var data = [];
for(var t=0; t<20; t++) {
    var cdata = [];
    data[t] = cdata;
    for(var country=0; country<5; country++) {
        cdata.push({
            x:50+Math.random()*500,
            y:50+Math.random()*300 ,
            size: 3+Math.random()*20,
            country: country});
    }
}

接下来在屏幕上用气泡描绘出这些数据,代码如下:

var canvas = document.getElementById(‘canvas’);
var ctx = canvas.getContext(’2d’);
 
var colors = [“red”,”green”,”blue”,”yellow”,”orange”];
 
var time = 0;
function draw() {
   
    //bg and border
    ctx.fillStyle = ”white”;
    ctx.fillRect(0,0, canvas.width,canvas.height);
    ctx.strokeStyle = ”black”;
    ctx.strokeRect(0,0,canvas.width,canvas.height);
   
    //time indicator
    ctx.fillStyle = ”black”;
    ctx.fillText(“time “ + time, 10,20);
   
    //draw the data for the current time slice
    data[time].forEach(function(d) {
        ctx.save();
        ctx.fillStyle = colors[d.country%colors.length];
        ctx.globalAlpha = 0.5;
        ctx.beginPath();
        ctx.arc(d.x,d.y,d.size,0,Math.PI*2);
        ctx.fill();
        ctx.restore();
    });

上述代码用白色填充背景,利用Canvas边框作为背景的边界。然后描绘当前时间指示器,最后描绘数据本身。每个数据点都根据当前点的x、y和大小描绘成相应的圆。每个国家的颜色从定义的颜色列表中选取。

基本表如下:

为了增加表的时间属性,我们只需要用不同的时间变量重复调用描绘函数重复描绘即可。代码如下:

$(“#play”).click(function() {
    var animdraw = function() {
        draw();
        time++;
        if(time < data.length) {
            setTimeout(animdraw,100);
        } else {
            time = 0;
        }
    }
    animdraw();
});

获取“世界数据银行”的数据

至此已经创建了一个基本表。下面让我们利用一些真实的数据填充这个表让它更有意思。我们从“世界数据银行”选择需要的数据,它是一个庞大的组织,具有从UN和其他公开的数据源采集到的庞大的数据集。除了数据承载基地,它还有一个定制的报表生成器,它可以让你创建数据切片以支持多种格式的数据下载。

本文选择了“世界发展指标和全球发展金融”数据库。选择“东亚及太平洋地区”,“欧洲和中亚”,“拉丁美洲和加勒比地区”一些国家或地区作为集合,利用气泡的颜色属性表示不同的集合。选择“死亡率”、“人口密度”、“总人口”作为研究点,分别用x、y坐标以及气泡的大小属性表示。最后选择了时间变量。简单的点击“全选”按钮即可选择从1960年到现在以年为单位的数据变量。

现在我们已经选定了一份报告。点击“导出”按钮下载CSV文件格式的数据。我们可以利用Excel打开CSV文件查看从“世界数据银行”所得到的这份报告的内容。如果你利用Excel打开下载的CSV文件,将看到每个国家或者地区都有三行组成,我们选择的每个变量都有一行。另外你可能看到早年的一些数据值并不存在,这意味着那几年并没有收集这些国家的数据。

解析CSV数据

如果你用Excel打开报告,它看起来只是一堆行。我们需要把数据解析成我们能用的形式。为了解析数据文件,我们需要CSV解析程序。Josh Marinacci从Ben Nadel下载了一个解析程序,很好用。为了下载CSV文件并解析它Josh Marinacci使用AJAX调用jQuery。

$(“#load”).click(function() {
    $.ajax({
        url: ”data.csv”,
        context: document.body,
        success: function(c) {
            var csvdata = CSVToArray(c);
            console.log(“got to here “ + csvdata[1]);
        }
    });
}

CSV文件被解析成由许多行组成的文件,这些行可以继续进行子划分被划分成列。我们可以通过先列循环再行循环的方式处理CSV文件。

data = [];
 
//start at 9 to skip non-year columns and first few years
for(var t=9; t<csvdata[0].length-1; t++) {
    var cdata = [];
   
    //loop through rows by threes
    for(var i=1; i<csvdata.length-1; i+=3) {
        var row_mort = csvdata[i];
        var row_dens = csvdata[i+1];
        var row_total = csvdata[i+2];
        var country = (i-1)/3;
        cdata.push({
            x:row_dens[t] ,
            y:row_mort[t],
            size: row_total[t],
            country: country,
        });
    }
    data[t-9] = cdata;
}

注意上述代码是从第9列开始处理数据的,不仅跳过了数据不完整的的前几年的数据点也跳过了一些元数据(如国家名字等等)。处理行的时候每次循环是处理三行数据目的是通过一次循环可以处理一个国家或地区的数据。

如果仅仅按照这种方式处理数据,我们将什么都看不到。整个Canvas都被单一的颜色填充,这不是我们想要的结果。这是因为数据并没有按比例进行适当的扩展。例如在1960年东亚和太平洋地区的人口是10亿。为了描绘图表需要按比例缩小这个值。为了适用于这些未修改的数据在绘制图表的时Josh Marinacci使用了可扩展的功能。只需要采用如下方式改变弧度即可:

ctx.arc(
    d.x*6,
    canvas.height-d.y*2.5,
    d.size/(1000*1000*11),
    0,Math.PI*2
    );

上述代码修改了x、y和气泡大小的值适应Canvas。气泡大小必须除以1100万才能得到合理的气泡半径。Y值由Canvas高度减去y乘以2.5得到。Canvas的标准坐标系要从左上方开始以便被Canvas高度减去之后可以翻转y坐标。

修改数据变量的值是可以任意选取的,选择某个特定的值是为了让表格看起来比较美观。具体选择什么值依赖于正在描绘的数据。可以写一个程序分析数据寻找合理的参数值,比如通过计算每一个变量的最大值或者最小值。

表格看起来如下:

增加交互性

赋予表格时间这一点很好,增加表格的交互性实际上就是让图表阅读者通过点击不同的气泡获取更多的信息。由于Canvas完全使用像素而不是我们需要的图形,这种图形使得通过程序我们可以计算哪一个图形被点击了。幸运地是Canvas中的气泡都是完美的圆,这将使事情容易很多。我们只需要计算被点击点与气泡圆心的距离即可,如果距离小于半径就认为该气泡被点击。代码如下:

$(“#canvas”).mousedown(function(e) {
    displayInfo = false;
    data[time].forEach(function(d) {
        var x = d.x*6;
        var y = canvas.height-d.y*2.5;
        var radius = d.size/(1000*1000*11);
        var dis = dist(e.offsetX,e.offsetY,x,y);
        if(dis < radius) {
            displayInfo = true;
            displayCountry = d.country;
        }
    });
    draw();
});

如果读者已经点击了某个图形,显示信息的布尔值将变为“true”,保存当前的国家并触发重绘操作,同时在右上方通过代码嵌入一个小的信息板。

注意在重绘代码中填充的数据是来自于当前时间片而不是用户点击表格时的时间。这就意味着当表格被赋予时间属性之后显示的内容将更新以便用户可以看到随着时间变化的数据。

if(displayInfo) {
        ctx.save();
        ctx.translate(canvas.width-305,5);
        ctx.fillStyle = ”rgba(200,200,200,0.7)”;
        ctx.fillRect(0,0,300,100);
        ctx.strokeStyle = ”black”;
        ctx.lineWidth = 2;
        ctx.strokeRect(0,0,300,100);
       
        ctx.fillStyle = ”black”;
        var displayPoint = data[time][displayCountry];
        ctx.fillText(“Region: “ + regions[displayPoint.country],5,20);
        ctx.fillText(“Population Density: “ + displayPoint.x,5,20+20*1);
        ctx.fillText(“Mortality rate: “ + displayPoint.y,5,20+20*2);
        ctx.fillText(“Population: “ + displayPoint.size,5,20+20*3);
        ctx.restore();
    }

改变图表的视觉效果

一般而言图报表不应该有这么多的可视元素否则将减损数据的潜在表现性,但是选择一个完美的颜色和模式将产生巨大的差别。为了让这个表看起来更好,Josh Marinacci给每个气泡增加了一层白色覆盖物使圆更加完美。同时也给每个圆增加一个黑色的边框并为每个集合选择不同的颜色。相对于计算每个气泡颜色的渐变度Josh Marinacci采用先选择一个基本的颜色然后改变它的透明度的方式为每个不同的气泡选择颜色。

var radgrad = ctx.createRadialGradient(
        x-radius/10,y-radius/10,0,
        x-radius/10,y-radius/10,radius+30);
radgrad.addColorStop(0,   ’white’);
radgrad.addColorStop(0.5, ’white’);
radgrad.addColorStop(1,   ’rgba(255,255,255,0.3)’);
ctx.globalAlpha = 0.6;
ctx.fillStyle = radgrad;
 
ctx.fill();

为了使图表更加美观上述代码改变了字体增加了边框,使用了一个来自于Subtle Patterns网站充满娱乐性的背景。最终图表如下:

总结

上面就是创建Bubble Chart的基本过程。利用上面的基本图表你可以创建自己的Bubble Chart。你可以增加数据自动调整功能,让图表阅读者自己选择数据集合扩大或者缩小,你也可以在每个时间间隔中插入时间使时间划分更细,动态过程更流畅。

你可以从这获取工程源码,也可以从作者的个人网站JoshOnDesign.com获取。

 


Have Your Say »

Required

Required, never published