Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML elements following each other along SVG path with javascript

I am trying get a few html elements to follow each other along a SVG path. I would like them to stay the same distance apart as they go around the path. I would also like the SVG image to scale to the container that holds it.

I have created a codepen that demonstrates what I have so far: http://codepen.io/mikes000/pen/GIJab

The problem I am having is that when the elements move along the X axis they seem to get further apart than they do on the Y axis.

Is there a way to make them stay the same distance as they travel along the line?

Thanks!

Update**

After some further fiddling I have discovered that the distance variation seems to be caused by the aspect ratio of the SVG viewbox being increased for X greater than it is for Y. When it is stretched along the X axis 1px down the line may become 3px on the screen.

The position of the red squares is being set by moving them in front and behind by half the width of the black box. When traveling along the line if the viewbox aspect ratio is changed the distance between each point on the line increase or decreases based off of this.

I have tried creating a similar SVG with the exact viewbox of the size of the container div and the red dots are exactly on the ends of the black box all the way down the line. This doesn't solve problem because I would like the SVG with the line to scale to any size container it is placed inside.

I think if there is a way to calculate how many pixels the size of the black box is in relation to how many pixels down the line it covers the red dots would line up exactly.

Any ideas how to accomplish this or any ideas on a better way to approach this problem?

like image 539
Simalam Avatar asked Jun 13 '14 16:06

Simalam


1 Answers

Take a look at http://jsfiddle.net/4LzK4/

var svg = d3.select("#line").append("svg:svg").attr("width", "100%").attr("height", "100%");
var data = d3.range(50).map(function(){return Math.random()*10})
var x = d3.scale.linear().domain([0, 10]).range([0, 700]);
var y = d3.scale.linear().domain([0, 10]).range([10, 290]);
var line = d3.svg.line()
  .interpolate("cardinal")
  .x(function(d,i) {return x(i);})
  .y(function(d) {return y(d);})

var path = svg.append("svg:path").attr("d", line(data));
var circle = 
    svg.append("circle")
      .attr("cx", 100)
      .attr("cy", 350)
      .attr("r", 3)
      .attr("fill", "red");

var circleBehind = 
    svg.append("circle")
      .attr("cx", 50)
      .attr("cy", 300)
      .attr("r", 3)
      .attr("fill", "blue");

var circleAhead = 
    svg.append("circle")
      .attr("cx", 125)
      .attr("cy", 375)
      .attr("r", 3)
      .attr("fill", "green");

var pathEl = path.node();
var pathLength = pathEl.getTotalLength();
var BBox = pathEl.getBBox();
var scale = pathLength/BBox.width;
var offsetLeft = document.getElementById("line").offsetLeft;
var randomizeButton = d3.select("button");

svg.on("mousemove", function() {
  var x = d3.event.pageX - offsetLeft; 
  var beginning = x, end = pathLength, target;
  while (true) {
    target = Math.floor((beginning + end) / 2);
    pos = pathEl.getPointAtLength(target);
    if ((target === end || target === beginning) && pos.x !== x) {
        break;
    }
    if (pos.x > x)      end = target;
    else if (pos.x < x) beginning = target;
    else                break; //position found
  }
  circle
    .attr("opacity", 1)
    .attr("cx", x)
    .attr("cy", pos.y);

  posBehind = pathEl.getPointAtLength(target-10); 
  circleBehind
    .attr("opacity", 1)
    .attr("cx", posBehind.x)
    .attr("cy", posBehind.y);

  posAhead = pathEl.getPointAtLength(target+10); 
  circleAhead
    .attr("opacity", 1)
    .attr("cx", posAhead.x)
    .attr("cy", posAhead.y);

});

randomizeButton.on("click", function(){
  data = d3.range(50).map(function(){return Math.random()*10});
  circle.attr("opacity", 0)                                     
  path
    .transition()
    .duration(300)
    .attr("d", line(data));
});

Instead of calculating the positions of the circles behind and ahead on your own, use getPointAtLength relative to the centre of object that has to stay in the middle.

Inspired by: http://bl.ocks.org/duopixel/3824661

like image 142
aa333 Avatar answered Sep 27 '22 18:09

aa333