I'm working on a motion chart showing the development of certain data points through time by updating the position and size of the corresponding circle on a chart (similar to https://bost.ocks.org/mike/nations/ ).
Since circles can overlap I need the smallest one to be drawn on top.
In the example this is accomplished by calling selection.sort(), but if I do the same (with the newer D3v4) I get an unexpected behaviour. The sort seems to switch the data objects associated to the visual circle object.
Check this fiddle to see. Run it once as it is (without calling sort(order) in line 45) - this is the expected behaviour. The y value of each object doesn't change so the circles should move on a horizontal line. Now uncomment line 45 to call the sort on each update and run it again. This time the paths of the circles suddenly cross (because the underlying object is switched).
https://jsfiddle.net/orj1rcy8/1/
The API states selection.sort() Returns a new selection that contains a copy .... I assume this is the problem, however I don't understand at this point how the correct approach would look like.
Short answer
You need a key function when you bind your data:
.data(dataForKey(keyIndex), function(d){ return d.name})
Here is your updated fiddle: https://jsfiddle.net/7kyvzkwe/
Long answer
What happened here is that you were victim of what we call object constancy, or, more precisely, not properly setting an object constancy (here is a good reading about it, written by Mike Bostock).
The problem is that, in D3, the data is bound to the elements in their order:
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.
So, let's see what happened. Your code has three circles, in this order:
The smallest circle, r = 3;
The medium circle, r = 10;
The largest circle, r = 15.
The data is appended in that above-mentioned order. But then, when you do sort(order):
function order(a, b){
return radius(b) - radius(a);
}
You sort the elements, and now you have:
The largest circle, r = 15;
The medium circle, r = 10;
The smaller circle, r = 3.
And then comes the problem: next time you run the next function and bind the new data, you're binding the data to DOM elements in that new order. That is, the data regarding the smallest circle is being bound to the largest circle in the DOM. Using the names of your elements, you're binding the data regarding item1 to the item3 in the DOM (given you have 3 elements, the only one always receiving the correct data is item2).
You can understand this better in the next fiddle. In this next fiddle, I'm using exactly your code, uncommenting the sort function. But, unlike your original code, here I'm changing the order of the data, so we have the largest circle first, then the medium, then the smallest:
[{name:"item1", x:1, y:2, z:15},
{name:"item2", x:1, y:4, z:10},
{name:"item3", x:1, y:6, z:3}];
You can see that, even calling sort, the circles stay in their positions. Here is the fiddle: https://jsfiddle.net/9p1tL43j/
That happens because the next time the function next runs, the data for the smallest circle is bound to the smallest circle (in the DOM), and so on...
Thus, in a nutshell, you need a key function if you want to keep the object constancy. A key function:
... may be specified to control which datum is assigned to which element, replacing the default join-by-index.
Related
I am working on a image search algorithm that finds certain shapes of certain colors; to save time I only register half of the shape's perimeter in 2 distinct sets, one for the rows and one for the columns used by the shape. The idea is that whenever I find a point which has the target color, I then check if this point's row and column are in a master set (which have both the previous sets); if they are I skip it, if they are not then I initialize 2 recursive fuctions that register the first row and the first column of the shape.
Since it's for a school project, my images are specially tailored
and the code would be
for y in range(height):
for x in range(width):
if img[y][x] == target:
if y in master_set and x in master_set:
continue
else:
row = set()
column = set()
flood_fillv2_y(img,x,y,target,column)
flood_fillv2_x(img,x,y,target,row)
row=frozenset(row)
column=frozenset(column)
master_set.add(row)
master_set.add(column)
The idea then is to check the len of master_set to see how many shapes I have, but as I said what I get is that y and x are never in the master set so it keeps doing it for all points of the shape, resulting in a wrong number.
It's hard to give a good answer without seeing the whole code, but I can give a guess:
master_set.add(row) literally adds the frozenset row to the master_set, but you probably want all elements from the set to be added to master_set. Take a look at the update() method of sets.
Does this help?
using the bl.ocks collapsable tree example: how do I increase the length of the lines between the circles (or move the circles further out, to the right)? I know I can style the lines easily using CSS and I can update the circle sizes easily in the script... but my text is longer and it is overlaid when the children are exposed. Better still would be either:
* calculate the max length of text in that set of children and set the new children out past the current set of children.
or
* when exposing children, move the current set of children apart so the child that is now also a parent, has room at top/bottom to accommodate it's children.
You can use the tree.nodeSize() function to set the height and width of the spacing. The thing to note is that if you are following the code posted above, it is using the tree.size() function and the both of these can't be used at the same time. Here is the documentation for the tree.nodeSize() function: https://github.com/d3/d3-3.x-api-reference/blob/master/Tree-Layout.md#nodeSize.
I want to be able to implement the D3 General Update Pattern III as a D3 pack layout. This means that each char would be inside a circle and that the char circles would be inside a larger enclosing circle. Everything should stay consistent with the GUPIII except that, of course, the positioning of the chars will be in a larger circle instead of on a horizontal axis.
I have tried to follow the same GUPIII structure including the important aspect of using a data join with key.
var alphaBubble = d3.layout.pack()
.size([diameter, diameter])
.padding(5);
var nodes = alphaBubble.nodes(data);
var vis = svg.selectAll("circle")
.data(nodes, function(d) { return d; });
But data join with key does not seem to work with the pack layout.
I'm having trouble getting my head around possible alternative mechanisms such as pack.value or nest. I have not been able to grasp whether they would somehow accomplish what is going on in GUPIII where incoming (enter) chars are green, existing (update) chars are black, exiting chars are removed.
Join by key is important in order to maintain the relative position of the char. I realize that this aspect of relative position is not as easy to see in the pack layout as it is in the linear axis layout, but I have a necessity for this which is not apparent in this example. I just need to successfully implement a "by key" join with pack that retains existing nodes when new data comes in.
Typically what I see with the pack layout is that either no nodes are replaced on data updates (maybe only attributes such as size are altered), or else all nodes are replaced with new incoming data, depending on the context. But I want to retain the nodes that are the same (by some key, in this case the char itself), remove the ones that are no longer present, and add the new ones "by key". Can anyone help me translate the D3 General Update Pattern II to a pack layout? I would include my full code attempt, but I really think just looking at the GUPIII is more clear if you are familiar with how the pack layout can facilitate join by key on update.
Thank you.
Ok, through much trial and error and study of existing examples I have been able to solve my question as to how to implement the D3 General Update Pattern III as a pack layout. I was not able to find any example that used data join by key, which is necessary for object constancy on update, so I'm hoping my example may provide some guidance for others who need the same pattern of behavior.
My solution is here. And my Javascript code is here.
I will point out a few important highlights. First, unlike in the linear non-pack version, the data join-by-key value must be accessed as an object property (i.e. d.id) instead of directly (i.e. d).
// Data join by key to <g> nodes
var node = svg.selectAll(".node")
.data(nodes, function(d) {
return d.id;
});
// Data join by key to circles
var circles = svg.selectAll("circle")
.data(nodes, function(d) {
return d.id;
});
Next, since the alphabet character is represented in a circle, both the circle and the char must be contained in a <g> element, i.e. node. And here was the trickiest part: the data needs to be joined by key separately to both the node and the circle elements. This is because the pack layout has performed the necessary calculation of scale (i.e. circle radii) and coordinate positions. So the circles need the data's calculated radii and the <g> node elements need the calculated coordinate positions. So a separate data join was required for each.
Once I figured this out, all of the necessary operations and references fell into place and the pack version of the General Update Pattern started working properly. Be sure to compare the linear and pack versions to each other.
The alphabetization sort is, I guess, unnecessary since the pack layout makes no accommodation for ordering. But I retained the sorting for consistency and because I wanted to observe its effect.
I've written a program which reads measurements from an impedance analyzer as it sweeps over a range of frequencies or voltages, saves the data to a text file, and also creates a scatter plot. In one type of measurement, I obtain x and y values for complex impedance, neither of which are the independent parameter. Now when plotting this graph, it appears that it simply puts each x value to the right of the previous one at regular spacings resulting in x axis labels looking like, from left to right, [45000, 43000, 40000,... etc.].
I've tried forcing the x-axis to start from zero which did not change anything and haven't been able to find much else on this. Is there a way to make sure the plot reflects the actual x values of each point?
Here's my current method of creating the chart,which pulls the data from the already created table:
For Each row In table.Rows
Chart1.Series("series1").Points.AddXY(row(0), row(1))
Next
I was wondering how would it be possible to associate a certain key function with a datum data binding in D3. This seems to be possible with .data([values[, key]]), but the key is not available as parameter in the .datum([value]) binding.
This becomes particularly relevant when drawing SVG paths in which the updated values are not appended to the end of the data array, but contribute to changes in granularity in the middle.
This example illustrates this case:
http://jsfiddle.net/vastur/LtHyZ/1/
Each data point is an [(x),(y)] tuple. The red dots are moving properly according to the key function on the x axis:
.data(lineData, function(d) {return d[0]})
But the line is created using datum() and therefore no key function is associated. Consequently, its line segments move illogically when new data points are added in between.
So, in this example, how to make line vertices move according to the motion of the red dots?
The short answer is that this is not possible for the case that you're dealing with. The problem is that there is only a single element (the line), so matching and computing joins doesn't make sense. To achieve the behaviour you want, you would need to specify the same number of support points for both lines.
A slightly more elaborate explanation. The D3 data model relies on binding data points to DOM elements. That is, each data point is matched to exactly one DOM element. The path you're drawing is a single DOM element and therefore has only one data "point" matched to it, the point being an array in this case. You can't use D3's data model for what you want precisely of this -- there's only a single DOM element for the path. Furthermore, SVG paths are entities that cannot be broken easily -- you need an M command to start with and then L commands in your case. You can sort of do it by breaking the line into different segments, but then the animation doesn't work (see here).
The way to achieve what you want would be to preprocess the data to compute any intermediate points.