html,body{margin: 0;padding: 0;}
body{margin:0px; padding:0px;overflow:hidden;}
let n = 7;
let scale = 200;
let control1, control2;
let slider, methodRadio;
let aLabel, bLabel;
let isDraggingControl1 = false;
let isDraggingControl2 = false;
const colors = {
  bg: '#282a36',
  plot: '#F8F8F2',
  controlDefault: '#4589ff',
  controlDragging: '#FF79C6',
  sumFill: '#44475A7A',
  stroke: '#6272A4',
  uiBg: '#8BE9FD',
  uiText: '#F8F8F2',
  uiHighlight: '#0062ff',
};
let func = (x) => Math.cos(x) + x / 3 + 0.5;

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(colors.bg);

  // Responsivo: Slider de divisão
  slider = createSlider(1, 100, n);
  slider.position(24, windowHeight - 50);
  slider.style('width', (windowWidth - 48) + 'px');
  slider.style('background-color', colors.uiBg);
  slider.style('color', colors.uiText);

  control1 = createVector(100, 0);
  control2 = createVector(500, 0);

  aLabel = createDiv("A");
  bLabel = createDiv("B");
  styleLabel(aLabel, control1);
  styleLabel(bLabel, control2);

  // Radio button de métodos de soma de Riemann
  methodRadio = createRadio();
  methodRadio.option('Esquerda');
  methodRadio.option('Centro');
  methodRadio.option('Direita');
  methodRadio.option('Trapezoidal');
  methodRadio.selected('Centro');
  methodRadio.position(24, windowHeight * 0.05);
  methodRadio.style('color', colors.uiText);
  //methodRadio.style('background-color', colors.uiBg);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  slider.style('width', (windowWidth - 48) + 'px');
  //methodRadio.position(24, windowHeight * 0.05);
}

function draw() {
  background(colors.bg);
  stroke(colors.plot);

  plotFunction();
  drawRiemannSums();
  updateControlPosition(control1, aLabel);
  updateControlPosition(control2, bLabel);
  drawControlPoint(control1, isDraggingControl1);
  drawControlPoint(control2, isDraggingControl2);
  handleCursor();
}

function plotFunction() {
  noFill();
  beginShape();
  const height = windowHeight * 0.8;
  const width = windowWidth - 32;
  for (let x = 0; x <= width; ++x) {
    vertex(x + 16, -func(x / scale) * scale + height);
  }
  endShape();
  rect(16, 0, windowWidth - 32, windowHeight * 0.8);
}

function drawRiemannSums() {
  let start = min(control1.x, control2.x);
  let end = max(control1.x, control2.x);
  let delta = (end - start) / slider.value();
  
  fill(colors.sumFill);
  stroke(colors.stroke);

  const method = methodRadio.value();  // Método selecionado

  for (let x = start; x < end - 0.0001; x += delta) {
    if (method === 'Trapezoidal') {
      drawTrapezoidalSum(x, delta);
    } else {
      drawRectangularSum(x, delta, method);
    }
  }
}

// Aproximação Trapezoidal Correta
function drawTrapezoidalSum(x, delta) {
  let yLeft = -func(x / scale) * scale + windowHeight * 0.8;
  let yRight = -func((x + delta) / scale) * scale + windowHeight * 0.8;
  
  beginShape();
  vertex(x + 16, windowHeight * 0.8);
  vertex(x + 16, yLeft);
  vertex(x + delta + 16, yRight);
  vertex(x + delta + 16, windowHeight * 0.8);
  endShape(CLOSE);
}

// Aproximação para Esquerda, Centro e Direita
function drawRectangularSum(x, delta, method) {
  const baseY = windowHeight * 0.8;
  let y = 0;

  switch (method) {
    case 'Esquerda':
      y = -func(x / scale) * scale + baseY;
      break;
    case 'Centro':
      const midX = (x + x + delta) / 2;
      y = -func(midX / scale) * scale + baseY;
      break;
    case 'Direita':
      y = -func((x + delta) / scale) * scale + baseY;
      break;
    default:
      console.warn('Unknown method:', method);
      return;
  }

  const height = baseY - y;
  rect(x + 16, y, delta, height);
}

function updateControlPosition(control, label) {
  control.y = -func(control.x / scale) * scale;
  styleLabel(label, control);
}

function drawControlPoint(control, isDragging) {
  fill(isDragging ? colors.controlDragging : colors.controlDefault);
  noStroke();
  ellipse(control.x + 16, control.y + windowHeight * 0.8, 12, 12);
}

function handleCursor() {
  if (isMouseOverControl(control1) || isMouseOverControl(control2)) {
    cursor(isDraggingControl1 || isDraggingControl2 ? 'grabbing' : 'grab');
  } else if (!isDraggingControl1 && !isDraggingControl2) {
    cursor('default');
  }
}

function isMouseOverControl(control) {
  return dist(mouseX, mouseY, control.x + 16, control.y + windowHeight * 0.8) < 10;
}

function mousePressed() {
  if (isMouseOverControl(control1)) isDraggingControl1 = true;
  else if (isMouseOverControl(control2)) isDraggingControl2 = true;
}

function mouseReleased() {
  isDraggingControl1 = false;
  isDraggingControl2 = false;
}

function mouseDragged() {
  if (isDraggingControl1) control1.x = constrain(mouseX - 16, 0, windowWidth - 32);
  else if (isDraggingControl2) control2.x = constrain(mouseX - 16, 0, windowWidth - 32);
}

function styleLabel(label, control) {
  label.position(control.x + 16, windowHeight - 100);
  label.style('color', colors.plot);
  label.style('font-family', 'KaTeX_Math');
  label.style('font-size', '22px');
  label.style('text-align', 'center');
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/p5@latest/lib/p5.min.js