Unable to repaint waterfall chart in D3js with Angular - d3.js

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.

Related

Rendering in the background of a dc.js chart with renderlet

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!

d3.js brush() inherits incorrect height and width From svg parent using viewBox

My root question is: is there a way to set the d3 brush window size so that it is properly inherited from its parent like the other SVG components when using viewBox?
Details:
I have two charts that are linked. The first displays a set of line charts and the second is a basic chart that allows zooming on the primary chart. It looks almost exactly like this example: https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
I wanted to add svg resizing to the charts to allow my charts to adjust automatically. For most of my charts, this works great, but I have a problem with the brush windows. When I use an SVG viewbox rather than explicitly using the parent sizes, then my brush region gets set to a small region of the overall svg (in my case the brush window is 300px x 150px area while the parent d3G in code below is actually 1150*100).
Here are the code snippets for initializing the chart and the brush window in a manner that works correctly:
this.width = this.parentNativeElement.offsetWidth - this.margin.left - this.margin.right;
this.height = this.parentNativeElement.offsetHeight - this.margin.top - this.margin.bottom;
this.d3G = this.d3.select(this.parentNativeElement).append('svg')
//change in the next two attr lines
.attr('width', this.parentNativeElement.offsetWidth)
.attr('height', this.parentNativeElement.offsetHeight)
.append<SVGElement>('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
// ... some other initialization here (scale, domain, line drawing functions etc)
this.d3G.append('g')
.attr('class', 'brush')
.call(this.d3.brushX().on('end', () => this.brushed() )
For all of my charts, the only real change I needed to make was to change the 'width' and 'height' attr lines and replace them with:
.attr('viewBox', '0 0 ' + this.parentNativeElement.offsetWidth + ' ' + this.parentNativeElement.offsetHeight)
.attr('preserveAspectRatio', 'none')
.attr('width', '100%')
I have seen other posts about issues initializing the Extents on Firefox: https://github.com/d3/d3-brush/issues/13, but I am not setting extents, I am having errors with the default height and width settings. This is also occurring on Chrome, not firefox.
I do not have any issues with the default height and width setting for any other svg subsets, only the brush window.
I also have verified that I do not have any static css settings that would define a static window size. The Google devtools show the height and width are auto/auto on that element as well.
This turned out to have a fairly trivial fix. Apparently, the brush does not accept default (from parent) settings to initialize both height and width. If I set one, and then use 100% of the other, then that is enough information for the brush to initialize correctly. Here is ultimately what I used for configuring the resizing:
.attr('width', '100%')
.attr('height', this.parentNativeElement.offsetHeight)
.attr('viewBox', '0 0 ' + this.parentNativeElement.offsetWidth + ' ' + this.parentNativeElement.offsetHeight)
.attr('preserveAspectRatio', 'none')
This results in an svg that maintains a static height, and scales only in the horizontal direction, but does not have any issues with the brush.

Graph not redrawing properly

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

replacing d3.js diagram instead of creating a new one every time

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.

d3.js interactive grouped bar chart alternating rotation

Using the example from http://blog.nextgenetics.net/demo/entry0032/ I wanted to try to label the grouped bar selected on mouseover as in http://bl.ocks.org/3177376.
Everything seems to work. However, the rotation only applies to the first set of bars and not the second. When I debug, the line
.attr("transform",
"rotate(-90 " + x(d.date) + x1(p.key) + "," + y(d.perf) + ")");
seems to get skipped. I have reached my limits. Please help. Does anyone have any suggestions?
very simple javascript error
x(d.date) + x1(p.key)
should be
(x(d.date) + x1(p.key))
a full working version can now be found at http://bl.ocks.org/3182035:

Resources