infodev
infodev

Reputation: 5235

Insert svg path based on another path coordination with a transform attribute

I would like to insert a path on another path ( with progression param ) which I have already applied a transform attribute.

The problem is that when I get point position from getPointAtLength(//h2 path//) the x and y params are not the same as the position of h2.

So they get positioned out of the box.

let paths = d3.select('svg').select("#h2").selectAll('path.train').data([1,2]);
let branch=d3.select('svg').select("#h2").select('path')
paths.enter().append('path')
          .attr('d', d3.symbol().type(d3.symbolTriangle))
          .attr('class', 'train')
          .attr('fill','black')
          .attr('transform',(d,i)=>  {
          let point = branch.node()
              .getPointAtLength(30)
           return `translate(${point.x},${point.y}`
          })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.0/d3.min.js"></script>
<svg viewBox="0 0 388.71 412.14">
    <g data-id="branch" id="h2" transform="translate(-885 -562) ">
        <g class="branch-label" id="h2select" transform="translate(-240 -340)"></g>
        <rect fill="rgba(255,255,255,1)" height="11" id="h2" width="12"></rect>
        <path d="M1093.763,1595.658 v -82.417 c 0,0 5,-14.987 -18.452,-16.644 -23.452,-1.657 -40.9,2.386 -54.093,-11.537 -13.193,-13.923 -132.873,-159.193 -132.873,-159.193 0,0 -6.456,-10.249 -24.986,-14.661 -18.53,-4.412 -11.029,-16.736 -2.10392,-28.6309 2.68431,-3.5775 12.32475,-15.4715 21.44325,-26.363" data-name="h2" fill="none" id="h2-3" stroke="#efcf2f" stroke-linecap="round" stroke-width="2" transform="translate(208.67 -656.38)" style="opacity: 1;"></path>
    </g>
</svg>

What I want is how to position the triangle at 30% of the path without deleting `transform attributes on svg ?

enter image description here

Upvotes: 0

Views: 180

Answers (1)

Alex L
Alex L

Reputation: 4241

We can "wrap" the path in a g element and give the g element the transform the path had and remove it from the path?

Then your approach should work?

var child = document.getElementById('h2-3');
var parent = child.parentNode;
var i = Array.prototype.indexOf.call(parent.children, child);
console.log(i);
child = parent.removeChild(child);

var g = document.createElementNS('http://www.w3.org/2000/svg','g');
g.setAttribute('transform', child.getAttribute('transform'));
child.removeAttribute('transform');
g.appendChild(child);

if (i < parent.childNodes.length) {
  parent.insertBefore(g, parent.childNodes[i]);
} else {
  parent.appendChild(g);
}

const len = child.getTotalLength();
//console.log(len)
const point = child.getPointAtLength(0.99*len);
var circle = document.createElementNS('http://www.w3.org/2000/svg','circle')
circle.setAttribute('cx', point.x);
circle.setAttribute('cy', point.y);
circle.setAttribute('r', 10);
circle.setAttribute('fill', 'black');
g.appendChild(circle);

var myInterval;
var currProgress = 0.995;
var direction = -1;
var count = 0;

myInterval = setInterval(myAnimate, 50);

function myAnimate() {
  if (currProgress <= 0.0 || currProgress >= 1.0) {
    direction *= -1;
  }
  //console.log(currProgress);
  const myPoint = child.getPointAtLength(currProgress * len);
  circle.setAttribute("cx", myPoint.x);
  circle.setAttribute("cy", myPoint.y);
  currProgress += direction * 0.005;

  if (count >= 5) {
    clearInterval(myInterval);
  }
}


/*
let paths = d3.select('svg').select("#h2").selectAll('path.train').data([1,2]);
let branch = d3.select('svg').select("#h2").select('path')
paths.enter().append('path')
          .attr('d', d3.symbol().type(d3.symbolTriangle))
          .attr('class', 'train')
          .attr('fill','black')
          .attr('transform',(d,i)=>  {
          let point = branch.node()
              .getPointAtLength(30)
           return `translate(${point.x},${point.y}`
          })
 */
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.0/d3.min.js"></script>
<svg viewBox="0 0 450 500">
    <g data-id="branch" id="h2" transform="translate(-885 -562) ">
        <g class="branch-label" id="h2select" transform="translate(-240 -340)"></g>
        <rect fill="rgba(255,255,255,1)" height="11" id="h2" width="12"></rect>
        <path d="M1093.763,1595.658 v -82.417 c 0,0 5,-14.987 -18.452,-16.644 -23.452,-1.657 -40.9,2.386 -54.093,-11.537 -13.193,-13.923 -132.873,-159.193 -132.873,-159.193 0,0 -6.456,-10.249 -24.986,-14.661 -18.53,-4.412 -11.029,-16.736 -2.10392,-28.6309 2.68431,-3.5775 12.32475,-15.4715 21.44325,-26.363" data-name="h2" fill="none" id="h2-3" stroke="#efcf2f" stroke-linecap="round" stroke-width="2" transform="translate(208.67 -656.38)" style="opacity: 1;"></path>
    </g>
</svg>

We can do this with the following JS:

var child = document.getElementById('h2-3');
var parent = child.parentNode;
var i = Array.prototype.indexOf.call(parent.children, child);
console.log(i);
child = parent.removeChild(child);

var g = document.createElementNS('http://www.w3.org/2000/svg','g');
g.setAttribute('transform', child.getAttribute('transform'));
child.removeAttribute('transform');
g.appendChild(child);

if (i < parent.childNodes.length) {
  parent.insertBefore(g, parent.childNodes[i]);
} else {
  parent.appendChild(g);
}

With this you "wrap" the path in a g element and give the g element the transform the path had and remove it from the path.

Resulting DOM is:

enter image description here

And then we can place a circle at 30% of the path length with the following JS:

const len = child.getTotalLength();
console.log(len)
const point = child.getPointAtLength(0.3*len);
var circle = document.createElementNS('http://www.w3.org/2000/svg','circle')
circle.setAttribute('cx', point.x);
circle.setAttribute('cy', point.y);
circle.setAttribute('r', 10);
circle.setAttribute('fill', 'black');
g.appendChild(circle);

Then we get this as the output (you can play with child.getPointAtLength(0.3*len) with other values like 0.6*len to see the circle at other positions):

enter image description here

UPDATE

Actually, I noticed some of your path is cut off as it is outside of the svg viewbox so I modified the viewbox like so:

<svg viewBox="0 0 450 500">

Then I also added some animation code, because, why not!

var myInterval;
var currProgress = 0.995;
var direction = -1;
var count = 0;

myInterval = setInterval(myAnimate, 50);

function myAnimate() {
  if (currProgress <= 0.0 || currProgress >= 1.0) {
    direction *= -1;
  }
  //console.log(currProgress);
  const myPoint = child.getPointAtLength(currProgress * len);
  circle.setAttribute("cx", myPoint.x);
  circle.setAttribute("cy", myPoint.y);
  currProgress += direction * 0.005;

  if (count >= 5) {
    clearInterval(myInterval);
  }
}

Result is:

enter image description here

Upvotes: 1

Related Questions