可视化想法出现频率

全篇共 4584 字。按500字/分钟阅读,预计用时 9.2 分钟。此篇想法总访问 69 次,今日访问 1 次。

我的博客主页有个可视化组件,它能动态呈现每月想法发布频率,灵感来自GitHub上代码提交频率图,不同的是,我的基本单位是月,GitHub的基本单位是天。一天提交许多次代码很常见,一天写很多篇文章很困难。

帅华君博客各月想法发布频率可视化组件

截止到今天,它可视化了我自2015到2019这5年间每月想法发布频率。
2016年6月份大学毕业。
2017年,开始有记录想法的意识。
2018年,写作断断续续的僵持着,心里有两个小人在打架。

直到2019年,终于像是找到了信仰,坚持,无以言表的感动。
个性影响着习惯,坚持着的习惯反过来点点滴滴地塑造着个性。
随着年龄的增长,要思考的事情增多,记性的减退,坚持写作,坚持记录当下的益处越发显著。


2019年,我终于有了很多时间,陆续地整理工作这三年的想法。
Airglass这个实验项目,是我在第一家公司时,想利用业余时间做的,
因为主观与客观的原因,竟然拖到了现在才完成Airglass。
Airglass库的开发仅用了我一周的时间。

Airglass依赖HTML5的Canvas能力。
我“厌烦”其它库为开发者考虑太多,学习成本增加,创造的可能性减少。个人感觉。
Airglass的作为很少,只定义了渲染器类、可渲染抽象类,以及对常见交互事件的封装。

我个人很喜欢Airglass的“无为”与可能性。
我的另一个实验项目“FUI”,基于Airglass开发的一系列未来用户界面风格的组件。
博客想法发布频率的可视化组件,就是基于Airglass开发。


准备数据。
去数据库里查,第一篇想法发布的年份,2015年。
每一篇想法在数据库里都有一条记录。
就仅呈现按月发布想法的频率这以需求,只需获取每条记录的创建日期即可。
下面的SQL查询语句里,我查找出每条想法的标题和创建日期。

let articleCount = 0;
let analyticsArticle = {};
for (let year = 2015; year <= (new Date).getFullYear(); year++) {
  analyticsArticle[year] = [];
  for (let month = 0; month < 12; month++) analyticsArticle[year][month] = [];
  let yearArticleData = await utils.query('autumn', `select title,timestamp from article where timestamp > ${new Date('' + year).getTime()} and timestamp < ${new Date('' + (year + 1)).getTime()} order by timestamp`);
  for (let i = 0; i < yearArticleData.length; i++) {
    articleCount++;
    let date = new Date(+yearArticleData[i].timestamp)
    let month = date.getMonth();
    analyticsArticle[year][month].push(1);
  }
}

整理数据。
每一行色块代表一年,每一个色块代表一个月。
上面代码片段,我把准备的数据,按年和月整理,方便在前端呈现。

new Date() 传入数字类型,JS会理解成传入的是时间戳,
如果传入字符串,JS理解成传入带格式的日期,
new Date(''+year)new Date(``+(year+1)) 传入实参为字符串,JS会按日期格式处理。

先初始化每年的12个月,遍历当前年份发布的全部想法,
得到这些想法发布的月份,将想法放入各自发布时月份的数组内。


前端JS代码部分,依赖Airglass。
创建Airglass实例:

let ag = new airglass.Airglass({
  element: document.getElementById('analyticsArticle'),
  width: 280,
  height: 120
})

我博客主页右侧边栏的宽度为300像素左右,
设置Airglass实例固定宽度280像素,高度120像素。

帅华君博客想法发布频率鼠标悬停效果

鼠标悬停在色块上,显示当月想法的发布次数。
共需两层Glass,底层渲染色块,上层显示鼠标悬停时出现的数字。

let renderer_main = ag.addRenderer('main');
let renderer_selected = ag.addRenderer('selected');

定义色块组件。
Airglass提供扩展抽象类的工具函数 airglass.extend
坚持面向对象的编程思维,编写代表色块的类。
每一个色块实例都有各自的位置、尺寸、颜色属性,
以及所属年份和表示的月份。
在编写类之前,想清楚它都能做什么,代码就顺理成章。

let MonthNode = airglass.extend(airglass.Renderable, {
  _constructor: function (params) {
    this.path = new Path2D;
    this.year = params.year;
    this.month = params.month;
    this.x = params.x * devicePixelRatio;
    this.y = params.y * devicePixelRatio;
    this.width = params.width * devicePixelRatio;
    this.height = params.height * devicePixelRatio;
    this.r = Math.round(this.width * 0.2);
    let colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
    this.dataLength = params.data.length
    if (this.dataLength == 0) {
      this.fill = colors[0];
    } else if (params.data.length > 0 && params.data.length <= 3) {
      this.fill = colors[1];
    } else if (params.data.length > 3 && params.data.length <= 9) {
      this.fill = colors[2];
    } else if (params.data.length > 9 && params.data.length <= 16) {
      this.fill = colors[3];
    } else {
      this.fill = colors[4];
    }
  },
  updatePath: function () {
    let path = new Path2D;
    path.moveTo(this.x + this.r, this.y);
    path.lineTo(this.x + this.width - this.r, this.y);
    path.arcTo(this.x + this.width, this.y, this.x + this.width, this.y + this.r, this.r);
    path.lineTo(this.x + this.width, this.y + this.height - this.r);
    path.arcTo(this.x + this.width, this.y + this.height, this.x + this.width - this.r, this.y + this.height, this.r);
    path.lineTo(this.x + this.r, this.y + this.height);
    path.arcTo(this.x, this.y + this.height, this.x, this.height - this.r, this.r);
    path.lineTo(this.x, this.y + this.r);
    path.arcTo(this.x, this.y, this.x + this.r, this.y, this.r);
    this.path = path;
  },
  draw: function (ctx) {
    ctx.fillStyle = this.fill;
    ctx.fill(this.path);
  }
})

鼠标悬停事件。
Airglass实例拥有订阅事件的能力。
若想订阅鼠标在Glass上悬停的事件,只需判断事件类型是否为“mousemove”。
renderer.getElementsContainPoint(event) 方法能获取事件发生的位置处是否有组件。
从下方代码逻辑看,当鼠标经过了至少发布了一个想法的色块时,显示我在该月发布的想法数量。

ag.subscribe(function (event) {
  if (event.type == 'mousemove') {
    let monthNodes = renderer_main.getElementsContainPoint(event);
    if (monthNodes.length) {
      let activeMonthNode = monthNodes[monthNodes.length - 1];
      renderer_selected.clear();
      if (activeMonthNode.dataLength) {
        ag.element.style.cursor = 'pointer';
        let ctx = renderer_selected.element.getContext('2d');
        ctx.font = `normal bold ${12 * ag.DPR}px sans-serif`;
        ctx.textBaseline = 'middle';
        ctx.textAlign = 'center';
        ctx.fillStyle = '#fff';
        ctx.fillText(activeMonthNode.dataLength, activeMonthNode.x + activeMonthNode.width / 2, activeMonthNode.y + activeMonthNode.height / 2)
      } else {
        ag.element.style.cursor = '';
      }
    }
  }
})

往往当悬停目标可点击时,鼠标指针应该变成小手的形状。
上方代码中,设置 cursor 为 pointer 即可。

鼠标点击或在移动端,手指触摸色块,出发 touchstart 事件。
touchstart 和 mousemove 事件的判断方式相同。
立即跳转到显示当月所有想法的页面。

if (event.type == 'touchstart') {
  touchstart: {
    let monthNodes = renderer_main.getElementsContainPoint(event);
    if (monthNodes.length) {
      let activeMonthNode = monthNodes[monthNodes.length - 1];
      if (activeMonthNode.dataLength) {
        let url = `/idea/${activeMonthNode.year}/${activeMonthNode.month+1}/`;
        window.location = url;
      }
    }
  }
}
发布日期 » 2019年11月29日 周五
原创声明 » 请勿复制转载,谢谢配合。
Airglass.js核心库
JavaScript核心概念
硬件编程、Arduino
文档翻译计划
微信开发
前端脚手架
运维
可视化
生活自有“道”理
视觉设计、用户体验
陈帅华的微信二维码