This is probably the best way, but I want to embed an external SVG and move it into position. I'm using the following code to load the external SVG image, but I can't seem to apply any transformation to the fragment.
var svg = new Snap('#svg');
Snap.load('logo.svg', function (fragment) {
svg.append(fragment);
});
It's not clear what you are really trying to transform, as you don't have any transform method in your snippet of code.
The normal route, is to append the fragment to a group element, and put the transform on that (as the svg element itself doesn't support transforms). So something like...
var svg = new Snap('#svg');
var g = svg.g();
Snap.load('logo.svg', function (fragment) {
g.append(fragment);
g.transform('t100,100');
});
Related
I have an svg that I'm trying to access and modify using d3.js. The svg file name is us-map.svg. I've included a reference to the svg in my html like this:
<object id="imgMap" data="us-map.svg" type="image/svg+xml">
</object>
I can select imgMap in my chrome watch panel like this:
var imgMap = d3.select('#imgMap')
However, I'm not able to select child elements. For example, my imgMap svg has several child <g> elements but the child elements are not returned with this function:
d3.select('#imgMap').selectAll('g')
Am I missing something here? I was thinking that d3 could be used to traverse and manipulate an existing svg?
I was thinking that d3 could be used to traverse and manipulate an existing SVG
This is pretty much what d3 does best. But when you write:
d3.select('#imgMap')
You are not selecting the SVG (unless you have an SVG with id = "imgMap", which is not your case). You're using an <object>. Thus, you have to write:
var mySVG = d3.select(document.getElementById("imgMap").contentDocument);
And then select your groups using mySVG.
var myGroups = mySVG.selectAll("g");
Have in mind that this selection only works after the object has been loaded.
Source: https://benfrain.com/selecting-svg-inside-tags-with-javascript/
EDIT:
As requested by the OP, this is a basic working demo: https://plnkr.co/edit/RJOznJROiqTpo5dm9M7L?p=preview
In this plunkr, "mysvg.svg" is an external file (in your code, you'll have to provide the correct path). The code finds the SVG:
var mySVG = d3.select(document.getElementById("imgMap").contentDocument);
And then selects the blue circle inside the SVG, moving it to the right:
var myCircle = mySVG.select("#blueCircle");
myCircle.transition().duration(2000).attr("cx", 180);
Pay attention to this: I set a setTimeout of 1000ms, just to make sure that the object is loaded before the code runs.
Im using snap.svg an snap.svg.zpd libraries. Same issue I have if I use snap.svg and jQuery panzoom library combination.
Code sample you can find here.
var mySvg = $("#plan")[0];
var snap = Snap("#plan");
//create an image
var imagePlan = snap.image("http://upload.wikimedia.org/wikipedia/commons/4/42/Cathedral_schematic_plan_fr_vectorial.svg", 10, 10, 900, 500);
var group = snap.group(imagePlan);
snap.zpd();
var pt = mySvg.createSVGPoint(); // create the point;
imagePlan.click(function(evt)
{
console.log(evt);
pt.x = evt.x;
pt.y = evt.y;
console.log(mySvg.getScreenCTM().inverse());
//When click, create a rect
var transformed = pt.matrixTransform(mySvg.getScreenCTM().inverse());
var rect1 = snap.rect(transformed.x, transformed.y, 40, 40);
group.add(rect1);
});
Problem is...if you click on initial svg it will add rectangle to the mouse position. If you pan/zoom image and then add rectangle it will be shiffted.
It looks like problem is in method mySvg.getScreenCTM().inverse(). Matrix returned is always same one, panning and zooming does not change it. It always use matrix from initialy rendered svg. However, if I inspect svg element, I can see that pann/zoom change transform matrix directly on element (image below).
Does anybody know how to fix this. My requirement is to be able to drag and drop elements outside svg into svg on any zoom scale or pan context, so I need transformation from mouse click point to svg offset coordinates. If you know any other approach or any other library combination that could done this, it would be ok for me.
Thanks in advance.
Problem is, the transform isn't in mySvg. Its on the 'g' group element thats inside the svg. Zpd will create a group to operate on as far as I know, so you want to look at that.
To hightlight this, take a look at
console.log(mySvg.firstElementChild.getScreenCTM().inverse());
In this case its the g element (there's more direct ways of accessing it, depending on whether you want to just work in js, or snap, or svg.js).
jsfiddle
Its not quite clear from your description where you want the rect (within the svg, separate or whatt) to go and at what scale etc though, and if you want it to be part of the zoom/panning, or static or whatever. So I'm not sure whether you need this or not.
I'm guessing you want something like this
var tpt = pt.matrixTransform( mySvg.firstElementChild.getScreenCTM().inverse() )
var rect1 = snap.rect(tpt.x, tpt.y, 40, 40);
I'm using d3 library to create a svg graphic. The problem I have is when I resize the window. The whole graphic resizes meaning that texts (legend and axis) resize as well, to the point where it's unreadable. I need it to keep the same size when resizing.
I've been searching online and I found this solution:
var resizeTracker;
// Counteracts all transforms applied above an element.
// Apply a translation to the element to have it remain at a local position
var unscale = function (el) {
var svg = el.ownerSVGElement;
var xf = el.scaleIndependentXForm;
if (!xf) {
// Keep a single transform matrix in the stack for fighting transformations
xf = el.scaleIndependentXForm = svg.createSVGTransform();
// Be sure to apply this transform after existing transforms (translate)
el.transform.baseVal.appendItem(xf);
}
var m = svg.getTransformToElement(el.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
};
[].forEach.call($("text"), unscale);
$(window).resize(function () {
if (resizeTracker) clearTimeout(resizeTracker);
resizeTracker = setTimeout(function () { [].forEach.call($("text"), unscale); }, 0);
});
And added it to my code, but it's not working. I debugged it and at this part of the code:
var xf = el.scaleIndependentXForm;
It always returns the same matrix: 1 0 0 1 0 0 and the text keeps resizing as does the rest of the svg elements instead of keeping static.
Could anyone help me, please?
Thanks in advance.
The same thing was happening to me with an SVG generated by SnapSVG until I noted that the example page on which this does work wraps its 'main' SVG tag in another SVG tag before using el.ownerSVGElement.ownerSVGElement rather than el.ownerSVGElement.
Wrapping my SVG in an 'empty' wrapper SVG (note style overflow:visible;) I had much better results!
Edit: oh, wait. Internet Explorer still isn't happy. Seems the author of the solution is aware...
I am looking for a way to create a circle with a gradient fill in Leaflet.
My approach so far is to define the fillColor of the circle as 'url(#gradient)' and add the Gradient definition manually via the following code:
function createGradient (map) {
// Get the SVG element from the overlayPane
var svg = map.getPanes().overlayPane.firstChild;
var doc = document.implementation.createDocument(null, null, null);
// Create def element
var svgDef = doc.createElement('defs');
// Create gradient and stops element
var svgGradient = doc.createElement("radialGradient");
var svgStart = doc.createElement('stop');
var svgStop = doc.createElement('stop');
// Set ID attribute of gradient
svgGradient.setAttribute('id', 'gradient');
// set stops and colors
svgStart.setAttribute('offset', '0%');
svgStop.setAttribute('offset', '100%');
svgStart.setAttribute('class', 'circle-start');
svgStop.setAttribute('class', 'circle-stop');
svgGradient.appendChild(svgStart);
svgGradient.appendChild(svgStop);
// Append blur element to filter element
svgDef.appendChild(svgGradient);
// Append filter element to SVG element
svg.appendChild(svgDef);
}
The funny thing is, that the gradient fill is not shown initially. However, if I go into devtools and remove the 'defs' block and add it again it the gradient fill is shown correctly.
Can anyone help me to get rid of this issue or alternatively another way to get a gradient fill?
You cannot create SVG elements using createElement, that's only for html elements. If you want to create an SVG element you must create an element in the SVG namespace using createElementNS i.e.
var svgDef = doc.createElementNS('http://www.w3.org/2000/svg', 'defs');
// Create gradient and stops element
var svgGradient = doc.createElementNS("http://www.w3.org/2000/svg", "radialGradient");
var svgStart = doc.createElementNS('http://www.w3.org/2000/svg', 'stop');
var svgStop = doc.createElementNS('http://www.w3.org/2000/svg', 'stop');
Using devtools reruns the html parser on the content which magically corrects the namespace.
Assume, I have some relatively complex SVG graphics as a set of files, for example, looking like these icons:
I want to use them in my D3-powered charts. So, I can go with <defs> and <use> tags and inject them as symbols. But I want to make them less solid monolithic assets, but more like fully active and editable graphic elements. I know I can manually import all paths from icons SVG code like this:
svg.append('path').attr('d', 'M7.5,5.809c-0.869,0-1.576-0.742-1.576-1.654c0-0.912,0.707-1.653,1.576-1.653 c0.87,0,1.577,0.742,1.577,1.653C9.077,5.067,8.369,5.809,7.5,5.809z')
But this doesn't seem to be a quick to run scenario as I need to build some script to convert icons to code like that or do it manually, but I want to have some simple workflow similar to just editing an icon Illustrator, saving, importing.
As a result, I want to have full control on the all the shapes and paths inside each icon.
How you think it can be done in the most straightforward and D3 way?
You can import your SVGs using d3.xml and then insert the xml directly into your document. In the example code below I create a g in a svg element and then insert an image into that:
<script>
var height = 500;
var width = 700;
var vis = d3.select("#vis").append("svg")
.attr("width", width).attr("height", height)
var g = vis.append("g").attr("id", "image");
d3.xml("drawing.svg", "image/svg+xml", function(xml) {
g.each(function() {
this.appendChild(xml.documentElement);
});
});
</script>
I guess you could also select separate icons from one SVG document containing multiple icons and insert each separately. I expect styling will be lost when the SVG document stores it's styling in a stylesheet and not directly in the elements.