<div id="controls">
  <div>
    <p>Frequency: <span id="cycles">1</span>Hz</p>
    <p class="holder">
      <span>1Hz</span>
      <input type="range" id="hertz" min="1" max="100" step="0.5" value="1">
      <span>100Hz</span>
    </p>
  </div>
  <div>
    <p>Amplitude: <span id="amplitudeLabel">1</span></p>
    <p class="holder">
      <span>0</span>
      <input type="range" id="amplitude" min="0" max="1" step="0.001" value="1">
      <span>1</span>
    </p>
  </div>
    <div>
    <p>Phase Shift: <span id="shiftLabel">0</span>&pi;</p>
    <p class="holder">
      <span>0</span>
      <input type="range" id="shift" min="0" max="3.15" step="0.01" value="0">
      <span>2&pi;</span>
    </p>
  </div>
</div>
<canvas id="chart" height="600" width="1440"></canvas> 
body
{
  font-family:"Microsoft Sans Serif","sans";
  color: lightgrey;
  background-color:#1d3030;
}

#controls
{
  margin:1em;
  diplay:flex;
}

#controls div
{
  display: inline-block;
  flex: 1 1 30%;
  margin-right:0.5em;
}

p
{
  background-color:#305050;
  width:300px;
  border-radius: 0.3em;
  padding:0.5em;
  margin:0;
  margin-bottom:3px;
}

.holder
{
  text-align: center;
}

.holder span
{
  vertical-align:top;
}
const chartElement = document.getElementById('chart');
const w = chartElement.width;
const h = chartElement.height;
const midW = w/2;
const midH = h/2;
const chartContext = chartElement.getContext('2d');
const hertzSlider = document.getElementById('hertz');
const cyclesLabel = document.getElementById('cycles');
let frequency = 1;

hertzSlider.addEventListener('input', e =>
{
  cyclesLabel.innerHTML = e.target.value;
  frequency = e.target.value;
  plotSine();
});

const amplitudeSlider = document.getElementById('amplitude');
const amplitudeLabel = document.getElementById('amplitudeLabel');
let amplitude = 1;

amplitudeSlider.addEventListener('input', e =>
{
  amplitudeLabel.innerHTML = e.target.value;
  amplitude = e.target.value;
  plotSine();
});

const shiftSlider = document.getElementById('shift');
const shiftLabel = document.getElementById('shiftLabel');
let shift = 0;

shiftSlider.setAttribute('max', Math.PI*2);
shiftSlider.setAttribute('step', Math.PI/100);

shiftSlider.addEventListener('input', e =>
{
  shiftLabel.innerHTML = (e.target.value / Math.PI).toFixed(2);
  shift = parseFloat(e.target.value);
  plotSine();
});

function getY(x, zeroOffsetX, zeroOffsetY)
{
  let degrees = ((x-zeroOffsetX)/w) * 720;
  let rads = (degrees/360)*Math.PI*2;
  let value = (rads)*frequency;
  let y = (Math.sin(value+shift)*midH*amplitude) + zeroOffsetY;
  return y;
}

function plotSine()
{
  chartContext.strokeStyle = 'limegreen';
  chartContext.fillStyle = 'black';
  chartContext.fillRect(0,0,w,h);
  chartContext.lineWidth = 1;
  chartContext.beginPath();
  chartContext.moveTo(midW, 0);
  chartContext.lineTo(midW, h);
  chartContext.moveTo(0, midH);
  chartContext.lineTo(w, midH);
  chartContext.stroke();
  
  chartContext.fillStyle = 'limegreen';
  chartContext.font = '1em monospace';
  chartContext.fillText('0 seconds', 0, midH+15);
  chartContext.fillText('1', midW, midH+15);
  chartContext.fillText('2', w-10, midH+15);
  chartContext.fillText('1', midW-15, 15);
  chartContext.fillText('0', midW-15, midH);
  chartContext.fillText('-1', midW-25, h);

  const zeroOffsetX = midW;
  const zeroOffsetY = midH;
  const T = 1/frequency;
  chartContext.lineWidth = 2;
  chartContext.beginPath();
  chartContext.moveTo(0, getY(0, zeroOffsetX, zeroOffsetY));
  for(let x = 0; x < w; x += T)
  {
    let y = getY(x, zeroOffsetX, zeroOffsetY);
    chartContext.lineTo(x, y);
  }
  chartContext.stroke(); 
}

plotSine();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.