Changing size of rect to fit inside text - javascript

I have some text I append to an svg object with D3.js using append('text').
My code looks like this:
var countries = svg.append("svg:g")
.attr("id", "countries");
var stateTexts = svg.append('rect')
.attr('x', xstateText)
.attr('y', ystateText)
.attr('width', 'auto')
.attr('height', 'auto')
var stateText = svg.append('text')
.attr('x', xstateText)
.attr('y', ystateText)
.style("font-family", "Arial")
.style("font-size", "14px")
.style("font-weight", 'bold');
What I'd like is to put that text "inside" a rect which changes size based on the length of the text I append. The rect would have a stroke of 1px so as to give the appearance of a box.
How can I accomplish this? Obviously, width and height can't be set to auto (css properties). I need something else there that can work native to D3.
Edit: Confused by the downvote..

You can't do this automatically in SVG -- the dimensions of the text have to be computed and the rectangle added accordingly. Fortunately, this is not too difficult. The basic idea is illustrated in this function:
function mkBox(g, text) {
var dim = text.node().getBBox();
g.insert("rect", "text")
.attr("x", dim.x)
.attr("y", dim.y)
.attr("width", dim.width)
.attr("height", dim.height);
}
Given a container and a text element, compute the dimensions of the text element (the text must be set for this to work correctly) and add a rect to the container with those dimensions. If you want to get a bit fancier, you could add another argument that allows you to specify padding so that the text and the border are not immediately next to each other.
Complete demo here.

Related

D3:reflect data changes on visualization keeping the same array size. Trying to represent a sort algorithm visually

I'm trying to represent a selection sort visually with d3 to show some students and I'm having problems updating the data once the positions swap(I will add the transitions and delays once it's working). The positional attributes don't seem to be working as well, I don't know why, any ideas?. The codepen is here:
HTML:
<div id="canvas">
</div>
CSS:
rect{
border:1px solid black;
}
JS:
function selectionSort(array,svg){
//where the index will position
var positioningIndex=0;
var aux;
var minIndex;
var minVal=Number.MAX_VALUE;
while(positioningIndex<array.length){
//get the index of the mínimum
for(var i=positioningIndex;i<array.length;i++){
if(array[i]<minVal){
minIndex=i;
minVal=array[i];
}
}
//swap the mínimum to the positioningIndex
aux=array[minIndex];
array[minIndex]=array[positioningIndex];
array[positioningIndex]=aux;
//update visualization
svg.selectAll("rect").data(array);
minVal=Number.MAX_VALUE;
++positioningIndex;
}
return array;
}
var dataSet=[10,7,8,44];
var svg=d3.select("#canvas").selectAll("rect")
.append("svg")
.attr("width", 960)
.attr("height", 500);
var rect=svg.data(dataSet)
.enter()
.append("rect");
rect.text(function(el){
return el;
})
.attr("width", 30)
.attr("height", 30)
.attr("x", function(d, i) {
return i*5;
})
.attr("y", 30);
.style("color","green");
array=selectionSort(dataSet,svg);
You've got a lot of mixing up of html and svg elements going on there
First off, your svg element isn't getting appended:
var svg=d3.select("#canvas").selectAll("rect") // <- selectAll("rect") is causing problems
.append("svg")
No existing rect elements at the start means no svg's getting appended (do you mean to add one for each rect?) Edit: and in fact that one error is the cause of everything that happens afterwards - the selectAll("rect") needs moved to the line where elements are added to the svg - not on the line where the svg itself is added -->
var rect=svg.selectAll("rect").data(dataSet) // rect should really be g
.enter()
.append("rect");
Secondly, and because of the above error, the elements called 'rect' that are added (and added directly to the #canvas id div) aren't svg:rect objects - they're just html elements with the name 'rect' - see Is there a way to create your own html tag in HTML5?. The browser just treats them as inline elements, so none of your x's or y's make a difference they just line up one after the other
Finally, if this was svg you wouldn't be able to add text directly to a rect, you'd need to use a group (g) element and add both rect and text elements to that to keep them associated, and style("transform", translate(x,y)) the group element to move them around.
var g=svg.selectAll("g").data(dataSet) // <-- now changed from rect
.enter()
.append("g") // <-- and here
.attr ("transform", function(d,i) {
return "translate("+(i*35)+" 30)";
})
;
// texts n rects added here
g.append("text").text(function(el){
return el;
})
.attr("dy", "1em")
g.append("rect")
.attr("width", 30)
.attr("height", 30)
;
See http://codepen.io/anon/pen/bwJWEa?editors=1111

D3.js transition for set of rectangles

Here is my problem, I want to use transition() method of d3.js but for a set of rectangles that I don't know the size.
For example: At first I have 2 rectangles then 3 then 4 and then 2.
Can I use transition() in this case? If so, what's the best way to do it?
Thanks in advance
I use invisible rectangles for providing good hover effect in charts. So while creating the rectangles I simply assign them a class. While updating I remove elements of that class first and just repeat the process.
tmpsvg = svg.transition();
g = tmpsvg.select('g');//prefer to refer by classname
g.selectAll(".bar-rect").remove();
var rect = g.selectAll(".bar-rect")
.data(data)
.enter().append("svg:rect")
.attr("class", "bar-rect")
.attr("x", function(d, i) { return x(d.key)-10; })
.attr("y", 0)
.attr("width", "20px")
.attr("height", h)
.on("mouseenter", function(d, i) {
//TOOLTIP EFFECTS ON MOUSE-ENTER
$('#myls'+i).animate( {opacity:1 },100);
$('.chart-tooltip[data-index='+i+']').addClass('hover');
// Add hover class to the targeted point
}).on("mouseleave", function(d, i) {
//REMOVE TOOLTIP EFFECT ON MOUSE-LEAVE
$('#myls'+i).animate( {opacity:0 },100);
$('.chart-tooltip').removeClass('hover');
// Remove hover class from the targeted point
});
This may not be the most efficient way but hope it helps.

D3 Changing the width attribute of an object depending on it's child

I am trying to change the width of the entire frame of something in D3 after it has already been created, depending on the width of some text inside of it (so that it doesn't get cut off). However, I'm having trouble returning the width of the text element.
The body looks like so:
this.body = d3.select("#" + this.placeholderName)
.append("svg:svg")
.attr("class", "gauge")
.attr("width", this.config.size)
.attr("height", this.config.size + 150);
The text inside the body looks like this:
this.body.append("svg:text")
.attr("x", this.config.cx - 60)
.attr("y", this.config.cy / 2 + fontSize / 2)
.attr("dy", fontSize / 2)
.attr("text-anchor", "start")
.text(this.config.label)
.style("font-size", fontSize + "px")
.style("fill", "#333")
.style("stroke-width", "0px")
Finally, I change the width of the frame with this. What would I need to put as the second argument here?
this.body.attr("width", <WIDTH OF TEXT ABOVE>);
Thanks
First, when you create your text element, give it a class or id so you can easily select it later.
Lets say you give it .attr('class', 'bodyText').
Then you can get its length using SVGTextContentElement.getComputedTextLength()
You can get this value for your text node by using selection.node() to grab the actual text element from a d3 selection, and then calling the method on the node. Like this:
var textLength = d3.select('.bodyText').node().getComputedTextLength();
Then you can use this value when setting your width property.
Have you tried this:
this.body.append("svg:text").attr("id","textValue")
and
var w=d3.select("#textValue").attr("width");
Now
this.body.attr("width", w);
I think it should work.

Adding a span icon to a D3 bar chart

I'm working on a fairly basic bar chart where I'm trying to have a span icon that appears, anchored at the start of each bar. Which icon appears is dependent on the class of the bar. For example, if the bar is blue, I want a certain icon vs. if the bar is red.
I've appended and added the span which shows up in the console, but is not actually appearing any where in the chart.
I have the icons stored as spans in my css, one for each version of the value name that gets plugged in.
I've tried a variety of selections, ordering, etc. But can't get it to stick.
var bars = svg.selectAll('.bar')
.data(data)
.enter().append('g')
.attr('class', 'bar');
bars.append('rect')
var icons = svg.selectAll('rect')
.data(data)
.enter().append("span")
.attr("class", function(d, i) {
return "icon-" + d.value + "-right";
})
.attr('dx', -6)
.attr('dy', (bar_height / 2) +5)
.attr('text-anchor', 'start');
You should use foreignObject element to insert HTML into SVG.
Like so:
var icons = svg.selectAll('foreignObject').data(data);
icons.enter().append("foreignObject")
.attr("class", function(d) { return "icon-" + d.value + "-right"; })
.append("xhtml:body")
.append("xhtml:span");
Also you can use text element to add icons to the SVG:
var icons = svg.selectAll('text').data(data);
icons.enter().append("text")
.html("&#xf00d") // utf-8 character for the icon

Adding FontAwesome icons to a D3 graph

I am trying to set an icon with FontAwesome instead of text in my D3 nodes. This is the original implmentation, with text:
g.append('svg:text')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id')
.text(function(d) { return d.label; });
And now I try with icons:
g.append('svg:i')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id icon-fixed-width icon-user');
But this is not working, even though the markup is right, and the CSS rules are properly hit: the icons are not visible.
Any idea why?
Here is the related jsbin
EDIT
I have found this alternative to insert images: http://bl.ocks.org/mbostock/950642
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
Which is exactly what I want to do, but it does not work with <i> elements used by FontAwesome.
You need to use the proper Unicode inside a normal text element, and then set the font-family to "FontAwesome" like this:
node.append('text')
.attr('font-family', 'FontAwesome')
.attr('font-size', function(d) { return d.size+'em'} )
.text(function(d) { return '\uf118' });
This exact code will render an "icon-smile" icon. The unicodes for all FontAwesome icons can be found here:
http://fortawesome.github.io/Font-Awesome/cheatsheet/
Be aware that you need to adapt the codes in the cheatsheet from HTML/CSS unicode format to Javascript unicode format so that  must be written \uf118 in your javascript.
Thanks to all that replied. My final solution is based on the answer by CarlesAndres:
g.append('text')
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'central')
.attr('font-family', 'FontAwesome')
.attr('font-size', '20px')
.text(function(d) { return ICON_UNICODE[d.nodeType]; });
Be careful with your CSS: it takes precedence over the SVG attributes.
And this is the way it looks:
The good thing about this, compared to the foreignObject solution, is that events are properly handled by D3.
I'm truly new to d3, but font awesome works by styling an <i> element with a class attribute.
The only way I found is to append a foreignObject and set on it the relevant HTML needed by font awesome.
Reference:
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject?redirectlocale=en-US&redirectslug=SVG%2FElement%2FforeignObject
http://fortawesome.github.io/Font-Awesome/examples/
Code:
g.append('svg:foreignObject')
.attr("width", 100)
.attr("height", 100)
.append("xhtml:body")
.html('<i class="icon-fixed-width icon-user"></i>');
Demo: http://jsbin.com/eFAZABe/3/
Given that the other answers don't work anymore (because d3js has been updated in the meanwhile) and because it's not a good solution to use svg:foreignObject due to compatability issues, here is an answer that works without having to use any hacks:
.append("text") // Append a text element
.attr("class", "fa") // Give it the font-awesome class
.text("\uf005"); // Specify your icon in unicode (https://fontawesome.com/cheatsheet)
Here is a working example (click "Run code snippet" and the d3 code outputs three stars):
var icons = [1, 2, 3];
d3.select("body")
.selectAll(".text")
.data(icons)
.enter()
.append("text") // Append a text element
.attr("class", "fa") // Give it the font-awesome class
.text("\uf005"); // Specify your icon in unicode
<link href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
I know this question is old, been resolved, but - this worked for me today.
From this site
svg.append('svg:foreignObject')
.attr("width", 50)
.attr("height", 50)
.append("xhtml:body")
.html('<i class="fa fa-user"></i>');
But for my chart, I dropped the append xhtml:body, otherwise it wouldn't let me set x and y coords.
The element will adopt the width and height of the font you set.
d3.select('svg')
.append('svg:foreignObject')
.attr('class', 'handle')
.attr('x', +getLeftBarPosition(i+1, 'handle')[0] + +getLeftBarPosition(i+1, 'handle')[1])
.attr('y', state.barHeight/2)
.html('<i class="fa fa-user"></i>')
Just to put in code here what worked for me based on CarlesAndres's answer and mhd's comment:
node.append("text")
.attr("style","font-family:FontAwesome;")
.attr('font-size', "50px" )
.attr("x", 440)
.attr("y", 440)
.text(function(d) { return '\uf118' });
Font awesome 4.x versions are not supporting if we use as follows
svg.append('text')
.attr('x', 15)
.attr('y', -17)
.attr('fill', 'black')
.attr('font-family', 'FontAwesome')
.attr('font-size', function (d) { return '20px' })
.text(function (d) { return '\uf2b9' });
so replace this
.attr('font-family', 'FontAwesome')
with
.attr("class", "fa")
Hope it helps for FA 4.x
For those who banging their head hard.
D3 - 6.2.0 and FontAwesome - 5.13.0
Below worked
nodeEnter.append('text')
.attr('width', "10px" ).attr('height', "10px" ) // this will set the height and width
.attr("class","fas fa-adjust") // Font Awesome class, any
.attr("x", 15) // for placement on x axis
.attr("y",-5); // for placement on y axis
For those who want to use svg icons from FontAwesome with D3, this snippet should work:
btnGroup.append("g")
.attr("width", 16)
.attr("height", 16)
.attr("class", "fas fa-check-square");
The only drawback here is that width and height are not inherited from CSS. The good news is that class toggling works as expected with d3 and jquery.
Demo with toggling on click: https://jsfiddle.net/diafour/6fagxpt0/

Categories

Resources