Click on the graph to draw points. A line of best fit will dynamically update to the new data points.
Last active
November 21, 2015 18:19
-
-
Save easadler/edae96ae440aa0361a4d to your computer and use it in GitHub Desktop.
Interactive Linear Regression
This file contains 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
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; | |
} |
This file contains 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
<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> | |
This file contains 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
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}; | |
} |
This file contains 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
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