How to draw a part of a line in SVG?

Would anyone have an idea whether/how it is possible to draw along a line between points while keeping a certain distance from the end points in SVG?

In case the line is horizontal (e.g. from (40,40) to (100,40)), you could easily draw a line that keeps a distance of about 20 to the points as follows

<line x1="40" y1="40" x2="100" y2="40" desc="directional line" />
<line x1="60" y1="40" x2="80" y2="40" desc="actual part of line" />

For diagonal lines, however, it is a bit harder. To draw an (easy) diagonal line from (40,40) to (100,100), you would need cos(pi/4) = sin(pi/4) = sqrt(2) to scale the distance you want to stay away from the end point (in this case: 20*sqrt(2) = 14.1)

<line x1="40" y1="40" x2="100" y2="100" desc="directional line" />
<line x1="54.1" y1="54.1" x2="85.9" y2="85.9" desc="actual part of line" />

A demonstration of this code can be found in this fiddle

It thus seems possible to do it, when calculating

  1. The angle between the directional line with a horizontal line
  2. The sine and cosine of the angle
  3. The end points of the actual part of the line you want to draw

Is this the only way or is SVG capable of specifying parts of lines or are there smarter, more convenient ways of doing this?

Upvotes: 1

Views: 1276

Answers (2)

Ruskin
Ruskin

Reputation: 6171

Once hacky way to do it is cheating with a circular pattern scaled to the size of your line. Not perfect but depends on your use case:

<svg width="200" height="200" viewbox="0 0 200 200">
  <defs>
    <pattern id="patt" width="1" height="1" patternContentUnits="objectBoundingBox">
      <rect x="0" y="0" width="1" height="1" fill="cyan" />
      <circle cx=".5" cy=".5" r=".4" fill="blue" />
    </pattern>
  </defs>
  
  <g id="hand-drawn">
    <line x1="40" y1="40" x2="100" y2="100" stroke="red" stroke-width="2" />
    <line x1="54.1" y1="54.1" x2="85.9" y2="85.9" stroke="green" stroke-width="2" />
  </g>
  
  <g id="circle-pattern">
    <line x1="80" y1="60" x2="200" y2="100" stroke="url(#patt)" stroke-width="2" />
    <line x1="150" y1="10" x2="100" y2="120" stroke="url(#patt)" stroke-width="2" />
    <line x1="0" y1="100" x2="150" y2="100" stroke="url(#patt)" stroke-width="2" />
    <line x1="0" y1="100" x2="150" y2="101" stroke="url(#patt)" stroke-width="2" />
  <g>
</svg>

Of course this only gives you a way to show a line that is a specific % from the ends, not an exact pixel value. Hope this gives you some ideas.

Note this is a bit buggy - it does not work for horizontal or vertical lines ... you would have to render them as rectangles or paths and use fill instead of stroke.

Upvotes: 0

Holger Will
Holger Will

Reputation: 7536

I'm not sure if this is smart or convinient, but one way to do this without script is the following.

You can use a rect as marker (marker-start and marker-end) and with the markerWidth and markerHeight property in combination with the stroke-width of the line, you can controll the size of the marker.

markerWidth * stroke-width = real width

<svg width="220" height="220">
  <marker id="m1" orient="auto" viewBox="0 0 20 6" markerWidth="10" markerHeight="3" refX="20" refY="3">
    <rect x="0" y="0" width="20" height="6" fill="red" opacity="0.5" />
  </marker>
  <marker id="m2" orient="auto" viewBox="0 0 20 6" markerWidth="10" markerHeight="3" refX="0" refY="3">
    <rect x="0" y="0" width="20" height="6" fill="red" opacity="0.5" />
  </marker>

  <line id="l2" x1="20" y1="20" x2="200" y2="80" marker-end="url(#m1)" marker-start="url(#m2)" stroke="black" stroke-width="2" />

</svg>

now imagine we use a white rect, then the marker will overlap the line with a fixed width, and we would have a fixed distance to the endpoints.

But this is not really what we want. To really "cut" the line by the marker, you can then use a mask. So draw your line as a mask, with white stroke, but with black markers.

apply this mask to your line (without marker)... there you go: a line with a visible stroke that has a fixed distance to the endpoints.

pros: no javascript involved

cons: you have to draw your line twice

function redraw() {
  var x1 = Math.random() * 200
  var y1 = Math.random() * 200
  var x2 = Math.random() * 200
  var y2 = Math.random() * 200

  l1.setAttribute("x1", x1)
  l1.setAttribute("y1", y1)
  l1.setAttribute("x2", x2)
  l1.setAttribute("y2", y2)

  l2.setAttribute("x1", x1)
  l2.setAttribute("y1", y1)
  l2.setAttribute("x2", x2)
  l2.setAttribute("y2", y2)

  c1.setAttribute("cx", x1)
  c1.setAttribute("cy", y1)
  c2.setAttribute("cx", x2)
  c2.setAttribute("cy", y2)
}
line {
  stroke-width: 2px
}
<svg width="220" height="220">
  <marker id="m1" orient="auto" viewBox="0 0 20 6" markerWidth="10" markerHeight="3" refX="20" refY="3">
    <rect x="0" y="0" width="20" height="6" fill="black" />
  </marker>
  <marker id="m2" orient="auto" viewBox="0 0 20 6" markerWidth="10" markerHeight="3" refX="0" refY="3">
    <rect x="0" y="0" width="20" height="6" fill="black" />
  </marker>
  <mask id="mask">
    <line id="l2" x1="20" y1="20" x2="200" y2="80" marker-end="url(#m1)" marker-start="url(#m2)" stroke="white" />
  </mask>
  <circle id="c1" cx="200" cy="80" r="5" fill="blue" />
  <circle id="c2" cx="20" cy="20" r="5" fill="blue" />
  <line id="l1" x1="20" y1="20" x2="200" y2="80" mask="url(#mask)" stroke="black" />
</svg>

<button onclick="redraw()">redraw</button>

Upvotes: 1

Related Questions