Plotly.js 스펙트럼 파장 그래프 만들어보기

스펙트로미터의 측정한 데이터 파장을 색깔을 카로티노이드와 엽록소처럼 채워서 넣는 그래프로 표현하는 스크립트이다.

<div id="plot"></div>
<script>
  // 파장 데이터 (380~700nm, 5nm 간격)
  let x = [];
  for (let i = 380; i <= 700; i += 5) {
    x.push(i);
  }

  // 엽록소 흡수 데이터 (두 개의 가우시안 곡선: ~430nm, ~660nm)
  function chlorophyllAbs(wavelength) {
    let peak1 = Math.exp(-Math.pow((wavelength - 430)/20, 2)); // 청색
    let peak2 = Math.exp(-Math.pow((wavelength - 660)/30, 2)); // 적색
    return 0.9 * peak1 + 1.0 * peak2;
  }

  // 카로티노이드 흡수 데이터 (~480nm 중심, 녹색-청록)
  function carotenoidAbs(wavelength) {
    return 0.6 * Math.exp(-Math.pow((wavelength - 480)/40, 2));
  }

  // 흡수 스펙트럼 생성
  let chl = x.map(w => chlorophyllAbs(w));
  let car = x.map(w => carotenoidAbs(w));
  let total = chl.map((v, i) => v + car[i]);

  // 파장 -> 색상 매핑 (RGB 근사)
  function wavelengthToRGB(w) {
    let r=0, g=0, b=0;
    if (w >= 380 && w < 440) {
      r = -(w - 440) / (440 - 380);
      g = 0.0;
      b = 1.0;
    } else if (w >= 440 && w < 490) {
      r = 0.0;
      g = (w - 440) / (490 - 440);
      b = 1.0;
    } else if (w >= 490 && w < 510) {
      r = 0.0;
      g = 1.0;
      b = -(w - 510) / (510 - 490);
    } else if (w >= 510 && w < 580) {
      r = (w - 510) / (580 - 510);
      g = 1.0;
      b = 0.0;
    } else if (w >= 580 && w < 645) {
      r = 1.0;
      g = -(w - 645) / (645 - 580);
      b = 0.0;
    } else if (w >= 645 && w <= 700) {
      r = 1.0;
      g = 0.0;
      b = 0.0;
    }
    // 감마 보정
    r = Math.pow(r, 0.8);
    g = Math.pow(g, 0.8);
    b = Math.pow(b, 0.8);
    return `rgb(${Math.round(r*255)},${Math.round(g*255)},${Math.round(b*255)})`;
  }

  // 파장 구간별로 fill trace 생성
  let traces = [];
  for (let i = 0; i < x.length - 1; i++) {
    let avgW = (x[i] + x[i+1]) / 2;
    let color = wavelengthToRGB(avgW);
    traces.push({
      x: [x[i], x[i+1]],
      y: [total[i], total[i+1]],
      mode: "lines",
      fill: "tozeroy",
      line: {color: color, width: 2},
      fillcolor: color,
      hoverinfo: "x+y",
      name: `${x[i]}-${x[i+1]} nm`
    });
  }

  let layout = {
    title: "Absorption Spectrum of Chlorophyll & Carotenoids",
    xaxis: {title: "Wavelength (nm)", range: [380, 700]},
    yaxis: {title: "Absorbance", range: [0, 2]},
    showlegend: false
  };

  plot = document.getElementById('plot');
  Plotly.newPlot(plot, traces, layout);
</script>