d3.js实战 git提交统计图
通过之前的多篇博文的详细介绍,我们已经对d3的开发过程有了相当深入的了解和熟悉。在这篇文章中,我们将利用这些知识,来开发一个展示git提交记录的效果。
首先,我们需要明确我们的目标:我们想要创建一个可视化的图表,能够清晰地展示出git的提交历史。这个图表应该包含每个提交的时间、作者、提交信息等基本信息,并且以一种直观的方式展示出来,使得我们可以一目了然地看到项目的发展历程。
为了实现这个目标,我们需要使用d3的各种功能。首先,我们需要使用d3的数据绑定功能,将我们的git提交数据绑定到图表上。这样,每当我们的数据发生变化时,图表也会自动更新。
接下来,我们需要使用d3的布局功能,来决定如何在我们的图表中放置每个提交。我们可以选择线性布局,使得最新的提交总是在图表的最右边;也可以选择径向布局,使得最早的提交位于图表的中心,而最新的提交位于边缘。
然后,我们需要使用d3的绘图功能,来绘制出每个提交的具体形状。我们可以使用矩形、圆形、箭头等各种形状来表示每个提交,使得我们的图表更加丰富和有趣。
最后,我们需要使用d3的交互功能,来增加我们的图表的互动性。我们可以添加鼠标悬停事件,当用户将鼠标移动到一个提交上时,显示出该提交的详细信息;也可以添加点击事件,当用户点击一个提交时,跳转到该提交的代码页面。
下图是d3作者之一mbostock的git实际提交图,很震撼吧。
设计和配色我们直接仿照github。
这段代码定义了一个名为Gitcommit
的类,它继承自Coresource
类。这个类主要用于绘制一个表示Git提交历史的矩形图。以下是代码的主要功能和结构:
-
构造函数:在构造函数中,首先调用父类的构造函数,然后创建一个SVG元素,并将其宽度和高度设置为传入的div的宽度和高度。接着,创建两个分组元素
commitG
和infoG
,并将它们的位置设置为绝对定位。最后,调用drawRects
和drawInfo
方法来绘制矩形和信息文本。 -
updateData
方法:这个方法接收一个参数s
,用于更新矩形图中的数据。它调用drawRects
方法来重新绘制矩形。 -
drawRects
方法:这个方法接收一个参数s
,用于生成随机数据。首先,根据给定的参数生成一个包含53周(每周7天)加上2个特殊日期(新年和圣诞节)共366天的数据集。然后,使用D3.js库将数据绑定到矩形元素上,并设置矩形的属性,如宽度、高度、颜色等。最后,返回矩形的宽度和高度。 -
drawInfo
方法:这个方法用于绘制矩形图的相关信息。首先,创建一个矩形作为背景,并在其上方添加一些文本,如"Learn how we count contributions."、"Less"和"More"。接着,为每个月和每周创建文本元素,并设置它们的属性,如位置、颜色等。最后,为每个颜色创建一个矩形元素,并设置它们的属性,如位置、颜色等。
通过这个类的实例化和方法调用,可以生成一个表示Git提交历史的矩形图。
js
class Gitcommit extends Coresource {
private svg: any;
private commitG: any;
private infoG: any;
private colors = ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39']; // 定义颜色数组
private month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; // 定义月份数组
private week = ["Mon", "Wed", "Fri"]; // 定义星期数组
constructor(div, options) {
super(div, options); // 调用父类构造函数
console.log('object :>> ', this.divWidth, this.divHeight);
// 创建 SVG 元素
this.svg = d3.select(div)
.append('svg')
.attr('width', this.divWidth)
.attr('height', this.divHeight)
.style('position', 'absolute');
// 创建 commitG 和 infoG 分组元素
this.commitG = this.svg.append('g').attr('class', 'commitG')
.attr('transform', 'translate(' + this.divWidth / 2 + ',' + 160 + ')');
this.infoG = this.svg.append('g').attr('class', 'infoG')
.attr('transform', 'translate(' + this.divWidth / 2 + ',' + (160) + ')');
// 绘制矩形并返回矩形的宽度和高度
let { boxw, boxh } = this.drawRects(5);
// 绘制信息区域
this.drawIonf(boxw, boxh);
}
// 更新数据
public updateData(s) {
this.drawRects(s);
}
// 绘制矩形
private drawRects(s) {
let num = 53 * 7 + 2 + 3; // 计算总共需要绘制的矩形数量
let data = [];
// 生成随机数据
for (let i = 0; i < num; i++) {
let k = Math.round(Math.random() * 4);
if (s < Math.random() * 10) {
k = 0;
}
data.push(k);
}
// 选择并绑定数据
let g = this.commitG.selectAll('g').filter('.rects')
.data([1])
.join('g')
.attr('class', 'rects');
// 绘制矩形
g.selectAll('rect')
.data(data)
.join('rect')
.attr('width', 10)
.attr('height', 10)
.attr('x', (d, i) => Math.floor(i / 7) * 13)
.attr('y', (d, i) => Math.floor(i % 7) * 13)
.attr('rx', 2)
.attr('ry', 2)
.attr('fill', d => this.colors[d])
.attr('stroke', '#1b1f230f')
.attr('stroke-width', 1)
.attr('opacity', (d, i) => i < 3 ? 0 : 1);
// 计算并返回矩形的宽度和高度
let boxw = 10, boxh = 10;
g.each(function () {
let box = this.getBBox();
boxw = box.width;
boxh = box.height;
d3.select(this).attr('transform', 'translate(' + (-boxw / 2) + ',' + (-boxh / 2) + ')');
});
return { "boxw": boxw, "boxh": boxh };
}
// 绘制信息区域
private drawIonf(boxw, boxh) {
let backw = boxw + 70;
let backh = boxh + 80;
// 绘制背景矩形
this.infoG.append('rect')
.attr('width', backw)
.attr('height', backh)
.attr('x', -backw / 2 - 10)
.attr('y', -backh / 2)
.attr('rx', 8)
.attr('ry', 8)
.attr('stroke', '#e1e4e8')
.attr('fill', 'none')
.attr('stroke-width', 1);
// 添加文本信息
this.infoG.append('text')
.text('Learn how we count contributions.')
.attr('fill', '#586069')
.attr('font-size', '0.75em')
.attr('x', -boxw / 2)
.attr('y', boxh / 2 + 20)
.attr('font-size', '12px');
// 添加更多文本信息和颜色示例矩形
// 省略部分代码...
// 添加月份标签
this.infoG.selectAll('text').filter('month')
.data(this.month)
.join('text')
.attr('class', 'month')
.text(d => d)
.attr('x', (d, i) => i * 59 - boxw / 2)
.attr('y', -boxh / 2 - 10)
.attr('fill', '#24292e')
.attr('font-size', '9px');
// 添加星期标签
this.infoG.selectAll('text').filter('week')
.attr('class', 'week')
.data(this.week)
.join('text')
.text(d => d)
.attr('x', -boxw / 2 - 30)
.attr('y', (d, i) => i * 25 + 22 - boxh / 2)
.attr('fill', '#24292e')
.attr('font-size', '9px');
}
}