<div class="container">
  <h2><span>Stonks</span>, the sparkline renderer</h2>
<p>文章内の任意の場所に<span class="stonk"></span>このように折れ線グラフを挿入する事が出来ます。折れ線グラフのデータは配列で管理され、<span class="stonk"></span>指定のclassを付与した要素の順に割り当てられます。</p>

<section>
  <p>例えばこちらのグラフ<span class="stonk"></span>は3つ目のデータを元に作られたグラフです</p>
  </section>
</div>
body, html {
  height: 100%;
  font-size: 18px;
  line-height: 1.5;
}

* {
  box-sizing: border-box;
}

h2 span {
  color: #E3342F;
}

h2 {
  margin-bottom: 2rem;
}

body {
  padding: 2rem;
  background: #f7fafc;
  font-family: system-ui, sans-serif;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: #4a5568;
}

.container {
  text-align: center;
  max-width: 640px;
}

section {
  margin-top: 2rem;
  font-size: 0.8rem;
}
/**
 * Data
 */

let stonks = document.querySelectorAll(".stonk");

data = [
  [
    420,
    700,
    500,
    480,
    460,
    490,
    390,
    420,
    550,
    340,
    320,
    240,
    180,
    160,
    200,
    40,
    0
  ],
  [1, 3, 2, 3, 1, 2, 3],
  [13, 30, 12, 3, 51, 20, 31]
];

const options = {
  lineWidth: 2,
  color: "#E3342F"
};

/**
 * Class
 */

class Stonk {
  constructor(wrapper, data, settings = {}) {
    const defaults = {
      width: 80,
      color: "#000000",
      lineWidth: 1
    };

    this.settings = Object.assign(defaults, settings);
    this.wrapper = wrapper;
    this.data = data;
    this.normalData = [];
    this.canvas = null;
    this.ctx = null;
    this.dimes = { w: 0, h: 0 };
  }

  draw() {
    this._buildCanvas();
    this._normalizeData(this.data);
    this._drawData();
  }

  _drawData() {
    let ctx = this.ctx;
    let { w, h } = this.dimes;
    w = w - this.settings.lineWidth / 2;
    h = h - this.settings.lineWidth / 2;
    let nd = this.normalData;
    let step = w / (this.data.length - 1);
    let tick = this.settings.lineWidth / 2;

    ctx.strokeStyle = this.settings.color;
    ctx.lineWidth = this.settings.lineWidth;

    ctx.beginPath();

    ctx.moveTo(tick, h - nd[0]);

    for (var i = 1; i <= nd.length - 1; i++) {
      tick += step;
      ctx.lineTo(tick, h - nd[i]);
    }

    ctx.stroke();
  }

  _buildCanvas() {
    let canvas = document.createElement("canvas");
    let absoluteDimes;

    this.wrapper.style.cssText += `
      position: relative;
      display: inline-block;
      line-height: 1;
      height: 0.9em;
    `;

    // Temporarily set the height so we can calculate the bounding
    canvas.style.height = "100%";
    canvas.style.width = `${this.settings.width}px`;

    // Add it to the document to return the bounding
    this.canvas = canvas;
    this.ctx = this.canvas.getContext("2d");
    this.wrapper.appendChild(this.canvas);
    absoluteDimes = this.canvas.getBoundingClientRect();

    const { width: w, height: h } = absoluteDimes;
    this.dimes = { w, h: Math.floor(h) };

    // Set absolute attributes
    this.canvas.setAttribute("height", this.dimes.h);
    this.canvas.setAttribute("width", this.dimes.w);

    // Clean up
    this.canvas.removeAttribute("style");
  }

  // Utils

  _normalizeData(array) {
    let min = Math.min(...array);
    let max = Math.max(...array);

    this.data.forEach((v) => {
      let percentage = this._scaleBetween(v, 0, 100, min, max);
      let h = (this.dimes.h - this.settings.lineWidth) * (percentage / 100);
      this.normalData.push(h);
    });
  }

  _scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
    return (
      ((maxAllowed - minAllowed) * (unscaledNum - min)) / (max - min) +
      minAllowed
    );
  }
}

/**
 * Mount
 */

stonks.forEach((s, idx) => {
  let stonk = new Stonk(s, data[idx], options).draw();
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.