(笔记)数据可视化实战:使用D3设计交互式图表

一、准备

章节文件:http://t.cn/zTI9BXG

整个D3项目:github

D3官方网站:d3.js.org

中文API:introduce

mbostock:bl.ocks.org/mbostock

blog.visual.ly/creating-animations-and-transitions-with-d3-js/

D3底层思想;血统的来源:http://vis.stanford.edu/ files/2011-D3-InfoVis.pdf

D3gallery:https://www.data-to-viz.com/

其他:

  1. 其他的探索性工具:http://www.tableausoftware.com、http://ggplot2.org/
  2. d3.geo.tile插件:随意放大缩小位图时不失真;http://t.cn/zTINvoy

1.1 D3

  1. D3是解释型的工具;
  2. Tableau/ggplot2是探索型的工具;(详见文章2.4节)

1.2 HTML/CSS

  1. 选择器
  2. 类选择器
  3. 后代选择器:空格
  4. 类选择器:点
  5. ID选择器:#

1.2.1 继承、层叠和特指度

继承:在没有为某个元素指定样式的情况下,这个元素的很多样式都是从它的祖先元素继承来的。

层叠:在特指度相同的情况下,后定义的规则会覆盖先定义的规则。

1.3 JavaScript

1.3.1 JSON/GeoJSON

JSON:Java Script Object Notion(JS对象表示法)

GeoJSON:用来专门保存地理数据,也是JSON对象

1.4 SVG

    SVG里有一些属性:fill(填充颜色)、stroke(边框颜色)、stroke-width(边框宽度);可以通过CSS对给SVG应用样式;(P53)属性值是SVG里有的则必须使用SVG特有的,除此之外,可以使用CSS内的,查看SVG所有的属性:https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute

1.4.1 分层与绘制顺序

    SVG里没有“层”的概念,也不支持CSS的z-index属性;出现的顺序决定它们的深度次序;

1.4.2 显示问题

    首先加载Modernizr,执行检测,然后只在测试成功的情况下才加载其他代码;至于检测失败后显示什么,可以使用CSS或者其他JavaScript脚本,也可以显示一副静态图表;

二、D3

2.1 数据

    在连缀方法时,次序很重要。每个方法的输出必须与下一个方法期待的输入匹配;

2.1.1 绑定数据

    必须具备两个条件:数据+选中的DOM元素;

    使用selection.data()方法绑定数据;

    CSV(Comma-Separated Value);

    使用全局变量,防止因d3.csv()的异步所导致的数据加载错误;

var dataset;
d3.csv("food.csv", function(error, data) {
	if(error)
		console.log(error);
	else
		console.log(data);
	dataset = data;
	generateVis();
	hideLoadingMsg();
});

    d3.tsv()的使用方法也相同;

    选择不存在的元素绑定数据:

var dataset = [5, 10, 15, 20, 25]
d3.select("body").selectAll("p")
	.data(dataset)
	.enter()
	.append("p")
	.text("New Paragraph!");

    在enter()中,如果数据值比对应的DOM元素多,就创建一个新的占位元素。然后把这个占位元素的引用交给链的下一个方法;D3绑定的数据没有出现在DOM中,而是作为该元素的__data__属性保存于内存中。

    data()方法也会返回一个元素集(selection)。

2.2 函数

    匿名函数:function(d){return d;}

    命名函数:var doSomething = function(){};

三、基于数据绘图

3.1 DIV

    柱形图:矩形沿垂直方向度量的图形

    条形图:矩形沿水平方向度量的图形

3.1.1 样式

1).attr():设置取得的元素的HTML属性;

class

id

src

width

alt

快速添加或删除元素的类:.classed(“bar”, true);.classed(“bar”, false)

2).style():设置取得的元素的CSS属性;

div.bar{
   display: inline-block;
   width: 20px;
   height: 75px;
   margin-right: 2px;
   background-color: teal;
}
var dataset = [];
for(var i = 0; i < 25; i++){
	var number = Math.floor(Math.random()*30);
	dataset.push(number);
}
d3.select("body").selectAll("p")
	.data(dataset)
	.enter()
	.append("div")
	.attr("class", "bar")
	.style("height", function(d){
	var barHeight = d*5;
	return barHeight+"px";
});

3.2 SVG

3.2.1 柱形图

var w = 500;
var h = 100;
var barPadding = 1;

var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
				11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];

//Create SVG element
var svg = d3.select("body")
			.append("svg")
			.attr("width", w)
			.attr("height", h);

svg.selectAll("rect")
   .data(dataset)
   .enter()
   .append("rect")
   .attr("x", function(d, i) {
   		return i * (w / dataset.length);
   })
   .attr("y", function(d){
   	return h-d*4;
   })
   .attr("width", w / dataset.length - barPadding)
   .attr("height", function(d){
   	return d*4;
   });
var text = svg.selectAll("text")
	 .data(dataset)
	 .enter()
	 .append("text")
	 .text(function(d) {
   		return d;
   })
   .attr("x", function(d, i) {
   		return (i*(w / dataset.length)) + (w/dataset.length-barPadding)/2;
   })
   .attr("y", function(d) {
   		return h - (d * 4) + 15;
   })
   .attr("font-family", "sans-serif")
   .attr("font-size", "11px")
   .attr("fill", "white")
   .attr("text-anchor", "middle");

3.2.2 散点图

var w = 500;
var h = 100;
var dataset = [
		[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
		 [410, 12], [475, 44], [25, 67], [85, 21], [220, 88]
		];
//Create SVG element
var svg = d3.select("body")
		.append("svg")
		.attr("width", w)
		.attr("height", h);
svg.selectAll("circle")
   .data(dataset)
   .enter()
   .append("circle")
   .attr("cx", function(d) {
		return d[0];
   })
   .attr("cy", function(d) {
		return d[1];
   })
   .attr("r", function(d) {
		return Math.sqrt(h - d[1]);
   });

一般来说,在通过圆形表现数量时,通用的做法都是将数据点编码为面积,而不是半径;映射为半径更简单,但结果却会导致数据表现不准确;(why? 使用半径之后最后的比是a:b2/使用面积的比是a:b)

四、比例尺

比例尺是一组把输入域映射为输出范围的函数;比例尺代表着一种数学关系;

4.1 线性比例尺

var xScale = d3.scaleLinear()
	       .domain([0, d3.max(dataset, function(d) { return d[0];})])
               .range([0, w]);

外边距约定:https://bl.ocks.org/mbostock/3019563

其他方法:clamp()

默认情况下,线性比例尺可以返回指定范围之外的值。例如,假如给定的值位于输入值域之外,那么比例尺也会返回一个位于输出范围之外的值。不过在比例尺上调用clamp(true)后,就可以强制所有输出值都位于指定的范围内。

4.2 其他比例尺

P(117)

五、数轴

5.1 数轴函数

数轴函数只适用于SVG图形,因为它们生成的都是SVG元素。

定义:

var xAxis = d3.axisBottom()
	      .scale(xScale);

调用:

svg.append("g")
   .attr("class", "axis")
   .call(xAxis);

在SVG标签内,g元素就是一个分组(group)元素;然后在给新创建的g元素指定一个axis类,可以给它添加CSS样式;

.axis path,
.axis line{
	fill:none;
	stroke:black;
	shape-rendering: crispedges;
}
.axis text{
	font-family: sans-serif;
	font-size: 11px;
}

5.2 平移

    attr(“transform”, “translate(0, ”+ (h-padding) +“)”)

    x轴不变,y轴向下平移h-padding个

    attr(“transform”, “translate(”+ padding +“,0)”)

    y轴不变,x轴整体往右平移padding格

5.3 刻度优化

    ticks(5)

    D3只将ticks()的值作为一个建议,如果它发现有更清晰更方便理解的值,它会采用更加方便的值;

六、更新、过渡和动画

6.1 序数比例尺

    序数:有固定顺序的一些类别;设置序数比例尺的值域,通常要指定一个包含类别名称的数组;[“freshman”, “sophomore”, “junior”, “senior”]

    .domain(d3.range(dataset.length))

    .rangeBands([0, w]);计算0-w可以均分为几档,然后把比例尺的范围设定为这些档

    .rangeBands([0, w], 0.2);指定档间距,20%

    .rangeRoundBands([0, w], 0.05),相比于以上,输出的值会舍入为最接近的整数;

6.2 过渡

6.2.1 动画

d3.select("p")
.on("click", function() {
	//New values for dataset
	dataset = [ 11, 12, 15, 20, 18, 17, 16, 18, 23, 25,
				5, 10, 13, 19, 21, 25, 22, 18, 15, 13 ];
	//Update all rects
	svg.selectAll("rect")
	   .data(dataset)
	   .attr("y", function(d) {
			return h - yScale(d);
	   })
	   .attr("height", function(d) {
			return yScale(d);
	   });
});
  1. 动画过渡:.transition()
  2. 延迟时间:.delay(1000)
  3. 持续时间:.duration(1000)
  4. 过渡效果:.easy(“linear”);circle/elastic/bounce

需要在过渡开始和结束之后执行一些操作。为此可以使用each()方法,它能对选中的每个元素执行任意代码:

svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.on("start", function() {
   d3.select(this)
	.attr("fill", "magenta")
	.attr("r", 3);
})
.attr("cx", function(d) {
	return xScale(d[0]);
})
.attr("cy", function(d) {
	return yScale(d[1]);
})
.on("end", function() {
   d3.select(this)
	.attr("fill", "black")
	.attr("r", 2);
});

需要注意的两点:

  1. 只能在each(“start”, …)里面执行立即变换,而不能再添加任何过渡效果;因为在D3中过渡效果是会覆盖的,这样第一个过渡就会被取消;
  2. 但是each(“end”, …)里面支持过渡,因为此时主过渡已经结束,不会产生任何副作用;

6.2.2 剪切路径

剪切路径就是一个SVG元素,可以包含可见的元素,并与这个可见元素一起构成可以应用到其他元素的剪切路径或蒙版。在把蒙版应用到某个元素时,只有落在该蒙版图形内部的像素才会显示;

//Define clipping path
svg.append("clipPath")
	.attr("id", "chart-area")
	.append("rect")
	.attr("x", padding)
	.attr("y", padding)
	.attr("width", w - padding * 3)
	.attr("height", h - padding * 2);

添加新的g元素对clipPath的引用:

svg.append("g")
   .attr("id", "circles")
   .attr("clip-path", "url(#chart-area)")
   .selectAll("circle")
   .data(dataset)
   .enter()
   .append("circle")
   .attr("cx", function(d) {
		return xScale(d[0]);
   })
   .attr("cy", function(d) {
		return yScale(d[1]);
   })
   .attr("r", 2);

6.3 更新

6.3.1 添加元素

  1. 选择数据(绑定数据)
  2. 加入新数据
  3. 与原始数据合并(主语是bars.enter.append之后)
  4. 对所有数据进行选择过渡动画效果
//1.
var bars = svg.selectAll("rect")	
	.data(dataset);
//2.
var bars = svg.selectAll("rect")	
	.data(dataset);
//3.
.merge(bars)
//4.
svg.selectAll("text")

样例:关于添加 eg_25_adding_values.html

6.3.2 删除元素

var texts = svg.selectAll("text")	
	.data(dataset);		
texts.exit()
	.transition()
	.duration(300)
	.attr("x", w)
	.remove();

6.4 通过键链接数据

维护数据与条形一致性的关键:https://bost.ocks.org/mike/constancy/

1).准备数据

var dataset = [ {key:0, value:5}, {key:1, value:10}, {key:2, value:13}, …]

2).现在数据都封装到了对象里,就不能再引用d了。而是使用d.key和d.value来取值;

3).定义键函数:

var key = function(d) { return d.key;}

4).绑定数据

.data(dataset, key)

6.4.1 删除元素

var labels = svg.selectAll("text")
	.data(dataset, key);

//Exit…
labels.exit()
	.transition()
	.duration(500)
	.attr("x", -xScale.bandwidth())
	.remove();

七、交互式图表

7.1 事件监听器

    D3支持所有JavaScript事件;

    on()事件

    在svg.selectAll下:

.on("mouseover", function() {
	d3.select(this)
		.attr("fill", "orange");
.on("mouseout", function(d) {
   	d3.select(this)
		.transition()
	.duration(250)
	.attr("fill", "rgb(0, 0, " + (d * 10) + ")");
});

7.2 指针事件与重叠元素

    两个元素叠加在一起,只有上面的元素才会触发mouseover事件;

    在SVG中,后加入的DOM元素在视觉层次上会被渲染在先加入元素的前面;指定的元素可以不触发任何时间,只需要设置:

svg text{
    pointer-events: none;
}

    或者,将不同的元素分别放置在各自的组里,不用再担心指针事件,只要给整个组绑定监听器。这样无论是单击rect还是单击text都会触发相同的代码,因为它们在一个组里;

7.2.1 排序

var sortBars = function() {

	svg.selectAll("rect")
	   .sort(function(a, b) {
		   return d3.ascending(a, b);
		})
	   .transition()
	   .duration(1000)
	   .attr("x", function(d, i) {
			return xScale(i);
	   });

悬停效果尽量交给CSS,否则会出现过渡中断的情况;

7.3 提示条

    1.浏览器自带提示条:

.append("title")
.text(function(d) {
	return "This value is " + d;
});

    2.将text与mouseover和mouseout结合形成动画效果

    3.使用html中的div和css联合:(P187)

7.4 触碰

    d3.mouse

    d3.touches()API文档:https://github.com/d3/d3/wiki#d3_touches

八、力导向布局

初始化:

var force = d3.forceSimulation(dataset.nodes)
	.force("charge", d3.forceManyBody())
	.force("link", d3.forceLink(dataset.edges))
	.force("center", d3.forceCenter().x(w/2).y(h/2));

加点:

//Create nodes as circles
var nodes = svg.selectAll("circle")
	.data(dataset.nodes)
	.enter()
	.append("circle")
	.attr("r", 10)
	.style("fill", function(d, i) {
		return colors(i);
	});

加边:

//Create edges as lines
var edges = svg.selectAll("line")
	.data(dataset.edges)
	.enter()
	.append("line")
	.style("stroke", "#ccc")
	.style("stroke-width", 1);

打点(tick):

force.on("tick", function() {

	edges.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; });

	nodes.attr("cx", function(d) { return d.x; })
		 .attr("cy", function(d) { return d.y; });

});

拖拽:

//加点的时候:
	.call(d3.drag()  //Define what to do on drag events
		.on("start", dragStarted)
		.on("drag", dragging)
		.on("end", dragEnded));

//Define drag event functions
function dragStarted(d) {
	if (!d3.event.active) force.alphaTarget(0.3).restart();
	d.fx = d.x;
	d.fy = d.y;
}

function dragging(d) {
	d.fx = d3.event.x;
	d.fy = d3.event.y;
}

function dragEnded(d) {
	if (!d3.event.active) force.alphaTarget(0);
	d.fx = null;
	d.fy = null;
}

九、地图

9.1 读取JSON数据

JSON数据无法通过本地访问!!!换成Web服务器之后又说什么跨域访问,我真的太菜了什么都不懂。

通过Python搭建简单Web服务器 

最后:这个人真的是太赞了!!!

getJSON无法读取本地json数据的问题

我用的是HBilder,所以设置了一个内置web服务器就可以访问了

9.1.1 加载JSON数据

d3.json("us-states.json", function(json) {
	//Bind data and create one path per GeoJSON feature
	svg.selectAll("path")
	   .data(json.features)
	   .enter()
	   .append("path")
	   .attr("d", path)
	   .style("fill", "steelblue");

});

 

9.1.2 将GeoJSON坐标换成SVG路径代码

//Define map projection
//定义投射地点(w/2,h/2)使其投射在正中间 且关于大小还可以设置比例尺
var projection = d3.geo.albersUsa()
			.translate([w/2, h/2])
			.scale([500]);

//路径生成器
var path = d3.geo.path()
		.projection(projection);

9.1.3 将有用信息添加到地图上

  • 需要知道每个城市的经纬度坐标信息:地理编码(geocoding)服务能够根据地名查找地图,返回较为精确的经纬度坐标;
  • 获得GeoJSON数据:1.NaturalEarth 2.美国人口普查局(Shapefile)
  • 将高解析度的数据文件转化为低解析度的数据文件:Matt Bloch MapShapper

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
 你手头有一些数据,想做成漂亮的图表放到网站上?好主意,通过浏览器来跨平台实现数据可视化是正确的选择。什么,你还想让图表能够响应用户操作?没问题,交互式图表比静态图片更能吸引人去探究本源。好啦,要生成通过浏览器展示的动态图表,目前热门的Web数据可视化库——D3。   《图灵程序设计丛书·数据可视化实战使用D3设计交互式图表》这本书很有意思,而且对读者要求不高。不需要知道什么是数据可视化,也不用有太多Web开发背景就能看懂它。不信?翻一翻就知道这是一本既好玩又实用的动手指南啦!看完这本书你会怎么样呢?   掌握必要的HTML、CSS、JavaScript和SVG基础知识;   学会基于数据在网页里生成元素和为它们设置样式的技巧;   能够生成条形图、散点图、饼图、堆叠条形图和力导向图;   使用平滑的过渡动画来展示数据的变化;   赋予图表动态交互能力,响应用户从不同角度探索数据的请求;   收集数据和创建自定义的地图;   另外,《图灵程序设计丛书·数据可视化实战使用D3设计交互式图表》100多个代码示例都可以在线浏览! 【电子版来自互联网,仅供预览及学习交流 使用,不可用于商业用途,如有版权问题,请联系删除,支持正版,喜欢的 请购买正版书籍: https://e.jd.com/30336473.html】
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值