Last active
December 16, 2016 03:25
-
-
Save ctlusto/6f681653df8fef16911e9307b3c0c23a to your computer and use it in GitHub Desktop.
Logistic Population Growth
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> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Logistic Population Growth</title> | |
<link rel="stylesheet" href="scrubber.css"> | |
<link rel="stylesheet" href="main.css"> | |
<script src="scrubber.js" charset="utf-8"></script> | |
<script src="//www.desmos.com/api/v0.8/calculator.js?apiKey=dcb31709b452b1cf9dc26972add0fda6"></script> | |
</head> | |
<body> | |
<div class="wrapper"> | |
<div class="slider-container"> | |
<div id="scrubber-left"> | |
<span id="k-label" class="scrubber-label">k=1000</span> | |
</div> | |
<div id="scrubber-right"> | |
<span id="r-label" class="scrubber-label">r=0.90</span> | |
</div> | |
</div> | |
<div id="calculator"></div> | |
</div> | |
<script src="index.js" charset="utf-8"></script> | |
</body> | |
</html> |
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
(function() { | |
// Calculator state | |
var state = { | |
"version": 3, | |
"graph": { | |
"showGrid": false, | |
"showXAxis": true, | |
"showYAxis": true, | |
"xAxisStep": 1, | |
"yAxisStep": 100, | |
"xAxisMinorSubdivisions": 0, | |
"yAxisMinorSubdivisions": 0, | |
"xAxisArrowMode": "NONE", | |
"yAxisArrowMode": "NONE", | |
"xAxisLabel": "", | |
"yAxisLabel": "", | |
"xAxisNumbers": true, | |
"yAxisNumbers": true, | |
"polarMode": false, | |
"polarNumbers": true, | |
"degreeMode": false, | |
"projectorMode": false, | |
"squareAxes": false, | |
"viewport": { | |
"xmin": -1, | |
"ymin": -150, | |
"xmax": 10, | |
"ymax": 1500 | |
} | |
}, | |
"expressions": { | |
"list": [ | |
{ | |
"id": "5", | |
"type": "text", | |
"text": "Population function" | |
}, | |
{ | |
"id": "1", | |
"type": "expression", | |
"latex": "P\\left(t\\right)=\\frac{k}{1+e^{-rt}}", | |
"domain": { | |
"min": "0", | |
"max": "1" | |
}, | |
"label": "", | |
"hidden": false, | |
"secret": false, | |
"color": "#000000", | |
"style": "normal", | |
"dragMode": "AUTO", | |
"residualVariable": "", | |
"regressionParameters": {}, | |
"isLogModeRegression": false | |
}, | |
{ | |
"id": "7", | |
"type": "folder", | |
"title": "Vector field", | |
"memberIds": { | |
"8": true, | |
"9": true, | |
"10": true, | |
"11": true | |
}, | |
"hidden": false, | |
"collapsed": true, | |
"secret": false | |
}, | |
{ | |
"id": "8", | |
"type": "expression", | |
"latex": "P_{prime}\\left(y\\right)=ry\\left(1-\\frac{y}{k}\\right)", | |
"domain": { | |
"min": "0", | |
"max": "1" | |
}, | |
"label": "", | |
"hidden": true, | |
"secret": false, | |
"color": "#000000", | |
"style": "normal", | |
"dragMode": "AUTO", | |
"residualVariable": "", | |
"regressionParameters": {}, | |
"isLogModeRegression": false | |
}, | |
{ | |
"id": "9", | |
"type": "expression", | |
"latex": "i\\ =\\ \\left[0,\\ .5,\\ ...,\\ 10\\right]", | |
"domain": { | |
"min": "0", | |
"max": "1" | |
}, | |
"label": "", | |
"hidden": false, | |
"secret": false, | |
"color": "#c74440", | |
"style": "normal", | |
"dragMode": "AUTO", | |
"residualVariable": "", | |
"regressionParameters": {}, | |
"isLogModeRegression": false | |
}, | |
{ | |
"id": "11", | |
"type": "expression", | |
"latex": "j=\\left[50,\\ 100,\\ ...1500\\right]", | |
"domain": { | |
"min": "0", | |
"max": "1" | |
}, | |
"label": "", | |
"hidden": false, | |
"secret": false, | |
"color": "#388c46", | |
"style": "normal", | |
"dragMode": "AUTO", | |
"residualVariable": "", | |
"regressionParameters": {}, | |
"isLogModeRegression": false | |
}, | |
{ | |
"id": "10", | |
"type": "expression", | |
"latex": "y=P_{prime}\\left(j\\right)\\left(x-\\frac{\\operatorname{floor}\\left(2x\\right)}{2}\\right)\\left\\{x>0\\right\\}\\left\\{x-\\frac{\\operatorname{floor}\\left(2x\\right)}{2}<\\frac{.25}{\\sqrt{1+\\left(\\frac{P_{prime}\\left(j\\right)}{100}\\right)^2}}\\right\\}+j", | |
"domain": { | |
"min": "0", | |
"max": "1" | |
}, | |
"label": "", | |
"hidden": false, | |
"secret": false, | |
"color": "#fa7e19", | |
"style": "normal", | |
"dragMode": "AUTO", | |
"residualVariable": "", | |
"regressionParameters": {}, | |
"isLogModeRegression": false | |
} | |
] | |
} | |
}; | |
// Set up the calculator | |
var elt = document.getElementById('calculator'); | |
var options = { | |
expressions: false, | |
zoomButtons: false, | |
lockViewport: true, | |
settingsMenu: false, | |
branding: false | |
}; | |
var calc = Desmos.GraphingCalculator(elt, options); | |
calc.setState(state); | |
calc.setExpressions([ | |
{ id: 'k', latex: 'k=1000' }, | |
{ id: 'r', latex: 'r=0.9', hidden: true } | |
]); | |
// Set up the labels | |
var kLabelElt = document.getElementById('k-label'); | |
var kLabel = Desmos.MathQuill.StaticMath(kLabelElt); | |
var rLabelElt = document.getElementById('r-label'); | |
var rLabel = Desmos.MathQuill.StaticMath(rLabelElt); | |
// Set up the scrubbers | |
var kElt = document.getElementById('scrubber-left'); | |
var kScrubber = new ScrubberView(); | |
kScrubber.min(300).max(1300).value(1000).step(10); | |
kScrubber.onValueChanged = function(val) { | |
var latex = 'k=' + val; | |
kLabel.latex(latex); | |
calc.setExpression({ id: 'k', latex: latex }); | |
}; | |
kElt.appendChild(kScrubber.elt); | |
var rElt = document.getElementById('scrubber-right'); | |
var rScrubber = new ScrubberView(); | |
rScrubber.min(0.1).max(2).value(0.9).step(0.01); | |
rScrubber.onValueChanged = function(val) { | |
var latex = 'r=' + val.toFixed(2); | |
rLabel.latex(latex); | |
calc.setExpression({ id: 'r', latex: latex }); | |
}; | |
rElt.appendChild(rScrubber.elt); | |
})(); |
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
.wrapper { | |
width: 500px; | |
height: 500px; | |
margin-left: auto; | |
margin-right: auto; | |
margin-top: 60px; | |
} | |
.slider-container { | |
display: flex; | |
-webkit-flex-direction: row; | |
flex-direction: row; | |
height: 90px; | |
border-left: 1px solid #ddd; | |
border-right: 1px solid #ddd; | |
border-top: 1px solid #ddd; | |
} | |
.scrubber { | |
width: 80%; | |
margin-left: auto; | |
margin-right: auto; | |
} | |
.scrubber-label { | |
margin-left: 10%; | |
} | |
#k-label, | |
#r-label { | |
margin-top: 15px; | |
margin-bottom: -10px; | |
} | |
#calculator { | |
padding-right: 2px; | |
height: 100%; | |
} | |
#scrubber-left, | |
#scrubber-right { | |
height: 100%; | |
width: 50% | |
} |
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
.scrubber { | |
margin-top: 10px; | |
width: 200px; | |
height: 40px; | |
position: relative; | |
} | |
.scrubber-vert { | |
margin-left: 10px; | |
width: 40px; | |
height: 200px; | |
position: relative; | |
} | |
.scrubber .track { | |
position: absolute; | |
top: 50%; | |
left: 0px; | |
width: 100%; | |
height: 6px; | |
background: #DDD; | |
border-radius: 3px; | |
margin-top: -3px; | |
} | |
.scrubber-vert .track { | |
position: absolute; | |
top: 0px; | |
height: 100%; | |
left: 50%; | |
width: 6px; | |
background: #DDD; | |
border-radius: 3px; | |
margin-left: -3px; | |
} | |
.scrubber .thumb { | |
-moz-box-sizing: border-box; | |
box-sizing: border-box; | |
position: absolute; | |
top: 50%; | |
left: 0px; | |
width: 22px; | |
height: 22px; | |
margin-left: -11px; | |
margin-top: -11px; | |
cursor: pointer; | |
opacity: 0.7; | |
border: 8px solid #BECFE4; | |
border-radius: 100%; | |
background: #4F81BD; | |
transition: border-width 0.2s ease 0s; | |
} | |
.scrubber-vert .thumb { | |
-moz-box-sizing: border-box; | |
box-sizing: border-box; | |
position: absolute; | |
top: 100%; | |
left: 50%; | |
width: 22px; | |
height: 22px; | |
margin-top: -11px; | |
margin-left: -11px; | |
cursor: pointer; | |
opacity: 0.7; | |
border: 8px solid #BECFE4; | |
border-radius: 100%; | |
background: #4F81BD; | |
transition: border-width 0.2s ease 0s; | |
} | |
.scrubber .thumb:hover, | |
.scrubber-vert .thumb:hover, | |
.thumb.dragging { | |
border-width: 0px; | |
opacity: 1; | |
} | |
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
function ScrubberView() { | |
this.makeAccessors(); | |
this.createDOM(); | |
this.attachListeners(); | |
this.onValueChanged = function () {}; | |
this.onScrubStart = function () {}; | |
this.onScrubEnd = function () {}; | |
} | |
ScrubberView.prototype.makeAccessors = function () { | |
var value = 0; | |
var min = 0; | |
var max = 1; | |
var step = 0; | |
var orientation = 'horizontal'; | |
this.value = function (_value) { | |
if (_value === undefined) return value; | |
if (value === _value) return this; | |
_value = Math.max(min, Math.min(max, _value)); | |
if (step > 0) { | |
var nsteps = Math.round((_value - min)/step); | |
var invStep = 1/step; | |
if (invStep === Math.round(invStep)) { | |
_value = (min*invStep + nsteps)/invStep; | |
} else { | |
_value = (min/step + nsteps)*step; | |
} | |
value = Math.max(min, Math.min(max, _value)); | |
} else { | |
value = _value; | |
} | |
this.redraw(); | |
this.onValueChanged(value); | |
return this; | |
}; | |
this.min = function (_min) { | |
if (_min === undefined) return min; | |
if (min === _min) return this; | |
min = _min; | |
this.redraw(); | |
return this; | |
}; | |
this.max = function (_max) { | |
if (_max === undefined) return max; | |
if (max === _max) return this; | |
max = _max; | |
this.redraw(); | |
return this; | |
}; | |
this.step = function (_step) { | |
if (_step === undefined) return step; | |
if (step === _step) return this; | |
step = _step; | |
this.redraw(); | |
return this; | |
}; | |
this.orientation = function(_orientation) { | |
if (_orientation === undefined) return orientation; | |
if (_orientation === orientation) return this; | |
orientation = _orientation; | |
this.redraw(); | |
return this; | |
}; | |
}; | |
ScrubberView.prototype.createDOM = function () { | |
this.elt = document.createElement('div'); | |
this.track = document.createElement('div'); | |
this.thumb = document.createElement('div'); | |
this.elt.className = this.orientation() === 'horizontal' ? 'scrubber' : 'scrubber-vert'; | |
this.track.className = 'track'; | |
this.thumb.className = 'thumb'; | |
this.elt.appendChild(this.track); | |
this.elt.appendChild(this.thumb); | |
}; | |
ScrubberView.prototype.redraw = function () { | |
var frac = (this.value() - this.min())/(this.max() - this.min()); | |
if (this.orientation() === 'horizontal') { | |
this.elt.className = 'scrubber'; | |
this.thumb.style.top = '50%'; | |
this.thumb.style.left = frac*100 + '%'; | |
} | |
else { | |
this.elt.className = 'scrubber-vert'; | |
this.thumb.style.left = '50%'; | |
this.thumb.style.top = 100 - (frac*100) + '%'; | |
} | |
}; | |
ScrubberView.prototype.attachListeners = function () { | |
var self = this; | |
var mousedown = false; | |
var cachedLeft; | |
var cachedWidth; | |
var cachedTop; | |
var cachedHeight; | |
var start = function (evt) { | |
evt.preventDefault(); | |
self.onScrubStart(self.value()); | |
mousedown = true; | |
var rect = self.elt.getBoundingClientRect(); | |
// NOTE: page[X|Y]Offset and the width and height | |
// properties of getBoundingClientRect are not | |
// supported in IE8 and below. | |
// | |
// Scrubber doesn't attempt to support IE<9. | |
var xOffset = window.pageXOffset; | |
var yOffset = window.pageYOffset; | |
cachedLeft = rect.left + xOffset; | |
cachedWidth = rect.width; | |
cachedTop = rect.top + yOffset; | |
cachedHeight = rect.height; | |
self.thumb.className += ' dragging'; | |
}; | |
var stop = function () { | |
mousedown = false; | |
cachedLeft = undefined; | |
cachedWidth = undefined; | |
cachedTop = undefined; | |
cachedHeight = undefined; | |
self.thumb.className = 'thumb'; | |
self.onScrubEnd(self.value()); | |
}; | |
var setValueFromPageX = function (pageX) { | |
var frac = Math.min(1, Math.max((pageX - cachedLeft)/cachedWidth, 0)); | |
self.value((1-frac)*self.min() + frac*self.max()); | |
}; | |
var setValueFromPageY = function (pageY) { | |
var frac = Math.min(1, Math.max(1 - (pageY - cachedTop)/cachedHeight, 0)); | |
self.value((1-frac)*self.min() + frac*self.max()); | |
}; | |
this.elt.addEventListener('mousedown', start); | |
this.elt.addEventListener('touchstart', start); | |
document.addEventListener('mousemove', function (evt) { | |
if (!mousedown) return; | |
evt.preventDefault(); | |
if (self.orientation() === 'horizontal') | |
setValueFromPageX(evt.pageX); | |
else | |
setValueFromPageY(evt.pageY); | |
}); | |
document.addEventListener('touchmove', function (evt) { | |
if (!mousedown) return; | |
evt.preventDefault(); | |
if (self.orientation() === 'horizontal') | |
setValueFromPageX(evt.changedTouches[0].pageX); | |
else | |
setValueFromPageY(evt.changedTouches[0].pageY); | |
}); | |
this.elt.addEventListener('mouseup', function (evt) { | |
if (!mousedown) return; | |
evt.preventDefault(); | |
if (self.orientation() === 'horizontal') | |
setValueFromPageX(evt.pageX); | |
else | |
setValueFromPageY(evt.pageY); | |
}); | |
this.elt.addEventListener('touchend', function (evt) { | |
if (!mousedown) return; | |
evt.preventDefault(); | |
if (self.orientation() === 'horizontal') | |
setValueFromPageX(evt.changedTouches[0].pageX); | |
else | |
setValueFromPageY(evt.changedTouches[0].pageY); | |
}); | |
document.addEventListener('mouseup', stop); | |
document.addEventListener('touchend', stop); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment