蛋白质相互作用网络d3.js可视化 js数据可视化框架
一、蛋白初识D3.js :打造专属可视化
随着现在自定义可视化的质相需求日益增长,Highcharts、互作化echarts等高度封装的用网可视化框架已经无法满足用户各种强定制性的可视化需求了,这个时候D3的可视框架无限定制的能力就脱颖而出。
如果想要通过D3完成可视化,数视化除了对于D3本身API的蛋白学习,关于web标准的HTML, SVG, CSS, Javascript和数据可视化的概念以及标准都是需要学习的。这无疑带来了较高的质相学习门槛,但这也是互作化值得的,因为掌握 D3后,用网我们几乎可以实现任何 2d的可视框架可视化需求。
本文通过对D3核心模块分析以及进行具体案例实践的数视化方式,来帮助初学者学习了解D3的蛋白绘图思路。
D3的质相全称是 Data-Driven Documents(数据驱动文档),是互作化基于数据来操作文档的 JavaScript库,其核心在于使用绘图指令对数据进行转换,在源数据的基础上创建新的可绘制数据,生成SVG路径以及通过数据和方法在DOM中创建数据可视化元素(如轴)。
相对于Echats等开箱即用的可视化框架来说,D3更接近底层,它可以直接控制原生的SVG元素,并且不直接提供任何一种现成的可视化图表,所有的图表都需我们在它的库里挑选合适的方法构建而成,这也大大提高了它的可视化定制能力。而且D3没有引入新的图形元素,它遵循了web标准(HTML, CSS, SVG以及 Canvas)来展示数据,所以它可以不需要依赖其他框架独立运行在现代浏览器中。
在V4版本后,D3的 API现在已经被拆分成一个个模块,我们可以根据自己的可视化需求进行按需加载。根据泛义可以将D3 API模块分为以下的几大类: DOM操作、数据处理,数据分析转换、地理路径,行为等。
这里我们主要对 D3-selection和 D3-scale模块进行解析:
D3-selection(选择集)是 D3js的核心模块,主要是用来进行选择元素,设置属性、数据绑定,事件绑定等操作。
选择元素: D3-selection提供了两种方法来获取目标元素,d3.select():返回目标元素的第一个节点,d3.selectAll():返回目标元素的集合,乍一看有点类似原生API的 querySelector和 querySelectorAll,但是 d3.select返回的是一个 selection对象,querySelector返回的是一个 NodeList数组。通过控制台打印的信息,可以看到 selection下的 groups存放了所有选择的元素集合,parents存放了所有选中元素的父节点。
设置属性或者绑定事件:我们不需要关心 groups的结构是怎么样的。当调用 selection.attr或者 selection.style的时候, selection中的所有 group的所有子元素都会被调用,group存在的唯一影响是:当我们传参是一个function的时候,例如 selection.attr('attrName', function(data, i))或 selection.on('click', function(data, i))时,传递的 function(data, i)中,第二个参数 i是元素在 group中的索引而不是在整个 selection中的索引。
数据绑定:实际上是给选择的DOM元素的 __data__属性赋值,这里提供了3种方式进行数据绑定:
(1)给每一个单独的 DOM元素调用 selection.datum:d3.select('body').datum(20)等价于 document.body.__data__= 20
(2)从父节点中继承来数据,比如: append, insert, select,子节点会主动继承父节点的数据:
(3)调用 selection.data()方法,支持传入装有基础数据类型的数据,也支持传入一个function(parentNode, groupIndex)根据节点索引与数据做映射,data()方法引入了 d3中非常重要的 join思想:
绑定 data到 DOM元素,在D3中是通过比较 data和 DOM的 key值来找到对应关系的。如果我们没有单独设置 key值,那么默认根据 data的下标索引来设定,但是当数据顺序发生改变,这个默认下标 key值就变得不可靠了,这时我们可以使用 selection.data(data, keyFunction)中的第二个参数 keyFunction,根据当前的数据返回一个对应的 key值。通过下面的图例可以看出,不管是有一个还是多个 group(每个group都是独立的),只要我们保证在任意一个 group中的 key值是唯一的,数据一旦发生变化都会反映给对应的 DOM元素( update的过程):
上面提到的都是data数据和DOM元素数量相同的情况下的数据绑定,那如果data数据和DOM元素数量不相同时,我们来看看 D3又是如何进行数据绑定的:现在终于可以来介绍 D3-selecion模块的核心 Join思想了,这个思想简单来说就是“不应该告诉D3去怎么创建元素,而是告诉D3,.selectAll()得到的 selecion集合应该和.data(data)绑定的数据要怎么一一对应”。
从上图可以看出,在进行 d3.data(data)数据绑定的时候,会产生三种状态的选择集:
用 Join的方式来理解意味着,我们要做的事情仅仅是声明 DOM集合和数据集合之间的关系,并且通过处理三个不同状态的集合 enter、update、 exit来描述这种关系。这种方式可以大大简化我们对DOM元素的操作,我们不需要再用 if和 for循环的方式来进行复杂的逻辑判断,来得到我们需要得到的元素集合。并且在处理动态数据的时候,可以通过处理这三种状态,轻松的展示实时数据和添加平滑的动态交互效果。
D3-scale(比列尺)提供多种不同类型的比例尺。经常和 D3-axis坐标轴模块一起使用。
D3-scale提供了多种连续性和非连续性的比例尺,总体可以将他们分为三大类:
常用的一些比例尺:
(1)d3-scaleLinear线性比例尺(连续性输入和连续性输出)
可以看出,调用d3.scaleLinear()可以生成线性比例尺,domain()是输入域,range()是输出域,相当于将domain中的数据集映射到range的数据集中。
使用示例:
映射关系:
(2)d3-scaleTime时间比例尺(连续性输入和连续性输出)
时间比例尺与线性比例尺类似,只不过输入域变成了一个时间轴。正常我们使用比例尺都是个正序的过程,但是D3也提供了invert()以及invertExtent()方法,我们可以通过输出域中的具体值得出对应输入域的值。
使用示例:
(3)d3.scaleQuantize量化比例尺(连续性输入和离散性输出)
量化比例尺是将连续的输入域根据输出域被分割为均匀的片段,所以它的输出域是离散的。
使用示例:
映射关系:
(4)d3. scaleThreshold阈值比例尺(连续性输入和离散性输出)
阈值比例尺可以为一组连续数据指定分割阈值,阈值比例尺默认的 domain:[0.5]以及默认的 range:[0, 1],因此默认的 d3.scaleThreshold()等价于 Math.round函数。阈值比例尺输入域为 N的话,输出域必须为 N+ 1,否则比例尺对某些值可能会返回 undefined,或者输出域多余的值会被忽略。
使用示例:
存在三种映射关系:
a.当domain和range的数据是 N: N+1
b.当domain和range的数据是 N: N+大于1
c.当domain和range的数据是 N+大于0: N
(5)d3.scaleOrdinal序数比例尺(离散性输入和离散性输出)
与scaleLinear等连续性比例尺不同,序数比例尺的输出域和输入域都是离散的。
使用示例:
存在三种映射关系:
a.当domain和range的数据是一一对应
b.当domain少于range的数据
c.当domain多于range的数据
通过以上的学习,应该对d3是如何操作DOM以及坐标轴的数据映射为相应的可视化表现有了一定的了解,下面我们来实际运用这两个模块,来实现我们常见的可视化图表:柱状图。
(1)首先添加一个SVG元素。
(2)根据我们上面说到 d3.scale模块以及 d3.axis模块绘制坐标轴,d3.scaleBand()叫做序数分段比例尺,类似我们说的 d3.scaleOrdinal()序数比例尺,但是它支持连续的数值类型的输出域,离散的输入域可以将连续的范围划分为均匀的分段。这里再讲一个细节,在绘制网格的时候,我们并没有额外添加 line元素来实现,而是通过 d3.axis坐标轴模块的 axis.ticks()方法对坐标轴刻度进行了设置,通过 tickSIze()设置了刻度线长度,来模拟和图表宽度相等的网格线,并且还可以通过 tickFormat()对Y轴刻度值进行格式化转换。
(3)坐标轴绘制好了后,我们通过数据绑定来绘制与之对应的矩形(rect)元素了。
(4)这个时候柱状图已经基本绘制好了,我们再丰富内容展示,添加标签、标题等提示信息。
(5)最后我们通过给柱子绑定监听事件,实现tooltips的信息浮层交互。
通过对 d3.selection、d3.scale以及 d3.axis等模块的学习,我们已经可以绘制出常用的柱状图等图表,我们也可以通过d3提供的其他模块绘制出更加复杂的可视化效果,例如通过 d3-hierarchy(层级模块)实现层级树图可视化,d3-geo(地理投影)实现地图数据可视化等,本文讲解的内容还只是D3库的冰山一角。所以等我们掌握了D3后,限制我们实现可视化的不再是技术而是想象力。
二、webgl、three.js、D3.js这三者是什么关系
webgl、three.js、D3.js这三者的关系是:
1、D3.js是一个数据可视化的库,看看他们的DEMO就可以知道,技术基础是SVG。兼容性是IE9+。
2、webgl是HTML5中提出的新技术,是一种3D绘图标准,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,关于它的教程可以看看hiwebgl。目前兼容性堪忧
3、three.js是以webgl为基础的库,封装了一些3D渲染需求中重要的工具方法与渲染循环。它的教程同样可以在hiwebgl里面找到。
4、three.js之于webgl,类似于windows版本的虚幻引擎之于D3D。当然,虚幻引擎的能力范围比three.js大得多。d3.js跟上面两者没有关系。
三、如何使用d3.js制作可视化图表
D3是目前最流行的JavaScript可视化图表库之一,D3的图表类型非常丰富,并且支持SVG格式,因此应用十分广泛,也有很多图表插件基于D3开发,比如MetricsGraphics.js,在D3上构建的数据图表非常强大。
D3的特点
允许绑定任意数据到DOM,将数据驱动转换应用到Document中。
不仅可以创建精美的HTML表格,而且可以绘制折线图、柱形图和饼图等数据图表。
支持SVG,在Web页面上渲染毫无压力。
回到顶部
D3的使用方法
关于D3的具体用法,可以看D3图形库API参考这篇文章。本文主要对介绍一些经典图表的实现效果及代码。
index.html代码:
<!DOCTYPEhtml>
<metacharset="utf-8">
<style>
svg{
font:10pxsans-serif;
}
.y.axispath{
display:none;
}
.y.axisline{
stroke:#fff;
stroke-opacity:.2;
shape-rendering:crispEdges;
}
.y.axis.zeroline{
stroke:#000;
stroke-opacity:1;
}
.title{
font:30078pxHelveticaNeue;
fill:#666;
}
.birthyear,
.age{
text-anchor:middle;
}
.birthyear{
fill:#fff;
}
rect{
fill-opacity:.6;
fill:#e377c2;
}
rect:first-child{
fill:#1f77b4;
}
</style>
<body>
<scriptsrc=""></script>
<script>
varmargin={ top:20,right:40,bottom:30,left:20},
width=960-margin.left-margin.right,
height=500-margin.top-margin.bottom,
barWidth=Math.floor(width/19)-1;
varx=d3.scale.linear()
.range([barWidth/2,width-barWidth/2]);
vary=d3.scale.linear()
.range([height,0]);
varyAxis=d3.svg.axis()
.scale(y)
.orient("right")
.tickSize(-width)
.tickFormat(function(d){ returnMath.round(d/1e6)+"M";});
//AnSVGelementwithabottom-rightorigin.
varsvg=d3.select("body").append("svg")
.attr("width",width+margin.left+margin.right)
.attr("height",height+margin.top+margin.bottom)
.append("g")
.attr("transform","translate("+margin.left+","+margin.top+")");
//Aslidingcontainertoholdthebarsbybirthyear.
varbirthyears=svg.append("g")
.attr("class","birthyears");
//Alabelforthecurrentyear.
vartitle=svg.append("text")
.attr("class","title")
.attr("dy",".71em")
.text(2000);
d3.csv("population.csv",function(error,data){
//Convertstringstonumbers.
data.forEach(function(d){
d.people=+d.people;
d.year=+d.year;
d.age=+d.age;
});
//Computetheextentofthedatasetinageandyears.
varage1=d3.max(data,function(d){ returnd.age;}),
year0=d3.min(data,function(d){ returnd.year;}),
year1=d3.max(data,function(d){ returnd.year;}),
year=year1;
//Updatethescaledomains.
x.domain([year1-age1,year1]);
y.domain([0,d3.max(data,function(d){ returnd.people;})]);
//Produceamapfromyearandbirthyearto[male,female].
data=d3.nest()
.key(function(d){ returnd.year;})
.key(function(d){ returnd.year-d.age;})
.rollup(function(v){ returnv.map(function(d){ returnd.people;});})
.map(data);
//Addanaxistoshowthepopulationvalues.
svg.append("g")
.attr("class","yaxis")
.attr("transform","translate("+width+",0)")
.call(yAxis)
.selectAll("g")
.filter(function(value){ return!value;})
.classed("zero",true);
//Addlabeledrectsforeachbirthyear(sothatnoenterorexitisrequired).
varbirthyear=birthyears.selectAll(".birthyear")
.data(d3.range(year0-age1,year1+1,5))
.enter().append("g")
.attr("class","birthyear")
.attr("transform",function(birthyear){ return"translate("+x(birthyear)+",0)";});
birthyear.selectAll("rect")
.data(function(birthyear){ returndata[year][birthyear]||[0,0];})
.enter().append("rect")
.attr("x",-barWidth/2)
.attr("width",barWidth)
.attr("y",y)
.attr("height",function(value){ returnheight-y(value);});
//Addlabelstoshowbirthyear.
birthyear.append("text")
.attr("y",height-4)
.text(function(birthyear){ returnbirthyear;});
//Addlabelstoshowage(separate;notanimated).
svg.selectAll(".age")
.data(d3.range(0,age1+1,5))
.enter().append("text")
.attr("class","age")
.attr("x",function(age){ returnx(year-age);})
.attr("y",height+4)
.attr("dy",".71em")
.text(function(age){ returnage;});
//Allowthearrowkeystochangethedisplayedyear.
window.focus();
d3.select_(window).on("keydown",function(){
switch(d3.event.keyCode){
case37:year=Math.max(year0,year-10);break;
case39:year=Math.min(year1,year+10);break;
}
update();
});
functionupdate(){
if(!(yearindata))return;
title.text(year);
birthyears.transition()
.duration(750)
.attr("transform","translate("+(x(year1)-x(year))+",0)");
birthyear.selectAll("rect")
.data(function(birthyear){ returndata[year][birthyear]||[0,0];})
.transition()
.duration(750)
.attr("y",y)
.attr("height",function(value){ returnheight-y(value);});
}
});
参考资料:云原生可观测性