I've searched all over the place for an answer to this question and found some resources that I thought may be useful, but ultimately did not lead me to an answer. Here are a few...
External SVG
Embed SVG
The issue
What I am trying to do it append an existing SVG element or string to a DIV on the page and then be able to apply various D3.js properties and attributes on it, so I can later manipulate and work with it (such as apply zooming abilities, etc.).
I was previously using jQuery SVG in which I did this:
var shapesRequest= '"<svg id="shapes" width="640" height="480" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svg="http://www.w3.org/2000/svg">' +
'<polygon id="svgSection_Floor" points="222.61,199.75,222.61,295.19,380.62,295.19,380.62,199.75,222.61,199.75,368.46,283.92,233.54,283.92,233.54,209.12,368.46,209.12,368.46,283.92" title="Floor" link="Primary" />' +
'<text x="302.61" y="289.4705">Floor</text>...and a lot more of the same...</svg>';
$('#svgtmp').append(shapesRequest);
$('#svgtmp #shapes text').attr('fill', 'white').attr('font-size', '9').attr('font-weight', 'bold');
$('#pnlShapes').width(640).height(480).svg().svg('get').add($('#svgtmp #shapes'));
So essentially what jQuery SVG and my code is doing is appending the SVG string to a temporary DIV, manipulating some properties within the SVG, then adding that SVG to its permanent location on the page.
This is exactly what I am trying to do with D3. Although I am not sure if I need to append to a temporary DIV first, as I have access to the SVG string and could just append/attach that to a DOM element with D3.
What I've tried...
var mapSvg = d3.select("#pnlShapes").append("svg").attr("width", svgWidth).attr("height", svgHeight);
d3.xml(shapesRequest, function (xmlData) {
var svgNode = xmlData.getElementsByTagName("svg")[0];
mapSvg.node().appendChild(svgNode);
});
But this isn't the correct approach, as I am not hitting an external API endpoint to retrieve the SVG - I already have it stored as a string.
I was also looking at doing something like this
d3.select("#pnlShapes").append('svg').append(shapesRequest).attr("width", svgWidth).attr("height", svgHeight);
but .append() when using D3 isn't used for this kind of appending.
Question
What is the proper way to append an existing SVG string to the DOM using D3 (also I'm using jQuery if that helps)?
My ultimate goal is to append this SVG and then allow it to be zoomable using this tutorial as my base.
You can use .html to insert a string into a DOM node
d3.select('#svgtmp').append('div').html('<svg...')
There may be an even more elegant way without the <div>.
Related
I've created a graph and am setting cells onto it via drag and drop. However when a drop or click happens I cannot find the clicked cell's html for use.
For instance I'm trying to get the ID of the dragged element within the mxEvent.CLICK.
graph.addListener(mxEvent.CLICK, function(sender, evt){
var cell = evt.properties.cell;
// cell.id is the cell id, not an id of html that's inside of it.
if(cell) {
var outer = cell.value.outerHTML; // I can see the HTML here encoded
}
})
UPDATES:
So I add the html to the cell with value.setAttribute('htmlLabel', label); when my draggable items are created. However htmlLabel does not seem to be a key anywhere in the evt object.
I've been working with MxGraph for a few months now on a large project, and while it's a great library, it's a bit cumbersome to use in some areas.
Getting at the HTML is one of those areas.
First off, you need to grab the objects of the event using the "MxGraph" tool set, as follows:
graph.addListener(mxEvent.CLICK, function(sender, event){
var mouseEvent = event.getProperty('event');
var selectedCell = event.getProperty('cell');
});
As you've already worked out, you can get the cell ID from the selected cell, via it's ID property, but other than that, there's not a great deal of other things on that object.
To go deeper, you need to drill down in the mouse event that you also get.
It's just a normal HTML event and as such has the regular properties on it you would expect to find.
In your case
mouseEvent.currentTarget.innerHTML
Will get you the HTML of the element you clicked on (You may need to explore different paths in your debugger, but that certainly works for me in the latest version of chrome)
One thing you need to be aware of though, MxGraph renders ALL of it's output using SVG and as far as I can see there is NO standard HTML ID attribute on the rendered tag, in fact none of the rendered graph elements appear to have ID's on them.
I hit this problem a week ago when I was trying to grab the output generated by MxGraph to do some image manipulation, and because I couldn't reliably grab a single image, I had to resort to using Base64 encoded strings and manipulating the cell stylesheets using MxGraph's API.
I used xml to create the diagram:
...
<mxCell id="start" value="Start" style="start" vertex="1" parent="1"><mxGeometry x="0" y="0" width="30" height="30" as="geometry"/></mxCell>
...
So I get the ID with this code:
graph.addListener(mxEvent.CLICK, function (sender, evt) {
var cell = evt.getProperty("cell");
var id = cell.id;
}
Version 3.9.11
in version 3.9.8
I tried to use the above code, but found error with (graph), with more search and try, I found the suitable is (mxGraph.prototype), so this code should be:
mxGraph.prototype.addListener(mxEvent.CLICK, function(sender, event){
var mouseEvent = event.getProperty('event');
var selectedCell = event.getProperty('cell');
});
I am using GWT 2.6.1 (+ GWT Graphics) and trying to draw SVG objects and display a tooltip on each one as follows:
Point point = new Point(100, 100);
Circle reading = new Circle(point.getX(), point.getY(), 3);
reading.setFillColor("#000");
final String infoTip = "120 bpm";
Element titleElement = DOM.createElement("title");
titleElement.setInnerText(infoTip);
reading.getElement().appendChild(titleElement);
Now although the SVG circle draws correctly, the tooltip doesn't work. I tried pasting the generated html into a static html file and that works fine, so it is something to do with dynamically adding the title element to the circle I guess but don't know how to solve it.
I have tried adding the title element inside a deferred action:
Scheduler.Get().ScheduleDeferred(...)
and I have tried adding it in the onLoad:
Event.sinkEvents(reading.getElement(), Event.ONLOAD);
Event.setEventListener(reading.getElement(), new EventListener(){});
but no luck.
DOM.createElement() will be creating elements in the HTML namespace. Your <title> element needs to be in the SVG namespace.
You will need to use native JS DOM methods to do that. See the answer by Jared Garst on this other question for how:
Using SVG in GWT
I am trying to override some JavaScript code to optimize an existing solution of which I do not have access to the original source code. It is a Liferay based portal with some portlets that render an SVG image. The SVG is used as a graphical navigation element which can be used to drill down into a technical service model. The loading and refreshing of the SVG image can be slow because of the way it has been implemented. The SVG disappears for a couple of seconds because the refresh actually does 2 requests to the webserver. At first the complete object tag is regenerated server side and re-inserted in the dom.
The response of the first request looks like this:
<div><object type="image/svg+xml" data="generateSvg.svg” width="100" height="300" name="svgId"> <param name="wmode" value="transparent"></object></div>
The actual request of the SVG starts by the time the first response text is inserted into the DOM and the original object tag is removed. Because the SVG images can get quite complex and some general slowness of the software the SVG disappears and returns by the time it is received and rendered by the web-browser.
I would like to skip the first step and make sure the image does not “blink” by refreshing the SVG asynchronously. Using jQuery I am able to override the original JavaScript code and get the url of the SVG and do a jQuery ajax request. I am also able to insert the results in the DOM but when I insert it as an inline SVG the image is rendered different then when it is embedded in an object tag.
Some parts of svg content elements are cut off and this does not happen when the SVG is added as an object tag with an external url.
I either would like to embed the SVG in an object tag and load its url asynchronously or find a fix for the rendering problems of the inline SVG. I cannot change the SVG code since it is part of the existing software. I there a way to add the asynchronous loaded data to an object tag without adding the url to the data attribute?
My javascript for adding the SVG looks like this:
indentifier.loadSvg = (function(){
if(this.svgUrl){
$.ajax({
url: this.svgUrl,
//enclose the right div in which the svg should be loaded as a context
context: $('#_layout_WAR_Portlets_INSTANCE_' + this.portalId + '_plugin'),
dataType: 'xml'
}).done(function(svgDoc) {
//construct an svg node and add it to the DOM
var svgNode = $('svg', svgDoc);
var docNode = document.adoptNode(svgNode[0]);
$(docNode).attr('width', '100%').attr('height', '300');
this.html(docNode);
});
}
});
I'm not sure if it's possible inside an object tag but an Iframe should do the trick.
You could try creating a new Object tag with javascript, loading the svg and replacing the old object with the new one.
It would look something like this:
var datasrc = "#";
// Create new object tag
var newobj = jQuery('<obj>', {
id: 'newobject',
data: datasrc,
type: 'image/svg+xml'
});
// Set parameters
jQuery('<param />', {
name: 'src',
value: datasrc
}).appendTo('#newobj');
jQuery('<param />', {
name: 'wmode',
value: 'transparent'
}).appendTo('#newobj');
// Dynamically load content and replace when done
newobj.load(datasrc,function(){
$('#objectwrapper').empty();
newobj.appendTo('#objectwrapper');
alert("succes"); // Just to check if this works!
});
JSFiddle: http://jsfiddle.net/cfERU/9/
The alert wil fire twice, I believe this is a JSFiddle thing.
I was hoping someone could tell me how to bring an entire set of elements toFront in Raphael JS.
I am working with some dynamically generated sets of raphael elements, each of which is stored in an array making it easier to interact with and target individual sets. When a user interacts with the various sets I would like to be able to bring the full set to the front of the application.
I have tried using .toFront() as outlined here: http://raphaeljs.com/reference.html#Element.toFront
However this hasn't worked properly. Each set contains either a circle or a circle sector, a text element and either an additional circle or an additional sector.
Any ideas? At any one point a user may have up to 40/50 of these sets so bringing sets to front is going to be a necessity regardless.
i've had similar issues with the toFront() function ...
well i'd say there are two ways to do it ...
The first one (not quite recommended) apply a loop on all the elements of the set to bring them to front but still they'd be ordered according to the sequence in the set .... not good approach ...
set.forEach(el){
e.toFront();}
The second one is really sexy :) my fav :) beautifully explained by mister phrogz here
I recommend using the 'g' tag ... simple javascript . let all your sets be enclosed in a g tag
var mySet = document.createElementNS("http://www.w3.org/2000/svg", "g");
mySet.setAttribute('id','myset');
//should look something like this
//use jQuery append or javaScript appendChild to add your set elements to this group
<g id = 'myset'>
<rect>
<circle>
//etc etc
</g>
now
mySet.parentNode.appendChild(mySet); //the key line
this would simply take it to the bottom of the svg container, hence being drawn last and shown at the top according to the painters model also mentioned by mister phrogz in the link .... you could use javascript to manipulate your own z-index if required...
Say you have this in your HTML:
<img src='example.svg' />
How would you access the contents ( ie. <rect>, <circle>, <ellipse>, etc.. ) of the example.svg via JavaScript?
It's not possible to get the DOM of a referenced svg from the img element.
If you use <object>, <embed> or <iframe> however then you can use .contentDocument (preferred) to get the referenced svg, or .getSVGDocument which may be more compatible with old svg plugins.
Here's an example showing how to get the DOM of a referenced svg.
I'm pretty sure this isn't possible. The external SVG is not part of the DOM in the way an inline SVG is, and I don't believe you can access the SVG DOM tree from the loading document.
What you can do is load the SVG as XML, using an AJAX request, and insert it into the DOM as an inline SVG you can then walk and manipulate. This D3 example demonstrates the technique. I think the d3.xml() function used here is more or less equivalent to jQuery's $.ajax() with dataType: "xml".
No, not possible but you can convert <img> to <svg> as mentioned HERE (same code available below) and you can access the nodes of svg in the DOM.
<script type="text/javascript">
$(document).ready(function() {
$('#img').each(function(){
var img = $(this);
var image_uri = img.attr('src');
$.get(image_uri, function(data) {
var svg = $(data).find('svg');
svg.removeAttr('xmlns:a');
img.replaceWith(svg);
}, 'xml');
});
});
</script>
<img id='img' src="my.svg" />
If you are using inlining of SVG into CSS url(data:...) or just using url(*.svg) background, you can embed them into DOM with svg-embed.
Support Chrome 11+, Safari 5+, FireFox 4+ and IE9+.
If you’re using a back end language that can go fetch the file and insert it, at least you can clean up the authoring experience. Like:
<?php echo file_get_contents("kiwi.svg"); ?>
A little PHP-specific thing here… it was demonstrated to me that file_get_contents() is the correct function here, not include() or include_once() as I have used before. Specifically because SVG sometimes is exported with that as the opening line, which will cause the PHP parser to choke on it.
(Information taken out of CSS-tricks)