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

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/

Related

Bind external svg to data

Community,
I would like to bind an external svg file to my data-array.
I loaded the element into my dom like this:
defs = d3.select("defs");
d3.html("combisymbol.svg", function(data) {
//get a selection of the image so we can pull out the icon
xml = d3.select(data);
icon = document.importNode(xml.select("#star").node(), true);
icon.id = "staricon";
defs.node().appendChild(icon);
// console.log("icon", icon);
Then I tried to make it visible. I used the same approach as when I take circles that I bind to my data. With the circles it works, but my external svg is not visible.
d3.select("body").select("div#divCombiSVG")
.selectAll("star")
.data(combiData)
.enter()
.append("svg:use")
.attr("xlink:href", "#staricon");
I don't see the svgs.
I have also tried this:
d3.select("body").select("div#divCombiSVG")
.selectAll("star")
.data(combiData)
.enter()
.append("svg")
.attr("width",200)
.attr("height",200)
.node().appendChild(icon);
But then the icon gets only added to the first data-element and not the second. Even though it's added to the first, it's still not visible.
The svg file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904 /DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 37.207 100" enable-background="new 0 0 37.207 100"
xml:space="preserve">
<path xmlns="http://www.w3.org/2000/svg" id="star" cx="50" cy="50" r="20" r2="43"
orient='point' points='3' radial-shift='0' outerCurve='86'
outerOffset='4.1' innerCurve='56' innerOffset='2.2' d="M300,168
C347.7790400396858,178.49361334198113
345.7070270919484,217.64466544113793 337.23909236273084,228.5
C350.87405522189334,226.59422068634012 385.8158673985199,244.3753308862077 371.014083110324,291
C338.0368273588341,327.1310557718112
305.1670281299449,305.76111387252195 300,293 C294.83297187005513,305.76111387252195
261.9631726411659,327.1310557718112 228.98591688967605,291 C214.1841326014801,244.37533088620776
249.12594477810666,226.59422068634015 262.7609076372691,228.50000000000003
C254.29297290805158,217.64466544113793 252.22095996031422,178.4936133419811 300,168 "
fill="yellow" stroke="black" stroke-width="2"></path>
</svg>
combiData currently has two objects.
I have looked for hours at other examples but I can't make it work. I think I'm close though...I'm pretty new to d3 (but very motivated) so please be patient with me. :-)
Thanks in advance for your help!
In the first case, you are doing a .selectAll('star') (searching for the tag star), which probably should have been a .selectAll('#star') (searching for a tag with id star).
Your second approach can be tweaked a little to work as well. Calling node() on a d3 selection always returns just one node. Hence, the subsequent .appendChild happens only on the first node.
You can try this, if you find this more amenable to what you wanted to do:
d3.select("body").select("div#divCombiSVG")
.selectAll("star")
.data(combiData)
.enter()
.append("svg")
.attr("width",200)
.attr("height",200)
.each(function (d) {
this.appendChild(icon);
});
Since in the comments you asked for which option to prefer: I would recommend the first approach of using the use element. It results in less code and you can even refer to the file containing the star externally which means that you will not have to download and inline the SVG yourself (note the caveat about IE).

SVG (1.1) : how to 'link-to' or 'centre' on a shape in a browser?

Is there is browser-independant way getting the browser to centre on a particular shape (by 'id' attribute) ?
I have tried using xlinks wrapped around shapes like this:
<a xlink:href="#node24"> .... </a>
I have reasonably busy (100+ shapes) directed graph diagrams (generated from dot): and when I load them up in Chrome , more often than not, the intial screen is just blank - forcing the user to use scrollbars to find the diagram at all.
I'm afraid I don't have any good news for you.
For stand-alone SVG documents, you can manipulate the part of an SVG displayed when following a link by linking to a <view> element (distinct from, but making use of, the SVG "viewBox" attribute). The view element specifies the viewBox to use and possibly some other parameters, and the graphic will be displayed with those parameters instead of the default ones.
Example code:
<?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 version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMin meet" >
<circle cx ="50" r="40"/>
<view id="panUp" viewBox="0 -50 100 100" />
<view id="zoomIn" viewBox="25 25 50 50" />
</svg>
If you linked to the file as a whole it would show you an image with half a circle centered at the top of the screen.
If, however, you linked to it like http://example.com/sample.svg#panUp, the circle would be the same size but centered on screen. If you linked to http://example.com/sample.svg#zoomIn, you'd only see the bottom edge of a circle that is twice as big.
(I don't have anywhere to host the file that can serve up raw SVG files, but this CodePen uses data URI to show the effects, although the data URI fragment identifiers doesn't seem to work in Firefox.)
You are supposed to be able to even specify the desired viewBox, transforms, or other attributes as part of the URL fragment (like http://example.com/sample.svg#myView(viewBox(0,0,200,200))), but I don't think that's widely implemented -- it had no effect on either Firefox or Chrome.
And even <view> fragments don't seem to work when the SVG is embedded within an HTML document. So unless your SVG is stand-alone, creating a view for each element (or one view that your dynamically change to match the clicked element), isn't going to be worth the trouble.
So what does work?
The default behaviour, when linking to a fragment (element id) that is not a <view> is to display the nearest ancestor <svg> element that contains that element ("nearest ancestor" because an SVG can contain nested <svg> tags). So if your document has a natural structure to it, you could replace some <g> elements with <svg> with a specified x,y,height and width parameter, and then linking to an element within that sub-graphic would show that view. That should work even when the SVG is embedded within a larger HTML document. But if you've got hundreds of elements moving around, it's probably not a practical solution.
Which leaves #Ian's solution of programmatically manipulating the main SVG viewBox. If you don't want to zoom in, just pan, leave the width and height as the full size of your visualization, and just change the x and y offsets. Something like:
function centerViewOnElement( el ) {
var bbox = el.getBBox()
var elCenterX = bbox.x + bbox.width/2,
elCenterY = bbox.y + bbox.height/2;
svg.setAttribute("viewBox", [(elCenterX - width/2),
(elCenterY - height/2),
width,
height
].join(" ") );
//assuming you've got the svg, width and height already saved in variables...
}
Thought I would do a simpler example, as this feels quite useful in general...with a jsfiddle here
<svg id="mySvg">
<circle id="myCirc" cx="20" cy="20" r="20"/>
<rect id="myRect" x="50" y="50" width="50" height="50"/>
</svg>
var mySvg = document.getElementById("mySvg");
function getNewViewbox( el ) {
var bbox = el.getBBox();
return newViewbox = bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height;
}
function focusElement( ev ) {
ev.stopPropagation();
mySvg.setAttribute("viewBox", getNewViewbox( ev.target ) );
}
//click on any element, or even the svg paper
document.getElementById("mySvg").addEventListener("click", focusElement);

How to append a text element with inline tspan children?

Starting with a DOM that already contains something like
<svg id="svg0" width="600" height="300" xmlns="http://www.w3.org/2000/svg" version="1.1">
</svg>
...I want to programmatically modify the element in d3.select("#svg0") so that I end up with
<svg id="svg0" width="600" height="300" xmlns="http://www.w3.org/2000/svg" version="1.1">
<text x="20" y="20">
Lorem ipsum
<tspan style="alignment-baseline:text-before-edge">dolor</tspan>
sit amet</text>
</svg>
This is as far as I can get:
var $svg = d3.select("#svg0");
$svg.append("text").text("Lorem ipsum ")
.attr({x:"20", y:"20"});
It looks as though the rest should be easy, but I've spent the last two hours trying all the "obvious" things to finish this without success.1
What does one have to do to finish the task described above?
1I've tried far too many things to describe them all. Suffice it to say that the text method, when used as a setter, wipes out whatever textContent the text object had before. This means that, effectively, this method can be called only once, which precludes solutions relying on calling .text(...) a second time to add the " sit amet" fragment.)
Normally you would think to use the html function for this, but from the docs:
Note: as its name suggests, selection.html is only supported on HTML
elements. SVG elements and other non-HTML elements do not support the
innerHTML property, and thus are incompatible with selection.html.
Consider using XMLSerializer to convert a DOM subtree to text. See
also the innersvg polyfill, which provides a shim to support the
innerHTML property on SVG elements.
Here's with the polyfill: http://jsfiddle.net/GNGF5/
And if you don't want to do that, you can hack it up using multiple tspan elements w/ a transform, as seen here: http://jsfiddle.net/cAuCM/
var $svg = d3.select("#svg0");
var $text = $svg.append("text");
var $tspan1 = $text.append('tspan');
var $tspan2 = $text.append('tspan');
var $tspan3 = $text.append('tspan');
$text.attr('transform', 'translate(0, 18)');
$tspan1.text('Lorem ipsum');
$tspan2.text('dolor').style('alignment-baseline', 'text-before-edge');
$tspan3.text('sit amet');
Here's how to do it with Snap.svg:
var paper = Snap("#svg0");
var t1 = paper.text(50, 50, "Snap");
var t2 = paper.text(50, 70, ["S","n","a","p"]);
<script src="https://cdn.jsdelivr.net/snap.svg/0.1.0/snap.svg-min.js"></script>
<svg id="svg0" width="600" height="300" xmlns="http://www.w3.org/2000/svg" version="1.1">
</svg>

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