This D3 js example shows all the code to produce a multi-line graph that can be toggled. Each line in the graph includes dots for existing data points.
While the lines can be toggled on/off, the dots stay stagnant. I would like for the toggle to work for both turning on/off the line & the dots that are associated with the same line.
I suspect that the svg.append("text") is the part that requires code update to also enable the dots to be turned on/off along with the line.
Here is the existing code snipet that turns on/off the line graph, but it doesn't turn on/off the dots.
svg.append("text")
.attr("x", (legendSpace/2)+i*legendSpace) // space legend
.attr("y", height + (margin.bottom/2)+ 5)
.attr("class", "legend") // style the legend
.style("font-size","15px") // Change the font size
.style("font-weight", "bold") // Change the font to bold
.style("text-anchor", "middle") // center the legend
.style("fill", function() { // Add the colours dynamically
return d.color = color(d.key); })
.on("click", function(){
// Determine if current line is visible
var active = d.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements based on the ID
d3.select("#tag"+d.key.replace(/\s+/g, ''))
.transition().duration(100)
.style("opacity", newOpacity);
// Update whether or not the elements are active
d.active = active;
})
.text(d.key);
Please help.
IDs are unique. You cannot set the same ID for several different DOM elements.
Solution: set classes instead.
For the lines:
.attr("class", 'tag'+d.key.replace(/\s+/g, ''))
And for the circles:
.attr("class", d=>'tag'+d.symbol.replace(/\s+/g, ''))
Then, get the class on the click event (use selectAll, not select):
d3.selectAll(".tag"+d.key.replace(/\s+/g, ''))
here is the updated fiddle: http://jsfiddle.net/gx4zc8tq/
Related
Newbie here. I am building a D3 bar-chart, and was able to animate the bar height; however, I have a text field next to each bar that shows the values, I was unable to make the text count up as the bar height grows.
I am using the .text attribute with I think where the problem is:
g.selectAll(".myText")
.transition()
.text(function(d){return (d.m6+"%")}) //code for counting from previous d.m6 value?
.attr("transform", function(d) { ...code for moving text location...)
.duration(700)
.ease(d3.easeLinear);
Any help will be greatly appreciated
Transitioning plain text will just result in the end value being displayed on transition start (after any delay):
For each selected element, sets the text content to the specified target value when the transition starts. .... Text is not interpolated by default because it is usually undesirable. (docs).
Here's an example of this at work:
var svg = d3.select("body")
.append("svg");
var text = svg.append("text")
.attr("x", 50)
.attr("y", 50)
.text(1);
text.transition()
.text(1000)
.duration(1000)
.delay(1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
However, you can interpolate text, the most canonical method would be to use transition.tween() (documentation). Though you could use .attrTween or even d3-timer. There are also a few other options out there but they are less straight forward.
A tween takes two arguments. The first argument is the name of the property being modified. The second argument is a tween value which returns a function of the form:
return function(t) { interpolator(t); })
Where t is a number between 0 and 1 representing the progress of the transition (0 = start, 1 = end), and interpolator is some function for interpolating a transition value for any point in the transition. With d3 this might be a d3-interpolator.
var svg = d3.select("body")
.append("svg");
var text = svg.append("text")
.attr("x", 50)
.attr("y", 50)
.text(1);
text.transition()
.tween("text", function() {
var selection = d3.select(this); // selection of node being transitioned
var start = d3.select(this).text(); // start value prior to transition
var end = 1000; // specified end value
var interpolator = d3.interpolateNumber(start,end); // d3 interpolator
return function(t) { selection.text(Math.round(interpolator(t))); }; // return value
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
I've just used rounding to keep the number formatted, otherwise the decimal points get pretty obnoxious. You could always apply some actual formatting.
I am trying to create a tooltip for my line chart that sends out dotted lines to the x and y axis (identical to this d3 n00b example, but my chart has multiple lines) http://www.d3noob.org/2014/07/my-favourite-tooltip-method-for-line.html
I have tried setting up two focus groups, tried adding the extra line info into the existing group, but all I can get is the date running on both lines but the dotted lines and data info only work on one.
Any help would be gratefully accepted.
Here is the chart with code underneath:
http://bl.ocks.org/anonymous/d1dbc221f95f6308b351
This is now fixed, renaming the two focus groups worked
Correct code:http://bl.ocks.org/anonymous/b77e904f34aa2162e1df
You are using the same variable for the two focus groups:
var focus = svg.append("g") // tooltip
.style("display", "none");
var focus = svg.append("g") // tooltip
.style("display", "none");
Name one variable focus1 and the other focus2 to make sure that they remain separate.
I would also recommend refactoring the code such that you don't have to call the exact same functions on both of these, once for the CPI line and once for RPIJ line. Instead, you should create a function which calls the required functions on a focus. You should pass the function which line to attach to and the correct focus group, i.e. focus1 and focus2.
Update for the edit:
As I understand it, you have now added text.y1, text.y2, text.y3 and text.y4 to both the focus groups and are effectively hiding the unnecessary information by setting the correct strokes to #fff here:
// place the value at the intersection RPIJ
focus2.append("text")
.attr("class", "y1")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "-.3em");
focus2.append("text")
.attr("class", "y2")
.attr("dx", 8)
.attr("dy", "-.3em");
// place the date at the intersection RPIJ
focus2.append("text")
.attr("class", "y3")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "1em");
focus2.append("text")
.attr("class", "y4")
.attr("dx", 8)
.attr("dy", "1em");
However, while doing it you have committed a couple of unique mistakes in each case.
For focus1, you do not create a text.y4 at all while for focus2, you update text.RPIJ instead of text.y3:
focus2.select("text.RPIJ")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.RPIJ) + ")")
.text("£" + d.RPIJ);
I'm basically using a modified version of : http://dimplejs.org/advanced_examples_viewer.html?id=advanced_bar_labels .
I'd like to be able to add for each value a border on the left as high as the value (with a specific color for that border).
I'm not really sure where to start for adding that.
Any ideas?
Thanks.
More details : This is what I'd like to obtain : https://dl.dropboxusercontent.com/u/2227188/Image%202.png - the border on the left is the issue. (jsfiddle.net/mkzTk/5/ this what I currently have which is pretty much what's in the example - I don't know where to start really for adding a border)
You could append a rectangle after drawing for each element of the series as follows:
mySeries.afterDraw = function (s, d) {
var shape = d3.select(s);
svg.append("rect")
.attr("x", shape.attr("x"))
.attr("y", shape.attr("y"))
.attr("height", shape.attr("height"))
.attr("width", "10px")
.style("fill", shape.style("stroke"))
.style("pointer-events", "none");
};
The example you mention already uses the afterDraw function so just add the contents above to the existing method for labelling.
It looks nice, here's an example:
http://jsbin.com/lorin/9/edit?js,output#J:L20
I would set up each bar + edge pair as its own group based on a certain data point, and then append two rect elements to that group. Differences in color can be used to give them their distinctive colors.
Your code would look something like this:
var monthBars = d3.selectAll('.monthBar') //These will be for each chart
.data(allMyData, idFunction) //Assign and key your data
.enter()
.append('g')
.classed('monthBar', true);
.each(function(d){
var taskGroups = d3.select(this).selectAll('.taskGroup')
.data(d.dataForThisMonth, taskIdFn)
.enter()
.append('g')
.classed('.taskGroup', true);
.attr('transform', ...) //Define the x and y positioning for the group
taskGroups.append('rect')
//Make this the 'body' rect with the text in it
taskGroups.append('rect')
//Make this the edge rect
})
I am drawing a piechart and i would like to have the labels with a break point. The labels are status and percentage which i get from csv file.
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc")
g.append("path")
.attr("d", arc)
.style("fill","#FFFFFF")
.transition()
.ease("bounce")
.duration(2000)
.delay(function(d, i) {return i * 1000;})
.style("fill", function(d) {return color(d.data.Source);});
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.html(function(d) {
if (eval(d.data.Components) >0)
{
return ((d.data.status) + " </br> " + " " + d.data.Percentage + "%");
}
});
An alternative to the tspan and text elements is the foreignObject. You can append a foreignObject, then append normal HTML. For you it would like something similar to this:
g.append("foreignObject")
.attr("width", "100px")
.attr("height", "100px")
.append("xhtml:div")
.html(function(d) {
return ((d.data.status) + " <br> " + " " + d.data.Percentage + "%");
});
I've found this approach easier to work with than tspan and text elements in the past, as it doesn't involve appending and positioning more elements than necessary.
The text element does not allow br tags in it.
From the docs:
Each ‘text’ element causes a single string of text to be rendered. SVG performs no automatic line breaking or word wrapping. To achieve the effect of multiple lines of text, use one of the following methods:
The author or authoring package needs to pre-compute the line breaks and use multiple ‘text’ elements (one for each line of text).
The author or authoring package needs to pre-compute the line breaks and use a single ‘text’ element with one or more ‘tspan’ child
elements with appropriate values for attributes ‘x’, ‘y’, ‘dx’ and
‘dy’ to set new start positions for those characters which start new
lines. (This approach allows user text selection across multiple lines
of text -- see Text selection and clipboard operations.)
Express the text to be rendered in another XML namespace such as XHTML [XHTML] embedded inline within a ‘foreignObject’ element. (Note:
the exact semantics of this approach are not completely defined at
this time.)
In your case, for example, you'll have to put d.data.status and d.data.Percentage + "%" in two different tspans in the text element and manually specify dy on them to align them vertically, like the examples shown here.
I would like to show a text on mouseover.
var circle = elemEnter.append("circle")
.attr("r", function(d){return d.r*2} )
.attr("dx", function(d){return d.x} )
.attr("stroke","gray")
.attr("fill", "#91c6ed")
.on("mouseover", function()
{d3.select(this).style("stroke", "#ff7f0e");
d3.select(this).style("stroke-width", "2px");
elemEnter.append("text")
.text(function(d){return d.name})})
.on("mouseout", function()
{d3.select(this).style("stroke", "gray");
d3.select(this).style("stroke-width", "1px");});
This piece of code works but show all the names on all the circles and when I try to replace
elemEnter.append("text").text(function(d){return d.name})
by
d3.select(this).append("text").text(d.name)
nothing happens.
I think it is possible to do it but I don't know what I'm doing wrong.
You can't append text to a circle. You need to start with a g and append the circle to the g and append the text to the g. Keep in mind that the g will not have cx/cy attributes and so you'll need to put that data into the following syntax:
.attr("transform", function (d) {return "translate("+d.X+","+d.Y")"})
If you're binding data, bind it to the g and then just naked append the circle and text to the g.