Built with blockbuilder.org
Last active
December 11, 2019 19:49
-
-
Save jtr13/12f45dd763b2b390ec48d6ef485a2b45 to your computer and use it in GitHub Desktop.
Best fitting line
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
license: mit |
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
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
td {padding: 20px;} | |
h4 {color: #0072B2;} | |
div {padding-left: 40px;} | |
</style> | |
</head> | |
<body> | |
<div style="width: 400px"> | |
<h3>Estimate the best fitting line</h3> | |
<p>Drag the endpoints of the blue line to estimate the best fitting line through the data points. Then click the button to see how you did.</p> | |
</div> | |
<div id="chart" style="width: 400px; float: left;"> | |
</div> | |
<div style = "width: 200px; float: left"> | |
<button type="button" onclick="bestfit()">Best fitting line</button> | |
<h4 id="bestline"> </h4> | |
</div> | |
<script type="text/javascript"> | |
//Width and height of svg | |
var w = 400; | |
var h = 300; | |
var padding = 30; | |
// axis min / max | |
var xmin = -40; | |
var xmax = 40; | |
var ymin = -30; | |
var ymax = 30; | |
// colors | |
var backgroundcolor = "#F4F4F4"; | |
// https://rdrr.io/cran/ggthemes/man/colorblind.html | |
var circlecolor = "#CC79A7"; | |
var dragcolor = "#56B4E9"; | |
var bestlinecolor = "#0072B2"; | |
// Scale functions | |
var xScale = d3.scaleLinear() | |
.domain([xmin, xmax]) | |
.range([padding, w - padding * 2]); | |
var yScale = d3.scaleLinear() | |
.domain([ymin, ymax]) | |
.range([h - padding, padding]); | |
//Define X axis | |
var xAxis = d3.axisBottom() | |
.scale(xScale) | |
.ticks(5); | |
//Define Y axis | |
var yAxis = d3.axisLeft() | |
.scale(yScale) | |
.ticks(5); | |
//Define line generator | |
var mylinegen = d3.line() | |
.x(d => xScale(d[0])) | |
.y(d => yScale(d[1])); | |
//Create SVG element | |
var svg = d3.select("#chart") | |
.append("svg") | |
.attr("width", w) | |
.attr("height", h); | |
svg.append("rect") | |
.attr("width", w) | |
.attr("height", h) | |
.attr("fill", backgroundcolor); | |
//Create X axis | |
svg.append("g") | |
.attr("transform", `translate(0, ${yScale(0)})`) | |
.call(xAxis); | |
//Create Y axis | |
svg.append("g") | |
.attr("transform", `translate(${xScale(0)}, 0)`) | |
.call(yAxis); | |
var m = d3.randomUniform(2)()-1; | |
var myrandom = d3.randomNormal(0, 15); | |
var n = 100; | |
var xcoord = d3.range(n).map(d => myrandom()); | |
var dataset = xcoord.map(d => [d, m*d + (1-m)*myrandom()]); | |
// starting endpoints of draggable line | |
var endpoints = [[-40, 0], [40, 0]]; | |
// add circles | |
svg.selectAll("circle") | |
.data(dataset) | |
.enter() | |
.append("circle") | |
.classed("points", true) | |
.attr("cx", d => xScale(d[0])) | |
.attr("cy", d => yScale(d[1])) | |
.attr("r", "3") | |
.attr("fill", circlecolor); | |
// add draggable line | |
var dragpath = mylinegen(endpoints); | |
d3.select("svg") | |
.append("path") | |
.attr("id", "dragline") | |
.attr("d", dragpath) | |
.attr("stroke", dragcolor) | |
.attr("stroke-width", "3"); | |
// add draggable endpoints | |
d3.select("svg") | |
.selectAll("circle.endpoint") | |
.data(endpoints) | |
.enter() | |
.append("circle") | |
.attr("class", "endpoint") | |
.attr("cx", d => xScale(d[0])) | |
.attr("cy", d => yScale(d[1])) | |
.attr("r", "4") | |
.attr("fill", dragcolor) | |
.call(d3.drag() | |
.on("drag", dragged)); | |
function dragged(d) { | |
var new_x = xScale.invert(d3.event.x); | |
var new_y = yScale.invert(d3.event.y); | |
d3.select(this).data([[new_x, new_y]]) | |
.attr("cx", d => xScale(d[0])) | |
.attr("cy", d => yScale(d[1])); | |
var enddata = d3.selectAll("circle.endpoint").data(); | |
var dragpath = mylinegen(enddata); | |
d3.select("path#dragline").attr("d", dragpath); | |
}; | |
var bestfit = function() { | |
// get data from circles | |
var data = d3.selectAll("circle.points").data(); | |
// calculate slope and intercept | |
x = data.map(d => d[0]); | |
y = data.map(d => d[1]); | |
Sxx = d3.sum(x.map(d => Math.pow(d-d3.mean(x), 2))); | |
Sxy = d3.sum(x.map( (d, i) => (x[i]-d3.mean(x))*(y[i]-d3.mean(y)))); | |
b1 = Sxy/Sxx; | |
b0 = d3.mean(y) - d3.mean(x)*b1; | |
// calculate two points of line for plotting | |
var y1 = b0 + b1*xmin; | |
var y2 = b0 + b1*xmax; | |
var mypath = mylinegen([[xmin, y1], [xmax, y2]]); | |
// add best fitting line | |
d3.select("svg") | |
.append("path") | |
.attr("d", mypath) | |
.attr("stroke", bestlinecolor) | |
.attr("stroke-width", "3"); | |
// add equation of the line | |
if (b0 > 0) { | |
var b = `+ ${b0.toFixed(2)}` | |
} else { | |
var b = `- ${Math.abs(b0).toFixed(2)}` | |
} | |
d3.select("#bestline") | |
.text(`y = ${b1.toFixed(2)}x ${b}`); | |
}; | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment