I was able to merge two separate paths together using this technique. However, my animation is still treating this as two separate paths.
Is there a way to combine these two paths without using spaces?
M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3809.63
M3809.63,496.31c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31"/>
svg {
fill: none;
}
path {
stroke: tomato;
stroke-width: 100;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 25 14905 623">
<path d="M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3809.63 M3809.63,496.31c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31"/>
</svg>
The originally separate paths can be viewable here:
svg {
fill: none;
}
path {
stroke: tomato;
stroke-width: 100;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 25 14905 623">
<path d="M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3511.9" />
<path d="M3809.63,496.31c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31" />
</svg>
The goal is to merge these paths to match the above svg snippet -- without using spaces in the path.
I found an awesome online editor for path manipulations:
yqnn.github.io/svg-path-editor/
Paste your path segments
[optional] Check parsed path commands (e.g. Remove the M between segments)
[optional] Convert to either 'abs' or 'rel' commands
[optional] Set 'minify output' checkbox
Copy output
m3322.09 361.23v112.22c0 14 2 23.41 23.41 23.41h464.13c21.41 0 166.41-1 166.41-1s13.07.87 82.08.87c31.75 0 63.51-36.21 95.26-75.31
The answer is
M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3809.63 c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31
H3511.9 means draw a horizontal line until x point at 3511.9 (with whatever y was previously inherited)
M3809.63,496.31 means move the "cursor" to an x, y coordinate.
I changed H3511.9 to H3809.63 and removed M3809.63,496.31 and continued with c21.41... which is a draw curve command.
These resources helped me to understand the draw commands for the d path attribute.
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
https://www.youtube.com/watch?v=k6TWzfLGAKo
I found the easiest solution was:
Open the SVG file in Inkscape (Free software, cross platform https://inkscape.org)
Select the paths to merge
From the Path menu, choose Union
Save the file
For those who came upon this question while researching to make sure there would be no problems from merging paths like I was, the problem here is just a typo.
In the above merged snippet, the ending of the first path was changed from its original 23.41H3511.9 to 23.41H3809.63.
Putting paths together by putting a space (or no space at all, just don't have a comma between the paths when placing them in the d attribute value) should always work without modification.
(On a side note, Raphael's answer made SVGs a lot less cryptic to me now after reading his links and tips)
Related
I would like to build a site where the text is not displayed in straight lines but on curved lines instead. I work with a single SVG path at the moment, that spans over multiple rows and put the text on that path with the textPath-element. I then scale the fontsize with JavaScript, to make it "responsive".
Here is what I have so far:
$(function() {
var myFontsize = window.innerWidth * 0.015 + ((window.innerWidth - 1300) / -30);
$( "text" ).css("font-size", myFontsize)
});
$( window ).resize(function() {
var myFontsize = window.innerWidth * 0.015 + ((window.innerWidth - 1300) / -30);
$( "text" ).css("font-size", myFontsize)
});
body {
margin: 1vw;
}
path {
fill: none;
}
#line-box {
position: fixed;
left: 1vw;
top: 0;
width: 101vw;
}
text {
fill: black;
font-size: 1.3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="line-box" viewBox="0 -30 1200 1200" preserveAspectRatio="none">
<path vector-effect="non-scaling-stroke" id="curve" d="M0 0C449.6667 0 857 0 1160 0M0 35C292 35 584 35 876 55 953.6667 55 1031.3333 55 1160 35"/>
<text text-anchor="start">
<textPath xlink:href="#curve">
Are you a human being? We apologize for the confusion, but we can't quite tell if you're a person or a script. Please don't take this personally. Bots and scripts can be remarkably lifelike these days!
</textPath>
</text>
</svg>
(Change the width of the view box to see the behavior I am talking about)
Now the rendered text on the path of course doesn't behave like a text in a p-element for example, so where there is a "line-break" it wraps around the edges in the middle of the words (which are not words but single rendered letters).
I thought about putting each word into a tspan-element, thinking I could get a grip on each word this way, but the rows of text are put on one consecutive path, so I don't really know how to calculate or detect wether a word is leaving or entering a specific row of the path.
Is there a way to continue a text over multiple paths instead of having one path drawing multiple rows? Maybe this way it could be somehow calculated when a tspan-element is leaving a path and must enter the next?
I basically want the text and it's "line-breaks" to work similarly to a normal paragraph without hyphenation, just that the lines move on paths. That idea doesn't sound so complicated to me, but I don't really know what the best way is to make it work.
Maybe SVG-paths are just not the right tool for this. In that case, does anyone have a better Idea where I should look? I am not hesitant to look into some js-libraries for example.
Thank you so much :)
You'll definitely need Javascript to implement this. SVG is a relatively low-level format for describing vector graphics, not a text layout format. Responsive line breaks is a feature in the pipeline, but not fully implemented yet AFAIK. Responsive line breaks on text paths? That is far too specialised.
That idea doesn't sound so complicated to me
Well, it is for various reasons. Here is a somewhat naive implementation:
const nssvg = "http://www.w3.org/2000/svg";
const nsxlink = "http://www.w3.org/1999/xlink";
const text = document.querySelector('text');
const originalText = text.textContent.trim();
function nextCurve(curves) {
const currentCurve = curves.shift();
const currentLength = currentCurve.getTotalLength();
return [currentCurve, currentLength, /\s+/g, 0];
}
function makeTextPath(content, id) {
const textPath = document.createElementNS(nssvg, 'textPath');
textPath.setAttributeNS(nsxlink, 'xlink:href', '#' + id);
textPath.textContent = content;
text.appendChild(textPath);
}
function onResize() {
text.style.fontSize = (window.innerWidth * 0.015 + ((window.innerWidth - 1300) / -30)) + 'px';
// flush old layout
text.innerHTML = "";
let currentText = originalText;
// set text in a provisorial tspan so it can be measured
const tspan = document.createElementNS(nssvg, 'tspan');
tspan.textContent = currentText;
text.appendChild(tspan);
const curves = [...document.querySelectorAll('.curve')];
let [currentCurve, currentLength, re, previousIndex] = nextCurve(curves);
// search for spaces
while (re.exec(currentText)) {
// measure the position of the space
const length = tspan.getSubStringLength(0, re.lastIndex)
if ( (length < currentLength) ) {
previousIndex = re.lastIndex;
} else {
// if the space is beyond the end, add a textPath with one word less,
// provided there is still a curve left to use
if (currentCurve) {
makeTextPath(currentText.substring(0, previousIndex), currentCurve.id);
}
// remove the already used part from the tspan
// and initialize for next line
tspan.textContent = currentText = currentText.substring(previousIndex).trim();
[currentCurve, currentLength, re, previousIndex] = nextCurve(curves);
}
}
// there is a rest that still needs to be layed out
if (currentCurve) {
makeTextPath(currentText, currentCurve.id);
}
//the provisorial tspan can be removed now
tspan.remove();
}
onResize();
window.addEventListener('resize', onResize);
#line-box {
left: 1vw;
width: 98vw;
}
text {
text-anchor: start;
fill: black;
font-size: 1.3rem;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="line-box" viewBox="0 -30 1200 1200" preserveAspectRatio="none">
<defs>
<path class="curve" id="c1" d="M0 0C449.6667 0 857 0 1160 0"/>
<path class="curve" id="c2" d="M0 35C292 35 584 35 876 55 953.6667 55 1031.3333 55 1160 35"/>
<path class="curve" id="c3" d="M 0,70 C 292,70 584,90 876,110 953.67,110 1031.3,90 1160,70"/>
</defs>
<text>
Are you a human being? We apologize for the confusion, but we can't quite tell if you're a person or a script. Please don't take this personally. Bots and scripts can be remarkably lifelike these days!
</text>
</svg>
One simplification about this you mentioned yourself: the potential line breaks are found with the regular expression /\s+/g, which will identify various space characters. This is even less sophisticated than the algorithms used for line breaks in HTML (with the default hyphens: none).
The big problem is identifying what is fitting on the path used in <textPath>. There are a few interfaces defined for SVGTextContentElement that look like they could be helpful. But in practice, they fall short. The algorithm for describing how to position glyphs is quite longish, but it doesn't say much about glyphs that cannot be positioned:
Glyphs whose midpoint-on-the-path are off the path are not rendered.
Continue rendering glyphs until there are no more glyphs (or no more space on path).
Suppose the nth character is not rendered. The quote above doesn't say anything about the return values of textPath.getSubStringLength(0, n) or textPath.getStartPositionOfChar(n). Firefox, for example, returns values as if they were all lumped up somewhere near the end of the path. Other browsers may return something completely different, there is no guarantee what.
I decided to circumvent this problem by first laying out the string in a straight line, then measuring where the spaces end up, and break at the last space whose position is still smaller than the length of the path it will be laid out with. But this is only a rough estimate. In reality the character spacing differs between straight and curved layout. Especially for narrow curves the results will be wrong, and when the text is positioned on its inside, some letters may disappear.
Another problem arises from the concept of "addressable characters" used for producing character indices in SVG:
A character that is addressable by text positioning attributes and SVG DOM text methods. Characters discarded during layout such as collapsed white space characters are not addressable, neither are characters within an element with a value of none for the display property. Addressable characters are addressed by their index measured in UTF-16 code units (thus, a single Unicode code point above U+FFFF will map to two addressable characters as a UTF-16 code unit consists of 16 bits).
If you have a string as returned from the textContent property, and apply methods of the Javascript String prototype, the same indices may point to completely different characters.
To completely solve both problems, a lot more code will be needed, and all the special cases to be considered are quite beyond the scope of this answer. Maybe there is a library out there somewhere that attempts this, but I am not aware of it.
I have two html elements (in my case they are SVG path elements). Each of these has a different stroke width. Is there any way to determine if two of these paths overlap? I have tried using the npm path intersection library, but it doesn't allow me to specify a width. Also, the results are approximate, but I need more exact data.
I don't need to calculate the actual intersection area, or coordinates. I just need to know if an intersection occurs between these two paths with definite widths. Is there a solution?
edit:
here is an example to demonstrate something i want
<svg xmls="http://www.w3.org/2000/svg">
<path id="p1" d="M10,10 C20,30, 50,70, 100,100" stroke-width="3" stroke='black'></path>
<path id="p2" d="M20,0 Q200,30,0,100" stroke-width="5" stroke="red"></path>
</svg>
path {
fill: none;
}
svg {
position: absolute;
top: 100px;
left: 100px;
}
in javascript, i have something like this:
let p1 = document.getElementById("p1"), p2 = document.getElementById("p2");
if (p1 intersects/overlaps with p2) {
//run some code
}
example 1
it produces the above, which i classify as an intersection, as the paths themselves overlap
example 2
here, i produced the same lines, but one is shifted. I do not classify this as an overlap
This question already has answers here:
How can I change the color of an 'svg' element?
(40 answers)
How to change color of SVG image using CSS (jQuery SVG image replacement)?
(18 answers)
Closed last year.
This post was edited and submitted for review last year and failed to reopen the post:
Original close reason(s) were not resolved
I am writing an SVG editor program. I am trying to implement highlighted feel for when an element (<image>, not <img>) is clicked. I have implemented this for other elements like <circle>, by setting stroke, stroke-width, and stroke-dash-array. But setting the presentation attributes of the <image> tag, either by setting the attributes directly or through CSS styling, is not taking effect. I also tried the border style but it's not working. How can I achieve this?
According to MDN , <image> is meant to have have global attributes, including presentation, which ultimately means this should work. If you also inspect <image> with a browser you will see it will highlight like it follows box model.
<svg viewBox="0 0 1370 1170" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="128" height="128" x="617.9207153320312" href="data:some-valid-link" style="stroke-width: 3px; stroke-dasharray: 7px;" stroke="#3aa2c2"></image>
</svg>
I don't think that you can set stroke on an image.
Usually those attributes are used to 'draw' vector image. Here you're trying to put a stroke on a raster image.
If you want to put lines around a square image you could use a border to achieve it.
If you want to put lines around a complicated shape, I don't think it's possible to do that in CSS. You would need to add that in graphics software.
You cannot restyle an external SVG, if you load it in an <img> tag.
Instead, you could paste the <svg> code directly into your HTML, and apply styles like this (source svg):
svg {
width: 500px;
}
svg path,
svg polygon {
stroke-width: 10px;
stroke-dasharray: 10px;
}
<svg height="210" width="500">
<polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:lime;stroke:purple;stroke-width:5;fill-rule:nonzero;"/>
Sorry, your browser does not support inline SVG.
</svg>
You would need and SVG with a more specific code targeting all the vectors and not just a tag. Then you could make groups of the parts of the image/draw you want to style. You can do this by using illustrator, then after grouping the parts, your code will have a 'g' tag: <g id="group_to_style" > with an ID that you have named when doing the groups. Then using CSS you can target the groups of the SVG and style or even animate. Like gru said, with an you cant style or animate anything.
For my website, I want the users to be able to click on various svg elements.
I've coded up a quick function to let users click on enclosed shapes. I basically just run document.elementFromPoint(x, y) on them. This works great for any enclosed shape, since they have clickable area. Open paths, however, are a pain to click because they're only 1-2 pixels in size.
I would like to allow the user to select them by clicking 'close enough' to it, such that if the click is, for example, 10 or fewer pixels away from the path, the path would be selected. I could write a mathematical algorithm for this (using Ray-Casting that only goes 10 pixels out for example, or turn points into 10px-radius circles and check for intersections with the path), but my intuition tells me there must be an easier way, perhaps via CSS.
One idea I was thinking of is if the stroke for this path was 10 pixels thick for selection purposes, but only 1 pixel thick visibly (or having a 'padding' that would apply to the path rather than the bounding box). I've also looked at pointer-events, but aside from bounding box (which I would rather not use to avoid selecting other shapes) I don't see any useful settings there. Is there something I could leverage in JavaScript or CSS that can help me accomplish this without doing the math for the paths myself (since it may get ugly for cubic beziers, etc.)?
I should also mention that this is an HTML5 app, so I don't care about backwards-compatibility.
If the paths have no stroke then you can create an invisible stroke (stroke-opacity="0") and use pointer-events="all" to give the shape an extended hit area.
If the paths do have strokes then you need to create a second invisible path on top of the visible path with the same coordinates but a bigger stroke-width. You can then use pointer-events="all" to make this invisible path the hit target and if it is hit you react in the event handler as if the visible shape was hit.
Robert Longson describes how you can do this, but I wanted to add some more details.
First, you don't need to duplicate or create new geometry if you don't want to. You can also use a <use> element. You can use some css to give shapes a different look even when some other invisible element is hovered.
<style>
g { fill: #ddd; stroke: black; pointer-events: none; }
use { pointer-events: all; stroke-width: 40; fill: none; stroke-opacity: 0; }
use:hover + * { fill: cornflowerblue; }
<g>
<use xlink:href="#c"/>
<circle id="c" cx="50" cy="60" r="20"/>
</g>
See live example.
EDIT:
I finally found a way to erode and dilate polygons (offsetting) so that new geometry is created using Clipper library:
https://sourceforge.net/projects/jsclipper/
Live demo of Javascript Clipper:
http://jsclipper.sourceforge.net/5.0.2.1/main_demo.html
The Clipper can only handle polygons or multi-polygons (eg. polygons with holes), so for it to work with other graphical objects of SVG format, they have to be converted to straight lines. At least paths are rather easy to convert to lines using path.getTotalLength() and path.getPointAtLength() (http://whaticode.com/2012/02/01/converting-svg-paths-to-polygons/).
The other possibility is use this like technique (that does not create new geometry):
https://stackoverflow.com/a/12723835/1691517
Is there any way to erode and dilate shapes in SVG via Javascript?
I have the following SVG example:
http://jsfiddle.net/timo2012/2S4Kt/1/
There are three shapes, blue is original, green is eroded (thinned) and red is dilated (bolded). They are made in Illustrator.
I have tested erode and dilate filters, but the effect is not so good:
https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/examples/feMorphology.svg
After few hours searching over internet, I have found only examples about bitmap image eroding and dilating, but nothing about vector shapes.
I have succeeded in dilating and eroding SVG polygons using Shapely ( http://toblerity.github.com/shapely/manual.html ) in Python by sending path points via Ajax call to PHP script which makes system() call to Python script, but this method is slow and requires server to do the work that could be done client side.
This is my code for dilating and eroding in Python (as you see it is quite short):
#!/usr/bin/python26
from shapely.geometry import Polygon
from shapely.geometry import MultiPolygon
import sys
if len(sys.argv)>2:
inset=eval(sys.argv[1])
coords=eval(sys.argv[2])
else:
sys.exit()
bowtie = Polygon(coords)
clean = bowtie.buffer(inset)
clean = clean.simplify(1, preserve_topology=False)
if clean.length>0:
if clean.geom_type=="MultiPolygon":
for n in range(0, len(clean)):
print list(clean[n].exterior.coords)
#print "\n"
elif clean.geom_type=="Polygon":
print list(clean.exterior.coords)
Also find this document, which tries to define dilate and erode in mathematical terms:
http://en.wikipedia.org/wiki/Mathematical_morphology
There is a sentence "The basic idea in binary morphology is to probe an image with a simple, pre-defined shape, drawing conclusions on how this shape fits or misses the shapes in the image. This simple "probe" is called structuring element, and is itself a binary image (i.e., a subset of the space or grid)."
I assume that this method could be used in morphing vector shapes, but how...
EDIT: One comment in a reply raised a possible issue of using filters instead of creating new geometry: if someone wants to add drag handles to polygon points, then drag handles may seem to be in wrong place. This can be acceptable, because then the impression is that the original path data is untouched, which is actually the case in filters, but - after further testing - it proved that the quality is a bigger problem. According to this and this SVG filter uses pixel representation of vector graphic object instead of path data itself, which leads to not so good looking results.
EDIT2: POSSIBLE WORKAROUND: One of the answers in this page led me to use variable-width strokes and mask to achieve a good looking workaround to this issue. I made a few tests and get implemented an Adobe Illustrator -like Offset Path Effect.
You can sort of get what you seem to be after by stroking with different stroke-widths in combination with clip-path or mask. Here's an example, some explanations of how it's constructed see here and here (arrow up or down to see some other slides on that example).
It doesn't give you new geometry though, just something that might look like new geometry.
Have you actually tested SVG's native filters? This looks close enough:
<svg width="612" height="792" viewBox="0 0 612 792" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="erode">
<feMorphology operator="erode" in="SourceGraphic" radius="12" />
</filter>
<filter id="dilate">
<feMorphology operator="dilate" in="SourceGraphic" radius="8" />
</filter>
<path id="original_path" d="M193.193,85c23.44,0.647,45.161,0.774,62,12c1.596,1.064,12,11.505,12,13
c0,2.941,8.191,5.669,3,12c-3.088,3.767-6.01-0.758-11-1c-19.56-0.948-33.241,12.296-33,34c0.163,14.698,8.114,24.492,4,41
c-1.408,5.649-6.571,15.857-10,21c-2.484,3.726-7.898,10.784-12,13c-4.115-11.677,2.686-27.29-6-35c-6.693-5.942-20.021-4.051-26,1
c-13.573,11.466-11.885,41.492-7,58c-5.8,1.772-18.938,7.685-23,12c-6.752-10.805-15.333-17.333-24-26c-3.307-3.307-9.371-12-15-12
c-16.772,0-13.963-15.741-13-28c1.283-16.324,1.727-28.24,4-42c1.276-7.72,8-16.411,8-23c0-7.416,15.945-29,23-29
c4.507,0,17.678-8.701,24-11C164.853,90.76,178.27,88.546,193.193,85"/>
</defs>
<use xlink:href="#original_path" fill="#f00" filter="url(#dilate)"></use>
<use xlink:href="#original_path" fill="blue"></use>
<use xlink:href="#original_path" fill="#1CFF00" filter="url(#erode)"></use>
</svg>
There is some clipping going on the dilate filter that can't seem to be resolved by increasing the filter region, but other than that it's pretty close to your illustrator rendering. Sure beats rendering server-side.
http://jsfiddle.net/5Qv5v/