我正在参加「掘金·启航计划」
引入D3模块
- 引入整个D3模块。
<!-- D3模块 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
数据
- 自定义数据和格式。
// 创建数据
let data = []
for (var i = 0; i < 300; i++) {
var datum = {}
datum.date = i
datum.price = Math.floor(Math.random() * 600)
data.push(datum)
}
添加画布
// 全局画布变量
const margin = { top: 10, right: 10, bottom: 100, left: 40 }
const margin2 = { top: 430, right: 10, bottom: 20, left: 40 }
const width = 750 - margin.left - margin.right
const height = 500 - margin.top - margin.bottom
const height2 = 500 - margin2.top - margin2.bottom
const svg = d3
.select('.d3Chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
比例尺和配置信息
// 比例尺
const x = d3.scaleBand().range([0, width]).padding(0.1)
const x2 = d3.scaleBand().range([0, width])
const y = d3.scaleLinear().range([height, 0])
const y2 = d3.scaleLinear().range([height2, 0])
- x轴,使用序数比例尺,计算每条数据的刻度。
- y轴,使用线性比例尺,计算值对应的高度。
// 坐标轴
const xAxis = d3.axisBottom(x)
const xAxis2 = d3.axisBottom(x2).tickValues([])
const yAxis = d3.axisLeft().scale(y)
- 创建坐标轴绘制函数。
x.domain(
data.map(function (d) {
return d.date
})
)
y.domain([
0,
d3.max(data, function (d) {
return d.price
})
])
x2.domain(x.domain())
y2.domain(y.domain())
- 为比例尺添加输入域。每一次平移操作输入域都会重新计算。
// 沿x轴创建画笔
const brush = d3
.brushX()
.extent([
[0, 0],
[width, height2]
]) // 设置 画笔范围
.on('brush', brushed)
function brushed(brush) {}
d3.brushX()沿x轴创建新的二维画笔。.extent()设置画笔可刷区域。.on()画笔操作后的监听。brushed()画笔操作的回调函数,重新绘制图表操作。
- 创建二维画笔。
绘制图表和画笔操作
绘制坐标轴
const chartSvg = svg
.append('g')
.attr('class', 'focus')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
- 创建图表绘制组。
chartSvg
.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
chartSvg.append('g').attr('class', 'y axis').call(yAxis)
- 绘制坐标轴。x轴现在是未处理的比例尺。
绘制柱状
enter(data)
/**
* 绑定数据 绘制柱状
*/
function enter(data) {
x.domain(
data.map(function (d) {
return d.date
})
)
y.domain([
0,
d3.max(data, function (d) {
return d.price
})
])
const bars = chartSvg.selectAll('.bar').data(data)
bars
.enter()
.append('rect')
.classed('bar', true)
.attr('fill', 'steelblue')
.attr('height', function (d, i) {
return height - y(d.price)
})
.attr('width', function (d, i) {
return x.bandwidth()
})
.attr('x', function (d) {
return x(d.date)
})
.attr('y', function (d) {
return y(d.price)
})
}
- 每次交互后都需要重新绘制柱状,创建公用函数。
- 更新比例尺数据,以最新数据来绘制柱状。
更新坐标轴
updateScale(data, { selection: [0, 100] })
/**
* 根据画笔数据 修改x轴显示的刻度 并根据最新的比例尺 重新绘制坐标轴
* */
function updateScale(data, brush) {
// 指数比例尺
const tickScale = d3
.scalePow()
.range([data.length / 10, 0])
.domain([data.length, 0])
.exponent(0.2) // 指数为0.2
// 画笔区域宽度
const brushValue = brush.selection[1] - brush.selection[0]
if (brushValue === 0) {
brushValue = width
}
// 得到数据间隔数
const tickValueMultiplier = Math.ceil(Math.abs(tickScale(brushValue)))
// 获取间隔刻度的数据名
const filteredTickValues = data
.filter(function (d, i) {
return i % tickValueMultiplier === 0
})
.map(function (d) {
return d.date
})
chartSvg.select('.x.axis').call(xAxis.tickValues(filteredTickValues))
chartSvg.select('.y.axis').call(yAxis)
}
d3.scalePow()创建指数比例尺。.exponent(0.2)设置指数,输入值进行求平方根。
- 每次交互后都需要重新绘制坐标轴,创建公用函数。
- 先计算出一个合理的数据间隔,根据数据间隔获取对应的刻度值,重新绘制坐标轴。
绘制画笔操作
// 刷子操作区
const context = svg
.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' + margin2.left + ',' + margin2.top + ')')
- 创建刷子操作区的绘制组。
const subBars = context.selectAll('.subBar').data(data)
subBars
.enter()
.append('rect')
.classed('subBar', true)
.attr('fill', 'gray')
.attr('height', function (d, i) {
return height2 - y2(d.price)
})
.attr('width', function (d, i) {
return x.bandwidth()
})
.attr('x', function (d) {
return x2(d.date)
})
.attr('y', function (d) {
return y2(d.price)
})
context
.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height2 + ')')
.call(xAxis2)
// 添加刷子
context.append('g').attr('class', 'x brush').call(brush)
.selectAll('rect').attr('y', 0).attr('height', height2)
d3.call()对整个集合进行操作。
- 根据数据,创建小一些的图表。用于区域选择,修改图表中展示的柱状数据。
- 添加刷子以图表的范围进行选择。
function update(data) {
x.domain(
data.map(function (d) {
return d.date
})
)
y.domain([
0,
d3.max(data, function (d) {
return d.price
})
])
var bars = chartSvg.selectAll('.bar').data(data)
bars
.attr('height', function (d, i) {
return height - y(d.price)
})
.attr('width', function (d, i) {
return x.bandwidth()
})
.attr('x', function (d) {
return x(d.date)
})
.attr('y', function (d) {
return y(d.price)
})
}
function exit(data) {
var bars = chartSvg.selectAll('.bar').data(data)
bars.exit().remove()
}
update()绑定数据时未使用标识,这里需要对获取到的d3对象,进行位置修改。exit()数据修改后,出现对多出的d3对象,进行删除。
/**
* 刷子操作函数
* 根据区域 获取要展示的数据
* */
function brushed(brush) {
let selected = null
selected = x2.domain().filter(function (d) {
return brush.selection[0] <= x2(d) && x2(d) <= brush.selection[1]
})
let start
let end
if (brush.selection[0] != brush.selection[1]) {
start = selected[0]
end = selected[selected.length - 1] + 1
} else {
start = 0
end = data.length
}
const updatedData = data.slice(start, end)
enter(updatedData)
update(updatedData)
exit(updatedData)
updateScale(updatedData, brush)
}
- 通过比例尺,获取在范围内的数据(x轴的输入域)。
- 通过得到的数据,在原始对象中等到范围内的数据。
- 使用最新数据创建柱状和比例尺。
- 代码地址