yoppa.org


芸大 – 情報編集(Web) 2014

最終課題に向けて: 芸大電力消費量のデータをProcessing.jsでビジュアライズする 02

今回も引き続き、芸大の電力使用料のデータを使用したProcessing.jsによるビジュアライズにとりくみます。今回は、より自由な発想でデータを使った表現に挑戦していきましょう。実際に動くサンプルを紹介しながら、考えていきます。

制作テンプレート

前回解説した、全てのデータファイルを順番に読み込んで「data[行][列]」という二次元配列の形式に変換するところまでの、テンプレートを作成しました。この制作テンプレートを出発点にオリジナルの表現に挑戦すると良いでしょう。データファイルを読み込むためのクラス「EneTable」クラスは、前回のものをそのまま使用しています。下記からZip形式でデータ一式をダウンロード可能です。

/*
 * 情報編集(web)2014
 * 最終課題制作テンプレート
 * 2014.07.11
 *
 */

EneTable ene; // ファイル読込のためのクラス
String[] filelist; // データファイル名リスト
int dataNum = 0; // データ番号
int hour = 0; // 現在の時間(0〜23)

// 初期化
void setup() {
  // 画面初期化
  size(800, 600);
  noStroke();
  frameRate(30);
  textSize(14);
  // ファイルリストの読み込み
  filelist = loadStrings("filelist.txt"); 
  // データ読み込み用クラスEneTableをインスタンス化(初期化)
  ene = new EneTable(filelist[dataNum]);
}

// 結果を表示
void draw() {
  // 背景
  background(0);

  // ================================================
  // !!!!! このループ内でデータを表現する !!!!!
  for (int i = 0; i < ene.data.length-1; i++) {
    // データの数値を表示
    // ene.data[i][hour] で値を取得する
  }
  // ================================================

  // 日付を画面に出力
  fill(255);
  text(ene.date + " "  + hour + ":00", 5, 20);

  // データ番号を更新
  hour++;
  if (hour > 23) {
    hour = 0;
    dataNum++;
    // もしファイル数よりも多ければ最初に戻る
    if (dataNum > filelist.length - 1) {
      dataNum = 0;
    }
    // クラスを再度初期化して次のデータファイルを読み込む
    ene = new EneTable(filelist[dataNum]);
  }
}

放射状に並べる

前回最後に紹介した、それぞれの場所ごとの棒グラフの配置を、放射状に並べ替えてみました。データ抽出のためのクラス「EneTable」は、前回のものと同じものをそのまま使用しています。

screenshot_277

EneTable ene;
String[] filelist;
int dataNum = 0;
int hour = 0;

void setup() {
  // 画面初期化
  size(800, 600);
  noStroke();
  frameRate(30);
  textSize(14);

  filelist = loadStrings("filelist.txt"); 
  // データ読み込み用クラスEneTableをインスタンス化(初期化)
  ene = new EneTable(filelist[dataNum]);
}

// 結果を表示
void draw() {
  // 背景
  background(0);
  // 日付
  fill(255);
  text(ene.date + " "  + hour + ":00", 5, 20);
  pushMatrix();

  translate(width/2, height/2);
  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    // データの数値を表示
    float graphWidth = map(ene.data[i][hour], 0, 1000, 0, width/2);
    if (graphWidth > width) {
      graphWidth = width;
    }
    float value = map(ene.data[i][hour], 0, 100, 0, 255);
    if (value > 255) {
      value = 255;
    }
    fill(value, value, 0, value);
    rect(0, -1, graphWidth, 2);
    rotate(PI * 2.0 / 109.0);
  }
  popMatrix();
  // 一つ先のファイルを再読み込み
  // データ番号を更新
  hour++;
  if (hour > 23) {
    hour = 0;
    dataNum++;
    // もしファイル数よりも多ければリセット
    if (dataNum > filelist.length - 1) {
      dataNum = 0;
    }
    // クラスを再度初期化する
    ene = new EneTable(filelist[dataNum]);
  }
}

実行結果

円の大きさで表現する

それぞれの場所を一つの円とします。この円をランダムな場所に配置して、電力使用の変化を半径に対応させてみます。

screenshot_278

EneTable ene;
String[] filelist;
int dataNum = 0;
int hour = 0;
PVector[] pos = new PVector[110];

void setup() {
  // 画面初期化
  size(800, 600);
  noStroke();
  frameRate(30);
  textSize(14);

  for (int i = 0; i < pos.length; i++) {
    pos[i] = new PVector();
    pos[i].x = random(width);
    pos[i].y = random(height);
  }

  filelist = loadStrings("filelist.txt"); 
  // データ読み込み用クラスEneTableをインスタンス化(初期化)
  ene = new EneTable(filelist[dataNum]);
}

// 結果を表示
void draw() {
  // 背景
  background(0);

  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    // データの数値を表示
    float diameter = map(ene.data[i][hour], 0, 1000, 0, 1000);
    float value = map(ene.data[i][hour], 0, 400, 0, 255);
    if (value > 255) {
      value = 255;
    }
    fill(31, 127, 255, 200);
    ellipse(pos[i].x, pos[i].y, diameter, diameter);
  }

  // 日付
  fill(255);
  text(ene.date + " "  + hour + ":00", 5, 20);

  // 一つ先のファイルを再読み込み
  // データ番号を更新
  hour++;
  if (hour > 23) {
    hour = 0;
    dataNum++;
    // もしファイル数よりも多ければリセット
    if (dataNum > filelist.length - 1) {
      dataNum = 0;
    }
    // クラスを再度初期化する
    ene = new EneTable(filelist[dataNum]);
  }
}

実行結果

円を動かす

さらに円の位置をアニメーションしてみましょう。

screenshot_279

EneTable ene;
String[] filelist;
int dataNum = 0;
int hour = 0;
PVector[] pos = new PVector[110];
PVector[] vel = new PVector[110];

void setup() {
  // 画面初期化
  size(800, 600);
  noStroke();
  frameRate(30);
  textSize(14);

  for (int i = 0; i < pos.length; i++) {
    pos[i] = new PVector();
    pos[i].x = random(width);
    pos[i].y = random(height);
    vel[i] = new PVector();
    vel[i].x = random(-2, 2);
    vel[i].y = random(-2, 2);
  }

  filelist = loadStrings("filelist.txt"); 
  // データ読み込み用クラスEneTableをインスタンス化(初期化)
  ene = new EneTable(filelist[dataNum]);
}

// 結果を表示
void draw() {
  // 背景
  background(0);

  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    // データの数値を表示
    float diameter = map(ene.data[i][hour], 0, 1000, 0, 1000);
    float value = map(ene.data[i][hour], 0, 400, 0, 255);
    if (value > 255) {
      value = 255;
    }
    fill(31, 127, 255, 200);
    ellipse(pos[i].x, pos[i].y, diameter, diameter);
    pos[i].add(vel[i]);
    if (pos[i].x < 0 || pos[i].x > width) {
      vel[i].x *= -1;
    }
    if (pos[i].y < 0 || pos[i].y > height) {
      vel[i].y *= -1;
    }
  }

  // 日付
  fill(255);
  text(ene.date + " "  + hour + ":00", 5, 20);

  // 一つ先のファイルを再読み込み
  // データ番号を更新
  hour++;
  if (hour > 23) {
    hour = 0;
    dataNum++;
    // もしファイル数よりも多ければリセット
    if (dataNum > filelist.length - 1) {
      dataNum = 0;
    }
    // クラスを再度初期化する
    ene = new EneTable(filelist[dataNum]);
  }
}

実行結果

色と大きさでプロット

今度は時間変化に注目します。左から右へ24時間分のデータを並べます。数値が大きいほど、上の位置に大きなサイズで点をうつようにします。

screenshot_280

EneTable ene;
String[] filelist;
int dataNum = 0;
int hour = 0;

void setup() {
  // 画面初期化
  size(800, 600);
  noStroke();
  frameRate(30);
  textSize(14);
  background(0);

  filelist = loadStrings("filelist.txt"); 
  // データ読み込み用クラスEneTableをインスタンス化(初期化)
  ene = new EneTable(filelist[dataNum]);
}

// 結果を表示
void draw() {
  // 背景
  fill(0, 14);
  rect(0, 0, width, height);

  fill(255);
  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    // データの数値を表示
    float y = map(ene.data[i][hour], 0, 1600, height, 0);
    float x = map(hour, 0, 23, 0, width);
    float col = map(ene.data[i][hour], 0, 1200, 0, 255);
    float diameter = map(ene.data[i][hour], 0, 1500, 1, 50);
    fill(col, col, 255-col);
    ellipse(x, y, diameter, diameter);
  }

  // 日付
  fill(0);
  rect(0, 0, 200, 40);
  fill(255);
  text(ene.date + " "  + hour + ":00", 5, 20);

  // 一つ先のファイルを再読み込み
  // データ番号を更新
  hour++;
  if (hour > 23) {
    hour = 0;
    dataNum++;
  }
  // もしファイル数よりも多ければリセット
  if (dataNum > filelist.length - 1) {
    dataNum = 0;
  }
  // クラスを再度初期化する
  ene = new EneTable(filelist[dataNum]);
}

実行結果

パーティクル!

screenshot_281

EneTable ene;
String[] filelist;
int dataNum = 0;
int hour = 0;

ParticleGen[] particle = new ParticleGen[110];

void setup() {
  // 画面初期化
  size(800, 600);
  noStroke();
  frameRate(30);
  textSize(14);

  for (int i = 0; i < particle.length; i++) {
    particle[i] = new ParticleGen();
  }

  filelist = loadStrings("filelist.txt"); 
  // データ読み込み用クラスEneTableをインスタンス化(初期化)
  ene = new EneTable(filelist[dataNum]);
}

// 結果を表示
void draw() {
  // 背景
  background(0);

  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    // データの数値を表示
    int value = int(map(ene.data[i][hour], 0, 1200, 0, 1000));
    float diameter = map(ene.data[i][hour], 0, 1200, 5, 100);
    particle[i].maxParticle = value;
    particle[i].centerDiameter = diameter;
    particle[i].draw();
  }

  // 日付
  fill(255);
  text(ene.date + " "  + hour + ":00", 5, 20);

  // 一つ先のファイルを再読み込み
  // データ番号を更新
  hour++;
  if (hour > 23) {
    hour = 0;
    dataNum++;
    // もしファイル数よりも多ければリセット
    if (dataNum > filelist.length - 1) {
      dataNum = 0;
    }
    // クラスを再度初期化する
    ene = new EneTable(filelist[dataNum]);
  }
}

class ParticleGen {
  PVector center;
  PVector centerVel;
  float centerDiameter;
  ArrayList <PVector> pos;
  ArrayList <PVector> vel;
  int maxParticle;

  ParticleGen() {
    center = new PVector(random(width), random(height)); 
    centerVel = new PVector(random(-0.1, 0.1), random(-0.1, 0.1));
    pos = new ArrayList<PVector>();
    vel = new ArrayList<PVector>();
    maxParticle = 10;
    centerDiameter = 0;
  }

  void draw() {
    fill(200, 100);
    ellipse(center.x, center.y, centerDiameter, centerDiameter);

    PVector v = new PVector(random(-0.5, 0.5), random(-0.5, 0.5));
    PVector p = new PVector(center.x, center.y);
    if (random(100) < maxParticle) {
      vel.add(v);
      pos.add(p);
      if (pos.size() > maxParticle) {
        pos.remove(0);
        vel.remove(0);
      }
    }
    for (int i = 0; i < pos.size (); i++) {
      fill(31, 127, 255, 200);
      ellipse(pos.get(i).x, pos.get(i).y, 2, 2);
      pos.get(i).add(vel.get(i));
    }
    center.add(centerVel);
    if (center.x < 0 || center.x > width) {
      centerVel.x *= -1;
    }
    if (center.y < 0 || center.y > height) {
      centerVel.y *= -1;
    }
  }
}

実行結果

サンプルファイルのダウンロード

ここまでで紹介した全てのサンプルは、下記からダウンロード可能です。