yoppa.org


最終課題に向けて: 芸大電力消費量のデータを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;
    }
  }
}

実行結果

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

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


デザイン言語総合講座 – クリエイティブ・コーディング

2000年代に入って「クリエイティブ・コーディング」と呼ばれる、アート、デザイン、建築など、いわゆる「クリエイティブ(創造的)」な作品制作のためのプログラミング環境が登場し注目されるようになりました。この講座では、このクリエイティブ・コーディングの成り立ちを紹介し、さらに将来の展望を考えます。


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

今日の内容

今回は、最終課題にむけて実際のデータを参照しながらどのように進めていくのか実践的に考えていきます。

最終課題で使用するデータとその形式

東京藝術大学の様々な場所でモニターした電力使用料のデータが入手できました。最終課題では、このデータを使用してProcessing.jsでビジュアライズしていきたいと思います。

データは、CSV形式と呼ばれるコンマ(,)区切りのテキストデータで記述されています。それぞれの場所ごとに1行ずつコンマ区切りで記述され、毎時間ごとの電力使用料が並んでいます。

データは1日ごとに別のファイルとして記録されていて、記録された日数分のファイルがデータとして存在しています。

各ファイルの最初の行は、「START」という文字列と、日付が入っています。また最後の行には「END」という文字列が記載されています。

  • ファイル名: yyyymmdd_ENE.CSV
  • 内容
START, yyyy/mm/dd
機器番号, 1時データ, 2時データ, 3時データ ... , 24時データ
機器番号, 1時データ, 2時データ, 3時データ ... , 24時データ
機器番号, 1時データ, 2時データ, 3時データ ... , 24時データ
...
機器番号, 1時データ, 2時データ, 3時データ ... , 24時データ
END

CSVのデータサンプル(2014年3月18日のデータ)

Processing.jsでデータを読み込む

1. 1日分のデータを読み込む(関数バージョン)

では、まず初めにこの形式のデータを読み込む簡単なプログラムを自作してみましょう。

// ファイルを指定して電力データを読み込む
// 関数(Function)バージョン

String date;   // 日付
int data[][];  // データの配列 data[行][列]

void setup() {
  // 画面初期化
  size(800, 1004);
  frameRate(30);
  // 文字サイズ
  textSize(8);
  // データ読み込み関数を実行
  loadData(”20140318_ENE.CSV”);
}

  // 結果を文字で表示
void draw() {
  // 背景
  background(0);
  // 日付
  text(date, 2, 10);
  // 全体を少し右下へ
  pushMatrix();
  translate(2, 20);
  // 行と列の数だけ繰り返し
  for (int i = 0; i < data.length - 1; i++) {
    for (int j = 0; j < 24; j++) {
      // データの数値を文字で画面に表示
      text(data[i][j], (width / 24.0) * j, 9 * i);
    }
  }
  popMatrix();
}

// ファイルを指定してデータを読み込む関数
void loadData(String filename) {
  // 行単位でデータを読み込み
  String[] rows = loadStrings(filename);
  // 最初の行はヘッダーとする
  String[] header = split(rows[0], ”,”);
  // ヘッダーの2番目の項目は日付
  date = header[1];
  // データ配列の準備
  data = new int[rows.length - 1][];
  // コンマ区切りで行のデータを分割
  for (int i = 1; i < rows.length - 1; i++) {
    int[] row = int(split(rows[i], ”,”));
    data[i - 1] = (int[]) subset(row, 1);
  }
}

このプログラムを実行すると、全てのデータがテキストで画面に描画されます。

実行結果

2. 1日分のデータを読み込む(クラスバージョン)

ファイルを指定してデータを読み込む機能は、繰り返し使用する一つの機能となります。関数として使用しても良いのですが、クラスとして実装したほうが後々拡張性やプログラムの可読性を考えると便利です。

「loadData()」関数と全く同じ機能をクラスにまとめてみます。クラス名は「EneTable」としました。クラスのソースは以下のようになります。

// EneTable
// 電力データ読み込み用クラス

class EneTable {
  String date;  // 日付
  int data[][]; // データ

  // コンストラクタ(初期化関数)
  // ファイルを指定して、データをパース
  EneTable(String filename) {
    String[] rows = loadStrings(filename);
    String[] header = split(rows[0], ",");
    date = header[1];    
    data = new int[rows.length - 1][];

    for (int i = 1; i < rows.length - 1; i++) {
      int[] row = int(split(rows[i], ","));
      data[i - 1] = (int[]) subset(row, 1);
    }
  }
}

このクラスを使用した表示のためのメインのプログラムは以下のようになります。

// ファイルを指定して電力データを読み込む
// クラス(Class)バージョン

EneTable ene;

void setup() {
  // 画面初期化
  size(800, 1004);
  frameRate(30);
  textSize(8);

  // データ読み込み用クラスEneTableをインスタンス化(初期化)
  ene = new EneTable("20140318_ENE.CSV");
}

// 結果を文字で表示
void draw() {
  // 背景
  background(0);
  // 日付
  text(ene.date, 2, 10);
  // 全体を少し右下へ
  translate(2, 20);
  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    for (int j = 0; j < 24; j++) {
      // データの数値を文字で画面に表示
      text(ene.data[i][j], (width / 24.0) * j, 9 * i);
    }
  }
}

実行結果

3. 全ての日付のデータを読み込む

次にファイル読み込みの仕組みを工夫して、全ての日付けのデータを順番に表示するように変更してみます。

まず初めにファイル名一覧のテキストファイルを作成します。1ファイル名1行でテキスト形式で作成します。例えば以下のようになります。このファイル名に「filelist.txt」という名前をつけdataフォルダ内に保存しておきます。

20140318_ENE.CSV
20140319_ENE.CSV
20140320_ENE.CSV
...
20140701_ENE.CSV

ファイルリスト(filelist.txt)

このファイルリストを使用して、全ての日時のデータを順番に表示するプログラムを作成してみましょう! 「EneTable」クラスに変更はありません。

// ファイルを指定して電力データを読み込む
// クラス(Class)バージョン

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

void setup() {
  // 画面初期化
  size(800, 1004);
  frameRate(30);
  textSize(8);

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

// 結果を文字で表示
void draw() {
  // 背景
  background(0);
  // 日付
  text(ene.date, 2, 10);
  // 全体を少し右下へ
  translate(2, 20);
  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    for (int j = 0; j < 24; j++) {
      // データの数値を文字で画面に表示
      text(ene.data[i][j], (width / 24.0) * j, 9 * i);
    }
  }

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

実行結果

4. 色を塗りわける

では、少し見せかたの工夫をしていきましょう。数値のテキストだけでなく背景色によってデータを表現してみましょう。

// ファイルを指定して電力データを読み込む
// クラス(Class)バージョン
// 

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

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

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

// 結果を文字で表示
void draw() {
  // 背景
  background(0);
  // 日付
  fill(255);
  text(ene.date, 2, 10);
  pushMatrix();
  // 全体を少し右下へ
  translate(2, 20);
  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    for (int j = 0; j < 24; j++) {
      // データの数値を文字で画面に表示
      float value = map(ene.data[i][j], 0, 100, 0, 255);
      if(value > 255){
        value = 255;
      }
      fill(value, 0, 255-value);
      rect((width / 24.0) * j, 9 * i - 6, (width / 24.0)-1, 8);
      fill(255,127);
      text(ene.data[i][j], (width / 24.0) * j, 9 * i);
    }
  }
  popMatrix();
  // 一つ先のファイルを再読み込み
  // データ番号を更新
  dataNum++;
  // もしファイル数よりも多ければリセット
  if (dataNum > filelist.length - 1) {
    dataNum = 0;
  }
  // クラスを再度初期化する
  ene = new EneTable(filelist[dataNum]);
}

実行結果

5. 色+長さで表現

では、最後に色だけでなく四角形の幅で値を表現してみましょう。さらに1日ごとに1フレームではなく、1時間で1フレームで表現してみましょう。

// ファイルを指定して電力データを読み込む
// クラス(Class)バージョン
// 

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

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

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

// 結果を文字で表示
void draw() {
  // 背景
  background(0);
  // 日付
  fill(255);
  text(ene.date + ” : ”  + hour, 2, 10);
  pushMatrix();
  // 全体を少し右下へ
  translate(2, 20);
  // 行と列の数だけ繰り返し  
  for (int i = 0; i < ene.data.length-1; i++) {
    // データの数値を文字で画面に表示
    float graphWidth = map(ene.data[i][hour], 0, 1000, 0, width);
    if (graphWidth > width) {
      graphWidth = width;
    }
    float value = map(ene.data[i][hour], 0, 100, 0, 255);
    if (value > 255) {
      value = 255;
    }
    fill(value, 0, 255-value);
    rect(0, 9 * i - 6, graphWidth, 8);
  }

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

実行結果

次回はさらに凝ったビジュアライズに挑戦します!

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

今回紹介した全てのサンプルコードは以下からダウンロードが可能です。


Markdownでスライド作成する、Swipeが素晴しい

Markdownを使用してWebベースのスライドが作成できる「Swipe」というWebサービスが素晴しい。いくつか試しにスライドつくってみたのだけれど、Markdownはアイデアをリアルタイムにガシガシと記述できるので、ストレス無くて良いですね。問題は、このサービスが今後安定して続いてくれるか、というところか。GoogleやGithubといった会社にサービス丸ごと買収されたりしないかな…

先日、研究会用に作成したスライドをEmbedしてみるテスト


Tumblrを使う4 – Webサイトを構成する、固定ページとタグ

ここまでやってきた内容で、自分のオリジナルのデザイン(テーマ)でTumblrのサイトを作成できるようになりました。しかし、時系列に投稿が堆積されていくBlog的なサイトは作成できるようになったものの、固定されたページや構造をもった「Webサイト」としてTumblrを活用するには、さらに工夫が必要です。今回はWebサイトとしてTumblrを活用するための方法として、固定ページの作成とタグによる投稿の分類について解説していきます。

スライド資料

スライド資料は下記のリンクから閲覧してください。