|
<!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> |