数据可视化测试驱动开发与交互式分析库介绍
1. 测试驱动可视化 - 图表创建
在测试环境准备就绪后,我们可以开始以测试驱动的方式开发一个简单的柱状图。虽然这和之前创建柱状图的操作类似,但此次重点在于展示如何在每一步构建测试用例,并自动验证柱状图的实现是否符合预期。
1.1 测试用例设置
首先要确保柱状图的实现存在并能接收数据。以下是测试用例的代码:
describe('BarChart', function () {
var div,
chart,
data = [
{x: 0, y: 0},
{x: 1, y: 3},
{x: 2, y: 6}
];
beforeEach(function () {
div = d3.select('body').append('div');
chart = BarChart(div);
});
afterEach(function () {
div.remove();
});
describe('.data', function () {
it('should allow setting and retrieve chart data',
function () {
expect(chart.data(data).data()).toBe(data);
});
});
});
在这个测试用例中,使用了几个Jasmine的构造函数:
-
describe
:定义一组测试用例,可以嵌套子组并定义测试用例。
-
it
:定义一个测试用例。
-
beforeEach
:定义一个预执行钩子,在每个测试用例执行前执行给定的函数。
-
afterEach
:定义一个后执行钩子,在每个测试用例执行后执行给定的函数。
-
expect
:定义测试用例中的期望,可以与匹配器(如
toBe
和
toBeEmpty
)链式调用以进行断言。
1.2 实现柱状图对象
运行上述测试用例时,会提示没有
BarChart
对象,因此需要创建该对象和函数:
function BarChart(p) {
var that = {};
var _parent = p, _data;
that.data = function (d) {
if (!arguments.length) return _data;
_data = d;
return that;
};
return that;
}
此时再次运行
SpecRunner.html
,测试用例将通过。
2. 测试驱动可视化 - SVG渲染
在创建了柱状图对象的基本框架后,接下来尝试生成
svg:svg
元素。
2.1 测试用例设置
渲染
svg:svg
元素不仅要将其添加到HTML主体中,还要将图表对象的宽度和高度设置转换为适当的SVG属性。以下是测试用例:
describe('.render', function () {
describe('svg', function () {
it('should generate svg', function () {
chart.render();
expect(svg()).not.toBeEmpty();
});
it('should set default svg height and width',
function () {
chart.render();
expect(svg().attr('width')).toBe('500');
expect(svg().attr('height')).toBe('350');
});
it('should allow changing svg height and width',
function () {
chart.width(200).height(150).render();
expect(svg().attr('width')).toBe('200');
expect(svg().attr('height')).toBe('150');
});
});
});
function svg() {
return div.select('svg');
}
2.2 实现渲染方法
由于此时还没有
render
函数,上述测试用例都会失败。以下是实现
render
方法的代码:
var _parent = p, _width = 500, _height = 350, _data;
that.render = function () {
var svg = _parent
.append("svg")
.attr("height", _height)
.attr("width", _width);
};
that.width = function (w) {
if (!arguments.length) return _width;
_width = w;
return that;
};
that.height = function (h) {
if (!arguments.length) return _height;
_height = h;
return that;
};
此时再次运行
SpecRunner.html
,测试用例将通过,但此时只是生成了一个空的SVG元素,还未使用数据。
3. 测试驱动可视化 - 像素完美的条形渲染
在这一阶段,我们将使用已有数据生成条形图,并确保所有条形图不仅被渲染,而且具有像素级的精度。
3.1 测试用例设置
以下是测试用例的代码:
describe('chart body', function () {
it('should create body g', function () {
chart.render();
expect(chartBody()).not.toBeEmpty();
});
it('should translate to (left, top)', function () {
chart.render();
expect(chartBody().attr('transform')).
toBe('translate(30,10)')
});
});
describe('bars', function () {
beforeEach(function () {
chart.data(data).width(100).height(100)
.x(d3.scale.linear().domain([0, 3]))
.y(d3.scale.linear().domain([0, 6]))
.render();
});
it('should create 3 svg:rect elements', function () {
expect(bars().size()).toBe(3);
});
it('should calculate bar width automatically',
function () {
bars().each(function () {
expect(d3.select(this).attr('width')).
toBe('18');
});
});
it('should map bar x using x-scale', function () {
expect(d3.select(bars()[0][0]).
attr('x')).toBe('0');
expect(d3.select(bars()[0][1]).
attr('x')).toBe('20');
expect(d3.select(bars()[0][2]).
attr('x')).toBe('40');
});
it('should map bar y using y-scale', function () {
expect(d3.select(bars()[0][0]).
attr('y')).toBe('60');
expect(d3.select(bars()[0][1]).
attr('y')).toBe('30');
expect(d3.select(bars()[0][2]).
attr('y')).toBe('0');
});
it('should calculate bar height based on y',
function () {
expect(d3.select(bars()[0][0]).
attr('height')).toBe('10');
expect(d3.select(bars()[0][1]).
attr('height')).toBe('40');
expect(d3.select(bars()[0][2]).
attr('height')).toBe('70');
});
});
function chartBody() {
return svg().select('g.body');
}
function bars() {
return chartBody().selectAll('rect.bar');
}
3.2 实现渲染方法
以下是实现渲染方法的代码:
var _parent = p, _width = 500, _height = 350,
_margins = {top: 10, left: 30, right: 10, bottom: 30},
_data,
_x = d3.scale.linear(),
_y = d3.scale.linear();
that.render = function () {
var svg = _parent
.append("svg")
.attr("height", _height)
.attr("width", _width);
var body = svg.append("g")
.attr("class", 'body')
.attr("transform", "translate(" + _margins.left + ","
+ _margins.top + ")")
if (_data) {
_x.range([0, quadrantWidth()]);
_y.range([quadrantHeight(), 0]);
body.selectAll('rect.bar')
.data(_data).enter()
.append('rect')
.attr("class", 'bar')
.attr("width", function () {
return quadrantWidth() / _data.length -
BAR_PADDING;
})
.attr("x", function (d) {return _x(d.x); })
.attr("y", function (d) {return _y(d.y); })
.attr("height", function (d) {
return _height - _margins.bottom - _y(d.y);
});
}
};
4. 交互式分析库介绍
在实际开发中,有时需要快速构建交互式分析或概念验证,而不是花费数天或数周时间。接下来介绍两个JavaScript库,它们可以帮助我们在几分钟内实现快速的浏览器内交互式多维数据分析。
4.1 crossfilter.js库
crossfilter.js
是由D3的作者Mike Bostock创建的库,最初用于为Square Register提供分析功能。它是一个用于在浏览器中探索大型多元数据集的JavaScript库,支持极快(<30ms)的协调视图交互,即使数据集包含数百万条记录。
数据维度可以看作是一种数据分组或分类方式,每个维度数据元素是一个分类变量。例如,对于一个描述酒吧支付交易的JSON数据集:
[
{"date": "2011-11-14T01:17:54Z", "quantity": 2, "total": 190,
"tip": 100, "type": "tab"},
{"date": "2011-11-14T02:20:19Z", "quantity": 2, "total": 190,
"tip": 100, "type": "tab"},
{"date": "2011-11-14T02:28:54Z", "quantity": 1, "total": 300,
"tip": 200, "type": "visa"},
...
]
这个数据集可以有多个维度,如
date
、
type
和
quantity
。这些维度允许我们从不同角度查看数据,并提出一些有趣的问题,如:
- 用账单支付的客户是否更有可能购买更多数量的商品?
- 客户是否更有可能在周五晚上购买更多数量的商品?
- 使用账单支付与现金支付相比,客户是否更有可能给小费?
4.2 创建维度和分组
以下是使用
crossfilter.js
创建维度和分组的代码:
var timeFormat = d3.time.format.iso;
var data = crossfilter(json); // <-A
var hours = data.dimension(function(d){
return d3.time.hour(timeFormat.parse(d.date)); // <-B
});
var totalByHour = hours.group().reduceSum(function(d){
return d.total;
});
var types = data.dimension(function(d){return d.type;});
var transactionByType = types.group().reduceCount();
var quantities = data.dimension(function(d){return d.quantity;});
var salesByQuantity = quantities.group().reduceCount();
创建维度和分组的步骤如下:
1. 通过调用
crossfilter
函数将JSON数据集传入
crossfilter
(代码中的A行)。
2. 调用
dimension
函数并传入一个访问器函数来创建维度。
3. 使用
dimension.group
方法进行分类或分组。
以下是使用的函数说明:
| 函数名 | 描述 |
| ---- | ---- |
|
crossfilter
| 如果指定了记录,则创建一个新的
crossfilter
,记录可以是任何对象或基本类型的数组。 |
|
dimension
| 使用给定的值访问器函数创建一个新的维度,函数必须返回自然排序的值。 |
|
dimension.group
| 为给定的维度创建一个新的分组。 |
|
group.all
| 按键的升序自然顺序返回所有组。 |
|
group.reduceCount
| 一个快捷函数,用于计算记录数,返回该组。 |
|
group.reduceSum
| 一个快捷函数,使用指定的值访问器函数对记录求和。 |
5. 维度图表 - dc.js
dc.js
是为了可视化
crossfilter
的维度和分组而创建的JavaScript库,它基于D3构建,API与D3类似。
5.1 创建图表
在一个示例中,我们将创建三个图表:
- 一个折线图,用于可视化时间序列上的交易总金额。
- 一个饼图,用于可视化按支付类型划分的交易数量。
- 一个柱状图,用于显示按购买数量划分的销售数量。
以下是创建图表的代码:
<div id="area-chart"></div>
<div id="donut-chart"></div>
<div id="bar-chart"></div>
dc.lineChart("#area-chart")
.width(500)
.height(250)
.dimension(hours)
.group(totalByHour)
.x(d3.time.scale().domain([
timeFormat.parse("2011-11-14T01:17:54Z"),
timeFormat.parse("2011-11-14T18:09:52Z")
]))
.elasticY(true)
.xUnits(d3.time.hours)
.renderArea(true)
.xAxis().ticks(5);
dc.pieChart("#donut-chart")
.width(250)
.height(250)
.radius(125)
.innerRadius(50)
.dimension(types)
.group(transactionByType);
dc.barChart("#bar-chart")
.width(500)
.height(250)
.dimension(quantities)
.group(salesByQuantity)
.x(d3.scale.linear().domain([0, 7]))
.y(d3.scale.linear().domain([0, 12]))
.centerBar(true);
dc.renderAll();
5.2 图表创建步骤
图表通常按以下步骤创建:
1. 调用图表创建函数之一并传入一个D3选择器作为其锚点元素,例如:
dc.lineChart("#area-chart")
- 设置每个图表的宽度、高度、维度和分组,对于在笛卡尔平面上渲染的坐标图表,还需要设置x和y比例:
chart.width(500)
.height(250)
.dimension(hours)
.group(totalByHour)
chart.x(d3.time.scale().domain([
timeFormat.parse("2011-11-14T01:17:54Z"),
timeFormat.parse("2011-11-14T18:09:52Z")
])).elasticY(true)
5.3 支持的图表类型
在撰写本文时,
dc.js
支持以下图表类型:
- 柱状图(可堆叠)
- 折线图(可堆叠)
- 面积图(可堆叠)
- 饼图
- 气泡图
- 复合图
- 等值区域图
- 气泡覆盖图
此外,还有一些其他基于D3的可重用图表库,如NVD3和Rickshaw,它们虽然不是为了与
crossfilter
原生配合使用,但在处理一般可视化挑战时往往更丰富和灵活。
综上所述,通过测试驱动开发可以确保数据可视化的准确性和稳定性,而
crossfilter.js
和
dc.js
库则可以帮助我们快速构建交互式数据分析。希望这些内容能对你在数据可视化和分析方面的工作有所帮助。
数据可视化测试驱动开发与交互式分析库介绍
6. 测试驱动开发的优势与流程总结
测试驱动开发(TDD)在数据可视化中有着显著的优势。它能够确保每一步的实现都符合预期,避免在开发后期出现难以调试的问题,提高代码的可维护性和稳定性。以下是测试驱动开发的基本流程:
graph LR
A[编写测试用例] --> B[运行测试,测试失败]
B --> C[编写实现代码]
C --> D[运行测试,测试通过]
D --> E{是否还有功能待实现}
E -- 是 --> A
E -- 否 --> F[完成开发]
在前面的柱状图开发过程中,我们严格遵循了这个流程。首先编写了关于数据设置和获取的测试用例,运行时测试失败,然后编写了
BarChart
对象和
data
方法,再次运行测试,测试通过。接着针对SVG渲染和条形渲染分别编写测试用例,重复上述流程,逐步完善柱状图的功能。
7. crossfilter.js 与 dc.js 的协同工作
crossfilter.js
和
dc.js
可以很好地协同工作,实现快速的交互式多维数据分析可视化。以下是它们协同工作的步骤:
1.
数据准备
:使用
crossfilter.js
加载和处理数据,创建维度和分组。例如:
var timeFormat = d3.time.format.iso;
var data = crossfilter(json);
var hours = data.dimension(function(d){
return d3.time.hour(timeFormat.parse(d.date));
});
var totalByHour = hours.group().reduceSum(function(d){
return d.total;
});
var types = data.dimension(function(d){return d.type;});
var transactionByType = types.group().reduceCount();
var quantities = data.dimension(function(d){return d.quantity;});
var salesByQuantity = quantities.group().reduceCount();
-
图表创建
:使用
dc.js基于crossfilter.js创建的维度和分组来创建图表。例如:
dc.lineChart("#area-chart")
.width(500)
.height(250)
.dimension(hours)
.group(totalByHour)
.x(d3.time.scale().domain([
timeFormat.parse("2011-11-14T01:17:54Z"),
timeFormat.parse("2011-11-14T18:09:52Z")
]))
.elasticY(true)
.xUnits(d3.time.hours)
.renderArea(true)
.xAxis().ticks(5);
dc.pieChart("#donut-chart")
.width(250)
.height(250)
.radius(125)
.innerRadius(50)
.dimension(types)
.group(transactionByType);
dc.barChart("#bar-chart")
.width(500)
.height(250)
.dimension(quantities)
.group(salesByQuantity)
.x(d3.scale.linear().domain([0, 7]))
.y(d3.scale.linear().domain([0, 12]))
.centerBar(true);
dc.renderAll();
-
交互实现
:用户与
dc.js创建的图表进行交互时,crossfilter.js会相应地过滤数据,所有图表都会实时更新。例如,当用户点击或拖动折线图时,其他饼图和柱状图也会根据过滤后的数据进行显示。
8. 实际应用场景分析
crossfilter.js
和
dc.js
在很多实际场景中都有广泛的应用,以下是一些常见的场景:
| 应用场景 | 描述 |
| ---- | ---- |
| 销售数据分析 | 可以对销售数据进行多维度分析,如按时间、产品类型、销售地区等维度进行分组和可视化,帮助企业了解销售趋势和市场需求。 |
| 金融数据分析 | 对金融交易数据进行分析,如按交易时间、交易类型、客户类型等维度进行过滤和可视化,帮助金融机构监控风险和发现潜在机会。 |
| 医疗数据分析 | 对医疗数据进行分析,如按疾病类型、治疗时间、患者年龄等维度进行分组和可视化,帮助医疗机构提高医疗质量和效率。 |
9. 总结与展望
通过本文的介绍,我们了解了测试驱动开发在数据可视化中的应用,以及
crossfilter.js
和
dc.js
这两个强大的JavaScript库在快速构建交互式多维数据分析可视化中的作用。测试驱动开发能够保证代码的质量和稳定性,而
crossfilter.js
和
dc.js
的结合则可以让我们在短时间内实现复杂的数据分析和可视化功能。
在未来的发展中,随着数据量的不断增加和数据分析需求的不断提高,这些技术和工具将会发挥更加重要的作用。同时,我们也可以期待更多的优秀开源库和工具的出现,为数据可视化和分析带来更多的可能性。
希望本文能够帮助你更好地理解和应用这些技术,在实际项目中取得更好的效果。如果你在使用过程中遇到任何问题,欢迎在评论区留言讨论。
超级会员免费看
1339

被折叠的 条评论
为什么被折叠?



