Skip to content

Instantly share code, notes, and snippets.

@easadler
Last active November 21, 2015 18:19
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 easadler/edae96ae440aa0361a4d to your computer and use it in GitHub Desktop.
Save easadler/edae96ae440aa0361a4d to your computer and use it in GitHub Desktop.
Interactive Linear Regression

Click on the graph to draw points. A line of best fit will dynamically update to the new data points.

var drawline = function(data){
var xValues = data.map(function(d){return d.x;});
var yValues = data.map(function(d){return d.y;});
var lsCoef = [LeastSquares(xValues, yValues)];
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
svg
.append('path')
.attr("d", lineFunction([{"x": 50, "y": lsCoef[0].m * 50 + lsCoef[0].b},{"x": 450, "y": lsCoef[0].m * 450 + lsCoef[0].b}]))
.attr("stroke-width", 2)
.attr("stroke", "black")
.attr('id', 'regline');
}
var transitionline = function(data){
var xValues = data.map(function(d){return d.x;});
var yValues = data.map(function(d){return d.y;});
var lsCoef = [LeastSquares(xValues, yValues)];
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
d3.select('#regline')
.transition()
.attr('d', lineFunction([{"x": 50, "y": lsCoef[0].m * 50 + lsCoef[0].b},{"x": 450, "y": lsCoef[0].m * 450 + lsCoef[0].b}]));
}
var drawresiduals = function(data){
//get least squares coeffs, great dotted red paths
var xValues = data.map(function(d){return d.x;});
var yValues = data.map(function(d){return d.y;});
var lsCoef = [LeastSquares(xValues, yValues)];
var lineFunction = d3.svg.line()
.y(function(d) { return d.y;
})
.x(function(d) { return d.x; });
var resids = data.map(function(d){
return {"x0": d.x, "y0": d.y, "x1": d.x , "y1": lsCoef[0].m * d.x + lsCoef[0].b}
})
var halfcircles = function(d){
var radius = r(200),
padding = 10,
radians = Math.PI;
var dimension = (2 * radius) + (2 * padding),
points = 50;
var angle = d3.scale.linear()
.domain([0, points-1])
.range([ 0, radians]);
var fullangle = d3.scale.linear()
.domain([0, points-1])
.range([ 0, 2*radians]);
var line = d3.svg.line.radial()
.interpolate("basis")
.tension(0)
.radius(radius)
.angle(function(e, i) {
if(d.y0-d.y1 < -r(200)) {
return angle(i) + Math.PI/2;
} else if (d.y0 - d.y1 > r(200)){
return angle(i) + Math.PI*(3/2);
} else {
return fullangle(i);
}
})
svg.append("path").datum(d3.range(points))
.attr("class", "line")
.attr("d", line)
.attr("fill", 'none')
.attr("transform", "translate(" + (d.x0) + ", " + (d.y0) + ")")
.style("stroke-dasharray", ("1, 1"))
.style("stroke", function(e){
if(d.y0-d.y1 > -r(200) && d.y0 - d.y1 < r(200)){
return "green";
} else {
return "red";
}
})
.attr("class", "halfcirc");
}
svg.selectAll('path.resline').remove();
svg.selectAll('path.halfcirc').remove();
var selection = svg.selectAll('.resline').data(resids)
selection.enter().append('path').transition()
.attr("d", function(d){
if(d.y0-d.y1 < -r(200)) {
return lineFunction([{"x": d.x0, "y": d.y0 + r(200)},{"x": d.x1, "y": d.y1}]);
} else if (d.y0 - d.y1 > r(200)){
return lineFunction([{"x": d.x0, "y": d.y0 - r(200)},{"x": d.x1, "y": d.y1}]);
}
})
.attr("stroke-width", 1)
.attr("stroke", "red")
.attr('class', 'resline')
selection.exit().remove()
selection.each(function(d){
halfcircles(d);
})
return resids;
}
<html>
<head>
<title></title>
<script src="http://d3js.org/d3.v2.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-MfvZlkHCEqatNoGiOXveE8FIwMzZg4W85qfrfIFBfYc= sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="theme.css">
<script src="leastsquares.js"></script>
<script src="d3functions.js"></script>
</head>
<body>
<button id="resid_button" class="btn btn-danger">Residual View</button>
<button id="reset_button" class="btn btn-primary">Reset</button>
</body>
</html>
<script type="text/javascript">
//Global Variables
var data = [];
var resids = [];
//D3 Set up
var width = 500,
height = 500,
margin = 50;
//makes scales
var svg=d3.select("body").append("svg").attr("width",width).attr("height",height);
var x=d3.scale.linear().domain([0,10]).range([margin,width-margin]);
var y=d3.scale.linear().domain([0,10]).range([height-margin,margin]);
var r=d3.scale.linear().domain([0,500]).range([0,20]);
var o=d3.scale.linear().domain([10000,100000]).range([.5,1]);
var c=d3.scale.category10().domain(["Africa","America","Asia","Europe","Oceania"]);
//create axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//draw axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (height - margin) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin + ",0)")
.call(yAxis);
//draw dashed lines
svg.selectAll(".h").data(d3.range(0,10,2)).enter()
.append("line").classed("h",1)
.attr("x1",margin).attr("x2",height-margin)
.attr("y1",y).attr("y2",y)
svg.selectAll(".v").data(d3.range(0,10,2)).enter()
.append("line").classed("v",1)
.attr("y1",margin).attr("y2",width-margin)
.attr("x1",x).attr("x2",x)
var residview = false;
d3.select('#resid_button').on('click', function() {
if ( residview ) {
svg.selectAll('path.resline').remove();
svg.selectAll('path.halfcirc').remove();
svg.selectAll("circle")
.style("opacity", 1)
residview = false;
} else {
svg.selectAll("circle")
.style("opacity", 0)
residview = true;
drawresiduals(data);
}
});
d3.select('#reset_button').on('click', function() {
svg.selectAll('path.resline').remove();
svg.selectAll('path.halfcirc').remove();
svg.selectAll('circle').remove();
svg.selectAll('path').remove();
residview = false;
data = []
resids = []
});
//click event: draw new circle
svg.on('click', function(){
if(d3.mouse(this)[0] > (50 + r(200)) && d3.mouse(this)[0] < (450 - r(200)) && d3.mouse(this)[1] > (50 + r(200)) && d3.mouse(this)[1] < (450 - r(200))){
//push new data point to data array
data.push({"x": d3.mouse(this)[0], "y": d3.mouse(this)[1], "radius": 200, "fill": "Europe", "opacity": 90000});
//select each circle and append the data
var selection = svg.selectAll("circle").data(data)
//update selection and draw new circle
selection.enter()
.append("circle")
.attr("cx",function(d) {return d.x;})
.attr("cy",function(d) {return d.y;})
.attr("r",function(d) {return r(d.radius);})
.style("fill",function(d) {return "green";})
.style("opacity",function(d) {
if(residview){
return 0;
} else {
return o(+d.opacity);
}
})
//exit selection
selection.exit().remove()
if(data.length == 2){
drawline(data);
} else if(data.length > 2){
transitionline(data);
if(residview){
resids = drawresiduals(data);
}
}
}
})
</script>
function LeastSquares(values_x, values_y) {
var sum_x = 0;
var sum_y = 0;
var sum_xy = 0;
var sum_xx = 0;
var count = 0;
/*
* We'll use those variables for faster read/write access.
*/
var x = 0;
var y = 0;
var values_length = values_x.length;
if (values_length != values_y.length) {
throw new Error('The parameters values_x and values_y need to have same size!');
}
/*
* Nothing to do.
*/
if (values_length === 0) {
return [ [], [] ];
}
/*
* Calculate the sum for each of the parts necessary.
*/
for (var v = 0; v < values_length; v++) {
x = values_x[v];
y = values_y[v];
sum_x += x;
sum_y += y;
sum_xx += x*x;
sum_xy += x*y;
count++;
}
/*
* Calculate m and b for the formular:
* y = x * m + b
*/
var m = (count*sum_xy - sum_x*sum_y) / (count*sum_xx - sum_x*sum_x);
var b = (sum_y/count) - (m*sum_x)/count;
return {'b': b, 'm': m};
}
body{
margin:0px;
}
.h,.v{
stroke:black;
stroke-dasharray:4 4;
stroke-width:1;
stroke-opacity:.5;
}
.axis path, .axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
#resid_button {
position: absolute;
top: 55px;
left: 500px;
}
#reset_button {
position: absolute;
top: 95px;
left: 500px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment