D3.jsで艦これ登場艦の年表

D3.jsで艦これ登場艦の年表

ほんとは撃破した艦の被害も調べようと思ったけど、艦の最期を調べただけでうつうつします……

「艦これ登場艦年表」かなり横長になったので別ページです。

割とシンプルなrectを使ったグラフです。

※追記

  • 比叡と榛名が間違ってたので直しました。 thx id:dummy1
  • ソート機能を付けました。

既知の不具合

  • 金剛のラベルが表示されていない。

TODO: ゆかりん@横アリから無事帰ったらなおす。

アイM@S HOMM@GE to music!

アイM@S HOMM@GE(オマージュ) to music!

「ゲンキトリッパー」はCymbalsへのオマージュ

作詞作曲を担当されたNBGIおおくぼひろしさん自身が、雑誌『リスアニ』*1で「バンドっぽいのがやりたいなって思って。最初から狙ってました。僕の大好きなバンドがあるんですけど、そんな方向で。」と楽曲解説されています。

この“大好きなバンド”ってCymbalsだよねと、Cymbalsを知っている方には聴いた瞬間にティンッときたんじゃないでしょうか。とはいうものの、要素要素はすごっくそれっぽいんですけど、記事を書くために似てる曲を探してもなくて、上手く要素を取り込んだんだなぁと感心してます。 敢えて言えば「RALLY」と「Highway Star, Speed Star」でしょうか。

ということで、この曲はイメージとしてはこんな感じとか、これあれのオマージュじゃない?みたいなことをつらつらと。

cymbals - RALLY - YouTube

cymbals - Highway Star,Speed Star - YouTube

エージェント夜を往く」はYAPOOSの「バーバラ・セクサロイド

この動画の投稿者コメントで大工Pが「 『エージェント夜を往く』の元ネタあるいは源流といえる、戸川純が歌うヤプーズの1987年の曲です。」とコメントしています。

○春香タグシリーズで(○は多くは色の名前)“売”が付けられていたのも懐かしいです。

「ヴィーナスシンドローム」は“ガンダム”

新田美波「ヴィーナスシンドローム」は、初出の時からコメントで「ガンダム」「ロボットアニメのOP」とか言われてました。

自分としてはT.M.Revolution 『INVOKE-インヴォーク-』(ガンダムSEED)をベースにTWO-MIXRHYTHM EMOTION』(ガンダムW)とROMANTIC MODE『DREAMS』(ガンダムX)の音を混ぜたりすると「ヴィーナス・シンドローム」っぽくなるような気がしています。

T.M.Revolution 『INVOKE-インヴォーク-』 - YouTube

「Romantic Now」は80年代テクノポップ

赤城みりあ「Romantic Now」 思い浮かんだのは、プラスチックスの「COPY」、ジューシィ・フルーツ「ジェニーはご機嫌ななめ」、C-C-BRomanticが止まらない」あたりかな。

その他モバマス組楽曲

神崎蘭子「華蕾夢ミル狂詩曲〜魂ノ導〜」は、アリプロなのか妖精帝国なのか。

輿水幸子「To my darling...」はアイドルソングと言うより、90年代後半から2000年代の“声優アイドルソング”っぽい雰囲気がする。

川島瑞樹「Angel Breeze」は、80年代後半から90年代初頭アイドルソングだろうけど、そのへん詳しくないのでティンッとはきてません。

多田李衣菜「Twilight Sky」はイントロがUKロック風味。具体的には分からないけど。余談ですけど桑島法子さんのラジオ主題歌「cocoon」はUK風でした。イントロのギターが似ている気がします。

アイマス関係ない楽曲

ラブライブ!のユニットlily white「あ・の・ね・が・ん・ば・れ!」は、山口百恵さんの「絶体絶命」ですよね。

【ラブライブ!】μ's内ユニット lily white シングル試聴動画 - YouTube


山口百恵 - 絶体絶命 - YouTube

けいおん!の劇伴でクラフトワークっぽいのあったよなー、と思い返して誰か書いてないかと思ったら「けいおん!」サントラは、こてこてな音楽パロディの宝庫だったよ。 - たまごまごごはんによると、「virtual love」ってのらしいですね。

ツッコミ・補足のコメント・記事、お待ちしてます。

*1:『リスアニ! Vol.5.1』(2011)ソニー・マガジンズ

ラブライブ!を全話見てきた。(おまけつき)

ラブライブ!を「みんなで見る会」

ほんとうにみんな見ていたというか見守っていたというか。「言葉は要らない」というほどではないですけど、まさにそれに近いような状態でした。

  • ここで背後から入ってくる絵里がかわいいんですよ。
  • この場面のにこ先輩がかわいい、腰の振り。
  • 真姫ちゃん、ちょろい。ちょろかわいい。
  • 希は鏡なんですよ。
  • ……などなど

ええ年こいた男(もしくは心の中におっちゃんが住んでいるひと)たちが、こんなことを語り合うと言うよりも、しみじみと言っていたわけです。

ラブライブ!(アニメ)ってなんなんでしょう?

自分はμ'sを応援していたい、好きだなぁという気持ちでラブライブ!のアニメを見ていました。

μ'sをファンとしてだけではなく、クラスメートや友達の視点で青春をいっしょに感じているような気分です。

関係の感覚

もちろん、単純にファンとして応援する感覚もまた楽しかったです。

アイマスPが会には多かったのですが、アイドルをプロデュースするのではないという感覚の違いが新鮮だったのかもしれません。

アイマスのPとアイドルは二人三脚ですが、μ'sと自分たちには直接関係がありません。ほんの少しだけファンとして応援できるだけです。

青春の物語が聞こえる

その意味でアニメ版は裏話的な位置といえるかもしれません。本来ならば目に触れない彼女たちの日常を垣間見るものだからです。

「きっと青春がきこえる」はエンディングテーマのタイトルですが、彼女たちの姿や歌声に誰もが青春の足音を聞こえてくるような気がします。残念ながら私には歌やダンスに青春を捧げた日々はないのですが、彼女たちの姿からそんな日々を感じられるんです。

舞台の上のアイドルとファンという関係だけではなく、同じ青春を共有したかのような感覚すらあるような気がします。

学校・クラスメート

きっとそれは学園という舞台のおかげもあるのでしょう。多くの人が学校へ通った思い出を持ち、そんな中に彼女たちがいたら、という想像を滑り込ませやすいのではないでしょうか。

そして「神モブ」といわれた穂乃香のクラスメートも見逃せません。ともに青春を過ごす、そして一番近いファンでもります。学校に・クラスにμ'sがいたら、という時に眼差しを共有できる存在です。

まとめ

アニメ版では本来からあるのファンとしてはもちろん、青春をともに過ごすように、クラスメートのように、μ'sを感じさせてくれました。その意味でラブライブ!の物語にいっそうの深みをもたらしたのではないでしょうか。

おまけ

765プロの面々とμ'sの皆さんを比べてみた。 「素人体型」とプロの体型に違いはあるのかなって。

春香さんは普通。やよいと千早はさすが。貴音さん、あずささんも。

D3.jsでラブライブ! 総選挙のセンターとポジションを振り返ってみる

センター以外はすんなり順位どおりではない模様。

ソースはWikipediaです。次のセンターがだれになるか楽しみですね。

なお、D3.js的にはeaseを使ったtransitionの順次実行が今回のポイントです。

D3.jsでForce-Directed Graphを使ってラブライブ!のキャラ関係を可視化する

Force-Directed Graph

力指向グラフは、nodeの間と描画領域に物理的な作用を計算してうまいぐあいに表示するらしいです。

各node(円)はマウスで動かせます。

元データはPixivのタグ検索集計です。「(ラブライブ! OR ラブライブ!) にこまき」などそれぞれのキーワードで検索して出てきた作品数を集計しました。

線の太さがタグ数(「にこまき」なら“にこ”“まき”間の線が出現数に応じて太くなる)を表しています。nodeの外周を囲む線は学年ごとの塗り分けをしています。

JSONのデータの構造

nodes配列をdata()でセットして、今までと同じようにcircleとlineをappendします。

links配列は、各nodeの関係を記述します。sourceの0は、nodes[0]です。

graph = { nodes : [
    { "name" : "にこ", "color" : "#ff5fdb" },
    { "name" : "まき", "color" : "#ff6136" },
    { "name" : "りん", "color" : "#55fbd6" },
    { "name" : "はな", "color" : "#42ce70" }
],
links : [
    { "source" : 0, "target" : 1, "value" : 157 },
    { "source" : 2, "target" : 3, "value" : 32 },
]}

一部のみです。

force layoutの使い方

nodeとしてcircleを、linkの関係を表すものとしてlineを準備します。なお、テキスト関連の処理は除いてあります。

nodeは.call(force.drag)とすることでドラッグ可能になります。

var color = d3.scale.category10();

var svg = d3.select("div.entry-content").append("svg")
    .attr("width", width)
    .attr("height", height);

var force = d3.layout.force()
    .charge(-240)
    .linkDistance(120)
    .size([width, height]);

force
    .nodes(graph.nodes)
    .links(graph.links)
    .start();

var link = svg.selectAll(".link")
    .data(graph.links)
    .enter().append("line")
    .attr("class", "link")
    .style("stroke-width", function(d) { return Math.sqrt(d.value) *  1.5; });

var node = svg.selectAll(".node")
    .data(graph.nodes)
    .enter().append("circle")
    .attr("class", "node")
    .style("fill", function(d) { return d.color; })
    .call(force.drag);

node.append("title")
    .text(function(d) { return d.name; });

force.on("tick", function() {
    link.attr("x1", function(d) { return d.source.x; })
    .attr("y1", function(d) { return d.source.y; })
    .attr("x2", function(d) { return d.target.x; })
    .attr("y2", function(d) { return d.target.y; });

    node.attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });

});

D3.jsのscale, transition, data binding覚え書き

前回のエントリー!では、とりあえず表示できるところまでやりました。今回はもう少しD3らしい形にしていきます。

d3.scale, selection.data()

var minHeight = d3.min(member, function(d){return d.height});
var maxHeight = d3.max(member, function(d){return d.height});
var hScale = d3.scale.linear()
    .domain([minHeight, maxHeight])
    .range([h - offset, offset]);

入力領域はdomain、出力範囲はrangeで指定する。

ここではminHeightからmaxHeightの入力領域を、表示領域(svgのサイズ)の端からoffsetを引いたところからoffset分ずらしたもう一端まで等間隔に対応させる数値にします。

つまりhScale(にこ先輩の身長) = offset, hScale(エリーチカの身長) = h -offset

var graph = svg.selectAll(".select")
    .data(member);

var group = graph.enter().append("g")
    .attr("class", "select");

var circles = group.append("circle")
    .attr("class", "select")
    .attr("cx", function(d, i) { return wScale(i); })
    .attr("cy", r + offset)
    .attr("r", r)
    .attr("fill", function(d) { return "rgb(" + d.color + ")"; });

selection.data()でbindingしたデータが、最終行のfunctionに引数として渡されています。なお、第二引数は呼びだし順の数値です。

g, transform

svgのg要素は、htmlのdivのような感じです。transform属性を指定することによって、含まれる要素をまとめて動かしたり変形することが出来ます。

例えば<g transform="translate(10, 20)">とすると、右に10, 下に20移動します。

group.transition()
    .attr("transform", function(d, i) {
        return "translate(0," + (hScale(d.height) - r - offset) + ")";
    });

transition

transformのソースに、selection.transition().attr()という形があります。

attr()で行われる変更にトランジション効果を設定しています。

selection.transition().
      .delay(750).
      .duration(750)
    .attr("r", 30);

この場合は、delay, durationともに750msでrを30に変更します。

ソースとサンプル

今回の動作サンプル

ソース

<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8">
   <style type="text/css">
        circle {
            stroke: #333;
            stroke-width: 1.5px;
        }
        text {
            font: 10px sans-serif;
        }
        text.shadow {
            stroke: #fff;
            stroke-width: 3px;
            stroke-opacity: .8;
        }
    </style>
</head>
<body>

<div id="graph">
<button>Run</button>
</div>

<script type="text/javascript" src="d3.v3.min.js"></script>

<script>
d3.json("https://dl.dropbox.com/u/450748/d3.v3/mus.json", function(error, json) {
var member = json.nodes;
member.sort(function(a, b) { return b.height - a.height; });

var w = 720;
var h = 540;

var r = 30;
var offset =  r * 2;

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

var graph = svg.selectAll(".select")
    .data(member);

var wScale = d3.scale.linear()
    .domain([0, member.length - 1])
    .range([offset, w - offset]);

var minHeight = d3.min(member, function(d){return d.height});
var maxHeight = d3.max(member, function(d){return d.height});
var hScale = d3.scale.linear()
    .domain([minHeight, maxHeight])
    .range([h - offset, offset]);

var group = graph.enter().append("g")
    .attr("class", "select");

var circles = group.append("circle")
    .attr("class", "select")
    .attr("cx", function(d, i) { return wScale(i); })
    .attr("cy", r + offset)
    .attr("r", r)
    .attr("fill", function(d) { return "rgb(" + d.color + ")"; });

var text = group.append("text")
    .attr("class", "select shadow")
    .attr("x", function(d, i) { return wScale(i); })
    .attr("y", r + offset)
    .attr("text-anchor", "middle")
    .text(function(d) { return d.name; });

var textShadowed = group.append("text")
    .attr("class", "select")
    .attr("x", function(d, i) { return wScale(i); })
    .attr("y", r + offset)
    .attr("text-anchor", "middle")
    .text(function(d) { return d.name; });

group.transition()
    .attr("transform", function(d, i) {
        return "translate(0," + (hScale(d.height) - r - offset) + ")";
    });


var yAxis = d3.svg.axis()
    .scale(hScale)
    .orient("left");

svg.append("g")
    .attr("transform", "translate(" + r + ",0)")
    .attr("class", "select")
    .call(yAxis);


// button click
d3.select("#graph button").on("click", function() {
    var delayTime = 750;
    var durationTime = 750;

    // r
    circles.transition()
        .delay(delayTime)
        .duration(durationTime)
        .attr("r", function(d) { return d.B/2; });

    var minB = d3.min(member, function(d){return d.B});
    var maxB = d3.max(member, function(d){return d.B});
    var wScaleByB = d3.scale.linear()
        .domain([minB, maxB])
        .range([offset, w - offset]);

    group.transition()
        .delay(delayTime)
        .duration(durationTime)
        .attr("transform", function(d, i) {
            return "translate(" + (wScaleByB(d.B) - wScale(i)) + "," + (hScale(d.height) - r - offset) +")";
        });


    var xAxis = d3.svg.axis()
        .scale(wScaleByB)
        .orient("bottom");

    var g = svg.append("g")
        .attr("class", "select")
        .attr("opacity", 0)
        .call(xAxis);
    g.transition()
        .duration(durationTime)
        .delay(delayTime)
        .attr("opacity", 1);

});

});
</script>

</body>
</html>

D3.jsの覚書ラブライブ!編

まとめ

D3.jsでData-Driven Documentsするとかよくわからなかったので、基礎っぽいところのメモとサンプル。

まずは、d3.selectAll(), selection.data(), selection.enter(). d3.scale()を理解することが重要。

svgのcircle, rectあたりは調べておきましょう。

同じくJavaScriptのArrayあたりはよく使うので復習しておく。

にこ先輩はちっちゃい。

f:id:AOI-CAT:20130326023840p:plain

f:id:AOI-CAT:20130326005923p:plain

Documentの読み方

主要API

公式のIntroductionと、Tutrialsは参考になる。

とりあえずIntroductionからThree Little CirclesThinking with Joinsまで読むのがおすすめ。

すごく単純に要約すると、d3.selectAll()で選択して、selection.data()でデータをバインディングして、selection.enter()などにデータから要素をappendしていくのがD3.jsの大まかな流れ。

selectAllで選択した要素よりも多い(例えば要素0個の)場合でも、dataの数だけ要素をappendできる。

あとこちらが参考になる。d3.js API Advent Calendar 2012 by id:muddydixon

今回のサンプル!

AxisとScaleは、また別の機会に。

実際に動くサンプル

あぁ、にこ先輩ちっちゃいなぁ。

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>d3 sample</title>
   <style type="text/css">
        circle {
            stroke: #333;
            stroke-width: 1.5px;
        }
        text {
            font: 12px sans-serif;
        }
        text.shadow {
            stroke: #fff;
            stroke-width: 3px;
            stroke-opacity: .8;
        }
    </style>
</head>
<body>
    <div id="chart">
    <button>Run</button>
    </div>
    <script type="text/javascript" src="d3.v3.min.js"></script>
    <script type="text/javascript" src="lovelive.js"></script>
</body>
</html>
var member = [
{ name : "高坂 穂乃果", cv : "新田恵海", grade : 2, age : 16, birthday : "8/3", bloodType : "O", height : 157, B : 78, W : 58, H : 82, like : "いちご", dislike : "ピーマン", color : "246,168,0", img : "01s_off.png" },
{ name : "絢瀬 絵里", cv : "南條愛乃", grade : 3, age : 17, birthday : "10/21", bloodType : "B", height : 162, B : 88, W : 60, H : 84, like : "チョコレート", dislike : "梅干し・のり", color : "0,186,255", img : "02s_off.png" },
{ name : "南 ことり", cv : "内田 彩", grade : 2, age : 16, birthday : "9/12", bloodType : "O", height : 159, B : 80, W : 58, H : 80, like : "チーズケーキ", dislike : "にんにく", color : "160,160,160", img : "03s_off.png" },
{ name : "園田 海未", cv : "三森すずこ", grade : 2, age : 16, birthday : "3/15", bloodType : "A", height : 159, B : 76, W : 58, H : 80, like : "穂乃果の家のまんじゅう", dislike : "炭酸飲料", color : "108,84,253", img : "04s_off.png" },
{ name : "星空 凛", cv : "飯田里穂", grade : 1, age : 15, birthday : "11/1", bloodType : "A", height : 155, B : 75, W : 59, H : 80, like : "ラーメン!", dislike : "お魚", color : "85,251,214", img : "05s_off.png" },
{ name : "西木野 真姫", cv : "Pile", grade : 1, age : 15, birthday : "4/19", bloodType : "AB", height : 161, B : 78, W : 58, H : 83, like : "トマト", dislike : "みかん", color : "255,97,54", img : "06s_off.png" },
{ name : "東條 希", cv : "楠田亜衣奈", grade : 3, age : 17, birthday : "6/9", bloodType : "O", height : 159, B : 90, W : 60, H : 82, like : "焼き肉", dislike : "キャラメル", color : "227,79,253", img : "07s_off.png" },
{ name : "小泉 花陽", cv : "久保ユリカ", grade : 1, age : 15, birthday : "1/17", bloodType : "B", height : 156, B : 82, W : 60, H : 83, like : "白いごはん", dislike : "なし", color : "66,206,112", img : "08s_off.png" },
{ name : "矢澤 にこ", cv : "徳井青空", grade : 3, age : 17, birthday : "7/22", bloodType : "A", height : 154, B : 74, W : 57, H : 79, like : "お菓子", dislike : "辛いもの", color : "255,95,219", img : "09s_off.png" }
];

var w = 800;
var h = 600;

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

member.sort(function(a, b){
    return b.height - a.height;
});

var r = 40; // 円の基本半径
var offset = 40;

// Scale
var widths = d3.scale.linear()
    .domain([0, 8])
    .range([r * 2, w - r*2]);

var heights = d3.scale.linear()
    .domain([member[0].height, member[member.length-1].height])
    .range([r + offset, h - r - offset]);

// Axis
var yAxis = d3.svg.axis()
    .scale(heights)
    .orient("left");

svg.append("g")
    .attr("transform", "translate(" + offset + ",0)")
    .attr("class", "select")
    .call(yAxis);

// Circle
svg.selectAll("circle")
    .data(member)
    .enter().append("circle")
    .attr("class", "member select")
    .attr("cx", function(d, i){
        return widths(i);
    })
    .attr("cy", function(d){
        return heights(d.height);
    })
    .attr("r", r)
    .attr("fill", function(d){
        return "rgb(" + d.color + ")";
    });

// text and shadowed text
var text = svg.append("g").selectAll("g")
    .data(member)
    .enter().append("g");

text.append("text")
    .text(function(d) { return d.name; })
    .attr("class", "shadow select")
    .attr("dx", function(d, i){
        return widths(i);
    })
    .attr("dy", function(d){
        return heights(d.height);
    })
    .attr("text-anchor", "middle");

text.append("text")
    .text(function(d) { return d.name; })
    .attr("class", "select")
    .attr("dx", function(d, i){
        return widths(i);
    })
    .attr("dy", function(d){
        return heights(d.height);
    })
    .attr("text-anchor", "middle");

// Run button
d3.select("button").on("click", function() {
    svg.selectAll(".select").remove();

    var minB = d3.min(member, function(d) { return d.B; });
    var maxB = d3.max(member, function(d) { return d.B; });
    var rate = 2;
    var widths = d3.scale.linear()
        .domain([maxB, minB])
        .range([maxB/rate + offset, w-maxB/rate - offset]);
    var heights = d3.scale.linear()
        .domain([member[0].height, member[member.length-1].height])
        .range([maxB/rate + offset, h - maxB/rate - offset]);

    var xAxis = d3.svg.axis()
        .scale(widths)
        .orient("bottom");

    svg.append("g")
        .attr("class", "select")
        .call(xAxis);

    var yAxis = d3.svg.axis()
        .scale(heights)
        .orient("left");

    svg.append("g")
        .attr("transform", "translate(" + offset + ",0)")
        .attr("class", "select")
        .call(yAxis);

    svg.selectAll("circle")
        .data(member)
        .enter().append("circle")
        .attr("class", "select")
        .attr("cx", function(d, i){
            return widths(d.B);
        })
        .attr("cy", function(d){
            return heights(d.height);
        })
        .attr("r", function(d) {
            return 5 + d.B / rate;
        })
        .attr("fill", function(d){
            return "rgb(" + d.color + ")";
        });

    var text = svg.append("g").selectAll("g")
        .data(member)
        .enter().append("g");

    text.append("text")
        .text(function(d) { return d.name; })
        .attr("class", "shadow select")
        .attr("dx", function(d, i){
            return widths(d.B);
        })
        .attr("dy", function(d){
            return heights(d.height);
        })
        .attr("text-anchor", "middle");

    text.append("text")
        .text(function(d) { return d.name; })
        .attr("class", "select")
        .attr("dx", function(d, i){
            return widths(d.B);
        })
        .attr("dy", function(d){
            return heights(d.height);
        })
        .attr("text-anchor", "middle");
});