Skip to content

Instantly share code, notes, and snippets.

@standarderror
Last active February 17, 2016 10:04
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 standarderror/4cbf70c74747c829e4a6 to your computer and use it in GitHub Desktop.
Save standarderror/4cbf70c74747c829e4a6 to your computer and use it in GitHub Desktop.
20150111 Regression Scatterplot Animation
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#chart {
}
.circle {
stroke: '#fff';
}
.label {
fill: #777;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
</style>
<body>
<div id='chart' align='center'></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="regression_animation.js"></script>
</body>
var width = 100
, height = 100
, margin = {top: 8, right: 8, bottom: 8, left: 8}
, uid = 0
, datasetSize = 3 // start dataset
, interval = 1500
, maxData = 10
, minData = 3;
var color = d3.scale.category10();
// DATASET
// [0] is uid
// [1] is x value
// [2] is y value
// [3] for colour
var dataset = [];
for (var i = 0 ; i < datasetSize ; i++) {
dataset.push(generateDatum());
}
// set up the scales
var xScale = d3.scale.linear()
.range([0, width])
.domain([0, 100])
.nice();
var yScale = d3.scale.linear()
.range([height, 0])
.domain([0, 100])
.nice();
// set the axes
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.outerTickSize(5)
.ticks(8)
;
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.outerTickSize(5)
.ticks(8)
;
// append the svg to the body of the page and set the width and height
// append a 'g' element to group the circles together and 'translate'
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
;
// draw x gridlines
var svgXaxis = svg.append("g");
svgXaxis.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(xAxis
.tickSize(-height, 0, 0)
.tickFormat("")
)
;
// var svgXaxis = svg.append('g')
// .attr("class", "axis")
// .attr({'transform': 'translate(0,' + height + ')'})
// .call(xAxis);
// draw Y gridlines
var svgYaxis = svg.append("g");
svgYaxis.attr("class", "grid")
.call(yAxis
.tickSize(-width, 0, 0)
.tickFormat("")
)
// var svgYaxis = svg.append('g')
// .attr({
// 'class': 'axis label'
// })
// .call(yAxis);
// draw initial display
update();
// add new data
var count = 0;
var interval = setInterval(function() {
// remove if too many, add if too few...
if (dataset.length >= maxData) {
var indexToRemove = parseInt((Math.random() * dataset.length));
// console.log('Removing point ' + indexToRemove + ': ' + dataset[indexToRemove]);
dataset.splice(indexToRemove, 1)
} else if (dataset.length <= minData) {
var d = generateDatum();
// console.log('Adding new point ' + d);
dataset.push(d);
// ...else, randomly decide whether to add or remove
} else if (Math.random() < 0.5) {
var indexToRemove = parseInt((Math.random() * dataset.length));
// console.log('Removing point ' + indexToRemove + ': ' + dataset[indexToRemove]);
dataset.splice(indexToRemove, 1)
} else {
var d = generateDatum();
// console.log('Adding new point ' + d);
dataset.push(d);
}
update();
// update counter
count++;
if (count >= 5000) {
clearInterval(interval);
console.log('Finished.');
}
}, interval);
var line = d3.svg.line().interpolate("monotone")
.x(function(d){ return xScale(d.x); })
.y(function(d){ return yScale(d.y); })
function update () {
// update the scales' domain
xScale.domain([0, d3.max(dataset, function(d) { return d[1]; })]);
yScale.domain([0, d3.max(dataset, function(d) { return d[2]; })]);
var xMax = d3.max(dataset, function(d) { return d[1]; });
// data-join
var dot = svg.selectAll("circle")
.data(dataset, function(d) {return d[0]});
// udpate
dot.transition()
.duration(750)
.attr({
"cx": function(d) { return xScale(d[1]); }
, "cy": function(d) { return yScale(d[2]); }
})
.attr('r','5')
.attr("fill", function(d) { return color(d[3]); })
;
// enter
dot.enter().append("circle")
.attr({
"class": "circle"
, "cx": function(d) { return xScale(d[1]); }
, "cy": function(d) { return yScale(d[2]); }
})
.attr('r','0')
.attr("fill", function(d) { return color(d[3]); })
.style('opacity', 1e-6)
.transition()
.duration(200)
.ease('bounce')
.style('opacity', 1)
.attr('r', 10)
.transition()
.duration(400)
.attr('r', 5)
;
dot.exit()
.transition()
.duration(700)
.attr('r','0')
.style('opacity', 1e-6)
.remove();
//// Regression line
// Calculate line of best fit
var yval = dataset.map(function (d) { return parseFloat(d[2]); });
var xval = dataset.map(function (d) { return parseFloat(d[1]); });
var lr = linearRegression(yval,xval);
// now you have:
// lr.slope
// lr.intercept
// lr.r2
var myLine = svg.selectAll(".line")
.data([lr])
.attr("stroke-width", 1.5)
.attr("stroke", "black")
;
myLine.transition()
.attr("class","line")
.delay(500)
.transition()
.attr("x1", xScale(0)+10)
.attr("y1", function(d) {
return yScale(d[0]);
})
.attr("x2", xScale(xMax)-10)
.attr("y2", function(d) {
return yScale((xMax * d[1]) + d[0] );
})
;
myLine.enter()
.append("line")
.attr("class","line")
.attr("x1", xScale(0)+10)
.attr("y1", function(d) {
return yScale(d[0]);
})
.attr("x2", xScale(0)+10)
.attr("y2", function(d) {
return yScale(d[0]);
})
.attr("stroke-width", 0)
.transition().duration(750)
.attr("stroke-width", 1.5)
.attr("stroke", "black")
.attr("x2", xScale(xMax)-10)
.attr("y2", function(d) {
return yScale((xMax * d[1]) + d[0] );
})
;
myLine.exit().remove()
;
// update axes
svgXaxis.transition().call(xAxis);
svgXaxis.selectAll("text").remove(); // remove tick labels
svgYaxis.transition().call(yAxis);
svgYaxis.selectAll("text").remove();
}
function generateDatum () {
return [
uid++
, parseInt(5 + Math.random() * 90)
, parseInt(5 + Math.random() * 90)
, parseInt((Math.random() * 10)) // + 10
];
}
function linearRegression(y,x){
// var lr = {};
var lr = [];
var n = y.length;
var sum_x = 0;
var sum_y = 0;
var sum_xy = 0;
var sum_xx = 0;
var sum_yy = 0;
for (var i = 0; i < y.length; i++) {
sum_x += x[i];
sum_y += y[i];
sum_xy += (x[i]*y[i]);
sum_xx += (x[i]*x[i]);
sum_yy += (y[i]*y[i]);
}
// intercept 0, slope 1
lr[1] = (n * sum_xy - sum_x * sum_y) / (n*sum_xx - sum_x * sum_x);
lr[0] = (sum_y - lr[1] * sum_x)/n;
// lr['slope'] = (n * sum_xy - sum_x * sum_y) / (n*sum_xx - sum_x * sum_x);
// lr['intercept'] = (sum_y - lr.slope * sum_x)/n;
//lr['r2'] = Math.pow((n*sum_xy - sum_x*sum_y)/Math.sqrt((n*sum_xx-sum_x*sum_x)*(n*sum_yy-sum_y*sum_y)),2);
return lr;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment