From e1d0c394ce303fb5458a042e462795eeddbe7c70 Mon Sep 17 00:00:00 2001 From: jtr13 Date: Wed, 13 Nov 2024 21:11:17 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20jtr13/d3?= =?UTF-8?q?book@ba9f61de1ff85644cf07f852a7d34b962deff968=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 404.html | 2 +- appendix-advanced-css.html | 2 +- correlation-coefficient.html | 2 +- d3console.html | 2 +- general-advice.html | 2 +- index.html | 4 ++-- interactivity.html | 2 +- jump.html | 2 +- just-enough-js.html | 2 +- layouts.html | 2 +- line-charts.html | 2 +- object.html | 2 +- population-density.html | 2 +- reading-files.html | 2 +- scales-and-axes.html | 2 +- search_index.json | 2 +- share-d3-online.html | 2 +- solutions.html | 2 +- spearman-rank-correlation-coefficient.html | 2 +- transitions.html | 2 +- update-enter-and-exit.html | 2 +- weather-forecast.html | 2 +- web.html | 2 +- 23 files changed, 24 insertions(+), 24 deletions(-) diff --git a/404.html b/404.html index c667931..6a9c7c7 100644 --- a/404.html +++ b/404.html @@ -23,7 +23,7 @@ - + diff --git a/appendix-advanced-css.html b/appendix-advanced-css.html index 984c2ba..234b5ae 100644 --- a/appendix-advanced-css.html +++ b/appendix-advanced-css.html @@ -23,7 +23,7 @@ - + diff --git a/correlation-coefficient.html b/correlation-coefficient.html index 89ccb57..4cc7b2a 100644 --- a/correlation-coefficient.html +++ b/correlation-coefficient.html @@ -23,7 +23,7 @@ - + diff --git a/d3console.html b/d3console.html index 88d1ebc..80477d7 100644 --- a/d3console.html +++ b/d3console.html @@ -23,7 +23,7 @@ - + diff --git a/general-advice.html b/general-advice.html index acba06f..e8e78f1 100644 --- a/general-advice.html +++ b/general-advice.html @@ -23,7 +23,7 @@ - + diff --git a/index.html b/index.html index abc243d..638a2f3 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,7 @@ - + @@ -391,7 +391,7 @@

Welcome to D3

diff --git a/interactivity.html b/interactivity.html index 2d351d0..fd55b22 100644 --- a/interactivity.html +++ b/interactivity.html @@ -23,7 +23,7 @@ - + diff --git a/jump.html b/jump.html index 577e999..23037a7 100644 --- a/jump.html +++ b/jump.html @@ -23,7 +23,7 @@ - + diff --git a/just-enough-js.html b/just-enough-js.html index 2a1ef0d..2b68b2f 100644 --- a/just-enough-js.html +++ b/just-enough-js.html @@ -23,7 +23,7 @@ - + diff --git a/layouts.html b/layouts.html index 6a70ba5..01828e8 100644 --- a/layouts.html +++ b/layouts.html @@ -23,7 +23,7 @@ - + diff --git a/line-charts.html b/line-charts.html index ed03e48..882d2b1 100644 --- a/line-charts.html +++ b/line-charts.html @@ -23,7 +23,7 @@ - + diff --git a/object.html b/object.html index 9efb706..8209dc5 100644 --- a/object.html +++ b/object.html @@ -23,7 +23,7 @@ - + diff --git a/population-density.html b/population-density.html index 110a3d3..24ecfa8 100644 --- a/population-density.html +++ b/population-density.html @@ -23,7 +23,7 @@ - + diff --git a/reading-files.html b/reading-files.html index 5eb08bf..e30080b 100644 --- a/reading-files.html +++ b/reading-files.html @@ -23,7 +23,7 @@ - + diff --git a/scales-and-axes.html b/scales-and-axes.html index 51a64c0..a03a087 100644 --- a/scales-and-axes.html +++ b/scales-and-axes.html @@ -23,7 +23,7 @@ - + diff --git a/search_index.json b/search_index.json index a043b07..a389b2b 100644 --- a/search_index.json +++ b/search_index.json @@ -1 +1 @@ -[["index.html", "D3 for R Users Welcome to D3 0.1 Workflow", " D3 for R Users Joyce Robbins 2024-11-04 Welcome to D3 rect { pointer-events: all; } .node { fill: blue; } .cursor { fill: none; stroke: brown; pointer-events: none; } .link { stroke: red; } .svg-container { display: inline-block; position: relative; width: 100%; padding-bottom: 20%; vertical-align: top; overflow: hidden; } .svg-content { display: inline-block; position: absolute; top: 0; left: 0; } Adapted from Build Your Own Graph! This guide serves as a companion text to Scott Murray’s Interactive Data Visualization for the Web, 2nd edition–henceforth IDVW2–a required text for GR5702. Be sure to get the second edition, which is a comprehensive update to D3 version 4. The first edition uses D3 version 3, which is not compatible. (The current version of D3 is actually v7. However, since differences between v4 and v5/v6/v7 are minimal, unless otherwise indicated in this guide, the code in IDVW2 will work with either.) We rely on the text heavily but also deviate from it in several ways. IDVW2 is written for graphics designers not data science students so the pain points are somewhat different. D3 is a JavaScript library, not a standalone language, so any time we refer to D3 we really mean D3/JavaScript, though it is not necessary to know JavaScript well before beginning; we will learn as we go. Most of the JavaScript we use is covered in IDVW2, though we also use some newer JavaScript options from ES5 and ES6, such as .map(), .filter(), arrow functions and template literals, that make coding easier (and more like R!)1 We use different examples, though you are strongly encouraged to study Murray’s code examples in addition to reading the text. Particularly through the first half, we don’t follow the text in order, so always refer to this guide first which will direct you to the pages of the text that you should read. This is very much a work-in-progress so please submit issues on GitHub to provide feedback and edit or add text by submitting pull requests. (Click the icon at the top of each page to get started. More detailed instructions are available on edav.info. If you would just like to view the source code, click the icon.) 0.1 Workflow A big hurdle to learning a new language is just getting setup. Often authors forget to mention what your programming environment should look like, what should be open on the screen. I will try not to do that and be as clear as possible so you know where you should be entering the code in the pages that follow. This task is somewhat complicated by the fact that we will be using a variety of workflow options. This section will serve as a reference guide; future sections will link back here as appropriate. All of our workflows require Google Chrome, so if you don’t have it already, download and install it. 0.1.1 JavaScript Console With this workflow we will open a web page–either online or local–in Chrome and run JavaScript in the Console. To view the Console, open Chrome DevTools by clicking View, Developer, JavaScript Console if you have a menu bar in Chrome, using a keyboard shortcut (Mac: option+command+j; Windows, Linux, Chrome OS: control+shift+J), or employing another one of the many options for doing so. The Console is one piece of a suite of tools available in the browser. With the DevTools open, your screen will look like this: The next chapter, Jump in the deep end, employs this workflow. 0.1.2 This book in the Console If you’re not reading the .pdf version, you can open DevTools on this very page. This is very convenient because not only to you not have to leave this book to practice D3, you can copy code blocks and paste them in the Console. In addition to opening DevTools (see above), close the side bar by clicking on the (“Toggle Sidebar”) icon on the top left of the page, to the left of the search icon, to give yourself more screen space. Let’s try it out. Open the JavaScript Console svg#demo Scroll so that both the blue rectangle above and the code chunk below are visible on your screen. Toggle the sidebar, open the Console, and then move the mouse onto the code block so the icon appears. Click on it to copy the code, paste it in the Console, and then press return. d3.select("svg#demo") .append("circle") .attr("cx", "-25") .attr("cy", "100") .attr("r", "20") .attr("fill", "red") .transition() .duration(3000) .attr("cx", "325") .remove(); Pretty neat. 0.1.3 Text editor This is a very basic local setup in which the same .html is open both in a text editor (if you don’t want to stray too far from home, use RStudio) and in a web browser (we will use Chrome), each on one half of your screen. The workflow is: make changes to the file in the text editor, save the changes and then refresh the page in the browser to see the updates. Keyboard shortcuts for saving and refreshing (on the Mac, command-s and command-r respectively) are very helpful. Let’s try an example: Download a copy of shapes.html by opening this page and clicking File, Save Page As… Open the file in a text editor of your choice on one half of your screen. On the other half of your screen open the same file in Chrome. As you make changes to the .html file, save the file and then refresh the browser to see the effects. Your screen should look like this: Ok, they’re not actually that new, but it takes a while for new JavaScript to catch on, mainly due to concern with maintaining compatibility with older browsers. Since D3 itself is not compatible with very old browsers, and since we can’t focus on everything at once, we are not going to concern ourselves with browser compatibility. If you are interested in this, caniuse.com is very helpful for looking up what works where.↩︎ "],["jump.html", "1 Quick demo 1.1 Get ready 1.2 Elements tab 1.3 Console tab 1.4 Modify elements 1.5 Add transitions 1.6 Add interactivity 1.7 Examples", " 1 Quick demo Let’s skip the explanations and start coding in D3 right now. Then we’ll go back and learn step by step. In this chapter we will work in the JavaScript Console (help). 1.1 Get ready If you don’t have it, install the Chrome browser. Download a copy of shapes.html by opening this file and choosing File, Save Page As… If Chrome is your default browser, open shapes.html by double clicking it. Otherwise, open Chrome first, click File, Open File…, and then choose shapes.html from the directory where you saved it. 1.2 Elements tab Open Chrome DevTools (help). Hover the mouse over various elements in the <body> ... </body> section. Observe the highlighted sections in the rendered web page on the left of the screen. Click on the mini black triangles to the left of the <body> and <svg> tags if needed to open these sections of the DOM tree. Your screen should look like this: Now try the reverse: right click on elements on the web page, choose “Inspect” and see what is highlighted in the Elements pane. Get comfortable with the connection between the code on the right and the rendered elements on the left. 1.3 Console tab Switch to the Console tab, next to the Elements tab. Let’s practice running some code. Note that the code is unrelated to the shapes.html web page that we have open. We will spend a lot of time in the Console since it’s interactive – think R console. Eventually we will switch to including JavaScript/D3 in .html or .js files and use the Console only for testing things out or debugging. Type the following lines of code at the prompt (>), press enter after each line–that is, after the semicolon (;)–and see what happens: 3 + 4; "3" + "4"; x = [1, 2, 3]; x[1]; x + 1; y = {a: 3, b: 4}; y["b"]; 1.4 Modify elements Now we’ll start using D3 to manipulate elements on the page. Try the following, by entering one line at a time in the Console as before: d3.select("circle").attr("cx", "200"); d3.select("circle").attr("cx", "500"); d3.select("circle").attr("cx", "100"); d3.select("circle").attr("r", "30"); d3.select("circle").attr("r", "130"); d3.select("circle").attr("r", "3"); d3.select("circle").attr("fill", "red"); d3.select("circle").attr("fill", "aliceblue"); d3.select("circle").attr("fill", "lightseagreen"); Note that “select” and “attr” are separate operations chained together with “.” – think pipe (|>) operator. Refresh the page. What happened? Go to Elements. Look at the value of the y1 attribute of the SVG <line> element. Go back to the Console and enter the following: d3.select("line").attr("y1", "10"); Switch back to Elements and observe. What happened? Stay in Elements and refresh the page. What happened to y1? Return to the Console to make style changes to the HTML elements: d3.select("h1").style("color", "purple"); d3.select("h2").style("font-size", "50px"); d3.select("h2").style("font-family", "Impact"); 1.5 Add transitions Try these: d3.select("svg").select("circle").transition().duration(2000).attr("cx", "400"); d3.select("svg").select("ellipse").transition().duration(2000).attr("transform", "translate (400, 400)"); d3.select("svg").select("line").transition().duration(2000).attr("x1", "400"); d3.select("svg").select("line").transition().duration(2000).attr("y1", "250"); d3.select("body").select("p").transition().duration(2000).style("font-size", "72px"); Experiment with more transitions. 1.6 Add interactivity Set up a function to turn the fill color to yellow: function goyellow() {d3.select(this).attr("fill", "yellow")}; Add an event listener to the circle that will be trigger a call to goyellow() on a mouseover: d3.select("svg").select("circle").on("mouseover", goyellow); Test it out. Add the same event listener to the ellipse. Test it out. Create a function goblue() that changes the fill color to blue. Add event listeners to the circle and ellipse that will trigger a call to goblue() on a mouseout. Test out your code. Try out a click event. (Note the use of an anonymous function.) d3.select("svg").select("line").on("click", function() {d3.select(this).attr("stroke-width", "10");}); Try another click event. What’s happening? d3.select("svg").on("click", function(event) {d3.select("text").text(`(${d3.pointer(event)})`)}); 1.7 Examples Check out this fun visualization by Yilan Chen (EDAV 2023) that started with shapes.html: Tricolor Dango "],["web.html", "2 Web tech 2.1 HTML 2.2 CSS 2.3 SVG 2.4 JavaScript 2.5 D3 2.6 HTML tree 2.7 Exercise : shapes", " 2 Web tech Read IDVW2, Chapter 3: Technology Fundamentals There is a lot of material in this chapter. It is worth making the effort to learn it now and start D3 with a solid foundation of elementary HTML/CSS/SVG/JavaScript. Here we examine shapes.html from Chapter 1 to see how the various technologies are combined into a single document. 2.1 HTML Note that shapes.html has an HyperText Markup Language or .html extension; HTML in fact provides the structure for the document. It has a <head> and <body> section. In the <head> section we use <script> tags to link to the D3 library: <script src="https://d3js.org/d3.v7.js"></script> HTML content is enclosed between opening an closing tags such as <h1> and </h1>. HTML class and ID attributes are included inside the opening tags: <h1 class=\"myclass\" id=\"myid\">This is an h1 header.</h1> 2.2 CSS CSS (Cascading Style Sheets) is used for styling web pages, and more importantly for our purposes, selecting elements on a page or in a graphic. We will generally work with internal style sheets since it’s simpler when starting out to have everything in one document. External style sheets, however, are generally the preferred method for web design. 2.2.1 Internal style sheet shapes.html has an internal style sheet: CSS style information appears in the <head> section marked off with <style> tags: <style type="text/css"> h1 {color:red;} /* CSS styling */ p {color:blue;} </style> Here we specify that all HTML <h1> headers should be red and all HTML paragraphs <p> should be blue. This is an example of an internal style sheet. Later we will consider alternatives: external style sheets and inline styling. Styling for coder designed classes is also specified in this section. For example, we could style a “formal” class as such: <style type="text/css"> .formal {color: red; font-size: 30px; font-family: Lucida Calligraphy; } </style> Note that classes are defined by the “.” before the name. 2.2.2 External style sheets External style sheets are .css files that contain styling information and are linked to with a <link> tag in the <head> section of an HTML document: <head> <link rel="stylesheet" href="style.css"> </head> External style sheets are the preferred way of styling as they can easily be modified without changing the web page; in fact, the motivation for CSS came from a desire in the early days of the internet to separate styling from content. Developers have the option now of choosing premade themes, which are shared through external style sheets. They can be quite complex. The .css file for the  Minty  theme from Bootswatch, for example, contains over 10,000 lines. CSS Zen Garden demonstrates the power of external style sheets: the same HTML document takes on very different looks depending on the stylesheet to which it is linked. 2.2.3 Inline styling With inline styling, styling is added to each tag individually: <span style="color: white; background-color: fuchsia; font-family: impact; font-size: 24px; border-style: solid; border-color: limegreen; border-width: 3px"> Styled inline </span> Styled inline This is how early web pages were styled. To take a step back in time, use developer tools to view the source code for the main page of www.dolekemp96.org, an old web site that has been maintained for historical purposes. As you can see, it’s a tedious way of writing content, which internal and external style sheets eliminate. Although you will not be adding inline styling manually, you will notice that when we select elements and change the styling with D3, the modifications are made inline. In other words, we do not make changes to the elements directly, not via a style sheet. 2.3 SVG SVG (Scalable Vector Graphics) is a human readable graphics format that facilitates manipulation of individual elements. You may be familiar with .svg files. Here we have SVG graphics within <svg> tags in the <body> section of the HTML document: <svg width="500" height="300"> <!-- some SVG --> <rect x="20" y="20" width="460" height="260" fill="lightblue"></rect> <circle cx="50" cy="75" r="20" fill="blue"></circle> <ellipse cx="175" cy="100" rx="45" ry="30" fill="green"></ellipse> <text x="150" y="200">(150, 200)</text> <line x1="250" y1="150" x2="300" y2="200" stroke="red" stroke-width="5"></line> </svg> (150, 200) There are very few SVG tags that you’ll need to know, and once we get going with D3, you will not have to code any SVG manually. It is worth doing a little to become familiar with the format and in particular to get used to the new location of the origin. 2.4 JavaScript JavaScript is the most common language for making web pages interactive. Code is executed when pages are opened or refreshed. So far we have run JavaScript in the Console, but have not included it in the web page itself. When we do so, it will be between <script> tags in the <body> section of the HTML document, or in a separate .js file. We will learn JavaScript on an as-needed basis. In terms of data, we will begin with simple arrays: const x = [3, 5, 1, 6, 7] In the Just Enough JS chapter, we cover more complex data structures and some methods for data manipulation. javascript.info is an excellent resource for expanding your knowledge beyond the basics. 2.5 D3 D3 (Data Driven Documents) is a JavaScript library well suited to interactive graphics. As such, it is also included between <script> tags in the <body> section. For D3 to work, you must link to the D3 library in the <head> section of the document. There seems to be a misconception that D3 is a high level language. It is not. You will be working on the pixel level to create graphics, including drawing your own axes and doing other things that you’re not used to doing if you’ve been working in R or Python. On the bright side, after D3, you will gain a new appreciation for base R graphics. You will write code such as plot(iris$Sepal.Length, iris$Sepal.Width, pch = 16, col = iris$Species, las = 1, xlab = \"Sepal.Length\", ylab = \"Sepal.Width\") and think: wow, there are axes! Amazing! It is legitimate to ask why you need to know D3 as a data scientist. Many if not most of you will not be coding in JavaScript from the ground up in your future careers. However, it’s a great way to learn how interactive graphics work under the hood, and will give you a solid foundation which you can draw on to tweak visualizations that you build with high level tools such as Plotly. 2.6 HTML tree While shapes.html appears as a single consistent document, it is actually comprised of multiple languages. HTML, CSS, and SVG are already there, and we will be adding JavaScript / D3 soon. Of note: An HTML document is composed of lines or sections set off with tags. In particular <style> ... </style>, <svg> ... </svg>, and <script> ... </script> indicate the inclusion of CSS, SVG, and JavaScript/D3 respectively. For D3 to work, you must link to a D3 library. To link to the online version, include this line: <script src=\"https://cdn.jsdelivr.net/npm/d3@7\"></script> or <script src=\"https://d3js.org/d3.v7.js\"></script>. Alternatively, you can download a copy of the library from https://d3js.org/getting-started#d3-in-vanilla-html–clicking on d3.v7.js or d3.v7.min.js will download the file. Then add the following to the <head> section of your .html file: <script src="d3.js"></script> There are two main sections. The <head> section contains the title, link to D3 library, and internal CSS. The <body> section contains HTML elements (<h1>, <p>, etc.), SVGs (between <svg>/</svg>tags) and JavaScript/D3 scripts (between <script>/<script>tags). Do not assume that if it works that it is correct; today’s browsers can be very forgiving. Comment syntax varies with language: <!-- single or multiline HTML or SVG comment --> /* single or multiline CSS comment */ // single line JavaScript comment /* JavaScript multiline comment */ 2.7 Exercise : shapes Download a copy of shapes.html by opening this page and clicking File, Save Page As… Set yourself up to work locally in a text editor help. (Developer Tools should not be open; we will not be using the Console.) Add an additional circle to the svg. Add styling to the internal style sheet to style circles. Add two additional paragraphs using the <p> tag. Add an ID attribute to one of the circles. Add a class attribute to two of the <p> tags. Use the internal style sheet to style paragraphs of the class you created in 5. Adjust additional elements as desired. Solution Contributed by Tracy Liu "],["d3console.html", "3 Modify, Add, Remove 3.1 Selections 3.2 Modify existing elements 3.3 Add elements 3.4 Remove elements 3.5 Exercise : green circles 3.6 Exercise : blue circles 3.7 Bind data… finally! 3.8 Exercise : data bind", " 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 .fancy { color: red; font-family: garamond; font-size: 30px; } <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: Select the circle with ID “henry” and make it blue. Select all circles of “apple” class make them red. Select the first circle and add an orange border (use attribute “stroke”), and stroke width (“stroke-width”) of 5. 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. Move all the circles to the right. Move them back to the left and change their color. 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. 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. "],["update-enter-and-exit.html", "4 Update, Enter, and Exit 4.1 Lecture slides 4.2 Use exit selection to remove elements 4.3 Use enter selection to add elements 4.4 Data / enter / append sequence 4.5 Exercise : horizontal bar chart 4.6 Merge selections 4.7 Exercise : merge 4.8 Groups 4.9 General Update Pattern (with merge) 4.10 General Update Pattern (with join) 4.11 Exercise: : functions 4.12 Exercise : vertical bar chart", " 4 Update, Enter, and Exit Read: IDVW2, Chapter 9, pp. 178-184; Chapter 12, pp. 231-249 4.1 Lecture slides D3 Data Bind Jump to data/enter/append Jump to general update pattern 4.2 Use exit selection to remove elements a.k.a. more DOM elements than data values We’ll start with six circles and remove some. Open six_blue_circles.html in Chrome and open the JavaScript Console. Let’s bind four data values to the six circles: d3.select("svg") .selectAll("circle") .data([123, 52, 232, 90]); Click the black triangle to view the _enter, _exit, and _groups fields. We can store the selection in a variable: const circ = d3.select("svg") .selectAll("circle") .data([123, 52, 232, 90]); Let’s look at the exit selection: circ.exit(); Try this: circ.attr("fill", "red"); What happened and why? Now try this: circ.exit().attr("fill", "purple"); What happened and why? What do you think this will do? Try it. circ.exit().transition().duration(2000).remove(); Create a new variable circ2 and compare it to circ: const circ2 = d3.selectAll("circle"); circ.data(); circ2.data(); circ.exit(); circ2.exit(); What’s going on? 4.3 Use enter selection to add elements a.k.a. more data values than DOM elements We’ll start with six_blue_circles.html in Chrome and add some circles. First, let’s bind new data to the circles: const circ = d3.select("svg") .selectAll("circle") .data([123, 52, 232, 90, 34, 12, 189, 110]); And look at the enter selection: circ.enter(); How many placeholders are in the enter selection? Let’s add circles for each of these placeholders: circ.enter() .append("circle") .attr("cx", "100") .attr("cy", (d, i) => i * 50 + 25) .attr("r", "20") .attr("fill", "blue"); Try this: circ.transition() .duration(3000) .attr("cx", "400"); What do you need to do to act on all of the circles? d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cy", (d, i) => (i * 50) + 25) .attr("cx", "200"); 4.4 Data / enter / append sequence We’ll start with nothing–not even an SVG–and add elements with the data / enter / append sequence. Work in the Console on this page (help). Open the JavaScript Console The SVG will be added here: d3.select("div#dea") .append("svg") .attr("width", "400") .attr("height", "250"); Create an array of values: const specialdata = [75, 150, 200]; Add rectangles: d3.select("svg") .selectAll("rect") .data(specialdata) .enter() .append("rect") .attr("x", d => d) .attr("y", d => d) .attr("width", "50") .attr("height", "30") .attr("fill", "pink"); 4.4.1 Labels Note that we can also label the rectangles with the data value: d3.select("svg") .selectAll("text") .data(specialdata) .enter() .append("text") .attr("x", d => d + 25) .attr("y", d => d + 25) .text(d => d) .attr("fill", "blue") .attr("text-anchor", "middle"); 4.5 Exercise : horizontal bar chart Save a copy of this exercise and open it in your text editor. Create a horizontal bar chart with bar widths equal to the data values stored in bardata. Do so by replacing all the “ADD’s” with constants or functions and then uncommenting those lines. const bardata = [300, 100, 150, 225, 75, 275]; It should look like this: Solution 4.6 Merge selections We now know how to work separately with the update, enter, and exit selections. Often in practice we wish to do the same thing or almost the same thing with the enter and update selections. That is where .merge() comes in. If we merge the update and enter selections we don’t have to repeat our code. Open six_blue_circles.html in Chrome. Run the following code in the Console: const newdata = [123, 52, 232, 90, 34, 12, 189, 110]; const svg = d3.select("svg"); const circ = svg.selectAll("circle") .data(newdata); circ.enter() // 2 placeholders .append("circle") // placeholders -> circles .attr("cx", "100") // acts on enter selection only .attr("cy", (d, i) => (i - 5) * 50) .attr("r", "20") .attr("fill", "red") .merge(circ) .transition() .duration(2000) .attr("cx", "200"); Note the pattern: Create a data array Store the svg selection Store the data bind in X X.enter() .append(some shape) *add attributes* // acts on enter selection only (no transitions!) .merge(X) *attributes, other stuff* // acts on enter and update selections Do not include transitions in a stored selection! 4.7 Exercise : merge Open this bar chart in Chrome and work in the Console. (You don’t have to download it.) All of your solutions should begin with: Creating a data array (ex. const dataset = [1, 2, 3];) Selecting the svg and storing it (const svg = d3.select(\"svg\");) Binding the data and storing it (ex. const bars = svg.selectAll(\"rect\").data(dataset));) Change the data to any six other values and update the lengths of the bars. Bind a new dataset, newbardata to the bars, update the bar lengths, and remove any extra bars. newbardata = [250, 125, 80, 100]; Bind a new dataset, reallynewbardata, to the bars, then add additional bars so each data value has a bar. Make the outline (stroke) of the new bars a different color. reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; Use .merge() to combine the update and enter selections into one selection and then transition the height of all of the bars to half their current height. Add text labels inside the bars at the right end with the length of the bar in pixels. Solution 4.8 Groups Open six_blue_circles.html in Chrome. Run this code in the Console: const specialdata = [100, 250, 300]; const bars = d3.select("svg") .selectAll("rect") .data(specialdata) .enter() .append("rect") .attr("x", d => d) .attr("y", d => d) .attr("width", "50") .attr("height", "30") .attr("fill", "red"); What’s going on? Refresh the page, and try the following instead: const svg = d3.select("svg"); const specialdata = [100, 250, 300]; const bars = d3.select("svg") .append("g") .attr("id", "rects") .selectAll("rect") .data(specialdata) .enter() .append("rect") .attr("x", d => d) .attr("y", d => d) .attr("width", "50") .attr("height", "30") .attr("fill", "red"); Compare: d3.select("svg") .select("g#rects") .selectAll("rect") .attr("fill", "purple"); and d3.select("svg") .selectAll("rect") .attr("fill", "purple"); 4.9 General Update Pattern (with merge) Open Developer Tools on this page. Create a function in the Console: function changedata(data) { d3.select("svg#gup") .selectAll("rect") .data(data) .attr("width", d => d); } Test it out: changedata([258, 373, 278, 9, 72, 96]); What happens if there are too many data values? changedata([196, 360, 283, 390, 46, 56, 152]); Let’s use the enter selection to add new bars in this case: function changedata(data) { const bars = d3.select("svg#gup") .selectAll("rect") .data(data); // bars is the update selection bars.enter() .append("rect") .attr("x", "30") // until merge, acts on .attr("y", (d, i) => i * 50) // enter selection only .attr("height", "35") .attr("fill", "lightgreen") .merge(bars) // merge in the update selection .attr("width", d => d); // acts on all bars } What happens if we have more bars than data values? changedata([325, 116, 25]); Let’s add to the function to remove the extra bars in this case: function changedata(data) { const bars = d3.select("svg#gup") .selectAll("rect") .data(data); // bars is the update selection bars.enter() .append("rect") .attr("x", "30") // until merge, acts on .attr("y", (d, i) => i * 50) // enter selection only .attr("height", "35") .attr("fill", "lightgreen") .merge(bars) // merge in the update selection .attr("width", d => d); // acts on all bars bars.exit() .remove(); } Try: changedata([271, 49, 389]); VOILA! We have created the D3 General Update Pattern! It is covered in IDVW in the “Other Kinds of Data Updates” section on pp. 178-186 in Chapter 9. (The earlier part of Chapter 9 deals with data updates in which the number of DOM elements remains the same.) Note that the General Update Pattern changed with D3 Version 4 so avoid examples from Version 3. Also available here: general_update_pattern.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <script src="https://d3js.org/d3.v7.min.js"></script> </head> <body> <script id="s1"> // Create svg and initial bars const svg = d3.select("body") .append("svg") .attr("width", "500") .attr("height", "400"); const bardata = [300, 100, 150, 225, 75, 275]; const bars = svg.selectAll("rect") .data(bardata); bars.enter().append("rect") .attr("x", "30") .attr("y", (d, i) => i*50) .attr("width", d => d) .attr("height", "35") .attr("fill", "lightgreen"); // General Update Pattern function update(data) { const bars = svg.selectAll("rect") // data join .data(data); bars.enter() .append("rect") // add new elements .attr("x", "30") .attr("y", (d, i) => i*50) .attr("width", d => d) .attr("height", "35") .attr("fill", "yellow") .merge(bars) // merge .transition() .duration(2000) .attr("width", d => d) .attr("fill", "orange"); bars.exit().remove(); // remove extra elements } </script> </body> </html> 4.10 General Update Pattern (with join) Optional This is a newer method–introduced in v5–which simplifies the general update pattern by automatically adding elements for the enter selection, removing elements for the exit selection, merging the enter and update selections and then acting on them all with .join(). The update pattern above could be replaced with the following: function changedata2(data) { const svg = d3.select("svg#gup") svg.selectAll("rect") .data(data) .join("rect") // need "rect" for appending elements .attr("x", "30") .attr("y", (d, i) => i * 50) .attr("height", "35") .attr("fill", "lightgreen") .attr("width", d => d); } Try: changedata2([271, 49, 389]); With .join() you can get more granular with enter, update, and exit selections as we did previously. changedata3() is equivalent to changedata2(): function changedata3(data) { const svg = d3.select("svg#gup") svg.selectAll("rect") .data(data) .join( enter => enter.append("rect") .attr("x", "30") .attr("y", (d, i) => i * 50) .attr("height", "35") .attr("width", d => d) .attr("fill", "lightgreen"), update => update .attr("width", d => d), exit => exit.remove() ); } Generally, however, unless you have transitions, you will not need to control the enter, exit and update selections separately. 4.11 Exercise: : functions Open general_update_pattern.html and practice running the update() function with different datasets in the Console. For example: update([100, 200, 300]); Solution 4.12 Exercise : vertical bar chart Save a copy of this exercise and open it in your text editor. Create a vertical bar chart with bar heights equal to the data values stored in bardata. Do so by replacing all the “ADD’s” with constants or functions and then uncommenting those lines. Solution "],["just-enough-js.html", "5 Just Enough JS 5.1 Arrays of arrays 5.2 Arrays of objects 5.3 .map() 5.4 D3 sorting 5.5 D3 statistics 5.6 D3 + .map()", " 5 Just Enough JS Basics: IDVW, pp. 36-52 Before JavaScript ES6, the ‘var’ keyword was used to declare a variable. Variables declared using the ‘var’ keyword are either globally or functionally scoped, they do not support block-level scope. Therefore, in JavaScript ES6, the ‘let’ keyword and ‘const’ keyword were introduced. The ‘let’ keyword deals with a block scope. It can be reassigned but cannot be redeclared. The ‘const’ keyword is also blocked scoped. It cannot be reassigned and cannot be redeclared. As a general rule, you should always declare variables with ‘const’, if you realize that the value of the variable needs to change, go back and change it to ‘let’. For more detailed information regaring ‘var’, ‘let’ and ‘const’, please see Difference between var, let, and const keyword in JavaScript objects, arrays, arrays of objects, functions (and other things) 5.1 Arrays of arrays Open the JavaScript Console // try me in the Console const array_dataset = [[100, 75, 30], [200, 125, 20]]; d3.select("svg#arrays") .selectAll("circle") .data(array_dataset) .enter() .append("circle") .attr("cx", d => d[0]) .attr("cy", d => d[1]) .attr("r", d => d[2]) .attr("fill", "red"); svg#arrays 5.2 Arrays of objects // Try me in the Console const object_dataset = [ {cx: 100, cy: 150, fill: `red`}, {cx: 200, cy: 100, fill: `blue`} ]; d3.select("svg#objects") .selectAll("circle") .data(object_dataset) .enter() .append("circle") .attr("cx", d => d.cx) .attr("cy", d => d.cy) .attr("r", "30") .attr("fill", d => d.fill); svg#objects See also: JavaScript Array of Objects Tutorial 5.3 .map() What’s the issue? In R many operations are vectorized: sqrt(3) ## R output ## [1] 1.732051 x <- c(3, 5, 7) sqrt(x) ## R output ## [1] 1.732051 2.236068 2.645751 Not so in JavaScript: Math.sqrt(3); // Try me in the Console const x = [3, 5, 7]; // Try me in the Console Math.sqrt(x); // Doesn't work... 5.3.1 Simple arrays Use .map() to operate on each array element separately. The concept is similar to lapply() or purrr::map(), but unlike in R, it’s needed for simple arrays. R x <- c(3, 5, 7) sqrt(x) ## R output ## [1] 1.732051 2.236068 2.645751 JavaScript Do something to every element of a simple array: // take the square root of each element const x = [3, 5, 7]; // try me x.map(Math.sqrt); // multiply each element by 3 [4, 10, 12].map(d => d*3); // try me // multiply each element by 3 [4, 10, 12].map(function(d) {return d*3;}); // try me // multiply each element by its index [10, 20, 30, 40].map((d, i) => d*i); // try me R: Sum two arrays # sum two arrays x <- 1:3 y <- 4:6 x + y ## R output ## [1] 5 7 9 JavaScript: Sum two arrays // sum two arrays const x = [1, 2, 3]; const y = [4, 5, 6]; x + y // try me... what went wrong? // sum two arrays const x = [1, 2, 3]; const y = [4, 5, 6]; x.map((d, i) => d + y[i]); // try me 5.3.2 Arrays of arrays Do something to the first item of every element of a nested array: [[1, 2], [3, 4]].map(d => Math.sqrt(d[0])) // try me Sum up all items in each element of the array: [[1, 2, 3], [4, 5, 6]].map(d => d[0] + d[1] + d[2]); // try me Created a nested array out of a simple array: [10, 20, 30].map(d => [d, Math.pow(d, 2)]); 5.3.3 Create arrays of objects Create an array of objects out of a simple array (note the parentheses around the object): [10, 20, 30].map(d => ({n: d, nsq: Math.pow(d, 2)})); // try me [10, 20, 30].map((d, i) => ({index: i, value: d})); // try me 5.4 D3 sorting Use d3.sort() rather than plain JavaScript options. const y = [3, 1, 5, 12, 7]; // try me d3.sort(y); 5.5 D3 statistics link to API D3 brings us back to familiar ground with functions that take an array and return a single value. Here are D3 functions with the same names and behavior as their R equivalents: R D3 min(x) d3.min(x) max(x) d3.max(x) sum(x) d3.sum(x) mean(x) d3.mean(x) median(x) d3.median(x) A few with different names: R D3 range(x) d3.extent(x) var(x) d3.variance(x) sd(x) d3.deviation(x) d3.quantile() takes a single value for p, not an array as in R. (In earlier versions of D3 it was necessary to sort the array before finding quantiles, but this is no longer the case.) R D3 quantile(x) d3.quantile(x, p) Thus for a single quantile we have: const x = [12, 34, 1, 43, 90, 72]; // try me d3.quantile(x, .25); https://github.com/d3/d3/blob/main/API.md#statistics 5.6 D3 + .map() D3 statistics functions combined with .map() can be helpful in a variety of situations. Vectorizing a parameter, for example to mimic quantile(x) in R: R x <- c(1, 12, 34, 43, 72, 90); quantile(x) ## R output ## 0% 25% 50% 75% 100% ## R output ## 1.00 17.50 38.50 64.75 90.00 JavaScript const x = [1, 12, 34, 43, 72, 90]; // try me [0, .25, .5, .75, 1].map(p => d3.quantile(x, p)); Sum up the first item of all elements in an array of arrays: R l <- list(c(100, 200, 40), c(300, 150, 20)) sum(purrr::map_dbl(l, ~.x[1])) ## R output ## [1] 400 JavaScript const dataset = [[100, 200, 40], [300, 150, 20]]; // try me d3.sum(dataset.map(d => d[0])); Sum up all items in each array to create a simple array: R l <- list(c(100, 200, 40), c(300, 150, 20)) purrr::map_dbl(l, ~sum(.x)) ## R output ## [1] 340 470 JavaScript const dataset = [[100, 200, 40], [300, 150, 20]]; // try me dataset.map(d => d3.sum(d)); "],["scales-and-axes.html", "6 Scales and Axes 6.1 Scales 6.2 Margins 6.3 Axes 6.4 Bar chart with categorical labels", " 6 Scales and Axes 6.1 Scales 6.1.1 Lecture slides Scales 6.1.2 Practice See: IDVW2, Chapter 7: Scales Practice creating an ordinal scale in the Console: Open the JavaScript Console const ordscale = d3.scaleBand() .domain([0, 1, 2, 3, 4]) .range([0, 100]); ordscale(1); Try other numbers: ordscale(3);, ordscale(2.5);, ordscale(7);, etc. Add inner padding and try again. See diagram here: https://github.com/d3/d3-scale#band-scales *Be sure to use d3.scaleBand(), not d3.scaleOrdinal() for this use case. 6.1.3 Bar chart d3.scaleBand() IDVW2 Chapter 9, pp. 150-153 Here d3.scaleBand() is used to create an xScale function to convert bar numbers to pixels. Change the w parameter and observe how the bars are resized to fit on the SVG. Code for download d3.scaleLinear() In the next graph, d3.scaleLinear() is added to create a yScale function to convert bar heights to pixels. Change the data and observe how the bars are resized to fit on the SVG. Code for download 6.2 Margins 6.2.1 Lecture slides Margins “Margin convention” const w = 500; const h = 400; const margin = {top: 25, right: 0, bottom: 25, left: 25}; const innerWidth = w - margin.left - margin.right; const innerHeight = h - margin.top - margin.bottom; 6.2.2 Bar chart with margins Code for download 6.3 Axes See: IDVW2, Chapter 8: Axes 6.3.1 Lecture slides Axes 6.3.2 Bar chart with axes Code for download Practice changing the data and seeing what happens. 6.4 Bar chart with categorical labels Code for download "],["interactivity.html", "7 Interactivity 7.1 Bar chart example 7.2 Binding event listeners 7.3 Separating the function and event listener 7.4 Buttons 7.5 Dependent event listeners", " 7 Interactivity Read: IDVW2, Chapter 10 Interactivity 7.1 Bar chart example For this demo we will use this file. 7.2 Binding event listeners (100, 150) It’s helpful to think carefully about what you want to happen when an event listener is triggered and what information you need. Open Developer Tools and try these in the Console. Note that event management changed in v6 so code written for earlier versions of D3 will not work. 7.2.1 Do something unrelated to the element that received the event d3.select("svg") .on("click", function () { d3.select("svg") .append("text") .attr("x", "100") .attr("y", "40") .text("Hello World"); }); 7.2.2 Change an attribute of the element that received the event d3.select("line") .on("click", function() { d3.select(this) .attr("stroke-width", "10"); }); In the context of event handlers, “this” is the element that received the event, a.k.a. what you clicked on if it’s a click event. An alternative (\\(\\geq\\) v6, see link above) is to pass the event and access the element with event.currentTarget: or d3.select("line") .on("click", function(event) { d3.select(event.currentTarget) .attr("stroke", "yellow"); }); 7.2.3 Get the value of an attribute of the element that received the event d3.select("circle") .on("click", function(event) { const rad = d3.select(event.currentTarget).attr("r"); d3.select("text") .text(`The radius is ${rad} pixels.`); }); 7.2.4 Do something with the data bound to the element that received the event d3.select("circle") .data([{s: "red", sw: "15"}]) .on("click", function(event, d) { d3.select(event.currentTarget) .attr("stroke", d.s) .attr("stroke-width", d.sw); }); Note that starting with v6, the data is the 2nd parameter to be passed: function(event, d). In addition, note that you do not need to pass d again when accessing the data: for example we use d.s not d => d.s. As in the previous example, d3.select(this) can be used instead of d3.select(event.currentTarget). Try changing the data value bound to the circle with d3.select(\"circle\").datum(\"10\") and clicking again. 7.2.5 Get the svg location of the event d3.select("svg") .on("click", function(event) { d3.select("text") .text(`(${d3.pointer(event).map(Math.round)})`) }); (Up to v5, d3.mouse(this) was used instead of d3.pointer(event).) 7.3 Separating the function and event listener Examples function goyellow() { d3.select(this) .attr("fill", "yellow") }; d3.select("circle") .on("mouseover", goyellow); 7.4 Buttons 7.4.1 HTML text as buttons HTML: <p id="add">Add an element</p> <p id="remove_left">Remove bar (left)</p> <p id="remove_right">Remove bar (right)</p> JavaScript: d3.selectAll("p") .on("click", function () { const paraID = d3.select(this).attr("id"); if (paraID == "add") { const newvalue = Math.floor(Math.random()*400); bardata.push(newvalue); } else if (paraID == "remove_left") { bardata.shift(); } else { bardata.pop(); }; update(bardata); }); Putting it all together: Vertical bar chart with add / remove buttons and general update pattern vertical_bar.html 7.4.2 Radio buttons HTML: <p id="color" style="background-color: silver; color: white;"> Please select your favorite primary color:</p> <input type="radio" name="fav_color" value="red">red</input> <input type="radio" name="fav_color" value="blue">blue</input> <input type="radio" name="fav_color" value="yellow">yellow</input> Note: type is always radio for radio buttons name is shared for a group of radio buttons value is unique JavaScript: d3.selectAll('input[name="fav_color"]') .on("click", function(event) { var favcolor = event.currentTarget.value; d3.select("p#color").style("color", favcolor); }); Please select your favorite primary color: red blue yellow 7.5 Dependent event listeners In these examples, the behavior or existence of one event listener depends on another. 7.5.1 Global variable example Here the circle click behavior depends on the value of the radio button: if the “Move left” radio button is checked, the circle will move left when clicked. If the “Move right” radio button is checked, the circle will move right when clicked. A global variable is used to keep track of the radio button value. The event listener on the circle conditions the behavior on the value of this global variable. Click the circle.  Move left  Move right svg#radio // global variable keeps track of which radio button is clicked let action = "left"; d3.select("div#rad") .selectAll("input") .on("click", function() { action = d3.select(this).node().value; }); // circle click behavior depends on value of "action" d3.select("svg#radio").select("circle") .on("click", function () { let cx_new; if (action == "left") { cx_new = +d3.select(this).attr("cx") - 50; if (cx_new < 20) cx_new = 20; } else { cx_new = +d3.select(this).attr("cx") + 50; if (cx_new > 280) cx_new = 280; } d3.select(this) .transition() .duration(500) .attr("cx", cx_new); }); 7.5.2 Turn off event listener In this example, the event listeners on the squares are turned on or off depending on the value of the radio button. Event listeners can be removed by setting the behavior to null. Click a square.  Red active  Blue active svg#radio2 // movement function const jump = function () { d3.select(this).transition().duration(500) .attr('y', '0') .transition().duration(500).ease(d3.easeBounce) .attr('y', '75'); }; // initial setup: add event listener to red square d3.select("svg#radio2") .select("rect#red") .on("click", jump); // switch event listeners if radio button is clicked d3.select("div#rad2").selectAll("input") .on("click", function () { if (d3.select(this).node().value == "blue") { d3.select("svg#radio2").select("rect#blue").on("click", jump); d3.select("svg#radio2").select("rect#red").on("click", null); } else { d3.select("svg#radio2").select("rect#red").on("click", jump); d3.select("svg#radio2").select("rect#blue").on("click", null); } }); "],["transitions.html", "8 Transitions 8.1 Examples 8.2 Do this 8.3 Not this 8.4 Strategy 8.5 Exercise : Bar chart with transitions", " 8 Transitions Read IDVW2, Chapter 9: transitions section (pp. 158-178) 8.1 Examples Open Developer Tools and try in the Console: d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cx", "275"); d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cx", "25") .attr("fill", "green"); 8.2 Do this Run simultaneous transitions on different selections: d3.select("svg").selectAll("circle#henry").transition() .duration(2000).attr("cx", "275"); d3.select("svg").selectAll("circle.apple").transition() .duration(2000).attr("cx", "25"); Run sequential transitions on the same selection in one chain: d3.select("svg").selectAll("circle") .transition().duration(2000).attr("cx", "275") .transition().duration(2000).attr("cx", "25"); Transition from something to something: d3.select("svg").append("circle") .attr("cx", "200") .attr("cy", "100") .attr("r", "5") .attr("fill", "lightblue") .transition() .duration(4000) .attr("r", "25") .attr("fill", "blue"); 8.3 Not this DO NOT run two transitions on the same selection at the same time (see p. 172). (What works in the Console will not work in a script.) d3.select("svg").selectAll("circle").transition() .duration(2000).attr("cx", "250"); d3.select("svg").selectAll("circle").transition() .duration(2000).attr("cx", "75"); DO NOT transition from nothing to something: d3.select("svg").append("circle") .transition() .duration(2000) .attr("cx", "200") .attr("cy", "100") .attr("r", "25") .attr("fill", "red"); DO NOT store a selection with a transition (it’s no longer a selection with the transition): Try this: const circ = d3.select("svg") .selectAll("circle") .data([50, 95, 100, 200, 50, 150, 250]) .enter() .append("circle") .attr("cx", d => d) .attr("cy", "100") .attr("fill", "blue") .attr("r", "0") .transition() .duration(2000) .attr("r", "25"); And then this: circ.attr("fill", "green"); DO NOT put a transition before a merge: d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cx", "300") .merge("oldcirc") .attr("fill", "green"); BE AWARE that not everything transitions (for example, text doesn’t.) 8.4 Strategy Example 1 Think carefully about what you want to happen, and then decide what goes before and after the transition. Add bar Remove bar Plan what you want to happen: new bars appear on the right side with orange fill new bars slide into place from the right as old bars are repositioned new bars transition to blue removed bars transition to right before disappearing 8.5 Exercise : Bar chart with transitions Download and make changes to bar_transition.html in a text editor so the transitions work as shown below. Solution code for download rendered version Further reading: Working with Transitions. "],["object.html", "9 Object Constancy 9.1 Lecture slides 9.2 No object constancy 9.3 Object constancy", " 9 Object Constancy 9.1 Lecture slides object_constancy.pdf 9.2 No object constancy Transitions  Off  On Add bar Remove bar (right) Remove bar (left) Of note: Rather than smoothly transitioning off to the left, all bars are resized when “Remove bar (left)” is clicked When \"Remove bar (right) is clicked, the bar on the right immediately disappears, and then the remaining bars transition to their new places to the right. Standalone version: no_object_constancy.html 9.3 Object constancy Transitions  Off  On Add bar Remove bar (right) Remove bar (left) Of note: Bars now smoothly transition off to the left and right Standalone version: object_constancy.html 9.3.1 Practice joining data by key Open the JavaScript Console Try the following: Create an svg with four text elements: const dataset = [{key: 12, x: 163, y: 200}, {key: 14, x: 206, y: 304}, {key: 16, x: 452, y: 152}, {key: 18, x: 321, y: 254}]; const svg = d3.select("#key").append("svg") .attr("width", "600").attr("height", "400"); svg.append("rect").attr("width", "600") .attr("height", "400").attr("fill", "aliceblue"); svg.selectAll("text") .data(dataset, d => d.key) .enter() .append("text") .attr("x", d => d.x) .attr("y", d => d.y) .text(d => `key: ${d.key}`); Bind a new dataset by key: const svg = d3.select("#key").select("svg"); const dataset = [{key: 12, x: 100, y: 200}, {key: 16, x: 250, y: 300}]; svg.selectAll("text") .data(dataset, d => d.key) .exit() .remove(); Then: svg.selectAll("text") .attr("x", d => d.x) .attr("y", d => d.y); And another one: const svg = d3.select("#key").select("svg"); const dataset = [{key: 23, x: 300, y: 150}, {key: 5, x: 450, y: 270}, {key: 16, x: 200, y: 250}]; const databind = svg.selectAll("text") .data(dataset, d => d.key); databind .enter() .append("text") .merge(databind) .attr("x", d => d.x) .attr("y", d => d.y) .text(d => `key: ${d.key}`); databind.exit().remove(); Experiment with other data binds. "],["reading-files.html", "10 Reading files 10.1 Promises 10.2 Local server 10.3 Other local options 10.4 Hosting online", " 10 Reading files As you’ve surely noticed by this point, many things in JavaScript operate on an asynchronous basis. Code is not executed linearly from beginning to end but rather in response to various triggers. For example, event listeners behave asynchronously: code will execute only if a mouse click event occurs. The benefit to reading files asynchronously is that we don’t have to wait to while a file loads for other things to happen. It would be very frustrating to navigate to a new web page and have to wait for all the scripts to finish before we could do anything on the page. 10.1 Promises Loading data is one area where D3 v5 introduces major changes from D3 v4. While v4 uses callbacks, v5 switches to promises, as promises facilitate cleaner and more flexile code than callbacks. The concept is simple. We want to control what code needs to wait until data loaded to be executed and what doesn’t. We can do that with the following structure: const rowConverter = function (d) { return { disp: +d.disp, mpg: +d.mpg, carname: d.carname, cylcolor: d.cylcolor } }; d3.csv("https://raw.githubusercontent.com/jtr13/d3book/main/data/mtcars.csv", rowConverter) .then(function(data) { // stuff that requires the loaded data }) .catch(function(error) { // error handling }); The row converter function is used to select variables and change data types (“+” converts to floating point). d3.csv() returns a promise. If the promise is resolved, the .then() function will execute; if the promise is rejected, the .catch() function will execute. Forget the mindset that you read files and store them in variables for later use. It doesn’t work that way here. The data is read in and acted on immediately. If most of the code requires loaded data, then most of the code will appear in the .then() method. A simple example of loading data in v5 can be found in this block. In contrast to the example above, an anonymous row converter function (with arrow functions) is used instead of calling a separate row converter function. Note as well that it’s not necessary to include all variables in the row converter as this author has done. For example, you could delete all the variables that aren’t used, so that the row converter in the d3.csv line becomes: d => ({ HighwayMpg: parseInt(d.HighwayMpg), Horsepower: parseInt(d.Horsepower), }) You will see that the code still works. For more about d3.csv(), see the d3.fetch API. 10.2 Local server Note: As long as you read from online sources as in the example above, you do not need to set up a local server. For security reasons, Chrome does not let you read local files. To be able to do so, you can run a local server. One option is http-server. Follow the instructions to install http-server, navigate in a terminal to the directory with your html file, and then enter http-server: joycerobbins@MacBook-Pro d3-book-murray % http-server You should get a message that ends with something like this: Starting up http-server, serving ./ ... Available on: http://127.0.0.1:8080 http://192.168.1.54:8080 Hit CTRL-C to stop the server Copy and paste one of the URLs in the browser and you should see an index of subfolders and/or files available in the folder in which you launched the local server: From here you can navigate to the desired file. Take note that you cannot move up in the file structure so be sure to start the server in the highest level directory that you plan to access, or that the files you open need to access. In this particular case I am opening the code files for Scott Murray’s Interactive Data Visualization for the Web, 2nd ed. (available here). Rather than link to the online version of D3 as we’ve been doing, each file links to a downloaded version of D3 located in the top level directory with: <script type=\"text/javascript\" src=\"../d3.js\"></script> Therefore we must launch the local server from one level higher than the folder in which the file we wish to open resides. As indicated, Control-c in the command line will stop the server. 10.3 Other local options A simple way to avoid this issue is to upload data files to GitHub and read them from there. There are other workarounds, including opening Chrome from the command line with the --allow-file-access-from-files flag. 10.4 Hosting online An alternative to the options above are to avoid the issue by hosting your code online. Options for doing so are covering in the chapter on sharing D3 online. "],["share-d3-online.html", "11 Share D3 online 11.1 Quarto 11.2 Observable", " 11 Share D3 online There are a number of ways you can share your D3 code online. Even if you’re not sharing, there are advantages to an online setup, for example, not having to set up a local server as described in the chapter on reading files. 11.1 Quarto Separate .js file Video tutorial To include D3 in a Quarto document, I recommend putting the script in a separate .js file that is linked to both by a .qmd file in your project as well as a temporary .html file that you’ll use while creating the visualization. The rationale is that you won’t have to Quarto render your book / web site each time you update the D3 script. Create a .qmd file for the visualization Create a .qmd file with a link to the D3 library, a link to your D3 script, a <div> for adding an svg in the right place and any additional HTML needed for your visualization. Use can use this file as a template. Note that you do not need <head>, <title>, or <body> tags as these are added in the process of converting the .qmd files to HTML. Example: d3graph.qmd Create a .js file for your D3 script This file should contain your script. To ensure that the svg will appear in the book (and not below it) append the svg to the <div> in your .qmd file. Example: myscript.js Create a separate .html for development purposes Create an .html file with the same contents as your .qmd file. With this setup you can test your code without rendering the book but simply opening the .html file. Example: practice.html iframe Create your entire visualization in a separate .html file and then include in your .qmd file with <iframe>: <iframe src="mybarchart.html" width="400" height="300"></iframe> You can work on the .html file separately and view updates without rendering the book. 11.2 Observable Observable, created by D3 author Mike Bostock, is the official D3 web tool for creating and sharing D3 code. It is a powerful, popular tool–all new D3 code examples are now presented in Observable–but program flow is different than it is for stand-alone JavaScript. If you’re interested in learning more, see Why Observable. "],["line-charts.html", "12 Line charts 12.1 Lecture slides 12.2 SVG <line> element 12.3 SVG <path> element 12.4 Data for line chart 12.5 Create a line generator 12.6 Put the line generator to work 12.7 Additional Resources", " 12 Line charts Read: IDVW2, Chapter 11 Using Paths 12.1 Lecture slides line_charts.pdf 12.2 SVG <line> element (Use for two points only.) <line x1="0" y1="80" x2="100" y2="20" stroke="black" /> const x1 = 0; const y1 = 80; const x2 = 100; const y2 = 20; d3.select("svg") .append("line") .attr("x1", x1) .attr("x2", x2) .attr("y1", y1) .attr("y2", y2); 12.3 SVG <path> element (Use if you have more than two points.) <svg width = "500" height = "400"> <path d="M 50 400 L 100 300 L 150 300 L 200 33 L 250 175 L 300 275 L 350 250 L 400 125" fill="none" stroke="red" stroke-width="5"> </path> </svg> d attribute: M = move to L = line to More options: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d 12.4 Data for line chart Format that we have: Day High Temp April 1 60 April 2 43 April 3 43 April 4 56 April 5 45 April 6 62 April 7 49 Format that we need looks something like this: <path class="line" fill="none" d="M0,149.15254237288136L71.42857142857143,264.40677966101697L142.85714285714286,264.40677966101697L214.28571428571428,176.27118644067798L285.7142857142857,250.84745762711864L357.14285714285717,135.59322033898303L428.57142857142856,223.72881355932205"></path> 12.5 Create a line generator Expects data in an array of 2-dimensional arrays, that is, an array of (x,y) pairs: const dataset = [ [0, 60], [1, 43], [2, 43], [3, 56], [4, 45], [5, 62], [6, 49] ]; const mylinegen = d3.line() Test it in the Console: mylinegen(dataset); Add an ordinal scale for x: const xScale = d3.scaleBand() .domain(d3.range(dataset.length)) .range([0, 500]) … and a linear scale for y: const yScale = d3.scaleLinear() .domain([d3.min(dataset, d => d[1]) - 20, d3.max(dataset, d => d[1]) + 20]) .range([400, 0]); *Why d[1] instead of d? (See p. 122) Add accessor functions .x() and .y(): mylinegen .x(d => xScale(d[0])) .y(d => yScale(d[1])); Test again: mylinegen(dataset); Now let’s add a <path> element with that d attribute: (this step is just for learning purposes…) const mypath = mylinegen(dataset); d3.select("svg").append("path").attr("d", mypath) .attr("fill", "none").attr("stroke", "red") .attr("stroke-width", "5"); 12.6 Put the line generator to work Now let’s do it the direct way: bind the datum and calculate the path in one step: d3.select("svg").append("path") .datum(dataset) .attr("d", mylinegen) .attr("fill", "none") .attr("stroke", "teal") .attr("stroke-width", "5"); Finally, we’ll add a class and style definitions: <style> .linestyle { fill: none; stroke: teal; stroke-width: 5px; } </style> The append(\"path\") line becomes: svg.append("path") .datum(dataset) .attr("d", mylinegen) .attr("class", "linestyle"); .linestyle { fill: none; stroke: teal; stroke-width: 5px; } Putting it all together, we have: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Line generator</title> <script src="https://d3js.org/d3.v7.js"></script> <style type ="text/css"> .linestyle { fill: none; stroke: teal; stroke-width: 5px; } </style> </head> <body> <script> const w = 500; const h = 400; const svg = d3.select("svg#noaxes"); const dataset = [ [0, 60], [1, 43], [2, 43], [3, 56], [4, 45], [5, 62], [6, 49] ]; let xScale = d3.scaleBand() .domain(d3.range(dataset.length)) .range([0, w]); let yScale = d3.scaleLinear() .domain([d3.min(dataset, d => d[1]) - 20, d3.max(dataset, d => d[1]) + 20]) .range([h, 0]); const mylinegen = d3.line() .x(d => xScale(d[0])) .y(d => yScale(d[1])); svg.append("path") .datum(dataset) .attr("d", mylinegen) .attr("class", "linestyle"); </script> </body> </html> And another example with axes: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title></title> <script src="https://d3js.org/d3.v7.js"></script> </head> <body> <svg id="withaxes" width="600" height="400"></svg> <script> const svg2 = d3.select("svg#withaxes") const margin = {top: 20, right: 50, bottom: 30, left: 50} const width = +svg2.attr("width") - margin.left - margin.right const height = +svg2.attr("height") - margin.top - margin.bottom const g = svg2.append("g").attr("transform", `translate(${margin.left}, ${margin.top})`); const parseTime = d3.timeParse("%d-%b-%y"); xScale = d3.scaleTime().range([0, width]); yScale = d3.scaleLinear() .domain([20, 80]) .range([height, 0]); const xAxis = d3.axisBottom() .scale(xScale) .tickFormat(d3.timeFormat("%Y-%m-%d")); const line = d3.line() .x(d => xScale(d.date)) .y(d => yScale(d.high)); const data = [{"date":"1-Apr-18","high":60}, {"date":"2-Apr-18","high":43}, {"date":"3-Apr-18","high":43}, {"date":"4-Apr-18","high":56}, {"date":"5-Apr-18","high":45}, {"date":"6-Apr-18","high":62}, {"date":"7-Apr-18","high":49}]; data.forEach(function(d) { d.date = parseTime(d.date); }); xScale .domain(d3.extent(data, d => d.date)); g.append("g") .attr("transform", `translate(0, ${height})`) .call(xAxis); g.append("g") .call(d3.axisLeft(yScale)) g.append("path") .datum(data) .attr("class", "line") .attr("fill", "none") .attr("stroke", "red") .attr("stroke-width", 1.5) .attr("d", line); </script> </body> </html> (Also uses: d3.timeParse() and JavaScript Array.foreach() ) 12.7 Additional Resources Multiple Time Series in D3 by Eric Boxer (EDAV 2018) "],["layouts.html", "13 Layouts 13.1 Lecture slides 13.2 Set up data and stack method 13.3 Set up scales 13.4 Add groups 13.5 Add rectangles 13.6 Get the code", " 13 Layouts IDVW2, Chapter 13 Layouts (d3.stack() only, pp. 264-270) 13.1 Lecture slides layouts.pdf 13.2 Set up data and stack method Open the JavaScript Console // try me in the Console const w = 500; const h = 300; // original data const dataset = [ { apples: 5, oranges: 10, grapes: 22 }, { apples: 4, oranges: 12, grapes: 28 }, { apples: 2, oranges: 19, grapes: 32 }, { apples: 7, oranges: 23, grapes: 35 }, { apples: 23, oranges: 17, grapes: 43 } ]; // set up stack method const stack = d3.stack() .keys([ "apples", "oranges", "grapes" ]) .order(d3.stackOrderDescending); // data, stacked const series = stack(dataset); Try stack(dataset) in the Console. 13.3 Set up scales const xScale = d3.scaleBand() .domain(d3.range(dataset.length)) .range([0, w]) .paddingInner(0.05); const yScale = d3.scaleLinear() .domain([0, d3.max(dataset, d => d.apples + d.oranges + d.grapes)]) .range([h, 0]); const colors = d3.scaleOrdinal(d3.schemeCategory10); Enter d3.schemeCategory10 in the Console. Now try out the colors function. What is the domain? See built-in color options here. Of course you are not limited to these and can use any appropriate color scheme. 13.4 Add groups //Create SVG element const svg = d3.select("#stacked") .append("svg") .attr("width", w) .attr("height", h); // Add a group for each row of data const groups = svg.selectAll("g") .data(series) .enter() .append("g") .style("fill", (d, i) => colors(i)); [There’s a div with id “stacked” here.] Why doesn’t anything show up yet? Right-click and Inspect to find out. 13.5 Add rectangles // Add a rect for each data value const rects = groups.selectAll("rect") .data(d => d) .enter() .append("rect") .attr("x", (d, i) => xScale(i)) .attr("y", d => yScale(d[1])) .attr("height", d => yScale(d[0]) - yScale(d[1])) .attr("width", xScale.bandwidth()); 13.6 Get the code Code for download "],["general-advice.html", "14 General Advice 14.1 Debugging Tips 14.2 Other", " 14 General Advice 14.1 Debugging Tips Make extensive use of Elements to see what’s being added to the DOM. Make extensive use of Console to check the values of variables and/or test code. Pay attention to errors in the Console. Use console.log() esp. in functions Post Minimal Working Examples on Ed Discussion in Canvas. See: “How to create a Minimal, Complete, and Verifiable example” (But don’t worry if it’s not perfect, we’re not going to judge.) Use a text editor that helps you identify unmatched () {} []. Have a tip? Click the button, add the tip, and create a pull request. 14.2 Other Save working versions! Pay attention to the order "],["solutions.html", "15 Solutions ", " 15 Solutions Web tech: shapes Solution Contributed by Tracy Liu D3 in the Console: green circles Select the circle with ID “henry” and make it blue. d3.select("svg") .select("circle#henry") .transition() // optional .duration(1000) // optional .attr("fill","blue"); Select all circles of “apple” class make them red. d3.select("svg") .selectAll("circle.apple") .attr("fill", "red"); Select the first circle and add an orange border (“stroke”), and stroke width (“stroke-width”) of 5. d3.select("svg") .select("circle") .attr("stroke", "orange") .attr("stroke-width", "5"); Select all circles of “apple” class and move them to the middle of the svg. d3.select("svg") .selectAll("circle.apple") .transition() .duration(2000) .attr("cx", d3.select("svg").attr("width") / 2) .attr("cy", d3.select("svg").attr("height") / 2); Contributed by Tracy Liu D3 in the Console: blue circles Move all the circles to the right. const svg = d3.select("svg") svg.selectAll("circle") .transition() .duration(2000) .attr("cx", 450); Move them back to the left and change their color. svg.selectAll("circle") .transition() .duration(2000) .attr("cx", 50) .attr("fill", "red"); 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. svg.select("circle#third") .transition() .duration(2000) .attr("cx", "450"); Move all the circles to the middle of the screen, then move them all to the same location. svg.selectAll("circle") .transition() .duration(2000) .attr("cx", 250) .transition() .duration(2000) .attr("cy", 200); (Extra: return the circles to their starting positions.) const circ = svg.selectAll("circle") .data([100, 150, 200, 250, 300, 350]); circ.transition() .duration(2000) .attr("cx", 50) .attr("cy", d => d) .attr("fill", "blue"); Contributed by Tracy Liu D3 in the Console: data bind const bluedata = [30, 20, 40, 70, 120, 70]; const circ = d3.select("svg") .selectAll("circle") .data(bluedata); circ.transition() .duration(2000) .attr("fill", "pink") .attr("r", d => d / 2) .attr("cx", d => 3 * d); Contributed by Tracy Liu Update, Enter, and Exit: horizontal bar chart For this exercise open up your text editor. We will be operating inside the <script> tags. Our goal is to create a horizontal bar chart. The first step is to create the svg element where our graph will live in. We want to make sure to make it tall and wide enough to hold the data of the given array. We also store our data in an array. const bardata = [300, 100, 150, 225, 75, 275]; const svg = d3.select("body") .append("svg") .attr("width", "700") .attr("height", "400"); Next we will bind the data to the DOM rectangles and store the selection in a variable called bars. Since there are no DOM rectangles (yet), the update and exit selections will be empty. Note that nothing will appear on the screen yet since the enter elements are not combined with DOM elements. const bars = svg.selectAll("rect") .data(bardata); Next step is to create DOM elements from the enter selection and give them the correct attributes to form a horizonal barchart. First we will set x to zero and then in order to set the y distances between the bars we will use each datapoint’s index. The mapping function for the y coordinate is (d, i) => i * 25 + 10 works as follows: When i = 0, y = 10, when i = 1, y = 35, when i = 2, y = 60, etc. Lastly, we want each rectangle’s width to be equal to its data value. bars.enter() .append("rect") .attr("x", 0) .attr("y", (d, i) => i * 25 + 10) .attr("width", d => d) .attr("height", "20") .attr("fill", "pink"); The complete solution is here Contributed by Kassie Papasotiriou Update, Enter, and Exit: merge Change the data to any six other values and update the lengths of the bars. console: const bardata = [130, 210, 90, 300, 200, 50]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(bardata); bars.transition() .duration(2000) .attr("width", d => d); Bind a new dataset, newbardata to the bars, update the bar lengths, and remove any extra bars. const newbardata = [250, 125, 80, 100]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(newbardata); bars.transition() .duration(2000) .attr("width", d => d) bars.exit() .transition() .duration(2000) .attr("width", 0) .remove(); Bind a new dataset, reallynewbardata, to the bars, then add additional bars so each data value has a bar. Make the outline (stroke) of the new bars a different color. const reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(reallynewbardata); bars.transition() .duration(2000) .attr("width", d => d) bars.enter() .append("rect") .attr("width", 0) .attr("x", 30) .attr("y", (d, i) => 50 * i) .attr("height", 35) .attr("width", d => d) .attr("fill", "lightgreen") .attr("stroke", "pink") .attr("stroke-width", 5); Use .merge() to combine the update and enter selections into one selection and then transition the length of all of the bars to half their current length. const reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(reallynewbardata); bars.enter() .append("rect") .attr("x", 30) .attr("y", (d, i) => 50 * i) .attr("height", 35) .attr("width", d => d) .attr("fill", "lightgreen") .attr("stroke", "pink") .attr("stroke-width", 5) .merge(bars) .transition() .duration(2000) .attr("height", "35") .attr("width", d => d); Add text labels inside the bars at the right end with the length of the bar in pixels. const reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; const svg = d3.select("svg"); const bartext = svg.selectAll("text") .data(reallynewbardata); bartext.enter() .append("text") .attr("x", d => d - 5) .attr("y", (d, i) => 50 * i + 12) .text(d => d); Contributed by Tracy Liu Update, Enter, and Exit: functions Question Answers Since there are no questions we will try out a few things and explain why the bars are behaving that way. Open up the file on Chrome and type the following in you Console: By running the code below we are rebinding the new data to the DOM elements. In this case, the inital data and the new data have the same length so no new elements will be added and none will be removed. We update the data, change the fill and then change the width. update([100, 200, 300, 400, 200, 300]); By running the code below, the two data values are bound to the first two DOM elements. The color and width are changed the extra DOM elements that did not receive data values are removed. update([200, 400]); By running the code below (without refreshing the page from 2.), we bind the data, and two of the values will be in the enter selection. For those two values we create two more DOM elements, fill them with yellow and adjust their width. Next we merge all elements together and to the merge selection we readjust the widths to make sure that all elements have the correct length and we turn them to orange. Note that since in this scenario we had enter elements there will be nothing in the exit selection. update([200, 300, 200, 125]); Update, Enter, and Exit: vertical bar chart Question Answers In order to change a horizontal bar chart to a vertical barchart we need to first flip the x and y coordinates. If we only make this change we will notice that our barchart is not vertical but it is flipped (meaning that all bars seem to be upside-down). This is because the coordinate system in the svg starts from the top left and not the bottom left as we are used to. Therefore in order to bring everything “down”, we need to tweak the y coordinate. In order to do that we can subtract from the height of the svg the data value of each bar. In this way all bars will end at 400 and start d pixels before the end. // Create svg with same data const svg = d3.select("body") .append("svg") .attr("width", "500") .attr("height", "400"); const bardata = [300, 100, 150, 225, 75, 275]; const bars = svg.selectAll("rect") .data(bardata); bars.enter() .append("rect") .attr("x", (d, i) => i * 50 + 50) .attr("y", d => 400 - d) .attr("width", "35") .attr("height", d => d) .attr("fill", "lightgreen"); In addition to that, we need to reflect those changes in the update function and in addition to updating all bar’s heights we need to also update all barsnewy` coordinates. bars.enter() .append("rect") // add new elements .attr("x", (d, i) => i * 50 + 50) .attr("y", d => 400 - d) .attr("width", "35") .attr("height", d => d) .attr("fill", "yellow") .merge(bars) // merge .transition() .duration(2000) .attr("y", d => 400 - d) .attr("height", d => d) .attr("fill", "orange"); The complete solution is here Contributed by Kassie Papasotiriou Transitions: bar chart with transitions code for download rendered version "],["correlation-coefficient.html", "16 Correlation Coefficient", " 16 Correlation Coefficient h2, h3, p, text { font-family: sans-serif; text-anchor: middle; } Correlation Coefficient The correlation coefficient (r) is a measure of the linear relationship between two variables x and y. To get a sense of the connection between the appearance of points – (x,y) pairs – in a scatterplot and the value of r, click anywhere on the graph to add points. To remove points, click the Remove Points button and then mouseover points. The correlation coefficient is shown below. Add points Remove points Click on the chart below to add points. "],["spearman-rank-correlation-coefficient.html", "17 Spearman Rank Correlation Coefficient", " 17 Spearman Rank Correlation Coefficient h2, h3, p, text { font-family: sans-serif; text-anchor: middle; } .rank { fill: white; font-weight: bold; dominant-baseline: central; /* https://stackoverflow.com/questions/12250403/vertical-alignment-of-text-element-in-svg */ } #rho { font-family: serif; font-size: 18pt; } Drag the circles to reorder the rankings in Groups A & B. The correlation coefficient, \\(\\rho\\), is displayed below. \\(\\LARGE \\rho = 1 - \\frac{6 \\Sigma{d_i^2}}{n(n^2 -1)} =\\) \\(d_{i}\\) = difference between the two ranks of each observation \\(n\\) = number of observations "],["population-density.html", "18 Population Density", " 18 Population Density * { font-family: sans-serif; } .source { font-size: 9pt; } New York City Each dot represents a person. Data source: http://www.demographia.com/dm-nyc.htm Standalone version: density.html "],["weather-forecast.html", "19 Weather forecast", " 19 Weather forecast * { font-family: sans-serif; } text { font-size: 12px; } Weather forecast for Columbia University Lat, Lon: 40.807793, -73.962144 (Hover over points for more details.) Fahrenheit Celsius "],["appendix-advanced-css.html", "20 Appendix: advanced CSS 20.1 Buttons", " 20 Appendix: advanced CSS Work-in-progress 20.1 Buttons 20.1.1 .active This is a class not a pseudoclass, indicates which button(s) are pressed. 20.1.2 :active A pseudoclass. Clicking is the main way to trigger an :active state. 20.1.3 :focus A pseudoclass. Mainly used for tabbing. Clicking may focus a button but not always. A box shadow such as box-shadow: 0 0 0 3px lightskyblue; is a good choice 20.1.4 :hover As expected, can have separate hover behavior for .active buttons. 20.1.5 Example See the Pen Button .active vs :active by Joyce Robbins (https://codepen.io/jtr13) on CodePen. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] +[["index.html", "D3 for R Users Welcome to D3 0.1 Workflow", " D3 for R Users Joyce Robbins 2024-11-13 Welcome to D3 rect { pointer-events: all; } .node { fill: blue; } .cursor { fill: none; stroke: brown; pointer-events: none; } .link { stroke: red; } .svg-container { display: inline-block; position: relative; width: 100%; padding-bottom: 20%; vertical-align: top; overflow: hidden; } .svg-content { display: inline-block; position: absolute; top: 0; left: 0; } Adapted from Build Your Own Graph! This guide serves as a companion text to Scott Murray’s Interactive Data Visualization for the Web, 2nd edition–henceforth IDVW2–a required text for GR5702. Be sure to get the second edition, which is a comprehensive update to D3 version 4. The first edition uses D3 version 3, which is not compatible. (The current version of D3 is actually v7. However, since differences between v4 and v5/v6/v7 are minimal, unless otherwise indicated in this guide, the code in IDVW2 will work with either.) We rely on the text heavily but also deviate from it in several ways. IDVW2 is written for graphics designers not data science students so the pain points are somewhat different. D3 is a JavaScript library, not a standalone language, so any time we refer to D3 we really mean D3/JavaScript, though it is not necessary to know JavaScript well before beginning; we will learn as we go. Most of the JavaScript we use is covered in IDVW2, though we also use some newer JavaScript options from ES5 and ES6, such as .map(), .filter(), arrow functions and template literals, that make coding easier (and more like R!)1 We use different examples, though you are strongly encouraged to study Murray’s code examples in addition to reading the text. Particularly through the first half, we don’t follow the text in order, so always refer to this guide first which will direct you to the pages of the text that you should read. This is very much a work-in-progress so please submit issues on GitHub to provide feedback and edit or add text by submitting pull requests. (Click the icon at the top of each page to get started. More detailed instructions are available on edav.info. If you would just like to view the source code, click the icon.) 0.1 Workflow A big hurdle to learning a new language is just getting setup. Often authors forget to mention what your programming environment should look like, what should be open on the screen. I will try not to do that and be as clear as possible so you know where you should be entering the code in the pages that follow. This task is somewhat complicated by the fact that we will be using a variety of workflow options. This section will serve as a reference guide; future sections will link back here as appropriate. All of our workflows require Google Chrome, so if you don’t have it already, download and install it. 0.1.1 JavaScript Console With this workflow we will open a web page–either online or local–in Chrome and run JavaScript in the Console. To view the Console, open Chrome DevTools by clicking View, Developer, JavaScript Console if you have a menu bar in Chrome, using a keyboard shortcut (Mac: option+command+j; Windows, Linux, Chrome OS: control+shift+J), or employing another one of the many options for doing so. The Console is one piece of a suite of tools available in the browser. With the DevTools open, your screen will look like this: The next chapter, Jump in the deep end, employs this workflow. 0.1.2 This book in the Console If you’re not reading the .pdf version, you can open DevTools on this very page. This is very convenient because not only to you not have to leave this book to practice D3, you can copy code blocks and paste them in the Console. In addition to opening DevTools (see above), close the side bar by clicking on the (“Toggle Sidebar”) icon on the top left of the page, to the left of the search icon, to give yourself more screen space. Let’s try it out. Open the JavaScript Console svg#demo Scroll so that both the blue rectangle above and the code chunk below are visible on your screen. Toggle the sidebar, open the Console, and then move the mouse onto the code block so the icon appears. Click on it to copy the code, paste it in the Console, and then press return. d3.select("svg#demo") .append("circle") .attr("cx", "-25") .attr("cy", "100") .attr("r", "20") .attr("fill", "red") .transition() .duration(3000) .attr("cx", "325") .remove(); Pretty neat. 0.1.3 Text editor This is a very basic local setup in which the same .html is open both in a text editor (if you don’t want to stray too far from home, use RStudio) and in a web browser (we will use Chrome), each on one half of your screen. The workflow is: make changes to the file in the text editor, save the changes and then refresh the page in the browser to see the updates. Keyboard shortcuts for saving and refreshing (on the Mac, command-s and command-r respectively) are very helpful. Let’s try an example: Download a copy of shapes.html by opening this page and clicking File, Save Page As… Open the file in a text editor of your choice on one half of your screen. On the other half of your screen open the same file in Chrome. As you make changes to the .html file, save the file and then refresh the browser to see the effects. Your screen should look like this: Ok, they’re not actually that new, but it takes a while for new JavaScript to catch on, mainly due to concern with maintaining compatibility with older browsers. Since D3 itself is not compatible with very old browsers, and since we can’t focus on everything at once, we are not going to concern ourselves with browser compatibility. If you are interested in this, caniuse.com is very helpful for looking up what works where.↩︎ "],["jump.html", "1 Quick demo 1.1 Get ready 1.2 Elements tab 1.3 Console tab 1.4 Modify elements 1.5 Add transitions 1.6 Add interactivity 1.7 Examples", " 1 Quick demo Let’s skip the explanations and start coding in D3 right now. Then we’ll go back and learn step by step. In this chapter we will work in the JavaScript Console (help). 1.1 Get ready If you don’t have it, install the Chrome browser. Download a copy of shapes.html by opening this file and choosing File, Save Page As… If Chrome is your default browser, open shapes.html by double clicking it. Otherwise, open Chrome first, click File, Open File…, and then choose shapes.html from the directory where you saved it. 1.2 Elements tab Open Chrome DevTools (help). Hover the mouse over various elements in the <body> ... </body> section. Observe the highlighted sections in the rendered web page on the left of the screen. Click on the mini black triangles to the left of the <body> and <svg> tags if needed to open these sections of the DOM tree. Your screen should look like this: Now try the reverse: right click on elements on the web page, choose “Inspect” and see what is highlighted in the Elements pane. Get comfortable with the connection between the code on the right and the rendered elements on the left. 1.3 Console tab Switch to the Console tab, next to the Elements tab. Let’s practice running some code. Note that the code is unrelated to the shapes.html web page that we have open. We will spend a lot of time in the Console since it’s interactive – think R console. Eventually we will switch to including JavaScript/D3 in .html or .js files and use the Console only for testing things out or debugging. Type the following lines of code at the prompt (>), press enter after each line–that is, after the semicolon (;)–and see what happens: 3 + 4; "3" + "4"; x = [1, 2, 3]; x[1]; x + 1; y = {a: 3, b: 4}; y["b"]; 1.4 Modify elements Now we’ll start using D3 to manipulate elements on the page. Try the following, by entering one line at a time in the Console as before: d3.select("circle").attr("cx", "200"); d3.select("circle").attr("cx", "500"); d3.select("circle").attr("cx", "100"); d3.select("circle").attr("r", "30"); d3.select("circle").attr("r", "130"); d3.select("circle").attr("r", "3"); d3.select("circle").attr("fill", "red"); d3.select("circle").attr("fill", "aliceblue"); d3.select("circle").attr("fill", "lightseagreen"); Note that “select” and “attr” are separate operations chained together with “.” – think pipe (|>) operator. Refresh the page. What happened? Go to Elements. Look at the value of the y1 attribute of the SVG <line> element. Go back to the Console and enter the following: d3.select("line").attr("y1", "10"); Switch back to Elements and observe. What happened? Stay in Elements and refresh the page. What happened to y1? Return to the Console to make style changes to the HTML elements: d3.select("h1").style("color", "purple"); d3.select("h2").style("font-size", "50px"); d3.select("h2").style("font-family", "Impact"); 1.5 Add transitions Try these: d3.select("svg").select("circle").transition().duration(2000).attr("cx", "400"); d3.select("svg").select("ellipse").transition().duration(2000).attr("transform", "translate (400, 400)"); d3.select("svg").select("line").transition().duration(2000).attr("x1", "400"); d3.select("svg").select("line").transition().duration(2000).attr("y1", "250"); d3.select("body").select("p").transition().duration(2000).style("font-size", "72px"); Experiment with more transitions. 1.6 Add interactivity Set up a function to turn the fill color to yellow: function goyellow() {d3.select(this).attr("fill", "yellow")}; Add an event listener to the circle that will be trigger a call to goyellow() on a mouseover: d3.select("svg").select("circle").on("mouseover", goyellow); Test it out. Add the same event listener to the ellipse. Test it out. Create a function goblue() that changes the fill color to blue. Add event listeners to the circle and ellipse that will trigger a call to goblue() on a mouseout. Test out your code. Try out a click event. (Note the use of an anonymous function.) d3.select("svg").select("line").on("click", function() {d3.select(this).attr("stroke-width", "10");}); Try another click event. What’s happening? d3.select("svg").on("click", function(event) {d3.select("text").text(`(${d3.pointer(event)})`)}); 1.7 Examples Check out this fun visualization by Yilan Chen (EDAV 2023) that started with shapes.html: Tricolor Dango "],["web.html", "2 Web tech 2.1 HTML 2.2 CSS 2.3 SVG 2.4 JavaScript 2.5 D3 2.6 HTML tree 2.7 Exercise : shapes", " 2 Web tech Read IDVW2, Chapter 3: Technology Fundamentals There is a lot of material in this chapter. It is worth making the effort to learn it now and start D3 with a solid foundation of elementary HTML/CSS/SVG/JavaScript. Here we examine shapes.html from Chapter 1 to see how the various technologies are combined into a single document. 2.1 HTML Note that shapes.html has an HyperText Markup Language or .html extension; HTML in fact provides the structure for the document. It has a <head> and <body> section. In the <head> section we use <script> tags to link to the D3 library: <script src="https://d3js.org/d3.v7.js"></script> HTML content is enclosed between opening an closing tags such as <h1> and </h1>. HTML class and ID attributes are included inside the opening tags: <h1 class=\"myclass\" id=\"myid\">This is an h1 header.</h1> 2.2 CSS CSS (Cascading Style Sheets) is used for styling web pages, and more importantly for our purposes, selecting elements on a page or in a graphic. We will generally work with internal style sheets since it’s simpler when starting out to have everything in one document. External style sheets, however, are generally the preferred method for web design. 2.2.1 Internal style sheet shapes.html has an internal style sheet: CSS style information appears in the <head> section marked off with <style> tags: <style type="text/css"> h1 {color:red;} /* CSS styling */ p {color:blue;} </style> Here we specify that all HTML <h1> headers should be red and all HTML paragraphs <p> should be blue. This is an example of an internal style sheet. Later we will consider alternatives: external style sheets and inline styling. Styling for coder designed classes is also specified in this section. For example, we could style a “formal” class as such: <style type="text/css"> .formal {color: red; font-size: 30px; font-family: Lucida Calligraphy; } </style> Note that classes are defined by the “.” before the name. 2.2.2 External style sheets External style sheets are .css files that contain styling information and are linked to with a <link> tag in the <head> section of an HTML document: <head> <link rel="stylesheet" href="style.css"> </head> External style sheets are the preferred way of styling as they can easily be modified without changing the web page; in fact, the motivation for CSS came from a desire in the early days of the internet to separate styling from content. Developers have the option now of choosing premade themes, which are shared through external style sheets. They can be quite complex. The .css file for the  Minty  theme from Bootswatch, for example, contains over 10,000 lines. CSS Zen Garden demonstrates the power of external style sheets: the same HTML document takes on very different looks depending on the stylesheet to which it is linked. 2.2.3 Inline styling With inline styling, styling is added to each tag individually: <span style="color: white; background-color: fuchsia; font-family: impact; font-size: 24px; border-style: solid; border-color: limegreen; border-width: 3px"> Styled inline </span> Styled inline This is how early web pages were styled. To take a step back in time, use developer tools to view the source code for the main page of www.dolekemp96.org, an old web site that has been maintained for historical purposes. As you can see, it’s a tedious way of writing content, which internal and external style sheets eliminate. Although you will not be adding inline styling manually, you will notice that when we select elements and change the styling with D3, the modifications are made inline. In other words, we do not make changes to the elements directly, not via a style sheet. 2.3 SVG SVG (Scalable Vector Graphics) is a human readable graphics format that facilitates manipulation of individual elements. You may be familiar with .svg files. Here we have SVG graphics within <svg> tags in the <body> section of the HTML document: <svg width="500" height="300"> <!-- some SVG --> <rect x="20" y="20" width="460" height="260" fill="lightblue"></rect> <circle cx="50" cy="75" r="20" fill="blue"></circle> <ellipse cx="175" cy="100" rx="45" ry="30" fill="green"></ellipse> <text x="150" y="200">(150, 200)</text> <line x1="250" y1="150" x2="300" y2="200" stroke="red" stroke-width="5"></line> </svg> (150, 200) There are very few SVG tags that you’ll need to know, and once we get going with D3, you will not have to code any SVG manually. It is worth doing a little to become familiar with the format and in particular to get used to the new location of the origin. 2.4 JavaScript JavaScript is the most common language for making web pages interactive. Code is executed when pages are opened or refreshed. So far we have run JavaScript in the Console, but have not included it in the web page itself. When we do so, it will be between <script> tags in the <body> section of the HTML document, or in a separate .js file. We will learn JavaScript on an as-needed basis. In terms of data, we will begin with simple arrays: const x = [3, 5, 1, 6, 7] In the Just Enough JS chapter, we cover more complex data structures and some methods for data manipulation. javascript.info is an excellent resource for expanding your knowledge beyond the basics. 2.5 D3 D3 (Data Driven Documents) is a JavaScript library well suited to interactive graphics. As such, it is also included between <script> tags in the <body> section. For D3 to work, you must link to the D3 library in the <head> section of the document. There seems to be a misconception that D3 is a high level language. It is not. You will be working on the pixel level to create graphics, including drawing your own axes and doing other things that you’re not used to doing if you’ve been working in R or Python. On the bright side, after D3, you will gain a new appreciation for base R graphics. You will write code such as plot(iris$Sepal.Length, iris$Sepal.Width, pch = 16, col = iris$Species, las = 1, xlab = \"Sepal.Length\", ylab = \"Sepal.Width\") and think: wow, there are axes! Amazing! It is legitimate to ask why you need to know D3 as a data scientist. Many if not most of you will not be coding in JavaScript from the ground up in your future careers. However, it’s a great way to learn how interactive graphics work under the hood, and will give you a solid foundation which you can draw on to tweak visualizations that you build with high level tools such as Plotly. 2.6 HTML tree While shapes.html appears as a single consistent document, it is actually comprised of multiple languages. HTML, CSS, and SVG are already there, and we will be adding JavaScript / D3 soon. Of note: An HTML document is composed of lines or sections set off with tags. In particular <style> ... </style>, <svg> ... </svg>, and <script> ... </script> indicate the inclusion of CSS, SVG, and JavaScript/D3 respectively. For D3 to work, you must link to a D3 library. To link to the online version, include this line: <script src=\"https://cdn.jsdelivr.net/npm/d3@7\"></script> or <script src=\"https://d3js.org/d3.v7.js\"></script>. Alternatively, you can download a copy of the library from https://d3js.org/getting-started#d3-in-vanilla-html–clicking on d3.v7.js or d3.v7.min.js will download the file. Then add the following to the <head> section of your .html file: <script src="d3.js"></script> There are two main sections. The <head> section contains the title, link to D3 library, and internal CSS. The <body> section contains HTML elements (<h1>, <p>, etc.), SVGs (between <svg>/</svg>tags) and JavaScript/D3 scripts (between <script>/<script>tags). Do not assume that if it works that it is correct; today’s browsers can be very forgiving. Comment syntax varies with language: <!-- single or multiline HTML or SVG comment --> /* single or multiline CSS comment */ // single line JavaScript comment /* JavaScript multiline comment */ 2.7 Exercise : shapes Download a copy of shapes.html by opening this page and clicking File, Save Page As… Set yourself up to work locally in a text editor help. (Developer Tools should not be open; we will not be using the Console.) Add an additional circle to the svg. Add styling to the internal style sheet to style circles. Add two additional paragraphs using the <p> tag. Add an ID attribute to one of the circles. Add a class attribute to two of the <p> tags. Use the internal style sheet to style paragraphs of the class you created in 5. Adjust additional elements as desired. Solution Contributed by Tracy Liu "],["d3console.html", "3 Modify, Add, Remove 3.1 Selections 3.2 Modify existing elements 3.3 Add elements 3.4 Remove elements 3.5 Exercise : green circles 3.6 Exercise : blue circles 3.7 Bind data… finally! 3.8 Exercise : data bind", " 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 .fancy { color: red; font-family: garamond; font-size: 30px; } <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: Select the circle with ID “henry” and make it blue. Select all circles of “apple” class make them red. Select the first circle and add an orange border (use attribute “stroke”), and stroke width (“stroke-width”) of 5. 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. Move all the circles to the right. Move them back to the left and change their color. 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. 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. "],["update-enter-and-exit.html", "4 Update, Enter, and Exit 4.1 Lecture slides 4.2 Use exit selection to remove elements 4.3 Use enter selection to add elements 4.4 Data / enter / append sequence 4.5 Exercise : horizontal bar chart 4.6 Merge selections 4.7 Exercise : merge 4.8 Groups 4.9 General Update Pattern (with merge) 4.10 General Update Pattern (with join) 4.11 Exercise: : functions 4.12 Exercise : vertical bar chart", " 4 Update, Enter, and Exit Read: IDVW2, Chapter 9, pp. 178-184; Chapter 12, pp. 231-249 4.1 Lecture slides D3 Data Bind Jump to data/enter/append Jump to general update pattern 4.2 Use exit selection to remove elements a.k.a. more DOM elements than data values We’ll start with six circles and remove some. Open six_blue_circles.html in Chrome and open the JavaScript Console. Let’s bind four data values to the six circles: d3.select("svg") .selectAll("circle") .data([123, 52, 232, 90]); Click the black triangle to view the _enter, _exit, and _groups fields. We can store the selection in a variable: const circ = d3.select("svg") .selectAll("circle") .data([123, 52, 232, 90]); Let’s look at the exit selection: circ.exit(); Try this: circ.attr("fill", "red"); What happened and why? Now try this: circ.exit().attr("fill", "purple"); What happened and why? What do you think this will do? Try it. circ.exit().transition().duration(2000).remove(); Create a new variable circ2 and compare it to circ: const circ2 = d3.selectAll("circle"); circ.data(); circ2.data(); circ.exit(); circ2.exit(); What’s going on? 4.3 Use enter selection to add elements a.k.a. more data values than DOM elements We’ll start with six_blue_circles.html in Chrome and add some circles. First, let’s bind new data to the circles: const circ = d3.select("svg") .selectAll("circle") .data([123, 52, 232, 90, 34, 12, 189, 110]); And look at the enter selection: circ.enter(); How many placeholders are in the enter selection? Let’s add circles for each of these placeholders: circ.enter() .append("circle") .attr("cx", "100") .attr("cy", (d, i) => i * 50 + 25) .attr("r", "20") .attr("fill", "blue"); Try this: circ.transition() .duration(3000) .attr("cx", "400"); What do you need to do to act on all of the circles? d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cy", (d, i) => (i * 50) + 25) .attr("cx", "200"); 4.4 Data / enter / append sequence We’ll start with nothing–not even an SVG–and add elements with the data / enter / append sequence. Work in the Console on this page (help). Open the JavaScript Console The SVG will be added here: d3.select("div#dea") .append("svg") .attr("width", "400") .attr("height", "250"); Create an array of values: const specialdata = [75, 150, 200]; Add rectangles: d3.select("svg") .selectAll("rect") .data(specialdata) .enter() .append("rect") .attr("x", d => d) .attr("y", d => d) .attr("width", "50") .attr("height", "30") .attr("fill", "pink"); 4.4.1 Labels Note that we can also label the rectangles with the data value: d3.select("svg") .selectAll("text") .data(specialdata) .enter() .append("text") .attr("x", d => d + 25) .attr("y", d => d + 25) .text(d => d) .attr("fill", "blue") .attr("text-anchor", "middle"); 4.5 Exercise : horizontal bar chart Save a copy of this exercise and open it in your text editor. Create a horizontal bar chart with bar widths equal to the data values stored in bardata. Do so by replacing all the “ADD’s” with constants or functions and then uncommenting those lines. const bardata = [300, 100, 150, 225, 75, 275]; It should look like this: Solution 4.6 Merge selections We now know how to work separately with the update, enter, and exit selections. Often in practice we wish to do the same thing or almost the same thing with the enter and update selections. That is where .merge() comes in. If we merge the update and enter selections we don’t have to repeat our code. Open six_blue_circles.html in Chrome. Run the following code in the Console: const newdata = [123, 52, 232, 90, 34, 12, 189, 110]; const svg = d3.select("svg"); const circ = svg.selectAll("circle") .data(newdata); circ.enter() // 2 placeholders .append("circle") // placeholders -> circles .attr("cx", "100") // acts on enter selection only .attr("cy", (d, i) => (i - 5) * 50) .attr("r", "20") .attr("fill", "red") .merge(circ) .transition() .duration(2000) .attr("cx", "200"); Note the pattern: Create a data array Store the svg selection Store the data bind in X X.enter() .append(some shape) *add attributes* // acts on enter selection only (no transitions!) .merge(X) *attributes, other stuff* // acts on enter and update selections Do not include transitions in a stored selection! 4.7 Exercise : merge Open this bar chart in Chrome and work in the Console. (You don’t have to download it.) All of your solutions should begin with: Creating a data array (ex. const dataset = [1, 2, 3];) Selecting the svg and storing it (const svg = d3.select(\"svg\");) Binding the data and storing it (ex. const bars = svg.selectAll(\"rect\").data(dataset));) Change the data to any six other values and update the lengths of the bars. Bind a new dataset, newbardata to the bars, update the bar lengths, and remove any extra bars. newbardata = [250, 125, 80, 100]; Bind a new dataset, reallynewbardata, to the bars, then add additional bars so each data value has a bar. Make the outline (stroke) of the new bars a different color. reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; Use .merge() to combine the update and enter selections into one selection and then transition the height of all of the bars to half their current height. Add text labels inside the bars at the right end with the length of the bar in pixels. Solution 4.8 Groups Open six_blue_circles.html in Chrome. Run this code in the Console: const specialdata = [100, 250, 300]; const bars = d3.select("svg") .selectAll("rect") .data(specialdata) .enter() .append("rect") .attr("x", d => d) .attr("y", d => d) .attr("width", "50") .attr("height", "30") .attr("fill", "red"); What’s going on? Refresh the page, and try the following instead: const svg = d3.select("svg"); const specialdata = [100, 250, 300]; const bars = d3.select("svg") .append("g") .attr("id", "rects") .selectAll("rect") .data(specialdata) .enter() .append("rect") .attr("x", d => d) .attr("y", d => d) .attr("width", "50") .attr("height", "30") .attr("fill", "red"); Compare: d3.select("svg") .select("g#rects") .selectAll("rect") .attr("fill", "purple"); and d3.select("svg") .selectAll("rect") .attr("fill", "purple"); 4.9 General Update Pattern (with merge) Open Developer Tools on this page. Create a function in the Console: function changedata(data) { d3.select("svg#gup") .selectAll("rect") .data(data) .attr("width", d => d); } Test it out: changedata([258, 373, 278, 9, 72, 96]); What happens if there are too many data values? changedata([196, 360, 283, 390, 46, 56, 152]); Let’s use the enter selection to add new bars in this case: function changedata(data) { const bars = d3.select("svg#gup") .selectAll("rect") .data(data); // bars is the update selection bars.enter() .append("rect") .attr("x", "30") // until merge, acts on .attr("y", (d, i) => i * 50) // enter selection only .attr("height", "35") .attr("fill", "lightgreen") .merge(bars) // merge in the update selection .attr("width", d => d); // acts on all bars } What happens if we have more bars than data values? changedata([325, 116, 25]); Let’s add to the function to remove the extra bars in this case: function changedata(data) { const bars = d3.select("svg#gup") .selectAll("rect") .data(data); // bars is the update selection bars.enter() .append("rect") .attr("x", "30") // until merge, acts on .attr("y", (d, i) => i * 50) // enter selection only .attr("height", "35") .attr("fill", "lightgreen") .merge(bars) // merge in the update selection .attr("width", d => d); // acts on all bars bars.exit() .remove(); } Try: changedata([271, 49, 389]); VOILA! We have created the D3 General Update Pattern! It is covered in IDVW in the “Other Kinds of Data Updates” section on pp. 178-186 in Chapter 9. (The earlier part of Chapter 9 deals with data updates in which the number of DOM elements remains the same.) Note that the General Update Pattern changed with D3 Version 4 so avoid examples from Version 3. Also available here: general_update_pattern.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <script src="https://d3js.org/d3.v7.min.js"></script> </head> <body> <script id="s1"> // Create svg and initial bars const svg = d3.select("body") .append("svg") .attr("width", "500") .attr("height", "400"); const bardata = [300, 100, 150, 225, 75, 275]; const bars = svg.selectAll("rect") .data(bardata); bars.enter().append("rect") .attr("x", "30") .attr("y", (d, i) => i*50) .attr("width", d => d) .attr("height", "35") .attr("fill", "lightgreen"); // General Update Pattern function update(data) { const bars = svg.selectAll("rect") // data join .data(data); bars.enter() .append("rect") // add new elements .attr("x", "30") .attr("y", (d, i) => i*50) .attr("width", d => d) .attr("height", "35") .attr("fill", "yellow") .merge(bars) // merge .transition() .duration(2000) .attr("width", d => d) .attr("fill", "orange"); bars.exit().remove(); // remove extra elements } </script> </body> </html> 4.10 General Update Pattern (with join) Optional This is a newer method–introduced in v5–which simplifies the general update pattern by automatically adding elements for the enter selection, removing elements for the exit selection, merging the enter and update selections and then acting on them all with .join(). The update pattern above could be replaced with the following: function changedata2(data) { const svg = d3.select("svg#gup") svg.selectAll("rect") .data(data) .join("rect") // need "rect" for appending elements .attr("x", "30") .attr("y", (d, i) => i * 50) .attr("height", "35") .attr("fill", "lightgreen") .attr("width", d => d); } Try: changedata2([271, 49, 389]); With .join() you can get more granular with enter, update, and exit selections as we did previously. changedata3() is equivalent to changedata2(): function changedata3(data) { const svg = d3.select("svg#gup") svg.selectAll("rect") .data(data) .join( enter => enter.append("rect") .attr("x", "30") .attr("y", (d, i) => i * 50) .attr("height", "35") .attr("width", d => d) .attr("fill", "lightgreen"), update => update .attr("width", d => d), exit => exit.remove() ); } Generally, however, unless you have transitions, you will not need to control the enter, exit and update selections separately. 4.11 Exercise: : functions Open general_update_pattern.html and practice running the update() function with different datasets in the Console. For example: update([100, 200, 300]); Solution 4.12 Exercise : vertical bar chart Save a copy of this exercise and open it in your text editor. Create a vertical bar chart with bar heights equal to the data values stored in bardata. Do so by replacing all the “ADD’s” with constants or functions and then uncommenting those lines. Solution "],["just-enough-js.html", "5 Just Enough JS 5.1 Arrays of arrays 5.2 Arrays of objects 5.3 .map() 5.4 D3 sorting 5.5 D3 statistics 5.6 D3 + .map()", " 5 Just Enough JS Basics: IDVW, pp. 36-52 Before JavaScript ES6, the ‘var’ keyword was used to declare a variable. Variables declared using the ‘var’ keyword are either globally or functionally scoped, they do not support block-level scope. Therefore, in JavaScript ES6, the ‘let’ keyword and ‘const’ keyword were introduced. The ‘let’ keyword deals with a block scope. It can be reassigned but cannot be redeclared. The ‘const’ keyword is also blocked scoped. It cannot be reassigned and cannot be redeclared. As a general rule, you should always declare variables with ‘const’, if you realize that the value of the variable needs to change, go back and change it to ‘let’. For more detailed information regaring ‘var’, ‘let’ and ‘const’, please see Difference between var, let, and const keyword in JavaScript objects, arrays, arrays of objects, functions (and other things) 5.1 Arrays of arrays Open the JavaScript Console // try me in the Console const array_dataset = [[100, 75, 30], [200, 125, 20]]; d3.select("svg#arrays") .selectAll("circle") .data(array_dataset) .enter() .append("circle") .attr("cx", d => d[0]) .attr("cy", d => d[1]) .attr("r", d => d[2]) .attr("fill", "red"); svg#arrays 5.2 Arrays of objects // Try me in the Console const object_dataset = [ {cx: 100, cy: 150, fill: `red`}, {cx: 200, cy: 100, fill: `blue`} ]; d3.select("svg#objects") .selectAll("circle") .data(object_dataset) .enter() .append("circle") .attr("cx", d => d.cx) .attr("cy", d => d.cy) .attr("r", "30") .attr("fill", d => d.fill); svg#objects See also: JavaScript Array of Objects Tutorial 5.3 .map() What’s the issue? In R many operations are vectorized: sqrt(3) ## R output ## [1] 1.732051 x <- c(3, 5, 7) sqrt(x) ## R output ## [1] 1.732051 2.236068 2.645751 Not so in JavaScript: Math.sqrt(3); // Try me in the Console const x = [3, 5, 7]; // Try me in the Console Math.sqrt(x); // Doesn't work... 5.3.1 Simple arrays Use .map() to operate on each array element separately. The concept is similar to lapply() or purrr::map(), but unlike in R, it’s needed for simple arrays. R x <- c(3, 5, 7) sqrt(x) ## R output ## [1] 1.732051 2.236068 2.645751 JavaScript Do something to every element of a simple array: // take the square root of each element const x = [3, 5, 7]; // try me x.map(Math.sqrt); // multiply each element by 3 [4, 10, 12].map(d => d*3); // try me // multiply each element by 3 [4, 10, 12].map(function(d) {return d*3;}); // try me // multiply each element by its index [10, 20, 30, 40].map((d, i) => d*i); // try me R: Sum two arrays # sum two arrays x <- 1:3 y <- 4:6 x + y ## R output ## [1] 5 7 9 JavaScript: Sum two arrays // sum two arrays const x = [1, 2, 3]; const y = [4, 5, 6]; x + y // try me... what went wrong? // sum two arrays const x = [1, 2, 3]; const y = [4, 5, 6]; x.map((d, i) => d + y[i]); // try me 5.3.2 Arrays of arrays Do something to the first item of every element of a nested array: [[1, 2], [3, 4]].map(d => Math.sqrt(d[0])) // try me Sum up all items in each element of the array: [[1, 2, 3], [4, 5, 6]].map(d => d[0] + d[1] + d[2]); // try me Created a nested array out of a simple array: [10, 20, 30].map(d => [d, Math.pow(d, 2)]); 5.3.3 Create arrays of objects Create an array of objects out of a simple array (note the parentheses around the object): [10, 20, 30].map(d => ({n: d, nsq: Math.pow(d, 2)})); // try me [10, 20, 30].map((d, i) => ({index: i, value: d})); // try me 5.4 D3 sorting Use d3.sort() rather than plain JavaScript options. const y = [3, 1, 5, 12, 7]; // try me d3.sort(y); 5.5 D3 statistics link to API D3 brings us back to familiar ground with functions that take an array and return a single value. Here are D3 functions with the same names and behavior as their R equivalents: R D3 min(x) d3.min(x) max(x) d3.max(x) sum(x) d3.sum(x) mean(x) d3.mean(x) median(x) d3.median(x) A few with different names: R D3 range(x) d3.extent(x) var(x) d3.variance(x) sd(x) d3.deviation(x) d3.quantile() takes a single value for p, not an array as in R. (In earlier versions of D3 it was necessary to sort the array before finding quantiles, but this is no longer the case.) R D3 quantile(x) d3.quantile(x, p) Thus for a single quantile we have: const x = [12, 34, 1, 43, 90, 72]; // try me d3.quantile(x, .25); https://github.com/d3/d3/blob/main/API.md#statistics 5.6 D3 + .map() D3 statistics functions combined with .map() can be helpful in a variety of situations. Vectorizing a parameter, for example to mimic quantile(x) in R: R x <- c(1, 12, 34, 43, 72, 90); quantile(x) ## R output ## 0% 25% 50% 75% 100% ## R output ## 1.00 17.50 38.50 64.75 90.00 JavaScript const x = [1, 12, 34, 43, 72, 90]; // try me [0, .25, .5, .75, 1].map(p => d3.quantile(x, p)); Sum up the first item of all elements in an array of arrays: R l <- list(c(100, 200, 40), c(300, 150, 20)) sum(purrr::map_dbl(l, ~.x[1])) ## R output ## [1] 400 JavaScript const dataset = [[100, 200, 40], [300, 150, 20]]; // try me d3.sum(dataset.map(d => d[0])); Sum up all items in each array to create a simple array: R l <- list(c(100, 200, 40), c(300, 150, 20)) purrr::map_dbl(l, ~sum(.x)) ## R output ## [1] 340 470 JavaScript const dataset = [[100, 200, 40], [300, 150, 20]]; // try me dataset.map(d => d3.sum(d)); "],["scales-and-axes.html", "6 Scales and Axes 6.1 Scales 6.2 Margins 6.3 Axes 6.4 Bar chart with categorical labels", " 6 Scales and Axes 6.1 Scales 6.1.1 Lecture slides Scales 6.1.2 Practice See: IDVW2, Chapter 7: Scales Practice creating an ordinal scale in the Console: Open the JavaScript Console const ordscale = d3.scaleBand() .domain([0, 1, 2, 3, 4]) .range([0, 100]); ordscale(1); Try other numbers: ordscale(3);, ordscale(2.5);, ordscale(7);, etc. Add inner padding and try again. See diagram here: https://github.com/d3/d3-scale#band-scales *Be sure to use d3.scaleBand(), not d3.scaleOrdinal() for this use case. 6.1.3 Bar chart d3.scaleBand() IDVW2 Chapter 9, pp. 150-153 Here d3.scaleBand() is used to create an xScale function to convert bar numbers to pixels. Change the w parameter and observe how the bars are resized to fit on the SVG. Code for download d3.scaleLinear() In the next graph, d3.scaleLinear() is added to create a yScale function to convert bar heights to pixels. Change the data and observe how the bars are resized to fit on the SVG. Code for download 6.2 Margins 6.2.1 Lecture slides Margins “Margin convention” const w = 500; const h = 400; const margin = {top: 25, right: 0, bottom: 25, left: 25}; const innerWidth = w - margin.left - margin.right; const innerHeight = h - margin.top - margin.bottom; 6.2.2 Bar chart with margins Code for download 6.3 Axes See: IDVW2, Chapter 8: Axes 6.3.1 Lecture slides Axes 6.3.2 Bar chart with axes Code for download Practice changing the data and seeing what happens. 6.4 Bar chart with categorical labels Code for download "],["interactivity.html", "7 Interactivity 7.1 Bar chart example 7.2 Binding event listeners 7.3 Separating the function and event listener 7.4 Buttons 7.5 Dependent event listeners", " 7 Interactivity Read: IDVW2, Chapter 10 Interactivity 7.1 Bar chart example For this demo we will use this file. 7.2 Binding event listeners (100, 150) It’s helpful to think carefully about what you want to happen when an event listener is triggered and what information you need. Open Developer Tools and try these in the Console. Note that event management changed in v6 so code written for earlier versions of D3 will not work. 7.2.1 Do something unrelated to the element that received the event d3.select("svg") .on("click", function () { d3.select("svg") .append("text") .attr("x", "100") .attr("y", "40") .text("Hello World"); }); 7.2.2 Change an attribute of the element that received the event d3.select("line") .on("click", function() { d3.select(this) .attr("stroke-width", "10"); }); In the context of event handlers, “this” is the element that received the event, a.k.a. what you clicked on if it’s a click event. An alternative (\\(\\geq\\) v6, see link above) is to pass the event and access the element with event.currentTarget: or d3.select("line") .on("click", function(event) { d3.select(event.currentTarget) .attr("stroke", "yellow"); }); 7.2.3 Get the value of an attribute of the element that received the event d3.select("circle") .on("click", function(event) { const rad = d3.select(event.currentTarget).attr("r"); d3.select("text") .text(`The radius is ${rad} pixels.`); }); 7.2.4 Do something with the data bound to the element that received the event d3.select("circle") .data([{s: "red", sw: "15"}]) .on("click", function(event, d) { d3.select(event.currentTarget) .attr("stroke", d.s) .attr("stroke-width", d.sw); }); Note that starting with v6, the data is the 2nd parameter to be passed: function(event, d). In addition, note that you do not need to pass d again when accessing the data: for example we use d.s not d => d.s. As in the previous example, d3.select(this) can be used instead of d3.select(event.currentTarget). Try changing the data value bound to the circle with d3.select(\"circle\").datum(\"10\") and clicking again. 7.2.5 Get the svg location of the event d3.select("svg") .on("click", function(event) { d3.select("text") .text(`(${d3.pointer(event).map(Math.round)})`) }); (Up to v5, d3.mouse(this) was used instead of d3.pointer(event).) 7.3 Separating the function and event listener Examples function goyellow() { d3.select(this) .attr("fill", "yellow") }; d3.select("circle") .on("mouseover", goyellow); 7.4 Buttons 7.4.1 HTML text as buttons HTML: <p id="add">Add an element</p> <p id="remove_left">Remove bar (left)</p> <p id="remove_right">Remove bar (right)</p> JavaScript: d3.selectAll("p") .on("click", function () { const paraID = d3.select(this).attr("id"); if (paraID == "add") { const newvalue = Math.floor(Math.random()*400); bardata.push(newvalue); } else if (paraID == "remove_left") { bardata.shift(); } else { bardata.pop(); }; update(bardata); }); Putting it all together: Vertical bar chart with add / remove buttons and general update pattern vertical_bar.html 7.4.2 Radio buttons HTML: <p id="color" style="background-color: silver; color: white;"> Please select your favorite primary color:</p> <input type="radio" name="fav_color" value="red">red</input> <input type="radio" name="fav_color" value="blue">blue</input> <input type="radio" name="fav_color" value="yellow">yellow</input> Note: type is always radio for radio buttons name is shared for a group of radio buttons value is unique JavaScript: d3.selectAll('input[name="fav_color"]') .on("click", function(event) { var favcolor = event.currentTarget.value; d3.select("p#color").style("color", favcolor); }); Please select your favorite primary color: red blue yellow 7.5 Dependent event listeners In these examples, the behavior or existence of one event listener depends on another. 7.5.1 Global variable example Here the circle click behavior depends on the value of the radio button: if the “Move left” radio button is checked, the circle will move left when clicked. If the “Move right” radio button is checked, the circle will move right when clicked. A global variable is used to keep track of the radio button value. The event listener on the circle conditions the behavior on the value of this global variable. Click the circle.  Move left  Move right svg#radio // global variable keeps track of which radio button is clicked let action = "left"; d3.select("div#rad") .selectAll("input") .on("click", function() { action = d3.select(this).node().value; }); // circle click behavior depends on value of "action" d3.select("svg#radio").select("circle") .on("click", function () { let cx_new; if (action == "left") { cx_new = +d3.select(this).attr("cx") - 50; if (cx_new < 20) cx_new = 20; } else { cx_new = +d3.select(this).attr("cx") + 50; if (cx_new > 280) cx_new = 280; } d3.select(this) .transition() .duration(500) .attr("cx", cx_new); }); 7.5.2 Turn off event listener In this example, the event listeners on the squares are turned on or off depending on the value of the radio button. Event listeners can be removed by setting the behavior to null. Click a square.  Red active  Blue active svg#radio2 // movement function const jump = function () { d3.select(this).transition().duration(500) .attr('y', '0') .transition().duration(500).ease(d3.easeBounce) .attr('y', '75'); }; // initial setup: add event listener to red square d3.select("svg#radio2") .select("rect#red") .on("click", jump); // switch event listeners if radio button is clicked d3.select("div#rad2").selectAll("input") .on("click", function () { if (d3.select(this).node().value == "blue") { d3.select("svg#radio2").select("rect#blue").on("click", jump); d3.select("svg#radio2").select("rect#red").on("click", null); } else { d3.select("svg#radio2").select("rect#red").on("click", jump); d3.select("svg#radio2").select("rect#blue").on("click", null); } }); "],["transitions.html", "8 Transitions 8.1 Examples 8.2 Do this 8.3 Not this 8.4 Strategy 8.5 Exercise : Bar chart with transitions", " 8 Transitions Read IDVW2, Chapter 9: transitions section (pp. 158-178) 8.1 Examples Open Developer Tools and try in the Console: d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cx", "275"); d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cx", "25") .attr("fill", "green"); 8.2 Do this Run simultaneous transitions on different selections: d3.select("svg").selectAll("circle#henry").transition() .duration(2000).attr("cx", "275"); d3.select("svg").selectAll("circle.apple").transition() .duration(2000).attr("cx", "25"); Run sequential transitions on the same selection in one chain: d3.select("svg").selectAll("circle") .transition().duration(2000).attr("cx", "275") .transition().duration(2000).attr("cx", "25"); Transition from something to something: d3.select("svg").append("circle") .attr("cx", "200") .attr("cy", "100") .attr("r", "5") .attr("fill", "lightblue") .transition() .duration(4000) .attr("r", "25") .attr("fill", "blue"); 8.3 Not this DO NOT run two transitions on the same selection at the same time (see p. 172). (What works in the Console will not work in a script.) d3.select("svg").selectAll("circle").transition() .duration(2000).attr("cx", "250"); d3.select("svg").selectAll("circle").transition() .duration(2000).attr("cx", "75"); DO NOT transition from nothing to something: d3.select("svg").append("circle") .transition() .duration(2000) .attr("cx", "200") .attr("cy", "100") .attr("r", "25") .attr("fill", "red"); DO NOT store a selection with a transition (it’s no longer a selection with the transition): Try this: const circ = d3.select("svg") .selectAll("circle") .data([50, 95, 100, 200, 50, 150, 250]) .enter() .append("circle") .attr("cx", d => d) .attr("cy", "100") .attr("fill", "blue") .attr("r", "0") .transition() .duration(2000) .attr("r", "25"); And then this: circ.attr("fill", "green"); DO NOT put a transition before a merge: d3.select("svg") .selectAll("circle") .transition() .duration(2000) .attr("cx", "300") .merge("oldcirc") .attr("fill", "green"); BE AWARE that not everything transitions (for example, text doesn’t.) 8.4 Strategy Example 1 Think carefully about what you want to happen, and then decide what goes before and after the transition. Add bar Remove bar Plan what you want to happen: new bars appear on the right side with orange fill new bars slide into place from the right as old bars are repositioned new bars transition to blue removed bars transition to right before disappearing 8.5 Exercise : Bar chart with transitions Download and make changes to bar_transition.html in a text editor so the transitions work as shown below. Solution code for download rendered version Further reading: Working with Transitions. "],["object.html", "9 Object Constancy 9.1 Lecture slides 9.2 No object constancy 9.3 Object constancy", " 9 Object Constancy 9.1 Lecture slides object_constancy.pdf 9.2 No object constancy Transitions  Off  On Add bar Remove bar (right) Remove bar (left) Of note: Rather than smoothly transitioning off to the left, all bars are resized when “Remove bar (left)” is clicked When \"Remove bar (right) is clicked, the bar on the right immediately disappears, and then the remaining bars transition to their new places to the right. Standalone version: no_object_constancy.html 9.3 Object constancy Transitions  Off  On Add bar Remove bar (right) Remove bar (left) Of note: Bars now smoothly transition off to the left and right Standalone version: object_constancy.html 9.3.1 Practice joining data by key Open the JavaScript Console Try the following: Create an svg with four text elements: const dataset = [{key: 12, x: 163, y: 200}, {key: 14, x: 206, y: 304}, {key: 16, x: 452, y: 152}, {key: 18, x: 321, y: 254}]; const svg = d3.select("#key").append("svg") .attr("width", "600").attr("height", "400"); svg.append("rect").attr("width", "600") .attr("height", "400").attr("fill", "aliceblue"); svg.selectAll("text") .data(dataset, d => d.key) .enter() .append("text") .attr("x", d => d.x) .attr("y", d => d.y) .text(d => `key: ${d.key}`); Bind a new dataset by key: const svg = d3.select("#key").select("svg"); const dataset = [{key: 12, x: 100, y: 200}, {key: 16, x: 250, y: 300}]; svg.selectAll("text") .data(dataset, d => d.key) .exit() .remove(); Then: svg.selectAll("text") .attr("x", d => d.x) .attr("y", d => d.y); And another one: const svg = d3.select("#key").select("svg"); const dataset = [{key: 23, x: 300, y: 150}, {key: 5, x: 450, y: 270}, {key: 16, x: 200, y: 250}]; const databind = svg.selectAll("text") .data(dataset, d => d.key); databind .enter() .append("text") .merge(databind) .attr("x", d => d.x) .attr("y", d => d.y) .text(d => `key: ${d.key}`); databind.exit().remove(); Experiment with other data binds. "],["reading-files.html", "10 Reading files 10.1 Promises 10.2 Local server 10.3 Other local options 10.4 Hosting online", " 10 Reading files As you’ve surely noticed by this point, many things in JavaScript operate on an asynchronous basis. Code is not executed linearly from beginning to end but rather in response to various triggers. For example, event listeners behave asynchronously: code will execute only if a mouse click event occurs. The benefit to reading files asynchronously is that we don’t have to wait to while a file loads for other things to happen. It would be very frustrating to navigate to a new web page and have to wait for all the scripts to finish before we could do anything on the page. 10.1 Promises Loading data is one area where D3 v5 introduces major changes from D3 v4. While v4 uses callbacks, v5 switches to promises, as promises facilitate cleaner and more flexile code than callbacks. The concept is simple. We want to control what code needs to wait until data loaded to be executed and what doesn’t. We can do that with the following structure: const rowConverter = function (d) { return { disp: +d.disp, mpg: +d.mpg, carname: d.carname, cylcolor: d.cylcolor } }; d3.csv("https://raw.githubusercontent.com/jtr13/d3book/main/data/mtcars.csv", rowConverter) .then(function(data) { // stuff that requires the loaded data }) .catch(function(error) { // error handling }); The row converter function is used to select variables and change data types (“+” converts to floating point). d3.csv() returns a promise. If the promise is resolved, the .then() function will execute; if the promise is rejected, the .catch() function will execute. Forget the mindset that you read files and store them in variables for later use. It doesn’t work that way here. The data is read in and acted on immediately. If most of the code requires loaded data, then most of the code will appear in the .then() method. A simple example of loading data in v5 can be found in this block. In contrast to the example above, an anonymous row converter function (with arrow functions) is used instead of calling a separate row converter function. Note as well that it’s not necessary to include all variables in the row converter as this author has done. For example, you could delete all the variables that aren’t used, so that the row converter in the d3.csv line becomes: d => ({ HighwayMpg: parseInt(d.HighwayMpg), Horsepower: parseInt(d.Horsepower), }) You will see that the code still works. For more about d3.csv(), see the d3.fetch API. 10.2 Local server Note: As long as you read from online sources as in the example above, you do not need to set up a local server. For security reasons, Chrome does not let you read local files. To be able to do so, you can run a local server. One option is http-server. Follow the instructions to install http-server, navigate in a terminal to the directory with your html file, and then enter http-server: joycerobbins@MacBook-Pro d3-book-murray % http-server You should get a message that ends with something like this: Starting up http-server, serving ./ ... Available on: http://127.0.0.1:8080 http://192.168.1.54:8080 Hit CTRL-C to stop the server Copy and paste one of the URLs in the browser and you should see an index of subfolders and/or files available in the folder in which you launched the local server: From here you can navigate to the desired file. Take note that you cannot move up in the file structure so be sure to start the server in the highest level directory that you plan to access, or that the files you open need to access. In this particular case I am opening the code files for Scott Murray’s Interactive Data Visualization for the Web, 2nd ed. (available here). Rather than link to the online version of D3 as we’ve been doing, each file links to a downloaded version of D3 located in the top level directory with: <script type=\"text/javascript\" src=\"../d3.js\"></script> Therefore we must launch the local server from one level higher than the folder in which the file we wish to open resides. As indicated, Control-c in the command line will stop the server. 10.3 Other local options A simple way to avoid this issue is to upload data files to GitHub and read them from there. There are other workarounds, including opening Chrome from the command line with the --allow-file-access-from-files flag. 10.4 Hosting online An alternative to the options above are to avoid the issue by hosting your code online. Options for doing so are covering in the chapter on sharing D3 online. "],["share-d3-online.html", "11 Share D3 online 11.1 Quarto 11.2 Observable", " 11 Share D3 online There are a number of ways you can share your D3 code online. Even if you’re not sharing, there are advantages to an online setup, for example, not having to set up a local server as described in the chapter on reading files. 11.1 Quarto Separate .js file Video tutorial To include D3 in a Quarto document, I recommend putting the script in a separate .js file that is linked to both by a .qmd file in your project as well as a temporary .html file that you’ll use while creating the visualization. The rationale is that you won’t have to Quarto render your book / web site each time you update the D3 script. Create a .qmd file for the visualization Create a .qmd file with a link to the D3 library, a link to your D3 script, a <div> for adding an svg in the right place and any additional HTML needed for your visualization. Use can use this file as a template. Note that you do not need <head>, <title>, or <body> tags as these are added in the process of converting the .qmd files to HTML. Example: d3graph.qmd Create a .js file for your D3 script This file should contain your script. To ensure that the svg will appear in the book (and not below it) append the svg to the <div> in your .qmd file. Example: myscript.js Create a separate .html for development purposes Create an .html file with the same contents as your .qmd file. With this setup you can test your code without rendering the book but simply opening the .html file. Example: practice.html iframe Create your entire visualization in a separate .html file and then include in your .qmd file with <iframe>: <iframe src="mybarchart.html" width="400" height="300"></iframe> You can work on the .html file separately and view updates without rendering the book. 11.2 Observable Observable, created by D3 author Mike Bostock, is the official D3 web tool for creating and sharing D3 code. It is a powerful, popular tool–all new D3 code examples are now presented in Observable–but program flow is different than it is for stand-alone JavaScript. If you’re interested in learning more, see Why Observable. "],["line-charts.html", "12 Line charts 12.1 Lecture slides 12.2 SVG <line> element 12.3 SVG <path> element 12.4 Data for line chart 12.5 Create a line generator 12.6 Put the line generator to work 12.7 Additional Resources", " 12 Line charts Read: IDVW2, Chapter 11 Using Paths 12.1 Lecture slides line_charts.pdf 12.2 SVG <line> element (Use for two points only.) <line x1="0" y1="80" x2="100" y2="20" stroke="black" /> const x1 = 0; const y1 = 80; const x2 = 100; const y2 = 20; d3.select("svg") .append("line") .attr("x1", x1) .attr("x2", x2) .attr("y1", y1) .attr("y2", y2); 12.3 SVG <path> element (Use if you have more than two points.) <svg width = "500" height = "400"> <path d="M 50 400 L 100 300 L 150 300 L 200 33 L 250 175 L 300 275 L 350 250 L 400 125" fill="none" stroke="red" stroke-width="5"> </path> </svg> d attribute: M = move to L = line to More options: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d 12.4 Data for line chart Format that we have: Day High Temp April 1 60 April 2 43 April 3 43 April 4 56 April 5 45 April 6 62 April 7 49 Format that we need looks something like this: <path class="line" fill="none" d="M0,149.15254237288136L71.42857142857143,264.40677966101697L142.85714285714286,264.40677966101697L214.28571428571428,176.27118644067798L285.7142857142857,250.84745762711864L357.14285714285717,135.59322033898303L428.57142857142856,223.72881355932205"></path> 12.5 Create a line generator Expects data in an array of 2-dimensional arrays, that is, an array of (x,y) pairs: const dataset = [ [0, 60], [1, 43], [2, 43], [3, 56], [4, 45], [5, 62], [6, 49] ]; const mylinegen = d3.line() Test it in the Console: mylinegen(dataset); Add an ordinal scale for x: const xScale = d3.scaleBand() .domain(d3.range(dataset.length)) .range([0, 500]) … and a linear scale for y: const yScale = d3.scaleLinear() .domain([d3.min(dataset, d => d[1]) - 20, d3.max(dataset, d => d[1]) + 20]) .range([400, 0]); *Why d[1] instead of d? (See p. 122) Add accessor functions .x() and .y(): mylinegen .x(d => xScale(d[0])) .y(d => yScale(d[1])); Test again: mylinegen(dataset); Now let’s add a <path> element with that d attribute: (this step is just for learning purposes…) const mypath = mylinegen(dataset); d3.select("svg").append("path").attr("d", mypath) .attr("fill", "none").attr("stroke", "red") .attr("stroke-width", "5"); 12.6 Put the line generator to work Now let’s do it the direct way: bind the datum and calculate the path in one step: d3.select("svg").append("path") .datum(dataset) .attr("d", mylinegen) .attr("fill", "none") .attr("stroke", "teal") .attr("stroke-width", "5"); Finally, we’ll add a class and style definitions: <style> .linestyle { fill: none; stroke: teal; stroke-width: 5px; } </style> The append(\"path\") line becomes: svg.append("path") .datum(dataset) .attr("d", mylinegen) .attr("class", "linestyle"); .linestyle { fill: none; stroke: teal; stroke-width: 5px; } Putting it all together, we have: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Line generator</title> <script src="https://d3js.org/d3.v7.js"></script> <style type ="text/css"> .linestyle { fill: none; stroke: teal; stroke-width: 5px; } </style> </head> <body> <script> const w = 500; const h = 400; const svg = d3.select("svg#noaxes"); const dataset = [ [0, 60], [1, 43], [2, 43], [3, 56], [4, 45], [5, 62], [6, 49] ]; let xScale = d3.scaleBand() .domain(d3.range(dataset.length)) .range([0, w]); let yScale = d3.scaleLinear() .domain([d3.min(dataset, d => d[1]) - 20, d3.max(dataset, d => d[1]) + 20]) .range([h, 0]); const mylinegen = d3.line() .x(d => xScale(d[0])) .y(d => yScale(d[1])); svg.append("path") .datum(dataset) .attr("d", mylinegen) .attr("class", "linestyle"); </script> </body> </html> And another example with axes: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title></title> <script src="https://d3js.org/d3.v7.js"></script> </head> <body> <svg id="withaxes" width="600" height="400"></svg> <script> const svg2 = d3.select("svg#withaxes") const margin = {top: 20, right: 50, bottom: 30, left: 50} const width = +svg2.attr("width") - margin.left - margin.right const height = +svg2.attr("height") - margin.top - margin.bottom const g = svg2.append("g").attr("transform", `translate(${margin.left}, ${margin.top})`); const parseTime = d3.timeParse("%d-%b-%y"); xScale = d3.scaleTime().range([0, width]); yScale = d3.scaleLinear() .domain([20, 80]) .range([height, 0]); const xAxis = d3.axisBottom() .scale(xScale) .tickFormat(d3.timeFormat("%Y-%m-%d")); const line = d3.line() .x(d => xScale(d.date)) .y(d => yScale(d.high)); const data = [{"date":"1-Apr-18","high":60}, {"date":"2-Apr-18","high":43}, {"date":"3-Apr-18","high":43}, {"date":"4-Apr-18","high":56}, {"date":"5-Apr-18","high":45}, {"date":"6-Apr-18","high":62}, {"date":"7-Apr-18","high":49}]; data.forEach(function(d) { d.date = parseTime(d.date); }); xScale .domain(d3.extent(data, d => d.date)); g.append("g") .attr("transform", `translate(0, ${height})`) .call(xAxis); g.append("g") .call(d3.axisLeft(yScale)) g.append("path") .datum(data) .attr("class", "line") .attr("fill", "none") .attr("stroke", "red") .attr("stroke-width", 1.5) .attr("d", line); </script> </body> </html> (Also uses: d3.timeParse() and JavaScript Array.foreach() ) 12.7 Additional Resources Multiple Time Series in D3 by Eric Boxer (EDAV 2018) "],["layouts.html", "13 Layouts 13.1 Lecture slides 13.2 Set up data and stack method 13.3 Set up scales 13.4 Add groups 13.5 Add rectangles 13.6 Get the code", " 13 Layouts IDVW2, Chapter 13 Layouts (d3.stack() only, pp. 264-270) 13.1 Lecture slides layouts.pdf 13.2 Set up data and stack method Open the JavaScript Console // try me in the Console const w = 500; const h = 300; // original data const dataset = [ { apples: 5, oranges: 10, grapes: 22 }, { apples: 4, oranges: 12, grapes: 28 }, { apples: 2, oranges: 19, grapes: 32 }, { apples: 7, oranges: 23, grapes: 35 }, { apples: 23, oranges: 17, grapes: 43 } ]; // set up stack method const stack = d3.stack() .keys([ "apples", "oranges", "grapes" ]) .order(d3.stackOrderDescending); // data, stacked const series = stack(dataset); Try stack(dataset) in the Console. 13.3 Set up scales const xScale = d3.scaleBand() .domain(d3.range(dataset.length)) .range([0, w]) .paddingInner(0.05); const yScale = d3.scaleLinear() .domain([0, d3.max(dataset, d => d.apples + d.oranges + d.grapes)]) .range([h, 0]); const colors = d3.scaleOrdinal(d3.schemeCategory10); Enter d3.schemeCategory10 in the Console. Now try out the colors function. What is the domain? See built-in color options here. Of course you are not limited to these and can use any appropriate color scheme. 13.4 Add groups //Create SVG element const svg = d3.select("#stacked") .append("svg") .attr("width", w) .attr("height", h); // Add a group for each row of data const groups = svg.selectAll("g") .data(series) .enter() .append("g") .style("fill", (d, i) => colors(i)); [There’s a div with id “stacked” here.] Why doesn’t anything show up yet? Right-click and Inspect to find out. 13.5 Add rectangles // Add a rect for each data value const rects = groups.selectAll("rect") .data(d => d) .enter() .append("rect") .attr("x", (d, i) => xScale(i)) .attr("y", d => yScale(d[1])) .attr("height", d => yScale(d[0]) - yScale(d[1])) .attr("width", xScale.bandwidth()); 13.6 Get the code Code for download "],["general-advice.html", "14 General Advice 14.1 Debugging Tips 14.2 Other", " 14 General Advice 14.1 Debugging Tips Make extensive use of Elements to see what’s being added to the DOM. Make extensive use of Console to check the values of variables and/or test code. Pay attention to errors in the Console. Use console.log() esp. in functions Post Minimal Working Examples on Ed Discussion in Canvas. See: “How to create a Minimal, Complete, and Verifiable example” (But don’t worry if it’s not perfect, we’re not going to judge.) Use a text editor that helps you identify unmatched () {} []. Have a tip? Click the button, add the tip, and create a pull request. 14.2 Other Save working versions! Pay attention to the order "],["solutions.html", "15 Solutions ", " 15 Solutions Web tech: shapes Solution Contributed by Tracy Liu D3 in the Console: green circles Select the circle with ID “henry” and make it blue. d3.select("svg") .select("circle#henry") .transition() // optional .duration(1000) // optional .attr("fill","blue"); Select all circles of “apple” class make them red. d3.select("svg") .selectAll("circle.apple") .attr("fill", "red"); Select the first circle and add an orange border (“stroke”), and stroke width (“stroke-width”) of 5. d3.select("svg") .select("circle") .attr("stroke", "orange") .attr("stroke-width", "5"); Select all circles of “apple” class and move them to the middle of the svg. d3.select("svg") .selectAll("circle.apple") .transition() .duration(2000) .attr("cx", d3.select("svg").attr("width") / 2) .attr("cy", d3.select("svg").attr("height") / 2); Contributed by Tracy Liu D3 in the Console: blue circles Move all the circles to the right. const svg = d3.select("svg") svg.selectAll("circle") .transition() .duration(2000) .attr("cx", 450); Move them back to the left and change their color. svg.selectAll("circle") .transition() .duration(2000) .attr("cx", 50) .attr("fill", "red"); 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. svg.select("circle#third") .transition() .duration(2000) .attr("cx", "450"); Move all the circles to the middle of the screen, then move them all to the same location. svg.selectAll("circle") .transition() .duration(2000) .attr("cx", 250) .transition() .duration(2000) .attr("cy", 200); (Extra: return the circles to their starting positions.) const circ = svg.selectAll("circle") .data([100, 150, 200, 250, 300, 350]); circ.transition() .duration(2000) .attr("cx", 50) .attr("cy", d => d) .attr("fill", "blue"); Contributed by Tracy Liu D3 in the Console: data bind const bluedata = [30, 20, 40, 70, 120, 70]; const circ = d3.select("svg") .selectAll("circle") .data(bluedata); circ.transition() .duration(2000) .attr("fill", "pink") .attr("r", d => d / 2) .attr("cx", d => 3 * d); Contributed by Tracy Liu Update, Enter, and Exit: horizontal bar chart For this exercise open up your text editor. We will be operating inside the <script> tags. Our goal is to create a horizontal bar chart. The first step is to create the svg element where our graph will live in. We want to make sure to make it tall and wide enough to hold the data of the given array. We also store our data in an array. const bardata = [300, 100, 150, 225, 75, 275]; const svg = d3.select("body") .append("svg") .attr("width", "700") .attr("height", "400"); Next we will bind the data to the DOM rectangles and store the selection in a variable called bars. Since there are no DOM rectangles (yet), the update and exit selections will be empty. Note that nothing will appear on the screen yet since the enter elements are not combined with DOM elements. const bars = svg.selectAll("rect") .data(bardata); Next step is to create DOM elements from the enter selection and give them the correct attributes to form a horizonal barchart. First we will set x to zero and then in order to set the y distances between the bars we will use each datapoint’s index. The mapping function for the y coordinate is (d, i) => i * 25 + 10 works as follows: When i = 0, y = 10, when i = 1, y = 35, when i = 2, y = 60, etc. Lastly, we want each rectangle’s width to be equal to its data value. bars.enter() .append("rect") .attr("x", 0) .attr("y", (d, i) => i * 25 + 10) .attr("width", d => d) .attr("height", "20") .attr("fill", "pink"); The complete solution is here Contributed by Kassie Papasotiriou Update, Enter, and Exit: merge Change the data to any six other values and update the lengths of the bars. console: const bardata = [130, 210, 90, 300, 200, 50]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(bardata); bars.transition() .duration(2000) .attr("width", d => d); Bind a new dataset, newbardata to the bars, update the bar lengths, and remove any extra bars. const newbardata = [250, 125, 80, 100]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(newbardata); bars.transition() .duration(2000) .attr("width", d => d) bars.exit() .transition() .duration(2000) .attr("width", 0) .remove(); Bind a new dataset, reallynewbardata, to the bars, then add additional bars so each data value has a bar. Make the outline (stroke) of the new bars a different color. const reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(reallynewbardata); bars.transition() .duration(2000) .attr("width", d => d) bars.enter() .append("rect") .attr("width", 0) .attr("x", 30) .attr("y", (d, i) => 50 * i) .attr("height", 35) .attr("width", d => d) .attr("fill", "lightgreen") .attr("stroke", "pink") .attr("stroke-width", 5); Use .merge() to combine the update and enter selections into one selection and then transition the length of all of the bars to half their current length. const reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; const svg = d3.select("svg"); const bars = svg.selectAll("rect") .data(reallynewbardata); bars.enter() .append("rect") .attr("x", 30) .attr("y", (d, i) => 50 * i) .attr("height", 35) .attr("width", d => d) .attr("fill", "lightgreen") .attr("stroke", "pink") .attr("stroke-width", 5) .merge(bars) .transition() .duration(2000) .attr("height", "35") .attr("width", d => d); Add text labels inside the bars at the right end with the length of the bar in pixels. const reallynewbardata = [300, 100, 250, 50, 200, 150, 325, 275]; const svg = d3.select("svg"); const bartext = svg.selectAll("text") .data(reallynewbardata); bartext.enter() .append("text") .attr("x", d => d - 5) .attr("y", (d, i) => 50 * i + 12) .text(d => d); Contributed by Tracy Liu Update, Enter, and Exit: functions Question Answers Since there are no questions we will try out a few things and explain why the bars are behaving that way. Open up the file on Chrome and type the following in you Console: By running the code below we are rebinding the new data to the DOM elements. In this case, the inital data and the new data have the same length so no new elements will be added and none will be removed. We update the data, change the fill and then change the width. update([100, 200, 300, 400, 200, 300]); By running the code below, the two data values are bound to the first two DOM elements. The color and width are changed the extra DOM elements that did not receive data values are removed. update([200, 400]); By running the code below (without refreshing the page from 2.), we bind the data, and two of the values will be in the enter selection. For those two values we create two more DOM elements, fill them with yellow and adjust their width. Next we merge all elements together and to the merge selection we readjust the widths to make sure that all elements have the correct length and we turn them to orange. Note that since in this scenario we had enter elements there will be nothing in the exit selection. update([200, 300, 200, 125]); Update, Enter, and Exit: vertical bar chart Question Answers In order to change a horizontal bar chart to a vertical barchart we need to first flip the x and y coordinates. If we only make this change we will notice that our barchart is not vertical but it is flipped (meaning that all bars seem to be upside-down). This is because the coordinate system in the svg starts from the top left and not the bottom left as we are used to. Therefore in order to bring everything “down”, we need to tweak the y coordinate. In order to do that we can subtract from the height of the svg the data value of each bar. In this way all bars will end at 400 and start d pixels before the end. // Create svg with same data const svg = d3.select("body") .append("svg") .attr("width", "500") .attr("height", "400"); const bardata = [300, 100, 150, 225, 75, 275]; const bars = svg.selectAll("rect") .data(bardata); bars.enter() .append("rect") .attr("x", (d, i) => i * 50 + 50) .attr("y", d => 400 - d) .attr("width", "35") .attr("height", d => d) .attr("fill", "lightgreen"); In addition to that, we need to reflect those changes in the update function and in addition to updating all bar’s heights we need to also update all barsnewy` coordinates. bars.enter() .append("rect") // add new elements .attr("x", (d, i) => i * 50 + 50) .attr("y", d => 400 - d) .attr("width", "35") .attr("height", d => d) .attr("fill", "yellow") .merge(bars) // merge .transition() .duration(2000) .attr("y", d => 400 - d) .attr("height", d => d) .attr("fill", "orange"); The complete solution is here Contributed by Kassie Papasotiriou Transitions: bar chart with transitions code for download rendered version "],["correlation-coefficient.html", "16 Correlation Coefficient", " 16 Correlation Coefficient h2, h3, p, text { font-family: sans-serif; text-anchor: middle; } Correlation Coefficient The correlation coefficient (r) is a measure of the linear relationship between two variables x and y. To get a sense of the connection between the appearance of points – (x,y) pairs – in a scatterplot and the value of r, click anywhere on the graph to add points. To remove points, click the Remove Points button and then mouseover points. The correlation coefficient is shown below. Add points Remove points Click on the chart below to add points. "],["spearman-rank-correlation-coefficient.html", "17 Spearman Rank Correlation Coefficient", " 17 Spearman Rank Correlation Coefficient h2, h3, p, text { font-family: sans-serif; text-anchor: middle; } .rank { fill: white; font-weight: bold; dominant-baseline: central; /* https://stackoverflow.com/questions/12250403/vertical-alignment-of-text-element-in-svg */ } #rho { font-family: serif; font-size: 18pt; } Drag the circles to reorder the rankings in Groups A & B. The correlation coefficient, \\(\\rho\\), is displayed below. \\(\\LARGE \\rho = 1 - \\frac{6 \\Sigma{d_i^2}}{n(n^2 -1)} =\\) \\(d_{i}\\) = difference between the two ranks of each observation \\(n\\) = number of observations "],["population-density.html", "18 Population Density", " 18 Population Density * { font-family: sans-serif; } .source { font-size: 9pt; } New York City Each dot represents a person. Data source: http://www.demographia.com/dm-nyc.htm Standalone version: density.html "],["weather-forecast.html", "19 Weather forecast", " 19 Weather forecast * { font-family: sans-serif; } text { font-size: 12px; } Weather forecast for Columbia University Lat, Lon: 40.807793, -73.962144 (Hover over points for more details.) Fahrenheit Celsius "],["appendix-advanced-css.html", "20 Appendix: advanced CSS 20.1 Buttons", " 20 Appendix: advanced CSS Work-in-progress 20.1 Buttons 20.1.1 .active This is a class not a pseudoclass, indicates which button(s) are pressed. 20.1.2 :active A pseudoclass. Clicking is the main way to trigger an :active state. 20.1.3 :focus A pseudoclass. Mainly used for tabbing. Clicking may focus a button but not always. A box shadow such as box-shadow: 0 0 0 3px lightskyblue; is a good choice 20.1.4 :hover As expected, can have separate hover behavior for .active buttons. 20.1.5 Example See the Pen Button .active vs :active by Joyce Robbins (https://codepen.io/jtr13) on CodePen. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] diff --git a/share-d3-online.html b/share-d3-online.html index cb931f7..1bf6707 100644 --- a/share-d3-online.html +++ b/share-d3-online.html @@ -23,7 +23,7 @@ - + diff --git a/solutions.html b/solutions.html index 0bc74c8..1c2f010 100644 --- a/solutions.html +++ b/solutions.html @@ -23,7 +23,7 @@ - + diff --git a/spearman-rank-correlation-coefficient.html b/spearman-rank-correlation-coefficient.html index ce64689..e9d49b7 100644 --- a/spearman-rank-correlation-coefficient.html +++ b/spearman-rank-correlation-coefficient.html @@ -23,7 +23,7 @@ - + diff --git a/transitions.html b/transitions.html index a020c3c..9182ec8 100644 --- a/transitions.html +++ b/transitions.html @@ -23,7 +23,7 @@ - + diff --git a/update-enter-and-exit.html b/update-enter-and-exit.html index 329d719..61dddd4 100644 --- a/update-enter-and-exit.html +++ b/update-enter-and-exit.html @@ -23,7 +23,7 @@ - + diff --git a/weather-forecast.html b/weather-forecast.html index 399b514..9240f85 100644 --- a/weather-forecast.html +++ b/weather-forecast.html @@ -23,7 +23,7 @@ - + diff --git a/web.html b/web.html index 92531e6..d05d487 100644 --- a/web.html +++ b/web.html @@ -23,7 +23,7 @@ - +