Back to sketches →
    
    let tSlide;
let fourier;

let axes;

let fourierJSON;

function setup() {
  sketchesCreateCanvas(800, 500);

  let fourierClasses = {
    "britain" : new FourierClass(Fourier.JSONCoef("britain"), 100, false),
    "ukraine" : new FourierClass(Fourier.JSONCoef("ukraine"), 100, false),
    "treble": new FourierClass(Fourier.JSONCoef("treble"), 100, false),
    "linear": new FourierClass(Fourier.LinearCoef, 100),
    "step": new FourierClass(Fourier.StepCoef, 100),
    "ellipse": new FourierClass(Fourier.EllipseCoef, 100),
    "fish": new FourierClass(Fourier.FishCoef, 100)
  };

  let currentFourierClass = fourierClasses["britain"];

  Fourier.scale = 200;
  let initalJ = 100;
  fourier = currentFourierClass.createFourier(initalJ);

  tSlide = createSlider(0, 1, 0, 0);
  tSlide.label("");
  tSlide.size(350);
  tSlide.id("tSlide");

  dropdown = createSelect();

  jInput = createInput(initalJ, "number");
  jInput.label("Number of coefficients:");

  dropdown.option("Britain", "britain");
  dropdown.option("Ukraine", "ukraine");
  dropdown.option("Treble Clef", "treble");
  dropdown.option("Linear", "linear");
  dropdown.option("Step", "step");
  
  dropdown.label("Function:")
  dropdown.changed(() => {
    currentFourierClass = fourierClasses[dropdown.value()];
    fourier = currentFourierClass.createFourier(parseInt(jInput.value()));
  });

  jInput.changed(() => {
    fourier = currentFourierClass.createFourier(parseInt(jInput.value()));
  });

  axes = new Axes(createVector(100, 250), 300);
}

class FourierClass {

  constructor(coefficientFunction, maxK, real = true) {
    this.coefficientFunction = coefficientFunction;
    this.maxK = maxK;
    this.real = real;
  }

  createFourier(K) {
    let last = this.real ? new RealFourierParent(-300, 300) : new ComplexFourierParent();

    for (let k = 0; k < K; k++) {
      for (let s = -1; s <= 1; s += 2) {
        let coeff = this.coefficientFunction(s * k);
        if (math.abs(coeff) < 0.00001)
          continue;
        last = new FourierComponent(coeff, s * k, last);

        if (k == 0)
          break;
      }
    }

    return last;
  }
}

function draw() {
  background(240);
  axes.draw();

  translate(width / 2, height / 2);
  rotate(-HALF_PI);

  noFill(0);
  stroke(0, 0, 0, 100);
  fourier.drawRecursive(tSlide.value());

  stroke(255, 0, 0);
  let end = fourier.end(tSlide.value());
  strokeWeight(10);
  point(end.x, end.y);

  strokeWeight(1);
  stroke(255, 0, 0);
  fourier.drawUpto(tSlide.value(), 1000);
}

class Axes {
  constructor(origin, scale) {
    this.origin = origin;
    this.scale = scale;
  }

  draw() {
    stroke(200);
    strokeWeight(1);
    line(this.origin.x, 0, this.origin.x, height);
    line(0, this.origin.y, width, this.origin.y);
  }
}

class Fourier{
  static scale = 1;

  static LinearCoef(k) {
    if (k == 0)
      return math.complex(0, 0);
    
    return math.complex(0, -2 * k * math.PI).inverse();
  }

  static StepCoef(k) {
    if (k % 2 == 0)
      return math.complex(0, 0);

    return math.complex(0, 1 / (math.PI * k));
  }

  static EllipseCoef(k) {
    switch (k) {
      case 1:
        return math.complex(2, 0);
      case -1:
        return math.complex(0.5, 0);
      default:
        return math.complex(0, 0);
    }
  }

  static FishCoef(k) {
    switch (k) {
      case 1:
        return math.complex(0, 1);
      case -1:
        return math.complex(0, 0.9);
      case 2:
        return math.complex(0, -0.5);
      default:
        return math.complex(0, 0);
    }
  }

  static JSONCoef(name) {
    function get_coefficient(k) {
      let coeffs = fourierJSON[name]['coefficients'];
      if (coeffs.hasOwnProperty(k)) {
        let [real, imag] = coeffs[k];
        return math.complex(imag, real);
      }
      return math.complex(0, 0);
    }
    return get_coefficient;
  }
}

class FourierComponent {
  constructor(coefficient, frequency, parent) {
    this.coefficient = math.multiply(coefficient, Fourier.scale);
    this.coefficentPolar = this.coefficient.toPolar();
    this.frequency = frequency;
    this.parent = parent;
    this.cache = {};
  }
  
  center(t) {
    return this.parent.end(t);
  }

  end(t) {
    if (this.cache.hasOwnProperty(t))
      return this.cache[t];

    let angle = this.coefficentPolar.phi + this.frequency * t * Math.PI * 2;
    let end = createVector(
      this.coefficentPolar.r * Math.cos(angle),
      this.coefficentPolar.r * Math.sin(angle)
    ).add(this.center(t));
    this.cache[t] = end;
    return end;
  }

  draw(t) {
    strokeWeight(1);
    let center = this.center(t);
    let end = this.end(t);

    circle(center.x, center.y, this.coefficentPolar.r * 2);
    line(center.x, center.y, end.x, end.y);

    strokeWeight(4);
    point(end.x, end.y);
  }

  drawRecursive(t) {
    this.draw(t);
    this.parent.drawRecursive(t);
  }

  drawUpto(t, n = 100) {
    let last = this.end(0);
    for (let i = 0; i < n * t; i++) {
      let newP = this.end(i / n);
      line(last.x, last.y, newP.x, newP.y);
      last = newP;
    }
    let finalP = this.end(t);
    line(last.x, last.y, finalP.x, finalP.y);
  }
}

class ComplexFourierParent {
  end(t) {
    return createVector(0, 0);
  }

  drawRecursive(t) {
    strokeWeight(4);
    point(0, 0);
  }
}

class RealFourierParent {
  constructor(min = -1, max = 1) {
    this.min = min;
    this.max = max;
  }

  end(t) {
    return createVector(0, map(t, 0, 1, this.min, this.max));
  }

  drawRecursive(t) {
    strokeWeight(4);
    point(this.end(t).x, this.end(t).y);
  }
}
    
  
Return to Sketch