D3.jsでオーディオビジュアライザーみたいなものを作成

今回は、オーディオビジュアライザー(Audio Visualizer)みたいなものを
作ってみようと思います。
D3.js は、4.0 です。

オーディオビジュアライザー

値は乱数ですが、音楽の周波数とかと組み合わせれば、
それらしく表示できるかもしれません。

See the Pen D3_AudioVisualizer by book_stone (@book_stone) on CodePen.

究極を言うと10×10のマスを並べて色を塗っているだけです。
とはいえ、妙に悩んだところもありました。

ソースコード

主だった個所をメモしたいと思います。

// 初期データ
var data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

// 10分割スケール
var dataScale = d3.scaleLinear() 
    .domain([0,100])
    .rangeRound([0,10]);
// 色の取得に使う
var colorScale = d3.scaleLinear() 
    .domain([0,10])
    .range([1, 0.3]);

初期データとスケール設定です。
今回は、data配列に入るデータが、0~100までの値と想定し
それをマス目に対応するよう10分割スケールとしてます。
rangeRound を使うと丸めてくれて整数になるから便利です。

カラースケールは、d3.interpolateWarm(t) を使用するためのもの。
t に 0~1 の値を入れることで色々なカラーが取得できます。
今回は、緑から赤紫の範囲を使用するので、1 ~ 0.3 までにしています。
d3-scale/README.md at master · d3/d3-scale · GitHub

// 升目のもととなる配列を作成
var cells = new Array();
for(var i = 0; i < cols; i++){
  for(var j = 0; j < rows; j++){
    cells.push({ "i": i, "j":j });
  }
}

// 升目オブジェクト
var rects = svg.selectAll(".rects").data(cells).enter()
  .append("rect")
  .attr("x", function(d){ return (width/cols) * d.i;   } )
  .attr("y", function(d){ return (height/rows) * (rows - d.j);  } )
  .attr("width", (width / cols) - 4)
  .attr("height", (height /rows) - 3)
  //.attr("stroke-width",1)
  .attr("rx",2)
  .attr("ry",2)
  .attr("stroke","black");

cells という変数にマス目の位置 (i,j) を持たせて作成しています。
これは、のちに enter() で繰り返し rect オブジェクトを作成したいので
1次元配列にしています。もっといい方法があるかもしれない。

rect オブジェクトの作成は、 位置 (i,j) から x座標とy座標を計算しています。
widthとheightはSVGの幅と高さとマス目分だけ割って、1辺を求めています。
サイズから -4 とか - 3は、少し隙間を出したほうがおしゃれかと思い調整した値です。
attr("rx" 2) と attr("ry",2) は rect を角丸とするため。

// 1秒ごとに更新を呼び出し
d3.interval(update, 150);

// 再描画がするメソッド
function update(){
  // 乱数取得
  var rand = d3.randomUniform(100);  
	// 合計10個の乱数を発生
	for( var data=[], i=10; i--; ) {
		data[ data.length ] = Math.floor( rand() ) ;
	}
  
  rects.attr("fill", function(d){  
    if(dataScale(data[d.i]) >= d.j){ 
      //return "cyan";
      return d3.interpolateWarm(colorScale(d.j));
    }
    else{ return "gray" }; 
  });
}

乱数を取得して、配列に放り込んで、その値によって
rect の色を塗るといったもの。比較的シンプルなつくりではないかと思います。

感想

色々検索してみると、最大のところでいったんバーが浮いたりするなど
こったアニメーションがあるものもありました。
すこし、カクカクしている感もあるので、まだ改良の余地もありそうです。
また、背景や少しマス目の幅や数を変えるだけでも印象が変わるのも面白いです。

ひとまずでした。

D3.jsでスピードメーターみたいなものを作成

今回は、スピードメーターみたいなものを作ってみようと思います。
D3.js は、4.0 です。

作成するスピードメーター

値は乱数ですが、CPUやメモリの使用量などの表示に組み合わせたら
それっぽくなるかもしれません。

See the Pen D3_SpeedMeter by book_stone (@book_stone) on CodePen.


ソースコード

主だった個所をメモしたいと思います。

// メーターの範囲を設定
var arcScale = d3.scaleLinear()
    .domain([0 , 100])
    .range([startAngleRate*90, endAngleRate*90]); // ここは度

メータのスケール設定です。
折れ線グラフの場合は、データの値をWidth や Heightのサイズにあうように
スケールの設定をしていましたが、今回はデータの値の大小によってメーターの針の角度が変わるため
[0, 100] ⇒ [円弧の開始角度, 円弧の終了角度] になるように設定しています。

var arc = d3.arc()
  .innerRadius(55)
  .outerRadius(60)
  .startAngle(startAngleRate * p/2) // ここはラジアン
  .endAngle(endAngleRate * p/2 )

円弧を作成しています。
innerRadius と outerRadius を設定することで円弧の幅や半径が調整できます。
startAngle と endAngle は、円弧の開始と終わりですがラジアンになるみたいです。

var svgDefs = svg.append('defs');
var mainGradient = svgDefs.append('linearGradient')
    .attr('id', 'mainGradient');

// グラデーションの設定(3色)
mainGradient.append('stop')
    .attr('class', 'stop-left') // green yellow
    .attr('offset', '0');
mainGradient.append('stop')
    .attr('class', 'stop-middle') // orange
    .attr('offset', '0.6');
mainGradient.append('stop')
    .attr('class', 'stop-right') // red
    .attr('offset', '1');

円弧のグラデーションの設定です。色の設定はCSSのクラスでしています。
offset で、グラデーションの間隔が調整できます。
上の例だと、オレンジは真ん中より少し赤よりからスタートしています。

// 目盛の追加
// 0 を100個の5間隔で配列を作成
var ticks = d3.range(0, 105, 5);
// グループ要素を目盛数分追加する
var ticks_g = svg.selectAll(".tick").data(ticks).enter()
  .append("g")
  .attr("class","tick")
  .attr("transform",function(d,i){ return "translate(" + width/2 + "," + height/2 + ")"; });

円弧の下に目盛を作成するためにまず目盛の数値を作成ています。
d3.range(0, 105, 5) は、[ 0, 5, 10, .... , 90, 95, 100] の配列です。

// 目盛りの線
var ticksLine = d3.line()
  .x(0) 
  .y(function(d) { return d; });

// 目盛りの追加
ticks_g.append("path")
  .attr("d", function(d){ 
    if(d % 20 == 0 ){ return ticksLine([ -50 , -45]); } // 主目盛
    else{ return ticksLine([-50 , -48]); }} // 補助目盛
  )
  .attr("stroke", "gray")
  .attr("stroke-width",2)
  .attr("stroke-linecap","round")
  .attr("transform",function(d,i){ return "rotate(" + arcScale(d) + ")"; });

目盛の配置をしています。
上で設定したグループ要素の持つデータ[ 0, 5, 10, .... , 90, 95, 100]が割り当てられます。
20 で割り切れる場合だけ、少し大きい目盛にしてます。
rotate で線を回転させることによって円弧に沿った目盛を表現しています。

// ラベル 位置は角度から(x,y)を求めて、貼り付け
ticks_g.append("text")
  .attr("x",  function(d){ return 32 * Math.sin(arcScale(d) * (Math.PI / 180)); }) 
  .attr("y" , function(d){ return -32 * Math.cos(arcScale(d) * (Math.PI / 180)) + 2; })
  .attr("text-anchor", "middle")
  .attr("fill", "black")
  .attr("font-size", "14px")
  .attr("font-family", "sans-serif")
  .text(function(d){ if(d % 20 == 0 ){ return d; }});

ラベルを配置をしています。
こちらも 20 で割り切れる場合だけ、文字を出力しています。
rotateで回転させてもよかったのですが、文字が回転すると見にくいため、
x, y の座標は、回転させるべく角度から三角関数で求めています。
度からラジアンに戻すため Math.PI / 180 をかけています。
(y座標の +2 は位置の調整のため追加しました。)

感想

これもとりあえず、動いたといった感じでしょうか。
見た目のインパクトがもう少しほしいところですね。

ひとまずでした。

D3.jsでリソースモニターみたいなグラフの作成(2) version 4.0

前回の記事では D3.js 3.0 と 4.0 の違いを簡単に記載しました。
今回は、タイマーやグリッドを使ってみようと思います。このまま 4.0 で進めます。

作成するグラフ

WindowsのタスクマネージャーにあるCPU使用率を表すグラフを参考にしてみました。
雰囲気が出ていると思うのですが、いかがでしょう。

See the Pen D3_ Resource_Monitor by book_stone (@book_stone) on CodePen.


ソースコード

主だった個所をメモしたいと思います。

// 0 を100個の配列を作成
var array = d3.range(100).map(function(d) { return 0; });

d3.rangeは、0 から 指定した数までの配列を作成する関数です。
d3.range(10)で、[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]となりますが、
この場合、mapで、値ごとに 0 を返却しているので [ 0, 0, ... , 0, 0] 0が100この配列になります。

// X軸
var tick = d3.range(0 , 100, 10);
var xAxis = d3.axisBottom(xScale)
              .tickSizeInner(-height)
              .tickSizeOuter(-height)
              .tickFormat("")
              .tickValues(tick);

基本的にはスケールを作成して d3.axisBottom(xScale) だけでデフォルト値で動きます。

d3.range(0 , 100, 10) は、0 から 100 まで 10 step という意味で、
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90] を 表します。
tickSizeInner は、グリッドラインの長さです。内側に伸ばすためマイナスになります。
tickSizeOuter は、外側のラインです。これで囲んでいる線を表現します。
tickFormat は、グラフのラベル(数字など)のフォーマットです。今回はなし。
tickValues は、グリッド幅です。range で作った配列を入れています。

// 面の部分
var area = d3.area()
    .x(function(d,i) { return xScale(i); })
    .y0(height)
    .y1(function(d,i) { return yScale(d); });

グラフの薄青い面部分です。
4.0 では、d3.svg.area から d3.area と名称が短くなっています。

area は、データごとに (x0, y0) と (x1, y1) の2点の座標を作成しますが
area.x(value) で x0, x1 同時に設定します。*1
なので、以下と同値(のはず。)

var area = d3.area()
    .x0(function(d,i) { return xScale(i); })
    .y0(height)
    .x1(function(d,i) { return xScale(i); })
    .y1(function(d,i) { return yScale(d); });

当然、データが1個だけだと面にはらない*2ので、データの個数は2個以上で面が表現できます。

詳しくはこちら
GitHub - d3/d3-shape: Graphical primitives for visualization, such as lines and areas.

// カウント
var i = 10

// D3のTimer関数(setIntervalとより良いことがある?)
d3.interval(pushData, 1000 );

これは setInterval と同じような使い方をしています。
次のpushData()を 1000 ms で読んでいます。
カウントも次の呼び出されている関数内で使用しています。

// データ追加し、再描画がするメソッド
function pushData(){
  // 先頭を削除
  array.shift();

  // 正規分布の乱数発生  
  var rand = Math.pow(d3.randomNormal(0, 0.3)(), 2);

  // 配列に追加
  array.push(rand);
  
  // 再描画
  paths.attr("d", line);
  areas.attr("d", area);
  
  // グリッドもずらしていく
  // 呼ばれるごとに 開始するtickの値を 9 はじまり、 8 はじまりと下げている
  tick = d3.range((i--)%10, 100, 10);
  xAxis.tickValues(tick);
  axis.call(xAxis);
  // グリッド線のリセット
  if(i<=0) i=10;
}

1000 ms で呼ばれている関数です。
疑似データとして D3 正規分布ガウス分布*3から取得しています。
d3.randomNormal(0, 0.3)() 平均 0 標準偏差 0.3 の正規分布から取得しています。
二乗しているのはマイナスをなくすためだけです。

tick = d3.range((i--)%10, 100, 10) 

これで、i が 9 に下がると [9, 19, 29, 39, 49, 59, 69, 79, 89, 99] の配列を作成し、
それを新たなX軸のグリッドに入れなおして、動きを表現しています。

感想

とりあえず、動いたといった感じでしょうか。
X軸のグリッドの更新の仕方に不安がありますが。。

ひとまずでした。

*1:正しくは、value が x0 に設定されて、 x1 が null になる。 x1 が null だと x1 は x0 となる。

*2:データが1個で座標が2個、データが2個で座標が2個増えて計4個と・・増えていきます。

*3:通常の乱数でも問題ないと思います。それっぽくデータになるかと思っただけです。

D3.jsでリソースモニターみたいなグラフの作成 version 4.0

比較的最近*1に 4.0 のメジャーバージョンアップされたみたいですね。
また、D3.js 3.0 と 4.0 は互換性がありません。

変更点は公式を参考にして、前回紹介した 3.0 グラフを書き直しました。
d3/CHANGES.md at master · d3/d3 · GitHub

対応後のグラフ

See the Pen D3_v4_LineChart by book_stone (@book_stone) on CodePen.


変更点

正直なところ 3.0 を理解していないので変更したソースも小さいため
あまり参考にならないかもしれませんが、記載させていただきます。

ざっくり大きな点といえば D3 のオブジェクト名が短くなりました。
ほとんどのオブジェクトが d3. と始まるようになったようです。

var xScale = d3.scale.linear() // v3

var xScale = d3.scaleLinear()  // v4

軸も変わりました。orientの指定せず、上下左右の軸
d3.axisTop, d3.axisRight, d3.axisBottom, d3.axisLeft が用意されています。

var yAxis = d3.svg.axis() // v3
   .scale(yScale)
    .orient("left");

var yAxis = d3.axisLeft(yScale); // v4

d3.lineオブジェクトにて、interpolateが使用できなくなり、
かわりにcurveが使えます。curveに用意されているオブジェクトを渡すことで使用できます。
用意されている種類もたくさんありますので色々試すと面白いかもしれません。
GitHub - d3/d3-shape: Graphical primitives for visualization, such as lines and areas.

var line = d3.svg.line()
    .x(function(d,i) { return xScale(i); })
    .y(function(d,i) { return yScale(d); })
    .interpolate("linear"); // v3

var line = d3.line()
    .x(function(d,i) { return xScale(i); })
    .y(function(d,i) { return yScale(d); })
    .curve(d3.curveLinear); // v4 これは直線

curveのパラメータを渡すときは下記のように。

var line = d3.line()
    .x(function(d,i) { return xScale(i); })
    .y(function(d,i) { return yScale(d); })
    .curve(d3.curveCatmullRom.alpha(0.5)); // v4

そのほか、アニメーションの個所も変更になったようです。
大幅に変更されているようですが、メソッドチェインの考え方は同じかと感じました。

*1:2016/6月末ごろに4.0にアップデートされたようです。

CodePenでD3.jsのグラフを記載してみた

CodePen

はてなブログ上でJavaScriptをゴリゴリ動かすと、
意図しない動きをする場合があるかもしれないのでCodePenを利用してみました。

CodePen - Front End Developer Playground & Code Editor in the Browser

いままでローカルでAtomChromeで試していたのですが、
CodePen上の編集も使いやすかったです。すぐ結果も出ますし、そのまま公開できますし。
よく考えたら、記事に埋め込む用のソースコードを作成していたのもナンセンスかもしれません。

CodePenの使い方はこちらを参考にしました。
【はてなブログ】CodePenをはてなに貼る方法 - のんびり猫プログラマの日常

作成したグラフ

前回のグラフと同じです。

See the Pen D3_LineChart by book_stone (@book_stone) on CodePen.

実行結果も見やすくなった気がします。
HTML、CSSおよびJavaScriptが分離されているのがいいですね。

ただペタッとコピペして使えないかもしれませんが。
次回は適当にグラフに数値を入れたりして遊んでみようと思います。

D3.jsでリソースモニターみたいなグラフの作成

前回、はてなブログで、D3.jsの動作が確認できたので、
表題のようなリソースモニターみたいなグラフができないかと作成してみました。

作成するグラフ

さっそく目標とするグラフを紹介したいと思います。
f:id:stone-book:20161020232329p:plain
データが古いものから左にずれていきますので、リソースモニターっぽく表現しようと思います。

ソースコード

基本的には、D3.jsのサンプルなどを参考にして作成しました。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<style>
/* 軸のスタイル */
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: auto; /*アンチエイリアス処理*/
}
/* Pathのスタイル */
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}
</style>
<body>
  <div id="result">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// マージンの設定(領域)
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 400 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

// 描画データ
var array = [ 2 , 0 , 3  , 4 , -1 , -2 , 5 , -3 , 1 , -5] ;

// スケールの設定 x要素としてインデックス
var xScale = d3.scale.linear()
    .domain(d3.extent(array, function(d,i) { return i; }))
    .range([0, width]);

// スケールの設定 y要素として配列の値
var yScale = d3.scale.linear()
    .domain(d3.extent(array, function(d,i) { return d; }))
    .range([height, 0]);

// X軸
var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom")
    .tickFormat(""); // 軸ラベルはなしにした

// Y軸
var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");

// 線グラフのラインオブジェクト
var linestyle = "linear";
var line = d3.svg.line()
    .x(function(d,i) { return xScale(i); })
    .y(function(d,i) { return yScale(d); })
    .interpolate(linestyle);

// SVGの追加
var svg = d3.select("#result").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// グループ要素にX軸を追加
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height / 2 + ")") // 真ん中にした
    .call(xAxis);

// グループ要素にY軸を追加
svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("x", 10)
    .attr("y", -10)
    .style("text-anchor", "end")
    .text("Value");

// 実際のラインを追加
var paths = svg.append("path")
    .datum(array) // 配列をマッピング
    .attr("class", "line")
    .attr("d", line);

// データ追加し、再描画がするメソッド
function pushData(){
  // 先頭を削除
  array.shift();

  // +5から-5までの乱数発生
  var rand = Math.floor( Math.random() * 11 ) - 5 ;
  // 配列に追加
  array.push(rand);

  // path要素を削除
  // paths.remove();

  // あらたにPath追加
  line.interpolate(linestyle);

  // 追加して削除だったが、Data-Drivenっぽくない
  // paths = svg.append("path")
  //    .datum(array)
  //    .attr("class", "line")
  //    .attr("d", line);

  // データだけ入れ替る。動きは一緒。
  paths.attr("d", line);

}

// スタイルを変える(コンボのイベント)
function changeStyle(obj){
  linestyle = obj.value;
}
</script>
  </div>
</body>
<input type="button" value="Add" onclick="pushData()"/>
<select name="interpolate" onChange="changeStyle(this)" >
  <option value="linear">linear</option>
  <option value="cardinal">cardinal</option>
  <option value="step-before">step-before</option>
  <option value="step-after">step-after</option>
</select>
</html>

タイマーなどをうまく使うことで、リアルタイムにグラフが更新できるようになりそうです。
これを使ってそれっぽいグラフを次、作成したいと思います。
心電図っぽくして遊ぶのも面白いかもしれませんね。

補足

どうやら、はてなブログ上でうまく動いていなかったようなので、画像に修正しました。
変数名が衝突していたのかな。。
はてなブログ上で、JavaScriptをゴリゴリ動かすより、CodePen利用したほうがいいかもしれませんね。
CodePen - Front End Developer Playground & Code Editor in the Browser

補足2

ソースコードが Data-Drivenっぽくなかったので修正しました。
こちらの記事で動作確認できます。
stone-book.hatenablog.com

はてなブログでD3.jsの動作実験

はてなブログで、D3.jsの動作ができるか試してみました。
可視化の際に使用できればと考えております。

こちらのサイトを参考にしてみました。
はてなブログの記事内でJavascriptを書く方法 - Three.jsを使って、作ってみた

JavaScriptは以下のタグでくくっておけば使えるそうです。

<script type="text/javascript">...</script>

手順

さっそく「はてな記法」で以下を記載しました。

<div id="test" style="width:100;height:100"></div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
var width = 100;
var height =100;

var svg = d3.select("#test").append("svg")
 .attr("width", width)
 .attr("height", height);

svg.append("circle")
 .attr("cx",50)
 .attr("cy",50)
 .attr("r",20)
 .attr("fill","blue")
 .attr("stroke-width",3)
 .attr("stroke","black");
</script>

以下のDIVにSVG出力を試しております。

<div id="test" style="width:100;height:100"></div>

出力結果

円が表示されているようなので、使えそうです。