1

I am attempting to use two different area generators inside one .data() selection, so that one set of points can have an area rendered from top to bottom while the second set's area can be rendered from left to right. I can pass fat arrow functions to every .attr() except 'd', which produces the following error:

Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "function area(da…".

I need to access the selection index in order to set the area from an array of booleans. Please see the jsfiddle I've set up with the relevant code. I've commented two of my attempts to pass a function to the .attr('d').

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
dukeluke
  • 646
  • 7
  • 22
  • Interestingly, even a simple, empty fat arrow function like `mySelection.attr('d', () => myArea)` throws an error – dukeluke Mar 27 '18 at 19:11
  • I'm using Opera, don't get errors and I can see graph. What's your browser?? – Klaujesi Mar 27 '18 at 19:29
  • It fails to load (after commenting line 18 and uncommenting line 19 or lines 20-26) in both Chrome and Firefox for me, although only Chrome throws an error. – dukeluke Mar 27 '18 at 19:37

2 Answers2

1

The issue in your code is simply the famous first argument in D3.

Your comment:

I was wondering why D3 executes the area generator automatically if it's passed by itself (.attr('d', myArea)) but doesn't automatically execute if the area generator is returned by an anonymous function...

Is not correct. D3 has no problems executing functions returned by functions.

When you do...

.attr('d', myArea)

... the datum is automatically passed as the first argument to myArea. Let's see a basic demo:

var data = ["foo", "bar", "baz"];
var sel = d3.select("body").selectAll(null)
  .data(data)
  .enter()
  .append("whatever")

sel.attr("whatever", callback)

function callback(d) {
  console.log("The datum is " + d)
}
<script src="https://d3js.org/d3.v5.min.js"></script>

So, if you want to put your myArea function inside an anonymous function, you have to specify the first argument, because the datum is passed to the outer function, not to myArea:

.attr('d', function(d){
//1st arg here------^
    myArea(d);
//and here-^
});

Let's see it:

var data = ["foo", "bar", "baz"];
var sel = d3.select("body").selectAll(null)
  .data(data)
  .enter()
  .append("whatever")

sel.attr("whatever", function(d) {
  callback(d);
})

function callback(d) {
  console.log("The datum is " + d)
}
<script src="https://d3js.org/d3.v5.min.js"></script>

Therefore, in your case, you just need:

.attr('d', (d, i) => (horizontalNotVertical[i]) ? horizontalArea(d) : verticalArea(d));

Just to show that D3 has no problems executing a function returned by another function, have a look at this last snippet:

var data = ["foo", "bar", "baz"];
var sel = d3.select("body").selectAll(null)
  .data(data)
  .enter()
  .append("whatever")

sel.attr("whatever", function() {
  callback();
})

function callback() {
  console.log("I don't have the datum!")
}
<script src="https://d3js.org/d3.v5.min.js"></script>

Here is the updated jsfiddle: https://jsfiddle.net/zt5vxbm6/

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • While trying to answer a [question](/questions/57204200) which is the TypeScript flavor of this question I thought there must be a nice dupe target for this type (don't want to close as duplicate, though, just reference it). Even your answer starts of talking about the *famous* first argument, i.e. *don't call the function, just pass its reference*... However, I wasn't able to find a nice, canonical target. Are you aware of any? – altocumulus Jul 26 '19 at 08:13
  • @altocumulus maybe [this one](https://stackoverflow.com/q/3246928/5768908)? – Gerardo Furtado Jul 26 '19 at 10:18
  • That's too general. I was looking for one specifically targeting D3. – altocumulus Jul 26 '19 at 10:46
0

if you inspect your generated code, you'll see this:

enter image description here

So, you are not geting values. Instead call the function.

Add (d) to the return value so that the area generator function is executed:

  .attr('d', function(d, i){
    if (horizontalNotVertical[i]) {
        return horizontalArea(d);
    } else {
        return verticalArea(d);
    }
  })
dukeluke
  • 646
  • 7
  • 22
Klaujesi
  • 1,816
  • 1
  • 13
  • 17
  • This works for me, thanks! I guess it makes sense that D3 simply executes a function if passed to `attr('d')`, whether that function is a fat arrow function or an area generator, but I'm curious as to why D3 doesn't similarly execute the area generator returned by the fat arrow function. – dukeluke Mar 27 '18 at 19:52
  • 1
    On both cases, you need to give the data: .attr('d', (d, i) => (horizontalNotVertical[i]) ? horizontalArea(d) : verticalArea(d)) – Klaujesi Mar 27 '18 at 20:04
  • Sorry, my comment was unclear. I was wondering why D3 executes the area generator automatically if it's passed by itself (`.attr('d', myArea)`) but doesn't automatically execute if the area generator is returned by an anonymous function...but now I realize that if D3 were to execute a functions returned by functions, it could lead to a recursion error! – dukeluke Mar 27 '18 at 20:17