D3js: two .append methods on the same selection can't be in the same line - d3.js

The problem is that it would look prettier if the code here:
var nodesgroup = svg.append("g")
.attr('class', 'nodes')
.selectAll('circle')
.data(data['nodes'])
.enter()
.append('g');
nodesgroup.append('circle')
.attr('r', 20)
.call(d3.drag() //Define what to do on drag events
.on("start", dragStarted)
.on("drag", dragging)
.on("end", dragEnded));
was on one line. But when you remove the nodesgroup of the second paragraph and the ; after the first paragraph, the circles do not longer get drawn. I don't know why as chaining these two paragraphs should be no different from first taking the output of the first paragraph and then taking each of these items and doing operation on them this way.
My understanding of what happens in the above code: the result of append('g') (a D3-selection) is stored in nodesgroup.
Then, in the second paragraph, for each of the elements in the selection, (because d3 has overridden append I assume?) .append('circle') gets executed.
Link to JSFiddle

The circles are drawn; in your JSFiddle they can be seen in the top left corner. The problem is that by concatenating the two statements you are changing the contents of the selection contained in nodesgroup. Keeping it in two different statements, nodesgroup will contain a selection of newly appended g elements. If, on the other hand, concatenating both statements into one, the selection will contain the circle elements which are appended to the former group elements.
While the first approach works with your tick callback, the latter will break that tick function. The tick function expects the nodesgroup to contain group elements in order to select the circles and texts contained within. This is the reason why the circles are drawn but are not moving around according to the force layout.

Related

Display text w/ brushed values in d3.js

I have some svg circles along a single axis and d3.brushX activated. In this small Observable example, I want displayText to respond to the brush, displaying the number of circles selected by the brush.
The values currently respond to the brush, but it just writes the text on top of the existing value. How do I clear it every time the brush moves?
Give the text a class, select all DOM elements of the class and remove them right before you append the text within your brushed function.
svg.selectAll(".label").remove()
svg.append("text")
.attr("class","label")
.attr("x", width/2)
.attr("y", 50)
.text(displayText)
OR create the text element outside your brushed function
svg.append("text")
.attr("class","label")
.attr("x", width/2)
.attr("y", 50)
...then, just change the text within the brushed function
svg.select(".label").text(displayText)
The second approach is probably better...

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!

dragging line in linechart: update line to new values?

Yet another d3.js dragging/updating question: I previously got help on creating linecharts where you could drag a dot and the connecting line would adjust. Now I am trying to modify this further so that it is possible to both drag a dot (to move just one datapoint) OR drag the line to move all dots/the entire series represented by the selected line.
I think I have most of it sorted now - expect for the correct updating of the dragged line.
Following a linedrag, the data is updated correctly but I can't get the line to redraw as I want. I think part if it may be due to the use of a d3.line function to draw the connectlines - I think there is some kind of spilling over happening between the paths of the connectline and the paths of the axes.
This is the code to update the lines that works partly yet in mysterious ways:
focus.selectAll("path")
.merge(focus.selectAll('connectline'))
.data(dByTime)
.attr("d", function(d) {
return connectLine(d.values);
})
.attr("stroke", function(d) {
return color(d.key);
})
.attr('stroke-width', 4)
.style("fill", "none")
.style('cursor', 'pointer');
So: this works but doesn't. When selecting the dark line (tt:2) at first, there is a moving line of the correct shape etc but it's located too low (by about the y-axis range I think?) and the old connectline does not disappear while the x-axis path does disappear. The new (lower) line has full functionality in that if you move one of the dots associated with the line, the line gets adjusted, and you can also move the line again and see the dots AND line move....!?
When dragging the light blue line first (tt:1), the same happens, but when you repeat drag it, the y axis path also disappears and eventually a fully working connect line appears for the dark blue (tt 2) series...
A fiddle of the full code is here: https://jsfiddle.net/m931k6w2/
I am a) perplexed by the difference in location for the newly appearing lines relative to the old lines, the y axis and the updated values in the data and b) unable to isolate the paths of the connectline from the other paths and wonder if I should not find a workaround that does not use:
var connectLine = d3.line()
.x(function(d) {
return x(d.tt);
})
.y(function(d) {
return y(d.RT);
});
yet I don't know how else to draw the lines in the first place.
Any helps much appreciated!

D3 multi line chart - strange animation

I have created a multi line chart with a simple animation. In the beginning there are no data and after clicking a button new values are emulated and the line "move" left. The move is animated using a "shift".
The problem occurs when the lines "fill" the whole graph area (that means there are y values for all x values) and then the lines are animated in a different way. It looks like the y values are animated on a curve, not slided to the left.
The animation works good for both axes:
svg.selectAll("g .x.axis")
.transition()
.duration(500)
.ease("linear")
.call(xAxis);
svg.selectAll("g .y.axis")
.transition()
.duration(500)
.ease("linear")
.call(yAxis);
And not for lines (this code helped me a lot)
svg.selectAll("g .city path")
.data(processedData).transition().duration(500)
.ease("linear")
.attr("d", function(d, i) { return line(d.values); })
.attr("transform", null);
The Fiddle is accessible here.
Thanks for help.
The problem is that you're deleting data when there is too much. The way d3 matches data to existing data (when you call the .data() function) is by index. That is, the first element in the array that you pass to .data() matches the first bound data element, regardless of what the data actually looks like.
What happens in your case is that as soon as you start deleting data, the individual data points are updated instead of shifted. That's why you're seeing the "squiggle" -- it's updating each data point to its new value, which is the value the data point to the right had before.
With the code you currently have, this is hard to fix because you are not matching the data for individual lines explicitly. I would recommend that you have a look at nested selections which allow you to draw multiple lines and still explicitly match the data for individual ones. The key is to use the optional second argument to .data() to supply a function that tells it how to match the data (see the documentation). This way you can tell it that some data points disappeared and the other ones should be shifted.
you can get around this problem in 2 step.
in function update() : redraw you .data() with the new point at the end but without remove the first old point (with animation), like that each key is the same before and after transition.
at the end of function update() : you can remove the old value and redraw .data() without animation.

Update multi-line graph D3.js

I'm attempting to update a d3 multi-line graph by pulling data at 5 second intervals from a mySQL database using PHP. At the moment the graph is displaying but not updating - I know that there is something not right with my updateData function but have tried everything can think of and nothing seems to work. Can anyone please help?
https://gist.github.com/Majella/ab32fe0151fd487da3f6
UPDATE:
As you can see the x-axis line is only showing sporadically and some of the lines aren't lined up with the y-axis.
Updated gist:
https://gist.github.com/Majella/ab32fe0151fd487da3f6
UPDATE 2: For some bizarre reason the lines are changing colour - or moving completely not exactly sure. So while on graph above the lines are from top - blue, orange then white - when graph updating the blue might move to bottom with orange on top and white in middle etc - but happening randomly?
In your original drawing of the graph, you correctly use:
var parameter = svg.selectAll(".parameter")
.data(data, function(d) { return d.key; })
.enter().append("g")
.attr("class", "parameter");
which joins the data (data) to the elements (g.parameter)
During your update function, you will need to join the data again in order to perform updates, deletes, and adds of elements. The 3 little circles tutorial is an excellent place to learn more about this.
Anyway, in your update function, you may want something like this (untested):
// re-acquire joined data
var containers = svg.selectAll("g.parameter")
.data( data );
// update existing elements for this data
containers
.select( "path.line" )
.attr( "d", function(d) { return line(d.values); })

Resources