How to draw a number centered inside a circle with inline svg? - svg

I'm trying to write a Django template tag that will generate a number inside a circle as an inline svg.
The math is pretty straight-forward (except for the fudge-factor in real_fontsize -- I'm thinking it has to do with numerals being shorter than the tallest character..?)
#register.inclusion_tag('numcircle.html')
def numcircle(n, size=36, border=4, color='white', background="black", **kw):
"""Draw a circle with a number inside.
"""
kw['padding'] = kw.get('padding', 2)
kw['num'] = n
kw['size'] = size
kw['border'] = border
kw['center'] = size / 2
kw['radius'] = (size / 2) - border
kw['color'] = color
kw['background'] = background
kw['fontsize'] = size - (2 * (border + kw['padding']))
real_fontsize = kw['fontsize'] * (0.8 if kw['fontsize'] > 25 else 1)
kw['ypos'] = kw['center'] + real_fontsize / 2 - kw['border'] + kw.get('yadjust', 0)
kw['xpos'] = kw.get('xpos', size / 2)
return kw
template:
<svg width="{{ size }}" height="{{ size }}" viewBox="0 0 {{ size }} {{ size }}">
<circle cx="{{ center }}"
cy="{{ center }}"
r="{{ radius }}"
stroke="{{ color }}"
stroke-width="{{ border }}"
fill="{{ background }}"/>
<text font-size="{{ fontsize }}"
fill="{{ color }}"
font-family="Verdana"
text-anchor="middle"
alignment-baseline="baseline"
x="{{ xpos }}"
y="{{ ypos }}">{{ num }}</text>
</svg>
it produces an svg that renders very nicely in all browsers I have tested, except the native iPad browser, where the number is flush with the bottom of the circle (and not in the center)..
<svg width="44" height="44" viewBox="0 0 44 44">
<circle cx="22"
cy="22"
r="20"
stroke="white"
stroke-width="2"
fill="#21435F"/>
<text font-size="36"
fill="white"
font-family="Verdana"
text-anchor="middle"
alignment-baseline="baseline"
x="22"
y="34.4">1</text>
</svg>
Is there any way to work around this issue in a cross-browser (IE9+) fashion?
jsfiddle: http://jsfiddle.net/onLyh6bm/
(using alignment-baseline: middle doesn't seem to align in the middle in any browser..: http://jsfiddle.net/68oamxdo/1/)

You can't really rely on the alignment-baseline property. Support for it is pretty spotty at best.
Even when it is supported, alignment-baseline: middle doesn't really vertically centre the characters. There is, in fact, no way to do it accurately and reliably.
You can centre horizontally with text-anchor="middle", which is supported everywhere.
IMO, the best you can do in the vertical case, is the solution I propose in this answer: https://stackoverflow.com/a/19273331/1292848

Related

resizing a svg spinner gives me weird results

I have this svg spinner and want to change its dimansions from 79x79 to 140x140
I tried to change some params - for example:
width="140px" height="140px" viewBox = "0 0 140 140" etc
and getting weird results, like the spinner is cutted, center of rotation is moved etc
could someone help to resize it and - if possible - explain - what is the trick ?
.spwrap{
text-align:center;
}
.spinner{
display:inline-block;
border-radius:50%;
}
.spcircle{
stroke:red;
}
<div class='spwrap'>
<svg class='spinner' id='spinner' width="79px" height="79px" viewBox = "0 0 79 79">
<g>
<circle class = 'spcircle' id='spcircle' fill = "none" stroke-width="5" stroke-linecap="round" cx="37" cy="37" r="27" stroke-dasharray="79" stroke-dashoffset = "340"></circle>
<animateTransform attributeName = "transform" type = "rotate" values = "0 37 37; 360 37 37" begin = "0s" dur="0.9s" repeatCount = "indefinite"/>
</g>
</svg>
</div>

Proper use of <use> and <svg>

I'm referencing an svg file's content from my html, like this:
<svg id='container' width="19" height="19">
<use href="../svg.svg#path"/>
</svg>
svg.svg
<svg
id="home"
width="19"
height="19"
viewBox="0 0 19 19"
xmlns="http://www.w3.org/2000/svg">
<path id='path' fill-rule="evenodd" clip-rule="evenodd" d="M6.10156 2.31433C4.85892 2.31433
3.85156 3.32169 3.85156 4.56433V7.27979L5.21733 6.15722C5.88058 5.61207 6.84134 5.62857 7.48549
6.19616L11.5046 9.7376C11.8811 10.0694 12.0971 10.547 12.0976 11.0489L12.1032 16.8638H14.6256C15.8682
16.8638 16.8756 15.8565 16.8756 14.6138V4.56433C16.8756 3.32169 15.8682 2.31433 14.6256 2.31433H6.10156ZM2.35156
4.56433V8.51269L0.879033 9.72301C0.474603 10.0554 0.240234 10.5514 0.240234 11.0749L0.240249
16.6071C0.240252 17.5731 1.02297 18.3564 1.98897 18.3571L12.1047 18.3645L12.1047 18.3638H14.6256C16.6966
18.3638 18.3756 16.6849 18.3756 14.6138V4.56433C18.3756 2.49326 16.6966 0.814331 14.6256 0.814331H6.10156C4.03049
0.814331 2.35156 2.49326 2.35156 4.56433ZM6.49381 7.32159C6.40179 7.2405 6.26454 7.23814 6.16979 7.31602L1.83149 10.8818C1.77372 10.9293 1.74023 11.0002 1.74023 11.0749L1.74025 16.6071C1.74025 16.7451 1.85207 16.857 1.99007 16.8571L5.42188 16.8596V14.1996C5.42188 13.7854 5.75766 13.4496 6.17188 13.4496C6.58609 13.4496 6.92188 13.7854 6.92188 14.1996V16.8607L10.6032 16.8634L10.5976 11.0503C10.5975 10.9786 10.5667 10.9104 10.5129 10.863L6.49381 7.32159Z" />
</svg>
Question is:
Why do I need to set width and height in container? If I fail to do so, container takes up 150px by 300px.
How could I just make container automatically shrink to fit home's dimensions?
Thanks
The HTML spec says that if the size of any "replaced element" (that's things like <svg>, <canvas>, <object> etc) is not specified, then its size should default to 300px x 150px.
The <use> points to a <path>, rather than a whole SVG image, so it's size is indeterminate.
Given your use case, you cannot.

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/

Half filled circle with d3.js

I am trying to create a half filled circle with d3.js to be like this.
I didn't find any example of how to do it.
How can this be done with d3.js?
Yes, you can do that with an SVG gradient. All you have to do is define it and then use it as fill for the circle.
var grad = svg.append("defs").append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", "50%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", "50%").style("stop-color", "white");
svg.append("circle")
.attr("fill", "url(#grad)");
JSfiddle here.
You may not even require d3 for this simple task. You may use this simple technique, Using Clippath on a circle, I have written it in details in my blog http://anilmaharjan.com.np/blog/2013/11/create-filled-circle-to-visualize-data-using-svg
Use Two circles one above another in a tag.
Fill one with the color you wish and another with white or may be your background color just to make it look like its empty in there.
Then clip the later one using with rectangle in it, assign radius few pixel less than the earlier circle.
Place clip path at the top left .. assign width equal to the diameter of the circle and height will be defined by your data.
The data will act reversible to the filling so you may subtract the actual data from your max. EG: if data is 20/100 do 100-20 so u ll get 80 in this way the empty part will be 80 and filled will be 20.
You may switch between height or width to switch between vertical or horizontal filling axis.
The HTML should look like this.
<svg height="200"> <a transform="translate(100,100)">
<g>
<circle fill="#f60" r="50"></circle>
</g>
<g>
<clippath id="g-clip">
<rect height="50" id="g-clip-rect" width="100" x="-50" y="-50">
</rect>
</clippath>
<circle clip-path="url(#g-clip)" fill="#fff" r="47"></circle>
</g>
</a>
</svg>
I have created a jsfiddle to illustrate this at: http://jsfiddle.net/neqeT/2/
create a div having id name id_cirlce and paste this code inside script tag
<div id="id_circle"></div>
<script>
var svg = d3.select("#id_circle")
.append("svg")
.attr("width",250)
.attr("height",250);
var grad = svg.append("defs")
.append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", "50%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", "50%").style("stop-color", "white");
svg.append("circle")
.attr("r",50)
.attr("cx",60)
.attr("cy",60)
.style("stroke","black")
.style("fill","url(#grad)");
</script>

SVG translate with em as unit?

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) + ") .../>")

Resources