Lucas Kent e39465ad2f Changes to be committed:
new file:   Files/flashplayer_32_sa.exe
	new file:   favicon.ico
	new file:   globe.gif
	new file:   imgs/download.png
	new file:   imgs/zuck.jpg
	new file:   index.html
	new file:   other.ico
	new file:   script.js
	new file:   site.webmanifest
	new file:   sitemap.html
	new file:   styles/backround.css
	new file:   styles/border.css
	new file:   styles/fonts/Titillium_Web/OFL.txt
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Black.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Bold.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Italic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Light.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-LightItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Regular.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-SemiBold.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf
	new file:   styles/fonts/webfontkit-20221027-163353/generator_config.txt
	new file:   styles/fonts/webfontkit-20221027-163353/specimen_files/grid_12-825-55-15.css
	new file:   styles/fonts/webfontkit-20221027-163353/specimen_files/specimen_stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-163353/stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-demo.html
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-webfont.woff
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-webfont.woff2
	new file:   styles/fonts/webfontkit-20221027-165950/generator_config.txt
	new file:   styles/fonts/webfontkit-20221027-165950/specimen_files/grid_12-825-55-15.css
	new file:   styles/fonts/webfontkit-20221027-165950/specimen_files/specimen_stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-165950/stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-demo.html
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-webfont.woff
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-webfont.woff2
	new file:   styles/style.css
	new file:   tools/2048/.gitignore
	new file:   tools/2048/.jshintrc
	new file:   tools/2048/CONTRIBUTING.md
	new file:   tools/2048/LICENSE.txt
	new file:   tools/2048/README.md
	new file:   tools/2048/Rakefile
	new file:   tools/2048/favicon.ico
	new file:   tools/2048/index.html
	new file:   tools/2048/js/animframe_polyfill.js
	new file:   tools/2048/js/application.js
	new file:   tools/2048/js/bind_polyfill.js
	new file:   tools/2048/js/classlist_polyfill.js
	new file:   tools/2048/js/game_manager.js
	new file:   tools/2048/js/grid.js
	new file:   tools/2048/js/html_actuator.js
	new file:   tools/2048/js/keyboard_input_manager.js
	new file:   tools/2048/js/local_storage_manager.js
	new file:   tools/2048/js/tile.js
    new file:   tools/2048/meta/apple-touch-icon.png
	new file:   tools/webretro/cores/neocd_libretro.js
	new file:   tools/webretro/cores/neocd_libretro.wasm
	new file:   tools/webretro/cores/nestopia_libretro.js
	new file:   tools/webretro/cores/nestopia_libretro.wasm
	new file:   tools/webretro/cores/o2em_libretro.js
	new file:   tools/webretro/cores/o2em_libretro.wasm
	new file:   tools/webretro/cores/opera_libretro.js
	new file:   tools/webretro/cores/opera_libretro.wasm
2022-11-02 08:40:01 -04:00

387 lines
37 KiB
JavaScript

let w,h,poly,dragQ,my={};function areaestimMain(mode){let version='0.81'
my.mode=mode
my.modePts=5;if(isNaN(my.mode)){}else{my.modePts=Number(my.mode);}
console.log("my.mode",my.mode,my.modePts);dragQ=false;my.dragScreenQ=false;w=540;h=360;my.graphWd=w;my.graphHt=h;my.boxesTgtN=5
let s='';s+='<div style="position:relative; width:'+w+'px; margin:auto; display:block;">';let ofStr=''
if(my.mode=='area')ofStr='Area'
if(my.mode=='mom1')ofStr='Centroid'
if(my.mode=='mom2')ofStr='2nd Moment of Area'
s+=wrap('','','rel','display:inline-block; z-index: 2; margin:4px;',`
<div style="font: bold 19px arial;">Estimation of ${ofStr}</div>
<span style="font: 13px/20px Arial; vertical-align: top; z-index: 2; ">Square Density:</span>
<input type="range" id="r2" value="' + my.boxesTgtN + '" min="2" max="30" step="1" style="width:200px; height:17px; border: none; " oninput="onBoxesChg(0,this.value)" onchange="onBoxesChg(1,this.value)" />
`)
s+=wrap('info','output','abs','float:right; display:inline-block; font: 15px arial; padding:5px; text-align:left;z-index: 2;','')
s+='<canvas id="canGraph" style="position:absolute; top:45px; left:0; "></canvas>';let btns=[{id:'centroidBtn',name:'Centroid'},{id:'anglesBtn',name:'Angles'},{id:'sidesBtn',name:'Sides'},{id:'coordsBtn',name:'Coords'},{id:'guidesBtn',name:'Guides'},{id:'snapBtn',name:'Snap'},{id:'regBtn',name:'Regular'},]
s+='<div style="height:80px; width:2px; ">&nbsp;</div>'
btns.map(btn=>{s+=wrap('','','rel','margin-left:'+(w-100)+'px; width:10px;',`<button id="${btn.id}" class="btn lo" style="z-index: 2;" onclick="toggle${btn.name}()" >${btn.name}</button>`)})
s+='<br><br>'
s+=wrap('btns2','','rel','text-align:center;',`
<span style="color:black; font: 13px/20px Arial; vertical-align: top; z-index: 2; ">Zoom:</span>
<input type="range" id="r1" value="0.5" min="0" max="1" step=".01" style="z-index:2; width:200px; height:17px; border: none; " oninput="onZoomChg(0,this.value)" onchange="onZoomChg(1,this.value)" />
&nbsp;
<button id="resetBtn" class="btn" style="z-index:2;" onclick="reset()" >Reset</button>
<button id="editBtn" class="btn" style="z-index:2;" onclick="editpop()" >Edit</button>
<button id="exBtn" class="btn" style="margin-top:5px; z-index:2;" onclick="exNext()" >Example</button>
`)
s+='<div id="editpop" style="position:absolute; left:-450px; top:40px; padding: 5px; border: 1px solid red; border-radius: 9px; background-color: #88aaff; box-shadow: 10px 10px 5px 0px rgba(40,40,40,0.75); z-index:1; transition: all linear 0.3s; opacity:0; ">';s+='<textarea id="editbox" value="ddd" style="width: 400px; height: 120px; font: 16px Arial; border: 1px solid red; border-radius: 9px; background-color: #eeeeff; display: block;">';s+='</textarea >';s+='<div style="float:right; margin: 0 0 5px 10px;">';s+='<button onclick="editYes()" style="z-index:2; font: 22px Arial;" class="btn" >&#x2714;</button>';s+='<button onclick="editNo()" style="z-index:2; font: 22px Arial;" class="btn" >&#x2718;</button>';s+='</div>';s+='</div>';s+=wrap('','','rel','font: 11px Arial; color: #6600cc;','&copy; 2021 MathsIsFun.com v'+version)
s+='</div>';let div=document.createElement('div')
div.innerHTML=s
let script=document.currentScript
script.parentElement.insertBefore(div,script);my.canGraph=canvasInit('canGraph',w,h,2)
my.currZoom=1;my.sttRect=[-1,-2.2,11,7.5];my.coords=new Coords(0,0,w,h,my.sttRect[0],my.sttRect[1],my.sttRect[2],my.sttRect[3],true);this.graph=new Graph(my.canGraph.g,my.coords);my.dragNo=0;my.anglesQ=false;my.coordsQ=false;my.diagsQ=false;my.sidesQ=false;my.guidesQ=false;my.snapQ=false;my.regQ=false;document.getElementById('centroidBtn').style.visibility=(my.mode=='mom2')?'visible':'hidden'
poly=new Poly();makeShapes();let el=my.canGraph.el
el.addEventListener("mousedown",mouseDownListener,false);el.addEventListener('touchstart',ontouchstart,false);el.addEventListener("mousemove",dopointer,false);my.exs=[{name:"Triangle",coords:'(4.26,5.27), (6.16,1.33), (1.80,1.65)'},{name:"Rectangle",coords:'(7.50,4.50), (7.50,1.00), (3.50,1.00), (3.50,4.50)'},{name:"Circl-ish",coords:'(6.01,4.99), (6.96,4.77), (7.74,4.18), (8.21,3.32), (8.29,2.34), (7.97,1.42), (7.30,0.71), (6.39,0.33), (5.42,0.35), (4.53,0.77), (3.89,1.51), (3.61,2.44), (3.73,3.41), (4.24,4.25), (5.04,4.81)'},{name:"Tilted Square",coords:'(6.91,5.15), (7.59,1.36), (3.80,0.68), (3.12,4.47)'},{name:"Pentagon",coords:'(6.09,5.80), (2.68,4.90), (2.48,1.38), (5.76,0.10), (8.00,2.83)'},{name:"Hexagon",coords:'(6.09,5.80), (2.68,4.90), (2.48,1.38), (5.76,0.10), (6.00,2.3), (8.00,2.83)'},{name:"Hexagon2",coords:'(2,0), (1,-1.73), (-1,-1.73), (-2,0), (-1,1.73), (1,1.73)'},];my.exNo=-1
exNext()}
function getQueryVariable(variable){var query=window.location.search.substring(1);var vars=query.split("&");for(var i=0;i<vars.length;i++){var pair=vars[i].split("=");if(pair[0]==variable){return pair[1];}}
return false;}
function exNext(){my.exNo++
if(my.exNo>=my.exs.length)my.exNo=0
let coordStr=my.exs[my.exNo].coords
decodeCSV(coordStr)}
function getDescr(){let polyNames=["","","","Triangle","Quadrilateral","Pentagon","Hexagon","Heptagon","Octagon","Nonagon","Decagon","Hendecagon","Dodecagon","Triskaidecagon ","Tetrakaidecagon ","Pentadecagon","Hexakaidecagon ","Heptadecagon","Octakaidecagon ","Enneadecagon","Icosagon"];let descrStr="";let infoStr="";if(poly.isComplex()){descrStr+="Complex ";infoStr="(Shape intersects itself.)";}else{if(poly.isConcave()){descrStr+="Concave ";}else{if(poly.isRegular(0.01)){if(poly.pts.length>4){descrStr+="Regular ";}}}
infoStr=''
if(my.mode=='area'||my.mode=='mom1'||my.mode=='mom2'){infoStr=my.estimStr}}
if(poly.pts.length<polyNames.length){descrStr+=polyNames[poly.pts.length];}else{descrStr+=poly.pts.length.toString()+"-gon";}
if(my.mode=="diag"){infoStr=poly.getDiagCount()+" Diagonals";}
if(my.mode=="coords"){descrStr=''
infoStr=''}
return[descrStr,infoStr];}
function editpop(){console.log("editpop");let pop=document.getElementById('editpop');pop.style.transitionDuration="0.3s";pop.style.opacity=1;pop.style.zIndex=12;pop.style.left='100px';document.getElementById('editbox').value=getPts();}
function editYes(){let pop=document.getElementById('editpop');pop.style.opacity=0;pop.style.zIndex=1;pop.style.left='-500px';let s=document.getElementById('editbox').value;console.log("editYes",s);decodeCSV(s);}
function editNo(){let pop=document.getElementById('editpop');pop.style.opacity=0;pop.style.zIndex=1;pop.style.left='-500px';}
function getPts(){let s='';for(let i=0;i<poly.pts.length;i++){if(i>0)s+=', ';s+='(';s+=poly.pts[i].x.toFixed(my.coords.decDigN);s+=',';s+=poly.pts[i].y.toFixed(my.coords.decDigN);s+=')';}
return s;}
function decodeCSV(s){console.log('decodeCSV',s)
s=s.replace(/\s/g,"");s=s.replace(/\)\,\(/g,"~");s=s.replace(/\)\(/g,"~");s=s.replace(/\)/g,"");s=s.replace(/\(/g,"");s=s.replace(/\,/g,"_");decode(s);}
function decode(s){let a=s.split("~");poly.pts=[];for(let i=0;i<a.length;i++){let s1=a[i];let xy=s1.split("_");if(xy.length>=2){poly.pts.push(new Pt(Number(xy[0]),Number(xy[1])));}}
poly.pts2pxs()
update();}
function reset(){my.coords=new Coords(0,0,w,h,my.sttRect[0],my.sttRect[1],my.sttRect[2],my.sttRect[3],true);makeShapes();update();}
function getNum(){return parseInt(my.modePts);}
function numDn(){let num=getNum();if(num>3){num--;chgNumPts(num);}}
function numUp(){let num=getNum();if(num<=100){num++;chgNumPts(num);}}
function chgNumPts(n){my.modePts=n;makeShapes();update();}
function toggleCentroid(){my.centroidQ=!my.centroidQ;btn("centroidBtn",my.centroidQ);update();}
function toggleAngles(){my.anglesQ=!my.anglesQ;btn("anglesBtn",my.anglesQ);update();}
function toggleCoords(){my.coordsQ=!my.coordsQ;btn("coordsBtn",my.coordsQ);update();}
function toggleGuides(){my.guidesQ=!my.guidesQ;btn("guidesBtn",my.guidesQ);update();}
function toggleDiags(){my.diagsQ=!my.diagsQ;btn("diagsBtn",my.diagsQ);update();}
function toggleSides(){my.sidesQ=!my.sidesQ;btn("sidesBtn",my.sidesQ);update();}
function toggleRegular(){my.regQ=!my.regQ;btn("regBtn",my.regQ);update();}
function toggleSnap(){my.snapQ=!my.snapQ;btn("snapBtn",my.snapQ);update();}
function btn(btn,onq){if(onq){document.getElementById(btn).classList.add("hi");document.getElementById(btn).classList.remove("lo");}else{document.getElementById(btn).classList.add("lo");document.getElementById(btn).classList.remove("hi");}}
function ontouchstart(evt){let touch=evt.targetTouches[0];evt.clientX=touch.clientX;evt.clientY=touch.clientY;evt.touchQ=true;mouseDownListener(evt)}
function ontouchmove(evt){let touch=evt.targetTouches[0];evt.clientX=touch.clientX;evt.clientY=touch.clientY;evt.touchQ=true;mouseMoveListener(evt);evt.preventDefault();}
function ontouchend(evt){el.addEventListener('touchstart',ontouchstart,false);window.removeEventListener("touchend",ontouchend,false);if(dragQ){dragQ=false;window.removeEventListener("touchmove",ontouchmove,false);}}
function dopointer(e){let el=my.canGraph.el
let bRect=el.getBoundingClientRect();let mouseX=(e.clientX-bRect.left)*(el.width/my.canGraph.ratio/bRect.width);let mouseY=(e.clientY-bRect.top)*(el.height/my.canGraph.ratio/bRect.height);let overQ=false;for(let i=0;i<poly.pxs.length;i++){if(hitTest(poly.pxs[i],mouseX,mouseY)){overQ=true;my.dragNo=i;}}
if(overQ){el.style.cursor="pointer";}else{el.style.cursor="default";}}
function mouseDownListener(evt){let i;let highestIndex=-1;let el=my.canGraph.el
let bRect=el.getBoundingClientRect();let mouseX=(evt.clientX-bRect.left)*(el.width/my.canGraph.ratio/bRect.width);let mouseY=(evt.clientY-bRect.top)*(el.height/my.canGraph.ratio/bRect.height);my.prevX=mouseX;my.prevY=mouseY;dragQ=false;my.dragScreenQ=false;for(i=0;i<poly.pxs.length;i++){if(hitTest(poly.pxs[i],mouseX,mouseY)){my.dragNo=i;dragQ=true;if(i>highestIndex){my.dragHoldX=mouseX-poly.pxs[i].x;my.dragHoldY=mouseY-poly.pxs[i].y;highestIndex=i;my.dragNo=i;}}}
if(!dragQ){dragQ=true;my.dragScreenQ=true;}
if(evt.touchQ){window.addEventListener('touchmove',ontouchmove,false);}else{window.addEventListener("mousemove",mouseMoveListener,false);}
console.log("mouseDownListener",dragQ,my.dragScreenQ,highestIndex);update();if(evt.touchQ){el.removeEventListener("touchstart",ontouchstart,false);window.addEventListener("touchend",ontouchend,false);}else{el.removeEventListener("mousedown",mouseDownListener,false);window.addEventListener("mouseup",mouseUpListener,false);}
if(evt.preventDefault){evt.preventDefault();}
else if(evt.returnValue){evt.returnValue=false;}
return false;}
function mouseUpListener(evt){dragQ=false;let el=my.canGraph.el
el.addEventListener("mousedown",mouseDownListener,false);window.removeEventListener("mouseup",mouseUpListener,false);window.removeEventListener("mousemove",mouseMoveListener,false);}
function mouseMoveListener(evt){let posX;let posY;let el=my.canGraph.el
let shapeRad=poly.pts[my.dragNo].rad;let minX=shapeRad;let maxX=el.width-shapeRad;let minY=shapeRad;let maxY=el.height-shapeRad;let bRect=el.getBoundingClientRect();let mouseX=(evt.clientX-bRect.left)*(el.width/my.canGraph.ratio/bRect.width);let mouseY=(evt.clientY-bRect.top)*(el.height/my.canGraph.ratio/bRect.height);if(my.dragScreenQ){let moveX=my.prevX-mouseX;let moveY=mouseY-my.prevY;if(Math.abs(moveX)>1||Math.abs(moveY)>1){my.coords.drag(moveX,moveY);my.prevX=mouseX;my.prevY=mouseY;poly.pxs=[]
for(let i=0;i<poly.pts.length;i++){let pt=new Pt(my.coords.toXPix(poly.pts[i].x),my.coords.toYPix(poly.pts[i].y));poly.pxs.push(pt)}}}else{posX=mouseX-my.dragHoldX;posX=(posX<minX)?minX:((posX>maxX)?maxX:posX);posY=mouseY-my.dragHoldY;posY=(posY<minY)?minY:((posY>maxY)?maxY:posY);let xVal=my.coords.toXVal(posX)
let yVal=my.coords.toYVal(posY)
if(my.snapQ){let fact=my.coords.tick
console.log('fact',fact)
xVal=Math.round(xVal/fact)*fact
yVal=Math.round(yVal/fact)*fact
posX=my.coords.toXPix(xVal)
posY=my.coords.toYPix(yVal)}
poly.pts[my.dragNo].x=xVal
poly.pts[my.dragNo].y=yVal
poly.pxs[my.dragNo].x=posX;poly.pxs[my.dragNo].y=posY;}
update();}
function hitTest(shape,mx,my){let dx;let dy;dx=mx-shape.x;dy=my-shape.y;return(dx*dx+dy*dy<shape.rad*shape.rad);}
function onBoxesChg(n,v){my.boxesTgtN=parseInt(1+v/3)
console.log('onBoxesChg',v)
update();}
function onZoomChg(n,v){let zoom=Math.pow(4,v*2-1)
let scaleBy=zoom/my.currZoom;my.coords.scale(scaleBy);my.currZoom*=scaleBy;if(n==1){my.currZoom=1;document.getElementById('r1').value=0.5;}
poly.pxs=[]
for(let i=0;i<poly.pts.length;i++){let pt=new Pt(my.coords.toXPix(poly.pts[i].x),my.coords.toYPix(poly.pts[i].y));poly.pxs.push(pt)}
update();}
function update(){let g=my.canGraph.g
g.clearRect(0,0,g.canvas.width,g.canvas.height)
this.graph.drawGraph();if(my.regQ){poly.makeRegular();poly.pts=[];for(let i=0;i<poly.pxs.length;i++){let pt=new Pt(my.coords.toXVal(poly.pxs[i].x),my.coords.toYVal(poly.pxs[i].y));poly.pts.push(pt);}}
drawPts();if(my.mode=='area'){areaUpdate();}
if(my.mode=='mom1'){areaUpdate();}
if(my.mode=='mom2'){areaUpdate();}
let descr=['',''];if(my.mode=='coords'){}else{descr=getDescr();}
document.getElementById('info').innerHTML=descr[1];}
function areaUpdate(){let showCentroidQ=(my.mode=='mom1'||my.mode=='mom2')
let showDistsQ=(my.mode=='mom1'||my.mode=='mom2')
let rect=poly.extents()
let lenMax=Math.max(rect.xSpan,rect.ySpan)
let tick=toTick(lenMax/my.boxesTgtN)
let xStt=Math.floor(rect.xStt/tick)*tick
let yStt=Math.floor(rect.yStt/tick)*tick
console.log('doArea: lenMax, my.boxesTgtN, tick, xStt, yStt',lenMax,my.boxesTgtN,tick,xStt,yStt)
let tickPx=tick/my.coords.xScale
let boxs=[]
for(let x=xStt;x<rect.xEnd;x+=tick){for(let y=yStt;y<rect.yEnd;y+=tick){let midx=x+tick/2
let midy=y+tick/2
let insideQ=poly.insideQ(midx,midy)
if(insideQ){boxs.push({x:x,y:y})}}}
let g=my.canGraph.g
let sumX=0
let sumY=0
let insideN=0
boxs.map(box=>{g.strokeStyle='#ccc'
let midx=box.x+tick/2
let midy=box.y+tick/2
sumX+=midx
sumY+=midy
insideN++
g.fillStyle='hsla(240,100%,90%,0.2)'
g.strokeStyle='#888'
let xPx=my.coords.toXPix(box.x)
let yPx=my.coords.toYPix(box.y)
g.beginPath()
g.rect(xPx,yPx,tickPx,-tickPx)
g.fill()
g.stroke()
if(showDistsQ){let midXPx=my.coords.toXPix(box.x)+tickPx/2
let axisXPx=my.centroidQ?my.coords.toXPix(0):my.coords.toXPix(0)
let midYPx=my.coords.toYPix(box.y)-tickPx/2
let axisYPx=my.coords.toYPix(0)
g.fillStyle='#faa'
g.beginPath()
g.arc(midXPx,midYPx,3,0,2*Math.PI)
g.fill()
g.strokeStyle=g.fillStyle
g.beginPath()
lineArrow(midXPx,midYPx,midXPx,axisYPx)
g.stroke()
g.beginPath()
lineArrow(midXPx,midYPx,axisXPx,midYPx)
g.stroke()}})
let areaEst=insideN*tick*tick
console.log('area ',insideN,tick,areaEst)
let areaTrue=poly.getArea();let cx=sumX/insideN
let cy=sumY/insideN
if(showCentroidQ){g.strokeStyle='#888'
g.fillStyle='hsla(240,100%,90%,1)'
g.beginPath()
g.arc(my.coords.toXPix(cx),my.coords.toYPix(cy),6,0,2*Math.PI)
g.fill()
g.stroke()
g.fillStyle='blue'
let s='('+fmt(cx,3)+','+fmt(cy,3)+')'
g.font='18px Arial'
g.fillText(s,my.coords.toXPix(cx)+6,my.coords.toYPix(cy)+8)}
let sumX2=0
let sumY2=0
boxs.map(box=>{g.strokeStyle='#ccc'
let midx=box.x+tick/2
let midy=box.y+tick/2
if(my.centroidQ){sumX2+=Math.pow(Math.abs(midx-cx),2)
sumY2+=Math.pow(Math.abs(midy-cy),2)}else{sumX2+=Math.pow(Math.abs(midx),2)
sumY2+=Math.pow(Math.abs(midy),2)}})
sumX2=sumX2*tick*tick
sumY2=sumY2*tick*tick
console.log('tick,sumX2,sumY2',tick,sumX2,sumY2)
let s=''
if(my.mode=='area'){s+='Count = '+insideN+'<br>';s+='Estim Area = '+insideN+' &times; '+fmt(tick,4)+' &times; '+fmt(tick,4)+'<br>';s+='Estim Area = '+fmt(areaEst,4)+'<br>';s+='True Area = '+fmt(areaTrue,5)}
if(my.mode=='mom1'){s+='Count = '+insideN+'<br><br>'
s+='Sum of x distances = '+fmt(sumX,4)+'<br>';s+='Centroid x &asymp; '+fmt(sumX,4)+'/'+insideN+' &asymp; <b>'+fmt(cx,4)+'</b><br><br>';s+='Sum of y distances = '+fmt(sumY,4)+'<br>';s+='Centroid y &asymp; '+fmt(sumY,4)+'/'+insideN+' &asymp; <b>'+fmt(cy,4)+'</b><br>';}
if(my.mode=='mom2'){let axisStr=my.centroidQ?'centroid':'axis'
s+='In relation to x '+axisStr+':<br>'
s+='Sum of (y<sup>2</sup> &times; area) = <b>'+fmt(sumY2,4)+'</b><br>';s+='<br>'
s+='In relation to y '+axisStr+':<br>'
s+='Sum of (x<sup>2</sup> &times; area) = <b>'+fmt(sumX2,4)+'</b>';}
my.estimStr=s}
function toTick(val){let index10=Math.floor(Math.log(val)*Math.LOG10E)
let pow10=Math.pow(10,index10)
let mantissa=val/pow10;let ticks=[1,2,5,10]
let best=0
let found=-1
for(let i=0;i<ticks.length;i++){let tick=ticks[i];let score=0.8*tick/mantissa
if(score>1)break
if(score>best){best=score
found=i}}
if(found<0){console.log('Error: toTick',val)
return 1}else{return ticks[found]*pow10}}
function getRegular(midX,midY,radius,sttAngle,n){let pts=[];let dAngle=Math.PI*2/n;for(let i=0;i<n;i++){let angle=sttAngle+i*dAngle;let x=midX+radius*Math.cos(angle);let y=midY+radius*Math.sin(angle);pts.push(new Pt(x,y));}
return pts;}
function makeShapes(){let i;let pos=getRegular(5,3,3,1.2,my.modePts);poly.pts=[];poly.pxs=[];for(i=0;i<my.modePts;i++){poly.pts.push(pos[i]);let pt=new Pt(my.coords.toXPix(poly.pts[i].x),my.coords.toYPix(poly.pts[i].y));poly.pxs.push(pt);}
console.log("makeShapes",my.modePts,poly.pts,poly.pxs);}
function drawPts(){let i;let g=my.canGraph.g
g.strokeStyle="rgba(0, 0, 255, 0.5)";g.fillStyle="rgba(255, 255, 100, 0.1)";let pts=poly.pxs
let dbg="";for(i=0;i<pts.length;i++){let pt=pts[i];g.fillStyle="rgba(0, 0, 255, 0.3)";g.beginPath();g.arc(pt.x,pt.y,pt.rad,0,2*Math.PI,false);g.closePath();g.fill();g.fillStyle="rgba(0, 0, 0, 0.8)";g.beginPath();g.arc(pt.x,pt.y,2,0,2*Math.PI,false);g.closePath();g.fill();g.textAlign="left";if(my.coordsQ){g.font="bold 14px Arial";let txt='(';txt+=fmtFix(poly.pts[i].x,my.coords.decDigN);txt+=', ';txt+=fmtFix(poly.pts[i].y,my.coords.decDigN);txt+=')';g.fillText(txt,pt.x+5,pt.y-9);}else{}
dbg+='['+Math.floor(poly.pts[i].x)+","+Math.floor(poly.pts[i].y)+"],";}
poly.pxs=pts;poly.updateMe();g.strokeStyle="rgba(255, 0, 0, 0.5)";g.lineWidth=1.5;if(my.guidesQ){let orig={x:my.coords.toXPix(0),y:my.coords.toYPix(0)};poly.drawGuides(g,orig);}
g.strokeStyle="rgba(0, 0, 255, 0.5)";g.fillStyle="rgba(255, 255, 100, 0.1)";g.lineWidth=1;let drawShapeQ=true;if(my.mode=='coords'){drawShapeQ=my.sidesQ;}
if(drawShapeQ){g.fillStyle="rgba(255, 255, 100, 0.16)";poly.drawLines(g);}
if(my.anglesQ)poly.drawAngles(g);if(my.diagsQ)poly.drawDiags(g);if(my.sidesQ)poly.drawSides(g);}
class Poly{constructor(){this.pts=[];this.pxs=[];}
updateMe(){setAngles(this.pxs);this.sides=getSides(this.pxs);}
insideQ(x,y){var inside=false;for(var i=0,j=this.pts.length-1;i<this.pts.length;j=i++){let xi=this.pts[i].x
let yi=this.pts[i].y
let xj=this.pts[j].x
let yj=this.pts[j].y
var intersect=((yi>y)!=(yj>y))&&(x<(xj-xi)*(y-yi)/(yj-yi)+xi);if(intersect)inside=!inside;}
return inside;}
pts2pxs(){this.pxs=[]
for(let i=0;i<this.pts.length;i++){let pt=this.pts[i]
let px=new Pt(my.coords.toXPix(pt.x),my.coords.toYPix(pt.y));this.pxs.push(px);}}
extents(borderFactor=1){let xStt,xEnd,yStt,yEnd;for(let i=0;i<this.pts.length;i++){let pt=this.pts[i];if(i==0){xStt=pt.x;xEnd=pt.x;yStt=pt.y;yEnd=pt.y;}else{xStt=Math.min(xStt,pt.x);xEnd=Math.max(xEnd,pt.x);yStt=Math.min(yStt,pt.y);yEnd=Math.max(yEnd,pt.y);}}
let xMid=(xStt+xEnd)/2;let xhalfspan=borderFactor*(xEnd-xStt)/2;xStt=xMid-xhalfspan;xEnd=xMid+xhalfspan;let yMid=(yStt+yEnd)/2;let yhalfspan=borderFactor*(yEnd-yStt)/2;yStt=yMid-yhalfspan;yEnd=yMid+yhalfspan;return{xStt:xStt,xEnd:xEnd,yStt:yStt,yEnd:yEnd,xSpan:xEnd-xStt,ySpan:yEnd-yStt}}
drawDiags(g){g.strokeStyle="#666666";let diagCount=0;for(let i=0;i<this.pxs.length-2;i++){for(let j=i+2;j<this.pxs.length;j++){if(i==0&&j==this.pxs.length-1){}else{g.beginPath();g.moveTo(this.pxs[i].x,this.pxs[i].y);g.lineTo(this.pxs[j].x,this.pxs[j].y);g.stroke();diagCount++;}}}}
drawLines(g){g.beginPath();for(let i=0;i<this.pxs.length;i++){g.lineTo(this.pxs[i].x,this.pxs[i].y);}
g.closePath();g.fill();g.stroke();}
drawGuides(g,orig){let ptsLen=this.pxs.length;for(let i=0;i<ptsLen;i++){let pt=this.pxs[i];g.beginPath();g.strokeStyle="rgba(0, 0, 0, 0.5)";g.moveTo(orig.x,pt.y);g.lineTo(pt.x,pt.y);g.stroke();g.beginPath();g.moveTo(pt.x,pt.y);g.lineTo(pt.x,orig.y);g.stroke();}}
drawSides(g){let ptC=new Pt();ptC.setAvg(this.pxs);g.fillStyle="#000000";g.font="bold 12px Arial";let ptM=new Pt();let ptsLen=this.pxs.length;for(let i=0;i<ptsLen;i++){ptM.setAvg([this.pxs[i],this.pxs[loop(i,0,ptsLen-1,1)]]);ptM.interpolate(ptM,ptC,1.2);let side=this.sides[loop(i-1,0,ptsLen-1,1)];side=(my.coords.xScale*side).toFixed(2);g.fillText(side,ptM.x-10,ptM.y+5,100);}}
isConcave(){for(let i=0;i<this.pxs.length;i++){let pt=this.pxs[i];let angDeg=Math.round(pt.getAngle()*180/Math.PI);if(angDeg>180)
return true;}
return false;}
drawAngles(g){let angSum=0;let angDescr="";let angs=[];for(let i=0;i<this.pxs.length;i++){let pt=this.pxs[i];let angDeg=Math.round(pt.getAngle()*180/Math.PI);let d=30;if(angDeg==90){g.strokeStyle='#888888';g.drawBox(pt.x,pt.y,25,pt.angleOut-Math.PI/2);}else{if(angDeg>90){g.strokeStyle='#ff0000';d=Math.max(20,30-(angDeg-90)/6);}else{g.strokeStyle='#4444FF';}
g.fillStyle="rgba(0, 0, 255, 0.3)";g.beginPath();g.moveTo(pt.x,pt.y);g.arc(pt.x,pt.y,d,pt.angleIn,pt.angleOut,false);g.closePath();g.fill();}
let ang=this.userAngle(pt.getAngle());if(i<this.pxs.length-1){angSum+=ang;}else{ang=(this.pxs.length-2)*180-angSum;if(ang<0)
ang+=360;}
angs[i]=ang;angDescr+=ang+"° + ";let aMid=(pt.angleIn+pt.angleOut)/2;let txtPt=new Pt(0,0);txtPt.x=pt.x+(d+15)*Math.cos(aMid)-0;txtPt.y=pt.y+(d+15)*Math.sin(aMid)-0;g.fillStyle="rgba(0, 0, 255, 1)";g.fillText(Math.round(ang)+"°",txtPt.x-10,txtPt.y+5,100);}
return angs;}
userAngle(x){return Math.round(x*180/Math.PI,this.dec);}
getArea(){let a=0;for(let i=0;i<this.pts.length;i++){let pt0=this.pts[i];let pt1=this.pts[loop(i,0,this.pxs.length-1,1)];let a1=(pt0.x*pt1.y-pt0.y*pt1.x);a+=a1;}
a=Math.abs(a)/2;return a;}
getDiagCount(){let n=this.pxs.length;return n*(n-3)/2;}
isComplex(){let lns=[];for(let i=0;i<this.pxs.length;i++){lns.push(new Line(this.pxs[i],this.pxs[loop(i,0,this.pxs.length-1,1)]));}
for(let i=0;i<this.pxs.length-1;i++){for(let j=i+2;j<this.pxs.length;j++){if(i==0&&j==this.pxs.length-1)continue;let ln=lns[i];if(ln.isIntersect(lns[j])){if(ln.getIntersection(lns[j],true)==null){}else{return true;}}}}
return false;}
isRegular(tolerRatio){tolerRatio=typeof tolerRatio!=='undefined'?tolerRatio:0.001;let ptC=new Pt();ptC.setAvg(this.pxs);let rads=[];let lens=[];for(let i=0;i<this.pxs.length;i++){let pt=this.pxs[i];rads[i]=dist(pt.x-ptC.x,pt.y-ptC.y);let nxt=(i+1)%this.pxs.length;let ptN=this.pxs[nxt];lens[i]=dist(pt.x-ptN.x,pt.y-ptN.y);}
let radAvg=avg(rads);let lenAvg=avg(lens);let toler=radAvg*tolerRatio;let sameQ=true;for(let i=0;i<this.pxs.length;i++){if(!isNear(rads[i],radAvg,toler)){sameQ=false;break;}
if(!isNear(lens[i],lenAvg,toler)){sameQ=false;break;}}
return sameQ;}
makeRegular(){let ptC=new Pt();ptC.setAvg(this.pxs);let rad=1;let avgQ=false;if(avgQ){let rads=[];for(let i=0;i<this.pxs.length;i++){let pt=this.pxs[i];rads[i]=dist(pt.x-ptC.x,pt.y-ptC.y);}
rad=avg(rads);}else{let pt;if(my.dragNo<this.pxs.length){pt=this.pxs[my.dragNo];}else{pt=this.pxs[0];}
rad=dist(pt.x-ptC.x,pt.y-ptC.y);}
let sttAngle=Math.atan2(this.pxs[0].y-ptC.y,this.pxs[0].x-ptC.x);let dAngle=Math.PI*2/this.pxs.length;for(let i=0;i<this.pxs.length;i++){let angle=sttAngle+i*dAngle;this.pxs[i].x=ptC.x+rad*Math.cos(angle);this.pxs[i].y=ptC.y+rad*Math.sin(angle);}}}
class Pt{constructor(ix,iy){this.x=ix;this.y=iy;this.rad=9;this.color="rgb("+0+","+0+","+255+")";this.prevx=0;this.prevy=0;this.prevQ=false
this.angleIn=0;this.angleOut=0;}
setxy(ix,iy){this.x=ix;this.y=iy;}
setPrevPt(){this.prevx=this.x;this.prevy=this.y;this.prevQ=true;}
getAngle(){return this.angleOut-this.angleIn;}
drawMe(g){g.fillStyle="rgba(0, 0, 255, 0.3)";g.beginPath();g.arc(this.x,this.y,20,0,2*Math.PI,false);g.closePath();g.fill();}
getAvg(pts){let xSum=0;let ySum=0;for(let i=0;i<pts.length;i++){xSum+=pts[i].x;ySum+=pts[i].y;}
let newPt=new Pt(xSum/pts.length,ySum/pts.length);newPt.x=xSum/pts.length;newPt.y=ySum/pts.length;return newPt;}
setAvg(pts){this.setPrevPt();let newPt=this.getAvg(pts);this.x=newPt.x;this.y=newPt.y;}
interpolate(pt1,pt2,f){this.setPrevPt();this.x=pt1.x*f+pt2.x*(1-f);this.y=pt1.y*f+pt2.y*(1-f);}
translate(pt,addQ){addQ=typeof addQ!=='undefined'?addQ:true;let t=new Pt(this.x,this.y);t.x=this.x;t.y=this.y;if(addQ){t.x+=pt.x;t.y+=pt.y;}else{t.x-=pt.x;t.y-=pt.y;}
return t;}
multiply(fact){return new Pt(this.x*fact,this.y*fact);}
multiplyMe(fact){this.x*=fact;this.y*=fact;}}
function isNear(a,b,toler){if(Math.abs(a-b)<=toler){return true;}else{return false;}}
function setAngles(pts){let CW=getClockwise(pts);let numPoints=pts.length;for(let i=0;i<numPoints;i++){let pt=pts[i];let ptm1=pts[loop(i,0,numPoints-1,-1)];let ptp1=pts[loop(i,0,numPoints-1,1)];let a1=Math.atan2(ptm1.y-pt.y,ptm1.x-pt.x);let a2=Math.atan2(ptp1.y-pt.y,ptp1.x-pt.x);if(CW==1){let temp=a1;a1=a2;a2=temp;}
if(a1>a2)
a2+=2*Math.PI;pt.angleIn=a1;pt.angleOut=a2;}}
function getClockwise(pts){let numPoints=pts.length;let count=0;for(let i=0;i<numPoints;i++){let pt=pts[i];let ptm1=pts[loop(i,0,numPoints-1,-1)];let ptp1=pts[loop(i,0,numPoints-1,1)];let z=0;z+=(pt.x-ptm1.x)*(ptp1.y-pt.y);z-=(pt.y-ptm1.y)*(ptp1.x-pt.x);if(z<0){count--;}else if(z>0){count++;}}
if(count>0)
return(1);if(count==0)
return(0);return(-1);}
function getSides(pts){let numPoints=pts.length;let sides=[];for(let i=0;i<numPoints;i++){let pt=pts[i];let ptp1=pts[loop(i,0,numPoints-1,1)];sides.push(dist(ptp1.x-pt.x,ptp1.y-pt.y));}
return(sides);}
function avg(vals){let sum=0;let count=vals.length;for(let i=0;i<count;i++){sum+=vals[i];}
return(sum/count);}
function dist(dx,dy){return(Math.sqrt(dx*dx+dy*dy));}
function loop(currNo,minNo,maxNo,incr){currNo+=incr;let range=maxNo-minNo+1;if(currNo<minNo){currNo=maxNo-(-currNo+maxNo)%range;}
if(currNo>maxNo){currNo=minNo+(currNo-minNo)%range;}
return currNo;}
function constrain(min,val,max){return(Math.min(Math.max(min,val),max));}
class Line{constructor(pt1,pt2){this.a=pt1;this.b=pt2;}
isIntersect(b){let a=this;if(this.ccw(a.a,a.b,b.a)*this.ccw(a.a,a.b,b.b)>0){return false;}
if(this.ccw(b.a,b.b,a.a)*this.ccw(b.a,b.b,a.b)>0){return false;}
return true;}
ccw(a,b,c){return((b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y));}
getIntersection(ln,asSegmentsQ){let A=this.a;let B=this.b;let E=ln.a;let F=ln.b;let a1=B.y-A.y;let b1=A.x-B.x;let c1=B.x*A.y-A.x*B.y;let a2=F.y-E.y;let b2=E.x-F.x;let c2=F.x*E.y-E.x*F.y;let denom=a1*b2-a2*b1;if(denom==0){return null;}
let ip=new Pt();ip.x=(b1*c2-b2*c1)/denom;ip.y=(a2*c1-a1*c2)/denom;if(asSegmentsQ){if(Math.pow(ip.x-B.x,2)+Math.pow(ip.y-B.y,2)>Math.pow(A.x-B.x,2)+Math.pow(A.y-B.y,2)){return null;}
if(Math.pow(ip.x-A.x,2)+Math.pow(ip.y-A.y,2)>Math.pow(A.x-B.x,2)+Math.pow(A.y-B.y,2)){return null;}
if(Math.pow(ip.x-F.x,2)+Math.pow(ip.y-F.y,2)>Math.pow(E.x-F.x,2)+Math.pow(E.y-F.y,2)){return null;}
if(Math.pow(ip.x-E.x,2)+Math.pow(ip.y-E.y,2)>Math.pow(E.x-F.x,2)+Math.pow(E.y-F.y,2)){return null;}}
return ip;}
getMidPt(){return new Pt((this.a.x+this.b.x)/2,(this.a.y+this.b.y)/2);}
getClosestPoint(toPt,inSegmentQ){let AP=toPt.translate(this.a,false);let AB=this.b.translate(this.a,false);let ab2=AB.x*AB.x+AB.y*AB.y;let ap_ab=AP.x*AB.x+AP.y*AB.y;let t=ap_ab/ab2;if(inSegmentQ){t=constrain(0,t,1);}
return this.a.translate(AB.multiply(t));}
setLen(newLen,fromMidQ){fromMidQ=typeof fromMidQ!=='undefined'?fromMidQ:true;let len=this.getLength();if(fromMidQ){let midPt=this.getMidPt();let halfPt=new Pt(this.a.x-midPt.x,this.a.y-midPt.y);halfPt.multiplyMe(newLen/len);this.a=midPt.translate(halfPt);this.b=midPt.translate(halfPt,false);}else{let diffPt=new Pt(this.a.x-this.b.x,this.a.y-this.b.y);diffPt.multiplyMe(newLen/len);this.b=this.a.translate(diffPt,false);}}
getAngle(){return Math.atan2(this.b.y-this.a.y,this.b.x-this.a.x);}}
class Coords{constructor(left,top,width,height,xStt,yStt,xEnd,yEnd,uniScaleQ){this.left=left;this.top=top;this.width=width;this.height=height;this.xStt=xStt;this.yStt=yStt;this.xEnd=xEnd;this.yEnd=yEnd;this.uniScaleQ=uniScaleQ;this.xLogQ=false;this.yLogQ=false;this.xScale;this.yScale;this.calcScale();}
calcScale(){if(this.xLogQ){if(this.xStt<=0)this.xStt=1;if(this.xEnd<=0)this.xEnd=1;}
if(this.yLogQ){if(this.yStt<=0)this.yStt=1;if(this.yEnd<=0)this.yEnd=1;}
let temp;if(this.xStt>this.xEnd){temp=this.xStt;this.xStt=this.xEnd;this.xEnd=temp;}
if(this.yStt>this.yEnd){temp=this.yStt;this.yStt=this.yEnd;this.yEnd=temp;}
this.xSpan=this.xEnd-this.xStt;if(this.xSpan<=0)this.xSpan=0.1;this.xScale=this.xSpan/this.width;this.xLogScale=(Math.log(this.xEnd)-Math.log(this.xStt))/this.width;this.ySpan=this.yEnd-this.yStt;if(this.ySpan<=0)this.ySpan=0.1;this.yScale=this.ySpan/this.height;this.yLogScale=(Math.log(this.yEnd)-Math.log(this.yStt))/this.height;this.maxSpan=Math.max(this.xSpan,this.ySpan)
this.tick=this.toTick(this.maxSpan/50)
let pow10=Math.floor(Math.log(this.maxSpan)*Math.LOG10E)
this.decDigN=3-pow10
if(this.uniScaleQ&&!this.xLogQ&&!this.yLogQ){let newScale=Math.max(this.xScale,this.yScale);this.xScale=newScale;this.xSpan=this.xScale*this.width;let xMid=(this.xStt+this.xEnd)/2;this.xStt=xMid-this.xSpan/2;this.xEnd=xMid+this.xSpan/2;this.yScale=newScale;this.ySpan=this.yScale*this.height;let yMid=(this.yStt+this.yEnd)/2;this.yStt=yMid-this.ySpan/2;this.yEnd=yMid+this.ySpan/2;}
console.log('calcScale',this)}
toTick(val){let index10=Math.floor(Math.log(val)*Math.LOG10E)
let pow10=Math.pow(10,index10)
let mantissa=val/pow10;let ticks=[1,2,5,10]
let best=0
let found=-1
for(let i=0;i<ticks.length;i++){let tick=ticks[i];let score=0.8*tick/mantissa
if(score>1)break
if(score>best){best=score
found=i}}
if(found<0){console.log('Error: toTick',val)
return 1}else{return ticks[found]*pow10}}
getXScale(){return this.xScale;}
getYScale(){return this.yScale;}
scale(factor,xMid,yMid){if(typeof xMid=='undefined')xMid=(this.xStt+this.xEnd)/2;this.xStt=xMid-(xMid-this.xStt)*factor;this.xEnd=xMid+(this.xEnd-xMid)*factor;if(typeof yMid=='undefined')yMid=(this.yStt+this.yEnd)/2;this.yStt=yMid-(yMid-this.yStt)*factor;this.yEnd=yMid+(this.yEnd-yMid)*factor;this.calcScale();}
drag(xPix,yPix){this.xStt+=xPix*this.xScale;this.xEnd+=xPix*this.xScale;this.yStt+=yPix*this.yScale;this.yEnd+=yPix*this.yScale;this.calcScale();}
newCenter(x,y){let xMid=this.xStt+x*this.xScale;let xhalfspan=(this.xEnd-this.xStt)/2;this.xStt=xMid-xhalfspan;this.xEnd=xMid+xhalfspan;let yMid=this.yEnd-y*this.yScale;let yhalfspan=(this.yEnd-this.yStt)/2;this.yStt=yMid-yhalfspan;this.yEnd=yMid+yhalfspan;this.calcScale();}
fitToPts(pts,borderFactor){for(let i=0;i<pts.length;i++){let pt=pts[i];if(i==0){this.xStt=pt.x;this.xEnd=pt.x;this.yStt=pt.y;this.yEnd=pt.y;}else{this.xStt=Math.min(this.xStt,pt.x);this.xEnd=Math.max(this.xEnd,pt.x);this.yStt=Math.min(this.yStt,pt.y);this.yEnd=Math.max(this.yEnd,pt.y);}}
let xMid=(this.xStt+this.xEnd)/2;let xhalfspan=borderFactor*(this.xEnd-this.xStt)/2;this.xStt=xMid-xhalfspan;this.xEnd=xMid+xhalfspan;let yMid=(this.yStt+this.yEnd)/2;let yhalfspan=borderFactor*(this.yEnd-this.yStt)/2;this.yStt=yMid-yhalfspan;this.yEnd=yMid+yhalfspan;this.calcScale();}
toXPix(val,useCornerQ){if(this.xLogQ){return this.left+(Math.log(val)-Math.log(this.xStt))/this.xLogScale;}else{return this.left+((val-this.xStt)/this.xScale);}}
toYPix(val){if(this.yLogQ){return this.top+(Math.log(this.yEnd)-Math.log(val))/this.yLogScale;}else{return this.top+((this.yEnd-val)/this.yScale);}}
toPtVal(pt,useCornerQ){return new Pt(this.toXVal(pt.x,useCornerQ),this.toYVal(pt.y,useCornerQ));}
toXVal(pix,useCornerQ){if(useCornerQ){return this.xStt+(pix-this.left)*this.xScale;}else{return this.xStt+pix*this.xScale;}}
toYVal(pix,useCornerQ){if(useCornerQ){return this.yEnd-(pix-this.top)*this.yScale;}else{return this.yEnd-pix*this.yScale;}}
getTicks(stt,span,ratio){let ticks=[];let inter=this.tickInterval(span/ratio,false);let tickStt=Math.ceil(stt/inter)*inter;let i=0;let tick=0;do{tick=tickStt+i*inter;tick=Number(tick.toPrecision(8));ticks.push([tick,1]);i++;}while(tick<stt+span);inter=this.tickInterval(span/ratio,true);for(i=0;i<ticks.length;i++){let t=ticks[i][0];if(Math.abs(Math.round(t/inter)-(t/inter))<0.001){ticks[i][1]=0;}}
return ticks;}
tickInterval(span,majorQ){let pow10=Math.pow(10,Math.floor(Math.log(span)*Math.LOG10E));let mantissa=span/pow10;if(mantissa>=5){if(majorQ){return(5*pow10);}else{return(1*pow10);}}
if(mantissa>=3){if(majorQ){return(2*pow10);}else{return(0.2*pow10);}}
if(mantissa>=1.4){if(majorQ){return(0.5*pow10);}else{return(0.2*pow10);}}
if(mantissa>=0.8){if(majorQ){return(0.5*pow10);}else{return(0.1*pow10);}}
if(majorQ){return(0.2*pow10);}else{return(0.1*pow10);}}}
class Graph{constructor(g,coords){this.g=g;my.coords=coords;this.xLinesQ=true;this.yLinesQ=true;this.xValsQ=true;this.yValsQ=true;this.majorTickClr="rgba(0,0,256,0.2)"
this.minorTickClr="rgba(0,0,256,0.07)"
this.skewQ=false;}
drawGraph(){this.hzAxisY=my.coords.toYPix(0);if(this.hzAxisY<0)this.hzAxisY=0;if(this.hzAxisY>my.coords.height)this.hzAxisY=my.coords.height;this.hzNumsY=this.hzAxisY+14;if(this.hzAxisY>my.coords.height-10)this.hzNumsY=my.coords.height-3;this.vtAxisX=my.coords.toXPix(0);if(this.vtAxisX<0)this.vtAxisX=0;if(this.vtAxisX>my.coords.width)this.vtAxisX=my.coords.width;this.vtNumsX=this.vtAxisX-5;if(this.vtAxisX<10)this.vtNumsX=20;if(my.coords.xLogQ){this.drawLinesLogX();}else{if(this.xLinesQ){this.drawHzLines();}}
if(my.coords.yLogQ){this.drawLinesLogY();}else{if(this.yLinesQ){this.drawVtLines();}}}
drawVtLines(){let g=this.g;g.lineWidth=1;let ticks=my.coords.getTicks(my.coords.xStt,my.coords.xEnd-my.coords.xStt,my.graphWd/100);for(let i=0;i<ticks.length;i++){let tick=ticks[i];let xVal=tick[0];let tickLevel=tick[1];if(tickLevel==0){g.strokeStyle=this.majorTickClr}else{g.strokeStyle=this.minorTickClr}
let xPix=my.coords.toXPix(xVal,false);g.beginPath();g.moveTo(xPix,my.coords.toYPix(my.coords.yStt,false));g.lineTo(xPix,my.coords.toYPix(my.coords.yEnd,false));g.stroke();if(tickLevel==0&&this.xValsQ){g.fillStyle="#0000ff";g.font="bold 12px Verdana";g.textAlign="center";g.fillText(fmt(xVal),xPix,this.hzNumsY);}}
if(this.skewQ)
return;g.lineWidth=1.5;g.strokeStyle="#ff0000";g.beginPath();g.moveTo(this.vtAxisX,my.coords.toYPix(my.coords.yStt,false));g.lineTo(this.vtAxisX,my.coords.toYPix(my.coords.yEnd,false));g.stroke();g.beginPath();g.fillStyle=g.strokeStyle;g.drawArrow(this.vtAxisX,my.coords.toYPix(my.coords.yEnd),15,2,20,10,Math.PI/2,10,false);g.stroke();g.fill();}
drawHzLines(){let g=this.g;g.lineWidth=1;let ticks=my.coords.getTicks(my.coords.yStt,my.coords.yEnd-my.coords.yStt,my.graphHt/100);for(let i=0;i<ticks.length;i++){let tick=ticks[i];let yVal=tick[0];let tickLevel=tick[1];if(tickLevel==0){g.strokeStyle=this.majorTickClr}else{g.strokeStyle=this.minorTickClr}
let yPix=my.coords.toYPix(yVal,false);g.beginPath();g.moveTo(my.coords.toXPix(my.coords.xStt,false),yPix);g.lineTo(my.coords.toXPix(my.coords.xEnd,false),yPix);g.stroke();if(tickLevel==0&&this.yValsQ){g.fillStyle="#ff0000";g.font="bold 12px Verdana";g.textAlign="right";g.fillText(fmt(yVal),this.vtNumsX,yPix+5);}}
if(this.skewQ)
return;g.lineWidth=2;g.strokeStyle="#0000ff";g.beginPath();g.moveTo(my.coords.toXPix(my.coords.xStt,false),this.hzAxisY);g.lineTo(my.coords.toXPix(my.coords.xEnd,false),this.hzAxisY);g.stroke();g.beginPath();g.fillStyle=g.strokeStyle;g.drawArrow(my.coords.toXPix(my.coords.xEnd,false),this.hzAxisY,15,2,20,10,0,10,false);g.stroke();g.fill();}}
function lineArrow(aX,aY,bX,bY){let g=my.canGraph.g
g.beginPath();g.moveTo(aX,aY);g.lineTo(bX,bY);g.stroke();g.beginPath();let ang=Math.atan2(bY-aY,-(bX-aX));g.drawArrow(aX,aY,0,0,10,5,ang,5,false);g.stroke();g.beginPath();ang=Math.atan2(aY-bY,-(aX-bX));g.drawArrow(bX,bY,0,0,10,5,ang,5,false);g.stroke();}
CanvasRenderingContext2D.prototype.drawArrow=function(x0,y0,totLen,shaftHt,headLen,headHt,angle,sweep,invertQ){let g=this;let pts=[[0,0],[-headLen,-headHt/2],[-headLen+sweep,-shaftHt/2],[-totLen,-shaftHt/2],[-totLen,shaftHt/2],[-headLen+sweep,shaftHt/2],[-headLen,headHt/2],[0,0]];if(invertQ){pts.push([0,-headHt/2],[-totLen,-headHt/2],[-totLen,headHt/2],[0,headHt/2]);}
for(let i=0;i<pts.length;i++){let cosa=Math.cos(-angle);let sina=Math.sin(-angle);let xPos=pts[i][0]*cosa+pts[i][1]*sina;let yPos=pts[i][0]*sina-pts[i][1]*cosa;if(i==0){g.moveTo(x0+xPos,y0+yPos);}else{g.lineTo(x0+xPos,y0+yPos);}}};CanvasRenderingContext2D.prototype.drawBox=function(midX,midY,radius,angle){g.beginPath();let pts=[[0,0],[Math.cos(angle),Math.sin(angle)],[Math.cos(angle)+Math.cos(angle+Math.PI/2),Math.sin(angle)+Math.sin(angle+Math.PI/2)],[Math.cos(angle+Math.PI/2),Math.sin(angle+Math.PI/2)],[0,0]];for(let i=0;i<pts.length;i++){if(i==0){g.moveTo(midX+radius*pts[i][0],midY+radius*pts[i][1]);}else{g.lineTo(midX+radius*pts[i][0],midY+radius*pts[i][1]);}}
g.stroke();};function fmt(num,digits=14){if(num==Number.POSITIVE_INFINITY)return "undefined";if(num==Number.NEGATIVE_INFINITY)return "undefined";num=num.toPrecision(digits);num=Number(num).toString()
if(num.charAt(num.length-1)==".")num=num.substr(0,num.length-1);if(Math.abs(num)<1e-15)num=0;return num;}
function fmtFix(num,digN=14){let s=Number(num).toFixed(Math.max(0,digN));s=s.replace(/\.0+$/g,"");return s;}
function canvasInit(id,wd,ht,ratio){let el=document.getElementById(id);el.width=wd*ratio;el.style.width=wd+"px";el.height=ht*ratio;el.style.height=ht+"px";let g=el.getContext("2d");g.setTransform(ratio,0,0,ratio,0,0);return{el:el,g:g,ratio:ratio}}
function wrap(id,classStr,type='rel',styleExtra='',middle=''){let s=''
s+='\n'
let inpQ=classStr.includes('input')
s+=inpQ?'<input class="input" value="'+middle+'" oninput="onChg()" onchange="onChg()"':'<div'
if(id.length>0)s+=' id="'+id+'"'
if(classStr.length>0)s+=' class="'+classStr+'"'
if(type=='rel'){s+=' style="position:relative; '+styleExtra+'"'}
if(type=='abs'){s+=' style="position:absolute; '+styleExtra+'"'}
s+=inpQ?' />':' >'+middle+'</div>'
return s}