How to get D3 tutorial bar chart working? [duplicate] - javascript

I'm trying to work out what's going on in Mike Bostock's Box Plot example from the D3 gallery. Here's the code inside an Observable notebook: https://observablehq.com/#d3/box-plot
In it there's a code block that does not appear to be a function definition but that has a return value:
chart = {
const svg = d3.select(DOM.svg(width, height));
const g = svg.append("g")
.selectAll("g")
.data(bins)
.join("g");
// [...]
return svg.node();
}
What does return do or mean when it is not in a function definition?

Yep, as the commenters have suggested, this is a syntax that's particular to Observable. What you're seeing a cell that uses a block, as mentioned in the Introduction to Code.
How you can think of this relative to other JavaScript is that it's kind of like an IIFE, but with the added consideration that, if it references other cells, it automatically resolves them. So in vanilla JavaScript, this would be like:
chart = (() => {
const svg = d3.select(DOM.svg(width, height));
const g = svg.append("g")
.selectAll("g")
.data(bins)
.join("g");
// [...]
return svg.node();
})()
In fact, that's roughly what they compile to. The particular syntax is that way because it's meant to be clear that it's code that runs when references change - see how Observable runs for details on that. Unlike an IIFE, a cell in Observable might run multiple times, if something that it references, like a generator or Promise, changes.

Related

Uncaught SyntaxError: Unexpected identifier - Try to understand D3 js [duplicate]

I'm trying to work out what's going on in Mike Bostock's Box Plot example from the D3 gallery. Here's the code inside an Observable notebook: https://observablehq.com/#d3/box-plot
In it there's a code block that does not appear to be a function definition but that has a return value:
chart = {
const svg = d3.select(DOM.svg(width, height));
const g = svg.append("g")
.selectAll("g")
.data(bins)
.join("g");
// [...]
return svg.node();
}
What does return do or mean when it is not in a function definition?
Yep, as the commenters have suggested, this is a syntax that's particular to Observable. What you're seeing a cell that uses a block, as mentioned in the Introduction to Code.
How you can think of this relative to other JavaScript is that it's kind of like an IIFE, but with the added consideration that, if it references other cells, it automatically resolves them. So in vanilla JavaScript, this would be like:
chart = (() => {
const svg = d3.select(DOM.svg(width, height));
const g = svg.append("g")
.selectAll("g")
.data(bins)
.join("g");
// [...]
return svg.node();
})()
In fact, that's roughly what they compile to. The particular syntax is that way because it's meant to be clear that it's code that runs when references change - see how Observable runs for details on that. Unlike an IIFE, a cell in Observable might run multiple times, if something that it references, like a generator or Promise, changes.

More modular D3.js coding

Consider the code snippet
let circles = svg.selectAll("circle")
.data(data)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 2);
The three lines attr-cx, attr-cy, and attr-r operate internally using the following pseudo-code:
foreach d in update-selection:
d.cx = (expression)
foreach d in update-selection:
d.cy = (expression)
foreach d in update-selection:
d.r = (constant)
Now suppose that we want to do it differently. We'd like to instead run:
foreach d in update-selection:
d.cx = (expression)
d.cy = (expression)
d.r = (constant)
by writing either
let circles = svg.selectAll("circle")
.data(data)
.myfunction(d => d);
or
let circles = svg.selectAll("circle")
.data(data)
.myfunction(d);
We might want to do this because:
No matter how fast the iteration control, it's still faster if we iterate once rather than three times.
The sequence of attr-cx, attr-cy, and attr-r is not just three statements, but a sequence of many dozens or hundreds of statements (that manipulate attributes, among other changes), and we'd like to isolate them into a separate block for readability and testability.
As an exercise to better understand the options available when coding in D3.
How might you isolate the triple of attr statements through a single function call?
Update
Towards Reusable Charts is a rare post from Mike Bostock suggesting a way to organize a visualization by separating the bulk of the code into a separate module. You know the rest: modularity facilitates reuse, enhances teamwork by programming against APIs, enables testing, etc. Other D3.js examples suffer for the most part from a reliance on monolithic programming that is more suited for discardable one-shot visualizations. Are you aware of other efforts to modularize D3.js code?
TL;DR: there is no performance gain in changing the chained attr methods for a single function that sets all attributes at once.
We can agree that a typical D3 code is quite repetitive, sometimes with a dozen attr methods chained. As a D3 programmer I'm used to it now, but I understand the fact that a lot of programmers cite that as their main complaint regarding D3.
In this answer I'll not discuss if that is good or bad, ugly or beautiful, nice or unpleasant. That would be just an opinion, and a worthless one. In this answer I'll focus on performance only.
First, let's consider a few hypothetical solutions:
Using d3-selection-multi: that may seem as the perfect solution, but actually it changes nothing: in its source code, d3-selection-multi simply gets the passed object and call selection.attr several times, just like your first snippet.
However, if performance (your #1) is not an issue and your only concern is readability and testability (as in your #2), I'd go with d3-selection-multi.
Using selection.each: I believe that most D3 programmers will immediately think about encapsulating the chained attr in an each method. But in fact this changes nothing:
selection.each((d, i, n)=>{
d3.select(n[i])
.attr("foo", foo)
.attr("bar", bar)
//etc...
});
As you can see, the chained attr are still there. It's even worse, not that we have an additional each (attr uses selection.each internally)
Using selection.call or any other alternative and passing the same chained attr methods to the selection.
These are not adequate alternatives when it comes to performance. So, let's try another ways of improving performance.
Examining the source code of attr we can see that, internally, it uses Element.setAttribute or Element.setAttributeNS. With that information, let's try to recreate your pseudocode with a method that loops the selection only once. For that, we'll use selection.each, like this:
selection.each((d, i, n) => {
n[i].setAttribute("cx", d.x);
n[i].setAttribute("cy", d.y);
n[i].setAttribute("r", 2);
})
Finally, let's test it. For this benchmark I wrote a very simple code, setting the cx, cy and r attributes of some circles. This is the default approach:
const data = d3.range(100).map(() => ({
x: Math.random() * 300,
y: Math.random() * 150
}));
const svg = d3.select("body")
.append("svg");
const circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cx", d=>d.x)
.attr("cy", d=>d.y)
.attr("r", 2)
.style("fill", "teal");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
And this the approach using setAttribute in a single loop:
const data = d3.range(100).map(() => ({
x: Math.random() * 300,
y: Math.random() * 150
}));
const svg = d3.select("body")
.append("svg");
const circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.each((d, i, n) => {
n[i].setAttribute("cx", d.x);
n[i].setAttribute("cy", d.y);
n[i].setAttribute("r", 2);
})
.style("fill", "teal")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Finally, the most important moment: let's benchmark it. I normally use jsPerf, but it's down for me, so I'm using another online tool. Here it is:
https://measurethat.net/Benchmarks/Show/6750/0/multiple-attributes
And the results were disappointing, there is virtually no difference:
There is some fluctuation, sometimes one code is faster, but most of the times they are pretty equivalent.
However, it gets even worse: as another user correctly pointed in their comment, the correct and dynamic approach would involve looping again in your second pseudocode. That would make the performance even worse:
Therefore, the problem is that your claim ("No matter how fast the iteration control, it's still faster if we iterate once rather than three times") doesn't need to be necessarily true. Think like that: if you had a selection of 15 elements and 4 attributes, the question would be "is it faster doing 15 external loops with 4 internal loops each or doing 4 external loops with 15 internal loops each?". As you can see, nothing allows us to say that one is faster than the other.
Conclusion: there is no performance gain in changing the chained attr methods for a single function that sets all attributes at once.
Does the .call() method of the d3 selection do what you're after? Documentation at https://github.com/d3/d3-selection/blob/v1.4.1/README.md#selection_call
I have sometimes used this method to define a more 'modular' feeling update function, and even pass different functions into the call() to do different things as required.
In your example I think we can do:
function updateFunction(selection){
selection
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 2);
}
let circles = svg.selectAll("circle")
.data(data)
.call(updateFunction);

Return value in non-function Javascript code block

I'm trying to work out what's going on in Mike Bostock's Box Plot example from the D3 gallery. Here's the code inside an Observable notebook: https://observablehq.com/#d3/box-plot
In it there's a code block that does not appear to be a function definition but that has a return value:
chart = {
const svg = d3.select(DOM.svg(width, height));
const g = svg.append("g")
.selectAll("g")
.data(bins)
.join("g");
// [...]
return svg.node();
}
What does return do or mean when it is not in a function definition?
Yep, as the commenters have suggested, this is a syntax that's particular to Observable. What you're seeing a cell that uses a block, as mentioned in the Introduction to Code.
How you can think of this relative to other JavaScript is that it's kind of like an IIFE, but with the added consideration that, if it references other cells, it automatically resolves them. So in vanilla JavaScript, this would be like:
chart = (() => {
const svg = d3.select(DOM.svg(width, height));
const g = svg.append("g")
.selectAll("g")
.data(bins)
.join("g");
// [...]
return svg.node();
})()
In fact, that's roughly what they compile to. The particular syntax is that way because it's meant to be clear that it's code that runs when references change - see how Observable runs for details on that. Unlike an IIFE, a cell in Observable might run multiple times, if something that it references, like a generator or Promise, changes.

Square grid exit selection in D3 v4

I have been working on changing this block to v4 with my limited exposure to d3js.
The changes are made are;
"https://d3js.org/d3.v3.js" to "https://d3js.org/d3.v4.js"
.ease('linear') to .ease(d3.easeLinear)
which i understood further from the change readme here.
The changes results in the chart not being able to reach the cell.exit.transition() block when I tried to console.log the exit function, this is also evident as the grids do not exit and just append from new randomized data:
cell.exit().transition()
.delay(function(d, i) { return (n0 - i) * updateDelay; console.log(n0, n1);})
.duration(updateDuration)
.attr("width", 0)
.remove();
From the readme, there is no change in the transition methods but I am thinking this is due to the change in the select function. I am having trouble seeing what when wrong as there seems to be no errors within the console when I run this.
The problem here seems to be the infamous magic behaviour that was introduced by Mike Bostock (D3 creator) in D3 v2, and later removed in D3 v4.
According to Bostock:
D3 2.0 introduced a change: appending to the enter selection would now copy entering elements into the update selection [...] D3 4.0 removes the magic of enter.append. (In fact, D3 4.0 removes the distinction between enter and normal selections entirely: there is now only one class of selection.)
You can read more about this issue in these answers: https://stackoverflow.com/a/47032222/5768908 and https://stackoverflow.com/a/45093007/5768908
In your case, the solution is changing the update selection to this:
var cellUpdate = cell.selectAll("rect")
.data(d3.range(n1));
And then:
cellUpdate.exit()
//etc...
cellUpdate.enter()
.append("rect")
//etc...
Here is the updated bl.ocks: https://bl.ocks.org/GerardoFurtado/b0d66087d9888a2cac3a42b114e5e8c4/72a0e54de5ce8cba2c398b282d953dd5c2bcc66e
PS: for this to work in v4/5 (but not from v5.8 onwards) you have to change the text tween as well:
.tween("text", function() {
var self = this;
var i = d3.interpolateNumber(n0, n1);
return function(t) {
self.textContent = formatNumber(Math.round(i(t)));
};
});

How to use 'this' in Angular with D3?

Tldr; How do I deal with this in reference to a D3 object when Angular binds this to the class (component/service)?
I am looking to use D3.js (v.4) in an Angular (v.4) app.
My code works in standalone JavaScript but I now need to integrate it into an Angular app.
The use of this is tripping me up.
I have an SVG group that I wish to drag and so I use .call(drag)
someFunction() {
this.unitGroup = this.svg.append('g')
.attr('id', 'unitGroup');
.call(drag)
}
My problem comes about when I try to reference the svg element that is being dragged. In my original code, I could refer to this e.g. let matrix = this.getCTM(). Using this is now not working when using this code within a service.
drag = d3.drag()
.on('start', () => {
this.setClickOrigin(d3.event);
})
.on('drag', (d) => {
const m = this.getCTM(); // <--- PROBLEM HERE
const x = d3.event.x - this.clickOrigin.x;
const y = d3.event.y - this.clickOrigin.y;
this.setClickOrigin(d3.event);
d3.select(this) // <--- PROBLEM HERE
.attr('transform', `matrix(${m.a},${m.b},${m.c},${m.d},${m.e + x},${m.f + y})`);
});
Any pointers on how to implement this or clarification of what I am doing wrong would be appreciated.
I don't think this is simply an error associated with the arrow function this binding as .on('drag', function(d){...} results in the same error.
Here is Plunker illustrating my issue: https://embed.plnkr.co/p1hdhMBnaebVPB6EuDdj/
In most of D3 methods, this refers to the DOM element, and it is the most simple way to get the element. However, you're facing some problems using this in your angular code.
The good news is that there is an idiomatic way to get the current DOM element without relying on this (and without relying on d3.event as well): using the second and the third arguments combined. This is quite useful in situations where you cannot use this, like your situation right now or when using an arrow function, for instance.
That alternative to this is extensively documented on the API. For most D3 methods, you can read that the method is...
... being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). (both emphases mine)
So, in a common D3 code, you can get the DOM element using:
.on("whatever", function(){
d3.select(this).etc...
// ^--- 'this' is the DOM element
Or:
.on("whatever", function(d,i,n){
// ^-^--- second and third arguments
d3.select(n[i]).etc...
// ^--- here, 'n[i]' is also the DOM element
Therefore, in your case, just use:
.on('drag', (d,i,n) => {
const m = d3.select(n[i]).node().getCTM();
//the same of 'this'-----^
...
}
Since d3.select(n[i]) is a selection, you'll have to use node() to get the actual DOM element.
Here is your updated plunker: https://plnkr.co/edit/tjQ6lV411vAUcEKPh0ru?p=preview
Try using d3.event.sourceEvent.target:
.on('drag', () => {
const target = d3.event.sourceEvent.target;
const m = target.getCTM();
const x = d3.event.x - this.clickOrigin.x;
const y = d3.event.y - this.clickOrigin.y;
this.setClickOrigin(d3.event);
d3.select(target)
Forked Plunker Example

Categories

Resources