I want to auto generate an imagemap type of result for a raster image. I was able to supply this image as a PNG:
The original SVG for this looks like this:
<svg width="580" height="400" xmlns="http://www.w3.org/2000/svg">
<g>
<rect fill="#fff" id="canvas_background" height="402" width="582" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>
</g>
<g>
<rect id="svg_1" height="67" width="54" y="119.5" x="125.5" stroke-width="1.5" stroke="#000" fill="#fff"/>
<rect id="svg_3" height="67" width="54" y="119.5" x="180.5" stroke-width="1.5" stroke="#000" fill="#fff"/>
</g>
</svg>
Once I traced it using the library: https://github.com/jankovicsandras/imagetracerjs I get back path data like this:
<svg width="156" height="114" version="1.1" xmlns="http://www.w3.org/2000/svg" desc="Created with imagetracer.js version 1.2.3" >
<path fill="rgb(60,60,60)" stroke="rgb(60,60,60)" stroke-width="1" opacity="1" d="M 20 20 L 131 20 L 131 89 L 20 89 L 20 20 Z M 22 22 L 22 87 L 74 87 L 74 22 L 22 22 Z M 77 22 L 77 87 L 129 87 L 129 22 L 77 22 Z " />
<path fill="rgb(255,255,255)" stroke="rgb(255,255,255)" stroke-width="1" opacity="1" d="M 0 0 L 156 0 L 156 114 L 0 114 L 0 0 Z M 20 20 L 20 89 L 131 89 L 131 20 L 20 20 Z " />
<path fill="rgb(255,255,255)" stroke="rgb(255,255,255)" stroke-width="1" opacity="1" d="M 22 22 L 74 22 L 74 87 L 22 87 L 22 22 Z " />
<path fill="rgb(255,255,255)" stroke="rgb(255,255,255)" stroke-width="1" opacity="1" d="M 77 22 L 129 22 L 129 87 L 77 87 L 77 22 Z " />
</svg>
I would like to go back to the rect or polygon method so I can measure the area of each object so that if there were traced text I could exclude it / flatten it by saying it's total area is lower than allowed as a polygon / rect object.
Is there a way to convert the path data back to where I have 2 separate objects? I want to be able to overlay the results over the original image and allow targeting each square
I try to answer your question as best as I can, but there are multiple solutions here.
If you force imagetracerjs to use only straight line edges (with qtres = 0.00001) , then SVG path elements are polygons, their coordinates are defined in the d attribute: d="M 20 20 L 131 20 ... " in the first example. (More info: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d)
But 1 d attribute is often not just 1 polygon. The shape can include holes, and they are represented as smaller polygons. (More info: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule)
You can extract the d attributes with e.g. Regex, split them by the Z tags (which mean roughly: this shape ends, a hole shape begins) , then use the Gauss's area formula (https://en.wikipedia.org/wiki/Shoelace_formula) on the coordinates to get the areas. You might need to subtract the areas of the holes from the area of the parent shape.
You have 4 separate objects on your image, but some are not trivial. I try to "translate" your SVG result so you can decide which path to process.
ImageTracer output is sorted by colors first. The 1. path is the blackish frame around the two smaller rectangles, technically this is a blackish rectangle with two rectangle holes. The 2. path is white frame around the blackish frame, technically a white rectangle with a big rectangle hole. The 3. and 4. paths are the smaller white rectangles, which might be relevant for you.
There's an alternative to splitting and parsing the SVG string, but it's a bit undocumented, sadly. You can get a tracedata object first, process it, and optionally render it to SVG string later. (More info: https://github.com/jankovicsandras/imagetracerjs#examples )
var options = {qtres:0.0001};
ImageTracer.imageToTracedata(
'example.png',
function(tracedata){
// color layers loop
for(var i=0; i<tracedata.layers.length; i++){
// paths loop
for(var j=0; j<tracedata.layers[i].length; j++){
var thispath = tracedata.layers[i][j];
// processing this path if it's not a hole
if( !thispath.isholepath ){
// accumulating coordinates in [ [x,y] , [x,y] , ... ] polygon
var thispolygon = [];
thispolygon.push( [ thispath.segments[0].x1 , thispath.segments[0].y1 ] );
for(var k=0; k<thispath.segments.length; k++){ thispolygon.push( [ thispath.segments[k].x2 , thispath.segments[k].y2 ] ); }
// TODO: calculate thispolygon area with https://en.wikipedia.org/wiki/Shoelace_formula here
}
}// End of paths loop
}// End of color layers loop
},// End of imageToTracedata callback()
options
);// End of ImageTracer.imageToTracedata()
I hope these help. :)
Related
acknowledgement: reference
[SVG] How to close the path (between the 1st and the last point) and fill the area accordingly:
It's not a single path, so i can't close it with 'Z' at the end
It's not a single path, so I can't close it with 'Z' at the end
Join the paths first.
E.g. the sample from your link is originally
<path xmlns="http://www.w3.org/2000/svg" fill="none" stroke="blue" stroke-width="8" d="M 60 60 C 111.55555555555557 160 163.11111111111114 260 220 300"/>
<path xmlns="http://www.w3.org/2000/svg" fill="none" stroke="red" stroke-width="8" d="M 220 300 C 276.88888888888886 340 339.11111111111114 320 420 300"/>
But if you append the dstring of the second to the first and replace the second M (Move) with L (LineTo), you get this:
<path xmlns="http://www.w3.org/2000/svg" fill="cyan" stroke="red" stroke-width="8" d="M 60 60 C 111.55555555555557 160 163.11111111111114 260 220 300 L 220 300 C 276.88888888888886 340 339.11111111111114 320 420 300 Z"/>
I have also set fill=cyan.
Some of these drawn things are from the linked site and not in the code in my answer.
I'm using symfony to generate a simple highcharts graph, but wkhtmltoimage does not show the gridlines properly:
My knp_snappy.image config is the following:
options:
encoding: UTF-8
format: svg
width: 0
enable-smart-width: true
zoom: 3
and I have added the following options to the graph:
plotOptions: {
series: {
shadow: false,
animation:false,
enableMouseTracking: false
}
}
What am I doing wrong?
If i use wkhtmltopdf the output is correct..
Ok, i have done!
The problem is with the perfectly horizontal (or vertical) path, probably is related with old's webkit bug
Than my solution is to edit the horizontal (and vertical) line of the grid to make it not perfectly horizontal-vertical.
//fix bug of horizontal-vertical path (TODO: check all series)
yaxis = document.getElementsByClassName('highcharts-yaxis-grid')[0].childNodes;
for (i=0; i<yaxis.length; i++) {
if (yaxis[i].nodeName.toLowerCase() == 'path') {
d = yaxis[i].getAttribute('d').split(' ')[2];
yaxis[i].setAttribute('d', yaxis[i].getAttribute('d').replace(d, parseInt(d)+0.000001));
}
}
xaxis = document.getElementsByClassName('highcharts-xaxis-grid')[0].childNodes;
for (i=0; i<yaxis.length; i++) {
if (yaxis[i].nodeName.toLowerCase() == 'path') {
d = yaxis[i].getAttribute('d').split(' ')[1];
yaxis[i].setAttribute('d', yaxis[i].getAttribute('d').replace(d, parseInt(d)+0.000001));
}
}
For example, with the following horizontal path i have to edit the first occurrency of the number 264.5, 213.5, 163
<g class="highcharts-grid highcharts-yaxis-grid ">
<path d="M 77 264.5 L 413 264.5"></path>
<path d="M 77 213.5 L 413 213.5"></path>
<path d="M 77 163.5 L 413 163.5"></path>
</g>
to obtain the following non-horizontal lines
<g class="highcharts-grid highcharts-yaxis-grid ">
<path d="M 77 264.500001 L 413 264.5"></path>
<path d="M 77 213.500001 L 413 213.5"></path>
<path d="M 77 163.500001 L 413 163.5"></path>
</g>
I'm trying to add a point into a SVG-Path at a certain position on the path.
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 48 241 C 50 13 129 26 130 238 C 138 521 214 431 220 228 C 237 65 288 -18 316 240" stroke="black" fill="transparent"/>
</svg>
Furthermore, I would like to add a point in the Path (curve), so that the path (curve) doesn't deform.
The point is e.g. "C x1 y1 x2 y2 210 331".
My question:.
How can I determine the controls x1 y1 x2 y2?
E.g. "C 100 100 100 500 210 331" is wrong, because the curve will be deformed.
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path d= "M 48 241 C 50 13 129 26 130 238 C 100 100 100 500 210 331 C 210 331 210 331 220 228 C 237 65 288 -18 316 240" stroke="black" fill="transparent"/>
</svg>
In other words the point (x = 210, y = 331) is given. Wanted are controls "x1 y1 x2 y2".
See here for more explanation
(In order to add a graphic in “stackoverflow.com”, it is needed some points. But I haven’t enough points, therefore I inserted a link)
Thanks in advance.
I see the following alert in my chrome console:
SVG's SMIL animations (<animate>, <set>, etc.) are deprecated and will be removed. Please use CSS animations or Web animations instead.
So I want to replace SMIL with CSS.
There are a few things what I can do in CSS, but I don't know how to do this one.
My problem is that I animated d attribute in SMIL and I don't know that can I / how can I do it in CSS.
Here is my code:
svg{
position: fixed;
}
<svg width="500" height="500" viewBox="0 0 500 500">
<path id="rect" fill="lightblue" d="M10 10 L30 10 L30 30 L10 30 Z"/>
<animate xlink:href="#rect"
attributeName="d"
attributeType="XML"
values="M20 20 L30 20 L40 40 L20 40 Z;
M10 10 L30 10 L40 30 L10 40 Z;
M10 10 L30 10 L30 30 L10 30 Z;
M20 20 L30 20 L40 40 L20 40 Z"
begin="0s"
dur="2s"
repeatCount="indefinite"
/>
</svg>
Thank you!
P.S.: If it is not possible in CSS or faster in JavaScript, JavaScript would be good too.
I wrote a some JavaScript to interpolate between paths. It is very easy to use. You just add the script to your page and write the following lines:
var rect = new bigT('#rect');
rect.indefinite(true)
.easing("linear")
.duration(2000)
.setPathKeyframes(
"M20 20 L30 20 L40 40 L20 40 Z",
"M10 10 L30 10 L40 30 L10 40 Z",
"M10 10 L30 10 L30 30 L10 30 Z",
"M20 20 L30 20 L40 40 L20 40 Z");
rect.startAnimation();
You can download here bigT.js
Also, here is a preview with your svg: http://jsfiddle.net/r99fkndq/3/
Alternatively, you can use a more extensive library like D3.js.
I have drawn two path lines using SVG and I've saved these elements into two variables in my javascript code: 'Line1', and 'Line2', and I need to merge the two lines into one single path element. Is there a way to do so?
Are your paths defined relatively (small letters) or absolutely (capitals)? If absolute, joining two paths is trivial, just append the values of the d attribute. If you have two paths like this:
<path id="Line1" d="M50,50
A30,30 0 0,1 35,20
L100,100"
style="stroke:#660000; fill:none;"/>
<path id="Line2" d="M110,110
L100,0"
style="stroke:#660000; fill:none;"/>
Then this JavaScript code:
var Line1 = document.getElementById("Line1");
var Line2 = document.getElementById("Line2");
//Add paths together
Line1.setAttribute('d', Line1.getAttribute('d') + ' ' + Line2.getAttribute('d'));
//Remove unnecessary second path
Line2.parentNode.removeChild(Line2);
Will lead to you having a single path like this:
<path id="Line1" d="M50,50
A30,30 0 0,1 35,20
L100,100 M110,110
L100,0"
style="stroke:#660000; fill:none;"/>
Here's a jsFiddle, it works in Firefox 4 (needs an HTML5 parser so you can have inline SVG).
If your paths are relative then you're going to have to add something between the appended paths so that the second one starts in the correct place.
Concatenate d attributes
Usually, you can just concatenate the pathdata d attribute of several <path> elements to get a combined path.
Unfortunately, you might encounter some »bad practices« using M or m as interchangeable commands.
Common misconceptions about M or m:
M (moveto) can be absolute or relative.
Unlike the z (closepath) command (lowercase/uppercase – doesn't matter).
Relative m commands can be used used for compound paths like e.g the inner "hole" of the letter "o" referring to the previous command's end coordinate.
In fact, every first m or M command uses absolute coordinates – since there are no preceding points.
However, the first M command can be uppercase or lowercase – doesn't matter
(blame the specs)
Exception: the lowercase m command introduces a row of implicit relative l lineto commands. (But you can/should also avoid this)
Example 1: paths starting with (unnecessary) relative m command
svg{
border:1px solid #ccc;
width:25%;
}
path{
fill:#555;
}
<p>Seperate paths</p>
<svg viewBox="0 0 50 10">
<path id="path1" d="m 20 0 l 10 0 l 0 10 l -10 0z" />
<path id="path2" d="m 40 0 l 10 0 l 0 10 l -10 0z" />
<path id="path3" d="m 0 0 l 10 0 l 0 10 l -10 0z" />
</svg>
<p>Merged paths</p>
<svg viewBox="0 0 50 10">
<path id="pathConcat"
d="
m 20 0 l 10 0 l 0 10 l -10 0z
m 40 0 l 10 0 l 0 10 l -10 0z
m 0 0 l 10 0 l 0 10 l -10 0z
" />
</svg>
<p>Merged paths - fixed</p>
<svg viewBox="0 0 50 10">
<path id="pathConcat"
d="
M 20 0 l 10 0 l 0 10 l -10 0z
M 40 0 l 10 0 l 0 10 l -10 0z
M 0 0 l 10 0 l 0 10 l -10 0z
" />
</svg>
Fix: just replace each starting m with an absolute M
Example 2: m command for adjacent linetos
The exception are m commands followed by coordinates – used as a shorthand for succeeding l (relative linetos). (See also w3c specs.)
svg{
border:1px solid #ccc;
width:25%;
}
path{
fill:#555;
}
<p>Seperate paths</p>
<svg viewBox="0 0 50 10">
<path id="path1" d="m 20 0 10 0 0 10 -10 0z" />
<path id="path2" d="m 40 0 10 0 0 10 -10 0z" />
<path id="path3" d="m 0 0 10 0 0 10 -10 0z" />
</svg>
<p>Merged paths</p>
<svg viewBox="0 0 50 10">
<path id="pathConcat"
d="
m 20 0 10 0 0 10 -10 0z
m 40 0 10 0 0 10 -10 0z
m 0 0 10 0 0 10 -10 0z
" />
</svg>
<p>Merged paths - fixed</p>
<svg viewBox="0 0 50 10">
<path id="pathConcat"
d="
m 20 0 10 0 0 10 -10 0z
M 40 0 l 10 0 0 10 -10 0z
M 0 0 l 10 0 0 10 -10 0z
" />
</svg>
Fix: insert l commands
<path d="m 20 0 10 0 0 10 -10 0z" />
equals
<path d="M 20 0 l 10 0 l 0 10 l -10 0z" />
or
<path d="M 20 0 l 10 0 0 10 -10 0z" />
Example 3: fix pseudo relative m commands via getPathData()
Currently still a draft and not natively supported by major browser.
However you can use Jarek Foksa's polyfill..
getPathData() will return an array of command objects and normalize
repeated commands like this:
[
{type: 'm', values:[20, 0] },
{type: 'l', values:[10, 0]},
{type: 'l', values:[0, 10]},
{type: 'l', values:[-10, 0]}
]
function concatSimple(){
let d1= path1.getAttribute('d')
let d2= path2.getAttribute('d')
let d3= path3.getAttribute('d')
pathConcat.setAttribute('d', d1+d2)
}
function concatPathData(){
let pathData1= fixFirstM(path1.getPathData());
let pathData2= fixFirstM(path2.getPathData());
let pathData3= fixFirstM(path3.getPathData());
let pathDataConcat = pathData1.concat(pathData2).concat(pathData3);
pathConcat.setPathData(pathDataConcat);
}
// change first m to absolute M
function fixFirstM(pathData){
pathData[0].type='M';
return pathData;
}
svg{
border:1px solid #ccc;
width:25%;
}
path{
fill:#555;
}
<p><button onclick="concatSimple()">concat d simple</button>
<button onclick="concatPathData()">concat d pathData</button>
</p>
<svg viewBox="0 0 50 10">
<path id="path1" d="m 20 0 10 0 0 10 -10 0z" />
<path id="path2" d="m 40 0 10 0 0 10 -10 0z" />
<path id="path3" d="m 0 0 10 0 0 10 -10 0z" />
</svg>
<svg viewBox="0 0 50 10">
<path id="pathConcat" d="" />
</svg>
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill#1.0.4/path-data-polyfill.min.js"></script>
To fix the first relative m we can convert by changing the first command type
pathData[0].type='M';
Recommendation: only use relative m commands if they are actually relative:
if you need a shorthand for following l commands (like m 20 0 10 0 0 10 -10 0z)
for relative (subpath) starting points in compound paths – like the letter "o"
Actually merging shapes: removing overlapping shapes
If you need to merge shapes - paper.js has some powerful path operations like unite, subtract etc.
Explained here: "Merging two bezier-based shapes into one to create a new outline"