2

Scratching my head on this one.

We have a list of text on the left side of the page. Each item in the list has a data-id attribute that makes it easy to match up corresponding schools in our SVG map. This SVG map is a map of the US, and has school locations fed in from a CSV excel sheet and stored in "schools" for access.

                circles.selectAll("circles")
            .data(schools)
            .enter().append("svg:a")
            .attr("xlink:href", function(d) { return d.url; })
            .append("svg:circle")
            .attr("school", function(d, i) { return d.name; })
            .attr("id", function(d, i) { return d.id; })
            .attr("cx", function(d,i) { return d.longitude; })
            .attr("cy", function(d,i) { return d.latitude; })
            .attr("r", function(d,i) { return 6; })
            .attr("i", function(d,i) { return i; })
            .attr("class", "icon")

So when a user hovers over this list of text I previously mentioned, I use this function:

                mapSearch = function(id) {
                  d3.selectAll("circle")
                    .filter(function(d) {
                      if (d.id == id) {
                          return show_bubble_2(d);
                       }
                    })
               }

Which calls:

show_bubble_2 = function(school_data) {
                var school_info = school_data,
                    latitude = school_info.latitude,
                    longitude = school_info.longitude;


                        bubble.css({
                            "left": (longitude - 75)+"px",
                            "top": (latitude - 67)+"px"
                        });

                bubble.html("<h1>" + school_info.name + "</h1>" + "<p>" + school_info.city + ", " + school_info.state + "</p>")
                                        .attr("class", function(d) { return school_info.letter; });
                bubble.addClass("active");
            }

This works unless we start resizing the map to fit different screen sizes, or unless we do special zoom functions on the map. Then the bubbles closer to the west coast are where they're supposed to be but the ones on the east coast are way off. In short, it's a complete nightmare and not at all scalable.

My question: How do I just append this DIV to the corresponding circle ID instead of using an absolute positioned DIV so that no matter what size the map is, the bubble will always pop up right on top of that circle.

I have tried appending inside the if (d.id == id) { } but it always returns errors and so far I haven't figured it out. I'll keep trying something along those lines because I feel like that's the way to do it. If you have a better solution or could point me in the right direction, I would really appreciate it.

Thanks, and have a good one!

Christophe
  • 146
  • 6
  • 14

2 Answers2

1

You can find the position of the circle even if there is a transform applied by using Element.getBoundingClientRect.

You could use your filtered selection, get the .node() and find its bounding rect. Then by adjusting for the scroll position, you can find the values of top and left to give to your bubble.

This means that the position of the bubble is based on the actual position at which the circle appears on the page, rather than being based on its data, which would require you to take the transforms into account. Try something like this:

mapSearch = function(id) {

  // get the selection for the single element that matches id
  var c = d3.selectAll("circle")
    .filter(function(d) {
      if (d.id == id) {
        return show_bubble_2(d);
      }
    });

  // get the bounding rect for that selection's node (the actual circle element)
  var bcr = c.node().getBoundingClientRect();

  // calculate the top/left based on bcr and scroll position
  var bubbleTop = bcr.top + document.body.scrollTop + 'px',
      bubbleLeft = bcr.left + document.body.scrollLeft + 'px';

  // set the top and left positions
  bubble.css({
    'top': bubbleTop,
    'left': bubbleLeft
  });

}

Of course, if you are zooming or panning and want the bubble to remain on the circle, you will need to recalculate these values inside your zoom and pan functions, but the process would be the same.

HERE is a demo using circles that are randomly placed within a g element that has a translation and scale applied. Click on an item in the list to place the bubble on the corresponding circle.

jshanley
  • 9,048
  • 2
  • 37
  • 44
  • This works beautifully. The only odd thing I encountered was when zooming in the map, the bubbles are a slightly too far to the left. Easy fix, though. Many thanks! – Christophe Jul 24 '14 at 02:31
0

A <div> is HTML. A <circle> is SVG. You can't (easily) put HTML elements inside SVG. You'd have to use <foreignobject> elements to do that. (See this question for details.) Alternatively, you could use native SVG elements such as <tspan> instead of <div>

Community
  • 1
  • 1
Stephen Thomas
  • 13,843
  • 2
  • 32
  • 53
  • 1
    You can also use css to make divs look like circles or use the `.text()` method on the circles to add text. – ckersch Jul 22 '14 at 16:17
  • Thanks for your answer, Stephen. Good to know you can't really add a div to SVG. I'm not set on using a div, though. I'm a lot more worried about making the bubbles pop up where they are supposed to no matter the map size. If that means scrapping divs then that's fine – Christophe Jul 22 '14 at 23:59