縱覽全局垂直打擊的組織模式(下)
- 2020 年 1 月 13 日
- 筆記
本文詳細記錄了如何在Hexo博客中實現用圖組織內容的方法,但是,請注意:以下內容並非操作教程,僅表明相信思路以供參考,或許您可以實現出更好的版本,但僅依照下文內容並不保證一定能重現,一些嘗試和debug的細節過於繁瑣並未列出,如有疑問歡迎留言。
代碼實現
hexo.extend.helper.register
文檔說明,藉助該函數,可以在Hexo渲染生成頁面文件之前,完成用戶的自定義JavaScript代碼。
其實,在Hexo的框架內,ejs(或其他類型的)模板中的代碼就是渲染生成html的代碼,在這些頁面中,藉助Hexo內建的對象,比如.post對象和.achieves對象,可以訪問到其中保存的全部文章信息及關聯信息。例如:
let posts = hexo.locals.get('posts'); let Xtags = posts.data[x].tags let tagsY = Xtags.data[y].name
上述內容,可以最終得到第X篇文章(POST)中的第Y個標籤的文本。類似的方法同樣可以得到某篇文章的Categories的信息。這就是構造可視化數據的基本方法。(在渲染前構造、藉助.post對象) 關於位置,在ejs模板中放置構造代碼當然可以,但是不優雅,Hexo中建議的插入方式是:
- 在專門放置自定義JavaScript處理邏輯的文件中(plugin.js)放入代碼,並使用內建函數。
- 在ejs(或其他)模板的相關位置,使用<%%>方式調用上述內建函數
- 使用console.log在渲染html時(hexo generate時的黑框)輸出至Console里,拿到輸出數據,放入到可視化的頁面中即可。
- 或者一氣呵成,直接將可視化的代碼寫入ejs模板中,即第一次渲染結束時產生的html就已經完成可視化頁面的生成。
由於處在嘗試階段,所以這裡使用步驟3 的方法,這樣各模塊相對獨立,對主題源代碼入侵小。
可視化頁面
這裡採用的是 D3.js 進行的可視化呈現,基本上是復用的 d3 的官方模板,但將文本信息一併和節點進行可視化展示。這段代碼首先需要被抽取出來,這對於 d3 來說非常簡單,只需注意引入的JavaScript庫以及使用的json文本數據。
<svg width="1000" height="1000"></svg> //d3繪製的內容全部放置在該畫布上 <script src="https://d3js.org/d3.v4.min.js"></script> <script> var sss = 'JSON字符串'; //這就是整個代碼所可視化的數據 var abc = parseInt($(".card").css("width").replace("px","")); if(abc>1080) abc=1050; else if(abc>1040) abc=1020; else abc=abc-40; $("svg").css("width",abc); $("svg").css("height",abc); //此部分將畫布大小跟隨文章頁寬度變化 var svg = d3.select("svg"), width = abc, height = abc; var color = d3.scaleOrdinal(d3.schemeCategory20); var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; })) .force("charge", d3.forceManyBody().strength(-180).distanceMin(10).distanceMax(300).theta(1)) .force("center", d3.forceCenter(width / 2 - 40, height / 2 - 30)); var graph = JSON.parse(sss); var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = svg.append("g") .attr("class", "nodes") .selectAll("g") .data(graph.nodes) .enter().append("g") var circles = node.append("circle") .attr("r", function(d) { if(d.group>=100) return d.group/100*(10.00/48.00)+1; //取整 else return d.group+1; }) .attr("fill", function(d) { if(d.group>=100) return "#ff4081"; else return "#3f51b5"; }) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); var lables = node.append("text") .html(function(d) { if(d.group>=100) { var p = d.group/100*(10.00/48.00)+10; return "<a style='font-size:"+p+"px;font-weight:600;color:red' href='/categories/"+d.id.replace("_","-")+"'>"+d.id+"</a>"; }else{ var q = d.group+10; return "<a style='font-size:"+q+"px;' href='/tags/"+d.id+"'>"+d.id+"</a>"; } }) .attr('x', function(d) { if(d.group>=100) return d.group/100*(10.00/48.00)+5; //取整 else return d.group+3; }) .attr('y',function(d) { if(d.group>=100) return d.group/100*(3.00/48.00)+5; //取整 else return 5; }); node.append("title") .text(function(d) { return d.id; }); simulation .nodes(graph.nodes) .on("tick", ticked); simulation.force("link") .links(graph.links); function ticked() { link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) } function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } </script>
構造數據格式
需要匹配示例的輸入格式,這樣才能最大化的復用代碼。上述內容的官方示例中使用的格式是:
{ "nodes": [ {"id": "Myriel", "group": 1}, ... ... {"id": "Mme.Hucheloup", "group": 8} ], "links": [ {"source": "Napoleon", "target": "Myriel", "value": 1}, ... ... {"source": "Mme.Hucheloup", "target": "Enjolras", "value": 1} ] }
即,需要在可視化頁面被渲染出來之前就得到上述格式的數據,這便是要藉助Hexo的輔助函數來完成,將構造數據的代碼封裝成一個函數,然後在適當的ejs模板中調用一下,即可在 hexo generate 之後,從Console中拿到構造好的數據。
在此,構造規則是:類別永遠單向的指向標籤,類別不互連,標籤不互連,同時,還需要計算的是類別和標籤出現的次數。
hexo.extend.helper.register('getPostData', () => { var posts = hexo.locals.get('posts'); var tagsMap = new Map(); //counter // 利用posts對象獲取類名和標籤名 for(var i = 0; i< posts.length; i++){ var nameCS; posts.data[i].categories.forEach(function(k, v) { nameCS = k.name; return; }) for(var j = 0; j< posts.data[i].tags.length; j++){ var pname = posts.data[i].tags.data[j].name; var pval = tagsMap.get(pname); if(pval != null){ // 將類名和標籤名壓制在一起 tagsMap.set(nameCS+">"+pname, parseInt(tagsMap.get(pname))+1); }else{ // tagsMap.set(nameCS+">"+pname, 1); } } } //由此開始,構造符合特定格式的JSON字符串 let obj= []; let setss = new Map(); for (let[k,v] of tagsMap) { var st = k.split(">"); var str = {}; str.source = st[0]; str.target = st[1]; str.value = v; obj.push(str); if(setss.get(st[0]) != null){ // 類節點 每次加100 setss.set(st[0], parseInt(setss.get(st[0]))+100); }else{ // setss.set(st[0], 100); } if(setss.get(st[1].trim()) != null){ // 標籤節點 每次加1 setss.set(st[1], parseInt(setss.get(st[1]))+1); setss.set(st[0], parseInt(setss.get(st[0]))+100); }else{ // setss.set(st[1], 1); setss.set(st[0], parseInt(setss.get(st[0]))+100); } } let obk= []; for (let [k,v] of setss) { var str = {}; str.id = k.trim(); str.group = v; //通過數量分類 obk.push(str); } let d3str = {}; d3str.nodes = obk; d3str.links = obj; console.log(JSON.stringify(d3str).trim()); //按第三步說的,可以手動放置數據到可視化頁面 return JSON.stringify(d3str).trim(); //或按第四步,將數據返回至ejs模板中,直接渲染出可視化頁面 });
注意上述代碼中的注釋,這裡利用了類節點和標籤節點出現的次數,來分辨兩種節點的種類,因為繪製時類節點和標籤節點都是一視同仁的被繪製。如何分辨呢?在可視化頁面中有以下代碼:
var circles = node.append("circle") .attr("r", function(d) { if(d.group>=100) return d.group/100*(10.00/48.00)+1; //取整 else return d.group+1; })
按照不同的次數計算步長,得到的類節點的次數一定是100的倍數,而標籤節點的次數一定小於100,這個值可以設的很大,從而讓兩者不可能出現交集。在判斷時「如果次數大於100」,那麼就是類節點,取整百的好處是,歸一化方便。例如上述代碼需要給定節點的大小,類節點的次數統計可能是100-4800(1-48次),而標籤節點的次數卻是1-10(1-10次),如是,兩者應繪製的一樣大。這就需要歸一化,只需要縮放100倍再乘比例係數即可。
最終調用
上文中hexo.extend.helper.register(『getPostData』, () => {})的「getPostData」即註冊的函數名,在ejs(或其他)模板中直接調用即可。但由於我希望把這個可視化模塊放在我的評論頁或者關於頁面,而這兩個頁面都不是渲染出來的,所以就只能採用先前第三步的做法,只構造出數據,再手動放入可視化頁面。
// 在 index.ejs 內添加: <% var arr = getPostData(); %>
所以,需要做的就是找一個渲染頁面的ejs,調用下該函數即可,這裡放在index.ejs里,注意由於分頁可能該模板會構造很多次,所以就會重複輸出很多遍JSON數據。
基本上還是抓住代碼執行的輸入輸出做文章。從待改造代碼的輸入找格式,然後從原代碼的框架中構造出該格式的數據(輸出),就像適配一樣,如此便可以利用Hexo可以獲得的數據,藉助D3.js等可視化庫,把自己的博客(知識系統)做一個梳理和呈現,從而更好的幫助自己管理和維護,也給了自己二次挖掘自己知識的機會。