Attempting to inject SVG elements with raw javascript. I can inspect and see the elements have been loaded, but dimensions end up being 0x0 instead of the size set on the element.
(function() {
// Constructor
this.Loading = function() {
this.svgNamespace = "http://www.w3.org/2000/svg";
var svg = createSvgObject("svg", "loaderSvg");
svg.setAttributeNS(null, "width", 200);
svg.setAttributeNS(null, "height", 200);
document.getElementById("possibleLoadingLocation").appendChild(svg);
var circle = createSvgObject("circle", "test");
circle.setAttribute("cx", 40);
circle.setAttribute("cy", 40);
circle.setAttribute("r", 30);
circle.setAttribute("stroke", "#000");
circle.setAttribute("stroke-width", 2);
circle.setAttribute("fill", "#f00");
svg.appendChild(circle);
}
function createSvgObject(elementType, elementId) {
if (elementType === undefined || elementType == null) {
throw "elementType is required to createSvgObject";
}
var element = document.createElementNS(this.svgNamespace, elementType);
if (elementId != null) {
element.setAttributeNS(null, "id", elementId);
}
return element
}
}());
var myLoading = new Loading();
Both the SVG object and the Circle object arrive with dimensions of 0x0. I have used css in the below example to force the svg to match the dimensions of the div containing it. I have also copied the exact svg code from the inspection into the body without css where I can see it sizing properly.
Thanks for any help.
https://jsfiddle.net/t92m5L8s/3/
Change the location of
this.svgNamespace = "http://www.w3.org/2000/svg";
to before:
this.Loading = function() {
Related
I am trying to manipulate elements inside svg (such as circle or path). I need to create them dynamically and be able to identify them.
I've stolen few functions from some online examples, and came up with following code (it should create circle inside svg on mouse click, and a text tag when the mouse is over the circle). However, it is not working as I expected. When mouse enters the element from left, the elementFromPoint(x, y).id returns valid ID "circle", but when the mouse enters the element from right, it returns it's parent ID "mySVG".
I was not able to find a proper solution anywhere, so any advice is welcomed - indeed, I will try to read the specs for svg and learn JS as best as I can, however, it is still a painful process for me to understand these things. Your time and suggestions are highly appreciated! Thanks. k
JS Fiddle:
https://jsfiddle.net/krisfiddle/2xc3tgdr/6/
The Code
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body, canvas, svg {position:absolute; margin:0; padding:0; border:none;}
svg {top:0; left:0;}
.circle:hover {opacity:0.5;}
</style>
</head>
<body>
<svg id="mySVG" xmlns:xlink="http://www.w3.org/1999/xlink" height="400" width="300" style="border: 1px solid black" onClick="circle()"></svg>
<script>
var w = window.innerWidth;
var h = window.innerHeight;
var x;
var y;
var color = undefined;
function handleMouseMove(event) {
var dot, eventDoc, doc, body, pageX, pageY;
event = event || window.event;
if (event.pageX == null && event.clientX != null) {
eventDoc = (event.target && event.target.ownerDocument) || document;
doc = eventDoc.documentElement;
body = eventDoc.body;
event.pageX = event.clientX +
(doc && doc.scrollLeft || body && body.scrollLeft || 0) -
(doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY +
(doc && doc.scrollTop || body && body.scrollTop || 0) -
(doc && doc.clientTop || body && body.clientTop || 0 );
}
x = event.pageX;
y = event.pageY;
}
document.onmousemove = handleMouseMove;
function circle() {
var myCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
myCircle.setAttribute('id', 'circleID');
myCircle.setAttribute('class', 'circle');
myCircle.setAttributeNS(null, "cx", x);
myCircle.setAttributeNS(null, "cy", y);
myCircle.setAttributeNS(null, "r", 50);
myCircle.setAttributeNS(null, "fill", "green");
myCircle.setAttributeNS(null, "stroke", "none");
myCircle.setAttributeNS(null, "onmouseover", "getIdXY()");
myCircle.setAttributeNS(null, "onmouseout", "deleteIdXY()");
document.getElementById("mySVG").appendChild(myCircle);
}
function getIdXY() {
var elementMouseIsOver = document.
var idMark = document.createElementNS("http://www.w3.org/2000/svg", "text");
idMark.setAttributeNS(null, "x", x);
idMark.setAttributeNS(null, "y", y);
idMark.setAttributeNS(null, "fill", "red");
idMark.setAttributeNS(null, "id", "text");
document.getElementById("mySVG").appendChild(idMark);
document.getElementById("text").innerHTML = elementMouseIsOver;
}
function deleteIdXY() {
var parent = document.getElementById("mySVG");
var child = document.getElementById("text");
parent.removeChild(child);
}
</script>
</body>
</html>
Well, for the time being, I implemented both sugestions by Robert and Marcin. For some reason the elementFromPoint() seems to be quite difficult for me to handle in this particular implementation, thus I gladly turned to "this obj" method. Indeed, storing coords in global variables and asigning unique ID to elements is a good idea. Thought the code is not very elegant it gave me a good intro into JS and dynamicaly created SVG. Many thans to Marcin and Robert. The result is sutisfactory for my purposes at the moment and can be observed at following fiddle in action:
http://jsfiddle.net/4uu1vbzz/2/
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<svg id="mySVG" xmlns:xlink="http://www.w3.org/1999/xlink" height="400" width="300" style="border: 1px solid black"></svg>
</body>
<script>
var x, y, recentX, recentY; // stores XY mouse coordinations
var circleID = 0; // used as increment to generate unique ID for each circle
var tagID = 0; // used as increment to generate unique ID for each circle's tag
var elementMouseIsOver = ""; // stores ID of the element recently under the pointer
// get mouse coords on mouse move
var mouseMoves = function(e) {
recentX = e.clientX;
recentY = e.clientY;
}
window.onload = function() {this.addEventListener('mousemove', mouseMoves);} //event handler for mousemove coords
//create circle within svg
var draw = function draw(e) {
x = e.clientX;
y = e.clientY;
circleID = circleID + 1; //increment the number for ID
var id = "circle" + circleID; //create string to pass the ID
//place circle with ID into svg
var myCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
myCircle.setAttribute('id', id);
myCircle.setAttribute('class', 'circle');
myCircle.setAttributeNS(null, "cx", x);
myCircle.setAttributeNS(null, "cy", y);
myCircle.setAttributeNS(null, "r", 50);
myCircle.setAttributeNS(null, "fill", "green");
myCircle.setAttributeNS(null, "stroke", "none");
myCircle.setAttributeNS(null, "onmouseover", "tagId(this)");
myCircle.setAttributeNS(null, "onmouseout", "deleteTag()");
document.getElementById("mySVG").appendChild(myCircle);
}
document.getElementById("mySVG").addEventListener('click', draw); //event handler for onclick action
//on mouseover get the ID of the element under the pointer and create an tag marking it
function tagId(obj) {
elementMouseIsOver = obj.id;
tagID = tagID + 1;
var id = "tag" + tagID;
var idMark = document.createElementNS("http://www.w3.org/2000/svg", "text");
idMark.setAttributeNS(null, "x", recentX);
idMark.setAttributeNS(null, "y", recentY);
idMark.setAttributeNS(null, "fill", "red");
idMark.setAttributeNS(null, "id", id);
document.getElementById("mySVG").appendChild(idMark);
document.getElementById(id).innerHTML = elementMouseIsOver;
}
//remove the tag when mouse leaves the element
function deleteTag() {
var id = "tag" + tagID;
var parent = document.getElementById("mySVG");
var child = document.getElementById(id);
parent.removeChild(child);
}
</script>
</html>
The problem is that your structure is all wrong.
Get the current coordinates of the click in your click handler, and keep some kind of counter outside of the handler (but accessible to it), which you increment every time you create a circle. That way, you can add that number to circleID.
I would like to be able to write formulas in latex in a fabric.js canvas, perhaps with MathJax.
http://fabricjs.com/fabric-intro-part-2#text
Is it possible to do that?
I would use the SVG renderer of MathJax to create a SVG file and then load that SVG into fabric.js. Here's an example:
<!-- It is important to use the SVG configuration of MathJax! -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_SVG">
</script>
<!-- You might need at least Fabric 1.4.6, because of a bug with data URLs -->
<script type="text/javascript" src="https://raw.githubusercontent.com/kangax/fabric.js/master/dist/fabric.js">
</script>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<script type="text/javascript">
// This function does the actual work
function tex(text, callback) {
// Create a script element with the LaTeX code
var div = document.createElement("div");
div.style.position = "absolute";
div.style.left = "-1000px";
document.body.appendChild(div);
var se = document.createElement("script");
se.setAttribute("type", "math/tex");
se.innerHTML = text;
div.appendChild(se);
MathJax.Hub.Process(se, function() {
// When processing is done, remove from the DOM
// Wait some time before doing tht because MathJax calls this function before
// actually displaying the output
var display = function() {
// Get the frame where the current Math is displayed
var frame = document.getElementById(se.id + "-Frame");
if(!frame) {
setTimeout(display, 500);
return;
}
// Load the SVG
var svg = frame.getElementsByTagName("svg")[0];
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("version", "1.1");
var height = svg.parentNode.offsetHeight;
var width = svg.parentNode.offsetWidth;
svg.setAttribute("height", height);
svg.setAttribute("width", width);
svg.removeAttribute("style");
// Embed the global MathJAX elements to it
var mathJaxGlobal = document.getElementById("MathJax_SVG_glyphs");
svg.appendChild(mathJaxGlobal.cloneNode(true));
// Create a data URL
var svgSource = '<?xml version="1.0" encoding="UTF-8"?>' + "\n" + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + "\n" + svg.outerHTML;
var retval = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgSource)));
// Remove the temporary elements
document.body.removeChild(div);
// Invoke the user callback
callback(retval, width, height);
};
setTimeout(display, 1000);
});
}
document.onload = function() {
var canvas = new fabric.Canvas("canvas");
tex("1+\\int_x^y e^x dx + \\ldots", function(svg, width, height) {
// Here you have a data url for a svg file
// Draw using FabricJS:
fabric.Image.fromURL(svg, function(img) {
img.height = height;
img.width = width;
canvas.add(img);
});
});
};
</script>
</body>
I've also put that into a JsFiddle: http://jsfiddle.net/3aHQc/39/
I programmatically create several svg-images by JavaScript. Images are lines.
function create(elem) { //function for create svg elem
var svg = "http://www.w3.org/2000/svg";
return document.createElementNS(svg, elem);
function set(elem, atrArr) { //function for set attributes svg elem
for (var i in atrArr) {
elem.setAttribute(i, atrArr[i])
}
}
function someFunc(valueWidth, valueHeight) {
var SVG = create('svg'); //canvas
var line = create('line'); //line
var width = valueWidth; //some value
var heigth = valueHeight; //some value
set(SVG, { //set canvas
version: '1.1',
width: width,
height: height
});
//set line
set(line, { x1: 0, x2: width, y1: 0, y2: height, style: 'stroke:rgb(0,0,0);stroke-width:1' });
SVG.style.position = 'absolute';
SVG.appendChild(line);
}
If height or width < 0.5, then line is drawn only in IE.
Can I draw line in other browsers without changing the size of the canvas?
This worked fine for me in Chrome, after correcting the syntax errors (height vs heigth), and appending the svg element to the body:
var SVG = create('svg'); //canvas
document.body.appendChild(SVG);
http://jsfiddle.net/4jRZT/5/
Has anyone tried using a svg to canvas library when creating d3.js visualizations? I've tried to use canvg.js and d3.js to convert svg to canvas from within an android 2.3 application webview, but when I call:
svg.selectAll(".axis")
.data(d3.range(angle.domain()[1]))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + angle(d) * 180 / Math.PI + ")"; })
.call(d3.svg.axis()
.scale(radius.copy().range([-5, -outerRadius]))
.ticks(5)
.orient("left"))
.append("text")
.attr("y",
function (d) {
if (window.innerWidth < 455){
console.log("innerWidth less than 455: ",window.innerWidth);
return -(window.innerHeight * .33);
}
else {
console.log("innerWidth greater than 455: ",window.innerWidth);
return -(window.innerHeight * .33);
}
})
.attr("dy", ".71em")
.attr("text-anchor", "middle")
.text(function(d, i) { return capitalMeta[i]; })
.attr("style","font-size:12px;");
I get the error: Uncaught TypeError: Cannot call method setProperty of null http://mbostock.github.com/d3/d3.js?2.5.0:1707
Would some sort of headless browser application, or a server side js parser work? Has anyone encountered this before?
Here's one way you could write your svg to canvas (and then save the result as a png or whatever):
// Create an export button
d3.select("body")
.append("button")
.html("Export")
.on("click",svgToCanvas);
var w = 100, // or whatever your svg width is
h = 100;
// Create the export function - this will just export
// the first svg element it finds
function svgToCanvas(){
// Select the first svg element
var svg = d3.select("svg")[0][0],
img = new Image(),
serializer = new XMLSerializer(),
svgStr = serializer.serializeToString(svg);
img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);
// You could also use the actual string without base64 encoding it:
//img.src = "data:image/svg+xml;utf8," + svgStr;
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = w;
canvas.height = h;
canvas.getContext("2d").drawImage(img,0,0,w,h);
// Now save as png or whatever
};
The answer by #ace is very good, however it doesn't handle the case of external CSS stylesheets. My example below will automatically style the generated image exactly how the original SVG looks, even if it's pulling styles form separate stylesheets.
// when called, will open a new tab with the SVG
// which can then be right-clicked and 'save as...'
function saveSVG(){
// get styles from all required stylesheets
// http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
var style = "\n";
var requiredSheets = ['phylogram_d3.css', 'open_sans.css']; // list of required CSS
for (var i=0; i<document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = sheet.rules;
if (rules) {
for (var j=0; j<rules.length; j++) {
style += (rules[j].cssText + '\n');
}
}
}
}
}
var svg = d3.select("svg"),
img = new Image(),
serializer = new XMLSerializer(),
// prepend style to svg
svg.insert('defs',":first-child")
d3.select("svg defs")
.append('style')
.attr('type','text/css')
.html(style);
// generate IMG in new tab
var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
window.open().document.write('<img src="' + img.src + '"/>');
};
And, to be complete, the button that calls the function:
// save button
d3.select('body')
.append("button")
.on("click",saveSVG)
.attr('class', 'btn btn-success')
Have you tried the same code on a browser supporting SVG to see if it's a problem with webview? Then try this example using canvg or this one using DOM serialization. For server-side rendering, you could start with this example for how to render it to canvas server-side using Node.js.
I've not tried a library, but have rendered a d3-produced SVG to a canvas following this post on MDN.
This code is a quick mish-mash of the MDN and some jQuery, that'll you'll need to tidy up, and it has no error- or platfrom-checking, but it works, and I hope it helps.
$(document.body).append(
'<canvas id="canvas" width="'+diameter+'" height="'+diameter+'"></canvas>'
);
// https://developer.mozilla.org/en/docs/HTML/Canvas/Drawing_DOM_objects_into_a_canvas
var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="' + el.attr('class') +'"'
+ ' width="' + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
alert('ok');
DOMURL.revokeObjectURL(url);
};
img.src = url;
I'm creating a jQuery plugin that creates Raphael objects on the fly, let's say you do...
$("div").draw({
type: 'circle',
radius: '10',
color: '#000'
})
And the plugin code (just as an example):
$.fn.draw = function( options ) {
//settings/options stuff
var width = $(this).width();
var height = $(this).height();
var widget = Raphael($(this)[0], width, height);
var c = widget.circle(...).attr({...})
//saving the raphael reference in the element itself
$(this).data('raphael', {
circle : c
})
}
But then I'd like to be able to update elements like this:
$("div").draw({
type: 'update',
radius: '20',
color: '#fff'
});
I can "rescue" the object doing $(this).data().raphael.circle, but then it refuses to animate, I know it's a raphael object because it even has the animate proto , but it yields a Uncaught TypeError: Cannot read property '0' of undefined).
I tried out your code, made some modifications, and $(this).data().raphael.circle does animate. This is what I did (I know it's not exactly the same as yours, but gives the gist)
$.fn.draw = function( options ) {
//settings/options stuff
if(options.type === 'draw') {
var width = $(this).width();
var height = $(this).height();
var widget = Raphael($(this)[0], width, height);
var c = widget.circle(100, 100, 50);
//saving the raphael reference in the element itself
$(this).data('raphael', {
circle : c
});
//$(this).data().raphael.circle.animate() does not work here
}
else if(options.type === 'update') {
$(this).data().raphael.circle.animate({cx: 200, cy: 200});
//But this works here
}
}
In this case, referencing the element with $(this).data().raphael.circle does work, but only in the else if. I'm not sure why.