Truncate text in d3 - text

I want to truncate text that is over a predefined limit in d3.
I'm not sure how to do it.
Here's what I have now:
node.append("text")
.attr("dx", 20)
.attr("dy", ".20em")
.text(function(d) { if(d.rating > 25) return d.name; }));
Text is only displayed if the rating > 25. How can truncate the text of those names?

To truncate text use substring
Try this code:
DEMO
.text(function (d) {
if(d.name.length > 5)
return d.name.substring(0,5)+'...';
else
return d.name;
});

Related

D3 proper text() in tremap

i'm new to d3. My problem is unreadable text.
I suppose it's cuz i added text not to rect but to svg. How do i recode or fix this thing?
https://codepen.io/DeanWinchester88/pen/xxrjLrM
svg
.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", (d) => d.x0 +10)
.attr("y", (d) => d.y0 + 20)
.text( (d) => d.data.name)
.attr("font-size", "12px")
.attr("fill","white")
.attr("overflow", "hidden")
.attr("max-width", (d) => d.x1 - d.x0)
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em")
Here's your revised code based on my comment above:
https://codepen.io/mattsrinc/pen/MWozBWQ
svg
.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr('transform', d => 'translate(' + d.x0 + ',' + d.y0 + ')')
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))
.enter()
.append('tspan')
.attr('font-size', '0.8em')
.attr('fill', 'white')
.attr('x', function(d) { console.log(d); return '0.5em' })
.attr('y', (d, i) => 12 * (i + 1))
.text(d => d);
I've left the console.log command so that you can see how the (game) name is split into parts. And that Pac-Man is right but it's the only one game for the console category (2600) so it translates to thin black rectangle on top left (something you should consider how to solve it visually).
Then also SVG CSS attribute "overflow: hidden" won't work for your data cells with TSPAN. You would have to handle that with calculating this cell width and width of the current word(s) in a line (of TSPAN tag to add). It would be better to follow and expand your other referenced codepen to work with cells that would ease this task.
Other than that I've just changed the color palette used and you can see color ranges here:
https://github.com/d3/d3-scale-chromatic

svg text translating and moving are not correct

It's my part of code to roate each text.
.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.attr("transform", function(d) {
return "rotate(90)";
})
It seems works but I don't know why
.attr("y", 0)
is move to left and right and
.attr("x", 9)
is move to up and down.
And why is text set as center with this code, not without .attr("y", 0) this line.
You have rotated the text by 90 degrees. So now, if you move the text to the "right" by increasing the X coordinate, it will actually move downwards (because of the 90 degree rotation)

change height and direction of triangles in D3

I have a basic Bar chart with red and green triangles. Red triangle on negative comparison of data and green triangle if the data is positive. The green triangle should always point upwards and red downwards at the bottom of the bar and if the data is neutral the circle should be displayed. I'm unable to align the arrow/triangle to bottom(touch x-axis) as well as rotate the arrow to point up or down based on condition. Here is my code
svg.selectAll("line.arrow")
.data(input.filter(function (d) { return d.AverageValue}))
.enter().append("line")
.attr("class", "arrow")
.attr("x1", function (d) {
return xScale(d.AppName) + xScale.rangeBand() / 2;
})
.attr("x2", function (d) {
return xScale(d.AppName) + xScale.rangeBand() / 2;
})
.attr("y1", function (d) {
return yScale(20);//bring arrows to bottom
})
.attr("y2", function (d) {
var getValue = d.AverageValue;
if (getValue >= 0) {
return yScale(10)-37;
} else {
return yScale(23) - 6;
}
})
.attr("marker-end", function (d) {
var getValue = d.ComparedToPreviousMonth;
if (getValue < 0) {
return "url(#redArrow)";
} else if (getValue > 0) {
return "url(#greenArrow)";
}
});
The entire code is in fiddle
http://jsfiddle.net/911vgmp1/
Looking at the generated svg output you will notice that the lines you are using for setting your markers are geometrically points rather than lines, i.e. x1 equals x2 and y1 equals y2:
<line class="arrow" x1="33.5" x2="33.5" y1="296" y2="296" marker-end="url(#redArrow)"></line>
Because this line / point doesn't have any direction there is no way to determine the orientation of the marker. Hence, your orient="auto" will not rotate the markers appropriately.
In the generator function for attr("marker-end") you are using d.ComparedToPreviousMonth which works fine determining which marker to use. The generator function for .attr("y2"), however, uses d.AverageValue to calcutate y2 and, thus, the direction of the line and the orientation of the marker. Since the AverageValues are all positive there is no change in direction of your lines / markers.
You may fix this by adjusting the y1 and y2 generators to:
.attr("y1", function (d) {
return h;
})
.attr("y2", function (d) {
var getValue = d.ComparedToPreviousMonth;
if (getValue < 0) {
return h - .001;
} else {
return h + .001;
}
})
For the purists the y2 generator may even be shortened to:
.attr("y2", function (d) {
return d.ComparedToPreviousMonth < 0 ? h - .001 : h + .001;
})
With that in place, you'll just have to set different refX values for your markers to visually position them on the x-axis:
.attr("refX", function(d) {
return d == "red" ? 0 : 10;
})
See the updated JSFiddle.
Here's one way. I've
Split the green and red arrows so they can have separate transforms
translated the markers down to the bottom of the bars
Made the markers overflow visible so they still display after the data is translated
// add text and arrow
svg.selectAll("line.arrow")
.data(["red"])
.enter()
.append("defs").append("marker")
.attr("id", function (d) {
return d + "Arrow";
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("markerWidth", 12)
.attr("markerHeight", 20)
.attr("orient", "auto")
.attr("overflow", "visible")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr("transform", "rotate(90, 5, 0) translate(56, 0)")
.attr("class", function (d) {
return "marker_" + d;
});
svg.selectAll("line.arrow")
.data(["green"])
.enter()
.append("defs").append("marker")
.attr("id", function (d) {
return d + "Arrow";
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("markerWidth", 12)
.attr("markerHeight", 20)
.attr("orient", "auto")
.attr("overflow", "visible")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr("transform", "rotate(270, 5, 0) translate(-56, 0)")
.attr("class", function (d) {
return "marker_" + d;
});

Sunburst Diagram Intelligent Text Wrapping

I have a zoomable sunburst diagram based on the great d3 "coffee wheel" example:
In that example there is simple text wrapping code that searches for a space in a label and wraps the text at the first space character:
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d) {
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) +
")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", click);
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) {
return d.depth ? d.name.split(" ")[0] : "";
});
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) {
return d.depth ? d.name.split(" ")[1] || "" : "";
});
This code works fine, but it is very basic, especially if the whole label would have fitted on one line in the available sunbust segment (eiether because the particular label is short or the available space is large enough as the sunburst is zoom'ed). In densely packed sunbursts wrapping the labels unnecesarily causes the diagram to look messy/cluttered.
I would like to make the text processing more intelligent and compare the length of the label with the available space (noting that the "width" of a segment changes depending on the depth of segments visible when zoom'ed).
Also if the available space for the label is known then the text wrapping could be more intelligent eg if the label contains more than two words code can decide whether it is better to break at first space or second.
If anyone has already solved this problem and has some example, greatly appreciated if it can be shared. Alternatively if anyone has any ideas about how the length of a label can be compared to the changing available space?

Get arrowheads to point at outer edge of node in D3

I'm new to D3 and I'm trying to create an interactive network visualization. I've copied large parts of this example, but I have changed the curved lines to straight ones by using SVG "lines" rather than "paths", and I've also scaled the nodes according to the data they represent. The problem is that my arrowheads (created with SVG markers) are at the ends of the lines. Since some of the nodes are large, the arrows get hidden behind them. I'd like my arrowheads to show up right at the outside edge of the node they point to.
Here is how I'm creating the markers and links:
svg.append("svg:defs").selectAll("marker")
.data(["prereq", "coreq"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
I noticed that the "refX" attribute specifies how far from the end of the line the arrowhead should show up. How can I make this dependent on the radius of the node it's pointing to? If I can't do that, could I instead change the endpoints of the lines themselves? I'm guessing I would do that in this function, which resets the endpoints of the lines as everything moves:
function tick() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
Which approach makes more sense, and how would I implement it?
Thanks Lars Kotthoff, I got this to work following the advice from the other question! First I switched from using lines to paths. I don't think I actually had to do that, but it made it easier to follow the other examples I was looking at because they used paths.
Then, I added a "radius" field to my nodes. I just did this when I set the radius attribute, by adding it as an actual field rather than returning the value immediately:
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", function(d) {
if (d.logic != null) {
d.radius = 5;
} else {
d.radius = node_scale(d.classSize);
}
return d.radius;
I then edited my tick() function to take this radius into account. This required a bit of simple geometry...
function tick(e) {
path.attr("d", function(d) {
// Total difference in x and y from source to target
diffX = d.target.x - d.source.x;
diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
// x and y distances from center to outside edge of target node
offsetX = (diffX * d.target.radius) / pathLength;
offsetY = (diffY * d.target.radius) / pathLength;
return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
});
Basically, the triangle formed by the path, it's total x change (diffX), and it's total y change (diffY) is a similar triangle to that formed by the segment of the path inside the target node (i.e. the node radius), the x change inside the target node (offsetX), and the y change inside the target node (offsetY). This means that the ratio of the target node radius to the total path length is equal to the ratio of offsetX to diffX and to the ratio of offsetY to diffY.
I also changed the refX value to 10 for the arrows. I'm not sure why that was necessary but now it seems to work!
I answered the same question over here. The answer uses vector math, it's quite useful for other calculations as well.

Resources