上周末,我在可视化世界历史,主要使用了QlikSense和QlikSense的扩展。然而我遇到了一个问题,不得不深入了解QlikSense扩展的实现原理,从而修改这个插件,满足我的需求。这里记录一下目前对QlikSense扩展开发的认识。

问题

我的世界历史可视化作品,最早的文明是公元前3300年的古埃及,等到我输入到Excel之后,才发现,Excel只支持公元1900年之后的年份。于是我在数据源表格中只记录历史节点的年份信息,然后在QlikSense中使用创建日期的函数MakeDate(Year, 1, 1)来创建日期,没想到,QlikSense也只支持公元后的年份。于是,我被迫使用这么一种变通方法:将最早的公元前年份加上一个Offset,伪装成一个公元后的日期,等到在Dashboard中展示的时候,通过Dual函数,减去Offset,重新渲染成带公元前后的字符串。

这招对QlikSense自带的折线图是有效的,但是对我采用的Timeline扩展确没有起效,于是,我不得不分析QlikSense扩展的实现原理。这里主要参考的是@stefanwalther在github上的QlikSense扩展开发教程

QlikSense Extension开发的一些知识

@stefanwalther老师这个教程本身还处在连载状态,我学习到足够修改Timeline插件的知识之后,就暂时先放下了。这里记录一些学习到的知识,以备后用。

QlikSense Extension的核心

其实QlikSense扩展的核心有两部分,一个是.qext元数据文件,里面包含了扩展的名称,图标,说明等信息。还有一个是JavaScript脚本文件,这个才是重头戏,负责渲染图形,处理各种事件。

define( [ /* dependencies */ ],
	function ( /* returned dependencies as arguments */ ) {
		'use strict';
		return {

			// Paint resp.Rendering logic
			paint: function ( $element, layout ) {
				// Your rendering code goes here ...				
			}
		};
	} );

这个就是扩展脚本的整体框架,核心就是paint函数,里面有两个重要的参数

  • $element, 其实就是被渲染的HTML结构
  • layout, 包含了数据和属性信息

调试

  1. 可以在桌面版QlikSense里,按住Ctrl + Shift + 右键,点击Show DevTools
  2. 打开一个QlikSense应用之后,用自己的浏览器访问localhost:4848/hub,这样还可以使用自己熟悉的前端调试工具,比如FireBug
  3. 可以使用console.log('someMessage', someObject)来打印调试信息

从Qlik Engine中获得数据

接下来的问题是,扩展是怎么样从QlikSense中获得数据的呢?

数据可以通过layout.qHyperCube获得

  • layout.qHyperCube.qDimensionInfo, 包含了使用到的维度的信息
  • layout.qHyperCube.qMeasureInfo,包含了使用到的度量的信息
  • layout.qHyperCube.qDataPages,详细的数据

hc.qDimensionInfo[i].qFallbackTitle就是dimension的名字

hc.qDataPages[0].qMatrix可能没有全量的数据,可以随着图表的滚动,来获得新的数据。

Timeline扩展的分析

通过以上的知识,可以知道,QlikSense扩展的核心是paint函数,在Timeline脚本的284行,作者把qMatrix中的数据,转换成一个dataset,然后赋给vis.DataSet,就会调用vis这个第三方可视化库,创建最终的图形。

var dataItems = new vis.DataSet(dataSet);
var container = document.getElementById(containerId);
var timeline = new vis.Timeline(container);
timeline.setOptions(options);
if (useGroups) timeline.setGroups(groups);
timeline.setItems(dataItems);

构造这个DataSet,使用的是这些数据:

var dataItem = {
	id: e[0].qElemNumber,
	content: e[1].qText,
	start: dateFromQlikNumber(e[2].qNum)
};

所以,最终,只要修改dateFromQlikNumber,加上我的workaround,把QlikSense中得到的日期时间,减去Offset,就可以让公元前的日期显示为一个负数。

我对世界历史感兴趣,我想了解当今这个世界是怎么被塑造起来的。我想了解这个世界上曾经存在过的伟大文明,他们所达到过的最远的边界,他们创造的伟大成就,他们留给我们的遗产,以及他们最终消逝在何方。

我在初中和高中所接受的历史教育,基本差不多:一年中国古代史,一年中国近代史,一年世界历史。当时的环境没有现在好,电脑和网络还没像现在这样普及,所以主要的媒介是纸质的教科书。教科书的主要问题是,信息有限。三年六册薄薄的书本,涵盖不了多少内容。第二个问题是,纸上的文字,很难和时间,地理位置对应上。最终,感觉每一段书上的历史,都是孤立的历史片段。于是,就会有这么一些疑问:曾经建造金字塔的古埃及人后来怎么样了,希腊和罗马后来怎么样了,法国,英国,德国,又是怎么崛起的,他们还没有阔起来之前,又是在哪里。作为一个中国人,又忍不住把中华文明和其他文明进行比较,但是用线性的历史书比较起来很不方便。

我最近突然有个想法,如果把历史朝代用甘特图(Gantt Chart)来表示,同时结合地图,就可以将时间和空间信息结合在一起,把历史进行可视化。我于是使用了用的比较顺手的QlikSense软件来创建一个Prototype。

QlikSense的主要优点有:

  • 单机版是免费的
  • Qlik强大的关联引擎(Associative Engine)可以将数据关联起来,在各种维度上进行钻取,跳转非常方便
  • Sense开放了前端的接口,现在已经有很多第三方开发的前端的扩展,能够提供额外的可视化功能

上周末,我做出了一个简单的prototype,大致长这个样子: WorldHistory

基本有以下几个组件:

  • 左上角有一个文明的选择菜单
  • 坐下有一个地图,不过使用的是现代国家的疆域
  • 主体是通过Timeline Extension实现的历史朝代的甘特图(Gantt Chart)
  • 下方是详细列表和一个时间轴(Timeline目前不支持选择,只支持移动和缩放)

通过Qlik的关联引擎,整个Dashboard的图、表都是相互关联的,在地图上点击埃及,就会出现埃及相关的文明(未来我会加上亚述,阿拉伯文明)。在下方的时间轴上选择一个时间范围,就会只显示那个时间范围中的历史信息。

目前,我只有中国的历史朝代的大的年表还有埃及的历史年表。但是,你可以看到,通过简单的可视化,就可以发现,古代埃及文明领先了中国不止1500年。埃及人建造最大的金字塔——胡夫金字塔的时候,中国还是上古神话时代,连有争议的夏朝,都还没有开始。这种有意思的对比,会随着历史信息的不断加入而变得越来越多,结合地图,能够给人更直观的理解。

接下来,我会逐步的添加各个古代文明的信息。主要的参考是斯塔夫里阿诺斯所著的《全球通史》一书。另外,吴军老师的《文明之光》也很不错。

最近开始了一个新项目,需要频繁的密集进行开会。这里来总结一些开会的经验和教训,让会议更加高效,节约每个与会者的时间和生命。

开会前的准备

开会前,首先要发送会议请求给自己认为需要参加这个会议的人。这个会议请求虽然看似简单,也是有很多门道的。

  • 一定在会议预定时间的一天前发送会议请求,特别要考虑到时区和节假日的问题,不要让收到会议邀请的人感觉措手不及,甚至把会议时间设定在他们可能打开邮箱之前。
  • 在会议邀请的邮件正文中,要说明这次会议的目标,议题或者需要解决的问题。如果需要对方做准备,也一定要尽早说明清楚,节省双方的时间
  • 会议的参加者名单,有些人可以设置为Optional。如果和对方是第一次见面,需要简单的说明自己的职位,介绍相互之间的关系和桥梁,让对方明白自己建立这个会议的必要性

会议中

  • 进入会议一定要准时,最好提前两三分钟进入会议室或者电话会议,如果是项目经理,可以跟组内成员定下这个规矩
  • 会议开始后,会议的主持人可以先重申一下本次会议需要的目标和大概的时间安排,使大家专注与讨论跟目标相关的事务
  • 会议过程中,主持人还要把控好会议的节奏,避免出现离题太远的情况
  • 会议即将结束的时候,主持人可以总结归纳今天的内容,比如做出的决定,另外,各个Action Item要明确责任人和截止日期,并且获得当事人的确认

会议后

  • 主持人或者书记需要发送会议纪要
  • Action Item的检查,如果超过截止日期,需要对责任人进行提醒

以上就是我对开会的一些经验和教训。接下来也会不断的践行上面的一些注意点,希望把会议开的更加高效。

这里也推荐这篇文章towards-better-meetings

I

咚哒-咚哒-咚哒。我的心脏要快要从嗓子里跳出来了。浑身上下每个地方都在疼。汗水沿着我的脸流了下来,却不能让我干燥的喉咙得到一丝的水分。我还感受到从左边传来一阵一阵的热浪。我睁开双眼,周围的景色从白茫茫一片逐渐变得清晰起来。

这里是艾欧泽亚大陆北部湖泊银泪湖的中央,帝国战舰残骸上部的一个开阔平台。我所在的小分队正奉命突破这个圆形的据点。在这里,我们遇到了帝国的鸟形机械战机。

我刚睁开的双眼很快认出眼前巨型的大鸟,逆着光,只能辨别出那巨大的机身轮廓,以及那闪着红光的双眼,就像死神的双眼。

在鸟的面前,躺着我的战友,骑士,他已经趴在地上,一动不动。现在,四人小队只剩下我一个人还活着。

难道,就要这样结束了吗?

II

十分钟前。

我和骑士,忍者,治疗法师攻入了这个圆形的露天平台。在这里,视野很好,能够看到远方魔都纳的山脉。但很快,一架鸟形战机便从左边的天空中飞了过来,与我们展开了战斗。

鸟形战机通体乌黑,十分巨大,翼展至少有30米,两边固定的翅膀分别和机身核心一样宽,机身翅膀以上的部分非常光滑,而翅膀下面则有着复杂的机械线条,装备着各式武器,非常厚实。战机靠着帝国神奇的魔道技术悬浮在空中。

我们按照平常的战术,与鸟展开战斗,骑士负责吸引火力,我和忍者分别进行远程攻击和近程攻击,治疗法师在后方进行回复支援。战机有两种范围攻击,一种是扇形的机枪扫射,范围有限,还有一种是直线贯通导弹。虽然比较麻烦,但是注意躲避还是能够避免的。

在战斗持续了一段时间后,形势突然急转直下。

III

鸟在战斗中突然停止了对骑士的攻击,然后,只听得,咚的重响,它向骑士面前投掷了燃烧弹,顿时一簇火焰在平台上燃烧了起来。骑士转过身开始向前冲刺躲避后面的燃烧弹。然而鸟还是在不停的投掷,整个平台顿时被接二连三的火焰分割成了两个半圆。我和骑士在一块,而忍者和治疗在对面一块。

投掷完燃烧弹的鸟停止了对骑士的追击。反而转向了忍者和治疗。糟了,隔着火墙,我看到鸟把忍者和治疗逼到了死角,再用机枪进行扫射。骑士想要冲过去去解救他们,但是火墙让他只能干望着。可恶,我跟骑士只能眼睁睁看着忍者和治疗被鸟的炮击杀死。看着他们倒下的身躯,我感觉我的腹部就像被人重重的打了一拳。

接下来,没有治疗的回复,我和骑士生存的可能性就非常小。只有速战速决,才可能生存下来。在鸟掉头转向我和骑士的时候,我发动了毕生所学的各种技能,将我的战斗力提升到极限。

咚哒,咚哒,咚哒。我看着前方的骑士在硬撑着鸟的各种攻击。然而,他的动作,他的装备,都感觉快要到极限了。突然,鸟又发动了贯通导弹。一股气浪将我吹飞,重重的摔在的地上。眼前一片漆黑。

银泪湖中央,蔚蓝的天空一尘不染。帝国军舰高处的一个圆形露天平台已经被烈焰分割成了左右两边。在刚才贯通炮的巨响后,硝烟渐渐弥散开。平台上倒着四副躯体。

IV

咚哒-咚哒-咚哒

咚哒-咚哒-咚哒

我睁开眼睛,用双手撑起我的上半身,可恶,浑身疼的厉害,估计肋骨断了不少。我的弓箭还掉落在火墙的旁边。鸟正在向我靠近,看样子,它又要发动下一轮攻击了。贯通炮需要时间重新装填,所以应该是是扇形的机枪攻击。我右手用力,将身体转过来,爬向火墙边,去捡我的弓。每走一步,我都头晕的不行,四肢也疼的要命。我捡起了弓箭,回过头,鸟果然开始开始用底部的机枪进行射击。我用尽所有脚步的力量,又向前挪了几部。几发子弹射进了我刚才蹲过的位置。

我的腿已经动不了,左边是火墙,如果鸟再发射一个范围攻击,我就死了。

我转过身,半蹲着。面对鸟,本能的拔箭,上弦,拉弓,瞄准,发射。

咚哒,咚哒,咚哒

鸟在空中起伏着,红色的闪光连成线团。

拔箭,上弦,拉弓,发射。

咚哒,咚哒,咚哒

鸟的下部开始动起来,贯通炮开始准备发射

拔箭,上弦,拉弓,发射

咚哒,咚哒,咚哒

鸟在空中爆炸了

后记

开了2.5之后,回来开始赌博,顺便补了一下主线剧情和事件屋。在密约塔副本里残血单挑老二boss成功,所以写了这么一篇文章。现在看来,这篇文章的主要问题是对角色本身的刻画不足。主角长什么样,什么性格,伙伴们长什么样,相互之间的关系,都没有进行说明。最关键的是,为什么要战斗,意义何在,除了战斗,还有没有别的方法,这些都没有交代。当然,如果谈起意义,需要说明的背景就会变多,篇幅就远不止这样了。另外一方面,有着“超越之力”,从来不担心死亡的我,根本没有用心想过生死攸关的战斗到底会是什么样。

我最早的博客在blogger上,然后你懂的,在墙外很不方便。于是去年买了一个域名,然后在新浪云上搭了一个wordpress。但是新浪云有流量限制,还得做实名身份验证,最后那个博客也弃用了。最近github和markdown用的比较多,想起以前看到阮一峰老师介绍Jekyll和github-pages的文章,于是就实际操练了一把。总的来说这个solution发布文章很方便,页面速度很快,而且支持markdown,语法高亮,我还是很满意的。不过整个过程中坑还是比较多,这里记录一下。

Github-pages是github的一个服务,可以让用户把网页托管在github上。Jekyll是ruby的一个静态博客生成工具。

Install Jekyll

我在Centos下进行Jekyll的安装,坑还是蛮多的。主要参考的是github-pages关于Jekyll的文档

首先得安装ruby, bundler

yum install ruby
gem install bundler

然后安装Jekyll,因为ruby官方的源速度很慢,首先得把gem的源改成taobao的源

gem sources --remove http://rubygems.org/  
gem sources -a http://ruby.taobao.org/  
gem sources -l  

我用的是bundle的方式安装,可以参考淘宝的文档,使用这种方式修改gem源

bundle config mirror.https://rubygems.org https://ruby.taobao.org

然后在一个自定义的目录里创建一个Gemfile,内容就是安装github-pages

gem 'github-pages'

在这个目录里运行bundle进行安装

bundle install 

解决依赖的问题

安装Jekyll出现了很多包缺失的问题,这里列出我所遇到的,各位可以按照实际的错误日志依次安装依赖的包

yum install ruby-devel
yum install gcc	
yum install zlib-devel	
yum install patch
yum install libxml2-devel

Use Jekyll

Jekyll安装完毕之后,使用起来就很方便了。主要可以参考Jekyll的文档

jekyll new site_name

Jekyll会自动创建一个目录,然后进入这个目录

jekyll serve

访问localhost:4000就可以看到博客了

Write posts

要写一篇新的文章,需要到/_posts目录下面创建指定格式的markdown文件 格式必须是

YEAR-MONTH-DAY-title.MARKUP

然后这个文件的开头也必须遵守Front Matter的规定

---
layout: post
title: Blogging Like a Hacker
---

Other Configuration

_config.yml 里面有很多配置项,也都可以参考文档进行配置

Publish to github-pages

github提供了github-pages服务可以托管各种页面,用户需要首先申请一个特定的repo username.github.io, 然后把Jekyll生成的博客目录全部push上去就可以了

yum install git
git init
git clone https://github.com/username/username.github.io
// create jekyll site, write some posts
git push https://github.com/username/username.github.io

本来以为每次写了文章之后还需要jekyll build, 结果好像github自动支持,文章push到repo上后,就会被自动build,非常方便

Config DNS

如果你跟我一样是一个顶级域名的话,可以参考这个文档

  1. 域名商那边添加两个A record, 然后加上CNAME, 跳转到username.github.io
  2. username.github.io中创建一个CNAME文件,里面写上自己的域名就好了

Add Disqus

因为Jekyll是静态博客,所以无法做评论,后来发现现在的评论都有专门的平台服务了。国外比较流行的是Disqus 注册一个账户,然后把Disqus的代码加入到_layouts\post.html里就可以了

Conclusion

以上就是安装Jekyll,搭建静态博客的教程