Skip to content

Instantly share code, notes, and snippets.

@jrbalsano
Last active November 24, 2016 02:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jrbalsano/78725132087e01c988e870926a50c854 to your computer and use it in GitHub Desktop.
Save jrbalsano/78725132087e01c988e870926a50c854 to your computer and use it in GitHub Desktop.
D3 Canvas Scatterplot - 3
<!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