Last active
November 24, 2016 02:53
-
-
Save jrbalsano/78725132087e01c988e870926a50c854 to your computer and use it in GitHub Desktop.
D3 Canvas Scatterplot - 3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script> | |
const margin = { top: 20, right: 20, bottom: 40, left: 40 }; | |
const height = 400 - margin.top - margin.bottom; | |
const width = 960 - margin.left - margin.right; | |
let colorToData = {}; | |
let timer, startTime, selectedDatum; | |
function showTimeSince(startTime) { | |
const currentTime = new Date().getTime(); | |
const runtime = currentTime - startTime; | |
document.getElementById('timeRendering').innerHTML = runtime + 'ms'; | |
} | |
function showFavoriteNumber() { | |
if (selectedDatum) { | |
document.getElementById('favoriteNumber').innerHTML = selectedDatum.favoriteNumber; | |
} | |
} | |
function startTimer() { | |
stopTimer(); | |
startTime = new Date().getTime(); | |
timer = setInterval(function() { | |
showTimeSince(startTime); | |
}, 10); | |
showTimeSince(startTime); | |
} | |
function stopTimer() { | |
if (timer) { | |
clearInterval(timer); | |
} | |
showTimeSince(startTime); | |
} | |
function generateData(numPoints) { | |
const data = []; | |
for (let i = 0; i < numPoints; i++) { | |
data.push({ | |
x: Math.random(), | |
y: Math.random(), | |
favoriteNumber: Math.round(Math.random() * 10) | |
}); | |
} | |
return data; | |
} | |
function getColor(index) { | |
return d3.rgb( | |
Math.floor(index / 256 / 256) % 256, | |
Math.floor(index / 256) % 256, | |
index % 256) | |
.toString(); | |
} | |
function paintPoint(context, virtualContext, d, i, x, y, r) { | |
const color = getColor(i); | |
if (i !== undefined) { | |
colorToData[color] = d; | |
virtualContext.fillStyle = color; | |
virtualContext.beginPath(); | |
virtualContext.arc(x(d.x), y(d.y), r, 0, 2 * Math.PI); | |
virtualContext.fill(); | |
} else { | |
context.strokeStyle = d3.rgb(0, 190, 25); | |
context.lineWidth = 4; | |
} | |
// start a new path for drawing | |
context.beginPath(); | |
// paint an arc based on information from the DOM node | |
context.arc(x(d.x), y(d.y), r, 0, 2 * Math.PI); | |
// fill the point | |
if (i !== undefined) { | |
context.fill(); | |
} else { | |
context.stroke(); | |
} | |
} | |
function paintCanvas(canvas, virtualCanvas, data, x, y) { | |
startTimer(); | |
// get the canvas drawing context | |
const context = canvas.getContext("2d"); | |
const virtualContext = virtualCanvas.getContext("2d"); | |
// clear the canvas from previous drawing | |
context.clearRect(0, 0, canvas.width, canvas.height); | |
virtualContext.clearRect(0, 0, virtualCanvas.width, virtualCanvas.height); | |
// clear data | |
colorToData = {}; | |
// draw a circle for each | |
data.forEach((d, i) => { | |
paintPoint(context, virtualContext, d, i, x, y, 2); | |
}); | |
if (selectedDatum) { | |
paintPoint(context, virtualContext, selectedDatum, undefined, x, y, 4); | |
} | |
stopTimer(); | |
} | |
function renderChart() { | |
// Get the amount of data to generate | |
const numPoints = parseInt(document.getElementsByName('numPoints')[0].value, 10); | |
if (isNaN(numPoints)) { | |
return; | |
} | |
const data = generateData(numPoints); | |
// Make a container div for our graph elements to position themselves against | |
const graphDiv = d3.selectAll('div').data([0]); | |
graphDiv.enter().append('div') | |
.style('position', 'relative'); | |
// Make an SVG for axes | |
const svg = graphDiv.selectAll('svg').data([0]); | |
svg.enter().append('svg') | |
.style('position', 'absolute') | |
.attr('height', height + margin.top + margin.bottom) | |
.attr('width', width + margin.left + margin.right) | |
// Create groups for axes | |
const xAxisG = svg.selectAll('g.x').data([0]); | |
xAxisG.enter().append('g') | |
.attr('class', 'x') | |
.attr('transform', 'translate(' + margin.left + ', ' + (margin.top + height) + ')'); | |
const yAxisG = svg.selectAll('g.y').data([0]); | |
yAxisG.enter().append('g') | |
.attr('class', 'y') | |
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); | |
// Create scales | |
const x = d3.scale.linear() | |
.domain([0, 1]) | |
.range([0, width]); | |
const y = d3.scale.linear() | |
.domain([0, 1]) | |
.range([height, 0]); | |
// Create axes | |
const xAxis = d3.svg.axis() | |
.scale(x) | |
.orient('bottom'); | |
const yAxis = d3.svg.axis() | |
.scale(y) | |
.orient('left'); | |
xAxisG.call(xAxis); | |
yAxisG.call(yAxis); | |
// Make a canvas for the points | |
const canvas = graphDiv.selectAll('canvas').data([0]); | |
canvas.enter().append('canvas') | |
.attr('height', height) | |
.attr('width', width) | |
.style('position', 'absolute') | |
.style('top', margin.top + 'px') | |
.style('left', margin.left + 'px'); | |
const virtualCanvas = d3.select(document.createElement('canvas')) | |
.attr('height', height) | |
.attr('width', width); | |
paintCanvas(canvas.node(), virtualCanvas.node(), data, x, y); | |
canvas.on('mousemove', function() { | |
const mouse = d3.mouse(this); | |
const mouseX = mouse[0]; | |
const mouseY = mouse[1]; | |
const imageData = virtualCanvas.node().getContext('2d').getImageData(mouseX, mouseY, 1, 1); | |
const color = d3.rgb.apply(null, imageData.data).toString(); | |
const possibleDatum = colorToData[color]; | |
if (!possibleDatum | |
|| Math.abs(x(possibleDatum.x) - mouseX) > 2 | |
|| Math.abs(y(possibleDatum.y) - mouseY) > 2) { | |
return; | |
} | |
selectedDatum = possibleDatum | |
paintCanvas(canvas.node(), virtualCanvas.node(), data, x, y); | |
showFavoriteNumber(); | |
}); | |
} | |
</script> | |
</head> | |
<body> | |
<form action=""> | |
<input name="numPoints" type="text" value="10000"> | |
<button type="button" id="render" onClick="renderChart()">Render</button> | |
</form> | |
Time Rendering: <span id="timeRendering"></span> | |
Favorite Number: <span id="favoriteNumber"></span> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment