专栏首页三言两语纵览全局垂直打击的组织模式(下)

纵览全局垂直打击的组织模式(下)

本文详细记录了如何在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中建议的插入方式是:

  1. 在专门放置自定义JavaScript处理逻辑的文件中(plugin.js)放入代码,并使用内建函数。
  2. 在ejs(或其他)模板的相关位置,使用<%%>方式调用上述内建函数
  3. 使用console.log在渲染html时(hexo generate时的黑框)输出至Console里,拿到输出数据,放入到可视化的页面中即可。
  4. 或者一气呵成,直接将可视化的代码写入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等可视化库,把自己的博客(知识系统)做一个梳理和呈现,从而更好的帮助自己管理维护,也给了自己二次挖掘自己知识的机会。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 引入基于LCloud的页面访问统计到Hexo博客中

    由于本主题(Volantis)使用的 Valine 暂时仍然无法正常开启 Counter 页面计数,所以,在单一页面上展示出该页面的访问量就成了一个问题。不蒜子...

    ZONGLYN
  • 注册型网站设计的阶段总结

    这是我自己想的方法,不知道大众化的方法是怎样实现的,其实分色就是利用bgcolor这个属性给表格上色

    ZONGLYN
  • Kafka集群常用命令

    ZONGLYN
  • ztree实现根节点单击事件,显示节点信息

    这段时间在维护公司的项目,去年做的项目里面有ztree树的例子,想起之前还没有开始写博客,一些知识点也无从找起,要新加一个右击节点事件,折腾了半天,其中也包含了...

    祈澈菇凉
  • 总结Js方法工具类库,总有你需要的方法

    实际开发工程中,会遇到一些重复的逻辑功能处理或者说是比较繁琐的前端函数。粗略整理了一下,希望对正在浏览的你有所帮助,也希望帮助新入行的小白们解决一下工作中的负担...

    Parker
  • 用canvas制作彩虹球喷枪(js面向对象小案例)

    剽悍一小兔
  • 记账类问题汇总

    Zoctopus
  • Bypass ngx_lua_waf SQL 注入防御(多姿势)

    ngx_lua_waf 是一款基于 ngx_lua 的 web 应用防火墙,使用简单,高性能、轻量级。默认防御规则在 wafconf 目录中,摘录几条核心的 S...

    信安之路
  • TypeScript 可辨识联合类型

    TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守...

    阿宝哥
  • iOS14 - 为听力障碍人士打造的声音识别功能

    从应用角度,对于很多人来说可能是锦上添花的功能,但对于听力障碍人士来说,相当于拥有了一双可以感知环境声音的耳朵。尽管所支持的声音感知还比较有限,但诸如高静音,孩...

    用户6026865

扫码关注云+社区

领取腾讯云代金券