How to sort a grouped bar-chart d3js v4 - javascript

In this block (FIXED) I've tried to do a sorting function in a similar fashion to this.
It technically sorts the bars but not in the expected way, if you check the sorting checkbox and shift years you can see what I mean.
I thought that it had something to do with the fact that it's only sorting based on data and not keys and/or the copy variable but I've tried sorting in all kinds of ways based on the mentioned variables without any success.
Not sure what I'm missing, appreciate any help!

Here you go! There was not much change required in your previous code.
Plunker
So this was related to the data binding to the barGroups. Every time the data was sorted or changed, new data was bound to the "g.layer" and with d3's update methodology, this would how it would work.
Changes in the new code:
Moved barGroups code above the data sorting with no transform attribute.
Added transform attribute to the groups after x0 domain is defined.
Relevant code:
Above the sort function:
// bars
let barGroups = g.selectAll("g.layer").data(data);
barGroups.enter().append("g")
.classed('layer', true);
barGroups.exit().remove();
Once x0 domain is set:
g.selectAll("g.layer").transition().duration(Globalvar.durations)
.attr("transform", function(d, i) {
return "translate(" + x0(d.State) + ",0)";
});
Hope this helps! :)

Related

Scatterplot update: same number of data points, but new data points

Apologies in advance that my question does not include code, but rather is a high level question on D3 and how to build my app correctly. I will attempt to make my question as clear and concise as possible:
I am building a React / D3 app that creates a scatter graph of NBA team logos, that allows users to click buttons to choose variables for the X and Y axis. The user can also filter the graph to include only certain teams (those in a particular division of the NBA).
Here is a quick demo gif of the app that features the main problem I am having:
.
.
.
.
and here is the link to my app for anyone interested.
What is working correctly
When I change the X or Y axis button (2nd half of the gif), the team logos correctly slide to their new locations.
What is working incorrectly
When I change the division (1st half of gif), it changes the 5 team logos that are showing, which is correct. However, the animation (which I show a few times in the gif) is incorrect. The old logos should simply disappear in place, and the new logos should simply appear in place. Instead, the logos change and slide.
I understand why the animation is doing this - D3 sees 5 data points before the update, and 5 data points after the update, but doesn't distinguish that my points are unique (different team logos). Since the updated data points have new (x,y) locations (different stats for each team), it simply animates the old logos to the locations of the new logos.
My proposed fix
I think the structure of my app is holding be back with regards to fixing this. Currently, I have a container component that loads the data, filters the teams (based on the division selected), and then passes the filtered data (an array of objects with the team stats) into a graph component that creates the logo scatter graph.
If, on the other hand, I pass the full object (of all 30 teams) to the graph component, then could I fix this problem by simply having D3 change the "fill" of the markers to transparent when they are filtered out? This way, there are always 30 logos being plotted, although 25 would be invisible, and the logos displaying should animate correctly.
Thanks in advance for your thoughts!
Edit: please let me know if the post is unclear in any way and I will try to clarify. I try to avoid posting Qs without code, but this is a fairly high level Q that focuses on how the D3 general update pattern works, and how I can build a graph with a specific animation that works within the general update pattern framework.
Edit2: The radio buttons are built in the container component here. Using my API to grab the data from my database, and then using these radio buttons to filter the data, are all done in the container component. I am considering bringing these radio buttons into the graph component and building them with D3. I think I may have to.
Edit3: Should have shared earlier, here is the D3 code that makes these markers:
const update = svg.select('g.points')
.selectAll("rect")
.data(graphData);
// Second exit and remove
update.exit().remove();
// Third Update
update
.enter()
.append("rect")
.attr("transform", d => `translate(${(xScale(+d[xColname]) - logoRadius)}, ${(yScale(+d[yColname]) - logoRadius)})`)
.attr("x", 0).attr("y", 0)
.attr("height", logoRadius)
.attr("width", logoRadius)
.attr("fill", d => `url(#teamlogo-${d.teamAbbrev})`)
.attr("opacity", 0.95);
// And Fourth transition
update
.transition()
.duration(750)
.delay((d, i) => i * 15)
.attr("transform", d => `translate(${(xScale(+d[xColname]) - logoRadius)}, ${(yScale(+d[yColname]) - logoRadius)})`)
.attr("x", 0).attr("y", 0)
.attr("height", logoRadius)
.attr("width", logoRadius)
.attr("fill", d => `url(#teamlogo-${d.teamAbbrev})`)
.attr("opacity", 0.95);
The issue here seems to be just the lack of a key function during the data join.
You asked in the comments section:
Doesn't the D3 general update pattern just look for the number of objects in the data array?
If you set up a key function the answer is no. The thing is that...
If a key function is not specified, then the first datum in data is assigned to the first selected element, the second datum to the second selected element, and so on. A key function may be specified to control which datum is assigned to which element, replacing the default join-by-index, by computing a string identifier for each datum and element. (source)
So, if you don't set up a key function, because you have always just 5 teams you don't effectively have working enter and exit selections when you change the division, but just an update one: as you're binding the data by their order, D3 thinks that Chicago Bulls and Atlanta Hawks are the same team.
Solution: set up a key function.
It can be simple as using the team abbreviation (supposing they are unique):
const update = svg.select('g.points')
.selectAll("rect")
.data(graphData, function(d){
return d.teamAbbrv;
});
PS: Just a question not related to your problem: why are you appending rects here? Since you have logoRadius, doesn't appending circles seem more natural? On top of that, the data representation would be more accurate, since the center of the circle, regardless its size, is at the correct datum coordinate. That's not the case with a rectangle, in which the coordinates (x, y) represent its top left corner.

D3 exit selecting wrong data

I'm trying to build kind of real time graph using D3.js. Code is available at https://plnkr.co/edit/hrawv8CTBIsJf2QWTBMb?p=preview.
The source data represent user authentication results from different organizations. For each organization there is a name, ok count and fail count. The graph should be dynamically (getting the data in loop) updated based on data.
The code is based on https://bl.ocks.org/mbostock/3808234.
There are few problems and few things i'm not sure about.
exit function only selects the red bars based on data update:
// JOIN new data with old elements
// specify function for data matching - correct?
var boxes = svg.selectAll(".box").data(data, function(d) {
return d.inst_name;
});
// EXIT old elements not present in new data
// this works somehow strange
// it does select all red boxes
boxes.exit().transition(t).remove();
Why does exit() select only red bars and not all? From my point of understanding the d3 documentation exit() should only select such elements that do not have any new data. Shouldn't that be all bars in case of infinite loop and constant data file?
This obviously breaks the graph quite a lot (see plunker). I need the exit to select only bars, which are not available in data file anymore. See example below.
initial state of data file:
inst_name,ok,fail
inst1,24,-1
inst2,23,-3
...
updated state of data file:
inst_name,ok,fail
inst1,26,-1
inst14,22,-4
...
The bars (both blue and red) for inst2 from intial state should be removed (and replaced by data of inst14) when the data is updated. Why is this not working?
I've read, that new data are matched against older using index. I've specified that inst_name should be used:
var boxes = svg.selectAll(".box").data(data, function(d) {
return d.inst_name;
});
Is this necessary (I've used it everywhere when inserting data)?
Also the transition for removing the elements does not work. What is the problem?
I'm also not sure if specifying data is necessary when adding new bars:
var boxes = svg.selectAll(".box").data(data, function(d) {
return d.inst_name;
});
.....
// add new element in new data
svg.selectAll(".blue")
.data(data, function(d) { // is this necessary ?
return d.inst_name;
}) // use function for new data matching against inst_name, necessary?
.enter().append("rect")
.transition(t)
.attr("class", function(d) {
return "blue box "
})
.attr("x", function(d) {
return x(d.inst_name);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.ok);
})
.attr("height", function(d) {
return height - y(d.ok + min);
})
Thanks for help.
EDIT
The underlying data get changed by script (this was not written clearly in the original post), so it can change independently of the graph state. The data should be only growing.
You've asked a lot of questions.
Why does exit() select only red bars and not all? From my point of understanding the d3 documentation exit() should only select such elements that do not have any new data. Shouldn't that be all bars in case of infinite loop and constant data file?
First, you build two sets of bars (blue [ok] and red [fail]). When you data bind these you give them the same key function, which identifies them by inst_name. You then do your data update, which now selects all the bars at once with:
svg.selectAll(".box")
You again data-bind with the same key function. Your data has 10 values in the array but you just selected 20 bars. The second 10 bars exit (the red ones) because to d3 they are not in your 10 data-points
The bars (both blue and red) for inst2 from intial state should be removed (and replaced by data of inst14) when the data is updated. Why is this not working?
I don't see that in your plunker, you are giving it the same data over and over.
Also the transition for removing the elements does not work. What is the problem?
You haven't given the transition anything to do. It'll run it, then at the end remove the rects. What you need is something for it to transition, like "height":
boxes.exit().transition(t).attr('height', 0).remove();
This will shrink them to 0 height.
So how do we clean up your code?
First, I would operate on g elements each one paired to an item in your data array. You then place both bars in the g that belong to that data point. Take a look here, I've started to clean-up your code (incomplete, though, hopefully it gets you going).

How to go about updating modular visualizations

I'm helping guide a project that's combining some visualizations in D3. In our specific example we are producing a graph of pies by using a regular D3 Pie chart with the sankey layout visualization. The affect of this is to produce something like:
The development is aimed to try and keep this as modular as possible, therefore the very first step was to produce an updating pie chart that could be used stand alone or plugged into another visualization. This is currently encapuslated into a pieObject which looks something like this:
var pieObject = function( d, target ){
var pie = {};
// other code to handle init
pie.update = function(data) {
// render code
};
};
Where it gets a little confusion is in the tree visualization, when I need to start handling updates. Here is how a new pie is added:
sankey.nodes(data.nodes)
.links(data.links)
.layout(32);
var node = svg.append("g")
.selectAll(".node")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
.each( function( d ) {
var pie = new pieObject( d, this );
})
If I however want to deal with an existing node, I'm not sure how I should go about accessing the pieObject? There are a couple of options I can think of, but I'm wondering if there's a general approach commonly used?
Store the pieObject on the element d
Store the pieObject in an array or JavaScript object with a lookup from a field on the d
Out of the examples I've outlined, I prefer option #1. But I'm also re-loading my entire data-set from JSON (e.g. new JSON request returns existing data + new data) so I believe when I call sankey.nodes(data.nodes).links(data.links).layout(32); that this is going to lose any additional information I've stored on the d object?
EDIT
I've put together a JSFiddle to help illustrate my problem. The code is a little lengthy and I don't own it as yet so don't know all the ins & outs but here's a breakdown:
1-#214 - Sankey code which produces the Tree layout
215-#451 - Code for a Pie Chart
453-#475 - Code to add the viz to the page
Specifically the area of creating the pies, and trying to figure out how to update them is in the render function between lines #129-#149
I'd recommend starting with Mike Bostock's tutorial on re-usable charts, which does pretty much what you are looking for: http://bost.ocks.org/mike/chart/
Specifically, the key to this type of thing is to use the selection.call() function to insert your chart into another chart/layout in a re-usable way. Then when you need to update your embedded chart, just do the same selection.call() again. Hopefully that gets you started.

Circles aren't updating in a multiline graph

I have a D3.js multiline graph with circles added on every path peak. When I update my graph, the paths update just fine with the new data but the circles don't seem to get updated at all. Here's my code: http://jsbin.com/eMuQOHoV/3/edit
Does anyone know what I'm doing wrong?
You need to update the data point circles in the same way that you've created them. In particular, you're using a nested selection when creating them, but not when updating. This means that the data won't get matched correctly on update and nothing happens.
The code for the update should look like this.
var sel = svg.selectAll('.series')
.data(sources);
sel.select('path')
.transition()
// etc
// update circles
sel.selectAll('.datapoint')
.data(function (d) {
return d.values;
})
// etc
Complete jsbin here.

D3 - issues on first transition after initialization

I have created this jsbin http://jsbin.com/ibewux/3/edit to show a strange behavior on transitions.
When the chart is initialized, it correctly displays the data (see the table below it).
If I try to change the chart type through the dropdown menu, some series are swapped; after this happens, the series are not swapped anymore.
Same thing happens if you click on updateChartData button; first time it will swap the series compared to the data displayed in the table.
So it seems that only the first transition after initialization is subject to this unwanted swap.
It's a short piece of code and wonder if you can spot the reason why this happens.
Thanks
When isVerticalChart is true, you are using an ordinal scale with domain svg.pointsNames (which seems to be an array of strings of the form "Col " + i):
x = d3.scale.ordinal().domain(svg.pointsNames);
However, you then go on to use the datum index with this scale, instead of these strings:
.attr("x", function(d, i) { return isVerticalChart ? x(i) : x(d.y0 - d.size); })
I think you should be passing a string from the domain to the scale here to avoid the strange behaviour you're seeing.
It only works at the moment because if you pass a key to an ordinal scale that hasn't been seen before, it will add it to the domain.
There may be other issues, but hopefully that gets you closer.

Categories

Resources