13

バックボーンビューでD3を使用したグラフの視覚化に取り組んでいます。ユーザーがグラフをピンチズームし、Webkitトランスフォームを使用してスムーズに移行し、リリース時に再描画できるようにします。コードを単純にするために、要素の新しい位置とサイズを再計算するのではなく、新しいスケールでグラフを再描画しています(これは私の元のアプローチでしたが、私のチームは再描画ルートを要求しました)。

[ツイッターでボストックと話した。これは実際には好ましい方法ではありません]

私が気付いているのは、再描画するたびに、クリーンアップされていない大量のdomノードをダンプしているということです。

ラベル以外のすべてを無効にしたため(これらにはハンドラーがアタッチされていないため)、これはイベントハンドラー/クロージャー内の循環参照とは関係ありません。同じ動作が発生します。

グラフから要素を積極的に削除しようとしましたが、それでもdomノードがリークしているようです。

ここにいくつかの関連するコードがあります。'render'は、新しいラベルのセットに対して呼び出されます。ズームが終了すると、古いグラフで「close」が呼び出され、別のビューのインスタンス化と「render」の呼び出しで新しいグラフが作成されます。

render: function() {

        // create the svg offscreen/off dom
        //document.createElementNS(d3.ns.prefix.svg, "svg")
        var svg = this.svg = d3.select(this.el)
            .append("svg:svg")
            .attr('width', this.VIEW_WIDTH)
            .attr('height', this.VIEW_HEIGHT)

        this._drawTimeTicks.call(this, true);
        return this;
    },



_drawTimeTicks: function(includeLabels) {
    var bounds = this.getDayBounds();
    var min = bounds.start;
    var date = new Date(min);
    var hour = 1000 * 60 * 60;
    var hourDiff = 60 * this.SCALE;
    var graphX = (date.getTime() - min) / 1000 / 60;
    var textMargin = 7;
    var textVert = 11;

    // Using for loop to draw multiple vertical lines
    // and time labels.

    var timeTicks = d3.select(this.el).select('svg');
    var width = timeTicks.attr('width');
    var height = timeTicks.attr('height');

    for (graphX; graphX < width; graphX += hourDiff) {
        timeTicks.append("svg:line")
            .attr("x1", graphX)
            .attr("y1", 0)
            .attr("x2", graphX)
            .attr("y2", height)
            .classed('timeTick');

        if (includeLabels) {
            timeTicks.append("svg:text")
                .classed("timeLabel", true)
                .text(this.formatDate(date))
                .attr("x", graphX + textMargin)
                .attr("y", textVert);
        }

        date.setTime(date.getTime() + hour);
    }



close: function() {
        console.log("### closing the header");
        this.svg.selectAll('*').remove();
        this.svg.remove();
        this.svg = null;
        this.el.innerHTML = '';

        this.unbind();
        this.remove();
    }

ご覧のとおり、私はイベントハンドラーやクロージャでトリッキーなことは何もしていません。いくつかのズーム操作で、GCによって再利用されることのない数十のdomノードをリークできます。

これはメモリリークですか、それともd3は将来のグラフの作成/更新を最適化するために、舞台裏で何かを行っていますか?私が知らないグラフを破壊するためのより良い方法はありますか?

何か案は?

4

1 に答える 1

21

D3はノードへの非表示の参照を保持しないため、「DOMノードのクリーンアップ」の内部概念はありません。DOM要素の配列である選択とDOM自体があります。DOMから要素を削除し、その要素への追加の参照を保持しない場合は、ガベージコレクターによって再利用される必要があります。

(余談ですが、参照しているリークがDOMに残っている要素なのか、孤立した要素がガベージコレクターによって再利用されていないのかは明らかではありません。以前は、一部の古いブラウザーには、DOM要素とJavaScriptクロージャー間の循環参照をガベージコレクションするバグがありました。 、しかし、私は最近のブラウザに影響を与えるそのような問題を認識していません。)

DOMを更新する場合、これを行う最もパフォーマンスの高い方法は、(一般に)データ結合を使用することです。これにより、既存の要素を再利用し、変更が必要な属性のみを変更できるためです。データ結合にキー関数を使用することも、オブジェクトの不変性のための良いアイデアです。更新の前後の両方で同じデータが表示される場合は、そのすべての属性を再計算する必要がない可能性があり、ブラウザーは更新の作業を少なくします。

場合によっては、子孫要素の位置を更新するのではなく、囲んでいるG要素の変換属性を更新するなど、より高速な任意の更新の代替手段があります。別の例として、viewBox属性を変更するだけで、SVG要素内で幾何学的ズームを実行できます。しかし、幾何学的ズームは非常に限られたケースです。一般に、最も効率的な更新は、正確に何が変更されているかによって異なります。データ結合を使用して、追加または削除する要素の数を最小限に抑え、再計算する必要のある属性の数を最小限に抑えることができます。

私が指摘する他のいくつかのこと…</p>

  • forループを使用する代わりに、データ結合を使用して同時に複数のティックを作成することができます。データ結合はループなしで複数の要素(および階層構造)を作成できるため、ForループがD3で使用されることはほとんどありません。さらに良いことに、軸コンポーネント(d3.svg.axis)と時間スケール(d3.time.scale)を時間形式(d3.time.format)で使用します。

  • 最近のバージョンのD3は「svg:」名前空間を必要としないため、「text」、「line」などを追加できます。

  • selectAll("*").remove()理にかなっている状況は考えられません。「*」セレクターはすべての子孫に一致するため、これによりすべての子孫が親から削除されます。子要素を冗長に削除するのではなく、常に最上位の要素(ここではSVGコンテナ)を削除するようにしてください。

于 2012-06-21T23:01:22.450 に答える