Skip to content

Instantly share code, notes, and snippets.

@jtr13
Last active December 11, 2019 19:49
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 jtr13/12f45dd763b2b390ec48d6ef485a2b45 to your computer and use it in GitHub Desktop.
Save jtr13/12f45dd763b2b390ec48d6ef485a2b45 to your computer and use it in GitHub Desktop.
Best fitting line
license: mit
<!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">&nbsp;</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