如何可视化傅里叶级数/傅里叶系数?

How to visualize Fourier series / Fourier coefficients?

我目前在可视化傅立叶级数方面遇到困难。为了找出错误,我尝试了大约 3 次同样的事情,但都是徒劳的。

现在我什至不知道我的代码或对傅里叶级数的理解有什么问题。

我想要做的是像以下 Youtube 视频中所示的东西:https://youtu.be/r6sGWTCMz2k

我想我知道什么是傅里叶级数了。我可以通过展示我以前的作品来证明这一点:

(1)方波逼近

(2) 参数

所以现在我想用参数化的方式绘制更复杂的东西。请让我展示我走过的过程。

① 从svg路径,获取坐标。例如,

// svg path
const d = 'M 0 0 L 20 30 L 10 20 ... ... ... Z';
↓
↓ convert with some processing...
↓
const cx = [0, 20, 10, ...]; // function Fx(t)
const cy = [0, 30, 20, ...]; // function Fy(t)

② 分别从Fx(t)、Fy(t)中得到傅立叶系数。之后,我用得到的系数分别计算傅里叶级数,就可以得到approximated坐标。例如,

Let's say I have a0_x, an_x, bn_x, a0_y, an_y, bn_y.
Then, Fx(t) = a0_x + an_x[1] * cos(1wt) + bn_x[1] * cos(1wt)
                   + an_x[2] * cos(2wt) + bn_x[2] * cos(2wt) + ...;

      Fy(t) = a0_y + an_y[1] * cos(1wt) + bn_y[1] * cos(1wt)
                   + an_y[2] * cos(2wt) + bn_y[2] * cos(2wt) + ...;

Therefore a set of points (Fx(t), Fy(t)) is an approximated path!

这就是全部!剩下的就是画画了!

同时,我对数据进行了如下处理:

const d = [svg path data];
const split = d.split(/[, ]/);
const points = get_points(split);
const normalized = normalize(points);
const populated = populate(normalized, 8);
const cx = populated.x; // Fx(t)
const cy = populated.y; // Fy(t)

/**
 * This function does the below job.
 * populate([0,3,6], 2) => output 0 12 3 45 6
 * populate([0,4,8], 3) => output 0 123 4 567 8
 */
function populate(data, n) {
  if (data.x.length <= 1) throw new Error('NotEnoughData');
  if (n < 1) throw new Error('InvalidNValue');

  const arr_x = new Array(data.x.length + (data.x.length - 1) * n);
  const arr_y = new Array(data.y.length + (data.y.length - 1) * n);

  for (let i = 0; i < data.x.length; i++) {
    arr_x[i * (n + 1)] = data.x[i];
    arr_y[i * (n + 1)] = data.y[i];
  }

  for (let i = 0; i <= arr_x.length - n - 1 - 1; i += (n + 1)) {
    const x_interpolation = (arr_x[i + n + 1] - arr_x[i]) / (n + 1);
    const y_interpolation = (arr_y[i + n + 1] - arr_y[i]) / (n + 1);

    for (let j = 1; j <= n; j++) {
      arr_x[i + j] = arr_x[i] + x_interpolation * j;
      arr_y[i + j] = arr_y[i] + y_interpolation * j;
    }
  }

  return { x: arr_x, y: arr_y };
}

// This function makes all values are in range of [-1, 1].
// I just did it... because I don't want to deal with big numbers (and not want numbers having different magnitude depending on data).
function normalize(obj) {
  const _x = [];
  const _y = [];

  const biggest_x = Math.max(...obj.x);
  const smallest_x = Math.min(...obj.x);
  const final_x = Math.max(Math.abs(biggest_x), Math.abs(smallest_x));

  const biggest_y = Math.max(...obj.y);
  const smallest_y = Math.min(...obj.y);
  const final_y = Math.max(Math.abs(biggest_y), Math.abs(smallest_y));

  for (let i = 0; i < obj.x.length; i++) {
    _x[i] = obj.x[i] / final_x;
    _y[i] = obj.y[i] / final_y;
  }

  return { x: _x, y: _y };
}

// returns Fx(t) and Fy(t) from svg path data
function get_points(arr) {
  const x = [];
  const y = [];
  let i = 0;

  while (i < arr.length) {
    const path_command = arr[i];

    if (path_command === "M") {
      x.push(Number(arr[i + 1]));
      y.push(Number(arr[i + 2]));
      i += 3;
    } else if (path_command === 'm') {
      if (i === 0) {
        x.push(Number(arr[i + 1]));
        y.push(Number(arr[i + 2]));
        i += 3;
      } else {
        x.push(x.at(-1) + Number(arr[i + 1]));
        y.push(y.at(-1) + Number(arr[i + 2]));
        i += 3;
      }
    } else if (path_command === 'L') {
      x.push(Number(arr[i + 1]));
      y.push(Number(arr[i + 2]));
      i += 3;
    } else if (path_command === 'l') {
      x.push(x.at(-1) + Number(arr[i + 1]));
      y.push(y.at(-1) + Number(arr[i + 2]));
      i += 3;
    } else if (path_command === 'H') {
      x.push(Number(arr[i + 1]));
      y.push(y.at(-1));
      i += 2;
    } else if (path_command === 'h') {
      x.push(x.at(-1) + Number(arr[i + 1]));
      y.push(y.at(-1));
      i += 2;
    } else if (path_command === 'V') {
      x.push(x.at(-1));
      y.push(Number(arr[i + 1]));
      i += 2;
    } else if (path_command === 'v') {
      x.push(x.at(-1));
      y.push(y.at(-1) + Number(arr[i + 1]));
      i += 2;
    } else if (path_command === 'Z' || path_command === 'z') {
      i++;
      console.log('reached to z/Z, getting points done');
    } else if (path_command === 'C' || path_command === 'c' || path_command === 'S' || path_command === 's' || path_command === 'Q' || path_command === 'q' || path_command === 'T' || path_command === 't' || path_command === 'A' || path_command === 'a') {
      throw new Error('unsupported path command, getting points aborted');
    } else {
      x.push(x.at(-1) + Number(arr[i]));
      y.push(y.at(-1) + Number(arr[i + 1]));
      i += 2;
    }
  }

  return { x, y };
}

同时,为了计算傅立叶系数,我使用了数值积分。这是代码。

/**
 * This function calculates Riemann sum (area approximation using rectangles).
 * @param {Number} div division number (= number of rectangles to be used)
 * @param {Array | Function} subject subject of integration
 * @param {Number} start where to start integration
 * @param {Number} end where to end integration
 * @param {Number} nth this parameter will be passed to 'subject'
 * @param {Function} paramFn this parameter will be passed to 'subject'
 * @returns {Number} numerical-integrated value
 */
function numerical_integration(div, subject, start, end, nth = null, paramFn = null) {
  if (div < 1) throw new Error(`invalid div; it can't be 0 or 0.x`);

  let sum = 0;
  const STEP = 1 / div;
  const isSubjectArray = Array.isArray(subject);
  
  if (isSubjectArray) {
    for (let t = start; t < end; t++) {
      for (let u = 0; u < div; u++) {
        sum += subject[t + 1] * STEP;
      }
    }
  } else {
    for (let t = start; t < end; t++) {
      for (let u = 0; u < div; u++) {
        const period = end - start;
        const isParamFnArray = Array.isArray(paramFn);

        if (isParamFnArray) sum += subject((t + 1), period, nth, paramFn) * STEP;
        else sum += subject(((t + STEP) + STEP * u), period, nth, paramFn) * STEP;
      }
    }
  }

  return sum;

// console.log(numerical_integration(10, (x) => x ** 3, 0, 2));
}

近似值接近。 For (x) => x, division 10, from 0 to 2, the approximations is 2.1 while actual answer is 2. For (x) => x ** 2, division 10, from 0 to 2, the approximation is 2.87,而实际答案是 2.67。 For (x) => x ** 3, division 10, from 0 to 2, approximation is 4.41, while actual answer is 4.

然后我通过以下方式找到了a0, an, bn: (※ 你可以在中找到傅立叶系数公式)

/**
 * This function will be passed to 'getAn' function.
 * @param {Number} t this function is a function of time
 * @param {Number} period period of a function to be integrated
 * @param {Number} nth integer multiple
 * @param {Array | Function} paramFn
 * @returns {Number} computed value
 */
function fc(t, period, nth, paramFn) {
  const isParamFnArray = Array.isArray(paramFn);
  const w = 2 * Math.PI / period;
  
  if (isParamFnArray) return paramFn[t] * Math.cos(nth * w * t);
  else return paramFn(t) * Math.cos(nth * w * t);
}

// This function will be passed to 'getBn' function.
function fs(t, period, nth, paramFn) {
  const isParamFnArray = Array.isArray(paramFn);
  const w = 2 * Math.PI / period;

  if (isParamFnArray) return paramFn[t] * Math.sin(nth * w * t);
  else return paramFn(t) * Math.sin(nth * w * t);
}

/**
 * This function returns a0 value.
 * @param {Number} period period of a function to be integrated
 * @param {Array | Function} intgFn function to be intergrated
 * @param {Number} div number of rectangles to use
 * @returns {Number} a0 value
 */
// Why * 30? in order to scale up
// Why - 1? because arr[arr.length] is undefined.
function getA0(period, intgFn, div) {
  return 30 * numerical_integration(div, intgFn, 0, period - 1) / period;
}

/**
 * This function returns an values.
 * @param {Number} period period of a function to be integrated
 * @param {Number} div number of rectangles to use
 * @param {Number} howMany number of an values to be calculated
 * @param {Array | Function} paramFn function to be integrated
 * @returns {Array} an values
 */
function getAn(period, div, howMany, paramFn) {
  const an = [];

  for (let n = 1; n <= howMany; n++) {
    const value = 30 * numerical_integration(div, fc, 0, period - 1, n, paramFn) * 2 / period;
    
    an.push(value);
  }

  return an;
}

// This function returns bn values.
function getBn(period, div, howMany, paramFn) {
  const bn = [];

  for (let n = 1; n <= howMany; n++) {
    const value = 30 * numerical_integration(div, fs, 0, period - 1, n, paramFn) * 2 / period;

    bn.push(value);
  }

  return bn;
}

const xa0 = getA0(cx.length, cx, 10);
const xan = getAn(cx.length, 10, 100, cx);
const xbn = getBn(cx.length, 10, 100, cx);

const ya0 = getA0(cy.length, cy, 10);
const yan = getAn(cy.length, 10, 100, cy);
const ybn = getBn(cy.length, 10, 100, cy);

然而,结果并不是我想要的。。。奇怪的形状。。。也许这就是生活吧。。。

下面是canvas绘图代码:

const $cvs = document.createElement('canvas');
const cctx = $cvs.getContext('2d');

$cvs.setAttribute('width', 1000);
$cvs.setAttribute('height', 800);
$cvs.setAttribute('style', 'border: 1px solid black;');
document.body.appendChild($cvs);

window.requestAnimationFrame(draw_tick);

// offset
const xoo = { x: 200, y: 600 }; // x oscillator offset
const yoo = { x: 600, y: 200 }; // y ~

// path
const path = [];

// drawing function
let deg = 0;

function draw_tick() {
  const rAF = window.requestAnimationFrame(draw_tick);

  // initialize
  cctx.clearRect(0, 0, 1000, 800);
  
  // y oscillator
  const py = { x: 0, y: 0 };

  // a0
    // a0 circle
  cctx.beginPath();
  cctx.strokeStyle = 'black';
  cctx.arc(yoo.x + py.x, yoo.y + py.y, Math.abs(ya0), 0, 2 * Math.PI);
  cctx.stroke();

    // a0 line
  cctx.beginPath();
  cctx.strokeStyle = 'black';
  cctx.moveTo(yoo.x + py.x, yoo.y + py.y);

  py.x += ya0 * Math.cos(0 * deg * Math.PI / 180);
  py.y += ya0 * Math.sin(0 * deg * Math.PI / 180);
  cctx.lineTo(yoo.x + py.x, yoo.y + py.y);
  cctx.stroke();
  
  // an
  for (let i = 0; i < yan.length; i++) {
    const radius = yan[i];

    // an circles
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.arc(yoo.x + py.x, yoo.y + py.y, Math.abs(radius), 0, 2 * Math.PI);
    cctx.stroke();

    // an lines
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.moveTo(yoo.x + py.x, yoo.y + py.y);

    py.x += radius * Math.cos((i+1) * deg * Math.PI / 180);
    py.y += radius * Math.sin((i+1) * deg * Math.PI / 180);
    cctx.lineTo(yoo.x + py.x, yoo.y + py.y);
    cctx.stroke();
  }
  
  // bn
  for (let i = 0; i < ybn.length; i++) {
    const radius = ybn[i];

    // bn circles
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.arc(yoo.x + py.x, yoo.y + py.y, Math.abs(radius), 0, 2 * Math.PI);
    cctx.stroke();

    // bn lines
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.moveTo(yoo.x + py.x, yoo.y + py.y);

    py.x += radius * Math.cos((i+1) * deg * Math.PI / 180);
    py.y += radius * Math.sin((i+1) * deg * Math.PI / 180);
    cctx.lineTo(yoo.x + py.x, yoo.y + py.y);
    cctx.stroke();
  }

  // x oscillator
  const px = { x: 0, y: 0 };

  // a0
    // a0 circle
  cctx.beginPath();
  cctx.strokeStyle = 'black';
  cctx.arc(yoo.x + py.x, yoo.y + py.y, Math.abs(xa0), 0, 2 * Math.PI);
  cctx.stroke();

    // a0 line
  cctx.beginPath();
  cctx.strokeStyle = 'black';
  cctx.moveTo(yoo.x + py.x, yoo.y + py.y);

  py.x += xa0 * Math.cos(0 * deg * Math.PI / 180);
  py.y += xa0 * Math.sin(0 * deg * Math.PI / 180);
  cctx.lineTo(yoo.x + py.x, yoo.y + py.y);
  cctx.stroke();
  
  // an
  for (let i = 0; i < xan.length; i++) {
    const radius = xan[i];

    // an circles
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.arc(xoo.x + px.x, xoo.y + px.y, Math.abs(radius), 0, 2 * Math.PI);
    cctx.stroke();

    // an lines
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.moveTo(xoo.x + px.x, xoo.y + px.y);

    px.x += radius * Math.cos((i+1) * deg * Math.PI / 180);
    px.y += radius * Math.sin((i+1) * deg * Math.PI / 180);
    cctx.lineTo(xoo.x + px.x, xoo.y + px.y);
    cctx.stroke();
  }

  // bn
  for (let i = 0; i < xbn.length; i++) {
    const radius = xbn[i];

    // bn circles
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.arc(xoo.x + px.x, xoo.y + px.y, Math.abs(radius), 0, 2 * Math.PI);
    cctx.stroke();

    // bn lines
    cctx.beginPath();
    cctx.strokeStyle = 'black';
    cctx.moveTo(xoo.x + px.x, xoo.y + px.y);

    px.x += radius * Math.cos((i+1) * deg * Math.PI / 180);
    px.y += radius * Math.sin((i+1) * deg * Math.PI / 180);
    cctx.lineTo(xoo.x + px.x, xoo.y + px.y);
    cctx.stroke();
  }

  // y oscillator line
  cctx.strokeStyle = 'black';
  cctx.beginPath();

  cctx.moveTo(yoo.x + py.x, yoo.y + py.y);
  cctx.lineTo(xoo.x + px.x, yoo.y + py.y);

  cctx.stroke();

  // x oscillator line
  cctx.strokeStyle = 'black';
  cctx.beginPath();

  cctx.moveTo(xoo.x + px.x, xoo.y + px.y);
  cctx.lineTo(xoo.x + px.x, yoo.y + py.y);

  cctx.stroke();

  // path
  path.push({ x: px.x, y: py.y });

  cctx.beginPath();
  cctx.strokeStyle = 'black';
  
  cctx.moveTo(200 + path[0].x, 200 + path[0].y);

  for (let i = 0; i < path.length; i++) {
    cctx.lineTo(200 + path[i].x, 200 + path[i].y);
  }

  cctx.stroke();

  // degree update
  if (deg === 359) {
    window.cancelAnimationFrame(rAF);
  } else {
    deg++;
  }
}

所以!我决定要合乎逻辑。首先,我通过在canvas处绘制来检查转换后的路径数据是否正确。下面是canvas代码和数据。

let count = 0;

function draw_tick2() {
  const rAF = window.requestAnimationFrame(draw_tick2);

  const s = 100; // scale up

  // initialize
  cctx.clearRect(0, 0, 1000, 800);

  cctx.beginPath();
  
  // 200 has no meaning I just added it to move the path.
  for (let i = 0; i < count; i++) {
    if (i === 0) cctx.moveTo(200 + s * cx[i], 200 + s * cy[i]);
    else cctx.lineTo(200 + s * cx[i], 200 + s * cy[i]);
  }

  cctx.stroke();

  if (count < cx.length - 1) {
    count++;
  } else {
    window.cancelAnimationFrame(rAF);
  }
}
const paimon = 'm 0,0 -2.38235,-2.87867 -1.58823,-1.29045 -1.9853,-0.893384 -3.17647,-0.39706 1.58824,-1.98529 1.09191,-2.08456 v -2.38235 l -0.79412,-2.87868 1.88603,2.18383 1.6875,1.88602 1.78677,0.99265 1.78676,0.39706 1.78676,-0.19853 -1.6875,1.58824 -0.69485,1.68749 -0.0993,2.084564 0.39706,2.18383 9.62867,3.87132 2.77941,1.9853 4.66544,-1.09192 3.07721,-1.88603 1.9853,-2.58088 -3.97059,0.49633 -3.375,-0.79412 -2.87868,-2.58088 -2.08456,-3.077214 2.38235,1.48897 2.08456,0.19853 3.57353,-0.89338 2.58089,-2.48162 -3.07721,0.39706 -3.87132,-1.88603 -2.97794,-2.08456 -2.48162,-2.87868 -3.87133,-4.06985 -4.06985,-2.68015 -5.95588,-2.58088 -5.85662,-0.79412 -5.45956,0.99265 0.59559,1.6875 -0.99265,1.09191 -0.79412,3.47427 -1.29044,-2.97794 -0.89338,-1.19118 0.79412,-1.48897 1.6875,-0.79412 0.39706,-3.772057 1.48897,1.290441 1.78676,0.09926 -2.08456,-1.985293 1.78677,-0.893382 4.36765,-0.19853 4.86397,0.992648 1.19117,1.091912 -2.38235,1.985301 3.17647,-0.49633 2.87868,-2.680149 -3.57353,-2.580881 -5.45956,-1.488972 h -4.46691 l -3.6728,-3.176471 -0.79412,1.389706 -0.79411,-1.488969 0.69485,-0.595588 -1.58824,-3.871325 -0.39706,3.672795 -0.69485,0.297794 0.89338,1.091911 v 1.091912 h -1.19113 l -0.59559,-0.992648 -1.98529,2.878677 -4.06986,1.588236 -4.26838,1.985293 3.27574,3.871329 2.87867,1.88603 2.58088,0.29779 -2.58088,-1.58823 -0.89338,-2.084566 4.86397,-0.992645 -1.19118,2.382351 h 1.58824 l 1.48897,-1.88603 0.29779,2.77942 -2.38235,2.38235 -3.57353,2.87868 -3.97059,4.86397 -2.08456,3.67279 -2.58088,2.58088 -2.68015,1.09192 -3.17647,0.0993 -1.3897,-0.69485 1.09191,3.17647 2.18382,3.573534 3.375,2.38235 -1.78676,5.85662 -1.38971,6.05514 0.39706,4.36765 1.38971,4.66544 3.87132,4.46691 -0.79412,-3.57352 -0.49632,-4.06986 v -2.48162 l 1.78676,5.85662 3.07721,3.17647 3.07721,1.29044 3.37499,0.79412 2.28309,-0.89338 0.69486,-1.48897 -1.19118,0.49632 -2.48162,-1.98529 -2.28309,-2.87868 2.28309,2.48162 h 0.99265 l 0.69485,-0.49632 0.2978,-1.19118 0.0993,-0.79412 -0.89339,0.59559 -1.58823,-0.99265 -1.29044,-1.3897 -1.19118,-2.38236 -0.89338,-4.86397 -0.0993,-4.56617 0.29779,-4.96324 0.39706,0.89338 1.19118,-0.44669 0.0496,-0.89338 1.09191,0.69485 1.48897,0.2978 1.53861,0.89338 0.99264,0.64522 h -0.79411 l 0.49632,2.43199 -0.44669,1.58823 -1.78676,0.39706 -1.24081,-1.24081 -0.24817,-1.43934 0.84375,-0.94301 1.19118,-0.49633 1.14154,0.94302 0.24816,1.14154 -0.0993,1.48897 -1.83639,0.64523 -1.58824,-1.53861 -0.44669,-1.48897 -0.24816,-2.18382 -1.43934,0.99264 0.0496,-0.99264 -0.44669,1.78676 0.69485,3.12684 1.09192,4.26838 1.78676,1.78677 6.89889,3.02757 -2.53124,0.99265 -3.17647,1.3897 -0.79412,0.39706 0.59559,0.39706 1.34007,-0.69485 0.0496,1.19117 1.98529,-0.39705 2.68015,-0.44669 -0.2978,-1.93567 0.79412,1.58824 2.82905,-0.44669 4.06985,-1.34008 1.04229,-0.59559 -0.2978,-1.78676 -0.34743,-1.73713 -4.9136,2.48162 -2.58088,0.94301 -3.17648,-4.81434 1.53861,0.49633 1.3897,0.0496 1.43935,-0.24816 -1.34008,0.24816 h -1.58824 l -1.41452,-0.54596 3.12684,4.78953 2.63052,-0.89339 4.86397,-2.4568 2.65533,-2.08456 0.39706,-5.90625 -0.84375,1.5386 -1.14155,0.54596 -1.5386,0.19853 -1.29044,-0.89338 -0.59559,-1.09191 -0.24816,-1.73714 0.24816,-1.3897 -2.08456,0.54595 -0.29779,-0.34742 0.34743,-0.49633 0.64522,-0.39706 1.5386,-0.39705 2.18382,-0.19853 1.24081,0.0993 1.14154,0.54596 0.4467,1.43934 -0.19853,1.63786 -0.59559,1.29044 -1.24081,0.89339 -1.43934,-0.39706 -0.99264,-1.09191 -0.0496,-1.19118 0.79412,-0.89338 0.89338,-0.44669 1.19118,-0.0496 0.64522,1.04228 0.34742,0.79412 -0.14889,1.14155 0.99265,-0.4467 0.29779,-1.34007 -0.19853,-4.06985 -1.93566,-0.44669 -2.53125,-1.6875 -2.23346,-1.88603 -2.23345,-4.069864 -0.44669,3.920964 0.64522,4.21875 1.5386,3.92096 0.74448,0.44669 h -1.73713 l -2.18383,-0.54596 -3.12684,-2.08456 -1.58823,-2.28309 -1.14154,-2.08456 -1.29044,-3.871324 -1.38971,2.481624 -1.48897,2.63051 -0.94302,1.9853 3.8217,-6.948534 1.29044,3.672794 2.33272,3.92096 2.9283,2.13419 0.49633,0.44669 2.28309,0.49632 h 1.63787 l -0.69485,-0.69485 -0.84375,-1.93566 -1.34008,-5.80698 0.44669,-3.970594 2.33273,4.069854 4.56617,3.47426 2.08456,0.59559 0.19853,2.82905 -0.0496,3.97058 -0.0993,6.00552 -0.54595,3.02757 -1.58824,2.77941 -1.5386,0.89339 -1.19118,0.24816 -1.48897,-0.69485 -0.69485,-0.1489 0.69485,1.24081 1.43934,1.6875 2.68015,1.19117 3.17647,0.2978 3.77206,-2.23346 1.3897,-2.77941 0.89339,-3.82169 0.0496,-3.375 0.14889,6.25368 -1.14154,5.11213 -2.08456,3.27573 -2.08456,1.6875 -1.88603,0.59559 -2.28308,-0.79412 1.78676,1.6875 4.9136,1.88603 2.43199,0.2978 2.68015,-0.39706 2.72977,-1.09191 3.62317,-3.27574 0.89338,-3.97059 0.49632,-3.57353 -0.0993,-2.87867 -0.39706,-3.17647 -0.49632,-3.07721 1.98529,3.47427 1.19117,2.18382 0.39706,1.29044 0.39706,-2.28309 -0.39706,-3.0772 -1.29044,-3.77206 -1.29044,-2.87868 -1.6875,-3.27573 -10.125,-4.16912 z';

我是★Paimon chan★。因此证明数据没有任何缺陷,因为所有数据都绘制正确。

接下来,我绘制了近似的(Fx(t),Fy(t))点,以便检查是否有问题。结果证明有问题。但我不明白这是什么问题。同时这条路也很有趣;路径的开始部分看起来像发夹。

这是绘图代码:

function approxFn(t) {
  let x = xa0;
  let y = ya0;

  for (let i = 0; i < xan.length; i++) {
    x += xan[i] * Math.cos(2 * Math.PI * i * t / cx.length);
    x += xbn[i] * Math.sin(2 * Math.PI * i * t / cx.length);
    y += yan[i] * Math.cos(2 * Math.PI * i * t / cx.length);
    y += ybn[i] * Math.sin(2 * Math.PI * i * t / cx.length);
  }

  return { x, y };
}

function draw_tick3() {
  const rAF = window.requestAnimationFrame(draw_tick3);

  const s = 5;

  // initialize
  cctx.clearRect(0, 0, 1000, 800);

  cctx.beginPath();

  for (let t = 0; t < count; t++) {
    if (count === 0) cctx.moveTo(200 + s * approxFn(t).x, 200 + s * approxFn(t).y);
    else cctx.lineTo(200 + s * approxFn(t).x, 200 + s * approxFn(t).y);
  }

  cctx.stroke();

  if (count < cx.length - 1) {
    count++;
  } else {
    window.cancelAnimationFrame(rAF);
  }
}

以上是我的js文件中的全部代码。我在哪里犯了错误?这是一个谜!我知道这个问题是一个非常严肃的长问题。但是请帮助我!我想实现陈派蒙! ㅠwㅠ

※(本节与本题无关)同时我在复数平面中成功绘制路径。如果您有兴趣,请查看我的作品...我想为此添加圆圈,但我不知道在这种情况下 'radius' 是什么。

// You can see that I used real part for x and imaginary part for y.

for (let i = 0; i <= count; i++) {
    if (i === 0) {
      cctx.moveTo(coords[i].real * scaler + paimonPosition, coords[i].imag * scaler + paimonPosition);
    } else {
      cctx.lineTo(coords[i].real * scaler + paimonPosition, coords[i].imag * scaler + paimonPosition);
    }
  }

这就是结果。但让我感到困惑的是 cn = -5000 ~ 5000 的情况。据我了解,cn 越大,原始波越准确。但是为什么cn这么大就崩溃了?

总之,非常感谢您阅读这么长的问题!

你好!

首先,你的代码有错误...

  1. 您没有考虑在绘图命令之后出现值序列的情况。例如,您的 get_points 函数无法处理 h 0 1 2.

    这样的情况
  2. 当前 get_points 函数无法处理第二个 m 绘图命令。如果你有多个路径,你需要手动连接字符串。

  3. 需要手动设置m x y为m 0 0,否则看不到canvas绘图。 (可能数值太小画不出来)

其次,简而言之,如果在xy平面中近似f(t),则无法绘制具有固定大小的旋转矢量的形状。因为你逼近的不是形状本身,而是形状的坐标。

第三个,当您尝试绘制近似数据时形状怪异的原因是您的 approxFn() 函数。

x += xan[i] * Math.cos(2 * Math.PI * i * t / cx.length);
x += xbn[i] * Math.sin(2 * Math.PI * i * t / cx.length);
y += yan[i] * Math.cos(2 * Math.PI * i * t / cx.length);
y += ybn[i] * Math.sin(2 * Math.PI * i * t / cx.length);

不是t,(t+1)是正确的。你的大概数据没有问题。

第四,所以如果你想要旋转向量,你需要采取复平面的方法。在这种情况下,圆的半径是实部向量和虚部向量的和向量的大小(勾股定理)。

第五,Cn公式中,你漏了1/T

第六,崩溃的原因是...我不知道确切的原因,但我认为数值积分and/or找到Cn是错误的。我写的新代码不会在高Cn时崩溃。

p.s。我写了一些关于傅立叶级数的文章。有兴趣请看:https://logic-finder.github.io/FourierSeriesExploration/opening/opening-en.html