flotの選択部分を拡大

flot って何?

flot (http://code.google.com/p/flot/) というのはjQueryで動作するJavaScriptのグラフプロットライブラリです。
動的にさまざまなグラフを生成することができます。
いいからさっさとサンプル見せろよって人は、公式のサンプルが http://people.iola.dk/olau/flot/examples/ にあります。

プロットされたグラフの一部を選択する

flotでは、x軸/y軸ともにドラッグアンドドロップによる選択が標準で用意されています。
たとえば x軸のみ選択可能にしたい場合はプロットするときのオプションを

var options = {
	selection: {
		mode: "x"
	}
};
$.plot(target, data, options)

のようにするだけです。x軸/y軸ともに選択したい場合は mode: "xy" となります。
範囲選択をすると "plotselected" というイベントが通知されます。

選択範囲の拡大

公式サンプルの "Selection support and zooming" にあるので、まずはそちらを見てみましょう。

var placeholder = $("#placeholder");

placeholder.bind("plotselected", function (event, ranges) {
    $("#selection").text(ranges.xaxis.from.toFixed(1) + " to " + ranges.xaxis.to.toFixed(1));

    var zoom = $("#zoom").attr("checked");
    if (zoom)
        plot = $.plot(placeholder, data,
                      $.extend(true, {}, options, {
                          xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to }
                      }));
});

となってますね。ズームが有効な時は $.plot() をしなおしてると…。毎回グラフを作り直してます。
ちなみに $.plot() は

    $.plot = function(target, data, options) {
        var plot = new Plot($(target), data, options, $.plot.plugins);
        /*var t0 = new Date();
        var t1 = new Date();
        var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
        if (window.console)
            console.log(tstr);
        else
            alert(tstr);*/
        return plot;
    };

となっています。Plotクラスのインスタンスをつくってそれを返しています。
今回やりたいことはグラフのズームだけなのに毎回グラフ全体を作り直す必要があるんでしょうか?
とっても無駄な気がします。

無駄な処理があるだけじゃなくてメモリも徐々に消費していく…?

少し話がそれてしまうんですが、今回この話を書こうと思ったのは無駄な処理が気になったわけではないのです。
先ほどの公式サンプルで "Zoom to selection." をチェックして何度も何度も拡大してみてください。
何度も繰り返していると徐々にブラウザのメモリ使用量が増えていませんか?
Firefox3.5, Google chrome 2.0 で確認したところどちらも顕著に増えています。
(IE8でもためそうとしたところそもそもマトモにグラフが表示されませんでした。なんでだろう?)

原因は明らかに毎回インスタンスを作り直すところなので、一度生成した canvas を使い回せば改善するんじゃないかと考えたわけです。

選択範囲の拡大

というわけで、がんばってグラフの再描画をするときに Plot インスタンスを使い回すようにしてみましょう。
まずは公式のAPIリファレンス*1を見てみましょう。
"Plot Methods" というところで何ができるか把握する必要があります。
下記に "APIリファレンスに載っている" メソッドを列挙します。

  • clearSelection()
  • setSelection(ranges, preventEvent)
  • highlight(series, datapoint)
  • unhighlight(series, datapoint)
  • setData(data)
  • setupGrid()
  • draw()
  • getData()
  • getAxes()
  • getCanvas()
  • getPlotOffset()

setData(), setupGrid(), draw() あたりが再描画に使えそうな感じですね。
説明を見てもそれっぽいことが書いてあります。
あ、今回は表示の拡大なので setData() は使わないですね。

残ったのは setupGrid() と draw() です。どちらも引数を取らないメソッドですね。
options はどこで指定するんでしょうか…。
拡大は options を設定しなおして再描画、というだけでできそうなんですがオプションが取れないですね。
こういう場合はとりあえずソースを見てみましょう。
オプションを返すメソッドがないなら最悪自作すればいいです。

        // public functions
        plot.setData = setData;
        plot.setupGrid = setupGrid;
        plot.draw = draw;
        plot.clearSelection = clearSelection;
        plot.setSelection = setSelection;
        plot.getSelection = getSelection;
        plot.getCanvas = function() { return canvas; };
        plot.getPlotOffset = function() { return plotOffset; };
        plot.width = function () { return plotWidth; };
        plot.height = function () { return plotHeight; };
        plot.offset = function () {
            var o = eventHolder.offset();
            o.left += plotOffset.left;
            o.top += plotOffset.top;
            return o;
        };
        plot.getData = function() { return series; };
        plot.getAxes = function() { return axes; };
        plot.getOptions = function() { return options; };
        plot.highlight = highlight;
        plot.unhighlight = unhighlight;
        plot.triggerRedrawOverlay = triggerRedrawOverlay;
        plot.pointOffset = function(point) {
            return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left),
                     top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) };
        };

ソースを見てみるとAPIリファレンスに載っていないメソッドもいっぱい載ってるぞ! やった!

        plot.getOptions = function() { return options; };

ん…? options をそのまま返しているメソッドが…!
これを使えば拡大ができそうです!
注意しないといけないのは、ここで戻してもらった options に別の配列とかをぶちこむともともとのoptionsと表示がずれる可能性があります。
必要なところだけ上書きしてあげましょう。

var plot = $.plot(target, data, options);
var placeholder = $("#placeholder");

placeholder.bind("plotselected", function(event, ranges){
	var options = plot.getOptions();

	options.xaxis.min = ranges.xaxis.from;
	options.xaxis.max = ranges.xaxis.to;
	options.yaxis.min = ranges.yaxis.from;
	options.yaxis.max = ranges.yaxis.to;
			
	plot.clearSelection();
	plot.setupGrid();
	plot.draw();
});

という感じですね。
ちなみに、「拡大したらページをリロードするまで元に戻さないぜフゥハハァー!」という人以外は元の大きさまで戻す手段も用意しておくといいですね。
min, maxをそれぞれ null にするとデフォルトの設定にもどります。あ、optionsで設定変えてるひとはその値を使うようにしてください。

placeholder.dblclick(function(event){
	var options = plot.getOptions();

	options.xaxis.min = null;
	options.xaxis.max = null;
	options.yaxis.min = null;
	options.yaxis.max = null;
			
	plot.clearSelection();
	plot.setupGrid();
	plot.draw();
});

まとめ

Plotインスタンスを使い回すと速いしすっきりする。