How to exclude shapes in svg or canvas? - svg

Forgive my English
I am using svg with filters, but faced the following problem.
This is the base for svg. The result is expected:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<g>
<circle id="2" cx="50" cy="50" r="50"/>
<g id="1">
<rect x="0" y="0" width="50" height="50" fill="#ccc"/>
<rect x="50" y="50" width="50" height="50" fill="#ccc"/>
</g>
</g>
</svg>
But with the filter feComposite:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<defs>
<filter id="myFilter1">
<feImage href="#1" result="1"/>
<feImage href="#2" result="2"/>
<feComposite in="1" in2="2" operator="xor"/>
</filter>
</defs>
<g filter="url(#myFilter1)">
<circle id="2" cx="50" cy="50" r="50"/>
<g id="1">
<rect x="0" y="0" width="50" height="50" fill="#ccc"/>
<rect x="50" y="50" width="50" height="50" fill="#ccc"/>
</g>
</g>
</svg>
As you can see, the image is shifted. If you inspect the code, the blocks will not match the visible image:
Here with the addition of interactivity:
const value = (max = 100000000, min = 0) => Math.round(Math.random() * (max - min)) + min;
const createCircle = (size) => {
const r = value(10, 3);
const cx = value(size - r - 10, r + 10);
const cy = value(size - r - 10, r + 10);
return {
r,
cx,
cy
}
};
const createCircles = (counts, size) => Array(counts).fill().map(() => createCircle(size));
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
position: {
x: 0,
y: 0,
}
};
this.size = 300;
this.circlesData = createCircles(100, this.size);
const getCoords = (c, i) => c + (this.state.position.x * 0.002 * c * (i % 2 ? 1 : -1));
this.circles = () => this.circlesData.map((item, i) => <circle key = {`circles_12dew1_${i}`} cx={getCoords(item.cx, i)} cy={getCoords(item.cy, i)} r={item.r}/>);
}
onMouseMove = e => {
const position = {
x: e.pageX,
y: e.pageY,
};
this.setState({position});
}
render() {
return (
<div className = "App" >
<svg onMouseMove={this.onMouseMove} ref = {elem => this.svg = elem} xmlns = "http://www.w3.org/2000/svg" width = {this.size} height = {this.size} viewBox={`0 0 ${this.size} ${this.size}`}>
<defs>
<filter id="myFilter1">
<feImage href="#1" result="1"/>
<feImage href="#2" result="2"/>
<feComposite in ="1" in2="2" operator="xor"/>
</filter>
</defs>
<g id = "3" filter = "url(#myFilter1)" >
<circle id = "2" cx={this.size / 2 + 100} cy={this.size / 2 + 100} r={this.size / 3}/>
<g id="1"> {this.circles()} </g>
</g>
</svg>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
As you can see, a big circle is moving, but it shouldn't be doing that.
How to fix?
Or how to make the exception of figures as in the interactive example without svg? For example using canvas, thank you!

The image placement behaviour you are seeing is because <feImage> elements are positioned using the filter region, or filter primitive region.
https://www.w3.org/TR/SVG11/single-page.html#filters-feImageElement
By default the filter region is an area that is 10% bigger than the original object on all sides.
x="-10%" y="-10%" width="120%" height="120%"
This is done to cater for filter primitives, such as <feGuassianBlur>, which extend outside the original size and would otherwise get clipped.
To get the images to position how you want, change the filter region, or filter primitive region, to be the same size as the original object. For example:
<feImage href="#1" x="0" y="0" width="100%" height="100%" result="1"/>
Updated demo:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<defs>
<filter id="myFilter1">
<feImage href="#1" x="0" y="0" width="100%" height="100%" result="1"/>
<feImage href="#2" x="0" y="0" width="100%" height="100%" result="2"/>
<feComposite in="1" in2="2" operator="xor"/>
</filter>
</defs>
<g filter="url(#myFilter1)">
<circle id="2" cx="50" cy="50" r="50"/>
<g id="1">
<rect x="0" y="0" width="50" height="50" fill="#ccc"/>
<rect x="50" y="50" width="50" height="50" fill="#ccc"/>
</g>
</g>
</svg>

Related

Change SVG paths to be clipped according to clipPath element

I have a SVG that looks like this:
<svg id="Layer_1" data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 576 576">
<defs>
<style>...</style>
<clipPath id="clip-path">
<rect class="cls-1" x="0.02" width="575.04" height="576"/>
</clipPath>
</defs>
<g class="cls-2">
<path class="cls-3" d="M137.91-147.28c-4-1.67-8.25-3.46-12.37-3.86-5.43-.53-9.26,1.73-12.55,5a18.75,18.75,0,0,0-4.69-9.42,19.23,19.23,0,0,0-6.45-...
<path class="cls-4" d="M.08,502.59c-.79-5.67-6.22-4.3-5.81-.22a17.15,17.15,0,0,1,0,2.95c-.22,2.82-1.46,7.6-5,7.61-1.35,0-2.61-1-3.12...
...
I want to change it such that:
there is no grouping (this is easy)
change the path, rect, etc. elements such that they are clipped according to the what's in the clipPath element. The clipPath should no longer be present in the SVG (because it isn't needed anymore)
I've tried this:
inkscape --actions \
"select-all:groups; SelectionUnGroup; ObjectUnSetClipPath; export-filename: output.svg; export-plain-svg; export-do;" \
Decorations.svg
This removes the grouping, but the path elements are not clipped. The clipPath is still present.
ObjectUnSetClipPath will remove the clipping attribute clip-path="url(#clip-path)" – not the <def> clipping path itself.
But you can't clip any element, if your clipPath definition is stripped.
function stripClip(el){
let clipPaths = document.querySelector(el).querySelectorAll('clipPath');
if(clipPaths.length){
clipPaths.forEach(function(item, i){
item.remove();
}
)
}
}
svg{
width: 20vw;
}
.clipped{
clip-path: url(#clip-path2);
}
<p><button onclick="stripClip('#svg1')" >remove ClipPath</button></p>
<svg id="svg1" data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
<defs>
<style>...</style>
<clipPath id="clip-path">
<rect class="cls-1" x="10" width="36" height="18"/>
</clipPath>
</defs>
<g class="cls-2">
<path clip-path="url(#clip-path)" d="M18 2.0845
a 15.9155 15.916 0 0 1 0 31.83
a 15.916 15.916 0 0 1 0 -31.83z" fill="red" />
<rect clip-path="url(#clip-path)" x="0" y="0" width="10" height="25" />
</g>
</svg>
<svg id="svg2" data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
<defs>
<style>...</style>
<clipPath id="clip-path2">
<rect class="cls-1" x="10" width="36" height="18"/>
</clipPath>
</defs>
<g class="cls-2">
<path class="clipped" d="M18 2.0845
a 15.9155 15.916 0 0 1 0 31.83
a 15.916 15.916 0 0 1 0 -31.83z" fill="red" />
<rect class="clipped" x="0" y="0" width="10" height="25" />
</g>
</svg>
**Edit:** Get svg intersection paths using paper.js
Admittedly, not the most convenient approach but it is possible:
In addition to paper.js, we also need some script/helper to convert svg shapes (circles, rects etc.) to <path> elements (I'm using 'pathThatSvg'
, since paper.js can only calculate new intersection paths based on 2 path elements like so:
var intersectionPath = path1.intersect(clipPath);
Based on this answer
var svg = document.querySelector("#svgIntersect");
// set auto ids for processing
function setAutoIDs(svg) {
var svgtEls = svg.querySelectorAll(
"path, polygon, rect, circle, line, text, g"
);
svgtEls.forEach(function (el, i) {
if (!el.getAttribute("id")) {
el.id = el.nodeName + "-" + i;
}
});
}
setAutoIDs(svg);
// convert shapes to paths
function shapesToPath(svg) {
pathThatSvg(svg.outerHTML).then((converted) => {
var tmp = document.createElement("div");
tmp.innerHTML = converted;
svg.innerHTML = tmp.querySelector("svg").innerHTML;
});
}
shapesToPath(svg);
function intersectPath(svg, decimals=2) {
// init paper.js and add canvas
canvas = document.createElement('canvas');
canvas.id = "canvasPaper";
canvas.setAttribute('style','display:none')
document.body.appendChild(canvas);
paper.setup("canvasPaper");
// process clipped elements
var all = paper.project.importSVG(svg, function (item, i) {
item.position = new paper.Point(
item.bounds.width / 2,
item.bounds.height / 2
);
//item.scale(0.5, new Point(0, 0) )
var items = item.getItems();
var ids = item._namedChildren;
var groups = item.children;
groups.forEach(function (gr, i) {
var group = gr["_namedChildren"];
if (group) {
for (key in group) {
//get clip path
var clip = group["clipPath"][0];
if (key !== "clipPath") {
var el = group[key][0];
//get intersection path and generate d commands
var elClipped = el.intersect(clip);
var elClippedD = elClipped
.exportSVG({ precision: decimals })
.getAttribute("d");
// select path by id and overwrite d attribute
var newEl = svg.querySelector("#" + key);
newEl.setAttribute("d", elClippedD);
}
}
}
});
// remove clip defs and attributes
var clippedEls = svg.querySelectorAll("[clip-path]");
clippedEls.forEach(function (clippedEl, e) {
clippedEl.removeAttribute("clip-path");
});
svg.querySelector("defs").remove();
svg.classList.add("svg-intersect");
console.log(svg.outerHTML)
});
}
svg{
border: 1px solid #ccc;
display:inline-block;
width:200px;
}
.svg-intersect path{
stroke:red;
stroke-width: 0.25;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/path-that-svg#1.2.4/dist/pathThatSvg.umd.min.js"></script>
<p>
<button type="button" onclick="intersectPath(svg)">get Path Intersect</button>
</p>
<svg id="svgIntersect" viewBox="0 0 100 100">
<defs>
<clipPath id="clipPath">
<circle cx="25" cy="25" r="25"/>
</clipPath>
</defs>
<g id="clipGroup" clip-path="url(#clipPath)">
<circle fill="#999" data-id="circle" cx="25" cy="25" r="25" />
<rect fill="#555" id="rect" x="25" y="25" width="50" height="50" />
</g>
<g clip-path="url(#clipPath)">
<circle fill="#444" id="circle2" cx="66" cy="25" r="25" />
<rect fill="#22" id="rect2" x="15" y="12.5" width="20" height="75" />
</g>
</svg>

Alternative to <use/> to avoid repeating ids when repeating Inline SVG

Plunkr example.
So lets say I have svg that I am including inline in HTML
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" class="col-sm-3">
<symbol id="circle" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="white" stroke="black" stroke-width="2"></circle>
<text x="50%" y="50%" text-anchor="middle" dy=".3em">___THIS_IS_A_VARIABLE__</text>
</symbol>
<use xlink:href="#circle" width="85" height="85"/>
</svg>
I have a slightly more complex use care where the inside of the <symbol/> is a complex path of a different scale than the svg root viewbox so I need to use the <symbol/> to scale it to be full size.
I need to include a bunch of these in my html which I do via a for loop. Everything works find, but I am afraid I might introduce bugs in the future because in my html document I have multiple elements with the same id.
My questions:
Is there a different way to use <use/> like with a css selector?
Am I correct that repeating ids in inline SVG inside a html document is as bad as repeating ids in a html document itself?
Is there a better way to nest viewbox attributes to avoid <use/>?
The best way I can think of is to make the id dynamic. So in ther case of using angular it would be:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" class="col-sm-3">
<symbol [id]="circleId" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="white" stroke="black" stroke-width="2"></circle>
<text x="50%" y="50%" text-anchor="middle" dy=".3em">___THIS_IS_A_VARIABLE__</text>
</symbol>
<use [attr.xlink:href]="'#' + circleId" width="85" height="85"/>
</svg>
No need for a Framework, native W3C Web Components can do the same:
If you don't/can't specify an id, generate one: let id = "id" + new Date()/1;
svg {
display: inline-block;
background: grey;
}
<svg-shape id=1 text="red"></svg-shape>
<svg-shape id=2></svg-shape>
<svg-shape id=3 text="blue"></svg-shape>
<script>
customElements.define('svg-shape', class extends HTMLElement {
connectedCallback() {
let id = this.getAttribute("id");
let text = this.hasAttribute("text") ? this.getAttribute("text") : "";
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" class="col-sm-3">
<symbol id="id${id}" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="white" stroke="${text}" stroke-width="2"></circle>
<text x="50%" y="50%" text-anchor="middle" dy=".3em">${text}</text>
</symbol>
<use href="#id${id}" width="85" height="85"/>
</svg>`;
this.onclick = (evt) => console.log('clicked', id);
}
});
</script>

How to make vertical svg rect height start from the bottom?

Desired behaviour
Make a "bar chart" style icon with vertical bars.
Actual Behaviour
The following displays the desired result if I flip it with scaleY().
/* uncomment below for desired appearance */
/*
svg {
transform: scaleY(-1);
}
*/
<svg style="background: yellow" width="20" height="20" viewbox="0 0 20 20">
<rect x="2" y="0" width="1" height="5" style="fill:rgb(0,0,0)"/>
<rect x="7" y="0" width="1" height="8" style="fill:rgb(0,0,0)"/>
<rect x="12" y="0" width="1" height="15" style="fill:rgb(0,0,0)"/>
<rect x="17" y="0" width="1" height="10" style="fill:rgb(0,0,0)"/>
Sorry, your browser does not support inline SVG.
</svg>
Question
What is the correct way to start the height from the bottom, rather than the top?
SVG's Y axis goes from the top down to the bottom (source)[https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Positions#The_grid].
In order to display the bars from the bottom of the icon, you need to calculate each bar's y coordinate accordingly: iconHeight - barHeight.
Adapted snippet:
/* uncomment below for desired appearance */
/*
svg {
transform: scaleY(-1);
}
*/
<svg style="background: yellow" width="20" height="20" viewbox="0 0 20 20">
<rect x="2" y="15" width="1" height="5" style="fill:rgb(0,0,0)"/>
<rect x="7" y="12" width="1" height="8" style="fill:rgb(0,0,0)"/>
<rect x="12" y="5" width="1" height="15" style="fill:rgb(0,0,0)"/>
<rect x="17" y="10" width="1" height="10" style="fill:rgb(0,0,0)"/>
Sorry, your browser does not support inline SVG.
</svg>

How can I get length of SVG polyline element?

This is my polyline and I want to get its length.
<div class="svg-1">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="612.417px" height="274.412px" viewBox="0 0 612.417 274.412" enable-background="new 0 0 612.417 274.412" xml:space="preserve">
<g>
<defs>
<rect id="SVGID_1_" y="0" width="612.417" height="274.412" />
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible" />
</clipPath>
<polyline class="square-1" clip-path="url(#SVGID_2_)" fill="none" stroke="#B2965F" stroke-width="4" stroke-miterlimit="10" points="
276.084,191.912 38.084,191.912 38.084,26.079 589.417,26.079 589.417,191.912 311.417,191.912 311.417,273.412 " />
</g>
</svg>
</div>
Use the SVG DOM to read each vertex position and then work out the distance between the vertices using pythagoras theorem. Then add up all the distances.
var totalLength = 0;
var prevPos;
var polyline = document.getElementById("polyline");
for (var i = 0 ; i < polyline.points.numberOfItems;i++) {
var pos = polyline.points.getItem(i);
if (i > 0) {
totalLength += Math.sqrt(Math.pow((pos.x - prevPos.x), 2) + Math.pow((pos.y - prevPos.y), 2));
}
prevPos = pos;
}
alert(totalLength);
<div class="svg-1">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="612.417px" height="274.412px" viewBox="0 0 612.417 274.412" enable-background="new 0 0 612.417 274.412" xml:space="preserve">
<g>
<defs>
<rect id="SVGID_1_" y="0" width="612.417" height="274.412" />
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible" />
</clipPath>
<polyline id="polyline" class="square-1" clip-path="url(#SVGID_2_)" fill="none" stroke="#B2965F" stroke-width="4" stroke-miterlimit="10" points="
276.084,191.912 38.084,191.912 38.084,26.079 589.417,26.079 589.417,191.912 311.417,191.912 311.417,273.412 " />
</g>
</svg>
</div>
Polyline element implements the SVGGeometryElement interface, which in turn has a method getTotalLength(). So you can basically do this (providing you polyline has an attribute id="polyline"):
var polyline = document.getElementById("polyline");
var totalLength = polyline.getTotalLength();
SVGGeometryElement.getTotalLength()
Here is the javascript code
function getPolylineLength(polylineElement){
function dis(p,q){
return Math.sqrt((p.x-q.x)*(p.x-q.x) + (p.y-q.y)*(p.y-q.y));
}
var ps = polylineElement.points, n = ps.numberOfItems, len=0;
for(var i=1; i<n; i++){
len += dis(ps.getItem(i-1),ps.getItem(i));
}
return len;
}

SVG: text inside rect

I want to display some text inside SVG rect. Is it possible?
I tried
<svg xmlns="http://www.w3.org/2000/svg">
<g>
<rect x="0" y="0" width="100" height="100" fill="red">
<text x="0" y="10" font-family="Verdana" font-size="55" fill="blue"> Hello </text>
</rect>
</g>
</svg>
But it does not work.
This is not possible. If you want to display text inside a rect element you should put them both in a group with the text element coming after the rect element ( so it appears on top ).
<svg xmlns="http://www.w3.org/2000/svg">
<g>
<rect x="0" y="0" width="100" height="100" fill="red"></rect>
<text x="0" y="50" font-family="Verdana" font-size="35" fill="blue">Hello</text>
</g>
</svg>
Programmatically using D3:
body = d3.select('body')
svg = body.append('svg').attr('height', 600).attr('width', 200)
rect = svg.append('rect').transition().duration(500).attr('width', 150)
.attr('height', 100)
.attr('x', 40)
.attr('y', 100)
.style('fill', 'white')
.attr('stroke', 'black')
text = svg.append('text').text('This is some information about whatever')
.attr('x', 50)
.attr('y', 150)
.attr('fill', 'black')
You can use foreignobject for more control and placing rich HTML content over rect or circle
<svg width="250" height="250" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="250" height="250" fill="aquamarine" />
<foreignobject x="0" y="0" width="250" height="250">
<body xmlns="http://www.w3.org/1999/xhtml">
<div>Here is a long text that runs more than one line and works as a paragraph</div>
<br />
<div>This is <u>UNDER LINE</u> one</div>
<br />
<div>This is <b>BOLD</b> one</div>
<br />
<div>This is <i>Italic</i> one</div>
</body>
</foreignobject>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgb(145,200,103);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(132,168,86);stop-opacity:1" />
</linearGradient>
</defs>
<rect width="220" height="30" class="GradientBorder" fill="url(#grad1)" />
<text x="60" y="20" font-family="Calibri" font-size="20" fill="white" >My Code , Your Achivement....... </text>
</g>
</svg>
Programmatically display text over rect using basic Javascript
var svg = document.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'svg')[0];
var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', 20);
text.setAttribute('y', 50);
text.setAttribute('width', 500);
text.style.fill = 'red';
text.style.fontFamily = 'Verdana';
text.style.fontSize = '35';
text.innerHTML = "Some text line";
svg.appendChild(text);
var text2 = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text2.setAttribute('x', 20);
text2.setAttribute('y', 100);
text2.setAttribute('width', 500);
text2.style.fill = 'green';
text2.style.fontFamily = 'Calibri';
text2.style.fontSize = '35';
text2.style.fontStyle = 'italic';
text2.innerHTML = "Some italic line";
svg.appendChild(text2);
var text3 = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text3.setAttribute('x', 20);
text3.setAttribute('y', 150);
text3.setAttribute('width', 500);
text3.style.fill = 'green';
text3.style.fontFamily = 'Calibri';
text3.style.fontSize = '35';
text3.style.fontWeight = 700;
text3.innerHTML = "Some bold line";
svg.appendChild(text3);
<svg width="510" height="250" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="510" height="250" fill="aquamarine" />
</svg>

Resources