摘要:查詢網上關于這三種格式的定義是如上所示,不過的實現不太一樣,是可以定義為任何一種分隔符,但是分隔符只能為長度為的字符,是以半角符逗號作為分割符,則是以斜杠作為分隔符。
D3是一個數據可視化的javascript庫,相對于highchart和echarts專注圖表可視化的庫,D3更適合做大數據處理的可視化,它只提供基礎的可視化功能,靈活而豐富的接口讓我們能開發出各式各樣的圖表。
D3代碼版本:“3.5.17”
D3的代碼骨架比較簡潔,相比jquery來說更適合閱讀,你可以很舒服地自上而下的看下去而不用看到一個新的函數發現聲明在千里之外,然后在代碼中跳來跳去。
內部代碼流水線基本的數學計算:最小最大、均值中值方差、偏分值……
各種集合類型: map、set、nest……
集合的操作、方法: text、html、append、insert、remove
d3的dragging
圖形操作
……
自執行匿名函數首先是典型的自執行匿名函數,對外提供接口,隱藏實現方式,實現私有變量等等功能。
!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 < n) if ((b = array[i]) != null && b >= b) { a = b; break; } while (++i < n) if ((b = array[i]) != null && a > b) a = b; } else { while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { a = b; break; } while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > 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) < 3) { i1 = array.length; if (m < 2) i0 = 0; } var m = i1 - i0, t, i; while (m) { i = Math.random() * m-- | 0; t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; console.log(i, m); } return array; };
d3使用的洗牌算法,關于Fisher-Yates shuffle的文章可以參考一下,它的演變思路簡單而優雅:
正常的思路是
每次從原數組中隨機選擇一個元素,判斷是否已經被選取,是的話刪除并放入新的數組中,不是的話重新選擇。
缺點:越到后面重復選擇的概率越大,放入新數組的時間越長。
優化
為了防止重復,每次隨機選擇第m張卡牌,m為待洗牌組從原始長度n逐步遞減的值
缺點:每次都要重新獲取剩余數組中的卡牌的緊湊數組,實際的效率為n2
再次優化
就地隨機洗牌,使用數組的后一部分作為存儲新的洗牌后的地方,前一部分為洗牌前的地方,從而將效率提升為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 < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); } else { for (var key in object) map.set(key, object[key]); } return map; }; function d3_Map() { this._ = Object.create(null); } var d3_map_proto = "__proto__", d3_map_zero = "x00"; d3_class(d3_Map, { has: d3_map_has, get: function(key) { return this._[d3_map_escape(key)]; }, set: function(key, value) { return this._[d3_map_escape(key)] = value; }, remove: d3_map_remove, keys: d3_map_keys, values: function() { var values = []; for (var key in this._) values.push(this._[key]); return values; }, entries: function() { var entries = []; for (var key in this._) entries.push({ key: d3_map_unescape(key), value: this._[key] }); return entries; }, size: d3_map_size, empty: d3_map_empty, forEach: function(f) { for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); } });
關于enumerable
在這里,使用d3_Map來作為對象的構造函數,d3_class來封裝類,這里調用了Object.defineProperty來設置屬性和值,這里有一個enumerable: false的屬性,它將該屬性的可枚舉性設置為false,使得該屬性在一般的遍歷中(for...in...)等中無法被獲取,但是還是可以通過obj.key直接獲取到,如果需要獲取對象自身的所有屬性,不管enumerable的值,可以使用 Object.getOwnPropertyNames 方法。
為什么要設置這個屬性呢?我們可以看到對d3_Map構造對象時,引入了一些原生內置的方法,其中有一個叫做empty的方法用來判斷后來設置的屬性是否為空,我們來看看這個函數的實現:
function d3_map_empty() { for (var key in this._) return false; return true; }
看完之后再結合上面提到的enumerable設置為false的屬性在for循環中會被忽略,這樣的話就不用再寫額外地條件去判斷是否為內置屬性,很棒的實現方式。
數據綁定函數data還記得D3獨特的將數據和圖形領域聯系起來的方式嗎?進入(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 < n) { if (node = group[i]) { value[i] = node.__data__; } } return value; } function bind(group, groupData) { var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; if (key) { var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; for (i = -1; ++i < n; ) { if (node = group[i]) { if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) { exitNodes[i] = node; } else { nodeByKeyValue.set(keyValue, node); } keyValues[i] = keyValue; } } for (i = -1; ++i < m; ) { if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { enterNodes[i] = d3_selection_dataNode(nodeData); } else if (node !== true) { updateNodes[i] = node; node.__data__ = nodeData; } nodeByKeyValue.set(keyValue, true); } for (i = -1; ++i < n; ) { if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) { exitNodes[i] = group[i]; } } } else { for (i = -1; ++i < n0; ) { node = group[i]; nodeData = groupData[i]; if (node) { node.__data__ = nodeData; updateNodes[i] = node; } else { enterNodes[i] = d3_selection_dataNode(nodeData); } } for (;i < m; ++i) { enterNodes[i] = d3_selection_dataNode(groupData[i]); } for (;i < n; ++i) { exitNodes[i] = group[i]; } } enterNodes.update = updateNodes; enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; enter.push(enterNodes); update.push(updateNodes); exit.push(exitNodes); } var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); if (typeof value === "function") { while (++i < n) { bind(group = this[i], value.call(group, group.parentNode.__data__, i)); } } else { while (++i < n) { bind(group = this[i], value); } } update.enter = function() { return enter; }; update.exit = function() { return exit; }; return update; };
數據綁定函數data最終返回了變量update,這個變量update一開始為一個空集合,它擁有d3的集合操作方法,然后data函數通過調用bind函數對傳入的參數進行逐項綁定,獲得update集合作為本身,以及enter集合和exit集合,最后在update上綁定了函數enter和exit,使得用戶在調用data后,可以再次調用enter和exit去獲取另外兩個集合。
關于后期debug的足跡d3也會有bug的時候,這個時候需要對bug進行修復,然后再更新,為了方便下次找到修改的bug,在代碼里面對其進行命名,是很好的做法:
【1167】
var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;D3的顏色空間
D3支持五種顏色表示方式,除了我們常常接觸了rgb、hsl外,還有lab、hcl、cubehelix,它們之間都可以轉化為rgb,內部的實現方式值得參考:
【line 1582】
function d3_hsl_rgb(h, s, l) { var m1, m2; h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h; s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s; l = l < 0 ? 0 : l > 1 ? 1 : l; m2 = l <= .5 ? l * (1 + s) : l + s - l * s; m1 = 2 * l - m2; function v(h) { if (h > 360) h -= 360; else if (h < 0) h += 360; if (h < 60) return m1 + (m2 - m1) * h / 60; if (h < 180) return m2; if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; return m1; } function vv(h) { return Math.round(v(h) * 255); } return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); }關于csv、dsv、tsv存儲方式
看代碼的好處之一是能看到很多平時不會用到的接口,然后會主動去了解是干什么的。
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的語法非常簡單,只包括以下兩點:
通過雙豎線"||"作為字段分隔符
把字段值中的"|"替換為"_|"進行轉義
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的骨架后,接下來可以深入到代碼函數實現中去研究其中奧妙。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79912.html
摘要:第一節點位于第二節點內。例如,返回意味著在在內部,并且在之前。這個函數返回一個函數,返回的函數綁定了當前對象并執行。 這是繼上一篇D3源碼解構文章后的對D3的研究筆記,筆者的能力有限,如有哪里理解錯誤,歡迎指正。 對集合的操作 關于d3.attr 一個可以處理很多情況的函數,當只傳入一個參數時,如果是string,則返回該屬性值,如果是對象,則遍歷設置對象的鍵值對屬性值,如果參數大于等...
摘要:數據可視化圖表圖表作為數據可視化最常見的表現形式之一,往往被以偏概全的認為圖表就是數據可視化。嚴格來說,數據可視化應該是連接數據與視覺的一個映射關系,將數據映射成人更容易感知其規律的可視化結果。 題目中的新一代是個相對的概念,事實上本文即將介紹的方法已經有了生產環境可用的實現方案(這也側面佐證了其可行性),但考慮到此方法與現在大部分前端項目中所使用的數據可視化方案相比仍有一些優勢,因此...
摘要:斯坦福宣布使用作為計算機課程的首選語言近日,某位有年教學經驗的斯坦福教授決定放棄,而使用作為計算機入門課程的教學語言。斯坦福官方站點將它們新的課程描述為是最流行的構建交互式的開發語言,本課程會用講解中的實例。 前端每周清單第 11 期:Angular 4.1支持TypeScript 2.3,Vue 2.3優化服務端渲染,優秀React界面框架合集 為InfoQ中文站特供稿件,首發地址為...
摘要:那之前的例子來使用一下的話,你會發現瀏覽器報錯了,如圖定義的變量不允許二次修改。如圖箭頭函數沒有它自己的值,箭頭函數內的值繼承自外圍作用域。如圖這里兩邊的結構沒有一致,如果是的話,是可以正常解構的。 前言 國慶假期已過一半,來篇干貨壓壓驚。 ES6,并不是一個新鮮的東西,ES7、ES8已經趕腳了。但是,東西不在于新,而在于總結。每個學前端的人,身邊也必定有本阮老師的《ES6標準入門》或...
摘要:數組元素甚至可以是對象或其它數組。它執行的是淺拷貝,這意味著如果數組元素是對象,兩個數組都指向相同的對象,對新數組中的對象修改,會在舊的數組的相同對象中反應出來。 JS中的數組是弱類型的,數組中可以含有不同類型的元素。數組元素甚至可以是對象或其它數組。JS引擎一般會優化數組,按索引訪問數組常常比訪問一般對象屬性明顯迅速。數組長度范圍 from 0 to 4,294,967,295(2^...
閱讀 2261·2021-10-09 09:41
閱讀 3409·2021-09-13 10:34
閱讀 1920·2019-08-30 12:59
閱讀 557·2019-08-29 17:27
閱讀 1063·2019-08-29 16:07
閱讀 2956·2019-08-29 13:15
閱讀 1306·2019-08-29 13:14
閱讀 1562·2019-08-26 12:18