I'm currently trying to combine two of my graphs into one view. My second graph, the bar chart will update the data if you use the slider. When you slide it forward, it works fine and dandy but when you slide it backwards it doesn't redraw the chart. When I was working on the bar chart separately it was fully working, it just seems that when I combine it with the line graph that it's only partially working.
I'm trying to find the cause of the problem and I think it's something wrong with this block of code. If I comment it out the bar graph redraws properly.
var chart1 = d3.select("body").append("svg")
.attr("width", chart1_width + chart1_margin.left + chart1_margin.right)
.attr("height", chart1_height + chart1_margin.top + chart1_margin.bottom)
.append("g")
.attr("transform", "translate(" + chart1_margin.left + "," + chart1_margin.top + ")");
Link to my fiddle: http://jsfiddle.net/flyingburrito/a8fym1ma/3/
Thanks!
I moved the bar chart on top and that made it work fine. An easy solution would be to keep it like that.
A better solution would be to analyse why that happens. With D3 when things happen like this it will often be in your selects. That is your problem here
d3.select("#sexYear").on("input", function () {
debugger;
d3.select("svg").selectAll("rect").remove();
update(this.value);
});
You are selecting 'svg' which selects the first svg which is the line chart, not the bar chart. This is because 'd3.select' selects the first of whatever is being called. What I did was add a class called 'barchart' to the barchart and then I select the barchart as such
var chart2 = d3.select("body").append("svg")
.attr('class','barchart')
and
d3.select("#sexYear").on("input", function () {
debugger;
d3.select(".barchart").selectAll("rect").remove();
update(this.value);
fiddle
Related
I use dc.js for showing the results of multiple classification algorithms. More specifically, I want to show a precision recall chart (each point corresponds to a result of a classification system).
I already used a dc.js scatter chart for this which works fine.
Additionally I would like to have a d3 contour in the background of the chart which shows the F-measure.
This is already implemented. The only issue is that the contour part is in the foreground and not in the background of the chart.
Please have a look at the jsfiddle for a full example.
Two questions are still open for me because I'm not a dc.js or d3 expert:
Is there a way to put the contour in the background or the symbols(cycles) of the scatter chart in the foreground (I already tried it with the help of this stackoverflow question but with no success)
I used the 'g.brush' selector to get the area of the inner chart. This works fine as long as the brushing is turned on. Is the selector a good way to go or are there better alternatives (which may also work if brushing is switched off).
In my example I put the contour part in the upper left corner to see if it works but I also provide the code (currently uncommented) to increase the width and height of the contour to the correct size.
chart
.on('renderlet', function (chart) {
var innerChart = chart.select('g.brush');
var width = 300, height=300;
//getting the correct width, height
//var innerChartBoundingRect = innerChart.node().getBoundingClientRect();
//var width = innerChartBoundingRect.width, height=innerChartBoundingRect.height;
[contours, color] = generateFmeasureContours(width,height, 1);
innerChart
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
var symbols = chart.chartBodyG().selectAll('path.symbol');
symbols.moveToFront();
});
jsfiddle
Putting something in the background is a general purpose SVG skill.
SVG renders everything in the order it is declared, from back to front, so the key is to put your content syntactically before everything else in the chart.
I recommend encapsulating it in an svg <g> element, and to get the order right you can use d3-selection's insert method and the :first-child CSS selector instead of append:
.on('pretransition', function (chart) {
// add contour layer to back (beginning of svg) only if it doesn't exist
var contourLayer = chart.g().selectAll('g.contour-layer').data([0]);
contourLayer = contourLayer
.enter().insert('g', ':first-child')
.attr('class', 'contour-layer')
.attr('transform', 'translate(' + [chart.margins().left,chart.margins().top].join(',') + ')')
.merge(contourLayer);
A few more points on this implementation:
use dc's pretransition event because it happens immediately after rendering and redrawing (whereas renderlet waits for transitions to complete)
the pattern .data([0]).enter() adds the element only if it doesn't exist. (It binds a 1-element array; it doesn't matter what that element is.) This matters because the event handler will get called on every redraw and we don't want to keep adding layers.
we give our layer the distinct class name contour-layer so that we can identify it, and so the add-once pattern works
contourLayer = contourLayer.enter().insert(...)...merge(contourLayer) is another common D3 pattern to insert stuff and merge it back into the selection so that we treat insertion and modification the same later on. This would probably be simpler with the newer selection.join method but tbh I haven't tried that yet.
(I think there may also have been some improvements in ordering that might be easier than insert, but again, I'm going with what I know works.)
finally, we fetch the upper-left offset from the margin mixin
Next, we can retrieve the width and height of the actual chart body using
(sigh, undocumented) methods from dc.marginMixin:
var width = chart.effectiveWidth(), height = chart.effectiveHeight();
And we don't need to move dots to front or any of that; the rest of your code is as before except we use this new layer instead of drawing to the brushing layer:
contourLayer
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
Fork of your fiddle.
Again, if you'd like to collaborate on getting a contour example into dc.js, that would be awesome!
I would like to be able to recenter a Pie charts legend after it has been filtered. Slices/Legends will removed when filtering because we remove empty bins. I added a pretransition listener to chart2, but that seems to be to late because the legend y value is the previous value and not current.
.on('pretransition', chart => buildLegend (chart))
If Male is selected on the Gender Pie chart I want the 4 legend items on the Job Pie chart to be re-centered. Any suggestions?
You can see a jsFiddle example.
A little more digging around showed me how to reference and update SVG elements.
function recenterLegend(chart) {
chart.selectAll('g.dc-legend')
.attr('transform', function(d) {
let legendY = (300 - (chart.group().all().length * 16)) / 2;
let translate = 'translate(220,' + legendY + ')';
return translate ;
});
}
Here is the updated jsfiddle.
I have created an Angular directive for waterfall chart in D3, and want to update the chart as soon as values get updated. I've put the scope.$watch and I can see the new data coming to the directive. But the paintChart() function which I am creating to re-render the chart with new data does not seem to work correctly.
Even if I am able to receive different arrays of data, I am not able to re-paint the chart with different data. I tried to put chart and bar variable definitions out of the paint method and re-use them again with different parameters, but no success. Like if I put the following code out of the paintChart() function:
var chart = d3.select(rawSvg[0])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
The function stops working.
Here is the Fiddle.. The data is changing perfectly on selction, but I am struggling to get the chart re-painted properly on changing data.
UPDATE: I put the following code as first lines in paintChart() function, to remove previous SVG and it's associated elements before creating chart:
var svg = d3.select("svg");
svg.selectAll("*").remove();
Now the chart is successfully updating when different data is selected. But there are more issues:
When I select different data to update the chart, and re-select previously selected data, The last column of waterfall Chart disappears and shows NaN. The last column shows no error when loaded at first time. Only when data is toggled, this error occurs. You can reproduce this error by selecting different data from select-box and then re-selecting previously selected data. This seems a very strange issue.
It doesn't seem to me a correct way to remove SVG and it's associated groups/elements and render a totally new chart every time new data comes.
I have 2 charts on a page (A and B) and wish for some custom behavior to be added to chart B when a brush filter on chart A is performed.
I thought I could achieve this by doing something like;
charta.on('postRender', function(){
...
d3.selectAll('#chartb svg')
.data(nested, function(d){ return d.key})
.enter()
.append("g")
.attr("class", "aclass")... more code....
But this #chartb selector doesn't seem to work - when I inspect the DOM it has appended the <g> attributes to the <html> element and not the svg element I wanted to append to.
Is what I am trying to achieve possible?
If you are just adding stuff to the other chart, something like this should be possible. I don't think you will be able to select the generated items of the other chart and then apply a d3 join to it, because it is already joined.
I believe the problem with the code above is that d3.select is what you use to choose the context for a join, and d3.selectAll is what you use to actually make the data join. See
http://bost.ocks.org/mike/join/
So your code is trying to join to the chart and svg elements, which would have the effect you are describing. Instead you'll want to d3.select the svg and then d3.selectAll the elements you want to add - even though they don't exist yet! Yes, it's kind of a mind-bender; take a look at the above and the linked articles to get a better idea of it.
Note: there are dc convenience methods on the chart object which will execute the selects in the right context.
I got this working in the end by replacing the .enter() with repeated calls to datum() instead. A bit of a hack, but it works; If anyone can suggest a more d3ish way of acheiving this, I would be very grateful.
var svg = chart.svg();
nested.forEach(function(withValues) {
_(withValues.values).filter(function(d){return d.value < threshold}).forEach(function(timesMatchingThreshold){
svg.datum(timesMatchingThreshold)
.append("rect")
.style("opacity", 0.6)
.attr("transform", "translate(" + 30 + ", " + (-30) + ")")
.attr("class", "belowThreshold")
.attr("x", function(d) {return x(d.date)})
.attr("y", function(d) {return 200 - y(d.value)})
.attr("width", 3)
.attr("height", function(d) {return y(d.value)});
});
I`m using d3.js combined with GWT and every time the javascript code for creating a pie chart is called it appends a new one rather than replacing the old one with new values.
How can I change this code so that the old one is removed before appending the new one?
var svg = d3.select(".body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Rather then changing your code i would suggest creating it once, then build your diagram using your data and taking a look at the .enter() and .exit() methods of d3.js - so if your data changes the library would add/remove some elements.
Cheers
Just add
d3.select(".body").selectAll("svg").remove();
before your code to remove all SVG elements.