I have some SVG elements and I would like to add the ability to resize using mouse.
It works great when element doesn't have any transforms (matrix, rotate, scale etc).
If I remove transform (transform="matrix(1.1,1.1,1,0,0,0)") from the rect element - it works great.
My understanding is that I should use matrix.inverse() function and transform it so I get mouse coordinates in the element space, but everything I tried didn't work for me.
I need to correctly calculate the x, y difference between start drag point and current point, but it doesn't work.
var rect = document.getElementById('rect');
var grip = document.getElementById('grip');
function moveGrip() {
var baseMatrix = rect.getCTM(),
x = rect.width.baseVal.value + rect.x.baseVal.value,
y = rect.height.baseVal.value + rect.y.baseVal.value;
grip.cx.baseVal.value = x * baseMatrix.a + y * baseMatrix.c + baseMatrix.e;
grip.cy.baseVal.value = x * baseMatrix.b + y * baseMatrix.d + baseMatrix.f;
}
grip.addEventListener('mousedown', onGripMouseDown);
document.addEventListener('mousemove', onDocMouseMove);
var startDragPoint, startDragSize;
function onGripMouseDown(evt) {
startDragSize = {w: rect.width.baseVal.value, h: rect.height.baseVal.value};
startDragPoint = {x: evt.clientX, y: evt.clientY};
}
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
rect.width.baseVal.value = startDragSize.w + evt.clientX - startDragPoint.x;
rect.height.baseVal.value = startDragSize.h + evt.clientY - startDragPoint.y;
moveGrip();
}
}
moveGrip();
<svg width="500" height="400">
<rect id="rect" x="20" y="20" width="200" height="100"
transform="matrix(1.1,1.1,1,0,0,0)"
style="fill:none;stroke: #3a2dd0; stroke-width: 2px;"></rect>
<g>
<circle id="grip" r="3" stroke-width="1" stroke="green"></circle>
</g>
</svg>
Probably, you'd need to apply the inverse matrix to delta-x,y as follows;
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
var invMatrix = rect.getCTM().inverse(),
x = evt.clientX - startDragPoint.x,
y = evt.clientY - startDragPoint.y;
rect.width.baseVal.value = startDragSize.w + x * invMatrix.a + y * invMatrix.c;
rect.height.baseVal.value = startDragSize.h + x * invMatrix.b + y * invMatrix.d;
moveGrip();
}
}
i have a problem, i convert the circles I drew to Autocad as high quality pdf, then I import this pdf with inkscape or corelDraw, my aim is to export this pdf as svg. No problems so far.
but circles in pdf appear as path not circle in svg. There are about 25 thousand flats, and showing it in svg as a path causes performance loss.
How can I fix path data as circle in svg?
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" id="path1932"/>
this is the path data for a circle as shown in the image, I want it
<circle cx="100" cy="100" r="75" /> to look like this, is it
possible?
Later edit I can use 3 cmd lines for the above one drawing but each case will need different approach. In the end the solution was ever so simple for this one example.
The compressed pdf is 102,186 bytes but expanded to text.svg (mu draw -o drawing7.svg drawing7.pdf) that is 4 times larger 409,446 bytes
given that this is only part of one array entry at a range of co-ordinates
<g id="Layer-1" data-name="ust-tribun$0$BK-seats">
<path transform="matrix(.0299676,.0013944,.0013944,-.0299676,694.69509,1952.665)" stroke-width="0" stroke-linecap="butt" stroke-miterlimit="10" stroke-linejoin="miter" fill="none" stroke="#808080" d="M 837.5 0 C 837.5 462.5385 462.5385 837.5 0 837.5 C -462.5385 837.5 -837.5 462.5385 -837.5 0 C -837.5 -462.5385 -462.5385 -837.5 0 -837.5 C 462.5385 -837.5 837.5 -462.5385 837.5 0 "/>
<path transform="matrix .......
the fix is to reverse the multiple parts of the symbol into the now rescaled source definition
<circle ........ r="837.5"/>
it will reduce the file to a more binary relavent size of 312,568 bytes but beware all those invisible lines with stroke-width="0" should be changed too (it's an all too common draughtsman error not to define their pen size).
Some background to the complexity of programmatically reversing vectors. Here I am illustrating using HTML in place of your DWG circle (so in one single hop) but we can see that on conversion to PDF the path instructions are translated the same as all other shape vectors into one of many paths. Your aim is to bulk reverse this process !
"WE" tend to think of two text letters as two instructions but in vectors that's at least \/\/ |β‘ where that last group is 3 separate paths, however for convenience the plain text in PDF is translated via ttf font look-up table (that themselves are like svg but SVG lettering fonts are generally not tolerated) One of those SVG letters β‘ in the picture is described as ο e.g. non reversible, thus using a big square O is not advisable.
So back to your question how to reverse a path to a circle will depend on the path being ALL ways the same string path format (or at least fairly consistent profiles).
So using text parsing method (you have still to show attempt) you need to pick out the arguments and back feed as parameters.
Use a typical test case of known size and position and determine the x and y values from the min and max then you can use the deltas for text substitution in <svg height="100" width="100"> the rest is then fairly simple since radius should be πΏx/2 and center should be minimum x + πΏx, minimum y + πΏy.
The fly in the ointment is how you translate your units so with no hard code to follow, in broad terms consider PDF origin like CAD is cartesian lower left and page units are usually converted using points as the measure so normally 1 point = 0.3527777777777778 mm
Your biggest problem is that the circle shown, inside the PDF will most likely be a series of vector chords. my sample has many small arcs but for your added sample see lower.
/GS0 gs
.24 0 0 -.24 -319.87684 182.32659 cm
1369.325 478.326 312.631 312.631 re
W*
n
3.126313 0 0 3.126313 1369.3252 478.32597 cm
90 50 m
90 51.31 89.936 52.617 89.807 53.921 c
89.679 55.224 89.487 56.519 89.231 57.804 c
88.976 59.088 88.658 60.358 88.278 61.611 c
...
87.457 35.903 87.897 37.135 88.278 38.389 c
88.658 39.642 88.976 40.912 89.231 42.196 c
89.487 43.481 89.679 44.776 89.807 46.079 c
89.936 47.383 90 48.69 90 50 c
h
0 0 0 rg
f
Q
on inspection of the sample it appears the circles are common for CAD output as 4 quarters so compare how Inkscape has reversed this PDF 4 arcs to SVG
q
-0.06113 -0.99813 0.99813 -0.06113 27455.5 34627.5 cm
837.5 0 m
837.5 462.53851 462.53851 837.5 0 837.5 c
-462.53851 837.5 -837.5 462.53851 -837.5 0 c
-837.5 -462.53851 -462.53851 -837.5 0 -837.5 c
462.53851 -837.5 837.5 -462.53851 837.5 0 c S
Q
so similar values to provided Inkscape's SVG conversion
<path d="M
837.5,0 C
837.5,462.53851 462.53851,837.5 0,837.5
-462.53851,837.5 -837.5,462.53851 -837.5,0 c
0,-462.53851 374.96149,-837.5 837.5,-837.5
462.53851,0 837.5,374.96149 837.5,837.5" id="path1932"/>
Short answer
don't embed SVG in PDF if at all possible nor expect to reverse easily. I think those arcs are going to be a problem in conversion it is do able but the svg modified in MS notepad looks good enough as it is.
It is just one line of code to transform the PDF to SVG with no changes.
One line of code to rectify the line thickness omission problem
But it would need many reams of code to convert 4-16 arcs into one circle.
Then reams more to do it for another drawing scale and layout.
The text manipulation could be done by whatever program you are familiar with, I use MS Notepad and CMD because I always find them reliable for hand editing. And cmd is good for basic text parsing, but for bulk programming you need MB of coding in a maths biased application.
anyway the text substitution is
d="M 837.5 0 C 837.5 462.5385 462.5385 837.5 0 837.5 C -862.5385 837.5 -837.5 462.5385 -837.5 0 C -837.5 -462.5385 -462.5385 -837.5 0 -837.5 C 462.5385 -837.5 837.5 -462.5385 837.5 0 "
using a simpler arc d="M -837.5,0 A 837.5,837.5 0 1,1 -837.5,0.001"
or better yet just replace with
r="837.5" and change the corresponding line start from <path to <circle but only for those array lines
CAD to PDF to SVG conversions will always introduce a lot of overhead
In other words you shouldn't expect a perfectly concise svg structure, since CAD formats (e.g DWG, DXF etc.) are quite different.
However, you might compare dedicated CAD to svg converters β some might actually produce a better output due to adaptive conversion techniques (e.g. recognizing primitives like circles).
Referring to your question, how to convert <path> elements to circles
Its <circle> replacement would be:
<circle cx="0" cy="0" r="837.5"/>
Due to its positioning within the svg user units space.
Svg primitives are like circles are not per se more performant than <path> elements, it rather depends on the the complexity of a path and also the total amount of svg elements.
Update: <use> elements and performance
As #Robert Longson pointed out:
use elements are much slower than raw paths because the UA has to
track changes and update all the use instances.
On the other hand, <use> elements help to significantly reduce the overall file size and thus improve loading times.
Solution 2: ungroup all elements to apply transformations
This solution is probably the compromise between file size (~200 KB β 50%) and rendering performance.
We need to ungroup all elements in inkscape (or another editor).
This way all <path> commands will be recalculated to the actual coordinates: See codepen example.
Benchmarks
unoptimized (~400 KB)
2. optimized: paths to use elements (~100 KB)
3. optimized: ungrouped; transformations applied (~200 KB)
Solution1: Vanilla js svg optimizer β replace <path> with <use>
const svg = document.querySelector("svg");
const paths = svg.querySelectorAll("path");
const ns = "http://www.w3.org/2000/svg";
// 0. add <def> if necessary
let defs = svg.querySelector("defs");
if (!defs) {
defs = document.createElementNS(ns, "defs");
svg.insertBefore(defs, svg.children[0]);
}
/**
* 1. inline styles to classes
*/
let styledEls = svg.querySelectorAll("[style]");
styleToClass(styledEls);
function styleToClass(els) {
//add <style> to parent svg if necessary
let css = svg.querySelector("style");
if (!css) {
css = document.createElementNS(ns, "style");
svg.insertBefore(css, svg.children[0]);
}
let styleObj = {};
els.forEach(function(el) {
let id = el.id;
let style = el.getAttribute("style");
style = style ? style.replaceAll(" ", "") : "";
let styleArr = style.split(";");
let stylesRounded = [];
//round nearby numeric values values
styleArr.forEach(function(prop) {
let propEl = prop.split(":");
let name = propEl[0];
let val = propEl[1];
if (parseFloat(val) == val) {
val = +parseFloat(val).toFixed(3);
}
stylesRounded.push(name + ":" + val);
});
style = removeCssProperties(stylesRounded.join(";"));
if (style) {
if (style in styleObj === false) {
styleObj[style] = {
count: 1,
ids: [id]
};
} else {
styleObj[style]["count"] += 1;
styleObj[style]["ids"].push(id);
}
}
});
let cssStr = "";
let classCount = 0;
for (style in styleObj) {
let css = style;
let className = "cl" + classCount;
cssStr += `.${className}{${style}}\n`;
classCount++;
let ids = styleObj[style]["ids"];
ids.forEach(function(id, i) {
let el = document.getElementById(id);
el.classList.add(className);
el.removeAttribute("style");
});
}
css.innerHTML = cssStr;
}
function removeCssProperties(css) {
css = css.replaceAll("; ", "");
let cssArr = css.split(";");
let cssFilter = [];
//default or propriatary properties
const remove = [
"stroke-miterlimit:10",
"stroke-dasharray:none",
"stroke-opacity:1",
"fill-opacity:1",
"-inkscape-font-specification:ArialMT",
"fill-rule:nonzero",
"fill:#000000",
"fill:black",
"stroke:none",
"writing-mode:lr-tb",
"stroke-linejoin:miter",
"font-variant:normal",
"font-weight:normal"
];
cssArr.forEach(function(prop) {
if (remove.indexOf(prop) === -1) {
cssFilter.push(prop);
}
});
cssFilter = cssFilter.join(";");
return cssFilter;
}
/**
* find repeated path "d" attributes
* replace them with <use> elements
*/
pathsToUse(paths);
function pathsToUse(paths) {
let useObj = {};
paths.forEach(function(path, i) {
let d = path.getAttribute("d").replaceAll(",", " ");
let id = path.id;
//add auto ids
if (!id) {
path.setAttribute("id", "pathID" + i);
}
//collect all d/pathdata
if (d in useObj === false) {
useObj[d] = {
count: 1,
ids: [id]
};
} else {
useObj[d]["count"] += 1;
useObj[d]["ids"].push(id);
}
});
//replace paths with <use> elements
let useDefs = "";
let useCount = 0;
for (d in useObj) {
let current = useObj[d];
let occurrences = current["ids"];
if (occurrences.length > 1) {
let useID = "p" + useCount;
//create def
useDefs += `<path id="${useID}" d="${d}" />\n`;
useCount++;
occurrences.forEach(function(id, i) {
let el = svg.getElementById(id);
let className = el.getAttribute("class");
let use = document.createElementNS(ns, "use");
use.setAttribute("href", "#" + useID);
use.setAttribute("xlink:href", "#" + useID);
use.classList.add(className);
el.replaceWith(use);
});
}
}
defs.insertAdjacentHTML("beforeend", useDefs);
}
// optimize d strings
let pathsOpt = svg.querySelectorAll("path");
pathsOpt.forEach(function(path) {
let d = path
.getAttribute("d")
.replace(/([a-zA-Z])(,)/g, "$1")
.replace(/( )([a-zA-Z])/g, "$2")
.replace(/([a-zA-Z])( )/g, "$1")
.replaceAll(" 0.", " .")
.replaceAll(",", " ")
.replaceAll(" -", "-");
path.setAttribute("d", d);
});
// optimize svg Markup
let svgMin = svg.outerHTML;
// minifying
svgMin = svgMin
.replaceAll("></path>", "/>")
.replaceAll("></use>", "/>")
.replace(/([ |\n|\r|\t])/g, " ")
.replace(/ +/g, " ")
.trim()
.replaceAll("> <", "><")
.replaceAll("><", ">\n<")
.replaceAll("} ", "}")
.replaceAll("}", "}\n");
//populate textarea
svgOpt.value = svgMin;
svg {
max-height: 90vh;
width: auto;
border: 1px solid #ccc
}
.cl0 {
stroke: green!important;
stroke-width: 10%!important;
}
<svg version="1.1" id="svg2" xml:space="preserve" viewBox="0 0 4224 3264" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="g8" transform="matrix(0,-0.04,-0.04,0,4160,3245.3333)">
<g id="g10">
<g id="g12" clip-path="url(#clipPath16)">
<g id="g18" transform="matrix(-0.04648,-0.99892,0.99892,-0.04648,16044.5,80843.5)">
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" style="fill:none;stroke:#808080;stroke-width:25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path20" />
</g>
<g id="g22" transform="matrix(-0.04715,-0.99889,0.99889,-0.04715,15943.5,78677.5)">
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" style="fill:none;stroke:#808080;stroke-width:24.9999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path24" />
</g>
<g id="g26" transform="matrix(-0.04782,-0.99886,0.99886,-0.04782,15840.5,76512.5)">
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" style="fill:none;stroke:#808080;stroke-width:24.9999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path28" />
</g>
<path d="m 41675,88799 -6933,313 -30,-649 6283,-284 -391,-8667 -6283,284 -30,-650 6934,-313 450,9966" style="fill:none;stroke:#dcdcdc;stroke-width:25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path2680" />
</g>
</g>
<g id="g2702">
<g id="g2704"><text transform="matrix(0,-1,-1,0,14155,86256)" style="font-variant:normal;font-weight:normal;font-size:2120.87px;font-family:Arial;-inkscape-font-specification:ArialMT;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none" id="text2712">
<tspan x="0 1179.2031" y="0" id="tspan2710">17</tspan>
</text>
</g>
</g>
<g id="g2714">
<g id="g2716" clip-path="url(#clipPath2720)">
<g id="g3830" transform="rotate(-90,31516,-5789.5)">
<path d="M 1507.5,0 C 1507.5,832.56927 832.56927,1507.5 0,1507.5 -832.56927,1507.5 -1507.5,832.56927 -1507.5,0 c 0,-832.56927 674.93073,-1507.5 1507.5,-1507.5 832.56927,0 1507.5,674.93073 1507.5,1507.5" style="fill:none;stroke:#000000;stroke-width:25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path3832" />
</g><text transform="matrix(-0.08733,0.99618,0.99618,0.08733,37824,24280)" style="font-variant:normal;font-weight:normal;font-size:1211.93px;font-family:Arial;-inkscape-font-specification:ArialMT;writing-mode:lr-tb;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" id="text3836">
<tspan x="0 875.01031 1548.8411 2222.6716" y="0" id="tspan3834">C129</tspan>
</text>
</g>
</g>
</g>
</svg>
<textarea name="svgOpt" id="svgOpt" style="width:100%; min-height:30em"></textarea>
The above snippet queries all <path> elements.
If there are recurring d path data attributes β we can save them to a reusable asset within the <defs> element and replace these instances with a referenced <use> element:
So each instance of
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" />
will be replaced by
<use href="#p0" />
defined in <defs> by
<path id="p0" d="M837.5 0C837.5 462.53851 462.53851 837.5 0 837.5-462.53851 837.5-837.5 462.53851-837.5 0c0-462.53851 374.96149-837.5 837.5-837.5 462.53851 0 837.5 374.96149 837.5 837.5" />
This replacements are achieved with a helper method pathsToUse()
function pathsToUse(paths) {
let useObj = {};
paths.forEach(function (path, i) {
let d = path.getAttribute("d").replaceAll(",", " ");
let id = path.id;
//add auto ids
if (!id) {
path.setAttribute("id", "pathID" + i);
}
//collect all// d/pathdata
if (d in useObj === false) {
useObj[d] = {
count: 1,
ids: [id]
};
} else {
useObj[d]["count"] += 1;
useObj[d]["ids"].push(id);
}
});
//replace paths with <use> elements
let useDefs = "";
let useCount = 0;
for (d in useObj) {
let current = useObj[d];
let occurrences = current["ids"];
if (occurrences.length > 1) {
let useID = "p" + useCount;
//create def
useDefs += `<path id="${useID}" d="${d}" />\n`;
useCount++;
occurrences.forEach(function (id, i) {
let el = svg.getElementById(id);
let className = el.getAttribute("class");
let use = document.createElementNS(ns, "use");
use.setAttribute("href", "#" + useID);
use.setAttribute("xlink:href", "#" + useID);
use.classList.add(className);
el.replaceWith(use);
});
}
}
defs.insertAdjacentHTML("beforeend", useDefs);
}
It loops through all <path> elements saving their d attributes to an data object also counting the number of occurrences/instances. In another loop we're replacing repeatedly used <path> instances with <use> elements.
We are saving the different d values as the object keys β this way we can easily increment an occurrence counter.
Besides we can save a lot of redundant code by replacing inline styles with class rules and removing useless or propriatary style properties like opacity:1 (default anyway) or -inkscape-font-specification:ArialMT (inkscape specific).
Other optimizations
reduce unnecessary groups (containing only one child node)
reduce unnecessary <tspan> elements that could be reduced to one single <text> element
concatenate single adjacent <path> elements
Complete optimization codepen example
(shrinking the filesize from ~400 KB to 100 KB)
Other optimization methods
Obviously you could also try Jake Archibald's SVGOMG
But always keep your original svg file as a backup, since SVGOMG tends to have quite aggressive optimization default settings.
So you need to compare different settings to get the best result β don't just look at the decreased file size!
Otherwise you might loose valuable markup/attributes such as ids or classes that you might need for your sites's js logic or even your css styling concept.
What about changing it to circle type in the SVG:
<circle cx="100" cy="100" r="75" />
I have following piece of code :
<svg>
<defs>
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "red"
fill = "blue" />
</defs>
<g transform = "translate(100,30)">
<use xlink:href = "#myRect" />
</g>
<g transform = "translate(100, 100) rotate(45 ? ?)">
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "green"
fill = "yellow" />
</g>
</svg>
When I translate rectangle without rotation, it is working fine. But when I rotate it, I wanted to rotate it around its center axis point. What should I need to pass to rotate attribute?
You would have to set the center as the center of the filled element. Like this:
svg .rotate {
transform-box: fill-box;
transform-origin: center;
transform: rotate(45deg);
}
You just need to add half the width/height of the rectangle to get its centre.
<g transform = "translate(100, 100) rotate(45 60 60)">
See transform documentation of the rotate function for more information.
The accepted answer works if you are drawing the rectangle starting at point (0,0) which was the OP case. However for me it was not!
Here is what worked for me:
To get the rectangle coordinates i used $('#rectID').getBBox()
method, should return [rect-height , rect-width , rect-y , rect x ]
The center point is ( rect-x + (rect-width/2) , rect-y + (rect-height/2) )
Here is a snippet i used on the browser console:
var coord = $('#elemID')[0].getBBox();
coord.x + (coord.width/2) +' '+ coord.y + (coord.height/2)
origin
x = x + width / 2
y = y + height / 2
here
x is 10
y is 10
width is 120
height is 120
<g transform = "translate(100, 100) rotate(45 70 70)">
I know this is an old post but if there are people out there who are looking make the values modifiable outside the group element
const centerX=100;
const centerY=100;
const firstAngle=45;
const secondAngle=60;
const thirdAngle =60;
<g transform ={`translate(${centerX}, ${centerY}) rotate(${firstAngle} ${secondAngle},
${thirdAngle})`}>
Nothing you just need to write the following code with the element in javascript:
element.rotate(angle Degree);