Built with blockbuilder.org
Inspirations:
Plotting a trendline with d3js
Basic Data Update & Linear Regression
Linear Regression for Scatter Plot
This version has JSON data inline.
license: mit |
Built with blockbuilder.org
Inspirations:
Plotting a trendline with d3js
Basic Data Update & Linear Regression
Linear Regression for Scatter Plot
This version has JSON data inline.
[{"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> |