3

My introductory question is:

What is the best way to bind data to a D3.js map?

I came up with three possibilities:

1. Apply a class based on the data, then apply style to the class.

.attr("class", function(d) { return quantize(rateById.get(d.id)); })

This option seems to be the most popular (see the choropleth block), but it is not actually binding the data. Thus it seems a bit dishonest to D3's intent. It also means the bound data cannot be simply referenced for tooltips, for example.

2. Merge the topo.json feature collection data with the rest of the data.

We can place all data inside a single variable (perhaps using forEach). The result might look something like:

var mergeData = [{
                    area: "NW",
                    mean: 45,
                    features: {...}
                }, {
                    area: "NE",
                    mean: 23,
                    features: {...}
                }, {
                    area: "SW",
                    mean: 87,
                    features: {...}
                }, {
                    area: "SE",
                    mean: 56,
                    features: {...}
                }];

Then we can bind the mergeData to path elements, generate the map by pointing the "d" attribute to d.features, and style using d.mean. This allows for each path element to contain all necessary data we will ever need. However, it seems cumbersome to have to combine it all together.

3. Replace the feature collection data with the desired data.

Feature collection data should not be needed once the map shapes have been drawn. It can be replaced. Why not simply bind the data we need to these paths (after generating the map shapes) so it is always available for styling, tooltips, etc.? We just need a key function to match the new data to the correct map shape. The update selection will take care of the replacement.

d3.selectAll(".area").data(data, function(d) {
                            return d.id;
                        })
                        .style("fill", function(d) {
                            return colorScale(d.mean);
                        });

This seems to work well and is nice and clean. The caveat is the structure of the data and the feature collection must be the same. The same key function is evaluated on both sets of data. So if our data is nested, we cannot access d.values.id because there is no corresponding values in the feature collection - it is simply d.id. Intuitively, I'm wishing for a key function working like something akin to filter, so that we could specify the location of the id in each data set, something like so:

return d.id === d.values.id;

There is no way to accomplish this, is there?

A workaround would be to refactor the nested data into a third, flattened data variable so it is no longer nested. That way d.id could be used in the key function. But once again this seems cumbersome.


Which is the best way? Or is there another?

I'm still learning D3, so please feel free to correct any of my assumptions or edit my question directly.

cplindem
  • 73
  • 5
  • I'm not sure what you mean. In the choropleth example, the data is bound to the `path` elements. Is that not what you want? – Lars Kotthoff Mar 10 '16 at 03:18
  • I vote for option #2, it seems to be the more straightforward method, which can be applied to any situation. But that's just a possible opinion among other. – tarulen Mar 10 '16 at 10:01
  • @LarsKotthoff Maybe I'm missing something, but I do not see any data bound to the `path` elements. If I run `d3.selectAll(".q7-9").data()`, I get an empty array. Yes, the paths have been filled with color based on the data. But it has not been bound, as far as I can tell. As I mentioned above, this means you cannot simply select the path and refer to `d` to generate tooltips, for example. – cplindem Mar 10 '16 at 16:49
  • You can check by inspecting the DOM and see if the `path` elements have a `__data__` member. – Lars Kotthoff Mar 10 '16 at 18:11
  • They don't have a `__data__` member, as far as I can tell. I also don't see anything in the code on the page that is adding new data -- only the original `topojson`. – cplindem Mar 10 '16 at 19:20

1 Answers1

2

A solution to option #3 that I have just discovered is actually quite simple. Immediately after creating the nested data, it can be iterated over, and the needed piece of data can be inserted at the first level.

data.map(function(d) { d.id = d.values.id; });

This way it will be available to the key function in exactly the same structure. All of the rollups within values are still available for later use.

cplindem
  • 73
  • 5