Skip to content

Instantly share code, notes, and snippets.

@ckinmind
Last active January 9, 2018 14:51
Show Gist options
  • Save ckinmind/20dca29a8933613f6b2f21ea2e1b674d to your computer and use it in GitHub Desktop.
Save ckinmind/20dca29a8933613f6b2f21ea2e1b674d to your computer and use it in GitHub Desktop.
Scatter——scatterplot

说明

  • 图表类型:散点图
  • 原图地址:D3 Scatterplot
  • 去掉原图的多选库和颜色库(额外的两个js文件)

知识点

  • d3.scaleSequential - 创建一个颜色顺序比例尺(定义域[0, 1])
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment