This question already has answers here:
clicking a node in d3 from a button outside the svg
(2 answers)
Closed 7 years ago.
I'm trying to link interactions on a bar chart with related data in a line chart using d3.js. I have it working now so hovering over a line highlights the associated bar, but am having trouble getting the reverse to work (i.e. hovering over a bar to highlight the related line).
I am relatively new at this, but I'm guessing it has something to do with how I'm trying to access the underlying data in the line chart to identify a match.
I've searched through stackoverflow answers and elsewhere but can't figure out what I am missing. Suggestions?
The code on bl.ocks.org
And here's the code snippet for the bar chart mouseover that's not working.
barchart.selectAll("rect")
.on("mouseover", function(d) {
activeState = d.state;
linechart.selectAll("line")
.classed("pathLight", function(d) {
if ( d.state == activeState) return true;
else return false;
});
console.log(activeState);
})
.on("mouseout", function() {
d3.selectAll("path")
.attr("class", "pathBase");
});
Edit:
Found another answer that it is helpful for questions like mine:
clicking a node in d3 from a button outside the svg
Hope below code will work for you.
Keep below code in mouseover of barChart
linechart.selectAll("g")
.each(function(d) {
if(d){
if ( d.state == activeState){
console.log(d3.select(this).select("path"));
d3.select(this).select("path").classed("pathLight", true);
return true;
}
else{
return false;
}
}
});
//Below Code is to show the highlighted region name, and don't forget to remove this in mouseout of barChart
var xPosition = xLabel + 8;
var yPosition = h/2;
linechart.append("text")
.attr("id", "hoverLabel")
.attr("x", xPosition)
.attr("y", yPosition)
.attr("text-anchor", "start")
.attr("font-family", "ff-nuvo-sc-web-pro-1,ff-nuvo-sc-web-pro-2, sans-serif")
.attr("font-size", "14px")
.text( activeState);
remove below code from that mouseouver
linechart.selectAll("line")
.classed("pathLight", function(d) {
if ( d.state == activeState) return true;
else return false;
});
If it's not working ask me, for more.
Related
var IndChart = dc.geoChoroplethChart("#india-chart");
var states = data.dimension(function (d) {
return d["state_name"];
});
var stateRaisedSum = states.group().reduceSum(function (d) {
return d["popolation"];
});
IndChart
.width(700)
.height(500)
.dimension(states)
.group(stateRaisedSum)
.colors(d3.scale.ordinal().domain().range(["#27AE60", "#F1C40F", "#F39C12","#CB4335"]))
.overlayGeoJson(statesJson.features, "state", function (d) { //console.log(d.properties.name);
return d.id;
})
.projection(d3.geo.mercator().center([95, 22]).scale(940))
.renderLabel(true)
.title(function (d) { console.log(d); return d.key + " : " + d.value ;
})
.label(function (d) { console.log(d);}) ;
wanted to add Label or custom value(25%, added in Map chart screen-shots) in map chart for each path using dc.js.
In the comments above, you found or created a working example that answers your original question. Then you asked how to make it work for two charts on the same page.
This is just a matter of getting the selectors right, and also understanding how dc.js renders and redraws work.
First off, that example does
var labelG = d3.select("svg")
which will always select the first svg element on the page. You could fix this by making the selector more specific, i.e. #us-chart svg and #us-chart2 svg, but I prefer to use the chart.select() function, which selects within the DOM tree of the specific chart.
Next, it's important to remember that when you render a chart, it will remove everything and start from scratch. This example calls dc.renderAll() twice, so any modifications made to the first chart will be lost on the second render.
In contrast, a redraw happens when any filter is changed, and it incrementally changes the chart, keeping the previous content.
I prefer to listen to dc.js chart events and make my modifications then. That way, every time the chart is rendered or redrawn, modifications can be made.
In particular, I try to use the pretransition event whenever possible for modifying charts. This happens right after drawing, so you have a chance to change things without any glitches or pauses.
Always add event listeners before rendering the chart.
Adding (the same) handler for both charts and then rendering, looks like this:
usChart.on('pretransition', function(chart) {
var project = d3.geo.albersUsa();
var labelG = chart.select("svg")
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("id", "labelG")
.attr("class", "Title");
labelG.selectAll("text")
.data(labels.features)
.enter().append("svg:text")
.text(function(d){return d.properties.name;})
.attr("x", function(d){return project(d.geometry.coordinates)[0];})
.attr("y", function(d){return project(d.geometry.coordinates)[1];})
.attr("dx", "-1em");
});
usChart2.on('pretransition', function(chart) {
var project = d3.geo.albersUsa();
var labelG = chart.select("svg")
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("id", "labelG")
.attr("class", "Title");
labelG.selectAll("text")
.data(labels.features)
.enter().append("svg:text")
.text(function(d){return d.properties.name;})
.attr("x", function(d){return project(d.geometry.coordinates)[0];})
.attr("y", function(d){return project(d.geometry.coordinates)[1];})
.attr("dx", "-1em");
});
dc.renderAll();
I used one more trick there: since pretransition happens for both renders and redraws, but we only want to add these labels once, I use this pattern:
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("class", "Title");
This is the simplest data binding there is: it says we want one g.Title and its data is just the value 0. Since we give the g element the Title class, this ensures that we'll add this element just once.
Finally, the result of this expression is an enter selection, so we will only add text elements when the Title layer is new.
Fork of your fiddle.
I'm trying to make a D3 chord diagram following Mike Bostock's v4 example and a v3 example with mouseover events.
In the v3 example above, there is a fade function that highlights specific ribbbons for a mouseover'd group:
function fade(opacity) {
return function(d, i) {
svg.selectAll("ribbons")
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
};
}
Though for the life of me I can't get it working in my v4 example, despite trying to put it in a similar spot:
//Draw the ribbons that go from group to group
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
.on("mouseover", fade(.1)) /* Where attempt at mouseover is made */
.on("mouseout", fade(1));
.append("title").
text(function(d){return chordTip(d);})
Here is a jsfiddle with my attempt (with working tooltips, but non-working fade mouseovers): https://jsfiddle.net/wcat76y1/3/
I believe my error has something to do with how I'm segregating variables, but I'm not sure how exactly where I went wrong.
I found part 2 of the previous answer didn't work for me but this did.
function fade(opacity) {
return function(d, i) {
d3.selectAll("g.ribbons path")
.filter(function(d) {
return d.source.index != i && d.target.index!= i;
})
.transition()
.style("opacity", opacity);
};
}
The line you had with just ribbons didn't seem to select the right element. I agree with the first corrections you did, but didn't use the text elements.
Here's my forked fiddle https://jsfiddle.net/kLe38tff/
I managed to figure it out. Looks like there were several issues that combined to obfuscate my attempted solutions. While I can mark this as solved, there are still some elements to my solution that I don't understand .
I was adding the mouseover events to the wrong section (DOM element?). It should have been added to the operations on the group elements not ribbon elements. Hence, it should have gone here:
//Draw the radial arcs for each group
group.append("path")
.style("fill", function(d) { return color(d.index); })
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
.attr("d", arc)
.on("mouseover", fade(.1))
.on("mouseout", fade(1))
Using svg.selectAll("ribbons") in the fade function was not selecting the elements I wanted (I'm still not sure why not...). Replacing that command with just the variable ribbons allowed the selection to complete successfully:
function fade(opacity) {
return function(d, i) {
ribbons
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
};
}
Adding <text> elements to the variable ribbons during its creation led to the opacity being modified on the text element instead of the ribbon element itself. Separating this to a separate line of code fixed the final issue.
The updated JSFiddle shows the fully working example: https://jsfiddle.net/wcat76y1/5/
I want to implement stack bar with toggle legend using D3.js ,on click on the legend, stack bar should get redrawn.If the legend was active,rectangle slab corresponding to the legend should get disappear and vise versa.
On click on the legend, I am not able to update the data binded with the group element and rect element present inside the group element properly.
In the DOM tree,on click on the legend,rect element is getting appended and added to first group element, rect element should actually get updated only.
You can view the source code in Jsfiddle here
I want something similar to stack bar with legend selection as implemented here in nvd3
function redraw() {
var legendselector = d3.selectAll("g.rect");
var legendData = legendselector.data();
var columnObj = legendData.filter(function(d, i) {
if (d.active == true)
return d;
});
var remapped = columnObj.map(function(cause) {
return dataArch.map(function(d, i) {
return {
x : d.timeStamp,
y : d[cause.errorType]
};
});
});
var stacked = d3.layout.stack()(remapped);
valgroup = stackBarGroup.selectAll("g.valgroup").data(stacked, function(d) {
return d;
}).attr("class", "valgroup");
valgroup.enter().append("svg:g").attr("class", "valgroup").style("fill",
function(d, i) {
return columnObj[i].color;
}).style("stroke", function(d, i) {
return d3.rgb(columnObj[i].color).darker();
});
valgroup.exit().remove();
rect = valgroup.selectAll("rectangle");
// Add a rect for each date.
rect = valgroup.selectAll("rectangle").data(function(d, i) {
return d;
}).enter().append('rect');
valgroup.exit().remove();
rect.attr("x", function(d) {
return x(d.x);
}).attr("y", function(d) {
return y(d.y0 + d.y);
}).attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
}).attr("width", 6);
}
function redraw() did not use transition inside it
You need to get more understanding about object constancy. (Three state described by the author)
I wrote an example of group chart in d3, the legend is interactable and works well, because i am new to d3, maybe the pattern or standard used is not very formal.
Listed it below only for you reference, hope it helps, good luck :-p
fiddle
How can d3 areas have their transitions animated? I've seen examples for lines but can't find anything on animating an area.
Eg area:
var area = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(height)
.y1(function(d) { return y(d.y); });
Update: I've found an example for an area chart but I don't understand it. How is this function creating the area transition?
function transition() {
d3.selectAll("path")
.data(function() {
var d = layers1;
layers1 = layers0;
return layers0 = d;
})
.transition()
.duration(2500)
.attr("d", area);
}
The transition of areas works just like for other attributes. Only in case of areas, we are interpolating strings instead of interpolating numbers. When you call the area function with some data, then it produces a string which looks like M0,213L4,214L9,215 ... L130,255.7, which is a DSL used for the d attribute. When you change the data you pass to the area function, this string changes and D3 interpolates them.
Regarding the example you have found, the interesting bit which causes the transition is only this:
.transition()
.duration(2500)
.attr("d", area);
The other part merely is a fancy way of alternatively returning layers1 and layers0 as the data for the area function on consecutive calls.
d3.selectAll("path")
.data(function() {
var d = layers1;
layers1 = layers0;
return layers0 = d;
})
Thanks #neptunemo for your suggestion. However, your code is too specific for your problem. I would like to take a general case for better illustration of your idea:
Please see the full code from an example of d3noob: https://bl.ocks.org/d3noob/119a138ef9bd1d8f0a8d57ea72355252
Original code of area generator:
var area = d3.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
Modified code of area generator:
var area = function(datum, boolean) {
return d3.area()
.y0(height)
.y1(function (d) { return y(d.close); })
.x(function (d) { return boolean ? x(d.date) : 0; })
(datum);
}
datum is to take the data,
boolean is to control the:
.x() (in case you want the animation along x-axis)
.y1() (in case you want the animation along y-axis)
By setting boolean to false, we're able to set .x() or .y1() to 0.
This will help us to set the initial state of area before triggering the transition process.
Modified code of area transition:
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", d => area(d, false))
.attr("fill", "lightsteelblue")
.transition()
.duration(2000)
.attr("d", d => area(d,true));
Effects?
case of controlling .x()
case of controlling .y1()
Note: The issue I met is that I cannot synchronize the animation of line and area :(
Bit late to the party, but:
I solved the problem by modifying the original 'area' function, passing two variables: the data, and the field I wish to chart:
var area = function(datum, field) {
return d3.svg.area()
.x(function(d) {
return xScale(d.period_end);
})
.y0(height)
.y1(function(d) {
return yScale(d[field] || 0);
})(datum);
};
Then when you draw the path, just use basic transition. First time, passing no 'field', resulting in drawing zero values, and then - after transition() - passing the field I wanted:
areaChart.append('path')
.attr('class', 'area')
.attr('d', area(chartData))
.attr('fill', function() {
return chartColor;
})
.attr('opacity', 0.15)
.transition().duration(chartSettings.duration)
.attr('d', area(chartData, 'value'));
Works nicely without the need for sub functions. Exactly the same can of course be done for line charts.
I have this working code. where the d3 part is basically:
var bar = chart.append("div").attr("class", "chart")
.selectAll('div')
.data(scope.data.sort().reverse()).enter().append("div")
.transition().ease("elastic")
.style("width", function(d) { return (d[0]/sum)*attrs.chartWidth + "px"; })//This is where I base the width as a precentage from the sum and calculate it according to the chart-width attribute
.style("background-color",function(){i++;if (i<=colors.length-1){return colors[i-1]} else {return colors[(i-1)%colors.length]}}).text(function(d) { return d[1] ; })
but when I try to append("span") in the chaining so the text would be on the span and not in the parent div. the text just disappears and the dev console shows no clue of both the span and the text. Also tried insert("span") and even replacing the .text for .html(function(d){return "<span>"+d[1]+"</span>"}
neither work.
any clues? Thanks!
The problem is that you are starting a transition in the chain. The transition object provides many functions just like a normal d3.selection including .remove, .text and .html, but does not allow .append operation.
You should re-factor the code to read:
var bar = chart.append("div").attr("class", "chart")
.selectAll('div')
.data(scope.data.sort().reverse()).enter().append("div");
bar
.transition().ease("elastic")
.style("width", function(d) { return (d[0]/sum)*attrs.chartWidth + "px"; })//This is where I base the width as a precentage from the sum and calculate it according to the chart-width attribute
.style("background-color",function(){i++;if (i<=colors.length-1){return colors[i-1]} else {return colors[(i-1)%colors.length]}}) })
bar.append('span')
.text(function(d) { return d[1] });
Demo
As a side note, while selecting the background-color, you do not need to maintain the index variable yourself, d3 passes the data d and the index i to the setter function you provide to .style:
.style("background-color",
function(d, i){ // <-- 'd' and 'i' are passed by d3
if (i<=colors.length-1)
{return colors[i-1]}
else {return colors[(i-1)%colors.length]}})
})