# d3api
# Histograms
直方图经常用来将大量的离散的值统计成相邻的少量的不重叠的区间. 直方图经常用来可视化离散的数值的分布情况.
- d3.histogram() 使用默认的设置构建一个新的直方图生成器.
- histogram - 根据给定的数组计算直方图. histogram(data)
- histogram.value - 设置或获取直方图值访问器.
- histogram.domain - 设置或获取直方图的可观测区间.
- histogram.thresholds - 设置直方图阈值生成方式.
- d3.thresholdFreedmanDiaconis - Freedman–Diaconis 阈值生成规则.
- d3.thresholdScott - Scott’s normal reference 阈值生成规则.
- d3.thresholdSturges - Sturges’ 阈值生成规则.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
</title>
</head>
<body>
<div id="test-svg">
</div>
</body>
<script src="https://d3js.org/d3.v5.js">
</script>
<script>
window.onload = function() {
//创建具有贝茨分布规律的随机数,0~1
var datas = d3.range(1000).map(function() {
return d3.randomBates(10)();
});
console.log(datas)
var width = 800,
height = 400,
padding = {
top: 10,
right: 40,
bottom: 40,
left: 40
};
var svg = d3.select("#test-svg")
.append('svg')
.attr('width', width + 'px')
.attr('height', height + 'px');
// x轴
var xScale = d3.scaleLinear()
.range([padding.left, width - padding.right]);
// 将x轴,0~1,轴分成20个刻度 [0,0.05,0.1,0.15 ...,1.00]
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(20);
svg.append('g')
.call(xAxis)
.attr("transform", "translate(0," + (height - padding.bottom) + ")");
// 构造一个直方图布局,返回随机数在每个x轴刻度区域出现的次数
var his = d3.histogram()
.domain(xScale.domain())
.thresholds(xScale.ticks(20))
(datas);
// y轴
var yScale = d3.scaleLinear()
.domain([0, d3.max(his,
function(d) {
return d.length;
})])
.range([height - padding.bottom, padding.top]);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(10);
svg.append('g')
.call(yAxis)
.attr("transform", "translate(" + padding.left + ",0)");
var bar = svg.selectAll(".bar")
.data(his)
.join("g")
.attr("class", "bar")
.attr("transform",
function(d) {
return "translate(" + xScale(d.x0) + "," + yScale(d.length) + ")";
});
// 构造柱
bar.append("rect")
.attr("x", 1)
.attr("width", xScale(his[0].x1) - xScale(his[0].x0) - 1)
.attr("height",
function(d) {
return height - yScale(d.length) - padding.bottom;
});
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", (xScale(his[0].x1) - xScale(his[0].x0)) / 2)
.attr("text-anchor", "middle")
.attr("font-size", "8px")
.attr("fill", "White")
.text(function(d) {
return d.length;
});
}
</script>
</html>
# d3-brush
刷取交互指在一维或二维空间内使用手势,比如点击然后拖拽鼠标拾取一块区域范围的操作
- d3.brush - 创建一个新的二维刷取交互
- d3.brushX - 创建一个新的x-维度的刷取交互
- d3.brushY - 创建一个新的y-维度的刷取交互
- brush - 将刷取操作应用到一个selection上
- brush.move - 移动刷取框选
- brush.extent - 定义可刷取的范围
- brush.filter - 过滤器定义哪些事件不触发刷取操作
- brush.handleSize - 设置刷取把柄的大小
- brush.on - 注册刷取事件句柄
- d3.brushSelection - 获取指定节点的刷取范围
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
</title>
</head>
<body>
<svg id="svg2">
</svg>
</body>
<script src="https://d3js.org/d3.v5.js">
</script>
<script>
const rawData = [
{ day: '2015-01', quantity: 1240 },
{ day: '2015-02', quantity: 1905 },
{ day: '2015-03', quantity: 6232 },
{ day: '2015-04', quantity: 7545 },
{ day: '2015-05', quantity: 543 },
{ day: '2015-06', quantity: 443 },
{ day: '2015-07', quantity: 246 },
{ day: '2015-08', quantity: 5445 },
{ day: '2015-09', quantity: 1154 },
{ day: '2015-10', quantity: 448 },
{ day: '2015-11', quantity: 1545 },
{ day: '2015-12', quantity: 4585 },
{ day: '2016-01', quantity: 1520 },
{ day: '2016-02', quantity: 9015 },
{ day: '2016-03', quantity: 632 },
{ day: '2016-04', quantity: 745 },
{ day: '2016-05', quantity: 343 },
{ day: '2016-06', quantity: 6443 },
{ day: '2016-07', quantity: 546 },
{ day: '2016-08', quantity: 1545 },
{ day: '2016-09', quantity: 1354 },
{ day: '2016-10', quantity: 848 },
{ day: '2016-11', quantity: 2155 },
{ day: '2016-12', quantity: 4585 },
{ day: '2017-01', quantity: 1540 },
{ day: '2017-02', quantity: 905 },
{ day: '2017-03', quantity: 632 },
{ day: '2017-04', quantity: 745 },
{ day: '2017-05', quantity: 3543 },
{ day: '2017-06', quantity: 4443 },
{ day: '2017-07', quantity: 2546 },
{ day: '2017-08', quantity: 545 },
{ day: '2017-09', quantity: 154 },
{ day: '2017-10', quantity: 4848 },
{ day: '2017-11', quantity: 155 },
{ day: '2017-12', quantity: 4585 }
];
const [svgWidth, svgHeight] = [1000, 500];
const margin = { top: 80, right: 40, bottom: 130, left: 40 };
const margin2 = { top: 410, right: 40, bottom: 60, left: 40 };
const width = svgWidth - margin.left - margin.right;
const height = svgHeight - margin.top - margin.bottom;
const height2 = svgHeight - margin2.top - margin2.bottom;
let chart = d3
.select('#svg2')
.attr('width', svgWidth)
.attr('height', svgHeight) // 设置总宽高
const parseDate = d3.timeParse('%Y-%m')
const data = rawData.map(item => {
// 把原始数据转为d3可接受的数据
return { ...item, day: parseDate(item.day) }
})
let xScale = d3
.scaleTime()
.range([0, width])
.domain(
d3.extent(data, function(d) {
return d.day
})
);// 设置mainChart x轴
let x2Scale = d3
.scaleTime()
.range([0, width])
.domain(xScale.domain()); // 设置subChart x轴
let yScale = d3
.scaleLinear()
.rangeRound([height, 0])
.domain([
0,
d3.max(data, function(d) {
return d.quantity
})
]); // 设置mainChart y轴
let y2Scale = d3
.scaleLinear()
.range([height2, 0])
.domain(yScale.domain()); // 设置subChart y轴
// 设mainChart在最外包层在总图上的相对位置
let mainChart = chart
.append('g')
.attr('class', 'main-chart')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
//x轴主轴
let xAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat('%Y年%m月'))
//x轴辅轴
let x2Axis = d3.axisBottom(x2Scale).tickFormat(d3.timeFormat('%Y年%m月'))
//y轴主轴
let yAxis = d3.axisLeft(yScale).ticks(10)
//添加x轴到页面
mainChart
.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
//添加y轴到页面
mainChart
.append('g')
.attr('class', 'axis axis--y')
.call(yAxis)
.append('text')
.attr('y', -16)
.attr('dy', '.71em')
.style('text-anchor', 'middle')
.style('fill', '#000')
.text('销量 (千克)');
// 画背景线
mainChart
.selectAll('.axis--y .tick')
.append('line')
.attr('class', 'bg-line')
.attr('stroke', '#fff1c9')
// .attr('shape-rendering', 'crispEdges')
.attr('x2', width);
mainChart.select('.axis--y .bg-line:last-of-type').remove()
mainChart
.selectAll('.axis--x .tick')
.append('line')
.attr('class', 'bg-line')
.attr('stroke', '#ff0000')
.attr('y2', -height)
//面积图
let mainAreaPath = d3
.area()
.curve(d3.curveMonotoneX)
.x(function(d, i) {
return xScale(d.day)
})
.y0(yScale(0))
.y1(function(d, i) {
return yScale(d.quantity)
});
//动画clipPath
mainChart
.append('defs')
.append('clipPath')
.attr('id', 'clip-main')
.append('rect')
.attr('height', height + 40)
.attr('y', '-10')
.attr('x', '-10')
.attr('width', 0)
.transition()
.duration(2000)
.attr('width', width + 70)
mainChart.append('g')
.append('path')
.attr('class', 'area')
.datum(data)
.attr('d', mainAreaPath)
.attr('stroke', 'none')
.attr('stroke-width', 1)
.attr('fill', 'steelblue')
.attr('clip-path', 'url(#clip-main)')
//添加关键点
let circle = mainChart
.append('g')
.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('clip-path', 'url(#clip-main)')
circle
.append('circle')
.attr('cx', function(d, i) {
return xScale(d.day)
})
.attr('cy', function(d, i) {
return yScale(d.quantity)
})
.attr('r', '5')
.attr('fill', 'red')
circle
.append('text')
.attr('x', function(d) {
return xScale(d.day)
})
.attr('dx', '1em')
.attr('y', function(d) {
return yScale(d.quantity)
})
.text(function(d) {
return d.quantity
})
.attr('font-size', '10')
let subChart = chart
.append('g')
// 设subChart的最外包层在总图上的相对位置
.attr('transform', 'translate(' + margin2.left + ',' + margin2.top + ')')
//添加x轴到页面
subChart
.append('g')
.attr('transform', 'translate( 0, ' + height2 + ' )')
.call(x2Axis)
//subArea 面积path
let subAreaPath = d3
.area()
.curve(d3.curveMonotoneX)
.x(function(d) {
return x2Scale(d.day)
})
.y0(height2)
.y1(function(d) {
return y2Scale(d.quantity)
})
//接入数据画面积图
subChart
.append('g')
.append('path')
.datum(data)
.attr('d', subAreaPath)
.attr('stroke', 'none')
.attr('stroke-width', 2)
.attr('fill', 'blue')
/***画刷缩放部分***/
//画刷函数
function brushed(a, b, c) {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === 'zoom') return
//通过事件对象获取画刷目前的长度以及位置,类似brush.move(selection, [50, 100])
let s = d3.event.selection || x2Scale.range()
//通过x2Scale.invert转化为新的xScale的domain
xScale.domain(s.map(x2Scale.invert, x2Scale))
//设置完新的xScale 重绘面积图以及x坐标轴
mainChart.select('.area').attr('d', mainAreaPath)
mainChart.select('.axis--x').call(xAxis)
//选取面积图上的所有点与文字,重新赋值位置信息
circle
.select('circle')
.attr('cx', function(d, i) {
return xScale(d.day)
})
circle
.select('text')
.attr('x', function(d) {
return xScale(d.day)
})
}
//缩放函数
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === 'brush') return
//t为随着滚轮缩放的动态比例值包含缩放信息k以及transform信息x和y
let t = d3.event.transform
//通过t上的rescaleX方法重新定义动态的domain且绑定至xScale
xScale.domain(t.rescaleX(x2Scale).domain())
//重新绘制面积图与坐标轴
mainChart.select('.area').attr('d', mainAreaPath)
mainChart.select('.axis--x').call(xAxis)
//选取所有面积图上的点和文字动态修改位置信息
circle
.select('circle')
.attr('cx', function(d, i) {
return xScale(d.day)
})
circle
.select('text')
.attr('x', function(d) {
return xScale(d.day)
})
//当面积图产生缩放时,让brush跟着变化大小与位置
//s为获取的当前面积图的domain(是一个时间数组)
let s = xScale.domain()
//把当前的domain通过x2Scale转化为range的数字数组(即为brush的位置信息)
let d = s.map(item => {
return x2Scale(item)
})
//通过brush.move方法动态修改brush的大小与位置
brush.move(subChart.select('.brush'), d)
}
//定义画刷
let brush = d3
.brushX()
.extent([ [0, 0], [width, height2] ])
.on('brush end', brushed)
//定义缩放zoom
let zoom = d3
.zoom() // 设置zoom
//设置缩放范围
.scaleExtent([1, Infinity])
//设置transform的范围
.translateExtent([[0, 0], [width, height]])
//设置缩放的视口的大小; 注:此时视口大小与transform范围一样说明无法拖动只可滚轮缩放
.extent([[0, 0], [width, height]])
.on('zoom end', zoomed)
//子图
subChart
.append('g') // 添加画刷
.attr('class', 'brush')
.call(brush)
// .call(brush.move, xScale.range())
//初始笔刷刷取范围
.call(brush.move, [50, 200])
chart
.append('g')
.append('rect') // 添加刷放方块
.attr('class', 'zoom')
.attr('width', width)
.attr('height', height)
.attr('cursor', 'move')
.attr('pointer-events', 'all')
.attr('fill', 'none')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(zoom)
</script>
</html>
# d3-chord
用圆形布局形象化关系或网络流量。
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.group-tick line {
stroke: #000;
}
.ribbons {
fill-opacity: 0.67;
}
</style>
<svg width="500" height="500"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
// 定义数据矩阵
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var svg = d3.select("svg"), // 获取svg元素
width = +svg.attr("width"), // 获取svg元素的宽度
height = +svg.attr("height"), // 获取svg元素的高度
// 计算外半径尺寸,这里取svg画布的宽、高的最小值的一半,减去40,表示两边留有余地;
outerRadius = Math.min(width, height) * 0.5 - 40,
// 计算内半径尺寸
innerRadius = outerRadius - 30;
// 定义数值的格式化函数
var formatValue = d3.formatPrefix(",.0", 1e3);
// 定义一个chord diagram的布局函数chord()由于通过chord()函数将matrix转换后,matrix实际分成了
// 两个部分,groups 和 chords ,其中groups
// 表示弦图上的弧,称为外弦,groups中的各个元素都被计算用添加上了angle、startAngle、endAngle、index、value
// 等字段;chords 称为内弦,表示弦图中节点的连线及其权重。chords 里面分为 source 和 target ,分别标识连线的两端。
var chord = d3.chord()
// 设置弦片段之间的间隔角度,即chord diagram 图中组成外层圆圈的各个弧段之间的角度
.padAngle(0.05)
// 设置数据矩阵matrix 的行内各列的排序顺序为降序排列
.sortSubgroups(d3.descending);
// 定义一个弧线的布局函数arc()
var arc = d3.arc()
// 设置弧线的内半径
.innerRadius(innerRadius)
// 设置弧线的外半径
.outerRadius(outerRadius);
// 定义一个弦布局函数ribbon()
var ribbon = d3.ribbon()
// 设置弦的半径为弧线的内半径
.radius(innerRadius);
// 定义一个颜色函数color(),返回离散的颜色值,即四种颜色
var color = d3.scaleOrdinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
// 定义一个组元素
var g = svg.append("g")
// 将组元素移动到画布的中心处
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
// chord(matrix)函数用来将matrix数组转换为chord diagram 所需的数据格式,
// 通过datum将转换后用于显示弦图的数据绑定到 g元素上;
.datum(chord(matrix));
// 定义一组g元素,用来绑定弦图的 groups数据,即弧线
var group = g.append("g")
.attr("class", "groups")
.selectAll("g")
.data(function(chords) { return chords.groups; })
.enter().append("g");
// group元素是用来放置弦图的“弧”的
group.append("path")
// 设置弧的填充色用color函数来获取
.style("fill", function(d) { return color(d.index); })
// 设置弧的边缘线用比其填充色较深的颜色来画
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
// 绑定arc布局到group的d属性上,用来画弧
.attr("d", arc);
// 定义每段弧上的刻度 元素
var groupTick = group.selectAll(".group-tick")
//为每段弧的刻度元素绑定数据,数据为当前弧上的刻度的角度数组
.data(function(d) { return groupTicks(d, 1e3); })
.enter().append("g")
.attr("class", "group-tick")
// 根据角度以及外半径定位刻度位置(这里的刻度指的是弦图上外围的小短刻度线)
.attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)"; });
// 绘制弦图外围的刻度线
groupTick.append("line")
.attr("x2", 6);
// 定义刻度线上的文字
groupTick
// 不能被5整除的数字不显示
.filter(function(d) { return d.value % 5e3 === 0; })
.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return formatValue(d.value); });
// 给之前定义的g这个元素添加样式并绑定数据用来画弦图的弦。
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
// 弦的填充色是目标点的索引值确定的
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); });
// Returns an array of tick angles and values for a given group and step.
// 该函数用来计算弧上的刻度的角度
function groupTicks(d, step) {
// k表示单位弧度
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(function(value) {
return {value: value, angle: value * k + d.startAngle};
});
}
</script>
# d3-collection
# 嵌套结构Nest
- d3.nest() 创建一个嵌套结构
- nest.key(function) 指定key
- nest.entries(array)指定数组array将被用于构建嵌套结构
let persons = [
{id:100,name:"apple",year:1992,hometown:"北京"},
{id:101,name:"orange",year:1993,hometown:"上海"},
{id:102,name:"water",year:1993,hometown:"北京"},
{id:103,name:"ok",year:1992,hometown:"广东"},
{id:104,name:"change",year:1991,hometown:"北京"},
]
let nest=d3.nest()
.key(function(d){return d.year})
.key(function(d){return d.hometown})
.entries(persons)
console.log(JSON.stringify(nest))
[
{
"key": "1992",
"values": [
{
"key": "北京",
"values": [
{
"id": 100,
"name": "apple",
"year": 1992,
"hometown": "北京"
}
]
},
{
"key": "广东",
"values": [
{
"id": 103,
"name": "ok",
"year": 1992,
"hometown": "广东"
}
]
}
]
},
{
"key": "1993",
"values": [
{
"key": "上海",
"values": [
{
"id": 101,
"name": "orange",
"year": 1993,
"hometown": "上海"
}
]
},
{
"key": "北京",
"values": [
{
"id": 102,
"name": "water",
"year": 1993,
"hometown": "北京"
}
]
}
]
},
{
"key": "1991",
"values": [
{
"key": "北京",
"values": [
{
"id": 104,
"name": "change",
"year": 1991,
"hometown": "北京"
}
]
}
]
}
]
- nest.sortKeys(comparator) 对key值进行排序
.key(function(d){return d.year}).sortKeys(d3.descending)
- nest.sortValues(comparator) 按照值对嵌套结构进行排序
let persons = [
{id:100,name:"apple",year:1992,hometown:"北京"},
{id:101,name:"orange",year:1993,hometown:"上海"},
{id:102,name:"water",year:1993,hometown:"北京"},
{id:103,name:"ok",year:1992,hometown:"广东"},
{id:104,name:"change",year:1991,hometown:"北京"},
]
let nest=d3.nest()
.key(function(d){return d.hometown})
.sortValues(function(a,b){
return d3.ascending(a.year,b.year)
})
.entries(persons)
console.log(JSON.stringify(nest))
[
{
"key": "北京",
"values": [
{
"id": 104,
"name": "change",
"year": 1991,
"hometown": "北京"
},
{
"id": 100,
"name": "apple",
"year": 1992,
"hometown": "北京"
},
{
"id": 102,
"name": "water",
"year": 1993,
"hometown": "北京"
}
]
},
{
"key": "上海",
"values": [
{
"id": 101,
"name": "orange",
"year": 1993,
"hometown": "上海"
}
]
},
{
"key": "广东",
"values": [
{
"id": 103,
"name": "ok",
"year": 1992,
"hometown": "广东"
}
]
}
]
- nest,rollup(function) 对每一组叶子节点调用指定的函数function,该函数含有一个参数values,是当前叶子节点的数组.
let persons = [
{id:100,name:"apple",year:1992,hometown:"北京"},
{id:101,name:"orange",year:1993,hometown:"上海"},
{id:102,name:"water",year:1993,hometown:"北京"},
{id:103,name:"ok",year:1992,hometown:"广东"},
{id:104,name:"change",year:1991,hometown:"北京"},
]
let nest=d3.nest()
.key(function(d){return d.hometown})
.sortValues(function(a,b){
return d3.ascending(a.year,b.year)
}).rollup(function(val){return val.length})
.entries(persons)
console.log(JSON.stringify(nest))
//[{"key":"北京","value":3},{"key":"上海","value":1},{"key":"广东","value":1}]