Shared by CK
const data = [
{id: 1, date: '2017-12-18-09},
{id: 2, date: '2017-12-18-11},
{id: 3, date: '2017-12-18-15},
{id: 4, date: '2017-12-19-22},
{id: 5, date: '2017-12-20-12},
{id: 6, date: '2017-12-21-13},
{id: 7, date: '2017-12-22-14},
{id: 8, date: '2017-12-23-15},
{id: 9, date: '2017-12-24-16},
]
66
2月18
配置, 数据预处理, 比例尺等
const margin = {top:50, right:50, bottom:50, left:50},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
const histHeight = 100
const parseDate = d3.timeParse('%Y-%m-%d-%H')
data.forEach(d => {
d.date = parseDate(d.date)
})
// d3.timeParse 时间解析
//
// parseDate('2017-12-18-09')
// =>
// Mon Dec 18 2017 09:00:00 GMT+0800 (CST)
const startDate = new Date('2017-12-18 00:00:00')
const endDate = new Date('2017-12-25 00:000:00')
const dateArray = d3.timeDays(startDate, endDate)
// d3.range(1, 5) => [1,2,3,4]
// d3.timeDays是d3.timeDay.range的别名
// 类似的还有 d3.timeYears, d3.timeMonths, d3.timeSaturdays等
const colours = d3.scaleOrdinal()
.domain(dateArray)
.range([
'#409ffb', '#85d1ea', '#65cccb',
'#77debd', '#6ccb74', '#abdf81', '#fbd340'
]);
// d3.scaleOrdinal - 创建一个序数比例尺
const x = d3.scaleTime()
.domain([startDate, endDate])
.range([0, width])
.clamp(true)
// 1. d3.scaleTime —— 创建时间线性比例尺
// 2. time.clamp —— 启用闭合
const histogram = d3.histogram()
.value(d => d.date)
.domain(x.domain())
.thresholds(dateArray)
const bins = histogram(data)
// d3.histogram 创建一个新的直方图生成器 (本质是一个数据切分器)
// histogram.value 为每个样本指定一个值访问器 (指定你要切分的数据字段)
// histogram.domain 指定可观测值的间隔 (指定数据范围)
// histogram.thresholds 指定值划分成不同箱的方法 (指定切分的方法或者数组)
const bins = histogram(data)
const y = d3.scaleLinear()
.domain([0, d3.max(bins, d => d.length)])
.range([0, histHeight])
// d3.scaleLinear - 创建定量线性比例尺
// d3.max d3.max - 计算数组中的最大值
// 直方图容器g
const hist = svg.append('g')
.attr('class', 'histogram')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
// 滑动条容器g
const slider = svg.append('g')
.attr('class', 'slider')
.attr('transform', `translate(${margin.left}, ${margin.top + histHeight})`)
// 散点图容器g
const plot = svg.append('g')
.attr('class', 'plot')
.attr('transform', `translate(${margin.left}, ${margin.top + histHeight + 50})`)
// y轴比例尺
const y = d3.scaleLinear()
.domain([0, d3.max(bins, d => d.length)])
.range([0, histHeight])
const bar = hist.selectAll('.bar')
.data(bins).enter()
.append('g')
.attr('class', 'bar')
.attr('transform', d => `translate(${x(d.x0)}, ${histHeight - y(d.length)})`)
bar.append('rect')
.attr('class', 'bar')
.attr('width', d => x(d.x1) - x(d.x0) - 1) // 1是柱子间的间隔
.attr('height', d => y(d.length))
.attr('fill', d => colours(d.x0))
bar.append('text')
.attr('y', '6')
.attr('x', d => (x(d.x1) - x(d.x0))/2)
.attr('text-anchor', 'middle')
.text(d => d.length)
function drawPlot(data) {
const locations = plot.selectAll('.location')
.data(data, d => d.id)
locations.enter()
.append('circle')
.attr('class', 'location')
.attr('cx', d => x(d.date))
.attr('cy', d => Math.random() * 100)
.attr('fill', d => colors(d3.timeDay(d.date)))
.attr('stroke', d => colors(d3.timeDay(d.date)))
.attr('opacity', 0.7)
.attr('r', 5)
.transition()
.duration(400)
.attr('r', 15)
.transition()
.attr('r', 5)
locations.exit().remove()
}
function drawSlider() {
// 轨迹背景条
slider.append('rect')
.attr('class', 'drag-bar')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', 10)
.attr('fill', '#dcdcdc')
.attr('rx', 4)
.attr('ry', 4)
// 绘制日期刻度
slider.append('g', '.track-overlay')
.attr('class', 'ticks')
.attr('transform', 'translate(0, 18)')
.selectAll('text')
.data(x.ticks(7))
.enter()
.append('text')
.attr('x', x)
.attr('y', 10)
.attr('text-anchor', 'middle')
.text(d => formatDateIntoDay(d))
// 绘制拖动点
handle = slider.append('circle', '.track-overlay')
.attr('class', 'handle')
.attr('r', 9)
.attr('cy', 5)
// 可拖拽区域(时间绑定区域)
slider.append('rect')
.attr('class', 'drag-layer')
.attr('x', 0)
.attr('y', -35)
.attr('width', width)
.attr('height', 70)
.attr('fill', 'transparent')
.call(
d3.drag().on('start drag', update)
)
}
slider.append('rect')
.attr('class', 'drag-layer')
.attr('x', 0)
.attr('y', -35) // 起始位置是0轴的位置
.attr('width', width)
.attr('height', 70)
.attr('fill', 'transparent')
.call(
d3.drag().on('start drag', update)
)
function update() {
// 获取位置转换的日期时间信息
const h = x.invert(d3.event.x)
// 改变拖动条的位置
handle.attr('cx', x(h))
// 刷选出当前位置所表示时间之前的数据
const newData = data.filter(d => d.date < h)
// 重汇散点图
drawPlot(newData)
// 重新设置颜色, 小于h的还是原来的颜色,大于h的则置灰
d3.selectAll('.bar')
.attr('fill', d => d.x0 < h ? colors(d.x0) : '#eaeaea')
}