1. 前言
涉及到的比较重要的点
- Scale(比例尺)
- Update、Enter、Exit
2. 比例尺
重点在于比例尺的应用
线性比例尺
domain是定义域,range是值域
可以将定义域放大或者缩写
什么意思呢? 假设我们的数据长度是[0, 1000],但是1000这个高度太高了,放不下
我们就可以将其缩小5倍
var y = d3.scaleLinear()
.domain([0, 1000])
.range([0, 200])
y(5) // 1
y(500) // 100
y(1000) // 200
序数比例尺
有时候定义域和值域不一定是连续的,所以可以使用这个
var index = [0, 1, 2, 3, 4];
var color = ["red", "blue", "green", "yellow", "black"];
var ordinal = d3.scaleOrdinal()
.domain(index)
.range(color);
ordinal(0); //返回 red
ordinal(2); //返回 green
ordinal(4); //返回 black
3. Update、Enter、Exit
可以看到代码里会反复出现这一段
svg.selectAll("rect") //选择svg内所有的矩形
.data(dataset) //绑定数组
.enter() //指定选择集的enter部分
.append("rect") //添加足够数量的矩形元素
这段代码出现了一个enter,为什么呢?
这就要讲到d3的机制了
假设在body中有3个p元素,有一个数组[3, 6, 9]
,现在我们把3个dom元素和我们的js数组绑定到一起
但是有两个个问题
- 如果数组有五个元素(此时数组长度 > DOM元素数量),那应该怎么映射关系?
- 如果有5个p元素(此时数组长度 > DOM元素数量),那应该怎么映射关系?
于是就提出了三个概念update, enter, exit
- dom元素与js数据对应的部分叫做update部分
- 多余的js数据的部分叫做enter部分(此时d3会建立空dom元素与之对应上)
- 多余的dom元素的部分叫做exit部分(此时d3会建立空js数据与之对应上)
如下图所示
2. 代码
const width = 400
const height = 400
const dataset = [50, 43, 120, 87, 99, 167, 142]
const color = d3.schemeCategory10
const padding = {
top: 20,
bottom: 20,
left: 30,
right: 30
}
const xAxisWidth = width - padding.left - padding.right
const yAxisWidth = height - padding.top - padding.bottom
const rectStep = 35
const rectWidth = 30
// x轴比例尺
const xScale = d3
.scaleBand()
.domain(d3.range(dataset.length))
.range([0, xAxisWidth])
.padding(0.1)
// y轴比例尺
const yScale = d3
.scaleLinear()
.domain([0, d3.max(dataset)])
.rangeRound([yAxisWidth, 0]) // 注意这里写反是因为Y轴的0需要从下面开始
// x轴
const xAxis = d3.axisBottom(xScale)
// y轴
const yAxis = d3.axisLeft(yScale)
// 插入svg
const svg = d3
.select('#svg')
.append('svg')
.attr('width', width)
.attr('height', height)
const genRect = obj => {
obj.attr('width', xScale.bandwidth())
.attr('height', d => yScale(0) - yScale(d))
.attr('x', (d, i) => {
return padding.left + xScale(i)
})
.attr('y', (d, i) => {
return height - padding.bottom - (yScale(0) - yScale(d))
})
.attr('fill', (d, i) => {
return color[i]
})
}
const genText = obj => {
obj.attr('fill', '#fff')
.attr('class', 'number')
.attr('font-size', '14px')
.attr('x', (d, i) => {
return padding.left + xScale(i)
})
.attr('y', (d, i) => {
return height - padding.bottom - (yScale(0) - yScale(d))
})
.attr('dy', 20)
.attr('dx', xScale.bandwidth() / 2)
.attr('text-anchor', 'middle')
.text(d => d)
}
const genAxis = (xAxis, yAxis) => {
const gX = svg
.append('g')
.attr(
'transform',
`translate(${padding.left}, ${height - padding.bottom})`
)
gX.call(xAxis)
const gY = svg
.append('g')
.attr('transform', `translate(${padding.left}, ${padding.top})`)
gY.call(yAxis)
}
const init = dataset => {
genRect(
svg
.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
)
genText(
svg
.selectAll('text')
.data(dataset)
.enter()
.append('text')
)
}
const update = dataset => {
genRect(svg.selectAll('rect').data(dataset))
genText(svg.selectAll('text').data(dataset))
}
const __main = () => {
init(dataset)
genAxis(xAxis, yAxis)
}
__main()
3. 效果图
参考文章:
Learning D3.js
从零开始画图表