Skip to content

Instantly share code, notes, and snippets.

@zanarmstrong
Last active June 28, 2018 19:28
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 zanarmstrong/204bb6fe2819b187b689dff52eb01c41 to your computer and use it in GitHub Desktop.
Save zanarmstrong/204bb6fe2819b187b689dff52eb01c41 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Anonymous+Pro|Roboto+Condensed" rel="stylesheet">
<style>
body {
position: relative;
font-family: 'Roboto Condensed', sans-serif;
color: #666666;
}
.data {
font-family: 'Anonymous Pro', monospace;
}
img,
.points {
position: absolute;
left: 300px;
top: 0px;
}
.points {
z-index: 10;
}
#settingScaleInputEle {
position: absolute;
background: white;
z-index: 20;
}
#instructions {
max-width: 300px;
}
#modesetting {
max-width: 300px;
margin: 0px;
}
#dataOutput {
top: 75px;
max-width: 300px;
}
.mode {
cursor: pointer;
text-decoration: underline;
}
.hidden {
display: none;
}
.selectedMode {
font-weight: 900;
color: black;
}
.regression {
width: 290px;
height: 290px;
}
</style>
</head>
<p id="instructions">First, click on points that you know the value of to set the scale. Then switch to "adding points mode" to get the data associated with those points.</p>
<!--<img id="sourceimage" src="https://gist.githubusercontent.com/zanarmstrong/204bb6fe2819b187b689dff52eb01c41/raw/0b4fcfe2c2b5e34d2e58a89b91fc386f7122846f/priceofcottonLarge.jpg">-->
<img id="sourceimage" src="totalpop.png">
<svg class="points"></svg>
<p id="modesetting">MODE: <span class="mode" id="settingScale">Setting Scale</span> or <span class="mode" id="addingDataPoints">Adding Data Points</span></p>
<!--
<svg class="regression x"></svg>
<svg class="regression y"></svg>
-->
<div id="dataOutput"></div>
<div id='settingScaleInputEle' class="hidden" style="width:110px;padding:10px">
<form style='height:75px;border:0;padding:0;margin:5'>
x value <input id="x" type='text' style='width:50px;height:18px;margin:2px;padding:2px;border:solid 1px'></input>
<br>
y value <input id="y" type='text' style='width:50px;height:18px;margin:2px;padding:2px;border:solid 1px'></input>
<br>
<div>
<span class="button" onclick="updateScale()">
<button type="button">enter</button>
</span>
<span class="button" onclick="clearSettingScaleInput()">
<button type="button">clear</button>
</span>
</div>
</form>
<div>
<body>
<script>
// type will determine if there is rounding to nearest integer
var xType = "float";
var yType = "int";
function adjustByType(v,type) {
if(type == "int"){
return Math.round(v);
} else {
return v;
}
}
// what type of function to use for regression mapping
var xFunc = "linear";
var yFunc = "linear";
/////////////////////////////
// setting up mode toggle
/////////////////////////////
var settingScale = true;
function updateMode(modeIsScale){
d3.selectAll("#settingScale").classed("selectedMode", modeIsScale);
d3.selectAll("#addingDataPoints").classed("selectedMode", !modeIsScale);
}
updateMode(settingScale);
d3.selectAll(".mode").on("click", function(){
if(this.classList[0].indexOf("selectedMode") == -1){
toggleMode();
}
});
function toggleMode(){
settingScale = !settingScale;
updateMode(settingScale);
}
// SET UP DATA STRUCTURES
function regressionLine(params){
return function(pos) {
return params.m * (pos) + params.b;
};
}
var currentPos = [0,0];
// when a user clicks in setting scale mode, the xPosition and the x value they input will be stored as a pair
// same for y.
// these will be used to calculate regression lines
var settingScaleInput = {
x: {
posValuePairs: [],
func: regressionLine(1,0)
},
y: {
posValuePairs: [],
func: regressionLine(1,0)
}
};
// this is the set of positions selected by the user
// each element in the array is a pair of x/y mouse positions
var pointPositionData = [];
// this is the data calculated from the pointPositionData
function convertPositionToData(positions, xFunc, yFunc) {
return positions.map(function(coord){
return [adjustByType(xFunc(coord[0]), xType),
adjustByType(yFunc(coord[1]), yType)];
});
}
// get the image
var imageObj = document.getElementById("sourceimage");
var width = imageObj.width,
height = imageObj.height;
d3.select(".regression .x")
var svg = d3.selectAll(".points")
.attr("width", width)
.attr("height", height)
.attr("class", "points")
.on("click", function(){
currentPos = d3.mouse(this);
// SETTING SCALE MODE
if(settingScale){
// add circle to SVG at selected point
d3.selectAll(".points").append("circle")
.attr("cx", currentPos[0])
.attr("cy", currentPos[1])
.attr("r", 3)
.attr("fill", "red");
// show input box near selected point
d3.selectAll("#settingScaleInputEle").style("top", (d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 25) + "px");
d3.selectAll("#settingScaleInputEle").classed("hidden", false);
} else {
// add circle to SVG at selected point
d3.selectAll(".points").append("circle")
.attr("cx", currentPos[0])
.attr("cy", currentPos[1])
.attr("r", 3)
.attr("fill", "blue");
// add positions to pointPositionData
pointPositionData.push(currentPos);
prettyPrintData(convertPositionToData(pointPositionData, settingScaleInput.x.func, settingScaleInput.y.func));
}
});
function updateScale() {
updateScaleValues([+document.getElementById('x').value,
+document.getElementById('y').value]);
clearInputForm();
}
function prettyPrintData(data) {
var csv = data.map(function(d){
return d.join();
}).join('\n');
console.log(csv);
d3.selectAll(".dataoutputtext").remove().exit();
d3.selectAll("#dataOutput").append("p").attr("class", "dataoutputtext").text(csv);
}
function updateScaleValues(currentValues) {
var iter = 0;
for(var value of ['x','y']){
var axis = settingScaleInput[value];
// add point to data
axis.posValuePairs.push([currentPos[iter], currentValues[iter]]);
// update regression line function
axis.func = regressionLine(linearRegression(axis.posValuePairs));
// update regression line viz
// update iterator
iter += 1;
}
// pretty print data to screen
// TODO(zan): handle for if there is no data, etc. Or fewer than 2 setting scale inputs
prettyPrintData(convertPositionToData(pointPositionData, settingScaleInput.x.func, settingScaleInput.y.func));
}
function clearSettingScaleInput() {
clearInputForm();
}
function clearInputForm(){
document.getElementById('x').value = "";
document.getElementById('y').value = "";
d3.selectAll("#settingScaleInputEle").classed("hidden", true);
}
// from https://unpkg.com/simple-statistics@6.0.1/dist/simple-statistics.js
function linearRegression(data/*: Array<Array<number>> */)/*: { m: number, b: number } */ {
var m, b;
// Store data length in a local variable to reduce
// repeated object property lookups
var dataLength = data.length;
//if there's only one point, arbitrarily choose a slope of 0
//and a y-intercept of whatever the y of the initial point is
if (dataLength === 1) {
m = 0;
b = data[0][1];
} else {
// Initialize our sums and scope the `m` and `b`
// variables that define the line.
var sumX = 0, sumY = 0,
sumXX = 0, sumXY = 0;
// Use local variables to grab point values
// with minimal object property lookups
var point, x, y;
// Gather the sum of all x values, the sum of all
// y values, and the sum of x^2 and (x*y) for each
// value.
//
// In math notation, these would be SS_x, SS_y, SS_xx, and SS_xy
for (var i = 0; i < dataLength; i++) {
point = data[i];
x = point[0];
y = point[1];
sumX += x;
sumY += y;
sumXX += x * x;
sumXY += x * y;
}
// `m` is the slope of the regression line
m = ((dataLength * sumXY) - (sumX * sumY)) /
((dataLength * sumXX) - (sumX * sumX));
// `b` is the y-intercept of the line.
b = (sumY / dataLength) - ((m * sumX) / dataLength);
}
// Return both values as an object.
return {
m: m,
b: b
};
}
</script>
</body>
1825 13.304487966471697
1826 9.223811054662434
1827 8.22446160687241
1828 9.223811054662434
1829 8.141182486223244
1830 8.141182486223244
1831 7.225112159082386
1832 7.141833038433219
1833 9.057252813364101
1834 10.139881381803292
1835 15.219907741402551
1836 12.221859398032507
1837 7.058553917784053
1838 9.057252813364101
1839 11.222509950242483
1840 7.974624244924911
1841 9.057252813364101
1842 6.975274797134887
1843 4.976575901554838
1844 5.059855022204005
1845 4.06050557441398
1846 5.9759253493448625
1847 6.89199567648572
1848 4.893296780905672
1849 5.9759253493448625
1850 10.889393467645789
1851 7.808066003626578
1852 7.891345124275745
1853 9.890044019855793
1854 7.724786882977412
1855 6.725437435187388
1856 8.640857210118241
1857 12.888092363225837
1858 8.807415451416603
1859 10.806114346996623
1860 9.723485778557432
1861 10.806114346996623
1862 19.716980256457646
1863 53.94469884326588
1864 71.59987242088957
1865 32.70852307772793
1866 31.709173629937908
1867 14.80351213815672
1868 15.802861585946744
1869 24.713727495407767
1870 14.720233017507553
1871 14.720233017507553
1872 17.718281360877626
1873 12.804813242576671
1874 14.636953896858387
1875 12.638255001278338
1876 10.63955610569829
1877 10.889393467645789
1878 8.640857210118241
1879 8.973973692714935
1880 10.722835226347456
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment