プログラミングとデザインの役割の一つに「データの視覚化」があります。一般的に「インフォグラフィック」と呼ばれるものですが、プログラムをうまく使えばインタラクションのあるインフォグラフィックを作ることも可能です。特に、近年はインターネットの普及によって大量のデータが日々生み出されている状況において、それらのデータを分かりやすく視覚化する必要性が重要視されています。
プログラミングとデザインの結びつきの一つの可能性として、実際に視覚化を体験してみましょう。
5.6.1 データ視覚化の実例
まずは、データ可視化の実例を参照してみましょう。
●Radiation & Food Map in Japan
福島第一原発事故後から現在に至るまでの日本全国の食品中放射性物質検査の結果をインタラクティブマップにしたもの。約157万件(2017年1月現在)の検査結果が視覚化されている。自身のプロジェクト。
●パスケットボールにおけるシュートポイントの視覚化
http://www.nytimes.com/interactive/2012/06/11/sports/basketball/nba-shot-analysis.html
●ガンジス川の水質汚染
https://graphics.reuters.com/INDIA-RIVER/010081TW39P/index.html
インド政府は、10億のヒンズー教徒が崇拝し4億人の水源のガンジス川を救うために闘っている。このプロジェクトは、川が日常的に処理しなければならない汚染と、水質がいかに悪いかを視覚化している。
●オバマ政権における2013年の国家予算提案の内訳
http://www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html
●アメリカにおける伝染病の拡散とワクチンの関係を視覚化したマップ
http://graphics.wsj.com/infectious-diseases-and-vaccines/
70年以上の罹患者数のデータが州ごとに色分けされている。このような手法は一般的にヒートマップと呼ばれる。
●イギリスのエネルギー消費
http://www.evoenergy.co.uk/uk-energy-guide/
●視覚化の様々な実験的サイト
http://infosthetics.com/http://datavisualization.ch/showcases/http://www.visualcomplexity.com/vc/
どうでしょうか。様々な視覚化の実例がありますね。
5.6.2 棒グラフをプログラムで記述してみる
それでは、実際の視覚化の第一歩として棒グラフをプログラムで記述するところから始めます。
int a = 10; //a〜fはそれぞれのバーの値
int b = 14;
int c = 20;
int d = 80;
int e = 2;
int f = 18;
float bar_w; //バーの横幅
size(400, 400);
background(255);
bar_w = 40; //バーの横幅を40ピクセルにする
fill(127); //グレーで描画
rect(0, height - a, bar_w, a); //一つずつバーを描画
rect(bar_w, height - b, bar_w, b);
rect(bar_w*2, height - c, bar_w, c);
rect(bar_w*3, height - d, bar_w, d);
rect(bar_w*4, height - e, bar_w, e);
rect(bar_w*5, height - f, bar_w, f);
これを、配列で書き換えてみましょう。コードはかなり短くなります。こういった用途も、配列の利点を表しています。また、数値もバーの上に表示してみます。
PFont myFont; //フォント用変数
int[] value = {10, 14, 20, 80, 2, 18};
String[] s = {"a", "b", "c", "d", "e", "f"};
float barW; //バーの横幅
size(400, 400);
background(255);
myFont = createFont("Arial", 12); //フォント作成
textFont(myFont, 12); //テキストのサイズを設定
textAlign(CENTER);
barW = 40; //バーの横幅を40ピクセルにする
//バーとテキストを描画。value.lenghは配列の要素数を返す
for (int i = 0; i < value.length; i ++) {
//バーのx座標, y座標を計算
float x = barW*i;
float y = height - value[i];
fill(127); //グレーで描画
rect(x, y, barW, value[i]);
//テキスト表示
fill(255, 0, 0); //赤で描画
text(s[i] + " = " + value[i], x + barW/2, y - 10);
}
次に、もう少し柔軟な表示が可能になるプログラムに書き換えてみます。バーのサイズが画面の横幅に応じて変化します。
PFont myFont; //フォント用変数
int[] value = {10, 14, 20, 80, 2, 18};
String[] s = {"a", "b", "c", "d", "e", "f"};
float barW; //バーの横幅
size(400, 400);
background(255);
myFont = createFont("Arial", 12); //フォント作成
textFont(myFont, 12); //テキストのサイズを設定
textAlign(CENTER);
barW = width/float(value.length); //バーの横幅を決定
//バーとテキストを描画。value.lenghは配列の要素数を返す
for (int i = 0; i < value.length; i ++) {
//バーのx座標, y座標を計算
float x = barW*i;
float y = height - value[i];
fill(127); //グレーで描画
rect(x, y, barW, value[i]);
//テキスト表示
fill(255, 0, 0); //赤で描画
text(s[i] + " = " + value[i], x + barW/2, y - 10);
}
このままだとちょっと高さが低くて差が分かりづらいので、バーの高さを画面の高さに合わせて拡大します。
PFont myFont; //フォント用変数
int[] value = {10, 14, 20, 80, 2, 18};
String[] s = {"a", "b", "c", "d", "e", "f"};
float barW; //バーの横幅
float barH;
float resize = 4.0;
size(400, 400);
background(255);
myFont = createFont("Arial", 12); //フォント作成
textFont(myFont, 12); //テキストのサイズを設定
textAlign(CENTER);
barW = width/float(value.length); //バーの横幅を決定
//バーとテキストを描画。value.lenghは配列の要素数を返す
for (int i = 0; i < value.length; i ++) {
//バーのx座標, y座標を計算
float x = barW*i;
float y = height - value[i]*resize;
barH = value[i]*resize;
fill(127); //グレーで描画
rect(x, y, barW, barH);
//テキスト表示
fill(255, 0, 0); //赤で描画
text(s[i] + " = " + value[i], x + barW/2, y - 10);
}
さらに、左右と下の余白、バー同士の余白も作ってみましょう。
PFont myFont; //フォント用変数
int[] value = {10, 14, 20, 80, 2, 18};
String[] s = {"a", "b", "c", "d", "e", "f"};
float barW; //バーの横幅
float barH;
float resize = 3.0;
float margin = 20.0; //左右と底辺の余白
float space = 4.0;
size(400, 400);
background(255);
myFont = createFont("Arial", 12); //フォント作成
textFont(myFont, 12); //テキストのサイズを設定
textAlign(CENTER);
//バーの横幅を決定
barW = (width - margin*2 - (value.length - 1)*space)/float(value.length);
//バーとテキストを描画。value.lenghは配列の要素数を返す
for (int i = 0; i < value.length; i ++) {
//バーのx座標, y座標を計算
float x = (barW + space)*i + margin;
float y = height - value[i]*resize - margin;
barH = value[i]*resize;
fill(127); //グレーで描画
rect(x, y, barW, barH);
//テキスト表示
fill(255, 0, 0); //赤で描画
text(s[i] + " = " + value[i], x + barW/2, y - 10);
}
5.6.3 データフォーマット
一般的に膨大なデータを視覚化する際には、ファイルからデータを読み込んでからプログラム上で数値を形態、色彩、運動、インタラクションに変換します。この項では、データベースを扱うときに使われる代表的なデータフォーマット(形式)を学習します。
XLS(XLSX)
Microsoft Excelなどで使われているフォーマットですが、これもデータベースのフォーマットと言えます。テーブルに保存しますが、Excel上で操作しやすいのが強みです。
CSV (comma-separated values)
データ同士がカンマで区切られているだけの、非常に単純な構造をしているデータ形式です。現状、インターネット上で入手できるデータは、この形式になっていることが多いといえます。
XML (Extensible Markup Language)
比較的歴史の浅いフォーマットですが、インターネットの発展とともに広まってきました。記述形式はHTMLとも良く似ています。この形式でデータを公開している公共機関も少なくありません。
JSON (JavaScript Object Notation)
現在主流になりつつあるフォーマットです。XMLとも似ていますが、一般的にXMLよりも高速に処理が可能といわれています。特にインターネットで広く利用されています。
それぞれに特徴がありますが、ここではXML形式とJSON形式のデータをProcessingに読み込んで視覚化をおこなってみます。
5.6.4 XMLのデータを読み込む
ステップ1
まずは、XMLファイルのデータを読み込んでみましょう。サンプルのXMLファイルは、2015年の東京の平均気温と平均風速を保存したものですが、まずは気温の情報だけを使用します。
気温と風速のデータは気象庁のサイトからダウンロードしました。
http://www.data.jma.go.jp/gmd/risk/obsdl/
CSVファイルをXMLに変換する際には次のサイトを利用しました。
http://tools.nissuk.info/csv-xml/
まず、loadDataという関数を作ります。ここでは、気温の値を入れる配列「temp」を作って、そこに値を入れます。
void loadData() {
xml = loadXML("weather_month.xml"); //ファイルの読み込み
//weatherタグ内の要素を取得
XML[] children = xml.getChildren("weather");
//要素数を取得して配列を生成
temp = new float[children.length];
//配列に値を入れる
for (int i = 0; i < temp.length; i++) {
//気温
XML tempXml = children[i].getChild("temp");
temp[i] = tempXml.getFloatContent();
}
}
このファイルを【list5_6_f】というファイル名で保存しておきます。そして、次のリンク先の「data.zip」ファイルをダウンロード、解凍してください。
data
解凍されたdataフォルダの中には次のファイルが入っているので、「list5_6_f.pde」ファイルと同じ階層におきましょう。
weather_month.csv / weather_day.csv
weather_month.xml / weather_day.xml
weather_month.json / weather_day.json
次に、描画する部分も記述していきます。XMLファイルから読み込んだ気温のデータを色に変換します。
最終的なコードは次の通りです【リスト5.6-f】。
float[] temp; //気温
float highTemp; //最高気温
float lowTemp; //最低気温
float barW = 40; //バーの幅
float barH = 200; //バーの高さ
XML xml;
void setup() {
size(480, 200);
colorMode(HSB, 360, 100, 100); //HSBモードにする
noStroke();
loadData(); //xmlファイルからデータ読み込み、オブジェクト化
//一番低い気温と高い気温をtemp配列内で検索し、
//lowTemp、highTempに入れる
lowTemp = min(temp);
highTemp = max(temp);
}
void draw() {
background(0, 0, 100);
for (int i = 0; i < temp.length; i ++) {
float x = i*barW; //x座標を決定
//気温を色に変換
fill(createColor(temp[i], lowTemp, highTemp), 100, 100);
rect(x, 0, barW, barH);
}
}
void loadData() {
xml = loadXML("weather_month.xml"); //ファイルの読み込み
//weatherタグ内の要素を取得
XML[] children = xml.getChildren("weather");
//要素数を取得して配列を生成
temp = new float[children.length];
//配列に値を入れる
for (int i = 0; i < temp.length; i++) {
//気温
XML tempXml = children[i].getChild("temp");
temp[i] = tempXml.getFloatContent();
}
}
//気温から色を決定
float createColor(float _temp, float _lowTemp, float _highTemp) {
//最低気温から最高気温の範囲を、色相の200°から0°に変換
//気温を色に変換
float barColor = map(_temp, _lowTemp, _highTemp, 200, 0);
return barColor;
}
ステップ2
これだけだと何の情報か分からないので、次の要素を追加してみます。
・余白を入れる
・画面サイズに合わせて可変的にグラフの大きさが変わるようにする
・日付情報も読み込み、テキスト表示する
・マウスオーバーで日付と気温が表示されるようにする
String[] date; //日付
float[] temp; //気温
float highTemp; //最高気温
float lowTemp; //最低気温
PFont font;
float barW; //バーの幅
float barH = 200; //バーの高さ
float margin = 25; //余白
boolean over = false; //マウスがバーの上にあるかを判定
float space = 1; //バー同士の隙間
XML xml;
void setup() {
size(600, 250);
colorMode(HSB, 360, 100, 100); //HSBモードにする
noStroke();
loadData(); //xmlファイルからデータ読み込み、オブジェクト化
//一番低い気温と高い気温をtemp配列内で検索し、
//lowTemp、highTempに入れる
lowTemp = min(temp);
highTemp = max(temp);
//画面サイズと余白分を計算してバーの幅を決める
barW = (width - margin*2 - space*(temp.length - 1))/float(temp.length);
font = createFont("Arial", 11);
textFont(font, 11);
textAlign(CENTER);
}
void draw() {
background(0, 0, 100);
//
for (int i = 0; i < temp.length; i ++) {
float x = i*(barW + space) + margin;
float y = margin;
if (mouseX > x && mouseX < x + barW && mouseY > y && mouseY < y + barH) {
fill(0, 0, 20);
text("気温" + temp[i] + "℃", x + barW/2, y - 8); //気温と風速を表示
text(date[i], x + barW/2, y + barH + 16); //日付を表示
//気温を色に変換
fill(createColor(temp[i], lowTemp, highTemp), 100, 100);
} else {
fill(createColor(temp[i], lowTemp, highTemp), 30, 100);
}
rect(x, y, barW, barH);
}
}
void loadData() {
xml = loadXML("weather_month.xml"); //ファイルの読み込み
//weatherタグ内の要素を取得
XML[] children = xml.getChildren("weather");
//要素数を取得して配列を生成
date = new String[children.length];
temp = new float[children.length];
//配列に値を入れる
for (int i = 0; i < date.length; i++) {
//月
XML dateXml = children[i].getChild("date");
date[i] = dateXml.getContent();
//気温
XML tempXml = children[i].getChild("temp");
temp[i] = tempXml.getFloatContent();
}
}
//気温から色を決定
float createColor(float _temp, float _lowTemp, float _highTemp) {
//最低気温から最高気温の範囲を、色相の200°から0°に変換
//気温を色に変換
float barColor = map(_temp, _lowTemp, _highTemp, 200, 0);
return barColor;
}
ステップ3
これまでの例では12ヶ月(月毎)のデータを表示しましたが、実はこのサンプルは365日(日毎)のデータに差し替えることもできます。さすがに日毎のデータを表示するためには画面の横幅がもっと必要なので、次の行を変更してください。
14行目
size(600, 250); → size(1500, 250);
59行目
weather_month.xml → weather_day.xml
weather_day.xmlには365日分の1日当たりの平均気温の情報が掲載されているので、その情報を読み込み自動的に視覚化してくれます。
完成したサンプルコードはダウンロードできます【list5.6-h】。
5.6.5 視覚化の方法を工夫してみる
同じデータを使っていますが、円状にレイアウトしてみました。このサンプルでは平均風速の情報も使い、気温は色、風速は棒の長さにしています。
//東京の、2015年における1日ごとの平均気温平均の風速を表した棒グラフを
//円状にレイアウトした。平均気温は色、風速は棒の長さで表している
String[] date; //日付
float[] temp; //気温
float[] wind; //風速
float highTemp; //最高気温
float lowTemp; //最低気温
PFont myFont; //フォント用変数
float x1, y1, x2, y2; //月ごとの分割用の線を描画するための変数
int month;
XML xml;
void setup() {
size(500, 500);
colorMode(HSB, 360, 100, 100); //色彩の表示方法をHSBに変更
loadData(); //xmlファイルからデータ読み込み、オブジェクト化
//一番低い気温と高い気温をtemp配列内で検索し、lowTemp、highTempに入れる
lowTemp = min(temp);
highTemp = max(temp);
myFont = createFont("Arial", 12); //フォント作成
textFont(myFont, 14); //テキストのサイズを設定
textAlign(CENTER);
}
void draw() {
background(0, 0, 100); //背景は白
stroke(0, 0, 80);
fill(0, 0, 100);
ellipse(width/2, height/2, 480, 480); //外側の円
//月ごとの分割線を描画。1ヶ月ごとにグレーの線を描いていく///////////////
//文字を表示するときにrotateを使うと、文字も回転してしまうので、
//ここではsin, cosを使っている。
for (int i = 0; i < 12; i ++) {
x1 = 50*cos(radians(i*30)); //30度ごとのグレーの線の内側の点を求める
y1 = 50*sin(radians(i*30));
x2 = 240*cos(radians(i*30)); //30度ごとのグレーの線の外側の点を求める
y2 = 240*sin(radians(i*30));
//線を描画
line(width/2 + x1, height/2 + y1, width/2 + x2, height/2 + y2);
//月をテキストで書く。一番上から時計回りで始めるため、90度引く。
//さらに、15度移動すれば、グレーの線の間に表示することができる
x1 = 200*cos(radians(i*30 + 15 - 90));
y1 = 200*sin(radians(i*30 + 15 - 90));
month = i + 1; //月は、i + 1で1月から始まる
fill(0, 0, 50);
text(month + "月", width/2 + x1, height/2 + y1); //月を描画
}
//気温と風速を描画////////////////////////////////////////
//365日分の1日は、角度にすると360度分の何度になるかを計算 = 0.986.....
float angle = 360.0/365.0;
//1日ごとに描画
for (int i = 0; i < temp.length; i ++) {
pushMatrix();
translate(width/2, height/2); //画面中央に座標系を移動
rotate(radians(i*angle)); //1日分ずつ座標系を回転
stroke(createColor(temp[i], lowTemp, highTemp), 100, 100);
line(0, -50, 0, -50 - wind[i]*28); //線を描画
popMatrix();
}
}
void loadData() {
xml = loadXML("weather_day.xml"); //ファイルの読み込み
//weatherタグ内の要素を取得
XML[] children = xml.getChildren("weather");
//要素数を取得して配列を生成
date = new String[children.length];
temp = new float[children.length];
wind = new float[children.length];
//配列に値を入れる
for (int i = 0; i < temp.length; i++) {
//月
XML dateXml = children[i].getChild("date");
date[i] = dateXml.getContent();
//気温
XML tempXml = children[i].getChild("temp");
temp[i] = tempXml.getFloatContent();
//風速
XML windXml = children[i].getChild("wind");
wind[i] = windXml.getFloatContent();
}
}
//気温から色を決定
float createColor(float _temp, float _lowTemp, float _highTemp) {
//最低気温から最高気温の範囲を、色相の200°から0°に変換
//気温を色に変換
float barColor = map(_temp, _lowTemp, _highTemp, 200, 0);
return barColor;
}
5.6.6 JSONの利用
JSONの場合は、loadData内のコードが変わりますが、基本的な考え方は同じです。次のコードは、date, tempの値をそれぞれ読み込んでいます。ここでは、「weather_day.json」ファイルを使います。
CSVからJSONへの変換は次のサイトでおこないました。
http://utils.ipentec.com/JsonConvert/CsvToJSon.aspx
void loadData() {
//ファイルをロードして配列を作る
JSONArray weatherData = loadJSONArray("weather_day.json");
//要素数を取得して配列を生成
date = new String[weatherData.size()];
temp = new float[weatherData.size()];
for (int i = 0; i < weatherData.size(); i++) {
//一つ一つのJSON配列の中を検索
JSONObject weather = weatherData.getJSONObject(i);
//date, temp, wind配列に一つずつ入れる
date[i] = weather.getString("date");
temp[i] = weather.getFloat("temp");
}
}
XMLを使用している【list5_6_h】を、JSONを使って書き換えてみました。
完成したサンプルコードはダウンロードできます【list5.6-j】。
5.6.7 オブジェクト指向プログラミングによる記述
このようなインタラクティブグラフィックスを作成する際には、オブジェクト指向プログラミングの記述の方が合理的な場合があります。例えば、1日の気温をひとつの単位としてクラス化し、必要な数だけインスタンス化すると1年でも10年でも簡単に視覚化できます。ここでは詳細については述べませんが、興味がある人はサンプルを参考にしてみてください。