gka / d3-jetpack

🚀 Nifty convenience wrappers that speed up your daily work with d3.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

enter().insert(...) doesn't preserve order

Epiphero opened this issue · comments

The d3 semantics of enter().insert(...) are such that when performing an insert on the enter selection of a join with a key, the "entering elements [should] be inserted immediately before the next following sibling in the update selection, if any."

However, when using d3-jetpack, the entering elements are merely appended to the end.

The following is a toy example:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Insert Order Bug</title>
        <script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
        <script src="https://raw.githubusercontent.com/gka/d3-jetpack/master/d3-jetpack.js"></script>
    </head>
    <body>
        <script type="text/javascript">
var identity = function (x) { return x; };

// Make a list of odd numbers less than 10
d3
    .select("body")
    .selectAll("div")
    .data([1, 3, 5, 7, 9], identity)
    .enter()
    .insert("div")
    .html(identity)
;

// Change the list to prime numbers less than 10
var update = d3
    .select("body")
    .selectAll("div")
    .data([2, 3, 5, 7], identity)
;

// Color the update selection yellow
update.style("background-color", "yellow");

// Insert and color the enter selection green
update
    .enter()
    .insert("div")
    .html(identity)
    .style("background-color", "green")
;

// Remove those pesky composite numbers
update
    .exit()
    .remove()
;
        </script>
    </body>
</html>

This page will show the list "3, 5, 7, 2", however commenting out the d3-jetpack script will preserve the d3 semantics and produce the list "2, 3, 5, 7".

And a very quick & dirty patch to fix:

--- d3-jetpack.js.orig	2017-02-08 16:49:07.000000000 -0600
+++ d3-jetpack.js	2017-02-08 16:47:26.000000000 -0600
@@ -57,8 +57,21 @@

     //no selection.enter in v4
     if (d3.selection.enter){
-        d3.selection.enter.prototype.append = d3.selection.prototype.append
-        d3.selection.enter.prototype.insert = d3.selection.prototype.insert
+        function d3_selection_enterInsertBefore(enter) {
+            var i0, j0;
+            return function(d, i, j) {
+                var group = enter[j].update, n = group.length, node;
+                if (j != j0) j0 = j, i0 = 0;
+                if (i >= i0) i0 = i + 1;
+                while (!(node = group[i0]) && ++i0 < n) ;
+                return node;
+            };
+        }
+        d3.selection.enter.prototype.append = d3.selection.prototype.append;
+        d3.selection.enter.prototype.insert = function(name, before) {
+            if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
+            return d3.selection.prototype.insert.call(this, name, before);
+        };
     }

     var d3_parse_attributes_regex = /([\.#])/g;

And as I continue to dig, this only applied to d3.v3, d3.v4's enter().append(...) takes over the former enter().insert(...) semantics, and d3-jetpack works fine with that.

Weird! We are only supporting v4 now