I have a cookie, called 'plan' which contains a string value representing a RaphaelJS canvas and some objects within it as SVG. The exact paths etc may vary, but here's typlically what's being saved to the cookie:
<svg height="100%" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" style="overflow: hidden; position: relative;"><desc>Created with Raphaël 2.0.1</desc><defs/><path style="" fill="none" stroke="#d9e026" d="M153,74L384,74L384,304L0,304L0,150L153,150L153,74"/><path style="" fill="#333333" stroke="#d9e026" d="M160,160L160,220L220,220A60,60,0,0,0,160,160" transform="matrix(1, 0, 0, 1, -103, 84)"/><rect x="121.53846153846155" y="158.5" width="76.92307692307692" height="3" r="0" rx="0" ry="0" fill="#333333" stroke="#d9e026" style="" transform="matrix(1, 0, 0, 1, -89, -9)"/><rect x="83.0769230769231" y="156" width="76.92307692307692" height="5" r="0" rx="0" ry="0" fill="#333333" stroke="#d9e026" style="" transform="matrix(0, -1, 1, 0, 223.038, 364.039)"/></svg>
Now I need to be able to pass this data into a new raphaeljs object on a new page and add some more paths etc into it.
Can anyone suggest how to do this as I'm struggling to know where to start?
Thanks!
Theres a spiffing new function in Raphael 2 which also simplifies these tasks
Paper.add
http://www.irunmywebsite.com/raphael/additionalhelp.php?v=2&q=paper.add
Looking into this in a lot more detail, it turns out not to be quite so simple. The SVG code is only available on newer browsers (IE8+) so this approach won't work in IE7 or less. Handily though, the Raphael object does store the required data to be able to use before it hits the DOM. There's a very handy plug-in I found to serialize the Raphael data then de-serialize on output. Although I'm having a few issues with the deserialization part, in theory this should be the solution: https://github.com/jspies/raphael.serialize.
So that plug-in essentially allows me to do this:
Raphael canvas > serialized JSON object > String > Cookie > String > serialized JSON object > Raphael canvas.
Related
I'm developing a prototype to mock this tool and provide minimal functionality like
Drag-n-Drop simple objects
Connect related objects via paths
Generate JSON from this structure
I opted SVG and Snap.svg framework (had hard time in deciding between D3 and Snap.svg but ended up with latter just because it is latest and successor of Raphael) to implement it. I stuck with below during implementation
Question 1: How to drag objects between different SVG elements? Try dragging elements in this Fiddle, objects were hidden when dragged outside of its parent dimension. Fiddle snippet below for your perusal.
HTML/SVG:
<div class="stencil">
<svg id="stencil" height="300" version="1.1" width="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
</svg>
</div>
<div class="paper">
<div class="paper__scroller">
<svg id="paper" height="1000" version="1.1" width="1000" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
</svg>
</div>
</div>
Javascript:
(function(){
var pap = Snap("#paper"),
stencil = Snap("#stencil"),
cir = stencil.circle(30, 50, 20),
rec = stencil.rect(70, 33, 35, 35);
stencilElements = stencil.group(rec, cir);
stencilElements.attr({
fill: "#f00",
stroke: "#000",
strokeWidth: 2,
"fill-opacity": 0.5
});
cir.clone().drag();
rec.clone().drag();
}());
Question 2: How to connect related objects via paths that adjust/moves accordingly when the object is dragged?
Question 3: How to convert the generated graph/diagram to JSON structure?
Really appreciate any reference or hints in implementing this.
I'm creating a dom structure which includes an SVG element:
<div data-bind="with: searchable_select.f0000001" style="verticalAlign: top">
<input data-bind="value: select_filter_value, valueUpdate: 'afterkeydown', event: { change: change_filter_, blur: blur_filter_ }" style="display: none">
<div>
<select data-bind="options: select_list, value: working_value, event: { change: change_selector_ }, optionsText: 'label', optionsValue: 'value'" style="display: inline-block; maxWidth: 150px">
<option value="person_full_name_asc">Member Full Name (A-Z)</option>
<option value="person_full_name_desc">Member Full Name (Z-A)</option>
</select>
<svg style="display: inline-block; verticalAlign: middle" width="18" height="18" xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<circle cx="6" cy="6" r="5" fill="#AAAAAA" stroke="#000000" stroke-width="2"></circle>
<path fill="#AAAAAA" stroke="#000000" stroke-width="2" d="M10,10 L17,17"></path>
</g>
</svg>
</div>
</div>
The svg element does not actually display. When I find the svg element in Chrome developer tools, both width and height show as zero.
I've tried removing the style attribute. I've tried setting the width and height in the style attribute.
I've copied the svg to a separate HTML file:
<html>
<head><title>maggen</title></head>
<body>
<svg style="display: inline-block; verticalAlign: middle" width="18" height="18" xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<circle cx="6" cy="6" r="5" fill="#AAAAAA" stroke="#000000" stroke-width="2"></circle>
<path fill="#AAAAAA" stroke="#000000" stroke-width="2" d="M10,10 L17,17"></path>
</g>
</svg>
</body>
</html>
Where it displays fine.
If I wrap the svg element in a div, the svg element shows up. In fact, if I edit the HTML in Chrome developer tools it will show up.
So, yeah, why? I've done a search on Google for this, but either it isn't there or (more likely) my Google Fu is not up to the task. I mean, sure, I can wrap it in a div - and maybe that's the right thing to do - but I'd rather not because then I'd need to wrap other stuff in divs and the dom will start to get cluttered.
EDIT: On a hunch I tried editing viewport attribute into the SVG element in Chrome tools. Voila! The element is visible! Expect when I included the viewport attribute when creating the document, it's not visible. So I tried just adding a random attribute in Chrome tools to the SVG element. Voila! The Element is visible! So, I thought, the problem is specific to Chrome and tried running it in Firefox...
...where the element doesn't show up.
EDIT: Great, so wrapping it in a div is not guaranteed to make it show up. But doing an "edit as HTML" in Chrome developer tools does make it show up.
EDIT: Well, I've gotten it to work correctly and, yes, it turns out to be a function of the Javascript creating the DOM elements. There's a lot of stuff in the code, but I can boil it down to this:
This code works (createElement creates a tag and sets attributes based on passed in parameters):
var div = this.createElement(
'div',
element_name + '_div',
null, style_options, null, null
);
div.innerHTML = [
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width=' + width + ' height=' + height + '>',
svg_xml.join(''),
'</svg>'
].join('');
if (parent) parent.appendChild(div);
return div;
This code doesn't:
var svg = this.createElement('svg', element_name, null, style_options, classlist, {
viewport: '0 0 ' + width + ' ' + height,
version: "1.1",
xmlns: "http://www.w3.org/2000/svg",
width: width,
height: height
});
svg.innerHTML = svg_xml.join('');
if (parent) parent.appendChild(svg);
return svg;
So, sure, I've got something working now, but I don't understand why. Or more to the point, I don't understand why one way works and the other doesn't. I have a couple of guesses, but they are just wild guesses, really.
"I've got something working now, but I don't understand why."
The issue is that the methods you were using do not create an SVG element in the SVG namespace. The xmlns attribute only affects the behaviour of an XML parser, not of DOM methods.
I'm not sure which library you're using for the this.createElement() method with multiple parameters. However, I suspect it probably starts by calling the basic document.createElement(tagName) method. This is what MDN says about the standard DOM createElement method:
In an HTML document creates the specified HTML element or HTMLUnknownElement if the element is not known. ... In other documents creates an element with a null namespaceURI.
In other words, because you're (presumably, indirectly) calling createElement on an HTML document, it always creates an HTML element. An HTML element with tag name "svg" is just treated as an unknown span-type element.
In contrast, using div.innerHTML to pass a markup string creates the SVG element correctly because it invokes the HTML5 parser to figure out what type of element to create. The namespace is determined using the same rules as when parsing markup from a file. Editing the HTML in the Chrome developer tools has the same effect.
Sidenote: Avoid calling .innerHTML on an SVG element. Some browsers support it, but it's not part of the specs. You're not getting an error because your svg variable is actually an instance of HTMLUnknownElement. Passing SVG code to the innerHTML method of a parent <div> usually works, although there are some bugs with SMIL animation. As #Robert Longson says in the comments, you can use the DOMParser object to parse either HTML or XML code into a document.
The other way to dynamically create an SVG element is to use document.createElementNS(namespaceURI, tagName). You'll also have to use this method to create all the child elements of the SVG. Once they are created, you may be able to set attributes, styles, and classes using your library methods. (But you haven't specified what library you're using, so I'm not sure.)
I'm creating a specific implementation for a d3.js graph, and it seems that when creating an SVG document dynamically, linearGradients are not working on Webkit browsers (tested only on Chrome, of the Webkit family), while Gecko is showing the expected behavior.
How do I conclude it has to do with dynamic generation, or d3? I tried copying the resulted document into an empty page, and the gradient came to life.
The d3 code initializes the document first:
// in construction:
// ...
svg = d3.select(el)
.append('svg:svg')
.attr('width', width)
.attr('height', height),
defs = svg.append('svg:defs'),
linearGradient1 = defs.append('svg:linearGradient')
.attr('id', 'linear-gradient-1')
.attr('x1', 0)
.attr('y1', 1)
.attr('x2', 6.123233995736766e-17)
.attr('y2', 0),
linearGradient1Stop1 = linearGradient1.append('svg:stop')
.attr('offset', '0%')
.attr('stop-color', '#e3e5e8'),
// several other stops here ...
… than refreshes the renderer, e.g. after adding "nodes" to the graph data struct, on demand (note: selfRef.node is simply a handle to the d3 selector of all the nodes):
// called e.g. when adding new nodes:
refresh: function() {
// ...
// add new nodes
var g = selfRef.node.enter().append('svg:g').attr('class', 'node');
g.append('svg:rect')
.attr('width', 144)
.attr('height', 42)
.attr('rx', 3)
.attr('ry', 3)
.attr('fill', 'url(#linear-gradient-1)')
.style('stroke', '#4d4d4d')
.style('stroke-width', 1)
// ...
}
Here's the generated document, it's nothing special:
<svg width="1889" height="400">
<defs>
<linearGradient id="linear-gradient-1" x1="0" y1="1" x2="6.123233995736766e-17" y2="0">
<stop offset="0%" stop-color="#e3e5e8"></stop>
<stop offset="11%" stop-color="#e6e8ec"></stop>
<stop offset="59%" stop-color="#eff2fa"></stop>
<stop offset="100%" stop-opacity="0.6" stop-color="#f2f6ff"></stop>
</linearGradient>
</defs>
<g>
<g class="node"
transform="translate(1113.425033222223,312.1412317958371)">
<rect width="144" height="42" rx="3" ry="3"
fill="url(#linear-gradient-1)"
style="stroke: #4d4d4d; stroke-width: 1px;"></rect>
<!-- some other shapes of the node... -->
</g>
<!-- etc. etc., some more node groups here... -->
</g>
</svg>
Things I have tried
Pulling the element referencing the gradient outside any group.
Adding version declaration to the SVG with varying values (maybe it only appears on 1.1?)
Wrapping the reference in quotes (i.e. fill="url('#linear-gradient-1')") or omitting the dashes from the id, thinking Webkit is less lenient here.
Note
Robert Longson mentioned that case matters in this post, and, weird enough, it seems that when pasting the document in an empty page (in the Chrome's dev-tools), the linear gradient element transforms to camel-case format (though it doesn't show in the DOM view, all is lowercase there). I discovered this after diffing the results of my d3 generated code and the pasted static document. What's up with that?
TL;DR
How come generating dynamic SVG gradients don't work when running in Chrome, and how to fix this?
Seems like the Safari developers marked the bug as works for me, based on it working in xhtml. For Safari it seems you'd have to serve your webpages with a mime type of application/xhtml+xml or petition the Safari developers to reopen the bug. FWIW it seems like a bug to me.
Is it possible to select and alter elements in an embedded (external) SVG , created in Adobe Illustrator?
html:
<object data="circles.svg" type="image/svg+xml" id="circles"></object>
circles.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="100px" height="100px" >
<circle id="c_red" fill="#A00" stroke="#000" cx="40" cy="40" r="40"/>
<circle id="c_grn" fill="#0A0" stroke="#000" cx="60" cy="60" r="40"/>
</svg>
d3 code:
<script>
var my_circles = d3.select("#circles svg").selectAll("circles");
my_circles.attr("fill", "black");
</script>
Otherwise, I'm open to other ways of doing this. For example, something like this might work to select (which does indeed locate the SVG):
var svg = document.getElementById('circles');
But how to then parse and alter in D3?
Bonus question: best way to debug D3 selectors?
This is actually a nasty case, because you can't use DOM selectors directly on embedded documents. In principle, the selector you need is "#circles > circle", but this won't work in this case. So you need something rather ugly like
var my_circles = d3.select(document.getElementById("circles").contentDocument)
.selectAll("circle");
I find the Javascript console quite useful for debugging selectors. Just type in what you want to test and see if the things you want are returned.
The problem is that the above code only works once the object has been loaded. Even using something like JQuery's .ready() won't be sufficient to ensure that. A quick and dirty solution is to repeatedly check whether the elements are present until they are:
function changeColor() {
var sel = d3.select(document.getElementById("circles").contentDocument)
.selectAll("circle");
if(sel.empty()) {
setTimeout(changeColor, 100);
} else {
sel.attr("fill", "black");
}
}
changeColor();
Full example here.
Is there a way to use em as unit for SVG translations? As in
<rect height="10em" width="10em" transform="translate(0em, 10em)"
style="fill:none;stroke-width:3;stroke:black/>
The rectangle does not translate in Firefox, unless I remove the em as unit.
You can sort of do this if you wrap the element(s) you want to translate in a new coordinate system:
<svg>
<svg width="1em" height="1em" overflow="visible" viewBox="0 0 1 1">
<rect height="10" width="10" transform="translate(0, 10)" .../>
</svg>
</svg>
Another option if you only need translations and use elements that have x and y attributes (or equivalent) is to use those instead, like this:
<rect x="0" y="10em" height="10em" width="10em"
style="fill:none;stroke-width:3;stroke:black/>
A new specification for transforms in CSS/SVG is currently being worked on, and it will indeed allow units in translations, see here.
Unfortunately, not;
The specs explicitly allow for user units - that correspond to CSS units and default to pixel units when otherwise not specified - to be applied for coordinates, while translations are meant to be used with floating point numbers exclusively as defined by the SVGMatrix interface.
Another thing you could do if you're creating the rect with javascript is retrieve the font size of a parent element and convert the em value to px.
Using jQuery:
var one_em = +$("#parent").css("font-size").replace("px", "");
$("#parent").append("<rect transform=translate(0," + (10*one_em) + ") .../>")