insert versus append and relative location of new elements

Hi All,

I am making a scatter chart with elements from several tutorials here as well as blocks.

I am working through as many functions as I can and this time .insert is confusing me a little.
From the documentation it should work like append but I am not able to see it on the screen if I use it.

I added a comment with .insert('rect', 'text') that I thought would work.
I think it is probably rendering but not where I want.

This also leads me to a second question, I wanted to append them all to a group and tried to find the new relative location.
Is there some way in d3 where I can use the absolute coordinates instead?
I thought relative would be 0, 0 but I am somehow not being able to place the texts and rects right this way so I am using svg.append() instead.

Thank you very much in advance! I really appreciate the helpful community here.

Hi (again!) Oliver,

Yup, sure thing.

  1. I wouldn’t worry about insert too much. It’s a pretty uncommon d3 function, and for what you’ve been building, its not needed. It can come in handy in very specific scenarios, but generally, append is all you really want.
  2. You can append items to a group by appending them to a “g” tag. So something like this:
svg.append('g').attr('class', 'labels').selectAll('.label').data(myData).join('text').attr('class', 'label')

That would append a bunch of SVG text objects to a group tag with class “labels”. So this way if you move the position of the group, it would move all the objects within it.

Hope that’s helpful. If you give me a little more about what you’re trying to build, I can whip up an example for you.

1 Like

Hi Paul, thanks that is very helpful.
I will try 2. this seems promising.

For 1, I was thinking of using insert (after searching the docs) because I want the rectangle to render behind the text, but I have to call it after the text is rendered to get the bounding box.
Hope this makes sense?
i.e. now it renders over the text, if the opacity weren’t 0.5 we wouldn’t see the text afterwards.

Thank you so much for your help!

Yup, makes sense. Typically, you might do something like this:

const labels = g.selectAll('.label')
    .join('g')
    .attr('class', 'label')
    .attr('transform', d => `translate(${d.x}, ${d.y})`) // assuming you have coordinates

labels.append('rect')
  .attr('width', 100)
  .attr('height', 100)

labels.append('text')
  .text('Hello world!') 
  .style('fill', 'white') // Can't read black text against a black rectangle, which is the default fill

So here I’m creating a group tag (‘g’) and appending a rectangle object to that, and then a text object. Both the rectangle and the text object are appended to the group tag, and since the text object is 2nd object appended, it renders on top of the rectangle.

SVG is a bit tricky in that you can’t render any object within another object. So, this doesn’t work:

svg.append('rect')
    .attr('width', 100)
    .attr('height', 100)
    .append('text')
    .text('hello world!')

That wouldn’t render the text, because you can’t put a text within a rectangle. It’s a bit annoying, especially for labels, or if you want to, say, put an image within something.

There are a couple of exceptions to this rule, like with textPaths, tspans, and foreignObjects. I’d say that definitively gets to the more expert (at least 200 level!) SVG usage, so if you’re just starting to learn D3, I’d wait on getting to those concepts.

I wrote this notebook a while ago which converts a D3 chart to a SVG DOM Tree. I think this can be helpful for you in your study. When working with D3, it’s important to think about what the structure of the visualization in terms of the DOM. Take a look at that notebook and see how the visualization itself is structured. (It might surprise you how some of them are in fact very simple!)

2 Likes

Hi Paul,

thanks a lot for the reference and link, I’ll study that notebook of yours.
So, I guess, in this one, I would change the height and width of the rectangle after it was rendered? The bounding box then would include the original rect though, so I guess, I’d start with it being small?

edit: an additional question, that still lingers now.
How are the coordinates from your example related?
Where do labels.append(‘text’) objects render relative to the screen or your group object g?

edit2: I was able to use insert, instead of selecting like ‘text’ I made a custom class for the first child. I saw an example where they used this.getElementFirstChild and that gave me the idea. :smile:

as an additional question though if I may, somewhat unrelated to this topic but I don’t want to spam the forum, it’s very jittery because of the sensitive mouse movements, I guess I should use a transition to slow it down? or is there some other better more professional way?

You could try to do the bounding box solution to adjust the size of the text. Often, folks will either just assume that the text will fit in a given size, or use a HTML Div for the tooltip that handles word-wrapping by default. The latter would be my suggestion It’s often better to use HTML vs SVG when you want some of the nice default features of HTML, like word-wrapping, divs auto-sizing based on what it contains, etc.

To do that in Observable, you’ll want to instantiate the SVG a little bit differently, like this:

const div = html`<div></div>`

const svg = d3.select(div).append('svg')
    .attr('width', mySVGWidth)
    .attr('height', mySVGHeight)
    .style('position', 'absolute')
    .style('left', '0px')
    .style('top', '0px')

const tooltip = d3.select(div).append('div')
    .style('position', 'absolute')
    .style('left', '0px')
    .style('top', '0px')

// write the rest of your code, then at the end instead of doing return svg.node(), you return the div

return div

For the label approach, whenever you assign coordinates to a group tag, everything within that group tag will be given a new default location, which is the coordinates of the group tag. So in SVG, but default, everthing is located as 0,0, which is the upper-left corner of the SVG. (Known as the inverted coordinate plane.) When you add a group tag at coordinates 10, 10, all the objects in that group tag will start at 0,0.

hope that helps!

1 Like

Yup, if you add a d3.transition(), it will remove some of that jitter. You can add things like duration() or delay() as well. This world becomes really complicated real fast :slightly_smiling_face: interaction design is hard!

1 Like