All data is generated randomly.
Selectiong on the graph, the data point and specific value will be highlighted.
license: mit |
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v3.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; | |
font-family: Futura, Arial, sans-serif; | |
} | |
.tick line, path.domain{stroke: #555;stroke-width: 1;fill: none;} | |
.tick text{fill: #555;} | |
.extent{ | |
fill: red; | |
opacity: 0.3; | |
} | |
.grid line{ | |
fill: none; | |
stroke: #ccc; | |
stroke-opacity: 0.7; | |
stroke-width: 1px; | |
} | |
.grid path{ | |
display: none; | |
} | |
circle.highlight{ | |
transition: all 0.6s; | |
stroke-width: 3; | |
stroke: red; | |
} | |
text.show{ | |
/* !important */ | |
opacity: 1 !important; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="container"></div> | |
<script> | |
function draw(){ | |
var margin = {top: 65, bottom: 55, left: 55, right: 45}, | |
axisPadding = 10, | |
radius = 3, // circle size | |
svgWidth = 550, | |
svgHeight = 400, | |
gWidth = svgWidth - (margin.left + margin.right), | |
gHeight = svgHeight - (margin.top + margin.bottom); | |
var xScale = d3.scale.linear().range([0, gWidth]).domain([0, 20]), | |
yScale = d3.scale.linear().range([0, gHeight]).domain([60, 0]), | |
xAxis = d3.svg.axis().ticks(5).scale(xScale).orient('bottom'), | |
yAxis = d3.svg.axis().ticks(5).scale(yScale).orient('left'), | |
xGrid = d3.svg.axis().ticks(5) | |
.scale(xScale).orient('bottom') | |
.tickSize(gHeight,0).tickFormat(''), | |
yGrid = d3.svg.axis().ticks(5) | |
.scale(yScale).orient('left') | |
.tickSize(-gWidth,0).tickFormat(''); | |
// generate all data randomly | |
var numGenerator = function(x){ | |
if (arguments.length == 0){ | |
var x = 1; | |
}; | |
return function(){ | |
return Math.floor(Math.random() * x); | |
}; | |
}; | |
// a generator range from 0 ~ 49 | |
var f = numGenerator(50); | |
var data = d3.range(20) | |
.map(function(d, i){ | |
return {x: i, y: f()}; | |
}); | |
// define svg generator | |
var lineStyle = 'monotone'; | |
var line = d3.svg.line() | |
.interpolate(lineStyle) | |
.x(function(d){return xScale(d.x)}) | |
.y(function(d){return yScale(d.y)}); | |
var area = d3.svg.area() | |
.interpolate(lineStyle) | |
.x(function(d){return xScale(d.x)}) | |
.y(function(d){return yScale(d.y)}) | |
.y0(yScale(0)); | |
// here we go | |
var svg = d3.select('#container') | |
.append('svg') | |
.attr('width', svgWidth) | |
.attr('height', svgHeight) | |
.attr('class', 'graph'); | |
var xGroup = svg.append('g') | |
.attr('class', 'xGroup') | |
.attr('transform', 'translate(' + | |
[margin.left, margin.top + gHeight + axisPadding] + ')'); | |
var yGroup = svg.append('g') | |
.attr('class', 'yGroup') | |
.attr('transform', 'translate(' + | |
[margin.left - axisPadding, margin.top] + ')'); | |
var grid1 = svg.append('g') | |
.attr('class', 'grid') | |
.attr('transform', 'translate(' + | |
[margin.left, margin.top] + ')'); | |
var grid2 = svg.append('g') | |
.attr('class', 'grid') | |
.attr('transform', 'translate(' + | |
[margin.left, margin.top] + ')'); | |
xGroup.call(xAxis); | |
yGroup.call(yAxis); | |
grid1.call(xGrid); | |
grid2.call(yGrid); | |
var graph = svg.append('g') | |
.attr('class', 'graph') | |
.attr('transform', 'translate(' + | |
[margin.left, margin.top] + ')'); | |
graph | |
.append('path') | |
.datum(data) | |
.attr('class', 'area') | |
.attr('d', area) | |
.attr('fill', 'steelblue') | |
.attr('fill-opacity', 0.5); | |
graph | |
.append('path') | |
.datum(data) | |
.attr({ | |
class : 'line', | |
d : line, | |
fill : 'none', | |
stroke : 'blue', | |
'stroke-width': 1 | |
}); | |
var circles = graph | |
.selectAll('circle') | |
.data(data) | |
.enter() | |
.append('circle') | |
.each(function(d,i){ | |
d3.select(this).attr({ | |
cx : xScale(d.x), | |
cy : yScale(d.y), | |
fill : 'white', | |
r : radius, | |
stroke : 'black', | |
'stroke-width': 1 | |
}) | |
}); | |
// creat labels with 0 opacity | |
var labels = graph | |
.selectAll('text') | |
.data(data) | |
.enter() | |
.append('text') | |
.each(function(d,i){ | |
d3.select(this) | |
.attr({ | |
x : xScale(d.x), | |
y : yScale(d.y), | |
dy: -6, | |
dx: -6 | |
}) | |
.style({transition: 'all .5s', opacity: 0}) | |
.text(d.y); | |
}); | |
// define the brush | |
var brush = d3.svg.brush() | |
.x(xScale) | |
.y(yScale); | |
// apply the brush to graph selection | |
brush(graph); | |
// define event | |
brush | |
.on('brushstart', function(){ | |
d3.selectAll('circle.highlight').classed('highlight', false); | |
d3.selectAll('text.show').classed('show', false); | |
}) | |
.on('brush', function(){ | |
var ext = brush.extent(); | |
circles.classed('highlight', function(d){ | |
// if circles located in the extent teritory, | |
// then they should be highlighted (return true) | |
return (ext[0][0] <= d.x && d.x <= ext[1][0]) && | |
(ext[0][1] <= d.y && d.y <= ext[1][1]) | |
}); | |
labels.classed('show', function(d){ | |
return (ext[0][0] <= d.x && d.x <= ext[1][0]) && | |
(ext[0][1] <= d.y && d.y <= ext[1][1]) | |
}) | |
}) | |
.on('brushend', function(){ | |
console.log(d3.selectAll('circle.highlight')[0].length) | |
}); | |
// initial a brush | |
d3.select('rect.extent') | |
.attr({ | |
width: gWidth / 2, | |
height: gHeight / 2, | |
x: gWidth / 4, | |
y: gHeight / 4, | |
}); | |
circles.classed('highlight', function(d){ | |
return xScale(d.x) >= (gWidth / 4) && | |
xScale(d.x) <= (gWidth / 4 * 3) && | |
yScale(d.y) >= (gHeight / 4) && | |
yScale(d.y) <= (gHeight / 4 * 3); | |
}); | |
labels.classed('show', function(d){ | |
return xScale(d.x) >= (gWidth / 4) && | |
xScale(d.x) <= (gWidth / 4 * 3) && | |
yScale(d.y) >= (gHeight / 4) && | |
yScale(d.y) <= (gHeight / 4 * 3); | |
}); | |
}; | |
draw(); | |
</script> | |
</body> |