forced graph

下图是用d3中的forced graph画出的post和tag的关联关系。为了区别post和tag,使用了两种颜色, 并且施加了两个力场,把post向左推,tag向右推。 {% raw %} //准备post和tag的关联数据 var graph={ "nodes":[], "links":[], }; var hash_tag={}; var post_id=0,tag_id=0; {% for post in site.posts %} graph.nodes.push({ "name": "{{ post.title }}", "url":"{{post.url}}", "type":"post", }); post_id=graph.nodes.length-1; {% for tag in post.tags %} if(hash_tag["{{tag}}"]===undefined){ graph.nodes.push({ "name": "{{ tag }}", "url":"/tags.html", "type":"tag", }); tag_id=graph.nodes.length-1; hash_tag["{{tag}}"]=tag_id; } else { tag_id=hash_tag["{{tag}}"]; } graph.links.push({ "source":post_id, "target":tag_id, "value":1 }); {% endfor %} {% endfor %} //数据准备完毕,开始画图 var width = 400, height = 400; //两个力场的中心位置 var foci={ "post":{"x":0,"y":height/2}, "tag":{"x":width,"y":height/2}, }; var color = d3.scale.category10(); var force = d3.layout.force() .linkDistance(100) .linkStrength(.3) .gravity(0) .size([width, height]); var svg = d3.select("#svg").append("svg") .attr("width", width) .attr("height", height); force .charge(-400) .nodes(graph.nodes) .links(graph.links) .start(); var link = svg.selectAll(".link") .data(graph.links) .enter().append("line") .attr("class", "link"); var node = svg.selectAll(".node") .data(graph.nodes) .enter().append("circle") .attr("class", "node") .attr("r",10) .on("click",function(d){ window.open(d.url); }) .style("cursor","pointer") .style("fill", function(d) { return color(d.type); }) .call(force.drag); node.append("title") .text(function(d) { return d.name; }); force.on("tick", function(e) { 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; }); //e.alpha是“温度”,粒子的“活性”与之相关,它是一个不断下降的变量,模拟冷却过程。 var k = .5 * e.alpha; //让每个粒子都被力场减速吸向中心,直到温度为0 //同时每个粒子还受到库仑力(由force.charge定义),从而彼此不会聚合在一起。 graph.nodes.forEach(function(o, i) { o.y += (foci[o.type].y - o.y) * k; o.x += (foci[o.type].x - o.x) * k; }); node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); }); {% endraw %} 效果如下: ...

July 26, 2013

用d3.js来呈现post-tag的多对多关系

最近想用更好的方式呈现blog和tag之间的关系,也就是重写本博客的标签页面。 log和tag之间是多对多的关系,就是说一篇blog可以有多个tag,一个tag可以包含多篇blog。适合表现这种映射关系的图表叫做平行集(Parallel Set)。 平行集是反应两组或以上数据集之间映射关系的图表。 d3是一个用javascript的库,主要用途是用svg做数据可视化。 d3非常强大,基本上能想到的图表都可以实现。而对于平行集,d3有一个插件d3.parsets。 需要添加如下css和js: d3.parsets.css d3.v3.min.js d3.parsets.js 然后在tags.html里写一些javascript。 先构造出所有post和tag的对应数据。d3.parsets使用csv格式,所以要先生成一个csv格式的字符串。在这里,列名就是post,tag。 比如本篇blog对应两个tag:d3和visualization,那么要在csv里添加两行: {{ page.title }},d3 {{ page.title }},visualization 相关代码如下: {%raw%} var csv="post,tag"; {% for post in site.posts %} {% for tag in post.tags %} csv+="\n{{post.title}},{{tag}}"; {% endfor %} {% endfor %} {%endraw%} 接着实例化一个chart,再把数据给它: var chart = d3.parsets() .tension(0.8) .width("800") .height("480") .dimensions(["tag","post"]); var vis = d3.select("#vis").append("svg") .attr("width", chart.width()) .attr("height", chart.height()); var parsed_csv=d3.csv.parse(csv); vis.datum(parsed_csv).call(chart); 我还想实现这样一个功能:点击图中的博客时可以跳转到博客页面。 {%raw%} var posts={}; {% for post in site.posts %} posts["{{post.title}}"]={ "url":"{{post.url}}", }; {% endfor %} vis.selectAll(".category text").on("click",function(d){ if(!(d.name in posts)) return; window.open(posts[d.name].url); }); {%endraw%} d3虽然可以看到很多jQuery的影子, 但在data和view的生成方式上很独特,非常的描述化。 这篇文章是作者自己总结的d3处理数据上的一种模式,也就是所谓的join。

July 22, 2013

删除github pages的master分支

github pages默认使用两个分支:master和gh-pages。 在两个分支间切换很不方便:在gh-pages写了一些blog, 然后切回master进行一次push,才能让服务器重新build。 但是可以删除master。 首先到github.com的repo设置里面把默认分支设为gh-pages,然后删除master分支,最后回到本地,用 git branch -D master 删除master分支。 但是删除master后,发现远端页面更新非常缓慢,而且页面状态不稳定(有的页面更新了,有的则没有)。因此后来恢复了用两个分支。有文章介绍可以用post commit hook来保持两个分支的一致性。 ###update 2013-08-07 写了一个bat脚本,取名push.bat @echo off rem 提示用户输入comment set /p comment="comments:" rem 在master分支上commit git commit -am "%comment%" git checkout gh-pages rem rebase命令使得gh-pages和master同步,然后生成一个commit git rebase master git checkout master git push 使用时直接输入push

July 21, 2013

github pages 配置

首先去github pages申请一个页面,默认的域名是http://USERNAME.github.io,其中USERNAME是你的github用户名。 github使用jekyll做解析引擎。可以直接克隆一个别人写好的jekyll,然后把上传路径改成自己的,像这样: git clone https://github.com/plusjade/jekyll-bootstrap.git USERNAME.github.com cd USERNAME.github.com git remote set-url origin git@github.com:USERNAME/USERNAME.github.com.git git push origin master 这个例子中使用的是jekyll-bootstrap。 ###写文章 如果装了ruby的话,直接在文件夹下执行 rake post title="a new post" 就可以新建一篇文章。内容支持markdown。 其实就是在_post目录下新建了一个md文件,文件名格式为年-月-日-标题.md。 文件开始是一些元信息,比如本文的md文件开头是这样的: --- layout: post title: "github pages 配置" description: "rake post title="a new post" layout: post title: "github pages 配置" description: "" category: misc tags: [jekyll,github pages,setup] 写好了内容部分,直接`git commit`然后`git push`到master分支就可以了。 comments : provider : disqus disqus : short_name : YOUR_DISQUS_SITE_NAME 你需要去disqus.com注册一个用户,然后在dashboard中新建一个站点,注意把`short_name`设置为配置文件中的那个`YOUR_DISQUS_SITE_NAME`。 $ CHCP 65001 如果是在git bash中的话,需要设置如下环境变量: export LC_ALL = enUS.UTF-8 export LANG = enUS.UTF-8 然后就可以 `jekyll serve --watch`了。 gem uninstall pygments.rb --version ">0.5.0" gem install pygments.rb --version "=0.5.0"" category: misc tags: [jekyll,github pages,setup] --- 写好了内容部分,直接git commit然后git push到master分支就可以了。 ...

July 19, 2013

为gem install 提速

编辑.gemrc,内容如下: :bulk_threshold: 1000 :backtrace: false gemcutter_key: XXXXX gem: --no-ri --no-rdoc :benchmark: false :verbose: true :update_sources: true :sources: - http://gems.rubyforge.org/ - http://gems.opscode.com 主要起作用的是gem: --no-ri --no-rdoc,少下载了很多文档。

July 19, 2013

Covid Report

Data description: Daily new confirmed covid cases of every province in the China mainland area. Move the slider to see daily data since Feburary 2020. Click on the button to travel through time. The source: National Health Commitee Data are updated weekly. link

August 8, 2012