Snap.svg inner shadow on animated arc - svg

I just starting working with svgs and Snap.svg library. Following a tutorial I created an arc progress indicator. What I want now is to add an inner shadow to the animated arc. I read the documentation but the filter.shadow command only gives the option for an outer shadow. I did some research on adding filters to regular svgs and I added one to the svg that I'm binding to my Snap object. It does give me the inner shadow, however it ruins the arc animation; only about half of the arc appears even though the inner shadow is present. I changed some of the filter settings trying to fix it but nothing works. I'm thinking that filter only works for static svgs.
Any help would be appreciated. Thanx
UPDATE
Here is the code that I'm using.
<svg class="gauge" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 400 200" xml:space="preserve">
<path fill="none" stroke="#D1D2D1" stroke-miterlimit="10" d="M199.7,12.7V18h0.4v-5.3H199.7z M382,200c0-50-20.3-95.4-53.1-128.4
l8.4-8.4l-0.4-0.4l-8.4,8.4C295.6,38.4,250.1,18,200,18S104.5,38.3,71.5,71.2l-8.3-8.3l-0.4,0.4l8.3,8.3C38.3,104.5,18,149.9,18,200
h44c0-38,15.4-72.4,40.3-97.4l7.5,7.5l0.4-0.4l-7.5-7.5c24.9-24.8,59.2-40.2,97.1-40.3v10.5h0.5V62c38.1,0.1,72.6,15.6,97.5,40.7
c-0.1-0.1-0.2-0.2-0.3-0.4l-7.4,7.4l0.4,0.4l7.4-7.4c24.8,25,40.2,59.4,40.2,97.3H382z M102.3,102.6l0.3-0.3L102.3,102.6z"/>
</svg>
<script type="text/javascript">
var canvasSize = 400,
centre = canvasSize/2,
radius = canvasSize*0.8/2,
s = Snap('.gauge'),
path = "",
startY = centre-radius,
arc = s.path(path);
function fill(percent) {
var endpoint = percent*180;
Snap.animate(0, endpoint, function (val) {
arc.remove();
var d = val,
dr = d-180;
radians = Math.PI*(dr)/180,
endx = centre + radius*Math.cos(radians),
endy = centre + radius * Math.sin(radians),
largeArc = d>180 ? 1 : 0;
path = "M"+startY+","+centre+" A"+radius+","+radius+" 0 "+largeArc+",1 "+endx+","+endy;
arc = s.path(path);
arc.attr({
stroke: '#fff',
fill: 'none',
strokeWidth: 44,
});
}, 1500, mina.backout);
}
fill(50/100);
</script>
I added this code to the attribute to get an outer shadow.
arc.attr({
filter : s.filter(Snap.filter.shadow(1, 1, 2, 'black', 0.5))
})
I was wondering if there's something similar for an inner shadow.
I also tried adding this to the the embedded svg and then adding the attribute to the animated arc thinking it might work like a regular, static svg. It does give me an inner shadow, however only part of the animated arc gets displayed; it gets displayed within a rectangle.
<defs>
<filter id="inner-shadow">
<feGaussianBlur result="offset-blur" stdDeviation="5"></feGaussianBlur>
<feOffset dx="1" dy="1"></feOffset>
<feFlood flood-color="black" flood-opacity="1" result="color"></feFlood>
<feComposite operator="in" in2="offset-blur"></feComposite>
<feComponentTransfer>
<feFuncA slope="0.5"></feFuncA>
</feComponentTransfer>
<feMerge>
<feMergeNode></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
arc.attr({
stroke: '#fff',
fill: 'none',
strokeWidth: 44,
filter: 'url(#inner-shadow)',
});

You can adjust the filter area that its applied over so that it doesn't get cut off. The Snap code seemed to give an error for me, so I just tried it using the svg filter markup, as the problem seems to be SVG related, rather than Snap.
So the key bit to change would be..
<filter id="inner-shadow" y="-30" x="-30" height="400" width="500">
This gives an offset wider range. You can read more on that at filter docs
jsfiddle

Related

Get attributes of use element that are defined by defs element with snap.svg

I'm new to svgs and brand new to snap.svg. I'm working on generating elements within an SVG and have the following to work with:
<svg width="600" height="400" style="shape-rendering: geometricPrecision; position: absolute;
left: 0; top: 0;">
<defs>
...
<circle id="dot" r="10" stroke-width="2"></circle>
</defs>
</svg>
I want to use javascript to create mutliple instances of the circle #dot at different positions. So I have some javascript using snap.svg like this:
var dot = svg.use("dot");
var r = dot.attr("r");
dot.attr({ x: shapeData.X-r, y: shapeData.Y-r, class: "dot" });
but the value I'm getting for the radius, r, is null. How can I access values describing my circle like, r, width, height, fill color, etc?
The way use elements work in SVG is they are basically just pointers to the original object. If you place a clone of that dot on the canvas with use, the clone doesn't have a defined radius. It points back to dot, which has a defined radius.
It's not entirely clear to me what all you need to do but I think the right way to approach this is to get a reference to dot that you can then use for this purpose. You can clone dot and add other attributes to it later.
Aside from that you're just missing a lot of Snap stuff you need but maybe that's because you're just giving us a snippet.
Here's some code:
<svg id="svg" width="600" height="400" style="shape-rendering: geometricPrecision; position:absolute;left: 0; top: 0;">
<defs>
<circle id="dot" r="10" stroke-width="2"></circle>
</defs>
</svg>
JS:
// reference to svg
var svg=Snap('#svg')
// reference to dot, stored in a
var a=svg.select('[id="dot"]')
// what's the radius of a?
var r=a.attr('r')
alert('The radius is '+r)
// clone a and add it to the svg
b=a.use()
svg.append(b)
// give b some attributes
b.attr({x:100,y:50})
console.log(b.attr())
Fiddle here: https://jsfiddle.net/ksy7mLsx/1/

Wrap text inside SVG rectangle. I want to resize the svg rectangle when user tries to enter the text continuously

I want something like below. Initially there will be a single word when user enters multiple words the size of the box increases. How can I achieve this? Anyone have any idea as to how to proceed on this ?
You can compute the length of the text using http://www.w3.org/TR/SVG/text.html#__svg__SVGTextContentElement__getComputedTextLength
and then you can resize the rect that depends on the textLength. You can call resize function when onkeydown event fires.
Here is an example of resizing the rect when the text length is changed by interval.
<svg id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect width="100" height="100" style="fill:rgb(255,255,255);stroke-width:3;stroke:rgb(0,0,0)" ></rect>
<text x="20" y="40">123</text>
</svg>
<script>
var textElement = document.getElementsByTagName('text')[0];
var rectElement = document.getElementsByTagName('rect')[0];
resizeRect();
setInterval(resizeRect, 1000);
function resizeRect(){
textElement.textContent += 0
var textLength = textElement.getComputedTextLength();
rectElement.setAttribute("width", 50 + textLength)
}
</script>
You can find a fiddle here: https://jsfiddle.net/0dvu604g/

SVG: Dragging objects between two different SVG elements

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.

Linear gradient not applying in Webkit with d3 generated SVG

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.

Pass SVG string from cookie to RaphaelJS

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.

Resources