Question

Situation:

I am working on a google map with a d3 overlay. It is largely based off this example from Mike Bostock: http://bl.ocks.org/mbostock/899711

I'm running it out of a Rails 4 app, and loading in the d3.js library from a CDN. So have access to d3.js and jQuery libraries and would prefer to avoid adding more libraries.

Task:

The data points(represented by svg:rect elements) sometimes obscure the place names on the google map. I want it so that a user can pass their cursor over those datapoints and that they will momentarily pop out of the way.

I should note I'm trying to have the points reposition randomly, so can't really go the hover CSS route.

So...

  • Put an event listener on the SVG (or rect depending on what element should actually be manipulated)

  • Write a callback function that repositions (offsets? animates? transitions?) the element that triggered the event

  • Waits for a setTimeout (or uses the d3 .each("end",function())) and then returns the element to it's prior position

Simple right?

Attempts:

So, attempted with d3 and then tried with jQuery. Bailed on the jQuery, since it doesn't seem to handle SVG (although I may have to resort to using the jQuery-SVG library if d3.js doesn't come through with the goods)

With the d3 attempt, the event handler assignment is fine. I have assigned the handler to the svg parent element, not the rect child element.

.on('mouseover', scatter)

This produces the expected behaviour of the event triggering and the scatter function being called when I mouse over.

However, I haven't been able to get any sensible articulation of the behaviour I am looking for.

I've tried changing the x,y values. I've tried changing the top and left values. The values seem to change on the object but nothing moves. I've tried those with straight d3 style selection and also with d3 style transitions.

I've tried the same changes on the rect child element. This moves the rect element but does so without bring the SVG along with it, so it doesn't display.

Relevant Code:

Appending SVGs to every data point, setting the correct x and y for the SVG and adding an event listener to each.

var marker = layer.selectAll("svg")
      .data(data)
      .each(transformMarker)
      .enter().append("svg:svg")
      .each(transformMarker)
      .on('mouseover', scatter);

Separately, I style the SVG parent elements to contain a rect child element, and style the rects, so something actually displays on the screen.

The transformMarker function, which you might need to make sense of how the marker is positioned.

    function transformMarker(d) {
      d = new google.maps.LatLng(J.lat(d), J.lon(d));
      d = projection.fromLatLngToDivPixel(d);
      return d3.select(this)
          .style("left", (d.x) + "px")
          .style("top", (d.y) + "px");

The scatter function (with a boatload of console.logs so you can see what is happening)

function scatter(d){
      console.log("the event has fired")
      d = new google.maps.LatLng(J.lat(d), J.lon(d));
      d = projection.fromLatLngToDivPixel(d);
      console.log(d.x)
      console.log((d.x + 15) + "px")
      console.log(this)
      d3.select(this)
        .attr("x", (d.x + 15))
        .attr("y", (d.y + 15))
      console.log(this)

And what the console.logs pump out

the event has fired
602.0032221842557
617.0032221842557px

<svg style=​"left:​ 602.0032221842557px;​ top:​ 230.00228557549417px;​" 
    x="617.0032221842557" y=​"245.00228557549417">​
    <rect height=​"7" width=​"7" fill=​"#138770" stroke=​"#0f0f02" stroke-width=​"0.5">​</rect>​
</svg>​

<svg style=​"left:​ 602.0032221842557px;​ top:​ 230.00228557549417px;​"
    x="617.0032221842557" y=​"245.00228557549417">​
    <rect height=​"7" width=​"7" fill=​"#138770" stroke=​"#0f0f02" stroke-width=​"0.5">​</rect>​
</svg>​

And the CSS for the SVG, since that could be throwing spanners in the works

.overlay, .overlay svg {
  position: absolute;
}

.wrapper, .overlay svg {
  /*border: 1px solid black;*/
  width: 7px;
  height: 7px;

  transform: translateZ(0px);
  -webkit-transform: translateZ(0px);
  -moz-transform: translateZ(0px);
}

I have a few days away from civilisation, so was hoping to lay this problem out there and come back with a fresh approach and some suggestions from Stack Overflow and the d3 google groups.

Please let me know if you have any other questions or need more information.

Était-ce utile?

La solution

To recap:

Each data point rectangle is within its own, separate <svg> element. Each svg element is positioned using absolute positioning (left and top styles), in the transformMarker event.

Clearly, if you want to move the svg graphics associated with a datapoint, you will need to change the <svg> element's positioning. You're trying to do it with x and y attributes, but that wasn't how the SVG was positioned in the first place. Those attributes are only relevant if the <svg> element is embedded within another SVG. Yours are separate elements absolutely positioned on the page. So you need to change the absolute positioning style values:

function scatter(d){
      d = new google.maps.LatLng(J.lat(d), J.lon(d));
      d = projection.fromLatLngToDivPixel(d);
      d3.select(this)
        .style("left", (d.x + 15) + "px")
        .style("top", (d.y + 15) + "px")
}

Also, I'm not sure why you're calling transformMarker twice in your initialization; you only need it once, but make sure the version you keep is the one that is called after creating the elements:

var marker = layer.selectAll("svg")
      .data(data)
      .enter().append("svg:svg")
        .each(transformMarker)
        .on('mouseover', scatter);

Autres conseils

I'd check out this nifty random function for Sass

module Sass::Script::Functions
  def random(max = Sass::Script::Number.new(100))
    Sass::Script::Number.new(rand(max.value), max.numerator_units, max.denominator_units)
  end
end

to add the random function into your sass, check out this guide. If you want your items to reposition randomly on mousever, I would just have a class with a random position using the random function and remove/add that class to the elements. Of course, there are a few examples using the random function in the links I provided.

Of course it's pretty simple, just add the code above to your config/initializers in sass.rb. Here is an example rails app using it.

The one problem with it, is that it if you set it in a class, it will only generate the random number once. On refresh and reload it won't generate a new random top/left etc for the css it generated. I'm not sure if there is a work around for that, or if this will actually help. But it was interesting so I thought I'd post it anyway.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top