Skip to content

Instantly share code, notes, and snippets.

@ctlusto
Last active December 16, 2016 07:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ctlusto/d8a7ff065ccf1c724e2d802481342f0a to your computer and use it in GitHub Desktop.
Save ctlusto/d8a7ff065ccf1c724e2d802481342f0a to your computer and use it in GitHub Desktop.
Exponential Population Growth
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Exponential 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="p-label" class="scrubber-label">P_0=57</span>
</div>
<div id="scrubber-right">
<span id="r-label" class="scrubber-label">r=0.46</span>
</div>
</div>
<div id="calculator"></div>
</div>
<script src="index.js" charset="utf-8"></script>
</body>
</html>
(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": -100,
"xmax": 10,
"ymax": 1000
}
},
"expressions": {
"list": [
{
"id": "5",
"type": "text",
"text": "Population function"
},
{
"id": "1",
"type": "expression",
"latex": "P\\left(t\\right)=P_0e^{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)=r\\cdot y",
"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,\\ ...1000\\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: 'p', latex: 'P_0=57' },
{ id: 'r', latex: 'r=0.46', hidden: true }
]);
// Set up the labels
var pLabelElt = document.getElementById('p-label');
var pLabel = Desmos.MathQuill.StaticMath(pLabelElt);
var rLabelElt = document.getElementById('r-label');
var rLabel = Desmos.MathQuill.StaticMath(rLabelElt);
// Set up the scrubbers
var pElt = document.getElementById('scrubber-left');
var pScrubber = new ScrubberView();
pScrubber.min(1).max(200).value(57).step(1);
pScrubber.onValueChanged = function(val) {
var latex = 'P_0=' + val;
pLabel.latex(latex);
calc.setExpression({ id: 'p', latex: latex });
};
pElt.appendChild(pScrubber.elt);
var rElt = document.getElementById('scrubber-right');
var rScrubber = new ScrubberView();
rScrubber.min(0.01).max(3).value(0.46).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);
})();
.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%;
}
#p-label {
margin-top: 15px;
margin-bottom: -10px;
}
#r-label {
margin-top: 15px;
margin-bottom: 1px;
}
#calculator {
padding-right: 2px;
height: 100%;
}
#scrubber-left,
#scrubber-right {
height: 100%;
width: 50%
}
.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;
}
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