|
<!doctype html> |
|
<html lang='en'> |
|
<head> |
|
<meta charset='UTF-8'> |
|
<title>Document</title> |
|
<style> |
|
body { |
|
/*background-color: #444*/ |
|
} |
|
|
|
.label { |
|
fill: #fff; |
|
font-family: sans-serif; |
|
font-size: 10px; |
|
} |
|
|
|
text { |
|
fill : #777; |
|
} |
|
|
|
.xaxis .tick:not(:first-of-type) line { |
|
stroke: #777; |
|
stroke-dasharray: 3,2; |
|
} |
|
.yaxis .tick:not(:first-of-type) line { |
|
stroke: #777; |
|
stroke-dasharray: 3,2; |
|
} |
|
|
|
.active { |
|
stroke: #000000; |
|
stroke-width: 1.5px; |
|
cursor: pointer; |
|
fill: darkorange; |
|
} |
|
|
|
.domain, line { |
|
stroke: #777; |
|
} |
|
|
|
.focusLine { |
|
shape-rendering: crispEdges; |
|
stroke: #777; |
|
stroke-width: 1.1; |
|
stroke-linecap: butt; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
|
|
<script src='https://d3js.org/d3.v4.min.js'></script> |
|
<script> |
|
|
|
const totalWidth = 900 |
|
const totalHeight = 600 |
|
|
|
const margin = {top: 20, left: 50, bottom: 30, right: 30} |
|
const width = totalWidth - margin.left - margin.right |
|
const height = totalHeight - margin.top - margin.bottom |
|
|
|
const formatDecimal = d3.format(',.0f') |
|
const theData = [] |
|
|
|
for (let i = 0; i < 50; i++) { |
|
let item = { |
|
radius: Math.random() * 0.20, |
|
cx: Math.random() * 100000, |
|
cy: Math.random() * 1000 |
|
} |
|
theData.push(item) |
|
} |
|
|
|
// 圆半径比例尺 |
|
const sizeDomain = d3.extent(theData, d => d.radius) |
|
const sizeScale = d3.scaleLinear().domain(sizeDomain).range([4, 16]) |
|
|
|
// x轴比例尺 |
|
const xDomain = d3.extent(theData, d => d.cx) |
|
const xScale = d3.scaleLinear().domain(xDomain).range([0, width]).nice(10) |
|
|
|
// y轴比例尺 |
|
const yDomain = d3.extent(theData, d => d.cy) |
|
const yScale = d3.scaleLinear().domain(yDomain).range([height, 0]).nice(5) |
|
|
|
// 颜色比例尺 |
|
const colorDomain = d3.extent(theData, d => d.radius) |
|
const colorize = d3.scaleSequential(d3.interpolateViridis) |
|
const colorScale = d3.scaleLinear().domain(colorDomain).range([0, 1]) |
|
|
|
|
|
const svg = d3.select('body').append('svg') |
|
.attr('id', 'scatterplot') |
|
.attr('width', totalWidth) |
|
.attr('height', totalHeight) |
|
.style('border', 'dashed 1px gray' ) |
|
|
|
const mainGroup = svg.append('g') |
|
.attr('id', 'mainGroup') |
|
.attr('transform', `translate(${margin.left}, ${margin.top})`) |
|
|
|
const xAxisGroup = mainGroup.append('g') |
|
.attr('class', 'axis xaxis') |
|
.attr('transform', `translate(0, ${height})`) |
|
.call( |
|
d3.axisBottom(xScale) |
|
.tickSize(6) |
|
.tickSizeInner(-height) |
|
.tickPadding(9) |
|
) |
|
|
|
const yAxisGroup = mainGroup.append('g') |
|
.attr('class', 'axis yaxis') |
|
.call( |
|
d3.axisLeft(yScale).ticks(5) |
|
.tickSizeInner(-width) |
|
.tickPadding(9) |
|
) |
|
|
|
// 事件层 |
|
const eventGroup = mainGroup.append('g').attr('id', 'event-overlay') |
|
const crosshair = eventGroup.append('g').attr('id', 'crosshair') |
|
const eventRect = eventGroup.append('rect') |
|
|
|
|
|
// 散点层(需要在事件层之后,因为散点上也绑有事件) |
|
const circleGroup = mainGroup.append('g').attr('id', 'circleGroup') |
|
|
|
// CHART ASSEMBLY |
|
const crosshairSettings = { |
|
xLabelTextOffset: height + 12, |
|
yLabelTextOffset: -9, |
|
labelWidth: 38, |
|
labelHeight: 14, |
|
labelColor: '#aaa', |
|
labelStrokeColor: 'none', |
|
labelStrokeWidth: '0.5px' |
|
} |
|
|
|
crosshair.append('line').attr('class', 'focusLine focusLineX') |
|
crosshair.append('line').attr('class', 'focusLine focusLineY') |
|
|
|
crosshair.append('rect') |
|
.attr('class', 'focusLineLabelBackground focusLineXLabelBackground') |
|
.attr('fill', '#aaa') |
|
.attr('width', 38) |
|
.attr('height', 14) |
|
|
|
crosshair.append('text') |
|
.attr('class', 'label focusLineXLabel') |
|
.attr('text-anchor', 'middle') |
|
.attr('alignment-baseline', 'central') |
|
|
|
|
|
crosshair.append('rect') |
|
.attr('class', 'focusLineLabelBackground focusLineYLabelBackground') |
|
.attr('fill', '#aaa') |
|
.attr('width', 38) |
|
.attr('height', 14) |
|
|
|
crosshair.append('text') |
|
.attr('class', 'label focusLineYLabel') |
|
.attr('text-anchor', 'end') |
|
.attr('alignment-baseline', 'central') |
|
|
|
circleGroup.selectAll('circle') |
|
.data(theData) |
|
.enter() |
|
.append('circle') |
|
.attr('class', 'circle') |
|
.attr('cx', d => xScale(d.cx)) |
|
.attr('cy', d => yScale(d.cy)) |
|
.style('fill', d => colorize(colorScale(d.radius))) |
|
.style('opacity', 1) |
|
.on('mouseover', function(d, i) { |
|
d3.select(this).classed('active', true).style('fill', 'darkorange') |
|
crosshair.style('display', null) |
|
setCrosshair(xScale(d.cx), yScale(d.cy)) |
|
}) |
|
.on('mouseout', function(d, i) { |
|
d3.select(this).classed('active', false).style('fill', d => colorize(colorScale(d.radius))) |
|
}) |
|
.transition() |
|
.attr('r', d => sizeScale(d.radius)) |
|
|
|
eventRect.attr('width', width) |
|
.attr('height', height) |
|
.style('opacity', 0) |
|
.style('display', null) |
|
.on('mouseover', function() { |
|
crosshair.style('display', null) |
|
}) |
|
.on('mouseout', function() { |
|
crosshair.style('display', 'none') |
|
}) |
|
.on('mousemove', function handleMouseMove() { |
|
const mouse = d3.mouse(this) |
|
const x = mouse[0] |
|
const y = mouse[1] |
|
setCrosshair(x, y) |
|
}) |
|
|
|
function setCrosshair(x, y) { |
|
|
|
d3.select('.focusLineX') |
|
.attr('x1', x) |
|
.attr('y1', 0) |
|
.attr('x2', x) |
|
.attr('y2', height + 6) |
|
|
|
d3.select('.focusLineY') |
|
.attr('x1', -6) |
|
.attr('y1', y) |
|
.attr('x2', width) |
|
.attr('y2', y) |
|
|
|
d3.select('.focusLineXLabel') |
|
.attr('x', x) |
|
.attr('y', height + 12) |
|
.text(formatDecimal(xScale.invert(x))) |
|
|
|
d3.select('.focusLineYLabel') |
|
.attr('transform', `translate(-9, ${y})`) |
|
.text(formatDecimal(yScale.invert(y))) |
|
|
|
|
|
d3.select('.focusLineXLabelBackground') |
|
.attr('transform', `translate(${(x - crosshairSettings.labelWidth * 0.5)}, ${(height + 5)})`) |
|
.text(formatDecimal(xScale.invert(x))) |
|
|
|
d3.select('.focusLineYLabelBackground') |
|
.attr('transform', `translate(${-crosshairSettings.labelWidth}, ${(y - 8)})`) |
|
} |
|
</script> |
|
</body> |
|
</html> |