Skip to content

Instantly share code, notes, and snippets.

@jalapic
Last active December 30, 2016 00:10
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 jalapic/18b997f9b1025b21470d058b918421b9 to your computer and use it in GitHub Desktop.
Save jalapic/18b997f9b1025b21470d058b918421b9 to your computer and use it in GitHub Desktop.
scatter - interactive regression
license: mit
[{"name":"setosa","x":5.1,"y":3.5},{"name":"setosa","x":4.9,"y":3},{"name":"setosa","x":4.7,"y":3.2},{"name":"setosa","x":4.6,"y":3.1},{"name":"setosa","x":5,"y":3.6},{"name":"setosa","x":5.4,"y":3.9},{"name":"setosa","x":4.6,"y":3.4},{"name":"setosa","x":5,"y":3.4},{"name":"setosa","x":4.4,"y":2.9},{"name":"setosa","x":4.9,"y":3.1},{"name":"setosa","x":5.4,"y":3.7},{"name":"setosa","x":4.8,"y":3.4},{"name":"setosa","x":4.8,"y":3},{"name":"setosa","x":4.3,"y":3},{"name":"setosa","x":5.8,"y":4},{"name":"setosa","x":5.7,"y":4.4},{"name":"setosa","x":5.4,"y":3.9},{"name":"setosa","x":5.1,"y":3.5},{"name":"setosa","x":5.7,"y":3.8},{"name":"setosa","x":5.1,"y":3.8},{"name":"setosa","x":5.4,"y":3.4},{"name":"setosa","x":5.1,"y":3.7},{"name":"setosa","x":4.6,"y":3.6},{"name":"setosa","x":5.1,"y":3.3},{"name":"setosa","x":4.8,"y":3.4},{"name":"setosa","x":5,"y":3},{"name":"setosa","x":5,"y":3.4},{"name":"setosa","x":5.2,"y":3.5},{"name":"setosa","x":5.2,"y":3.4},{"name":"setosa","x":4.7,"y":3.2},{"name":"setosa","x":4.8,"y":3.1},{"name":"setosa","x":5.4,"y":3.4},{"name":"setosa","x":5.2,"y":4.1},{"name":"setosa","x":5.5,"y":4.2},{"name":"setosa","x":4.9,"y":3.1},{"name":"setosa","x":5,"y":3.2},{"name":"setosa","x":5.5,"y":3.5},{"name":"setosa","x":4.9,"y":3.6},{"name":"setosa","x":4.4,"y":3},{"name":"setosa","x":5.1,"y":3.4},{"name":"setosa","x":5,"y":3.5},{"name":"setosa","x":4.5,"y":2.3},{"name":"setosa","x":4.4,"y":3.2},{"name":"setosa","x":5,"y":3.5},{"name":"setosa","x":5.1,"y":3.8},{"name":"setosa","x":4.8,"y":3},{"name":"setosa","x":5.1,"y":3.8},{"name":"setosa","x":4.6,"y":3.2},{"name":"setosa","x":5.3,"y":3.7},{"name":"setosa","x":5,"y":3.3},{"name":"versicolor","x":7,"y":3.2},{"name":"versicolor","x":6.4,"y":3.2},{"name":"versicolor","x":6.9,"y":3.1},{"name":"versicolor","x":5.5,"y":2.3},{"name":"versicolor","x":6.5,"y":2.8},{"name":"versicolor","x":5.7,"y":2.8},{"name":"versicolor","x":6.3,"y":3.3},{"name":"versicolor","x":4.9,"y":2.4},{"name":"versicolor","x":6.6,"y":2.9},{"name":"versicolor","x":5.2,"y":2.7},{"name":"versicolor","x":5,"y":2},{"name":"versicolor","x":5.9,"y":3},{"name":"versicolor","x":6,"y":2.2},{"name":"versicolor","x":6.1,"y":2.9},{"name":"versicolor","x":5.6,"y":2.9},{"name":"versicolor","x":6.7,"y":3.1},{"name":"versicolor","x":5.6,"y":3},{"name":"versicolor","x":5.8,"y":2.7},{"name":"versicolor","x":6.2,"y":2.2},{"name":"versicolor","x":5.6,"y":2.5},{"name":"versicolor","x":5.9,"y":3.2},{"name":"versicolor","x":6.1,"y":2.8},{"name":"versicolor","x":6.3,"y":2.5},{"name":"versicolor","x":6.1,"y":2.8},{"name":"versicolor","x":6.4,"y":2.9},{"name":"versicolor","x":6.6,"y":3},{"name":"versicolor","x":6.8,"y":2.8},{"name":"versicolor","x":6.7,"y":3},{"name":"versicolor","x":6,"y":2.9},{"name":"versicolor","x":5.7,"y":2.6},{"name":"versicolor","x":5.5,"y":2.4},{"name":"versicolor","x":5.5,"y":2.4},{"name":"versicolor","x":5.8,"y":2.7},{"name":"versicolor","x":6,"y":2.7},{"name":"versicolor","x":5.4,"y":3},{"name":"versicolor","x":6,"y":3.4},{"name":"versicolor","x":6.7,"y":3.1},{"name":"versicolor","x":6.3,"y":2.3},{"name":"versicolor","x":5.6,"y":3},{"name":"versicolor","x":5.5,"y":2.5},{"name":"versicolor","x":5.5,"y":2.6},{"name":"versicolor","x":6.1,"y":3},{"name":"versicolor","x":5.8,"y":2.6},{"name":"versicolor","x":5,"y":2.3},{"name":"versicolor","x":5.6,"y":2.7},{"name":"versicolor","x":5.7,"y":3},{"name":"versicolor","x":5.7,"y":2.9},{"name":"versicolor","x":6.2,"y":2.9},{"name":"versicolor","x":5.1,"y":2.5},{"name":"versicolor","x":5.7,"y":2.8},{"name":"virginica","x":6.3,"y":3.3},{"name":"virginica","x":5.8,"y":2.7},{"name":"virginica","x":7.1,"y":3},{"name":"virginica","x":6.3,"y":2.9},{"name":"virginica","x":6.5,"y":3},{"name":"virginica","x":7.6,"y":3},{"name":"virginica","x":4.9,"y":2.5},{"name":"virginica","x":7.3,"y":2.9},{"name":"virginica","x":6.7,"y":2.5},{"name":"virginica","x":7.2,"y":3.6},{"name":"virginica","x":6.5,"y":3.2},{"name":"virginica","x":6.4,"y":2.7},{"name":"virginica","x":6.8,"y":3},{"name":"virginica","x":5.7,"y":2.5},{"name":"virginica","x":5.8,"y":2.8},{"name":"virginica","x":6.4,"y":3.2},{"name":"virginica","x":6.5,"y":3},{"name":"virginica","x":7.7,"y":3.8},{"name":"virginica","x":7.7,"y":2.6},{"name":"virginica","x":6,"y":2.2},{"name":"virginica","x":6.9,"y":3.2},{"name":"virginica","x":5.6,"y":2.8},{"name":"virginica","x":7.7,"y":2.8},{"name":"virginica","x":6.3,"y":2.7},{"name":"virginica","x":6.7,"y":3.3},{"name":"virginica","x":7.2,"y":3.2},{"name":"virginica","x":6.2,"y":2.8},{"name":"virginica","x":6.1,"y":3},{"name":"virginica","x":6.4,"y":2.8},{"name":"virginica","x":7.2,"y":3},{"name":"virginica","x":7.4,"y":2.8},{"name":"virginica","x":7.9,"y":3.8},{"name":"virginica","x":6.4,"y":2.8},{"name":"virginica","x":6.3,"y":2.8},{"name":"virginica","x":6.1,"y":2.6},{"name":"virginica","x":7.7,"y":3},{"name":"virginica","x":6.3,"y":3.4},{"name":"virginica","x":6.4,"y":3.1},{"name":"virginica","x":6,"y":3},{"name":"virginica","x":6.9,"y":3.1},{"name":"virginica","x":6.7,"y":3.1},{"name":"virginica","x":6.9,"y":3.1},{"name":"virginica","x":5.8,"y":2.7},{"name":"virginica","x":6.8,"y":3.2},{"name":"virginica","x":6.7,"y":3.3},{"name":"virginica","x":6.7,"y":3},{"name":"virginica","x":6.3,"y":2.5},{"name":"virginica","x":6.5,"y":3},{"name":"virginica","x":6.2,"y":3.4},{"name":"virginica","x":5.9,"y":3}]
<html>
<head>
<h3>Interactive Scatterplot</h3>
<style>
body {
margin: 0 auto;
margin-top: 1em;
display: table;
font-family: "Helvetica Neue", sans-serif;
}
svg {
padding-bottom: 16px;
}
.regression {
stroke-width: 2px;
stroke: red;
stroke-dasharray: 10,5;
}
.equation {
font-size: 12px;
margin-top: 5px;
text-align: center;
}
.axistext {
font-size: 14px;
}
#selector {
margin-left: 50px;
}
</style>
</head>
<body>
<div><select id="selector"></select></div>
<div class="chart"></div>
<div class="equation"></div>
<div class="equation"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// setup
var margin = {top: 33, right: 5, bottom: 20, left: 50},
width = 450 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
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 + ")");
var x = d3.scaleLinear()
.range([0,width]);
var y = d3.scaleLinear()
.range([height,0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y);
//// data comes in...............
d3.json("data.json", function(error, data){
// add basic axes, all points, calculate scales
y.domain(d3.extent(data, function(d){ return d.y}));
x.domain(d3.extent(data, function(d){ return d.x}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".point")
.data(data)
.enter().append("circle")
.attr("class", "point")
.attr("r", 4)
.attr("cy", function(d){ return y(d.y); })
.attr("cx", function(d){ return x(d.x); })
;
// axis title variables
var yaxistext = "Sepal width"
var xaxistext = "Sepal length"
// text label for the x axis
svg.append("text")
.attr("class", "axistext")
.attr("transform",
"translate(" + (width - margin.left) + " ," +
(height + margin.top) + ")")
.style("text-anchor", "middle")
.text(xaxistext);
// text label for the y axis
svg.append("text")
.attr("class", "axistext")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text(yaxistext);
// Group/filter by selector............
var names = d3.nest()
.key(function(d){ return d.name;}) // group by name
.rollup(function(a) { return a.length}) // aggregation on array
.entries(data);
//add all
names.unshift({"key": "ALL",
"value": d3.sum(names,function(d) {return d.value;})})
// add dropdown options to selector
var selector= d3.select("#selector"); // selector
selector
.selectAll("option")
.data(names)
.enter()
.append("option")
.text(function(d) { return d.key + ": " + d.value;})
.attr("value", function(d){ return d.key;})
// Calculate R2, slope, add trendline
var xSeries = data.map(function(e) { return e.x; }); // minimum of all x
var ySeries = data.map(function(e) { return e.y; }); // minimum of all y
var rsq = leastSquares(xSeries,ySeries); // do lin.reg. calculations
// First Equation - slope
document.getElementsByClassName("equation")[0].innerHTML = "y = " + round(rsq[0] ,2) + "x + " + round(rsq[1] ,2);
// Second Equation - R2
document.getElementsByClassName("equation")[1].innerHTML = "R<sup>2</sup> = " + round(rsq[2],2);
// Add trendline
ptAx = d3.min(xSeries);
ptAy = rsq[0] * d3.min(xSeries) + rsq[1];
ptBy = d3.min(ySeries);
ptBx = (d3.min(ySeries) - rsq[1]) / rsq[0];
svg.append("line")
.attr("class", "regression")
.attr("x1", x(ptAx))
.attr("y1", y(ptAy))
.attr("x2", x(ptBx))
.attr("y2", y(ptBy))
;
// Dynamically alter when dropdown menu changes ......
selector.on("change", function(){
d3.selectAll("line.regression").remove(); // remove preexisting lines
// Change points
d3.selectAll(".point")
.attr("opacity", 1);
var value = selector.property("value");
if(value!="ALL") {
d3.selectAll(".point")
.filter(function(d) { return d.name != value; })
.attr("opacity",0.1);
filteredData = filterJSON(data, 'name', value);
var xSeries1 = filteredData.map(function(e) { return e.x; });
var ySeries1 = filteredData.map(function(e) { return e.y; });
var rsq1 = leastSquares(xSeries1,ySeries1);
// First Equation - slope
document.getElementsByClassName("equation")[0].innerHTML = "y = " + round(rsq1[0] ,2) + "x + " + round(rsq1[1] ,2);
// Second Equation - R2
document.getElementsByClassName("equation")[1].innerHTML = "R<sup>2</sup> = " + round(rsq1[2],2);
// Add trendline
ptAx1 = d3.min(xSeries1);
ptAy1 = rsq1[0] * d3.min(xSeries1) + rsq1[1];
ptBy1 = d3.max(ySeries1);
ptBx1 = (d3.max(ySeries1) - rsq1[1]) / rsq1[0];
svg.append("line")
.attr("class", "regression")
.attr("x1", x(ptAx1))
.attr("y1", y(ptAy1))
.attr("x2", x(ptBx1))
.attr("y2", y(ptBy1));
} // end of if value!=ALL loop
// re-add equations + trendline if ALL re-selected:
if(value==="ALL") {
// First Equation - slope
document.getElementsByClassName("equation")[0].innerHTML = "y = " + round(rsq[0] ,2) + "x + " + round(rsq[1] ,2);
// Second Equation - R2
document.getElementsByClassName("equation")[1].innerHTML = "R<sup>2</sup> = " + round(rsq[2],2);
svg.append("line")
.attr("class", "regression")
.attr("x1", x(ptAx))
.attr("y1", y(ptAy))
.attr("x2", x(ptBx))
.attr("y2", y(ptBy));
} // end of if value=ALL loop
}) // end of on.change function
}); // csv/json fn end
/// FUNCTIONS..................................................
// round decimals
function round(value, decimals) {
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}
// filter JSON data
function filterJSON(json, key, value) {
var result = [];
for (var indicator in json) {
if (json[indicator][key] === value) {
result.push(json[indicator]);
}
}
return result;
}
// calculate linear regression
function leastSquares(xSeries,ySeries) {
var reduceSumFunc = function(prev, cur) { return prev + cur; };
var xBar = xSeries.reduce(reduceSumFunc) * 1.0 / xSeries.length;
var yBar = ySeries.reduce(reduceSumFunc) * 1.0 / ySeries.length;
var ssXX = xSeries.map(function(d) { return Math.pow(d - xBar, 2); })
.reduce(reduceSumFunc);
var ssYY = ySeries.map(function(d) { return Math.pow(d - yBar, 2); })
.reduce(reduceSumFunc);
var ssXY = xSeries.map(function(d, i) { return (d - xBar) * (ySeries[i] - yBar); })
.reduce(reduceSumFunc);
var slope = ssXY / ssXX;
var intercept = yBar - (xBar * slope);
var rSquare = Math.pow(ssXY, 2) / (ssXX * ssYY);
return [slope, intercept, rSquare];
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment