I receive a list of points from my layout engine (ELK) which I would like to convert to a SVG path.
I've got the following:
A start point
An end point
A list of bend points that "must be interpreted as control points for a piecewise cubic spline"
When I receive exactly two bend points, I am able to convert this to a cubic Bezier curve with two control points in SVG:
<svg width="400" height="100">
<g stroke="black" fill="black">
<!--Start point-->
<circle cx="10" cy="10" r="2" />
<!--Bend points-->
<circle cx="90" cy="60" r="1" />
<circle cx="210" cy="60" r="1" />
<!--End point-->
<circle cx="290" cy="10" r="2" />
</g>
<!--Resulting path-->
<path d="M 10 10 C 90 60, 210 60, 290 10" stroke="blue" fill="none" />
</svg>
But when I receive more than 2 control points, I struggle to understand what should be the resulting path. Eg with 4 control points:
<svg width="400" height="100">
<g stroke="black" fill="black">
<!--Start point-->
<circle cx="10" cy="10" r="2" />
<!--Bend points-->
<circle cx="50" cy="60" r="1" />
<circle cx="90" cy="60" r="1" />
<circle cx="210" cy="60" r="1" />
<circle cx="250" cy="60" r="1" />
<!--End point-->
<circle cx="290" cy="10" r="2" />
</g>
<!--Resulting path?-->
</svg>
So how can I convert a "piecewise cubic spline" with a variable amount control points to a SVG path?
Based on the text it sounds like you're dealing with a fairly simple "each omitted point lies exactly between the control points", which means your points should be interpreted as:
on-curve: 10,10
control1: 50, 60
control2: 90, 60
on-curve: MID-POINT OF PREVIOUS AND NEXT CONTROL POINTS
control1: 210,60
control2: 250,60
on-curve: 290, 10
Which means that each missing on-curve point is trivially computed using (previous control 2 + following control 1)/2, so in this case the missing point is (90 + 210) /2, (60 + 60) / 2 = 150, 60.
<svg width="400" height="100">
<g stroke="black" fill="black">
<!--Start point-->
<circle cx="10" cy="10" r="2" />
<!--control points-->
<circle cx="50" cy="60" r="1" />
<circle cx="90" cy="60" r="1" />
<!-- implicit point -->
<circle cx="150" cy="60" r="2" />
<!--control points-->
<circle cx="210" cy="60" r="1" />
<circle cx="250" cy="60" r="1" />
<!--End point-->
<circle cx="290" cy="10" r="2" />
</g>
<path stroke="blue" fill="none"
d="M 10 10
C 50 60, 90 60, 150 60
210 60, 250 60, 290 10"/>
</svg>
And of course in general, in pseudo-code:
# First, remove the start point from the list
start <- points.shift
# Then build the missing points, which requires running
# through the point list in reverse, so that data
# at each iteration is unaffected by previous insertions.
i <- points.length - 3
while i >= 2:
points.insert(i, (points[i-1] + points[i])/2 )
i <- i - 2
# Now we can walk through the completed point set.
moveTo(start)
for each (c1,c2,p) in points:
cubicCurveTo(c1, c2, p)
I never got a clear answer to my question from the ELK team, although they pointed me to the code they use in their vscode extension and in their Java application. So based on that, and this anwser, I ended up using this code (JavaScript). I can't say it's correct, but I managed to draw decent splines no matter the number of points received:
function getBezierPathFromPoints(points) {
const [start, ...controlPoints] = points;
const path = [`M ${ptToStr(start)}`];
// if only one point, draw a straight line
if (controlPoints.length === 1) {
path.push(`L ${ptToStr(controlPoints[0])}`);
}
// if there are groups of 3 points, draw cubic bezier curves
else if (controlPoints.length % 3 === 0) {
for (let i = 0; i < controlPoints.length; i = i + 3) {
const [c1, c2, p] = controlPoints.slice(i, i + 3);
path.push(`C ${ptToStr(c1)}, ${ptToStr(c2)}, ${ptToStr(p)}`);
}
}
// if there's an even number of points, draw quadratic curves
else if (controlPoints.length % 2 === 0) {
for (let i = 0; i < controlPoints.length; i = i + 2) {
const [c, p] = controlPoints.slice(i, i + 2);
path.push(`Q ${ptToStr(c)}, ${ptToStr(p)}`);
}
}
// else, add missing points and try again
// https://stackoverflow.com/a/72577667/1010492
else {
for (let i = controlPoints.length - 3; i >= 2; i = i - 2) {
const missingPoint = midPoint(controlPoints[i - 1], controlPoints[i]);
controlPoints.splice(i, 0, missingPoint);
}
return getBezierPathFromPoints([start, ...controlPoints]);
}
return path.join(' ');
}
function midPoint(pt1, pt2) {
return {
x: (pt2.x + pt1.x) / 2,
y: (pt2.y + pt1.y) / 2,
};
}
function ptToStr({ x, y }) {
return `${x} ${y}`;
}
Explanation: we set the start point and take the remaining points. Then:
If there's only one point left, we draw a straight line
If we have a multiple of 3 points, then draw Bezier curves
If we have an even number of points, we draw quadratic curves
Else, we add midpoints as described in this answer, then we try again (recurse)
Related
i am trying to learn how SVG filters work and came up with the following situation:
There is an input SVG like this, minimal example:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
<polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
</svg>
Now my quest is to create a filter that does not need to change the two polylines but adds color to the SVG between the two polylines.
My naive idea is:
1.) replicate the outer object and fill its inside space with some color.
2.) replicate the inner object the same way and fill it with the same color
3.) add the difference of both to the image (with a Z-position below the original polylines so they stay visible)
My first attempt looks like this:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="fillingFilter">
<use href="#inner" style="fill:green" />
<feComposite in2="sourceGraphic" operator="out" />
</filter>
</defs>
<polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
<polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
<use href="#outer" filter="url(#fillingFilter)" style="fill:green" />
</svg>
This does not work, as the added fillings are ignored, and this way the composite seems to do nothing.
From studying the SVG documentation i can't really find out how to do it.
What is the right way to achieve the result?
You don't really need a filter for this - just convert your polylines to a compound path.
Concatenate both polyline points attributes:
Prepend a M (Moveto - starting point) command and append a z command (closepath)
copy these values to a <path> <d> attribute like so:
<path d="M 50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0 z M 35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0 z" fill-rule="evenodd" />
add a fill-rule="evenodd" – this way inner shapes will be subtracted from the outer shape.
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
<polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
</svg>
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path id="outer"
d="
M 50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0z
M 35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0z"
fill-rule="evenodd"
stroke="black"
stroke-width="1"
fill="green" />
</svg>
Combine polylines via javaScript helper
You can easily automate this process by writing a simple helper method.
let svg = document.querySelector('svg');
polysToPath(svg)
function polysToPath(svg){
let polylines = document.querySelectorAll('polyline');
// create compound path
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
svg.insertBefore(path, polylines[0]);
// combinded path data
let d = '';
polylines.forEach(poly=>{
d += 'M'+poly.getAttribute('points')+'z';
//remove polyline
poly.remove();
})
path.setAttribute('d', d);
// style new path via css class or attributes
path.classList.add('polyPath');
//path.setAttribute('fill-rule', 'evenodd');
//path.setAttribute('fill', 'green');
}
svg{
width:20em
}
.polyPath{
fill-rule: evenodd;
fill:green;
}
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
<polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
</svg>
Points are for herrstrietzel.
You can shorten his code with a native Web Component, supported in all modern browsers:
<svg-merge-polylines>
<polyline points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0"/>
<polyline points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0"/>
</svg-merge-polylines>
<script>
customElements.define("svg-merge-polylines", class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // wait till innerHTML/lightDOM is parsed
let d = [...document.querySelectorAll('polyline')]
.map(p => 'M' + p.getAttribute('points') + 'z');
this.innerHTML = `<svg viewBox="0 0 100 100" style="height:180px">` +
`<path d="${d.join("")}" fill-rule="evenodd" fill="green"/>` +
`</svg>`;
});
}
})
</script>
This question already has an answer here:
Declaratively stroke a line with a composite line symbol using SVG
(1 answer)
Closed 7 months ago.
Is there a better solution to add to a path element a dashed border (lets say the border should have an offset of 2px in each direction)
I am looking for a general solution for a lot of path elements
For example my initial path element would be
<path stroke="black" fill="none" d="M10 10 L 50 10 L 50 80 L 10 80 Z"></path>
and at the moment I am creating another path element to add the border around the initial path element
<path stroke="black" stroke-dasharray="3" fill="none" d="M6 6 L 54 6 L 54 84 L 6 84 Z"></path>
<svg height="1000" width="1000">
<path stroke="black" fill="none"
d="M10 10 L 50 10 L 50 80 L 10 80 Z"></path>
<path stroke="black" stroke-dasharray="3"
fill="none" d="M6 6 L 54 6 L 54 84 L 6 84 Z"></path>
</svg>
"a general solution for a lot of path elements" is pretty vague. I'll handle simple closed paths in this answer.
This way may actually be more of an example how not to do it, but I think it shows a general problem with what you try to achieve. It uses only one place to define a path, and then re-uses it in three other places:
first, to draw the inner border,
then, to draw a much wider dashed border,
and finally, as a mask to hide that part of the dashed border that would otherwise overlap the inner one.
This has the advantage of not having to create extra paths, but the dashed border looks strange. The corners either show gaps or exta-long dashes, and in curved sections the length of the dashes are differing.
.distance {
stroke-width: 6;
}
.inner {
fill:none;
stroke: black;
stroke-width: 2;
}
.outer {
stroke: black;
stroke-width: 10;
stroke-dasharray: 4;
stroke-dashoffset: 2;
}
<svg viewBox="0 0 100 100" width="200" height="200">
<defs>
<path id="src" d="M 32,13 20,42 Q 30,90 85,90 L 92,53 Q 60,53 60,13 Z" />
<mask id="mask">
<rect width="100%" height="100%" fill="white" />
<use class="distance" href="#src" stroke="black" />
</mask>
</defs>
<use class="inner" href="#src" />
<use class="outer" href="#src" mask="url(#mask)" />
</svg>
Why is that so? The dashes are computed in relation to where the original path is, but what is shown is only the outer fringe of the whole stroke, at an offset. (or to put it the other way round, the path defining where dashes start and end is at an offset from the middle of the shown dashed line.) For concave sections, dashes get longer, and for convex sections, shorter.
The only way the dash length can be stable is when the path used to compute dashes sits in the middle of the dashes. You could change the order around and define the dashes on the inner border:
.distance {
stroke-width: 6;
}
.inner {
fill:none;
stroke: black;
stroke-width: 2;
stroke-dasharray: 4;
}
.outer {
stroke: black;
stroke-width: 10;
}
<svg viewBox="0 0 100 100" width="200" height="200">
<defs>
<path id="src" d="M 32,13 20,42 Q 30,90 85,90 L 92,53 Q 60,53 60,13 Z" />
<mask id="mask">
<rect width="100%" height="100%" fill="white" />
<use class="distance" href="#src" stroke="black" />
</mask>
</defs>
<use class="inner" href="#src" />
<use class="outer" href="#src" mask="url(#mask)" />
</svg>
..but that is as far as you get. The bottom line remains: you need to have a path where the dashed line is, not at an offset.
Use a native JavaScript Web Component <svg-outline> (you define once)
to do the work on an <svg>
<svg-outline>
<svg viewBox="0 0 100 100" height="180">
<path outline="blue" fill="none" d="M5 5 L 50 30 L 50 40 L 10 80 Z"/>
</svg>
</svg-outline>
The Web Component clones your original shapes (marked with "outline" attribute)
sets a stroke and stroke-dasharray on it
removes any existing fill
transforms clone
translates clone to account for scale(1.2)
scales clone to 1.2 size
then corrects translate
SO snippet output:
See JSFiddle: https://jsfiddle.net/WebComponents/2goahcqv/
<svg-outline>
<style> circle[outline] { stroke: blue } </style>
<svg viewBox="0 0 100 100" height="180">
<rect outline="green" x="15" y="15" width="50%" height="50%" stroke="blue" fill="teal"/>
<circle outline fill="lightcoral" cx="50" cy="50" r="10"/>
</svg>
</svg-outline>
<svg-outline>
<svg viewBox="0 0 100 100" height="180">
<path outline="blue" fill="pink" d="M15 10 L 50 30 L 50 40 L 20 70 Z"/>
</svg>
</svg-outline>
<script>
customElements.define("svg-outline", class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // make sure innerHTML is parsed
let svg = this.querySelector("svg");
svg.querySelectorAll('[outline]').forEach(original => {
let outlined = svg.appendChild(original.cloneNode(true));
original.after(outlined); // so we don't create "z-index" issues
let outline_stroke = outlined.getAttribute("outline") || false;
if (outline_stroke) outlined.setAttribute("stroke", outline_stroke );
original.removeAttribute("outline"); // so we can use CSS on outlines
let {x,y,width,height} = original.getBBox();
let cx = x + width/2;
let cy = y + height/2;
outlined.setAttribute("fill", "none"); // outlines never filled
outlined.setAttribute("stroke-dasharray", 3); // or read from your own attribute, like "outline"
outlined.setAttribute("transform", `translate(${cx} ${cy}) scale(${1.2}) translate(-${cx} -${cy})`);
});
// (optional) whack everything into shadowDOM so styles don't conflict
this.attachShadow({mode:"open"}).append(...this.children);
})
}
})
</script>
I need to represent something like a donut chart in SVG.
I was trying to draw several arcs inside the same circle, each one with the length defined by a percentage that is previously givven. But with no luck...
Here is a pic:
What I need is that every region will be dynamically defined according to a percentage previously established.
Does anyone knows how can this be done?
#user1835591 What do you think about it?
<svg xmlns="http://www.w3.org/2000/svg" style="transform-origin:50% 50%;transform:rotate(270deg)" stroke-width="8%" fill="none" stroke-dasharray="400%">
<circle cx="50%" cy="50%" r="25%" stroke="#ff8c00"/>
<circle cx="50%" cy="50%" r="25%" stroke-dashoffset="284%" stroke="#7fffd4"/>
<circle cx="50%" cy="50%" r="25%" stroke-dashoffset="318%" stroke="#228b22"/>
<circle cx="50%" cy="50%" r="25%" stroke-dashoffset="352%" stroke="#6495ed"/>
<circle cx="50%" cy="50%" r="25%" stroke-dashoffset="376%" stroke="#4169e1"/>
<circle cx="50%" cy="50%" r="25%" stroke-dashoffset="390%" stroke="#ffa500"/>
</svg>
This is how I've used to resolve a similar situation:
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20">
<circle r="5" cx="10" cy="10" stroke="red" stroke-width="10" />
<circle r="5" cx="10" cy="10" stroke="green" stroke-width="10" stroke-dasharray="calc(60 * 31.42 / 100) 31.42" transform="rotate(-90) translate(-20)" />
<circle r="5" cx="10" cy="10" stroke="yellow" stroke-width="10" stroke-dasharray="calc(40 * 31.42 / 100) 31.42" transform="rotate(-90) translate(-20)" />
<circle r="5" cx="10" cy="10" stroke-width="10" fill="white" />
</svg>
To calculate percentages You need to calculate the percentage for the last circle "yellow" and then for the second circle "green" you have to calculate the percentage and sum the yellow circle percentage.
Example:
Yellow -> 20% -> calc(20 * 31.42 / 100) 31.42
Green -> 30% -> calc(50 * 31.42 / 100) 31.42 (50 = 20(yellow) + 30(green))
Having a SVG path, what would be the easiest SVG way to draw hops on intersections, to make paths crossing each other more UX friendly? Both intersections with other paths, and within the path itself.
Something like this:
or
Computing intersections and drawing each path segment separately is an option, but I'm afraid about impact on performance, because the SVG path is drawn internally by Leaflet polyline, and there can be many polylines on the map.
In the first SVG canvas I'm using an other wider white line to mark the intersection. In the second I'm using javascript to calculate the intersection and I'm drawing a white circle to mark it. The formula for the intersecting lines is from Intersection point of two line segments in 2 dimensions - written by Paul Bourke
function Intersect(p1,p2,p3,p4){
var denominator = (p4.y - p3.y)*(p2.x - p1.x) - (p4.x - p3.x)*(p2.y - p1.y);
var ua = ((p4.x - p3.x)*(p1.y - p3.y) - (p4.y - p3.y)*(p1.x - p3.x))/denominator;
var ub = ((p2.x - p1.x)*(p1.y - p3.y) - (p2.y - p1.y)*(p1.x - p3.x))/denominator;
var x = p1.x + ua*(p2.x - p1.x);
var y = p1.y + ua*(p2.y - p1.y);
if(ua > 0 && ua < 1 && ub > 0 && ub < 1){
return {x:x,y:y};
}else{return false;}
}
let p1={x:50,y:90}
let p2={x:50,y:10}
let p3={x:10,y:50}
let p4={x:90,y:50}
let _int = Intersect(p1,p2,p3,p4);
int.setAttributeNS(null,"cx", _int.x);
int.setAttributeNS(null,"cy", _int.y);
svg{border:1px solid; width:60vh}
line{stroke-linecap:round;}
.white{stroke:#fff;stroke-width:6}
.black{stroke:#000;stroke-width:2}
<svg viewBox="0 0 100 100">
<defs>
<line id="l1" x2="50" y2="10" x1="50" y1="90" />
<line id="l2" x1="50" y1="10" x2="10" y2="50" />
<line id="l3" x1="10" y1="50" x2="90" y2="50" />
</defs>
<use xlink:href="#l1" class="black" />
<use xlink:href="#l3" class="white" />
<use xlink:href="#l2" class="black" />
<use xlink:href="#l3" class="black" />
</svg>
<svg viewBox="0 0 100 100">
<use xlink:href="#l1" class="black" />
<use xlink:href="#l2" class="black" />
<circle id="int" cx="0" cy="0" r="3" fill="white" />
<use xlink:href="#l3" class="black" />
</svg>
I need to create several SVG shapes (like circles or polygons) and fill each one of them with some SVG elements.
One way to achieve this is to define the shape and apply a pattern with the fill attribute (cf. snippet below). However, the inner SVG elements are clipped by the outer shape's boundary (the triangles are clipped by the circle).
I would like to find a way to hide all the triangles that do not intersect with the circle, and leave the triangles that overflow at the boundary intact.
Please note that computing whether an element intersects with a circle is quite easy in Javascript but I need to create shapes with complicated boundaries such as polygons.
<!-- Learn about this code on MDN: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/pattern -->
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="Triangle" width="10" height="10"
patternUnits="userSpaceOnUse">
<polygon points="5,0 10,10 0,10"/>
</pattern>
</defs>
<circle cx="60" cy="60" r="50" fill="url(#Triangle)"/>
</svg>
This is my sample code, but it is a bit tricky (uses pattern and mask and filter).
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
<!--filter to find area in which pattern shapes must be painted-->
<filter id="Range">
<feMorphology radius="4" operator="dilate"/>
<feComponentTransfer>
<feFuncA type="linear" slope="255"/>
</feComponentTransfer>
<feConvolveMatrix order="2 2" kernelMatrix="1 1 1 1" divisor="1"/>
</filter>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
<!--pattern for filling-->
<pattern id="Triangle" width="10" height="10"
patternUnits="userSpaceOnUse">
<polygon points="5,0 10,10 0,10"/>
</pattern>
<!--mask by paint area-->
<mask id="Mask" maskUnits="userSpaceOnUse">
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5" filter="url(#Range)"/>
</mask>
</defs>
<use xlink:href="#Base" fill="none" stroke="red"/>
<!--fill all screen by pattern and mask by paint area-->
<rect fill="url(#Triangle)" width="100%" height="100%" mask="url(#Mask)"/>
</svg>
So I explain it step by step.
1) find "hit area"(center points of pattern shapes included by paint area of base shape) by using dot pattern filling.
stroke-width is used to enlarge base shape.
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:gray;">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
</defs>
<use xlink:href="#Base" fill="none" stroke="red"/>
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5"/>
</svg>
2) fatten the hit area by filter. This is pattern area.
feConvolveMatrix is used to make box size to be even.
10px(pattern unit size) = 1px(dot size) + 4 * 2px(feMorphology) + 1px(feConvolveMatrix)
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:gray;">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
<!--filter to find area in which pattern shapes must be painted-->
<filter id="Range">
<feMorphology radius="4" operator="dilate"/>
<feComponentTransfer>
<feFuncA type="linear" slope="255"/>
</feComponentTransfer>
<feConvolveMatrix order="2 2" kernelMatrix="1 1 1 1" divisor="1"/>
</filter>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
</defs>
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5" filter="url(#Range)"/>
<use xlink:href="#Base" fill="none" stroke="red"/>
</svg>
3) finally , spread rect element to all svg area, and fill it by pattern shapes and mask it by pattern area created at 2).
This answer does not help the OP now. But I am posting this anyway as it might help future readers.
The following Javascript function should fill any shape with any pattern. It uses the new SVG2 SVGSVGElement.checkIntersection() method.
Unfortunately, checkIntersection() doesn't work properly yet in any browsers. The method call works in Chrome, but it doesn't perform the intersection test properly. The other browsers haven't even implemented the method.
function fillShapeWithPattern(shapeId, patternId)
{
var shape = document.getElementById(shapeId);
var pattern = document.getElementById(patternId);
var svg = shape.ownerSVGElement;
var shapeBounds = shape.getBBox();
var patternBounds = pattern.getBBox();
if (patternBounds.width == 0 || patternBounds.height == 0)
return; // Avoid infinite loops
// To simplify the intersection test, let's adjust the shape bounding
// boxe so that we can pretend the pattern box is at (0,0).
shapeBounds.x -= patternBounds.x;
shapeBounds.y -= patternBounds.y;
// An SVGRect object that we need for the intersection test
var testRect = svg.createSVGRect();
testRect.width = patternBounds.width;
testRect.height = patternBounds.height;
// Loop through a grid checking whether a rectangle representing
// the bounding box of the pattern, intersect with the shape
for (var y = shapeBounds.y;
y < (shapeBounds.y + shapeBounds.height);
y += patternBounds.height)
{
testRect.y = y + patternBounds.y;
for (var x = shapeBounds.x;
x < (shapeBounds.x + shapeBounds.width);
x += patternBounds.width)
{
testRect.x = x + patternBounds.y;
if (svg.checkIntersection(shape, testRect))
{
// Add a copy of the pattern shape to the SVG, by creating
// a <use> element at the right coordinates
var use = document.createElementNS(svg.namespaceURI, "use");
use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#"+patternId);
use.setAttribute("x", x);
use.setAttribute("y", y);
svg.appendChild(use);
}
}
}
}
fillShapeWithPattern("shape", "pattern");
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg">
<defs>
<polygon id="pattern" points="5,0 10,10 0,10"/>
</defs>
<circle id="shape" cx="60" cy="60" r="50" fill="none" stroke="red"/>
</svg>