How to ensure a single append data-indepent? - javascript

I have a section where using transitions I fake a computation and put a message "Please wait ... computing" sort of thing.
<!-- in the html -->
<body>
<svg width="1700" height="650"/>
</body>
// js code
var svg = d3.select('svg');
// then somewhere later
var computingG = svg.append("g")
.attr("id", "computingId");
computingG.append("text")
.attr("y", height / 2)
.attr("x", width / 2)
.attr('text-anchor', 'middle')
.attr('font-family', 'arial')
.attr('fill', 'black')
.attr('stroke', 'black')
.attr('font-size', 30)
.text("Please wait ... computing");
// and further later
svg.selectAll('#computingId').remove();
But I see the text is being created many times on top of each other instead of only once (I see many g created and the text is very aliased) ... why is that? I intended this to be a one of text annotation and not data-depending.
UPDATE I see there are many appended ... how can I prevent many g from being appended to that selection?

Related

div element is getting appended outside the body

I want to implement a list on right-click of a data node. In order to do so I came across d3-context-menu plugin of d3.js. The problem I am facing is that the div element is getting appened outside the body tag.
I have never seen such an issue before.
I am following the plugin example given here:
http://plnkr.co/edit/hAx36JQhb0RsvVn7TomS?p=preview
This is the link to the library documentation:
https://github.com/patorjk/d3-context-menu
I have no clue why it is behaving in such manner. My code structure looks like this :
eventGroup = focusClip.selectAll(".event").data(data);
// Enter phase ---
eventGroupEnter = eventGroup.enter().append("svg");
eventGroupEnter.append("rect");
eventGroupEnter.append("circle");
eventGroupEnter.append("text");
// Event Group
eventGroup
.attr("class", "event")
.attr("x", function(d) {
return parseInt(x(d.time)) - 10;
}) // offset for the bg and center of dot
.attr("y", function(d) {
return parseInt(y(d.plotY));
})
.attr("width", function(d) {
return parseInt((d.label.length / 2)) + 60 + "em";
})
.attr("height", "20");
// Background
eventGroup.select("rect")
.attr("x", 0) // removes the "<rect> attribute x: Expected length, 'NaN'" Error
.attr("y", 4)
.attr("width", "100%")
.attr("height", "12")
.attr("fill", "url(#event-bg)");
menu = [{
title: "Item #1"
}];
// Dot
eventGroup.select("circle")
.attr("class", "dot")
.attr("r", 4)
.attr("cx", 10)
.attr("cy", 10)
.attr("fill", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke-width", 2)
.on("contextmenu", d3.contextMenu(menu, function() {
console.log("Quick! Before the menu appears!");
}))
.on("mouseenter", tooltip.mouseover)
.on("mouseleave", tooltip.mouseout)
.on("click", annotateBox.click);
In order to explain it well I am adding the image of the chart:
The right click event is being called on the "dot" part of the event. Why would div element get appended outside the body?
This seems to be by design. If you look at the source code of that plugin, you'll see:
d3.selectAll('.d3-context-menu').data([1])
.enter()
.append('div')
.attr('class', 'd3-context-menu');
Since selectAll is called on the root, the div will be appended to the <html>, not to the <body>.
So, the author either did this intentionally or she/he forgot that d3.selectAll is different from selection.selectAll.
Here is a basic demo, click "Run code snippet", open your browser's dev tools and inspect the snippet window.
d3.selectAll("foo")
.data([1])
.enter()
.append("div")
.attr("class", "test")
<script src="https://d3js.org/d3.v3.min.js"></script>
You're gonna see this:
<html>
<head>...</head>
<body>...</body>
<div class="test"></div>
</html>

How to change d3.js text entry from within that text's onclick method?

My AngularJS app uses d3.js to draw a nice chart.
While drawing this chart, it uses paints some text on the screen.
I want to change that text when someone clicks on it based on the boolean value of myCondition. This is how I do it:
var nodeEnter = node.enter()
var myLabel = nodeEnter.append("text")
.attr("x", 50)
.attr("dy", "3")
.text("Hello World")
.style("fill-opacity", 0)
.style("cursor", "pointer")
.on("click", function(d) {
if (myCondition)
myLabel.text("Mars");
else
myLabel.text("Venus");
}
);
It sorta works. The value of the text does indeed change from Hello World to Mars or Venus. But there is a problem. This code is called within a recursive function and within a loop. That recursion + loop use the same code to draw numerous such texts on the SVG Container. So when I click this label, not only does it change the text that I want. It also changes the text in other places too! I don't want that. How can I prevent it?
I really just need a way I can address this or myself from within the click function so it knows I'm talking about the object. How?
Without knowing your recursive function and the loop, I'll try two different approaches, I hope that one of them works.
The first one is using this for the click event:
var myLabel = nodeEnter.append("text")
.attr("x", 50)
.attr("dy", "3")
.text("Hello World")
.style("fill-opacity", 0)
.style("cursor", "pointer")
.on("click", function(d) {
if (myCondition)
d3.select(this).text("Mars");
else
d3.select(this).text("Venus");
}
);
If this doesn't work, you can try to set a specific class to your different myLabel texts. Doing this, even if you have several myLabel in your SVG, each one has a unique class. Suppose that index is a specific value for the loop (like i). So, you can try:
var myLabel = nodeEnter.append("text")
.attr("x", 50)
.attr("dy", "3")
.attr("class", "myLabel" + index)//index is any value unique for the loop
.text("Hello World")
.style("fill-opacity", 0)
.style("cursor", "pointer")
.on("click", function(d) {
if (myCondition)
d3.selectAll(".myLabel" + index).text("Mars");
else
d3.selectAll(".myLabel" + index).text("Venus");
}
);

Changing size of rect to fit inside text

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.

Adding label to D3 path

I know how to add text element to simple node (append text). The problem is when I would like to add text to path surrounding several nodes. I have created example on http://jsfiddle.net/FEM3e/5/ Please ignore nodes in upper left corner. So I have two groups of nodes. And I would like to add text for each group. Printscreen of desired output http://dopisna.bencin.si/screenshot.png.
I set path in
force.on("tick", function () {
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
vis.selectAll("path")
.data(groups)
.attr("d", singlePath)
.enter().insert("path", "g")
.style("fill", groupFill)
.style("stroke", groupFill)
.style("stroke-width", 57)
.style("stroke-linejoin", "round")
.style("opacity", .7);
});
I have tried appending text with no success. I am asking for some hints.
OK then. The problem is that you're using text instead of textPath. I've modified your fiddle and now there's some text, albeit some rather ugly text, appended to your path.
The only real change I've made is the addition of this snippet:
vis.selectAll("text")
.data(groups)
.enter()
.append("text")
.attr("x", 8)
.attr("dy", 28)
.append("textPath")
.attr("xlink:href", function (d,i) { return "#path_" + i; })
.text(function (d,i) { return "path_" + i; });
You can see that you go through the usual selection and data binding. You then append your text with the attributes you want (definitely change the ones I borrowed from Mikes Bl.ock) and then you append the text path linking it to a path element in the xlink:href attribute. Obviously you then create some text. One of the cool things about textPath is that it allows you append curved paths.
I think that there's a bit of overkill using the groups as data for the textPath, so you might want to select a more appropriate data selection to bind to this.

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