Show description
Master Trig Visuals — Unit Circle, Graphs, Identities
Master Trig Visuals — Unit Circle, Graphs, Identities
Master Trig Visuals
Unit Circle
Reference Triangle
Circle → Waves
Trig Graphs
Inverse Graphs
Transform Sandbox
Degree ↔ Radian Ring
Special Angle Table
Identity Map
Unit Circle (special angles, ASTC)
Degrees
Reference lines
θ
Sine = y, Cosine = x, Tangent = y/x
Reference Triangle
Right triangle with sides x = r·cosθ, y = r·sinθ, hypotenuse r. Toggle r and θ.
r
θ
Unit Circle → Sine & Cosine (projection)
▶ Play
speed
sine
cosine (dashed)
Trig Function Graphs
sin
cos
tan
sec
csc
zoom
asymptotes
π marks
Inverse Trig Graphs (principal branches)
arcsin
arccos
arctan
Transform Sandbox: y = A·sin(Bx − C) + D
A (amplitude)
B (freq)
C (phase)
D (vertical)
use cos
Period = 2π, Midline = y = 0, Phase shift = 0
Degree ↔ Radian Ring
Hover/tap to see matching degree & radian labels.
Special Angles (degrees ↔ radians ↔ values)
Identity Map
Pythagorean
sin²θ + cos²θ = 1
1 + tan²θ = sec²θ
1 + cot²θ = csc²θ
Reciprocal
secθ = 1 / cosθ, cscθ = 1 / sinθ, cotθ = 1 / tanθ
Quotient
tanθ = sinθ / cosθ, cotθ = cosθ / sinθ
Cofunction (complements)
sin(π/2−θ)=cosθ, cos(π/2−θ)=sinθ
tan(π/2−θ)=cotθ, sec(π/2−θ)=cscθ
Sum/Difference
sin(a±b)=sin a cos b ± cos a sin b
cos(a±b)=cos a cos b ∓ sin a sin b
tan(a±b)= (tan a ± tan b)/(1 ∓ tan a tan b)
Double / Half
sin 2θ=2 sinθ cosθ, cos 2θ=cos²θ−sin²θ=1−2 sin²θ=2 cos²θ−1
tan 2θ=2 tanθ/(1−tan²θ)
ASTC rule: All (Q1) — Sine (Q2) — Tangent (Q3) — Cosine (Q4).
Built for lalo. No libs. Pure HTML/CSS/JS.
Master Trig Visuals — Unit Circle, Graphs, Identities
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Master Trig Visuals — Unit Circle, Graphs, Identities</title>
<style>
:root{
--fg:#111; --bg:#fff; --ink:#000; --sub:#555; --accent:#000;
--card:#fff; --border:#000; --muted:#888; --grid:#ddd;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0; font:16px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", sans-serif;
color:var(--fg); background:var(--bg);
}
header{
position:sticky; top:0; z-index:10; backdrop-filter:saturate(1.5) blur(6px);
background:rgba(255,255,255,.85); border-bottom:1px solid var(--border);
}
.wrap{max-width:1200px; margin:auto; padding:16px;}
h1{margin:0 0 8px; letter-spacing:.3px}
nav{display:flex; gap:8px; flex-wrap:wrap}
nav a{
text-decoration:none; color:var(--ink); border:1px solid var(--border);
padding:6px 10px; border-radius:10px; font-weight:600;
}
nav a:hover{background:#f3f3f3}
section{scroll-margin-top:90px}
.grid{display:grid; gap:16px}
.grid.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.grid.cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:1000px){ .grid.cols-3{grid-template-columns:repeat(2,1fr)} }
@media (max-width:740px){
.grid.cols-2,.grid.cols-3{grid-template-columns:1fr}
nav{overflow:auto; white-space:nowrap}
}
.card{
border:1.25px solid var(--border); border-radius:16px; background:var(--card);
padding:16px; box-shadow:0 4px 0 #000;
}
.card h2{margin:0 0 10px}
.dim{color:var(--sub)}
.kbd{border:1px solid var(--border); border-radius:8px; padding:2px 6px; font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace}
canvas{width:100%; height:420px; display:block; border:1px solid var(--border); border-radius:12px; background:#fff}
.small canvas{height:320px}
.controls{display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin:8px 0 4px}
.controls label{font-size:13px; color:var(--sub)}
.pill{
display:inline-flex; gap:6px; align-items:center; border:1px solid var(--border);
padding:6px 10px; border-radius:999px; background:#fff; font-weight:600;
}
.tbl{width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums}
.tbl th,.tbl td{border:1px solid var(--border); padding:8px 10px; text-align:center}
.tbl th{background:#f8f8f8}
code{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:13px}
.legend{display:flex; gap:12px; flex-wrap:wrap; margin:6px 0 10px}
.swatch{display:inline-flex; align-items:center; gap:6px}
.dot{width:12px; height:12px; border-radius:50%; background:#000; border:1px solid #000}
.muted{color:#777}
</style>
</head>
<body>
<header>
<div class="wrap">
<h1>Master Trig Visuals</h1>
<nav>
<a href="#unit-circle">Unit Circle</a>
<a href="#reference-triangle">Reference Triangle</a>
<a href="#wave-projection">Circle → Waves</a>
<a href="#graphs">Trig Graphs</a>
<a href="#inverse">Inverse Graphs</a>
<a href="#transform">Transform Sandbox</a>
<a href="#deg-rad">Degree ↔ Radian Ring</a>
<a href="#special-table">Special Angle Table</a>
<a href="#identities">Identity Map</a>
</nav>
</div>
</header>
<main class="wrap">
<!-- Unit Circle -->
<section id="unit-circle" class="card">
<h2>Unit Circle (special angles, ASTC)</h2>
<div class="controls">
<span class="pill"><input type="checkbox" id="uc-deg" /><label for="uc-deg">Degrees</label></span>
<span class="pill"><input type="checkbox" id="uc-refs" checked/><label for="uc-refs">Reference lines</label></span>
<span class="pill"><input type="range" id="uc-theta" min="0" max="6.28318530718" step="0.001" value="0"/><label for="uc-theta">θ</label></span>
<span class="muted">Sine = y, Cosine = x, Tangent = y/x</span>
</div>
<canvas id="unitCircle"></canvas>
</section>
<!-- Reference Triangle -->
<section id="reference-triangle" class="card">
<h2>Reference Triangle</h2>
<p class="dim">Right triangle with sides x = r·cosθ, y = r·sinθ, hypotenuse r. Toggle r and θ.</p>
<div class="controls">
<span class="pill"><input type="range" id="rt-r" min="0.5" max="2.0" step="0.01" value="1"/><label for="rt-r">r</label></span>
<span class="pill"><input type="range" id="rt-theta" min="0.05" max="1.4" step="0.005" value="0.9"/><label for="rt-theta">θ</label></span>
</div>
<canvas id="refTri" class="small"></canvas>
</section>
<!-- Circle to Waves Projection -->
<section id="wave-projection" class="card">
<h2>Unit Circle → Sine & Cosine (projection)</h2>
<div class="controls">
<button id="wp-play" class="pill">▶ Play</button>
<span class="pill"><input type="range" id="wp-speed" min="0.2" max="3" value="1" step="0.1"><label for="wp-speed">speed</label></span>
</div>
<div class="legend">
<span class="swatch"><span class="dot"></span><span>sine</span></span>
<span class="swatch"><span class="dot" style="background:#fff;border-color:#000; box-shadow:inset 0 0 0 2px #000;"></span><span>cosine (dashed)</span></span>
</div>
<canvas id="proj"></canvas>
</section>
<!-- Function Graphs -->
<section id="graphs" class="card">
<h2>Trig Function Graphs</h2>
<div class="controls">
<span class="pill">
<label><input type="radio" name="fn" value="sin" checked/> sin</label>
<label><input type="radio" name="fn" value="cos"/> cos</label>
<label><input type="radio" name="fn" value="tan"/> tan</label>
<label><input type="radio" name="fn" value="sec"/> sec</label>
<label><input type="radio" name="fn" value="csc"/> csc</label>
</span>
<span class="pill"><input type="range" id="g-zoom" min="0.5" max="4" step="0.1" value="1"><label for="g-zoom">zoom</label></span>
<span class="pill"><input type="checkbox" id="g-asym" checked/><label for="g-asym">asymptotes</label></span>
<span class="pill"><input type="checkbox" id="g-marks" checked/><label for="g-marks">π marks</label></span>
</div>
<canvas id="graph"></canvas>
</section>
<!-- Inverse Graphs -->
<section id="inverse" class="card">
<h2>Inverse Trig Graphs (principal branches)</h2>
<div class="controls">
<span class="pill">
<label><input type="radio" name="inv" value="asin" checked/> arcsin</label>
<label><input type="radio" name="inv" value="acos"/> arccos</label>
<label><input type="radio" name="inv" value="atan"/> arctan</label>
</span>
</div>
<canvas id="inv"></canvas>
</section>
<!-- Transform Sandbox -->
<section id="transform" class="card">
<h2>Transform Sandbox: y = A·sin(Bx − C) + D</h2>
<div class="controls">
<span class="pill"><input type="range" id="t-A" min="-3" max="3" step="0.1" value="1"><label for="t-A">A (amplitude)</label></span>
<span class="pill"><input type="range" id="t-B" min="0.2" max="4" step="0.1" value="1"><label for="t-B">B (freq)</label></span>
<span class="pill"><input type="range" id="t-C" min="-6.283" max="6.283" step="0.01" value="0"><label for="t-C">C (phase)</label></span>
<span class="pill"><input type="range" id="t-D" min="-3" max="3" step="0.1" value="0"><label for="t-D">D (vertical)</label></span>
<span class="pill"><label><input type="checkbox" id="t-cos" /> use cos</label></span>
</div>
<div class="dim">Period = <span id="t-period">2π</span>, Midline = y = <span id="t-mid">0</span>, Phase shift = <span id="t-shift">0</span></div>
<canvas id="transformer"></canvas>
</section>
<!-- Degree/Radian Ring -->
<section id="deg-rad" class="card">
<h2>Degree ↔ Radian Ring</h2>
<p class="dim">Hover/tap to see matching degree & radian labels.</p>
<canvas id="ring" class="small"></canvas>
</section>
<!-- Special Angle Table -->
<section id="special-table" class="card">
<h2>Special Angles (degrees ↔ radians ↔ values)</h2>
<table class="tbl" id="angTbl"></table>
</section>
<!-- Identity Map -->
<section id="identities" class="card">
<h2>Identity Map</h2>
<div class="grid cols-2">
<div>
<h3>Pythagorean</h3>
<ul>
<li><code>sin²θ + cos²θ = 1</code></li>
<li><code>1 + tan²θ = sec²θ</code></li>
<li><code>1 + cot²θ = csc²θ</code></li>
</ul>
<h3>Reciprocal</h3>
<ul>
<li><code>secθ = 1 / cosθ</code>, <code>cscθ = 1 / sinθ</code>, <code>cotθ = 1 / tanθ</code></li>
</ul>
<h3>Quotient</h3>
<ul>
<li><code>tanθ = sinθ / cosθ</code>, <code>cotθ = cosθ / sinθ</code></li>
</ul>
</div>
<div>
<h3>Cofunction (complements)</h3>
<ul>
<li><code>sin(π/2−θ)=cosθ</code>, <code>cos(π/2−θ)=sinθ</code></li>
<li><code>tan(π/2−θ)=cotθ</code>, <code>sec(π/2−θ)=cscθ</code></li>
</ul>
<h3>Sum/Difference</h3>
<ul>
<li><code>sin(a±b)=sin a cos b ± cos a sin b</code></li>
<li><code>cos(a±b)=cos a cos b ∓ sin a sin b</code></li>
<li><code>tan(a±b)= (tan a ± tan b)/(1 ∓ tan a tan b)</code></li>
</ul>
<h3>Double / Half</h3>
<ul>
<li><code>sin 2θ=2 sinθ cosθ</code>, <code>cos 2θ=cos²θ−sin²θ=1−2 sin²θ=2 cos²θ−1</code></li>
<li><code>tan 2θ=2 tanθ/(1−tan²θ)</code></li>
</ul>
</div>
</div>
<p class="dim">ASTC rule: All (Q1) — Sine (Q2) — Tangent (Q3) — Cosine (Q4).</p>
</section>
<footer class="dim" style="margin:26px 0 40px">
Built for lalo. No libs. Pure HTML/CSS/JS.
</footer>
</main>
<script>
/* ---------- utilities ---------- */
const TAU = Math.PI*2, PI=Math.PI;
const clamp=(x,a,b)=>Math.max(a,Math.min(b,x));
function px(ctx,x,y){ctx.moveTo(x-3,y);ctx.lineTo(x+3,y);ctx.moveTo(x,y-3);ctx.lineTo(x,y+3)}
function grid(ctx,w,h,ox,oy,sx=PI/2,sy=0.5){
ctx.save(); ctx.strokeStyle='#ddd'; ctx.lineWidth=1;
ctx.beginPath();
for(let x=ox% (sx*50); x<w; x+=sx*50){ ctx.moveTo(x,0); ctx.lineTo(x,h); }
for(let y=oy% (sy*50); y<h; y+=sy*50){ ctx.moveTo(0,y); ctx.lineTo(w,y); }
ctx.stroke(); ctx.restore();
}
function axis(ctx,w,h,ox,oy){ // x-axis y=0, y-axis x=0
ctx.save(); ctx.strokeStyle='#000'; ctx.lineWidth=1.25;
ctx.beginPath(); ctx.moveTo(0,oy); ctx.lineTo(w,oy); ctx.moveTo(ox,0); ctx.lineTo(ox,h); ctx.stroke();
ctx.restore();
}
function label(ctx,txt,x,y,align='center'){ctx.save(); ctx.fillStyle='#000'; ctx.textAlign=align; ctx.textBaseline='middle'; ctx.font='12px ui-monospace,Menlo,Consolas,monospace'; ctx.fillText(txt,x,y); ctx.restore();}
/* ---------- Unit Circle ---------- */
(function(){
const canvas=document.getElementById('unitCircle'), ctx=canvas.getContext('2d');
const thetaSlider=document.getElementById('uc-theta');
const degChk=document.getElementById('uc-deg');
const refsChk=document.getElementById('uc-refs');
function draw(){
const dpr=window.devicePixelRatio||1; const rect=canvas.getBoundingClientRect();
canvas.width=rect.width*dpr; canvas.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
const w=rect.width, h=420, cx=w/2, cy=h/2+10, R=Math.min(w,h)*0.32;
ctx.clearRect(0,0,w,h);
// circle
ctx.beginPath(); ctx.arc(cx,cy,R,0,TAU); ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
// axes
ctx.beginPath(); ctx.moveTo(cx-R-20,cy); ctx.lineTo(cx+R+20,cy); ctx.moveTo(cx,cy-R-20); ctx.lineTo(cx,cy+R+20); ctx.strokeStyle='#000'; ctx.stroke();
// ASTC labels
ctx.fillStyle='#000'; ctx.font='12px Inter,system-ui,sans-serif';
ctx.fillText('All',cx+22,cy-22);
ctx.fillText('Sin+',cx-44,cy-22);
ctx.fillText('Tan+',cx-44,cy+34);
ctx.fillText('Cos+',cx+22,cy+34);
// ticks + special angles
const specials=[0,PI/6,PI/4,PI/3,PI/2,2*PI/3,3*PI/4,5*PI/6,PI,7*PI/6,5*PI/4,4*PI/3,3*PI/2,5*PI/3,7*PI/4,11*PI/6,TAU];
ctx.strokeStyle='#000';
specials.forEach(a=>{
const x=cx+R*Math.cos(a), y=cy-R*Math.sin(a);
ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(cx+(R+10)*Math.cos(a), cy-(R+10)*Math.sin(a)); ctx.stroke();
const txt=degChk.checked ? Math.round(a*180/PI)+'°' :
(a===0||a===TAU)?'0':(a===PI)?'π':(a===PI/2)?'π/2':(a===3*PI/2)?'3π/2':
(a===PI/6)?'π/6':(a===PI/4)?'π/4':(a===PI/3)?'π/3':
(a===2*PI/3)?'2π/3':(a===3*PI/4)?'3π/4':(a===5*PI/6)?'5π/6':
(a===7*PI/6)?'7π/6':(a===5*PI/4)?'5π/4':(a===4*PI/3)?'4π/3':
(a===5*PI/3)?'5π/3':(a===7*PI/4)?'7π/4':'';
if(txt) label(ctx,txt,x+(x>cx?14:-14),y+(y<cy?-10:10),x>cx?'left':'right');
if(refsChk.checked){ ctx.save(); ctx.strokeStyle='#ddd'; ctx.beginPath(); ctx.moveTo(cx,cy); ctx.lineTo(x,y); ctx.stroke(); ctx.restore(); }
});
// moving point
const t=parseFloat(thetaSlider.value); const px=cx+R*Math.cos(t), py=cy-R*Math.sin(t);
ctx.fillStyle='#000'; ctx.beginPath(); ctx.arc(px,py,4,0,TAU); ctx.fill();
// projections
ctx.setLineDash([6,4]); ctx.strokeStyle='#000'; ctx.beginPath(); ctx.moveTo(px,cy); ctx.lineTo(px,py); ctx.moveTo(cx,py); ctx.lineTo(px,py); ctx.stroke(); ctx.setLineDash([]);
// labels
const x=Math.cos(t).toFixed(3), y=Math.sin(t).toFixed(3);
label(ctx,`x = cosθ = ${x}`,px,cy+18);
label(ctx,`y = sinθ = ${y}`,cx-38,py);
label(ctx,`tanθ = y/x ${(Math.abs(Math.cos(t))<1e-6)?'= undefined':('= '+(Math.tan(t)).toFixed(3))}`, cx, cy+R+28);
}
thetaSlider.addEventListener('input',draw); degChk.addEventListener('change',draw); refsChk.addEventListener('change',draw);
addEventListener('resize',draw,{passive:true}); draw();
})();
/* ---------- Reference Triangle ---------- */
(function(){
const c=document.getElementById('refTri'), ctx=c.getContext('2d');
const rEl=document.getElementById('rt-r'), tEl=document.getElementById('rt-theta');
function draw(){
const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
c.width=rect.width*dpr; c.height=320*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
const w=rect.width, h=320; ctx.clearRect(0,0,w,h);
const base=240, x0=40, y0=h-40, r=parseFloat(rEl.value), th=parseFloat(tEl.value);
const x=r*Math.cos(th)*base/r, y=r*Math.sin(th)*base/r;
// triangle
ctx.strokeStyle='#000'; ctx.lineWidth=1.5;
ctx.beginPath(); ctx.moveTo(x0,y0); ctx.lineTo(x0+base,y0); ctx.lineTo(x0+base,y0- base*Math.tan(th)); ctx.closePath(); ctx.stroke();
// project to point with radius r scaled
const hx=x0 + x, hy=y0 - y;
ctx.beginPath(); ctx.moveTo(x0,y0); ctx.lineTo(hx,hy); ctx.lineTo(x0+ x, y0); ctx.lineTo(x0,y0); ctx.stroke();
ctx.fillStyle='#000'; ctx.fillText(`x = r cosθ = ${(r*Math.cos(th)).toFixed(2)}`, x0+ x/2, y0+14);
ctx.fillText(`y = r sinθ = ${(r*Math.sin(th)).toFixed(2)}`, x0+ x+8, y0- y/2);
ctx.fillText(`r = ${(r).toFixed(2)}`, x0+(x)/2-4, y0 - (y)/2);
// angle arc
ctx.beginPath(); ctx.arc(x0,y0,30,-Math.PI, -Math.PI+th, false); ctx.stroke();
ctx.fillText(`θ=${th.toFixed(2)} rad`, x0+36, y0-10);
}
rEl.addEventListener('input',draw); tEl.addEventListener('input',draw); addEventListener('resize',draw); draw();
})();
/* ---------- Circle → Waves Projection ---------- */
(function(){
const c=document.getElementById('proj'), ctx=c.getContext('2d');
const btn=document.getElementById('wp-play'), speedEl=document.getElementById('wp-speed');
let running=false, t=0;
btn.onclick=()=>{running=!running; btn.textContent=running?'⏸ Pause':'▶ Play'};
function step(dt){ if(running){ t+=dt*parseFloat(speedEl.value); } }
function draw(){
const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
const w=rect.width, h=420; ctx.clearRect(0,0,w,h);
const R=120, cx=R+40, cy=h/2, kx= (w-cx-60)/ (2*PI), ox=cx, oy=cy;
// circle
ctx.beginPath(); ctx.arc(cx,cy,R,0,TAU); ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
// moving point
const ang=t%TAU, x=cx+R*Math.cos(ang), y=cy-R*Math.sin(ang);
ctx.fillStyle='#000'; ctx.beginPath(); ctx.arc(x,y,4,0,TAU); ctx.fill();
// projections to waves
const startX=ox+20, waveW=w-startX-20, baseY=cy;
// axes
ctx.beginPath(); ctx.moveTo(startX, baseY); ctx.lineTo(startX+waveW, baseY); ctx.moveTo(startX, baseY- R); ctx.lineTo(startX, baseY+R); ctx.strokeStyle='#000'; ctx.stroke();
// sine
ctx.beginPath(); for(let i=0;i<=waveW;i++){
const a= (i/kx); const yy=baseY - R*Math.sin(ang - a);
if(i===0) ctx.moveTo(startX+i,yy); else ctx.lineTo(startX+i,yy);
} ctx.strokeStyle='#000'; ctx.stroke();
// cosine dashed
ctx.setLineDash([6,4]); ctx.beginPath(); for(let i=0;i<=waveW;i++){
const a= (i/kx); const yy=baseY - R*Math.cos(ang - a);
if(i===0) ctx.moveTo(startX+i,yy); else ctx.lineTo(startX+i,yy);
} ctx.stroke(); ctx.setLineDash([]);
// guide lines
ctx.setLineDash([3,3]); ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(startX, baseY - (y-cy)); ctx.moveTo(x,y); ctx.lineTo(startX, baseY - (x-cx)); ctx.strokeStyle='#000'; ctx.stroke(); ctx.setLineDash([]);
requestAnimationFrame((ts)=>{ const now=performance.now(); step(1/60); draw(); });
}
draw();
})();
/* ---------- Function Graphs ---------- */
(function(){
const c=document.getElementById('graph'), ctx=c.getContext('2d');
const radios=[...document.querySelectorAll('input[name=fn]')];
const zoomEl=document.getElementById('g-zoom');
const asymEl=document.getElementById('g-asym');
const marksEl=document.getElementById('g-marks');
function f(name,x){
if(name==='sin') return Math.sin(x);
if(name==='cos') return Math.cos(x);
if(name==='tan') return Math.tan(x);
if(name==='sec') return 1/Math.cos(x);
if(name==='csc') return 1/Math.sin(x);
}
function asymptotes(name,xmin,xmax){
const arr=[];
if(name==='tan' || name==='sec'){ // cos=0 at (π/2)+kπ
for(let k=Math.floor((xmin-PI/2)/PI)-1; k<=Math.ceil((xmax-PI/2)/PI)+1; k++){
arr.push(PI/2 + k*PI);
}
}
if(name==='csc'){ // sin=0 at kπ
for(let k=Math.floor(xmin/PI)-1;k<=Math.ceil(xmax/PI)+1;k++) arr.push(k*PI);
}
return arr.filter(a=>a>xmin && a<xmax);
}
function draw(){
const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
const w=rect.width,h=420; ctx.clearRect(0,0,w,h);
const name=radios.find(r=>r.checked).value, zoom=parseFloat(zoomEl.value);
// world coords
const xMin=-PI*zoom*2, xMax=PI*zoom*2, yMin=-3, yMax=3;
const ox = (-xMin)/(xMax-xMin)*w, oy = (yMax/(yMax-yMin))*h;
grid(ctx,w,h,ox,oy,PI,0.5); axis(ctx,w,h,ox,oy);
// π marks
if(marksEl.checked){
ctx.fillStyle='#000'; ctx.font='12px ui-monospace,monospace';
for(let k=Math.ceil(xMin/ (PI/2)); k<=Math.floor(xMax/(PI/2)); k++){
const x=k*(PI/2); const sx=(x-xMin)/(xMax-xMin)*w;
const label=(k===0)?'0':(k===1)?'π/2':(k===2)?'π':(k===3)?'3π/2':(k===4)?'2π':
(k===-1)?'-π/2':(k===-2)?'-π':(k===-3)?'-3π/2':(Math.abs(k)%2?`${k}·π/2`:`${k/2}π`);
ctx.fillText(label,sx+4,oy+12);
}
}
// asymptotes
if(asymEl.checked){
ctx.save(); ctx.setLineDash([6,6]); ctx.strokeStyle='#000'; ctx.lineWidth=1;
asymptotes(name,xMin,xMax).forEach(a=>{
const sx=(a-xMin)/(xMax-xMin)*w; ctx.beginPath(); ctx.moveTo(sx,0); ctx.lineTo(sx,h); ctx.stroke();
});
ctx.restore();
}
// plot
ctx.beginPath();
for(let i=0;i<w;i++){
const x=xMin + (i/w)*(xMax-xMin);
let y=f(name,x);
if(!isFinite(y) || Math.abs(y)>1e6){ ctx.moveTo(i, -999); continue; }
const sy = (1-(y - yMin)/(yMax-yMin))*h;
if(i===0) ctx.moveTo(i,sy); else ctx.lineTo(i,sy);
}
ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
}
[zoomEl,asymEl,marksEl,...radios].forEach(el=>el.addEventListener('input',draw));
addEventListener('resize',draw); draw();
})();
/* ---------- Inverse Graphs ---------- */
(function(){
const c=document.getElementById('inv'), ctx=c.getContext('2d');
const radios=[...document.querySelectorAll('input[name=inv]')];
function draw(){
const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
const w=rect.width,h=420; ctx.clearRect(0,0,w,h);
const name=radios.find(r=>r.checked).value;
const xMin=-3, xMax=3, yMin=-PI, yMax=PI;
const ox=(-xMin)/(xMax-xMin)*w, oy=(yMax/(yMax-yMin))*h;
grid(ctx,w,h,ox,oy,1,PI/4/PI); axis(ctx,w,h,ox,oy);
ctx.beginPath();
for(let i=0;i<w;i++){
const x=xMin + (i/w)*(xMax-xMin);
let y=(name==='asin')?Math.asin(clamp(x,-1,1)):
(name==='acos')?Math.acos(clamp(x,-1,1)):
Math.atan(x);
const sy=(1-(y - yMin)/(yMax-yMin))*h;
if(i===0) ctx.moveTo(i,sy); else ctx.lineTo(i,sy);
}
ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
// domains
ctx.fillStyle='#000';
const dom = name==='asin' || name==='acos' ? 'domain: [-1,1]' : 'domain: (-∞,∞)';
const ran = name==='asin' ? 'range: [-π/2, π/2]' :
name==='acos' ? 'range: [0, π]' : 'range: (-π/2, π/2)';
ctx.fillText(dom + ' ' + ran, 10, 18);
}
radios.forEach(r=>r.addEventListener('input',draw)); addEventListener('resize',draw); draw();
})();
/* ---------- Transform Sandbox ---------- */
(function(){
const c=document.getElementById('transformer'), ctx=c.getContext('2d');
const A=document.getElementById('t-A'), B=document.getElementById('t-B'),
C=document.getElementById('t-C'), D=document.getElementById('t-D'),
COS=document.getElementById('t-cos'),
P=document.getElementById('t-period'), M=document.getElementById('t-mid'), S=document.getElementById('t-shift');
function draw(){
const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
const w=rect.width,h=420; ctx.clearRect(0,0,w,h);
const a=+A.value,b=+B.value,cph=+C.value,d=+D.value, useCos=COS.checked;
const xMin=-2*PI, xMax=2*PI, yMin=-3, yMax=3;
const ox=(-xMin)/(xMax-xMin)*w, oy=(yMax/(yMax-yMin))*h;
grid(ctx,w,h,ox,oy,PI,0.5); axis(ctx,w,h,ox,oy);
ctx.beginPath();
for(let i=0;i<w;i++){
const x=xMin + (i/w)*(xMax-xMin);
const y=a*((useCos?Math.cos:Math.sin)(b*x - cph)) + d;
const sy=(1-(y - yMin)/(yMax-yMin))*h;
if(i===0) ctx.moveTo(i,sy); else ctx.lineTo(i,sy);
}
ctx.strokeStyle='#000'; ctx.lineWidth=1.6; ctx.stroke();
// readouts
const period=(2*PI/Math.abs(b)); P.textContent=(isFinite(period)?formatPi(period):'—');
M.textContent=d.toFixed(2);
S.textContent=formatPi(cph/b);
}
function formatPi(val){
const s = (Math.abs(val-PI)<1e-4)?'π':
(Math.abs(val-2*PI)<1e-3)?'2π':
(Math.abs(val-PI/2)<1e-4)?'π/2':
(Math.abs(val-PI/3)<1e-4)?'π/3': val.toFixed(2);
return s;
}
[A,B,C,D,COS].forEach(el=>el.addEventListener('input',draw));
addEventListener('resize',draw); draw();
})();
/* ---------- Degree/Radian Ring ---------- */
(function(){
const c=document.getElementById('ring'), ctx=c.getContext('2d');
let hover=null;
c.addEventListener('mousemove',e=>{
const r=c.getBoundingClientRect(), x=e.clientX-r.left, y=e.clientY-r.top;
hover={x,y};
});
c.addEventListener('mouseleave',()=>hover=null);
function draw(){
const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
c.width=rect.width*dpr; c.height=320*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
const w=rect.width,h=320; ctx.clearRect(0,0,w,h);
const cx=w/2, cy=h/2, R=120;
ctx.beginPath(); ctx.arc(cx,cy,R,0,TAU); ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
const angles=[0,PI/6,PI/4,PI/3,PI/2,2*PI/3,3*PI/4,5*PI/6,PI,7*PI/6,5*PI/4,4*PI/3,3*PI/2,5*PI/3,7*PI/4,11*PI/6,TAU];
angles.forEach(a=>{
const x=cx+R*Math.cos(a), y=cy-R*Math.sin(a);
ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(cx+(R+10)*Math.cos(a), cy-(R+10)*Math.sin(a)); ctx.strokeStyle='#000'; ctx.stroke();
const deg=Math.round(a*180/Math.PI);
const rad=(a===0||a===TAU)?'0':(a===PI)?'π':(a===PI/2)?'π/2':(a===3*PI/2)?'3π/2':
(a===PI/6)?'π/6':(a===PI/4)?'π/4':(a===PI/3)?'π/3':
(a===2*PI/3)?'2π/3':(a===3*PI/4)?'3π/4':(a===5*PI/6)?'5π/6':
(a===7*PI/6)?'7π/6':(a===5*PI/4)?'5π/4':(a===4*PI/3)?'4π/3':
(a===5*PI/3)?'5π/3':(a===7*PI/4)?'7π/4':'';
label(ctx,`${deg}° ↔ ${rad}`, x+(x>cx?16:-16), y+(y<cy?-10:10), x>cx?'left':'right');
});
if(hover){
const dx=hover.x-cx, dy=hover.y-cy, a=(Math.atan2(-dy,dx)+TAU)%TAU;
const x=cx+R*Math.cos(a), y=cy-R*Math.sin(a);
ctx.beginPath(); ctx.moveTo(cx,cy); ctx.lineTo(x,y); ctx.strokeStyle='#000'; ctx.stroke();
ctx.beginPath(); ctx.arc(x,y,4,0,TAU); ctx.fillStyle='#000'; ctx.fill();
const deg=(a*180/Math.PI).toFixed(1), rad=a.toFixed(3);
ctx.fillStyle='#000'; ctx.fillText(`${deg}° | ${rad} rad`, cx, cy+R+20);
}
requestAnimationFrame(draw);
}
draw();
})();
/* ---------- Special Angle Table ---------- */
(function(){
const tbl=document.getElementById('angTbl');
const rows=[
[0,'0','0','1','0'],
[30,'π/6','1/2','√3/2','1/√3'],
[45,'π/4','√2/2','√2/2','1'],
[60,'π/3','√3/2','1/2','√3'],
[90,'π/2','1','0','—'],
[120,'2π/3','√3/2','-1/2','-√3'],
[135,'3π/4','√2/2','-√2/2','-1'],
[150,'5π/6','1/2','-√3/2','-1/√3'],
[180,'π','0','-1','0'],
[210,'7π/6','-1/2','-√3/2','1/√3'],
[225,'5π/4','-√2/2','-√2/2','1'],
[240,'4π/3','-√3/2','-1/2','√3'],
[270,'3π/2','-1','0','—'],
[300,'5π/3','-√3/2','1/2','-√3'],
[315,'7π/4','-√2/2','√2/2','-1'],
[330,'11π/6','-1/2','√3/2','-1/√3'],
[360,'2π','0','1','0'],
];
tbl.innerHTML=`<tr><th>Degrees</th><th>Radians</th><th>sin</th><th>cos</th><th>tan</th></tr>`+
rows.map(r=>`<tr><td>${r[0]}°</td><td>${r[1]}</td><td>${r[2]}</td><td>${r[3]}</td><td>${r[4]}</td></tr>`).join('');
})();
</script>
</body>
</html>