Replacing text instead of appending in d3 & Jquery - javascript

I am using a combination of Javascript+jQuery & d3.js to create custom Chart elements in SAP UI5 dashboard.
In my render function in gauge.ds, I have below code:
this.render = function()
{
this.body = d3.select("#" + this.placeholderName)
.append("svg:svg")
.attr("class", "gauge")
.attr("width", this.config.size + 10)
.attr("height", this.config.size + 10);
this.body.append("svg:circle")
.attr("cx", this.config.cx)
.attr("cy", this.config.cy)
.attr("r", this.config.raduis)
.style("fill", "#ccc")
.style("stroke", "#000")
.style("stroke-width", "0.5px");
this.body.append("svg:circle")
.attr("cx", this.config.cx)
.attr("cy", this.config.cy)
.attr("r", 0.9 * this.config.raduis)
.style("fill", "#fff")
.style("stroke", "#e0e0e0")
.style("stroke-width", "2px");
if (undefined != this.config.label)
{
var fontSize = Math.round(this.config.size / 12);
this.body.append("svg:text")
.attr("x", this.config.cx)
.attr("y", this.config.cy * 2 + fontSize / 2)
.attr("dy", fontSize / 2)
.attr("text-anchor", "middle")
.text(this.config.label)
.style("font-size", fontSize + "px")
.style("fill", "#333")
.style("stroke-width", "0px");
}
This creates a gauge and there is a label beside that gauge as shown in the image.
Now in my redraw function, I want to replace this label with a new text. I have written following code but it does not work as it writes over previous label and both are visible now on one another.
this.redraw = function(value)
{
var fontSize = Math.round(this.config.size / 12);
this.body.append("svg:text")
.attr("x", this.config.cx)
.attr("y", this.config.cy * 2 + fontSize / 2)
.attr("dy", fontSize / 2)
.attr("text-anchor", "middle")
.text(value)
.style("font-size", fontSize + "px")
.style("fill", "#333")
.style("stroke-width", "0px");
}
What code should I change to replace the text in the label instead of writing over it?
Thanks.

Give an ID to your text in the render function:
this.body.append("svg:text")
.attr("id", "textLabel")
//etc...
And select by class in the redraw function:
this.body.select("#textLabel")
.text(value)
If that is the only <text> element in the SVG selection, you could simply do...
this.body.select("text")
.text(value)
... without any ID or class.
A third solution is naming a selection outside both function, which you could change inside them.
Finally, two advices:
First, you said "I am using a combination of jQuery & d3.js". That's almost always a terrible idea. Don't do that.
Second, I'd advise you to mind the names of your variables and objects. You are referring to a SVG selection as body. Normally, we would expect that this.body refers to the <body>. Thus, change it to this.svg, it's clearer for whoever is reading your code.

Related

Why does this text "text 1" appended on the rectangular not render? [duplicate]

I'm looking to append html onto a rectangle in D3 to give me a multiple line tooltip. The bottom part is how I'm adding a rectangle which may be part of the problem. The top is the code that should work in my world.
newRect.().html(" <textArea font-family=Verdana font-size=20 fill=blue > Test " + "</br>" + "Test2 </textArea>");
Which does insert a text field into the SVG, it just doesn't display:
HTML:
<rect id="rectLabel" x="490" y="674" width="130" height="160" fill="red">
<textarea fill="blue" font-size="20" font-family="Verdana"> Test </br>Test2 </textarea>
</rect>
I have a mouse over function which runs the following:
newRect = svg.append("rect")
.attr("x", xCor)
.attr("y", yCor)
.attr("width", 130)
.attr("height", 160)
.attr("fill", "red")
.attr("id", "rectLabel");
I think I should be doing this but it doesn't work. It just removes the g.node that I'm trying to append to.
newRect = $(this).enter().append("rect")
.attr("x", xCor)
.attr("y", yCor)
.attr("width", 130)
.attr("height", 160)
.attr("fill", "red")
.attr("id", "rectLabel");
Question:
Why doesn't my text appear? Ive tried .html, .textArea. I want a multiple line label so I don't think .text will work correct? Also, how should I be appending the rectangle?
A rect can't contain a text element. Instead transform a g element with the location of text and rectangle, then append both the rectangle and the text to it:
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d; });
http://bl.ocks.org/mbostock/7341714
Multi-line labels are also a little tricky, you might want to check out this wrap function.
Have you tried the SVG text element?
.append("text").text(function(d, i) { return d[whichevernode];})
rect element doesn't permit text element inside of it. It only allows descriptive elements (<desc>, <metadata>, <title>) and animation elements (<animate>, <animatecolor>, <animatemotion>, <animatetransform>, <mpath>, <set>)
Append the text element as a sibling and work on positioning.
UPDATE
Using g grouping, how about something like this? fiddle
You can certainly move the logic to a CSS class you can append to, remove from the group (this.parentNode)

Link lines across small multiple charts d3js

I am trying to get lines to change style on mouseover across multiple charts. In this example available here, I have two charts that both have five groups A,B,C,D,E. Each however is in a different csv (I am open to bringing the data in one csv or as one json array, but this is just how I have it set up right now).
I can get two charts each with five lines corresponding to the group. Using the below code, I get the hovered over line to change style whilst fading out the other lines in that chart.
// Fading and Selecting Lines
d3.selectAll('path.line.mainline')
.on("mouseover", function(d) {
var HoveredLine = this;
d3.selectAll('path.line.mainline').transition().duration(0)
.style('opacity',function () {
return (this === HoveredLine) ? 1.0 : 0.1;
})
.style('stroke-width',function () {
return (this === HoveredLine) ? 4 : 2;
})
;
})
This is achieved by giving the lines an id using classed. Using a different id, the lines in the other chart are selected similarly.
What I want to achieve is a way that if the line of e.g. group A is highlighted in one chart, it is also highlighted in the other chart also (and all other non-selected lines are faded in all charts). I thought maybe this could be done by getting the index of the selected line and somehow using that in the other chart.
We can solve it by having a single place where we handle mouseover and mouseout for both lines.
Primarily to avoid code repeat (DRY principle)
We will write mouse over and mouse out in a single place from where we can handle events in both svg.
So instead of attaching listeners individually like this
d3.selectAll('path.line.mainline')
.on("mouseover", function(d) {
and
d3.selectAll('path.line.mainlinel')
.on("mouseover", function(d) {
Do it like this:
d3.selectAll('path.line')//register this to all paths
.on("mouseover", function(d,i) {
Make use of filter to get the lines on which it is hovered.
d3.selectAll('path.line').filter(function(d1) {
return d.name == d1.name; all which have same name get it via filter
})
.style("opacity", 1)//show filtered links
.style("stroke-width", 4);
Full method will be like this:
function doHover() {
d3.selectAll('path.line')//register this to all paths
.on("mouseover", function(d,i) {
//first make all lines vanish
d3.selectAll('path.line')
.style("opacity", 0.1)
.style("stroke-width", 2)
//only show lines which have same name.
d3.selectAll('path.line').filter(function(d1) {
return d.name == d1.name
})
.style("opacity", 1)
.style("stroke-width", 4);
d3.select("div#chartw.container svg")
.append("text")
.attr("id", "cohorttext")
.html("Cohort " + d.name)
.attr("x", (width) / 1.2)
.attr("y", margin.top * 1.5)
.style("fill", color(d.name))
.style("font-weight", "bold")
.style("font-size", "18px");
d3.select("div#chartw.container svg")
.append("text")
.attr("id", "cohorttextx")
.html("Gini = " + giniw[i%giniw.length])//so that its always within the max length
.attr("x", (width) / 1.2)
.attr("y", 20 + margin.top * 1.5)
.style("fill", color(d.name))
.style("font-size", "14px");
d3.select("div#chartl.container svg")
.append("text")
.attr("id", "cohorttext")
.text("Cohort " + d.name)
.attr("x", (width) / 1.2)
.attr("y", margin.top * 1.5)
.style("fill", color(d.name))
.style("font-weight", "bold")
.style("font-size", "18px");
d3.select("div#chartl.container svg")
.append("text")
.attr("id", "cohorttextx")
.html("Gini = " + ginil[i%ginil.length])//so that its always within the max length
.attr("x", (width) / 1.2)
.attr("y", 20 + margin.top * 1.5)
.style("fill", color(d.name))
.style("font-size", "14px");
})
.on("mouseout", function() {
d3.selectAll('path.line')
.style("opacity", 1)
.style("stroke-width", 2);
//selectALL because we are giving same id to both text in 2 svgs
d3.selectAll("#cohorttext").remove()
d3.selectAll("#cohorttextx").remove()
})
}
Working code here
Please let me know if you have any queries on this.

D3 Appending Text to a SVG Rectangle

I'm looking to append html onto a rectangle in D3 to give me a multiple line tooltip. The bottom part is how I'm adding a rectangle which may be part of the problem. The top is the code that should work in my world.
newRect.().html(" <textArea font-family=Verdana font-size=20 fill=blue > Test " + "</br>" + "Test2 </textArea>");
Which does insert a text field into the SVG, it just doesn't display:
HTML:
<rect id="rectLabel" x="490" y="674" width="130" height="160" fill="red">
<textarea fill="blue" font-size="20" font-family="Verdana"> Test </br>Test2 </textarea>
</rect>
I have a mouse over function which runs the following:
newRect = svg.append("rect")
.attr("x", xCor)
.attr("y", yCor)
.attr("width", 130)
.attr("height", 160)
.attr("fill", "red")
.attr("id", "rectLabel");
I think I should be doing this but it doesn't work. It just removes the g.node that I'm trying to append to.
newRect = $(this).enter().append("rect")
.attr("x", xCor)
.attr("y", yCor)
.attr("width", 130)
.attr("height", 160)
.attr("fill", "red")
.attr("id", "rectLabel");
Question:
Why doesn't my text appear? Ive tried .html, .textArea. I want a multiple line label so I don't think .text will work correct? Also, how should I be appending the rectangle?
A rect can't contain a text element. Instead transform a g element with the location of text and rectangle, then append both the rectangle and the text to it:
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d; });
http://bl.ocks.org/mbostock/7341714
Multi-line labels are also a little tricky, you might want to check out this wrap function.
Have you tried the SVG text element?
.append("text").text(function(d, i) { return d[whichevernode];})
rect element doesn't permit text element inside of it. It only allows descriptive elements (<desc>, <metadata>, <title>) and animation elements (<animate>, <animatecolor>, <animatemotion>, <animatetransform>, <mpath>, <set>)
Append the text element as a sibling and work on positioning.
UPDATE
Using g grouping, how about something like this? fiddle
You can certainly move the logic to a CSS class you can append to, remove from the group (this.parentNode)

What is String or where does this come from?

While trying to understand d3 I saw the line .text(String);. I could not understand what String is suppose to be. I thought maybe its an empty string (nope), a method (i didnt see that in the api reference) and pondered what else it could be.
I commented it out below and got expected results. What I don't understand is what is String and why does it work. With this line my 3 squared boxes has text (its a internal value of the data it will represent later) while commented out it does not.
Demo
Html
<div class='chart' id='chart-10'/>
<script src="http://d3js.org/d3.v3.min.js"></script>
JS:
var w = 360;
var h = 180;
var svg = d3.select("#chart-10").append("svg")
.attr("width", w)
.attr("height", h);
var g = svg.selectAll(".data")
.data([50,150,250])
.enter().append("g")
.attr("class", "data")
.attr("transform", function(d, i) { return "translate(" + 20 * (i + 1) + ",20)"; });
g.append("circle")
.attr("class", "little")
.attr("r", 1e-6);
g.append("rect")
.attr("x", -10)
.attr("y", -10)
.attr("width", 20)
.attr("height", 20)
.style("fill", "lightgreen")
.style("stroke", "green");
g.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
;// .text(String);
g.attr("transform", function(d, i) { return "translate(" + 20 * (i + 1) + ",20)"; });
g.select("rect").style("opacity", 1);
g.select("circle").attr("r", 1e-6);
var t = g.transition().duration(750);
t.attr("transform", function(d, i) { return "translate(" + d + ",90)"; });
t.select("circle").attr("r", Math.sqrt);
t.select("rect").style("opacity", 1e-6);
It looks like the String constructor. According to d3 documentation, as pointed out by Matt:
if value is a function, then the function is evaluated for each selected element (in order), being passed the current datum d and the current index i, with the this context as the current DOM element. The function's return value is then used to set each element's text content.
So, you set g.data to [50,150,250] a few lines before. Each number is converted to a String object by the String constructor, returned and used as the text values of your DOM nodes.

d3js force layout dynamically adding image or circle for each node

I am new to D3JS, I need to create force layout with both image or circle for each node.
that means, A Image node or Circle node be added dynamically.
Is this possible?, if any examples please answer
Well if you just want to have an image in the middle of the circle try this:
/* Create nodes */
var node = vis.selectAll("g.node")
.data(json.nodes) // get the data how you want
.enter().append("svg:g")
.call(node_drag);
/* append circle to node */
node.append("svg:circle")
.attr("cursor","pointer")
.style("fill","#c6dbef")
.attr("r", "10px"})
/* append image to node */
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
You can also append a title, some text... See the documentation of selection.append(name) for more info.
For Dynamic Image, you can keep the image in local and using image name you can use the dynamic image from the local.
For making circle, you can use :
var groups = node.enter().append("g")
.attr("class", "node")
.attr("id", function (d) {
return d.entityType;
})
.on('click', click)
groups.append("circle")
.attr("cursor", "pointer")
.style("fill", function(d) { return color(d.entityType); })
.style("fill", "#fff")
.style("stroke-width", "0")
.style("stroke", "#ddd")
.attr("r", 20);
Here, d.entityType will be the name of the image example : favicon.png
groups.append("image")
.attr("xlink:href",function (d) {
return "../assets/images/"+d.entityType+".png";
})
.attr("x", -14)
.attr("y", -15)
.attr("width", 30)
.attr("height", 30)
If you want to add text inside the circle, you can use :
groups.append("text")
.attr("dy", 18)
.style("font-size", "2.5px")
.style("text-anchor", "middle")
.style('fill','#000')
.attr("refX", 15)
.attr("refY", -1.5)
.text(function (d) {
return d.entityName;
});

Categories