var layers = this.base_layer.selectAll("g.layer")
.data(data)
.enter().append("svg:g")
.attr("stroke", function(d, i) {
return color(i / (n - 1));
})
.attr("class", "layer");
This one doesn't.
var layers = this.base_layer.selectAll("g.layer")
.data(data) ;
layers.enter().append("svg:g")
.attr("stroke", function(d, i) {
return color(i / (n - 1));
})
.attr("class", "layer");
ANy ideas what's going on here?
Cheers,
-max
-----------------
OML_line_chart3 = function(opts) {
this.opts = opts || {};
this.init = function(data) {
var n = data.length;
var o = this.opts;
var w = o['width'] || 400;
var h = o['height'] || 200;
var margin = o['margin'] || 20;
var offset = o['offset'] || [0, 0];
var x_max = this.x_max = d3.max(data, function(d) {return d.length});
var y_max = this.y_max = d3.max(data, function(d) {return d3.max(d)});
var y = this.y = d3.scale.linear().domain([0, y_max]).range([0 + margin, h - margin]);
var x = this.x = d3.scale.linear().domain([0, x_max]).range([0 + margin, w - margin]);
var color = d3.scale.category10(); /* d3.interpolateRgb("#f00", "#0f0"); */
var vis = d3.select(o['base_el'] || "body")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
var g = this.base_layer = vis.append("svg:g")
.attr("transform", "translate(0, " + h + ")");
var layers = this.base_layer.selectAll("g.layer")
.data(data)
/*** >>>>>>>> comment out the next two lines to make it work */
// ;
// layers
.enter().append("svg:g")
.attr("stroke", function(d, i) {
return color(i / (n - 1));
})
.attr("class", "layer");
//layers.exit().remove();
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return -1 * y(d); })
var layer = layers.selectAll("path")
.data(function(d) {
return [d];
})
;
layer
.enter()
.append("svg:path")
.attr("d", function(d) {
return line(d);
});
layer.exit().remove();
};
};
//var test = new OML_line_chart3({height: 300, width: 600});
var data = [[3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 7],
[5, 7, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2],
[12, 2, 9, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9]
];
var test2 = new OML_line_chart3({base_el: '#graph', offset: [50, 50]});
test2.init(data);
> var layers = this.base_layer.selectAll("g.layer")
> .data(data)
> .enter().append("svg:g")
This code defines `layers` as the updating selection, the result of `data()`:
> var layers = this.base_layer.selectAll("g.layer")
> .data(data) ;
If you are initializing the visualization, and no nodes exist yet,
then the updating selection will be empty, and all the nodes will be
in the entering selection.
I'm writing another tutorial on selections, but there's a bit more info here:
http://mbostock.github.com/d3/tutorial/bar-2.html
Mike
I think I understand that part (to some extent). What I don't understand seems to be some basic javascript behavior.
It looks like that:
x().y();
produces something different from:
var a = x();
a.y();
Translated to D3, why do the two different code snippets lead to different results?
var update = this.base_layer.selectAll("g")
.data(data);
var enter = update.enter();
----
var enter = = this.base_layer.selectAll("g")
.data(data)
.enter();
Strangely, it does work when I define the "lines" in the following section themselves using the same code pattern.
Yes, I did read the bar2 tutorial multiple times and I seem to follow the same pattern:
3 var rect = chart.selectAll("rect")
4 .data(data, function(d) { return d.time; });
5
6 // Enter…
7 rect.enter().insert("svg:rect", "line")
8 .attr("x", function(d, i) { return x(i) - .5; })
9 .attr("y", function(d) { return h - y(d.value) - .5; })
10 .attr("width", w)
11 .attr("height", function(d) { return y(d.value); });
Curious.
-max
http://en.wikipedia.org/wiki/Method_chaining
If you say `a = x()`, then the variable `a` is initialized to the
return value of the function `x`. If you say `a = x().y()`, then the
variable `a` is initialized to the return value of the function `y`.
If these two functions return different values, then the value of `a`
will be different.
As far as D3 goes, the `data` operator returns the updating selection:
the elements that already exist in the document, and that match the
data. If you don't have any elements in the document yet, then the
updating selection is empty.
The `data` operator also defines `enter()` and `exit()` methods on the
returned selection. These let you access the entering and exiting
selections, respectively. If the document doesn't have any elements
yet, then the `enter()` method will return placeholder elements for
each datum. You can then instantiate the elements in the document
using `append` or `insert`.
The code snippets you mentioned do the same thing. Say you have this:
var update = selection.data(data);
var enter = update.enter();
If you never need to refer to the `update` (updating) selection, you
can shorten the code:
var enter = selection.data(data).enter();
On the other hand, if you refer to the updating selection when you
meant to refer to the entering selection, and the updating selection
is empty, your code won't do anything.
Mike
var a = x().y();
Leads to a different outcome from:
var b = x(); var a = b.y();
That's the part I don't understand?
Yes. Because `b` is the updating selection and `a` is the entering selection.
Mike
1. var layers = …
Declares the variable `layers`, which will be initialized to the
return value of some long statement.
2. … = g.selectAll("g")
Given an existing selection `g` (here a single svg:g element), selects
all the child "g" elements. Since the svg:g element contained in the
existing selection `g` was just added, it has no children, and
selectAll("g") returns the empty selection.
3. .data(data)
Binds the empty selection returned by #2 to the specified array of
data. Here `data` is a 2-dimensional array; the `data` array has 3
elements, and each element is a 8-element array of numbers. Since the
result of #2 is the empty selection, the selection returned by the
`data` operator here is also empty: there are no existing nodes in the
selection, so the updating selection is empty. However, the returned
selection also has `enter()` and `exit()` methods now defined, which
we'll access in the next step.
4. .enter()
Returns the entering selection, which was created by #3. Because the
data was joined to an empty selection, there is one entering node per
data element; thus, there are 3 placeholder nodes returned in this
selection. The entering selection doesn't yet refer to actual nodes,
but we can create the nodes and add them to the document using the
`append` and `insert` operators.
5. .append("svg:g")
For each of the three placeholder nodes in the entering selection,
creates a new svg:g element and adds it to the original svg:g
container contained in `g` (the parent node referenced in #2).
6. .attr("stroke", function(d, i) { … })
For each of the three new svg:g elements added in #5, sets the
attribute "stroke" to the value returned by the specified function.
The argument `d` refers to each element in the `data` array (specified
in #3), which are arrays the three arrays of numbers. The argument `i`
refers to the numeric index: 0, 1 and 2. Returns `this`, in other
words, returns the current selection which is the three svg:g elements
added in #5.
Thus, the result of #1-5 is that `layers` is a selection of the three
svg:g elements added in #5.
OK, now compare this to your code that doesn't work:
1'. var layers = …
Same as #1, above.
2'. … = g.selectAll("g")
Same as #2.
3'. .data(data)
Same as #3. But note that the method chaining ends here, and thus
`layers` is initialized as the updating selection, which is empty
(because there are not yet any children in the selection `g`).
Thus, in the broken code, when you say `layers.selectAll("path")` on
L87, the `layers` selection is empty and nothing happens:
var layer = layers.selectAll("path")
.data(function(d) { return [d]; })
.enter().append("svg:path")
.attr("d", function(d) { return line(d); });
However, if you initialize `layers` to be the *entering* selection as
you do in the code that works, it contains the three svg:g elements
and all is happy.
Mike
var layers = g.selectAll("g")
.data(data);
var new_layers = layers
.enter().append("svg:g")
....
var layer = new_layers.selectAll("path")
.data(function(d) { return [d]; })
and it works.
But what happens when I do an update with a new 4 x N matrix where I essentially add a new line but the numbers for the existing ones may change.
Wouldn't the above just give me the newly added single layer? To update the remaining three, I assume I have to kind of duplicate the above to modify the existing ones?
var layer = new_layers.selectAll("path")
.data(function(d) { return [d]; })
.enter()...
// modify the existing line
var layer = layers.selectAll("path")
.data(function(d) { return [d]; })
.attr("d", function(d) { return line(d); });
or is there a more elegant solution?
Thanks again for explaining this to me. There is a lot less magic here when compared to Protovis, but still enough to trip me up :)
Cheers,
-max