D3 源代码解构

D3是一个数据可视化的javascript库,相对于highchart和echarts专注图表可视化的库,D3更适合做大数据处理的可视化,它只提供基础的可视化功能,灵活而丰富的接口让我们能开发出各式各样的图表。

D3代码版本:“3.5.17”

D3的代码骨架比较简洁,相比jquery来说更适合阅读,你可以很舒服地自上而下的看下去而不用看到一个新的函数发现声明在千里之外,然后在代码中跳来跳去。

内部代码流水线

  1. 基本的数学计算:最小最大、均值中值方差、偏分值……

  2. 各种集合类型: map、set、nest……

  3. 集合的操作、方法: text、html、append、insert、remove

  4. d3的dragging

  5. 图形操作

  6. ……

自执行匿名函数

首先是典型的自执行匿名函数,对外提供接口,隐藏实现方式,实现私有变量等等功能。

!function() {
// code here
}()
这里用到的是感叹号,其实和使用括号是一样的作用,就是将函数声明变成函数表达式,以便于函数的自执行调用,你可以试试

function() {
console.log('no console')
}()
这是因为JS禁止函数声明和函数调用混用,而括号、逻辑运算符(+、-、&&、||)、逗号、new等都可以将函数声明变成函数表达式,然后便可以自执行。有人做过调查关于这些转化的方法哪个更快,可以查看这篇博客,大概new是最慢的,相比使用括号是基本最快,感叹号反而性能一般,所以其实用哪个都没什么区别,当然如果你想省敲一个符号也是可以用感叹号的。

对外暴露私有变量d3

对于d3,采用的是创建私有变量对象,然后对它进行扩展,最后对外暴露

var d3 = {  version: '3.5.17'};// code here//...if (typeof define === 'function' && defind.amd)   this.d3 = d3, define(d3);else if (typeof module == 'object' && module.exports)  module.exports = d3;else  this.d3 = d3;

第一种为异步模块加载模式,第二种为同步模块加载或者是ecma6的import机制,第三种则是将d3设置为全局变量,因为匿名自执行函数中,函数的环境就是全局的,所以this == window。

创建公用方法

d3的方法是属于d3对象的属性:

d3_xhr( url, mimeType, response, callback) {  // code }d3.json = function(url, callback) {  return d3_xhr(url, 'application/json', d3_json, callback);};function d3_json(request) {  return JSON.parse(request.responseText);}

不太好的是d3没有在命名上区分哪些是私有函数,哪些是公用函数,不过对于通过创建对象来对外暴露接口的对象来说,应该也不用去区分吧。

提取一些常用的原生函数

var d3_arraySlice = [].slice, d3_array = function(list) {  return d3_arraySlice.call(list);};var d3_document = this.document;

提取slice方法,使用它来生成数组的副本,slice不会对原生数组做切割,而是会返回数组的复制品,但是要注意是浅复制,对于数组中的对象、数组,是单纯的引用,所以对原数组中的对象或数组的更改还是会影响到复制品。

部分代码实现阅读

一段用来测试d3_array的函数,但什么情况下会重写d3_array函数呢?

【line15】

if (d3_document) {  var test = d3_array(d3_document.documentElement.childNodes);  console.log(test);  try {    d3_array(d3_document.documentElement.childNodes)[0].nodeType;  } catch (e) {    console.log('catch error:', e);    d3_array = function(list) {      var i = list.length, array = new Array(i);      while (i--) array[i] = list[i];      return array;    };  }}

由前面我们可以知道d3_array可以用来获取传入数组的副本,通过try来测试document的子节点的第一个子元素,一般就是header这个元素,我们通过查询w3c可以知道nodeType为1,表示html element,感觉应该是测试是否是浏览器环境,如果不是的话,就换成自己写的函数的意思吗?还是为了兼容一些少数的浏览器呢?

设置对象属性的兼容?

【line 30】

if (d3_document) {  try {    d3_document.createElement("DIV").style.setProperty("opacity", 0, "");  } catch (error) {    var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;    d3_element_prototype.setAttribute = function(name, value) {      d3_element_setAttribute.call(this, name, value + "");    };    d3_element_prototype.setAttributeNS = function(space, local, value) {      d3_element_setAttributeNS.call(this, space, local, value + "");    };    d3_style_prototype.setProperty = function(name, value, priority) {      d3_style_setProperty.call(this, name, value + "", priority);    };  }}

暂时不知道是为了跨浏览器还是跨文档而做的检测,待研究。

数组最小值函数

【line 53】

  d3.min = function(array, f) {    var i = -1, n = array.length, a, b;    if (arguments.length === 1) {      while (++i = b) {        a = b;        break;      }      while (++i  b) a = b;    } else {      while (++i = b) {        a = b;        break;      }      while (++i  b) a = b;    }    return a;  };

首先获取第一个可比较的元素,测试了下,发现对于b >= b,无论b是数字、字符串、数组甚至是对象都是可以比较的,那么什么情况下 b>=b == false呢,对于NaN来说,无论和哪个数字比较,都是false的,但是对于Infinity却返回真,是个点。所以应该是为了排除NaN这种有问题的数字。

d3的洗牌方法

  d3.shuffle = function(array, i0, i1) {    if ((m = arguments.length) 洗牌算法,关于Fisher-Yates shuffle的文章可以参考一下,它的演变思路简单而优雅:**正常的思路是**1. 每次从原数组中随机选择一个元素,判断是否已经被选取,是的话删除并放入新的数组中,不是的话重新选择。1. 缺点:越到后面重复选择的概率越大,放入新数组的时间越长。**优化**1. 为了防止重复,每次随机选择第m张卡牌,m为待洗牌组从原始长度n逐步递减的值1. 缺点:每次都要重新获取剩余数组中的卡牌的紧凑数组,实际的效率为n2**再次优化**1. 就地随机洗牌,使用数组的后一部分作为存储新的洗牌后的地方,前一部分为洗牌前的地方,从而将效率提升为n。### d3.map 关于内置对象【line 291】

function d3_class(ctor, properties) {
for (var key in properties) {
Object.defineProperty(ctor.prototype, key, {
value: properties[key],
enumerable: false
});
}
}
d3.map = function(object, f) {
var map = new d3_Map();
if (object instanceof d3_Map) {
object.forEach(function(key, value) {
map.set(key, value);
});
} else if (Array.isArray(object)) {
var i = -1, n = object.length, o;
if (arguments.length === 1) while (++i 进入(enter)--更新(update)--退出(exit) 模式。

【line 832】

d3.selectAll('div')  .data(dataSet)  .enter()  .append('div')  ;d3.selectAll('div')  .data(data)  .style('width', function(d) {     return d + 'px';  }) ;d3.selectAll('div')  .data(newDataSet)  .exit()  .remove()  ;

这里涉及到了三个函数,data、enter、exit,每次进行操作前我们需要先调用data对数据进行绑定,然后再调用enter或者exit对图形领域进行操作,那么内部实现原理是怎么样的呢,看完下面这段代码就恍然大悟了:

  d3_selectionPrototype.data = function(value, key) {    var i = -1, n = this.length, group, node;    if (!arguments.length) {      value = new Array(n = (group = this[0]).length);      while (++i  1 ? 1 : s;    l = l  1 ? 1 : l;    m2 = l  360) h -= 360; else if (h 看代码的好处之一是能看到很多平时不会用到的接口,然后会主动去了解是干什么的。**csv格式**在文本数据处理和传输过程中,我们常常遇到把多个字段通过分隔符连接在一起的需求,如采用著名的CSV格式(comma-separated values)。CSV文件的每一行是一条记录(record),每一行的各个字段通过逗号','分隔。**dsv格式**由于逗号和双引号这两个特殊字符的存在,我们不能简单地通过字符串的split操作对CSV文件进行解析,而必须进行CSV语法分析。虽然我们可以通过库的形式进行封装,或者直接采用现成的库,但毕竟各种平台下库的丰富程度差异很大,这些库和split、join这样的简单字符串操作相比也更加复杂。为此,我们在CSV格式的基础上设计了一种DSV (double separated values)格式。DSV格式的主要设计目的就是为了简化CSV语法,生成和解析只需要replace, join, split这3个基本的字符串操作,而不需要进行语法分析。DSV的语法非常简单,只包括以下两点:1. 通过双竖线'||'作为字段分隔符1. 把字段值中的'|'替换为'_|'进行转义** tsv格式** TSV 是Tab-separated values的缩写,即制表符分隔值。查询网上关于这三种格式的定义是如上所示,不过d3的实现不太一样,dsv是可以定义为任何一种分隔符,但是分隔符只能为长度为1的字符,csv是以半角符逗号作为分割符,tsv则是以斜杠作为分隔符。### d3.geo【line 2854】geo是d3的图形处理实现,应该算是核心代码了,不过到了4.0版本被分割成依赖,并且不再有d3.geo.path了,而是改用d3.geoPath的方式去引用。## 总结版本3的d3九千多行代码,版本4的d4则进行了依赖分割,如果全部依赖引入的话不压缩就要过16000行了,如果想整体去看骨架的话,版本3是比较清晰的,版本4则适合深入研究每一部分的实现,因为依赖都分割得很清晰了,并且相互独立开。初步了解整个d3的骨架后,接下来可以深入到代码函数实现中去研究其中奥妙。#JavaScript、d3#


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部