0
votes

I need to display an arrow to show the evolution of some metric, for this I created an algorithm that draws the line and add use a marker to have the shape of an arrow.

I've tried to change the refX so the marker would finish at the end but since the line is thick it displays the rest of the line after the marker. I can reduce change this refX in a way that I don't see the rest of the line but I want to see the whole marker within the limits of the svg.

I could also reduce the length of the line so the line + the marker takes the whole place but then I cannot find the correct amount of pixels I need to remove.

The line is rendered dynamically which means it won't always be in that way. In a svg(x,y) from (0,0) to (100,100) it can go from (100,0) to (100,100).

Current code displays the arrow overflowing the svg, I would like to have everything in it.

<svg class="svg" style="height: 190px; width: 328px; border:1px solid blue;">
        <marker class="red_arrow_normal" markerUnits="strokeWidth" viewBox="0 0 10 10" refY="6" orient="auto"
            markerHeight="10" markerWidth="10" id="red-3" refX="10">
            <path d="M2,2 L10,6 L2,10 L6,6 L2,2" style="fill: red;"></path>
        </marker>
        <line x1="0" y1="0" x2="328" y2="190" stroke="red" stroke-width="10" marker-end="url(#red-3)"></line>
    </svg>
2
Change the value of the refX attribute to a smaller value for example:refX="9" However since your line has the same size as the svg canvas diagonal the tip of the arrow will fall slightly outside the svg canvas. Y suppose you need a bigger canvas or to do svg{overflow:visible;} - enxaneta
Yes, but what is slightly bigger? How much width and height, bear in mind that the line can change its direction - Jérôme B
You'd need to use trigonometry to shorten the line by a some fixed length amount - Robert Longson
Make sense but then I need to know the exact length of the marker and it doesn't look like the width or the length of it is equal to 10px, what am I missing? - Jérôme B
You could copy the path into the main document and call getBBox to find its dimensions. - Robert Longson

2 Answers

0
votes

You can edit it online in codepen here. I have modified the width and height.

<svg class="svg" style="height: 190px; width: 328px; border:1px solid blue;">
        <marker class="red_arrow_normal" markerUnits="strokeWidth" viewBox="0 0 10 10" refY="6" orient="auto"
            markerHeight="10" markerWidth="10" id="red-3" refX="9">
            <path d="M2,2 L10,6 L2,10 L6,6 L2,2" style="fill: red;"></path>
        </marker>
        <line x1="0" y1="0" x2="320" y2="180" stroke="red" stroke-width="10" marker-end="url(#red-3)"></line>
    </svg>
0
votes

There doesn't look to be a clean solution to this problem that doesn't involve some javascript, but you can solve it with some math:

function drawArrow(x1, y1, x2, y2) {
  const svg = document.getElementById("my-svg")
  
  let dx = x2 - x1;
  let dy = y2 - y1;
  const length = Math.sqrt(dx * dx + dy * dy);
  if (length > 0)
  {
      dx /= length;
      dy /= length;
  }
  const SHORTEN = 6
  const x3 = x1 + dx * (length - SHORTEN)
  const y3 = y1 + dy * (length - SHORTEN)
  
  svg.innerHTML += `<line x1=${x1} y1=${y1} x2=${x3} y2=${y3} stroke="red" stroke-width="5" marker-end="url(#arrow)"></line>`
}

You will first have to make sure your arrow head extends past the end of the line, so that the square end of the line isn't showing. Then you will have to adjust the SHORTEN variable to offset the extra length of the arrow head.

My fiddle: https://jsfiddle.net/h4pgoaLw/