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
I have a parallel coordinates plot and I want to show lines onclick for d.dataset = train else hide them.
I wanted to access the row using .filter() like this:
data.filter(function(d) { return d.dataset == "train"; }).attr("visibility", "hidden");
and then set the attr visibility to hidden so that afterwards I can write a function with onclick to make the visibility visible, something like this:
// On Click, we want to add data to the array and chart
svg.on("click", function() {
var line = d3.mouse(this);
if (d.dataset === "train"){
//Display line of d.dataset === train
// line.attr("visibility", "visible");
}
});
This one I found also d3.selectAll("[dataset=train]").attr("visibility", "hidden"); but this doesn't work when doing with data elements right?
Right now I tried these and nothing happens. This is the jsfiddle I am working in. The line with "dataset":"train", is visible and doesn't hide.
How can I hide the lines when "dataset":"train", and then show them when onclick to the other lines in the parallel coordinates plot?
Any help would be highly appreciated.
First, make some marks on each path, for example, give a class name like coorPath so that it will be easier to find them. I added it for both background and foreground since I didn't know their difference.
svg.append("g")
.attr("class", "background coorPath") // add classname
.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("d", draw);
// CHANGE: duplicate with below code
/* svg.append("g")
.attr("class", "foreground coorPath")
.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("d", draw); */
// USE THE COLOR SCALE TO SET THE STROKE BASED ON THE DATA
foreground = svg.append("g")
.attr("class", "foreground coorPath")
.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("d", draw)
.style("stroke", function(d) {
var company = d.type.slice(0, d.type.indexOf(' '));
return color(company);
})
Then, find out the line of train, and make it invisible at first
let trainline = d3.selectAll("path").filter(function(d) { return d.dataset == "train"; })
trainline.attr("visibility", "hidden");
Show the line of train when one of other lines is clicked.
svg.selectAll(".coorPath").on("click", function(d) {
// show train when click others
trainline.attr("visibility", "visible")
});
a demo here
I have been going through some code I found online for creating and playing with directed graphs in D3 (http://bl.ocks.org/cjrd/6863459). I asked a question about this yesterday - Directed graph - node level CSS styles and that gave me a general idea of how to add CSS styles to SVG objects. However, I am still unable to do what I want. This is because, in the JS file, they seem to use the "nodes" to create "circles" and then render them all in one go instead of looping through them. In the updateGraph function, we have the lines -
// add new nodes
var newGs= thisGraph.circles.enter()
.append("g");
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);
First of all, I am not sure what the .append("g") means here. But more importantly, the line where the CSS class is applied,
newGs.classed(consts.circleGClass, true)
seems to apply the class to all "circles" in one line. Instead, I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node (to keep things simple, lets say that it the "title" starts with a certain text, I want to make it a blue circle). I still have no idea how to do this. Can someone help here? Again, the answers to my previous question helped a lot in understanding CSS but this other issue is still blocking me from doing what I want.
Adding comments for more clarity.
// here thisGraph.circles is data selection
//so if the data array has 10 elements in array it will generate 10 g or groups.
var newGs= thisGraph.circles.enter()
.append("g");
//here we are adding classes to the g
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
//attaching mouse event to the group
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);//attaching drag behavior to the group
What does this line mean?
newGs.classed(consts.circleGClass, true)
This line means to add class to all the created g DOM element or group.
In the code you referring it means circleGClass: "conceptG"
Read this on how to add CSS to DOM in D3
In the code you are appending circle to the group like this
newGs.append("circle")
.attr("r", String(consts.nodeRadius));
So now each group will have a circle.
Next Question
I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node
You can iterate through all the circles and add style depending on the data associated with the node like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.style("fill", function(d){
if(d)//some condition on data
{
return "red";
}
else
return "blue";
});
Question:
if you could tell me how to add CSS classes instead of "red", "blue" it would be every thing I need.
To add class you can do like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.attr("class", function(d){
function(d){
if(d)//some condition on data
{
return "red";//this will put class red in the node.
}
else
return "blue";//this will put class blue in the node.
});
Another way of doing the same:
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.classed({
'red': function(d) { return d.condition1 == "something"; },
'blue': function(d) { return d.condition1 != "something"; }
});
Hope this helps!
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.
Thanks to previous answers, I've made a map and a related graph with D3js.
The bar and the map are in specific divs, and I don't use the same data source. That's a part of my problem.
For the map, I used queue.js to load several files at a time. One of these files is a .csv which follow specifically the same order than the geojson where polygons are stocked. If I sort differently .csv's data, the correspondance with my .geojson's polygons is bad and my choropleth map become false.
Here's the associated code for the interactive polygons of the map :
svg.append("g").attr("class","zones")
.selectAll("path")
.data(bureaux.features) //"bureaux" is a reference to the geojson
.enter()
.append("path")
.attr("class", "bureau")
.attr("d", path)
.attr("fill", function(d,i){
if (progression[i].diff_ries<-16.1){ //"progression" is the reference to my .csv
return colors[0] // colors is a previous array with the choropleth's colors
}
else if (progression[i].diff_ries<-12.6){
return colors[1]
}
else if (progression[i].diff_ries<-9){
return colors[2]
}
else {return colors[3]
}
})
.on('mouseover', tip.show) // tip.show and tip.hide are specific functions of d3.js.tip
.on('mouseout', tip.hide)
};
No problem here, the code works fine. We arrived now to the graph. He used a .json array called at the beginning of the script, like this
var array=[{"id_bureau":905,"diff_keller":4.05,"diff_ries":-15.02},{etc}];
"id_bureau" is the common' index of my .geojson, my .csv and this .json's array. Then, I sort the array with a specific function. Here's a part of the code associated to the graph :
svg2.selectAll(".bar")
.data(array)
.enter().append("rect")
// I colour on part of the bars like the map
.attr("fill", function(d,i){
if (array[i].diff_ries<-16.1){
return colors[0]
}
else if (array[i].diff_ries<-12.6){
return colors[1]
}
else if (array[i].diff_ries<-9){
return colors[2]
}
else {return colors[3]
}
})
.attr("x", function (d) {
return x(Math.min(0, d.diff_ries));
})
.attr("y", function (d) {
return y(d.id_bureau);
})
.attr("width", function (d) {
return Math.abs(x(d.diff_ries) - x(0));
})
.attr("height", y.rangeBand());
// this part is for the other bars
svg2.selectAll(".bar")
.data(tableau)
.enter().append("rect")
// the others bars are always blue, so I used a simple class
.attr("class", "bar_k")
.attr("x", function (d) {
return x(Math.min(0, d.diff_keller));
})
.attr("y", function (d) {
return y(d.id_bureau);
})
.attr("width", function (d) {
return Math.abs(x(d.diff_keller) - x(0));
})
.attr("height", y.rangeBand());
svg2.append("g")
.attr("class", "x axis")
.call(xAxis);
svg2.append("g")
.attr("class", "y axis")
.append("line")
.attr("x1", x(0))
.attr("x2", x(0))
.attr("y2", height2);
So now, what I wan't to do is, when the mouse is over one polygon, to keep the correspondent bar of the graph more visible than the others with an opacity attribution (and when the mouse out, the opacity of all the graph returns to 1).
Maybe it seems obvious, but I don't get how I can correctly link the map and the graph using the "id_bureau" because they don't follow the same order like in this question : Change class of one element when hover over another element d3.
Does somebody know if I can easily transform the mouseover and mouseout events in the map's part to change at the same time my graph?
To highlight a feature on the map
To perform a focus on one feature, you just need a few line of CSS:
/* Turn off every features */
#carte:hover .bureau {
opacity:0.5;
}
/* Turn on the one you are specifically hovering */
#carte:hover .bureau:hover {
opacity:1;
}
To highlight a bar in your second chart
First of all, you need to distinguish the two kind of bar with two classes :
// First set of bars: .bar_k
svg2.selectAll(".bar_j")
.data(tableau)
.enter().append("rect")
// Important: I use a common class "bar" for both sets
.attr("class", "bar bar_j")
// etc...
// Second set of bars: .bar_k
svg2.selectAll(".bar_k")
.data(tableau)
.enter().append("rect")
.attr("class", "bar bar_k")
// etc...
Then you have to change your mouseenter/mouseleave functions accordingly:
svg.append("g").attr("class","zones")
.selectAll("path")
.data(bureaux.features)
.enter()
// creating paths
// ...
// ...
.on('mouseover', function(d, i) {
// You have to get the active id to highligth the right bar
var id = progression[i].id_bureau
// Then you select every bars (with the common class)
// to update opacities.
svg2.selectAll(".bar").style("opacity", function(d) {
return d.id_bureau == id ? 1 : 0.5;
});
tip.show(d,i);
})
.on('mouseout', function(d, i) {
// To restore the initial states, select every bars and
// set the opcitiy to 1
svg2.selectAll(".bar").style("opacity", 1);
tip.hide(d,i);
});
Here is a demo.
Performance issue
This implementation is kind of slow. You might improve it by toggling an "active" class to the bars you want to highlight.
An other good tail might be to gather the two kinds of bar in a single group that you describe singularly with an id (ie bureau187 for instance). That way you could select directly the bar you want into the mouseenter function and turn it on with an "active" class.
With this class you could mimic the strategy I implemented to highlight a feature and then remove svg2.selectAll(".bar").style("opacity", 1); from the mouseleave function :
/* Turn off every bars */
#carte:hover .bar {
opacity:0.5;
}
/* Turn on the one you want to highligth */
#carte:hover .bar.active {
opacity:1;
}
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("") // utf-8 character for the icon