user3611
user3611

Reputation: 151

d3.map() function not returning expected results

I'm trying to use map.set(key, value) for a specific dataset - and assigning it to variable mapdata - instead of specifying it when I queue the csv.

I.e., instead of:

d3.queue()
  .defer(d3.json, 'counties.json')
  .defer(d3.csv, 'employmentdata.csv') function(d) {
      d3.map()
      .set(d.county, +d.rate);
   })
  .await(ready);

I want something more along the lines of

var mapdata = d3.map(data)
  .set(function(d) {d.county}, function(d) {d.rate});

except the code isn't returning what I want. the indexes are keys instead of the counties, and the values include the entire dataset instead of just rate. How would I fix this?

Thanks in advance

Upvotes: 1

Views: 3471

Answers (3)

Andrew_1510
Andrew_1510

Reputation: 13526

As D3 version 6, v6.4.0

d3.map() with not parameters give me error: values is undefined. When degrade to version v5 the error also comes up until changed to v4

The following is my code show the error:

<!DOCTYPE html>
<html>
<head>
  <title>D3 Example</title>

  <!-- 
      All of this gives error: values is undefined:

  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script src="node_modules/d3/dist/d3.js"></script>
  <script src="unziped/d3.js"></script>
  -->

  <script src="https://d3js.org/d3.v6.min.js"></script>

  <!--
      Version 4, This is ok:
  <script src="https://d3js.org/d3.v4.js"></script>
  -->

  <script>
  var data_map = d3.map();
  </script>
</head>
<body>
    <h1>Try D3, d3.map() gives error</h1>
    <div id="map"></div>
</body>
</html>

Upvotes: 0

Andrew Reid
Andrew Reid

Reputation: 38191

When used as such:

.defer(d3.csv, 'employmentdata.csv') function(d) {
      map
      .set(d.county, +d.rate);
   })

The callback function with map.set is called for each row in the csv, and each key and value is set individually. If you have an array of data already, you can emulate this with a forEach loop:

var data = [
  {key: "A", value: 100},
  {key: "B", value: 200},
  {key: "C", value: 300},
  {key: "D", value: 400},
  {key: "E", value: 500}
]

var map = d3.map();
data.forEach(function(d) {
  map.set(d.key, d.value);
})

console.log(map.get("A"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

We can expand this to manipulate the key and value, as the row function or a forEach loop allows access to the datum d, we don't need to actually enclose the manipulation in a function:

var data = [
  {key: "A", value: 100},
  {key: "B", value: 200},
  {key: "C", value: 300},
  {key: "D", value: 400},
  {key: "E", value: 500}
]

var map = d3.map();
data.forEach(function(d,i) {
  map.set(d.key+"-"+i, d.value*2);
})

console.log(map.get("A-0"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>


Your second example:

var mapdata = d3.map(data)
  .set(function(d) {d.county}, function(d) {d.rate});

This will not work because .set is only being called once, and d is not defined. In fact, the functions will not be called at all.

Before we look at the functions in the set method, let's look at the first line a little closer:

   var mapdata = d3.map(data)

d3.map expects two parameters, one defining the data array, and one defining the key. As you don't provide a key, the default key is used, index:

d3.map([object[, key]]) <>

Constructs a new map. If object is specified, copies all enumerable properties from the specified object into this map. The specified object may also be an array or another map. An optional key function may be specified to compute the key for each value in the array. (source)

So, the behavior you are seeing doesn't come from the .set method but simply from the default indexing:

var data = [
  {key: "A", value: 100},
  {key: "B", value: 200},
  {key: "C", value: 300},
  {key: "D", value: 400},
  {key: "E", value: 500}
]

var map = d3.map(data);

console.log(map.get(0));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

So what is .set doing in your example? It is adding a new value key pair to your dataset, let's take a close look at the map before and after:

var data = [
  {key: "A", value: 100},
  {key: "B", value: 200},
  {key: "C", value: 300},
  {key: "D", value: 400},
  {key: "E", value: 500}
]

var map = d3.map(data);

console.log(map);
console.log("---------------");

map.set(function(d) { console.log(d); },
        function(d) { console.log(d); })
        
console.log(map);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

This is interesting behavior, not only are the console.logs not called in the set method, the two functions are added as key, value pairs in the array. It's easy to miss (largely due to stack snippet's logging), but here's the addition to the map:

"$function (d) { console.log(d); }": function (d) { console.log(d); },

The set method will only add one key value pair, it will not modify existing pairs (unless to overwrite one). This is why .set will work in a row function or a forEach loop, but not otherwise when setting multiple key/value pairs.

While the function that was key was coerced to a string, you could embed functions as values:

var data = [
  {key: "A", value: 100},
]

var map = d3.map(data);

map.set("B",function() { return "Hello"; })
        
console.log(map.get("B")());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>


Summing Up

So, if you want to create a map using a key and the whole object as a value you can use:

var data = [
  {key: "A", value: 100},
  {key: "B", value: 200},
  {key: "C", value: 300},
  {key: "D", value: 400},
  {key: "E", value: 500}
]

var map = d3.map(data, function(d) { return d.key; });

console.log(map.get("A"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

But if you want to specify a certain value for each key (as opposed to the data array item), it will be easier to use a forEach loop (as seen in the first snippet in this answer):

var map = d3.map()
data.forEach(function(d) {
  map.set(d.key,d.value)
})

If you want to use a function in the set method here, you would need to execute the function and have a return value (both missing in your second example/desired code), you want the function's outputs, not the function itself:

var data = [
  {key: "A", value: 100},
  {key: "B", value: 200},
  {key: "C", value: 300},
  {key: "D", value: 400},
  {key: "E", value: 500}
]

var map = d3.map();

data.forEach(function(d) {
  map.set(function() { return d.key + "-key"}(),
          double(d.value))
})

console.log(map.get("A-key"));

function double(n) { return n*2; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>


A last note, your initial (reference/starting point) code block isn't quite set up properly, d3.map() creates a new map. We don't want to create a new map for each row, we want to create a map once and then use set for each row.

Consequently this:

d3.queue()
  .defer(d3.json, 'counties.json')
  .defer(d3.csv, 'employmentdata.csv') function(d) {
      d3.map()
      .set(d.county, +d.rate);
   })
  .await(ready);

Should read like:

var map = d3.map();  // create a map.
d3.queue()
  .defer(d3.json, 'counties.json')
  .defer(d3.csv, 'employmentdata.csv') function(d) {
      map // use that same map for each row.
      .set(d.county, +d.rate);
   })
  .await(ready);

The snippets above use this same approach, create a map, and call .set for each item in the array.

Conversely, something like : d3.map().get(d.property) won't return anything because you've just created a new map which is empty with d3.map().

Upvotes: 5

user3611
user3611

Reputation: 151

Thank you for your explanation. I tried your above code and it returns undefined. I might know what the issue is:

So my current data has multiple categories. I want to create different keys and values for different categories, and I want to distinguish them by assigning them to new variables. I want to do this because I want to add different features on my map for different data, for instance, i want to color the map based on unemployment, but I want to add bubbles on the map based on a different rate, and then I want to create dropdown menus based on something else. For instance, I currently have:

 var nest = d3.nest()
    .key(function(d) {return d.occupation; })
    .entries(data);

   // array of locations in the data
   var nocs = nest.map(function(d) { return d.key; });

So, when I'm creating my dropdown menu, I write this:

var nocMenu = d3.select('#dropDown')
    nocMenu
      .append("select")
      .attr("id", "selectMenu")
      .selectAll("option")
        .data(nest)   /// i'm using 'nest' now instead of the entire dataset
        .enter()
        .append("option")
        //.attr("value", function(d) { return d.values })
        .text(function(d) { return d.key; });

And this gives me what I want. The only relationship between the topojson file and the csv file is the county names. I need to have county names as a key with unemployment as the value so I can use d3.map().get(d.properties.counties) when filling my map with color.

I tried:

  var mMap = d3.map()
var mapdata = data.forEach(function(d) {
  mMap
    .set(d.geography, +d.rate)
});

console.log(mapdata);

but it returned undefined.

Upvotes: 0

Related Questions