0

I have a bar graph in D3 and I want to update the bars using a custom function update_bars(). Each rect element has been assigned an id from 0 to 9. The function is supposed to select the rect by id then change the width attribute, but I get "this.setAttribute" not a function. However, when I use plain old javascript, it works fine. I've commented where things went wrong.

<!DOCTYPE>
<html>
<head>
<link rel="shortcut icon" href="">
<script src="https://d3js.org/d3.v4.min.js"></script>

</head>
<body>
 
<script>

<!-- everything works fine here -->
var width = 800, barHeight = 20, height = barHeight*10;
var chart = d3.select("body")
 .append("svg")
 .attr("width", width)
 .attr("height", height); 
var init_year = 2000;

d3.json("topten.json", function(data){
 the_data = data;
 
 var bar = chart.selectAll("g")
    .data(data[init_year])
    .enter()
    .append("g")
    .attr("transform", function(d, i){
     return "translate(0," + i * barHeight + ")";
    })

    
  bar.append("rect")
   .attr("width", function(d){
    return d['articles']
   })
   .attr("height", barHeight - 1)
   .attr("id", function(d,i){ return i; } );
 
});

<!-- problem is in update_bars() -->

function update_bars(data, yr){
 
 data[yr].forEach( function(d, i){

 <!-- this d3 code gives "this.setAttribute is not a function" error -->
 
 /. d3.select(i).attr("width", d['articles']); ./
 
 <!-- this javascript works --> 
 temp = document.getElementById(i); 
 temp.setAttribute("width", d['articles']);
 });
}



</script>
</body>
</html>
Dij
  • 9,761
  • 4
  • 18
  • 35
thewayup
  • 1,155
  • 1
  • 9
  • 11

2 Answers2

4

First, in order to select by id you need to preface the selector with # like: d3.select("#myID")

But another problem is that your code is assigning an id to each rect of a single number like <rec id="1"> with this line:

.attr("id", function(d,i){ return i; } );

This causes some trouble with the selector because you need to select this with d3.select("#" +i), which isn't going to work. Selecting Ids with leading numbers is painful unless you are willing to select each with their unicode code point like this:

d3.select("#\\31")

(See here for details: Using querySelector with IDs that are numbers )

A better plan would be to assign a different id to each rect for example:

.attr("id", function(d,i){ return "_" + i; } );

Then you can select with:

d3.select("#_" + i).attr()

Having said all that, generally speaking you shouldn't be looping through your data in d3, but instead use the general update pattern illustrated here: https://bl.ocks.org/mbostock/3808234

Mark
  • 90,562
  • 7
  • 108
  • 148
  • This is also recommended method if working with ES6 modules, since `this` according to [standard](http://exploringjs.com/es6/ch_modules.html#_browsers-scripts-versus-modules) returns `undefinded` inside the ES6 module by default. – mutantkeyboard Oct 18 '17 at 12:58
0

Tried fixing the snippet and I can't reproduce the mentioned issue:

<!DOCTYPE>
<html>
<head>
<link rel="shortcut icon" href="">
<script src="https://d3js.org/d3.v4.min.js"></script>

</head>
<body>
 
<script>

<!-- everything works fine here -->
var width = 800, barHeight = 20, height = barHeight*10;
var chart = d3.select("body")
 .append("svg")
 .attr("width", width)
 .attr("height", height); 
var init_year = 2000;

//d3.json("topten.json", function(data){
// the_data = data;
 
  var data = { 
  2000: [
    { articles: 10 },
    { articles: 35 },
    { articles: 47 },
    { articles: 2 },
    { articles: 87 },
  ],
  2001: [
    { articles: 5 },
    { articles: 65 },
    { articles: 23 },
    { articles: 76 },
    { articles: 18 },
  ],
  };
  
 var bar = chart.selectAll("g")
    .data(data[init_year])
    .enter()
    .append("g")
    .attr("transform", function(d, i){
     return "translate(0," + i * barHeight + ")";
    })

    
  bar.append("rect")
   .attr("width", function(d){
    return d['articles']
   })
   .attr("height", barHeight - 1)
   .attr("id", function(d,i){ return i; } );
 
//});

<!-- problem is in update_bars() -->

function update_bars(data, yr){
 
 data[yr].forEach( function(d, i){

 <!-- this d3 code gives "this.setAttribute is not a function" error -->
 
 /. d3.select(i).attr("width", d['articles']); ./
 
 <!-- this javascript works --> 
 temp = document.getElementById(i); 
 temp.setAttribute("width", d['articles']);
 });
}

   setTimeout( update_bars, 1000, data, 2001 );



</script>
</body>
</html>
  • I've replaced your code fetching data from JSON file with some obviously suitable data matching your code.
  • Then I've added some code actually invoking update_bars().

See the snippet. It's working. So the issue seems to be related to either topten.json providing unexpected/mismatching data format or update_bars() being invoked with bad timing e.g. prior to having fetched the JSON file.

Thomas Urban
  • 4,649
  • 26
  • 32