Skip to content

Instantly share code, notes, and snippets.

@ckinmind
Last active December 11, 2017 15:15
Show Gist options
  • Save ckinmind/9b94bbfa380391fde77ff91680683855 to your computer and use it in GitHub Desktop.
Save ckinmind/9b94bbfa380391fde77ff91680683855 to your computer and use it in GitHub Desktop.
Line——Step Chart

说明

  • 图表类型:折线图(Step Line)
  • 原图地址:D3 Step Chart
  • 原图是d3.v3版本实现,这版用v4版本重写了

知识点

  • d3.bisector 使用访问器和比较器二分查找某个值的index值
  • 关于path中的值比如M,V,Z, 参考这里
<!doctype html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Document</title>
<style>
svg text {
font-family: Helvetica,Arial,Verdana,sans-serif;
fill: #666;
font-size: 12px;
}
.line {
fill: none;
stroke: #3880aa;
stroke-width: 3px;
}
.path-fill {
fill: #3880aa;
opacity: 0.3;
stroke: none;
}
.axis .domain {
fill: none;
stroke: #fff;
}
.axis .tick line {
stroke: #EEE;
stroke-width: 1px;
}
.tooltip .tooltip-line {
fill: none;
stroke: #3880aa;
stroke-width: 1px;
stroke-dasharray: 3px;
}
.tooltip .tooltip-point {
fill: #3880aa;
stroke: #3880aa;
stroke-width: 1px;
}
.avg-line {
fill: none;
stroke: #3880aa;
stroke-width: 1px;
stroke-dasharray: 5px;
}
</style>
</head>
<body>
<div id='chart'></div>
<script src='//d3js.org/d3.v4.min.js'></script>
<script>
let data = [{
start_date: '2015-09-04',
end_date: '2015-09-08',
points: 46.44
},{
start_date: '2015-09-08',
end_date: '2015-09-15',
points: 52.37
},{
start_date: '2015-09-15',
end_date: '2015-09-20',
points: 41.63
},{
start_date: '2015-09-20',
end_date: '2015-09-24',
points: 55.35
},{
start_date: '2015-09-24',
end_date: '2015-09-28',
points: 47.51
},{
start_date: '2015-09-28',
end_date: '2015-10-07',
points: 56.55
},{
start_date: '2015-10-07',
end_date: '2015-10-12',
points: 45.74
},{
start_date: '2015-10-12',
end_date: '2015-10-15',
points: 54.79
},{
start_date: '2015-10-15',
end_date: '2015-10-20',
points: 48.86
},{
start_date: '2015-10-20',
end_date: '2015-10-29',
points: 47.6
},{
start_date: '2015-10-29',
end_date: '2015-11-04',
points: 48.04
}]
let margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
let parseDate = d3.timeParse('%Y-%m-%d'),
bisectDate = d3.bisector(d => d.start_date).left, // d3.bisector - 使用访问器和比较器二分查找
formatDate = d3.timeFormat('%Y-%m-%d')
const x = d3.scaleTime().range([0, width])
const y = d3.scaleLinear().range([height, 0])
const xAxis = d3.axisBottom(x)
.ticks(d3.timeSunday, 1)
.tickSize(-height, 0, 0)
.tickFormat(d => formatDate(d))
const yAxis = d3.axisLeft(y)
.ticks(6)
.tickSize(-width, 0, 0)
.tickFormat(d => d)
data.forEach((d, i) => {
d.start_date = parseDate(d.start_date)
d.end_date = parseDate(d.end_date)
})
// 这样首尾相连的,第一个的start_date就是最小值,最后一个的end_date是最大值
x.domain([
d3.min(data, d => d.start_date),
d3.max(data, d => d.end_date)
]);
y.domain([30, 60]); // 写死了
// 这里没法使用line去生成轨迹,只能自己手动生成
let line = 'M',
fill = `M0,${height}`
let svg = d3.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
// 手动生成线和面积区域的路径信息
data.forEach((d, i) => {
let y0 = y(d.points),
x0 = x(d.end_date)
if (i === 0) {
line += `${x(d.start_date)},${y0}H${x0}`
} else {
line += `H${x0}`
}
fill += `V${y0}H${x0}`
if (data[i + 1]) {
line += `V${y(data[i + 1].points)}`
}
})
fill += `V${height}Z`
console.log(fill)
// d3.mean - 计算数组的算术平均值
const avg = d3.mean(data, d => d.points)
// 绘制x轴
svg.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0,${height})`)
.call(xAxis)
// 绘制y轴
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Points')
// 绘制折线轨迹
svg.append('path')
.attr('class', 'line')
.attr('d', line)
// 绘制相应的折线面积区域
svg.append('path')
.attr('class', 'path-fill')
.attr('d', fill)
// 绘制均值线
svg.append('line')
.attr('class', 'avg-line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(avg))
.attr('y2', y(avg))
let focus = svg.append('g')
.attr('class', 'tooltip')
.style('display', 'none')
// hover时候的移动点的圆形
focus.append('circle')
.attr('class', 'tooltip-point')
.attr('r', 6)
// hover时移动点上文字
focus.append('text')
.attr('class', 'y1')
.attr('dx', '-2em')
.attr('dy', '-.75em')
// hover时候间断开始的那根线
focus.append('line')
.attr('class', 'tooltip-line tooltip-start-date')
.attr('y1', height)
.attr('y2', height)
// hover时候间断结束的那根线
focus.append('line')
.attr('class', 'tooltip-line tooltip-end-date')
.attr('y1', height)
.attr('y2', height)
focus.append('line')
.attr('class', 'tooltip-line tooltip-mileage')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', height)
.attr('y2', height)
// 第一根线上的日期文字
focus.append('text')
.attr('class', 'x1')
.attr('dx', '-4.5em')
.attr('dy', '-.5em')
.attr('transform', 'rotate(90)')
// 第二根线上的日期文字
focus.append('text')
.attr('class', 'x2')
.attr('dx', '-4.5em')
.attr('dy', '-.5em')
// 整个覆盖一层rect,用于绑定事件
svg.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', 'none')
.style('pointer-events', 'all') // 可以触发所有类型事件
.on('mouseover', () => focus.style('display', null)) //似乎相当于覆盖了block
.on('mouseout', () => focus.style('display', 'none'))
.on('mousemove', mousemove)
function mousemove() {
let x0 = x.invert(d3.mouse(this)[0]), // 用于获取鼠标位置的x轴坐标
i = bisectDate(data, x0, 1), // 查找到当前点在x轴排列的index值
d = data[i - 1] // 这里和原图处理不一样,直接去
// 移动点的位置变动
focus.select('circle.tooltip-point')
.attr('transform',`translate(${d3.mouse(this)[0]},${y(d.points)})`)
// 移动点上的文字位置变动
focus.select('text.y1')
.text(d.points)
.attr('transform', `translate(${d3.mouse(this)[0]}, ${y(d.points)})`)
// 第一根线上的文字位置
focus.select('text.x1')
.text(formatDate(d.start_date))
.attr('transform', `translate(${x(d.start_date)}, ${((height + y(d.points))/2)}) rotate(-90)`)
// 第一根线的位置
focus.select('line.tooltip-start-date')
.attr('y2', y(d.points))
.attr('x1', x(d.start_date))
.attr('x2', x(d.start_date))
// 第二根线上的文字
focus.select('text.x2')
.text(formatDate(d.end_date))
.attr('transform', `translate(${x(d.end_date)}, ${((height + y(d.points))/2)}) rotate(-90)`)
// 第二根线的位置
focus.select('line.tooltip-end-date')
.attr('y2', y(d.points))
.attr('x1', x(d.end_date))
.attr('x2', x(d.end_date))
// 移动点上的那根线
focus.select('line.tooltip-mileage')
.attr('y1', y(d.points))
.attr('y2', y(d.points))
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment