Fix broken display of SVG using ipywidgets DOMWidget in Jupyter Notebook - javascript

Summary
Please see this example notebook, which isn't working as expected. The third cell in the notebook should show an SVG window containing a simple green box. Example output is shown in cell 4*.
Details
After running the notebook, a tooltip popup is displayed when I hover over the element in the console. Because of this popup I believe the SVG container and box (which is a PATH element) are definitely being created, they just aren't being displayed. The popup says they are being rendered at size 0x0.
How can I get this to work as expected, so that the contents of the SVG window show up in the output cell (cell #3)?
Code
Here is the cell by cell code for convenience.
Cell #1
import ipywidgets.widgets as widgets
from traitlets import Unicode
class Test(widgets.DOMWidget):
_view_name = Unicode('TestView').tag(sync=True)
_view_module = Unicode('test').tag(sync=True)
_view_module_version = Unicode('0.1.0').tag(sync=True)
Cell #2
%%javascript
require.undef('test');
define('test', ["#jupyter-widgets/base"], function(widgets) {
var TestView = widgets.DOMWidgetView.extend({
render: function() {
TestView.__super__.render.apply(this, arguments);
var svg = document.createElement('svg');
svg.innerHTML = '<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="2.0" opacity="0.6" d="M 0.0,0.0 L 50.0,0.0 L 50.0,50.0 L 0.0,50.0 L 0.0,0.0 z" />';
this.el.appendChild(svg);
console.log(svg); // when you hover over this line in the console, you can see the SVG has been created...
},
});
return {
TestView : TestView,
};
});
Cell #3
Test() # this cell should output a green box
Cell #4: Example Output
%%html
<svg>
<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="2.0" opacity="0.6" d="M 0.0,0.0 L 50.0,0.0 L 50.0,50.0 L 0.0,50.0 L 0.0,0.0 z" />
</svg>
* NOTE: clone and run notebook to show the expected output at the bottom.

Figured this out: for reasons I don't understand, you have to utilize document.createElementNS() function to add SVG, not document.createElement(). You also have to use svg.setAttributeNS() rather than svg.setAttribute().
Here's a fixed version of the notebook. Code:
%%javascript
require.undef('test');
define('test', ["#jupyter-widgets/base"], function(widgets) {
var TestView = widgets.DOMWidgetView.extend({
render: function() {
TestView.__super__.render.apply(this, arguments);
var xmlns = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(xmlns, "svg");
svg.setAttributeNS(null, "viewBox", "0 0 100 100");
svg.innerHTML = '<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="2.0" opacity="0.6" d="M 0.0,0.0 L 50.0,0.0 L 50.0,50.0 L 0.0,50.0 L 0.0,0.0 z" />';
this.el.appendChild(svg);
console.log(svg); // when you hover over this line in the console, you can see the SVG has been created...
},
});
return {
TestView : TestView,
};
});

Related

SVG : how to merge multiple lines to obtain single object

i have square shape made of 4 lines and as these are 4 different paths so am not able to get size of the shape for that am trying to merge these lines together so that i have square shape as single path and then i can get its size using getBBox() method :
square shape
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100%" height="100%" viewBox="0 -237.4911 302.46116 237.4911">
<g xmlns="http://www.w3.org/2000/svg" transform="matrix(1 0 0 -1 0 0)">
<path d= "M129.734598 192.711157V226.160594" stroke="red" stroke-width=".56693" id="1"/>
<path d= "M129.734598 192.711157H159.496439" stroke="red" stroke-width=".56693" id="2"/>
<path d= "M159.496439 192.711157V226.160594" stroke="red" stroke-width=".56693" id="3"/>
<path d= "M129.734598 226.160594H159.496439" stroke="red" stroke-width=".56693" id="4"/>
</g>
</svg>
so i try to merged them which is partially ok like this
M129.734598 192.711157 V226.160594 H159.496439 V226.160594 H159.496439
so any idea how to properly do it to get square shape as single path
Hint 1
Your first path
M 129.734598 192.711157 V 226.160594
is equivalent to
M 129.734598 192.711157 L 129.734598 226.160594
Your second path
M 129.734598 192.711157 H 159.496439
is equivalent to
M 129.734598 192.711157 L 159.496439 192.711157
Perhaps in this form, the sequence of moves might be more obvious.
Hint 2
In your first attempt you were close (I've rounded these values for more clarity)
M 129 192 V 226 H 159 V 226 H 159
Your last two path commands are not doing anything.
In order to complete the square, your third side (the second V) needs to return to the start Y position. And your fourth side (the second H) needs to return to the start X position.
Hope this helps, and is not too cryptic.

Routing onclick event from embedded SVG to higher level

I have an HTML file that embeds two different SVG files, like so:
<html>
<body>
<object id="svg0" data="histograms.svg" type="image/svg+xml"></object>
<object id="svg1" data="test.svg" type="image/svg+xml"></object>
</body>
</html>
Both SVG files are interactive, by adding a javascript function that is triggered by onclick, like such:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:ns1="http://www.w3.org/1999/xlink" height="172pt" version="1.1" viewBox="0 0 1209 172" width="1209pt">
<script type="text/ecmascript">
function choose(obj) {
var values = [0.08,0.77];
var names = [ "hist_1", "hist_2", "hist_3", "hist_4", "hist_5", "hist_6", "hist_7", "hist_8", "hist_9", "hist_10", "hist_11" ];
for ( var i=0; i<names.length; i++) {
var o = document.getElementById( names[i] );
o.style['opacity'] = values[0];
}
obj.style['opacity'] = values[1];
}
</script>
...
<g id="figure_1">
<g id="patch_1">
<path d=" M0 172.8 L1209.6 172.8 L1209.6 0 L0 0 z " style="fill:#ffffff;" />
</g>
<g id="axes_1">
<g cursor="pointer" id="hist_1" onclick="choose(this)">
<path d=" M20.835 70.52 L189.696 70.52 L189.696 12.96 L20.835 12.96 z " style="fill:#ffe6cc;" />
</g>
...
How can I have a click in one SVG file trigger javascript in the other SVG file? (Possibly via top level .html file as intermediate, if necessary?)
If you're writing code in test.svg then top gets you the containiner, so
var svg0 = top.document.getElementById("svg0");
would get you the object element from the container document.
Then
obj0Document = svg0.contentDocument;
if (obj0Document && obj0Document.defaultView)
obj0Window = obj0Document.defaultView;
else if (svg0.window)
obj0Window = svg0.window;
gets you the content's document and window.
accessing the SVG document's "window" allows you to access variables and functions defined in scripts in the SVG document.
e.g. obj0Window.choose(something)
Everything must have the same domain for this to work.

Get absolute coordinates of SVG path with Javascript

I am creating a program which converts a SVG file to my own format. I have created a web based application to do this. I use the default DOM parsing functionality of web browsers to iterate over the SVG contents.
With Javascript I can get a SVG path element using:
var path = document.getElementById("path3388");
I can get the path segments using:
var pathSegments = path.pathSegList
However these path segments are relative to whatever parent SVG element is defined. Transforms are not included in the path segment list.
Is there a way to get the absolute coordinates of this path as they are ultimately used when drawn on the screen?
Example: say I got the following SVG snippet:
<g transform="translate(100, 100)">
<g transform="translate(50, 50)">
<path d="M 0,0 10,0 10,10"></path>
</g>
</g>
What I want is to retrieve is the coordinates of the path with the transforms of the two g elements applied. In this case the coordinates of the path should be:
[150,150], [160, 150], [160, 160]
You want is to do something like this to each path segment coordinate...
var root = document.getElementById("root");
var path = document.getElementById("path");
var point = root.createSVGPoint();
point.x = 0; // replace this with the x co-ordinate of the path segment
point.y = 0; // replace this with the y co-ordinate of the path segment
var matrix = path.getTransformToElement(root);
var position = point.matrixTransform(matrix);
alert(position.x + ", " + position.y);
<svg id="root">
<g transform="translate(100, 100)">
<g transform="translate(50, 50)">
<path id="path" d="M 0,0 10,0 10,10"></path>
</g>
</g>
</svg>
If you find that there's no getTransformToElement function any more since it's been removed in SVG 2 then this polyfill will restore that missing method.
path.getTransformToElement() is no longer supported in Chrome as of v48.
A slightly simpler method might entail...
const path = document.getElementById("path");
const pathBBox = path.getBBox();
console.log(pathBBox.x, pathBBox.y);

Replace color in Svg files with Javascript

I have a lot of Svgs files, and I want to replace one specific color by an other.
I've not path id and set ids in over 600 files is too long.
All the files have the same colors as the two in the example. I would like to select all elements with the color #EB1A21 and replace them with a variable containing another color. My svgs files are embed in object html tags.
I don't want save results in external files because the new colors are set in an input type=text by users, so I can't save all new coloured Svg file. I want them to be "dynamically" changed
The format of my file is like this
<svg>
<g>
<path style="fill:#EB1A21;" d="....."/>
<path style="fill:#292B87;" d="....."/>
<path style="fill:#EB1A21;" d="....."/>
</g>
</svg>
and I want to show files like this :
<svg>
<g>
<path style="fill:#color1;" d="....."/>
<path style="fill:#color2;" d="....."/>`
<path style="fill:#color1;" d="....."/>
</g>
</svg>
Ps: sorry for my language i'm a french guy :)
Thanks in advance..
----SOLUTION----
<script type="text/javascript">
for (i in mySvgFiles){
var curImage = escape(mySvgFiles[i]);
$("#contents").append('<object data="file.svg" type="image/svg+xml" onload="selectObj(this);"><object/ >');
}
function selectObj(my_object){
var colorChoice1 = "#FF0000"
var colorChoice2 = "#00FF00"
var svgDoc = my_object.contentDocument;
var svgRootemp = svgDoc.documentElement;
colorize(svgRootemp, colorChoice1, colorChoice2);
}
function colorize(svgRoot, color1, color2){
$("[style='fill:#292B87;']",svgRoot).css("fill", color1);
$("[style='fill:#EB1A21;']",svgRoot).css("fill", color2);
}
</script>
<div id="contents"></div>
If you have questions about this ask me :)
var color1 = "#00FF00";
var color2 = "#FF0000";
$(document).ready(function () {
$("path").each(function () {
var color = $(this).css("fill");
if (color == "rgb(235, 26, 33)") {
$(this).css("fill", color1);
} else if (color == "rgb(41, 43, 135)") {
$(this).css("fill", color2);
}
});
});

Create SVG anchor element programmatically?

How do I create an SVG anchor through JavaScript? Please see relevant section and an example from spec. How do I convert this example to JavaScript (basically, how to dynamically generate the container element a so that when I click the ellipse, it navigates away.
<?xml version="1.0"?>
<svg width="5cm" height="3cm" viewBox="0 0 5 3" version="1.2" baseProfile="tiny"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Example 17_01</title>
<desc>A simple link on an ellipse.</desc>
<rect x=".01" y=".01" width="4.98" height="2.98"
fill="none" stroke="blue" stroke-width=".03"/>
<a xlink:href="http://www.w3.org/">
<ellipse cx="2.5" cy="1.5" rx="2" ry="1"
fill="red" />
</a>
</svg>
This is just basic DOM:
var xlinkNS="http://www.w3.org/1999/xlink", svgNS="http://www.w3.org/2000/svg";
var a = document.createElementNS(svgNS, "a");
a.setAttributeNS(xlinkNS,"href","http://www.w3.org/");
var ellipse = document.createElementNS(svgNS, "ellipse");
ellipse.setAttributeNS(null,"cx","2.5");
ellipse.setAttributeNS(null,"cy","1.5");
ellipse.setAttributeNS(null,"rx","2");
ellipse.setAttributeNS(null,"ry","1");
ellipse.setAttributeNS(null,"fill","red");
a.appendChild(ellipse);
document.documentElement.appendChild(a);
Using my function below, it's as easy as this:
// Find the first SVG element
var svg = document.getElementsByTagName('svg')[0];
var a = createOn(svg,'a',{'xlink:href':'http://www.w3.org/'});
createOn(a,'ellipse',{cx:2.5,cy:1.5,rx:1,ry:1,fill:'red'});
function createOn(root,name,attrs,text){
var doc = root.ownerDocument,
svg = root.ownerSVGElement || root; // In case the root _is_ the <svg>
var svgNS = svg.getAttribute('xmlns');
var el = doc.createElementNS(svgNS,name);
for (var attr in attrs){
if (!attrs.hasOwnProperty(attr)) continue;
var parts = attr.split(':');
if (parts[1]) el.setAttributeNS(
svg.getAttribute('xmlns:'+parts[0]),parts[1],attrs[attr]
);
else el.setAttributeNS(null,attr,attrs[attr]);
}
if (text) el.appendChild(document.createTextNode(text));
return root.appendChild(el);
}
If you already have the ellipse and want to wrap it, then create the 'a' element and:
// Get a reference to the ellipse however you like
var ellipse = document.getElementsByTagName('ellipse')[0];
// Put the anchor node immediately preceding the ellipse
ellipse.parentNode.insertBefore(a,ellipse);
// Move the ellipse to be a child of the anchor
a.appendChild(ellipse);

Categories