效果
需求
每隔两秒
- 更新比例尺
- 所有的点按照新的比例尺重新定位
- 加一个点 —— 从左上角0,0移动到当相应位置 新加点初始绿色
- 移除一个点—— 当前页数据第一个点,变红直径渐变为0移除
七步走
获取数据
import { dataset } from '../data/my_weather_data'
const xAccessor = (d: weatherData) => d.dewPoint
const yAccessor = (d: weatherData) => d.humidity
创建图标尺寸
const dimensions = {
viewBox: '0, 0, 400,400',
margin: {
top: 10,
right: 10,
bottom: 50,
left: 50,
},
boundedWidth: 340,
boundedHeight: 340,
}
绘制画布
const wapper = d3.select('#demo-scatterplot-animate')
const svg = wapper.append('svg').attr('width', '100%').attr('height', '100%').attr('viewBox', dimensions.viewBox)
const bounds = svg.append('g').style('transform', `translate(${dimensions.margin.left}px,${dimensions.margin.top}px)`)
由于我们后续要更新bounds中的点的属性以及刻度尺内容,我们将刻度尺和数据点的外层标签先写好。
bounds.append('g').attr('class', 'scatter-wapper')
const xAxis = bounds.append('g')
xAxis.append('text')
const yAxis = bounds.append('g')
yAxis.append('text')
创建刻度尺
我们将会变动的元素放在一个函数中处理。
const drawScatter = function (
bounds: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
dataset: Iterable<weatherData>,
xAxis: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
yAxis: d3.Selection<SVGGElement, unknown, HTMLElement, any>
) {
}
数据变动则调用drawScatter。 创建刻度尺
const xScale = d3.scaleLinear().domain(d3.extent(dataset, xAccessor)).range([0, dimensions.boundedWidth]).nice()
const yScale = d3.scaleLinear().domain(d3.extent(dataset, yAccessor)).range([dimensions.boundedHeight, 0]).nice()
绘制数据
数据绑定,我们将dataset绑定在单个点的包裹层 .scatter ,而且要传入第二个参数key来确保唯一性,为下次更新数据时做匹配(如果不加key,本次数据和上次数据长度一样就全覆盖了)。
let scatterGroups = bounds
.select('.scatter-wapper')
.selectAll('.scatter')
.data(dataset, (d: any) => d.date)
添加两个过渡 这里我最后u选择让exit和update同时发生。
const exitTransition = d3.transition().duration(1000)
const updateTransition = d3.transition().duration(1000)
选择要移除的元素,添加消失过渡性效果以及移除节点操作:
const exitGroup = scatterGroups.exit()
exitGroup.selectAll('circle').attr('fill', 'red').transition(exitTransition).attr('r', 0)
exitGroup.transition(exitTransition).remove()
选择新增的元素: 要注意这里的enterGroups是scatterGroups.enter()后添加的.scatter选择集。刚开始理解不深如果使用scatterGroups.enter()后续合并就会出问题,选择集合的问题大家可以自己看下。
const enterGroups = scatterGroups.enter().append('g').attr('class', 'scatter')
enterGroups.append('circle').attr('cx', 0).attr('cy', 0).attr('r', 0).attr('fill', 'green')
合并数据并绘制元素:
scatterGroups = scatterGroups.merge(enterGroups as any)
scatterGroups
.selectAll('circle')
.transition(updateTransition)
.attr('cx', (d: weatherData) => xScale(xAccessor(d)))
.attr('cy', (d: weatherData) => yScale(yAccessor(d)))
.attr('r', 5)
.attr('fill', 'cornflowerblue')
绘制外围
const xAxisGenerator = d3.axisBottom(xScale)
xAxis
.call(xAxisGenerator)
.style('transform', `translate(0,${dimensions.boundedHeight}px)`)
.select('text')
.attr('x', dimensions.boundedWidth / 2)
.attr('y', dimensions.margin.bottom - 10)
.attr('fill', 'black')
.style('font-size', '1.4em')
.text('Dew point (℉)')
const yAxisGenerator = d3.axisLeft(yScale).ticks(4)
yAxis
.call(yAxisGenerator)
.select('text')
.attr('x', -dimensions.boundedHeight / 2)
.attr('y', -dimensions.margin.left + 10)
.attr('fill', 'black')
.style('font-size', '1.4em')
.text('Relative humidity')
.style('transform', 'rotate(-90deg)')
.style('text-anchor', 'middle')
加个循环结束
let i = 0
setInterval(() => {
i++
const data = dataset.slice(i, i + 10)
drawScatter(bounds, data, xAxis, yAxis)
}, 2000)