3 Modify, Add, Remove

Read IDVW2, Chapter 6: Drawing with Data. Skip pp. 89-96 as we will not be drawing bar charts with the divapproach.

3.1 Selections

3.1.1 Select by tag

The ability to select elements on a page is key to being able to manipulate them. d3.select() will select the first match; d3.selectAll() will select all matches.

d3.select("svg").select("circle");

selects the first circle in the order in which circles appear in the <svg> grouping. If there were more than one circle we could select them all with:

d3.select("svg").selectAll("circle");

We can select HTML elements by tag in the same way:

d3.select("body").select("h1");
d3.select("body").selectAll("h1");

3.1.2 Select by class

Classes are selected by adding a “.” before the class name:

d3.select("svg").selectAll("circle.apple")

This provides one method of selecting a certain collection of elements of the same type.

3.1.3 Select by ID

IDs differ from classes in that they are unique identifiers. IDs are selected by adding a “#” before the ID:

d3.select("svg").select("circle#henry");

3.1.4 Store selections

It is often helpful to store selections for later use. Here we store the svg selection in mysvg:

const mysvg = d3.select("svg");

The JavaScript community is moving toward using let and const instead of var; we, however, will stick with var to be consistent with IDVW2. Of course you’re welcome to use const and let instead, and if so, may find these articles helpful: Let It Be - How to declare JavaScript variables and ES2015 const is not about immutability.

Store circle selection in a variable:

const svg = d3.select("svg");

const circ = svg.selectAll("circle");

3.2 Modify existing elements

Try out the code in this section with a downloaded copy of five_green_circles.html opened in Chrome and the Console visible.

3.2.1 Modify attributes

link to get or set attribute API

d3.select("circle").attr("r");           // see radius

d3.select("circle").attr("r", "10");     // set radius to 10

3.2.2 Modify styles

link to get or set style API

d3.select("h1").style("color");

d3.select("h1").style("color", "blue");

It is often difficult to remember whether to use .attr() or .style() In general, properties such as position on the SVG, class, and ID are attributes, while decorative properties such as color, font, font size, etc. are styles. However, in some cases, you can use either. For example, the following both make the circle blue:

d3.select("circle").attr("fill", "blue");

d3.select("circle").style("fill", "blue");

The first will add a fill="blue" attribute to the <circle> tag, while the latter will add style="fill: blue;". All is well and good until you find yourself with both in the same tag, in which case the style property will take precedence. The bottom line: don’t mix the two options because it can cause problems.

To further complicate matters, .style() is just shorthand for .attr("style", "...") so the following are in fact equivalent:

d3.select("circle").style("fill", "blue");

d3.select("circle").attr("style", "fill: blue;");

In other words, style is an attribute!

3.2.3 Modify text

This section is interactive: You can hover over code as directed to observe effects.

The interactivity is enabled by D3 scripts that are included in the .Rmd source file of this page. If you’re interested you can view these scripts by either clicking on the eye icon above or opening Developer Tools in Chrome. In either case, look for the code between the <script> </script> tags.

HTML text

<p id="typo" class="fancy">Manhatten</p>

Manhatten

Hover to execute this code (and fix the typo):

d3.select("#typo").text("Manhattan");

SVG text

<svg width="500" height="100">
  <rect width="500" height="100" fill="#326EA4"></rect>
  <text id="svgtypo" x="50" y="70" fill="white" font-weight="bold" font-size="40px">
     Web scrapping is fun.</text>
</svg>  

Hover on this SVG to execute the code below it (and fix the typo):

Web scrapping is fun.
d3.select("#svgtypo").text("Web scraping is fun.");

The SVG <text> tag can be tricky. It differs from HTML text tags (<p>, <h1>, <h2>, etc.) in that it has x and y attributes that allow you to position text on an SVG canvas. Unlike HTML, the fill attribute controls the color of the text. Compare:

d3.select("p").style("color", "red");   // HTML

d3.select("text").attr("fill", "red");  // SVG

3.2.4 Move SVG text

<svg width="600" height="100">
  <rect width="600" height="100" fill="#326EA4"></rect>
  <text id="moveleft" x="200" y="70" fill="white" font-weight="bold" font-size="40px">
      I want to move left.</text>
</svg>  

Hover on this SVG to execute the code below it:

I want to move left.
d3.select("#moveleft").attr("x", "20").text("Thanks, now I'm happy!");

3.3 Add elements

3.3.1 HTML

Continue trying out code with five_green_circles.html open in Chrome.

Or download the file and open it.

The following adds a <p> tag but doesn’t change how the page looks, since there’s no text associated with it.

d3.select("body").append("p");

To add text, use .text():

d3.select("body").append("p").text("This is a complete sentence.");

To debug adding an element, go to the Elements tab to see what was added and where. If an element is in the wrong place in the HTML tree, it will not be visible.

3.3.2 SVG

Likewise, here we add a <circle> to the <svg>, but we can’t see it since it has no attributes.

d3.select("svg").append("circle");

Adding attributes will create visible circles:

d3.select("svg").append("rect").attr("x", "0").attr("y", "0")
    .attr("width", "500").attr("height", "400").attr("fill", "lightblue");
    
d3.select("svg").append("circle").attr("cx", "200")
    .attr("cy", "100").attr("r", "25").attr("fill", "orange");
    
d3.select("svg").append("circle").attr("cx", "300")
    .attr("cy", "150").attr("r", "25").attr("fill", "red");  

We can use a saved selection to assist in creating a new element:

(IDVW2, pp. 97-98)

mysvg = d3.select("svg");

mysvg.append("circle").attr("cx", "250").attr("cy", "250").attr("r", "50")
  .attr("fill", "red");

3.4 Remove elements

These methods will remove matching elements in order, starting with the first find in the document.

3.4.1 HTML

d3.select("p").remove();

3.4.2 SVG

d3.select("svg").select("circle").remove();

d3.select("svg").selectAll("circle").remove();

3.5 Exercise : green circles

Return to five_green_circles.html, open Developer Tools, and do the following in the Console with D3:

  1. Select the circle with ID “henry” and make it blue.

  2. Select all circles of “apple” class make them red.

  3. Select the first circle and add an orange border (use attribute “stroke”), and stroke width (“stroke-width”) of 5.

  4. Select all circles of “apple” class and move them to the middle of the svg.

Solution

3.6 Exercise : blue circles

Return to six_blue_circles.html, open Developer Tools, and execute Steps 1-4 one at a time in the Console. After Step 4, refresh the page to go back to Step 1 if so desired. (You do not need to create a loop as in the visual.)

This exercise is provided as a challenge. It’s fine to skip this exercise and move on to the next section.

  1. Move all the circles to the right.

  2. Move them back to the left and change their color.

  3. In a text editor, add an id to the third circle in six_blue_circles.html, save the file, and then in the Console, move only that circle to the right.

  4. Move all the circles to the middle of the screen, then move them all to the same location.

Solution

3.7 Bind data… finally!

(IDVW2, pp. 98-108)

To follow along with the code in this section, download and open six_blue_circles.html.

Bind data:

d3.select("svg").selectAll("circle").data([90, 230, 140, 75, 180, 25]);

Check data binding:

d3.select("svg").selectAll("circle").data();

Set x-coordinate of each circle to data value using arrow function:

d3.select("svg").selectAll("circle").attr("cx", d => d);

Set x-coordinate of each circle to data value with a JavaScript function:

d3.select("svg").selectAll("circle").attr("cx", function(d) {return d;});

We’ll bind a new set of data to the circles, this time storing the dataset in a variable:

const dataset = [50, 80, 110, 140, 170, 200];

We’ll also store a selection of all circles before binding the data:

const circ = d3.select("svg").selectAll("circle");

And now, the data bind:

circ.data(dataset);

Nothing appears to have happened; the circles remain the same and there is no evidence of any changes looking at the circles in the DOM (see Elements tab).

We can check that the data are indeed bound with:

circ.data();  // now we see data

Modify elements w/ stored selections, bound data:

circ.attr("cx", function(d) {return d;});

circ.attr("cx", function(d) {return d/2;});

circ.attr("cx", function(d) {return d/4;}).attr("r", "10");

Same as above, using arrow functions:

circ.attr("cx", d => d);

circ.attr("cx", d => d/2);

circ.attr("cx", d => d/4).attr("r", "10");

Note that if we bind a new set of data to the DOM elements, the original set will be overwritten:

const newdata = [145, 29, 53, 196, 200, 12];

circ.data(newdata);

circ.transition()
    .duration(2000)
    .attr("cx", d => 2*d);

3.8 Exercise : data bind

Return to six_blue_circles.html, open Developer Tools, and practice binding data to the circles and modifying the circles based on the data as in the examples above.