syakoo's Lab

技術ブログやちょっと気合の入った記事を残すブログ

tooltipにグラフを描写する[解説](d3.js)

はじめに

ここは下の記事で紹介したtooltipを作った過程や解説をしているので、コードをお求めの方は以下のリンクから記事に飛んでください。 syakoo.qrunch.io

まずは普通のtooltipをd3で書く

いきなりグラフを書いてもいいのだが、tooltipを書くのも久しいのでまずは普通のtooltipを書いてみる

//tooltipの設定
var tooltip = d3.select('#tooltip')
    .style("position", "absolute")
    .style("padding", "5px")
    .style("background","rgba(256,256,256,0.8)")
    .style("border","3px")
    .style("opacity","0")
    .style("border-radius","8px");

//textを表示する
tooltip.html("TEXT");

tooltipの設定に関してはcssで直接書けばいいじゃんと思うけれど、今回はd3のみで実装したいので見にくくても我慢.... opacityが"0"になっているので、今は表示しても見れません。

早く結果が見たいため、前回作ったグラフのrectの下にイベントを追加

.on("mouseover", function (d) {
    tooltip.html("戦法名:" + d.name + " 先手勝率:" + d.value + "%")
        .style("opacity", "0.9");
    })
    .on("mousemove", function () {
        tooltip.style("left", d3.event.pageX + "px")
            .style("top", (d3.event.pageY - 50) + "px");
    })
    .on("mouseout", function () {
        tooltip.style("opacity", "0");
    });

もちろんhtmlで呼び出す(最初忘れてたなんて言えない)

<div id="tooltip" class="tooltip"></div>
<script type="text/javascript" src='tooltip.js'></script>

出力

undefined.jpg カーソルは心がきれいな人が見えます(自分は見えない)

なぜ関数で書かないのか

最初の記事でもある「メーターっぽいグラフ」は関数で宣言していたが、今回は関数で宣言していない。なぜなのかというと、グラフは他の物と関わることはほとんどないがtooltipは他の物のデータを拾ってきて出力するので、グローバルの方が楽なのである。 まあ、楽ってだけなので、書こうと思えばかけるが、若干(ほんとに若干)複雑になるので記事にするには邪魔と判断した。 でも思ったより最終的にコード長くなったので関数で書いたほうが逆によかったかも


やりたいことを説明

図で書くとこんな感じ。今まで文字を入れていたところにsvg領域をねじ込んでその上にいつも通りにグラフを描写すれば理論上はできると思った。 undefined.jpg

tooltipを拡張させる

前回作ったtooltipのテキストを表示させていたところをいじっていく。それでできたのがこちら

//tooltipやら円グラフの色やらの設定
var width = 200,
    height = 200,
    color = ["#7cfc00", "#333333"];

//tooltipの設定
var tooltip_ex = d3.select('#tooltip_ex')
    .style("position", "absolute")
    .style("padding", "3px")
    .style("background", "rgba(0,0,0,0.8)")
    .style("border", "3px")
    .style("opacity", "0")
    .style("border-radius", "8px");

    
//tooltipに寄生するものを書く(今回は円グラフ)--------------------------------------
//半径を求めておく
var radius = Math.min(width, height) / 2 - 20;

//グラフ全体の設定。中央に移動している
var chart = tooltip_ex.append("svg")
    .attr("width",width).attr("height",height)
    .append("g")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

//円グラフ用のデータを生成する物を作成
var pie = d3.pie().value(function (d) {
    return d;
}).sort(null);

//円グラフの描写。一回適当な値を入れて描写しておく。
chart.selectAll(".arc").data(pie([10, 10])).enter()
    .append("path").attr("class", "arc")
    .attr("fill", function (d, i) {
        return color[i];
    }).attr("d", d3.arc().outerRadius(radius).innerRadius(radius * 0.6))
    .attr("stroke", "white");

//円グラフの中央に書くテキスト
var text = chart.append("text")
    .style("text-anchor", "middle")
    .attr("fill", color[0]).style("font-weight","600");

/**
 * 円グラフのデータを更新する
 * @param {float} data 0~100までの値 
 */
function set_data(data) {
    //円グラフの値の更新。
    chart.selectAll(".arc").data(pie([data, 100 - data]))
        .attr("d", d3.arc().outerRadius(radius).innerRadius(radius * 0.6));

    //最初の円だけ大きくする
    chart.select(".arc")
        .attr("d", d3.arc().outerRadius(radius * 1.1).innerRadius(radius * 0.5));

    //テキストも更新
    text.text(data + "%");
}

16行目からsvg領域を寄生させ円グラフを描写している。 それぞれのブロックで何をしているかはコメントで書いているから大丈夫だと思う。ハマったのは円グラフのデータを更新する関数set_data()の中身。 "d"の中身は毎回同じだしデータの値によって変わらないと思って最初書いていなかったが、書かないとデータが更新されない。これは実際にd3.arc()を訪ねると解決すると思う。とりあえず、更新するときに必要なものであるということは頭に入れようと思う。

メーターグラフの記述も前回と変えた

.on("mouseover", function (d) {
    set_data(d.value);
    tooltip_ex.style("opacity", "0.99");
})
.on("mousemove", function () {
    tooltip_ex.style("left", d3.event.pageX + "px")
        .style("top", (d3.event.pageY - 230) + "px");
})
.on("mouseout", function () {
    tooltip_ex.style("opacity", "0");
});

変えたのはmouseoverのイベントの入力を文字からデータにしたところだけ。

出力

undefined.jpg