Skip to content
This repository has been archived by the owner on Mar 17, 2020. It is now read-only.

Can't use function in d3.tip.attr() #174

Closed
neuquen opened this issue Oct 1, 2016 · 8 comments · May be fixed by #261
Closed

Can't use function in d3.tip.attr() #174

neuquen opened this issue Oct 1, 2016 · 8 comments · May be fixed by #261

Comments

@neuquen
Copy link

neuquen commented Oct 1, 2016

I'd like to style two separate tooltips, depending on the data, so I want to pass in a function, but it doesn't look like that is currently supported.

I'm trying to do the below:

d3.tip()
  .attr("class", function(d) {
    if(d.value === true) {
      return "class1";
    } else {
      return "class2";
  }
})
@vdh
Copy link
Collaborator

vdh commented Oct 3, 2016

tip.attr is supposed to be a simple proxy to d3-selection's attr.

  1. Which version of d3-tip are you using?
  2. Which version of d3 are you using?

@neuquen
Copy link
Author

neuquen commented Oct 4, 2016

@vdh
Copy link
Collaborator

vdh commented Oct 5, 2016

@neuquen Do you have any sample code to test this against?

@vdh
Copy link
Collaborator

vdh commented Oct 5, 2016

I'm no expert at d3 or d3-tip, but I took a look at one of the existing example files and the d3-tip source. tip.attr does accept a function but it gets invoked immediately. It doesn't get invoked dynamically over datum in the same way tip.html does when tip.show is called.

A workaround would be to embed the dynamic class generation inside a wrapper element in tip.html.

Is it normal for a d3 plugin to dynamically calculate attr in this way? I'm not sure if it's a bug or if it's just expected behaviour.

@neuquen
Copy link
Author

neuquen commented Oct 5, 2016

I actually am already doing your suggestion as my workaround:

tip.html(function(d) {
  if (d.type === "this") return "<div class='class1'>this</div>";
  if (d.type === "that") return "<div class='class2'>that</div>";
}

However, as you can see above, now I need to wrap the content each within a container DIV inside of the d3-tip DIV, and I have more complex html structure below that as well, which is getting unruly, and making CSS calls more complex.

D3 does allow passing in a function to selection.attr. See the D3 docs for more info: https://github.com/d3/d3-selection/blob/master/README.md#selection_attr

It outlines the behavior

...if the value is a function, the function is evaluated for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element. The function’s return value is then used to set each element’s attribute.

@vdh
Copy link
Collaborator

vdh commented Oct 6, 2016

@neuquen But how is tip.attr expected to handle this? The datum (d) is only available via the context of the selection, which is just the isolated tip node when tip.attr is called. tip.html gets its arguments via tip.show, after data is available via some sort of .on('mouseover', tip.show) or tip.show(d) call. (tip.html also doesn't need a key like class so it's easier to write this sort of custom behaviour inside tip.show for it.)

If you are already using tip.show in that sort of fashion, you could always do something like:

rect.on('mouseover', function(d) {
  tip.attr('class', d.value ? 'class1' : 'class2').show(d)
})

If this is a bug or misuse of d3, or not how a d3 plugin should be written, I need some sample code of how exactly a plugin can correctly make a dynamic evaluation on arbitrary attributes like that.

If it isn't a bug or plugin design problem, it should be a separate feature request issue or PR for custom dynamic handling of the class attribute, since tip.attr technically does support functions.

@neuquen
Copy link
Author

neuquen commented Oct 6, 2016

Thanks @vdh. You're exactly right. After a closer look I realize now that when instantiating the tip, there is no reason that the .attr should have access to the selection. What is throwing me off though is the fact that tip.direction and tip.offset do have access. When I define the tip I am doing the following:

vis.tip = d3.tip()
  .attr("class", "class1")
  .direction(function(d) {
    return (d.type === "this") ? 'e' : 'w';
  })
  .offset(function(d) {
    return (d.type === "this") ? [0, 15] : [0, -15];
  });

I haven't looked closely enough at the d3-tip code to know if this is possible, but it looks like html, offset, and direction are all getting called within tip.show(). Could the same behavior be applied to attr? I'm aware now that it isn't expected behavior and may not be reasonable, so I'm OK using your suggestion above or my current implementation.

@vdh
Copy link
Collaborator

vdh commented Oct 7, 2016

Yes, html, offset and direction are dynamically evaluated in tip.show.

It may be a bit trickier to do the same for attr since it has two arguments (so potentially any attribute could be targeted), but it could be possible. Either through some cached function trickery or by writing something just for the class attribute. If you like you could make a new feature request issue/PR for something like that.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants