I'm a beginner at SVG, but I'd like to learn some techniques.
To be short, is there a simple way to create something like this?
I was thinking about creating a polar gradient and then clipping it:
But how do I generate a polar gradient?
Even if there's no native method, maybe it could be made with a simple linear gradient and then using some rectangular-polar coordinate transformation. Is there a way to do so?
So this is the solution I developed:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 100 100" version="1.1" onload="makeGradient();">
<script>
function makeGradient() {
var root = document.rootElement, i = 256, cir, a;
for (; i--;) {
a = i*Math.PI/128;
cir = document.createElementNS("http://www.w3.org/2000/svg", "circle");
cir.setAttribute("cx", 50 - Math.sin(a)*45);
cir.setAttribute("cy", 50 - Math.cos(a)*45);
cir.setAttribute("r", "5");
cir.setAttribute("fill", "rgb(" + i + ", " + i + ", " + i + ")");
root.appendChild(cir);
}
}
</script>
</svg>
Minified version (395 bytes):
<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" version="1.1" onload="g(this.namespaceURI,document,Math)"><script>function g(n,d,m,i,c,a,p,z){for(i=256;i--;){a=i*m.PI/128;c=d.createElementNS(n,"circle");for(p in z={cx:10-m.sin(a)*9,cy:10-m.cos(a)*9,r:1,fill:"rgb("+[i,i,i]+")"})c.setAttribute(p,z[p]);d.rootElement.appendChild(c)}}</script></svg>
This was made creating circles filled with 256 shades of gray (it sounds like porn literature for coders!) and conveniently placed.
The radii can be adjusted: I've chosen 45 for the whole spinner and 5 for the single circles. Moreover, the detail can be adjusted too if 256 are too many:
for (; i -= 2;) { ...
Use powers of 2 for optimal results. Or just define the number of steps:
var steps = 100, i = steps;
for (; i--;) {
a = i*2*Math.PI/steps;
...
cir.setAttribute("fill", "rgb(" + i*255/steps + ", " + ...);
}
A big "thank you" to Erik Dahlström for the hint, and thank you Michael Mullany for the attempt :)
Edit: Here's a fiddle to demonstrate the code.
Edit 2: Here's another fiddle using curved segments to create the spinner. You can adjust the number of segments and the size, and even see it spinning. I don't know why when the size is auto, there's a bottom margin of 5 pixels on the SVG, this making the spinning slightly off-centered...
There are no paintservers in SVG 1.1 that allow this directly, but you can e.g do it using a bit of script. Here's an article that explains how.
There is no support for polar gradients in SVG 1.1 (what's available in most edge browsers today) although there are proposals to allow capabilities like these in SVG 2. The only workaround I can think of is to apply a blend filter using an externally generated image as your multiply source. But then, I'm assuming the whole point it to try to avoid external images so this would be a little pointless:)
Related
My question: Can svg <marker> elements inherit color from the <line> they are referenced on?
The background: I have a D3 graph that has different styled lines, and I want my lines to have arrows at the end.
So at the top of my <svg> I have const defs = svg.append('defs'); and then further along I generate my defs using a generator function:
function makeDefs(defs: Selection<SVGDefsElement, unknown, null, undefined>, color: string, status: string) {
const markerSize = 3
const start = defs.append
.append('marker')
.attr('id', `arrow-start-${color}-${status}`)
.attr('viewBox', '-5 -10 20 20')
.attr('markerWidth', markerSize)
.attr('markerHeight', markerSize)
.attr('orient', 'auto-start-reverse');
start
.append('path')
.attr(
'd',
status === 'PUBLISHED' ? customPaths.arrowLarge : customPaths.arrowSmall
)
.attr('stroke', color)
.attr('fill', color);
}
And use it like so:
makeDefs(defs, 'red', 'DRAFT');
And then I add the markers to my lines with:
// d3 code to draw the lines etc
line.attr(
'marker-start',
(d) =>
`url(
#arrow-start-${d.color}-${d.status}
)`
);
This all works great, my arrows have lines. But my current setup feels burdensome and clunky. I have about 20 colors and 3 statuses. With my current setup that would be 60 different:
makeDefs(defs, 'one-of-20-colors', 'one-of-3-statues');
My understanding of markers is that they can inherit color using the currentcolor attribute. Currently my <defs> sit up top underneath my main <svg> so any color inherited is inherited directly from that top level svg which is not what I want. The issue in my case is my <line> elements are the elements who's color I want to inherit, but according to the MDN docs <line>s cannot have <defs> as children, thus leaving me with the only option, of defining all my <defs> up front all at once.
Is there a trick or some attribute I'm missing here?
Any way to pass color to my marker when doing:
line.attr(
'marker-start',
(d) =>
`url(
#arrow-start-${d.color}-${d.status}
)`
);
?
For what is is worth, I'm currently wrapping all my <line>s in <g>. I suppose I could wrap them in <svg>s instead, and apply the fill and stroke there, and then define my <defs> per svg container? I tried this briefly and swapping the <g> for an <svg> broke a lot, but I'm not even sure if it would work, or be better for that matter.
I'm trying to create an SVG element with a width defined by a percentage of the parent and a fixed value, say 50% + 20px. For normal html elements, in the CSS you can use calc(50% + 20px). Is there an equivalent way to do this for embedded SVGs? Specifically, I'm using snap.svg, though I'm not sure if this capability exists with SVGs in general.
EDIT:
Tried setting <svg> width with percentages and px, which I couldn't get to work. I tried both:
<svg width='calc(50% + 20px)'>...</svg>
<svg width='50% + 20px'>...</svg>
I also tried setting it in CSS:
svg {
width: calc(50% + 20px);
}
It should be possible with the upcoming SVG2 as width etc. become geometry properties and then you can style them with calc
I have quite complex SVG created in Inkscape. It has couple layers(groups), <path>, <rect>, <circle> and <text> elements inside. I want to add ability of zooming for greater details, so I tried to implement geometric zoom like in this Mike's example: SVG Geometric Zooming. And it works as expected in Firefox, but in IE9+ and webkit based browsers I can see transform attribute changing, but picture stays the same.
Here is code for zoom:
var svg = d3.select("svg#svgCanvas");
var zoom = d3.behavior.zoom()
.scaleExtent([1, 3])
.on("zoom", zooming);
svg.call(zoom);
function zooming() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Any ideas why it can fails?
Issue was caused by Inkscape's layers, so changing them to simple <g> elements by deleting inkscape:groupmode = layer and other unnecessary attributes fixed all.
I have a huge svg 3200*1800. I only want to show a part of that image something like 400*1000, ensuring that the width is the dominant attribute and having a scroll bar for the height but when I set viewbox it increase the width to display the added height.
viewBox="900 550 400 1000"
Is their a way to stop this happening?
I worked it out you need to increase the height relative to the viewbox for example I ended up with something like this:
width="1400"
height="4000"
viewBox="966 555 350 1000"
Compared to what I used to have:
width="350"
height="1000"
viewBox="966 555 350 1000"
You just set 'preserveAspectRatio' to "none" along with your 'viewBox' attribute, then your problem is solved.
This answer builds on Shane's answer (which does not cater to variable window sizes)...
To have width-dominant overflows:
Let the 'viewbox' define the portion of the graphic to display (any known aspect ratio)
Let the svg element have default width and height (100%)
With javascript, dynamically set the height of the svg element every time the window resizes
The code below works for my learning project and is NOT production code.
In the head element:
<script type="application/javascript">
var svgRatio = ${viewboxRatio}; // ratio must be known
// From http://stackoverflow.com/a/13651455
if(window.attachEvent) {
window.attachEvent('onresize', resizeSvg);
}
else if(window.addEventListener) {
window.addEventListener('resize', resizeSvg, true);
}
else {
//The browser does not support Javascript event binding
}
function resizeSvg() {
var height = window.innerWidth * svgRatio;
var svg = document.getElementsByTagName('svg')[0];
svg.setAttribute("height", height.toString());
}
</script>
At the end of the body:
<script type="application/javascript">
resizeSvg();
</script>
I get an XML or JSON with paths only, and I need to recreate the SVG image.
I create an empty
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'></svg>,
I add a <g transform="scale(1 -1)" fill='#aaa' stroke='black' stroke-width='5' ></g> in it, and then in this element I add all of the paths in it (e.g. <path d= ... />).
In the end I get a SVG image, but because I haven't set the viewBox attribute in the SVG element the image isn't properly displayed - when I open it in browser, a part of it is displayed full size.
Can the viewBox be calculated from the values from the paths?
Thank you!
Similar to Martin Spa's answer, but a better way to do get the max viewport area is using the getBBox function:
var clientrect = path.getBBox();
var viewBox = clientrect.x+' '+clientrect.y+' '+clientrect.width+' '+clientrect.height;
You can then set the viewbox to these co-ordinates.
n.b. i think you can change the viewbox of an svg after it's rendered so you may have to re-render the svg.
OK so I solved it the following way:
removed all letters from the paths string and made an array out of it with
var values = pathValue.split('L').join(' ').split('M').join(' ').split('z').join('').split(' ');
found max and min from those values:
var max = Math.max.apply( Math, values );
var min = Math.min.apply( Math, values );
set the viewBox:
viewBox = max min max max
This worked in my case excellent. Hope that it will be helpful to someone else too.