JavaScript
vue.js
SOM

カラーコードによる自己組織化マップをVue.jsで実装してみた

池谷裕二さんの「単純な脳、複雑な「私」」を読みました。
この本の最後のほうで、自己組織化マップについての話があったので、Vue.jsを使って実装してみました。
ソースコード、およびWebページはGithubで公開しています。

image.png

w(幅)、h(高さ)を指定してinitボタンで初期化。
step(処理回数)を指定してexecuteボタンを押すと指定回数処理を繰り返した結果を表示します。
10×10で1000ステップくらい実行するとグラデーションっぽい感じになる。つまり、近い色が集まるようになります。
マップサイズやステップ数が大きくなると処理に時間がかかります。

image.png

自己組織化マップとは

わかりやすい解説は池谷さんのWebページで見ることができます。
アルゴリズム的には4つのステップに分かれています。

0.初期化。ランダムにマップを配置
1.選択。ランダムに色を決定
2.検索。マップの中から1で決めた色に一番近いセルを検索。
3.暴露。2で見つかったセルとその8近傍のセルに色を混ぜる。

1~3を繰り返す。

ソースコード(抜粋)

Map.vue
export default {
  data () {
    return {
      width: 10,
      height: 10,
      count: 1000,
      cells: []
    };
  },
  created () {
    this.init();
  },
  methods: {
    init: function () { //0.初期化
      this.cells = [];
      for (let y = 0; y < this.height; y++) {
        this.cells.push([]);
        for (let x = 0; x < this.width; x++) {
          this.cells[y].push(new Cell());
        }
      }
    },
    execute: function () { //1~3を指定回数実行する
      for (let i = 0; i < this.count; i++) {
        // 1.選択
        let r = Math.floor(Math.random() * 255);
        let g = Math.floor(Math.random() * 255);
        let b = Math.floor(Math.random() * 255);
        let bmu = this.searchBMU(r, g, b);
        this.expose(bmu, r, g, b);
      }
    },
    searchBMU: function (r, g, b) { //2.検索
      let bmu = {
        x: 0,
        y: 0,
        distance: this.cells[0][0].calcColorDistance(r, g, b)
      };
      for (let y = 0; y < this.height; y++) {
        for (let x = 0; x < this.width; x++) {
          let distance = this.cells[y][x].calcColorDistance(r, g, b);
          if (distance < bmu.distance) {
            bmu.x = x;
            bmu.y = y;
            bmu.distance = distance;
          }
          if (distance === 0) {
            break;
          }
        }
      }
      return bmu;
    },
    expose: function (bmu, r, g, b) { // 3.暴露
      for (let h = -1; h <= 1; h++) {
        for (let w = -1; w <= 1; w++) {
          let x = Math.min(Math.max(bmu.x + w, 0), this.width - 1);
          let y = Math.min(Math.max(bmu.y + h, 0), this.height - 1);
          this.cells[y][x].mixColor(r, g, b, 0.1); // 色の混合比率は0.1
        }
      }
    }
  }
};
Cell.js
export default class Cell {
  constructor () {
    this.r = Math.floor(Math.random() * 255);
    this.g = Math.floor(Math.random() * 255);
    this.b = Math.floor(Math.random() * 255);
  }
  /**
   * スタイルオブジェクトの取得(背景色)
   * @return Object スタイルオブジェクト
   */
  getStyle () {
    return {
      'background-color': `rgb(${this.r},${this.g},${this.b})`
    };
  }

  /**
   * セルの色と引数の色との距離を計算する
   * @param  Number r red
   * @param  Number g green
   * @param  Number b blue
   * @return Number   色距離
   */
  calcColorDistance (r, g, b) {
    return Math.pow((this.r - r), 2) +
           Math.pow((this.g - g), 2) +
           Math.pow((this.b - b), 2);
  }

  /**
   * 現在のセルの色に指定色を混ぜる
   * @param  Number r     red
   * @param  Number g     green
   * @param  Number b     blue
   * @param  Number ratio 混色比率(0~1.0)
   * @return void
   */
  mixColor (r, g, b, ratio) {
    this.r = Math.floor(this.r * (1 - ratio) + r * ratio);
    this.g = Math.floor(this.g * (1 - ratio) + g * ratio);
    this.b = Math.floor(this.b * (1 - ratio) + b * ratio);
  }
}