-
D3.js, or D3, stands for Data Driven Documents. D3 is a JavaScript library to create dynamic and interactive data visualizations in the browser. It's built to work with common web standards, namely HTML, CSS, and Scalable Vector Graphics (SVG).
-
D3 takes input data and maps it into a visual representation of that data. It supports many different data formats. D3 lets you bind (or attach) the data to the Document Object Model (DOM). You use HTML or SVG elements with D3's built-in methods to transform the data into a visualization.
-
D3 has several methods that let you add and change elements in your document.
-
The
select()method selects one element from the document. It takes an argument for the name of the element you want and returns an HTML node for the first element in the document that matches the name. Here's an example:const anchor = d3.select("a"); -
The above example finds the first anchor tag on the page and saves an HTML node for it in the variable
anchor. You can use the selection with other methods. The "d3" part of the example is a reference to the D3 object, which is how you access D3 methods. -
Two other useful methods are
append()andtext().- The
append()method takes an argument for the element you want to add to the document. It appends an HTML node to a selected item, and returns a handle to that node. - The
text()method either sets the text of the selected node, or gets the current text. To set the value, you pass a string as an argument inside the parentheses of the method.
- The
-
D3 allows you to chain several methods together with periods to perform a number of actions in a row.
-
Example: Use the
selectmethod to select thebodytag in the document. Thenappendanh1tag to it, and add the text "Learning D3" into theh1element.
<body>
<script>
d3.select("body")
.append("h1")
.text("Learning D3")
</script>
</body>- D3 also has the
selectAll()method to select a group of elements. It returns an array of HTML nodes for all the items in the document that match the input string. Here's an example to select all the anchor tags in a document:
const anchors = d3.selectAll("a");-
Like the
select()method,selectAll()supports method chaining, and you can use it with other methods. -
Example: Select all of the
litags in the document, and change their text to "list item" by chaining the.text()method.
<body>
<ul>
<li>Example</li>
<li>Example</li>
<li>Example</li>
</ul>
<script>
d3.selectAll("li")
.text("list item")
</script>
</body>-
The D3 library focuses on a data-driven approach. When you have a set of data, you can apply D3 methods to display it on the page. Data comes in many formats, but this challenge uses a simple array of numbers.
-
The first step is to make D3 aware of the data. The
data()method is used on a selection of DOM elements to attach the data to those elements. The data set is passed as an argument to the method. -
A common workflow pattern is to create a new element in the document for each piece of data in the set. D3 has the
enter()method for this purpose. -
When
enter()is combined with thedata()method, it looks at the selected elements from the page and compares them to the number of data items in the set. If there are fewer elements than data items, it creates the missing elements. -
Example: Select the
bodynode, then select allh2elements. Have D3 create and append anh2tag for each item in thedatasetarray. The text in the h2 should say "New Title". Your code should use thedata()andenter()methods.
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body")
.selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text("New Title")
</script>
</body>- The D3
text()method can take a string or a callback function as an argument:
selection.text((d) => d)
-
In the example above, the parameter
drefers to a single entry in the dataset that a selection is bound to. -
Example: Change the
text()method so that eachh2element displays the corresponding value from thedatasetarray with a single space and "USD". For example, the first heading should be "12 USD".
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text(d => d + " USD");
</script>
</body><body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text((d) => `${d} USD`);
</script>
</body> -
D3 lets you add inline CSS styles on dynamic elements with the
style()method. -
The
style()method takes a comma-separated key-value pair as an argument. Here's an example to set the selection's text color to blue:
selection.style("color","blue");- Example: Add the
style()method to the code in the editor to make all the displayed text have afont-familyofverdana.
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text((d) => (d + " USD"))
.style("font-family","verdana")
</script>
</body>-
D3 is about visualization and presentation of data. It's likely you'll want to change the styling of elements based on the data. You can use a callback function in the
style()method to change the styling for different elements. -
eg.
selection.style("color", (d) => {
/* Logic that returns the color based on a condition */
});-
The
style()method is not limited to setting thecolor- it can be used with other CSS properties as well. -
Example: Add the
style()method to the code in the editor to set thecolorof theh2elements conditionally. Write the callback function so if the data value is less than 20, it returns "red", otherwise it returns "green".
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text((d) => (d + " USD"))
.style("color", d => {
if (d < 20) {
return "red"
} else {
return "green"
}
})
</script>
</body>-
Using a lot of inline styles on HTML elements gets hard to manage, even for smaller apps. It's easier to add a class to elements and style that class one time using CSS rules. D3 has the
attr()method to add any HTML attribute to an element, including a class name. -
The
attr()method works the same way thatstyle()does. It takes comma-separated values, and can use a callback function. Here's an example to add a class of "container" to a selection:
selection.attr("class", "container");
- Example: Add the
attr()method to the code in the editor and put a class ofbaron thedivelements.
<style>
.bar {
width: 25px;
height: 100px;
display: inline-block;
background-color: blue;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class","bar")
</script>
</body>- Recall the format to set a style using a callback function:
selection.style("cssProperty", (d) => d)- Exaple: Add the
style()method to the code in the editor to set theheightproperty for each element. Use a callback function to return the value of the data point with the string "px" added to it.
<style>
.bar {
width: 25px;
height: 100px;
display: inline-block;
background-color: blue;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.style("height", d => d + 'px')
// or style("height", d => `${d}px`)
</script>
</body>-
The last challenge created a bar chart, but there are a couple of formatting changes that could improve it:
-
- Add space between each bar to visually separate them, which is done by adding a margin to the CSS for the
barclass
- Add space between each bar to visually separate them, which is done by adding a margin to the CSS for the
-
- Increase the height of the bars to better show the difference in values, which is done by multiplying the value by a number to scale the height
-
Example:
- First, add a
marginof 2px to thebarclass in thestyletag. Next, change the callback function in thestyle()method so it returns a value 10 times the original data value (plus the "px"). - Note: Multiplying each data point by the same constant only alters the scale. It's like zooming in, and it doesn't change the meaning of the underlying data.
- First, add a
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.style("height", (d) => (d*10 + "px"))
</script>
-
SVG stands for Scalable Vector Graphics.
-
Here "scalable" means that, if you zoom in or out on an object, it would not appear pixelated. It scales with the display system, whether it's on a small mobile screen or a large TV monitor.
-
SVG is used to make common geometric shapes. Since D3 maps data into a visual representation, it uses SVG to create the shapes for the visualization. SVG shapes for a web page must go within an HTML
svgtag. -
CSS can be scalable when styles use relative units (such as
vh,vw, or percentages), but using SVG is more flexible to build data visualizations. -
Example:
- Add an
svgnode to thebodyusingappend(). Give it awidthattribute set to the providedwconstant and aheightattribute set to the providedhconstant using theattr()orstyle()methods for each. You'll see it in the output because there's abackground-colorof pink applied to it in thestyletag. - Note: When using
attr()width and height attributes do not have units. This is the building block of scaling - the element will always have a 5:1 width to height ratio, no matter what the zoom level is.
- Add an
<style>
svg {
background-color: pink;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width",w)
.attr("height",h)
</script>
</body>-
The last challenge created an
svgelement with a given width and height, which was visible because it had abackground-colorapplied to it in thestyletag. The code made space for the given width and height. -
The next step is to create a shape to put in the
svgarea. There are a number of supported shapes in SVG, such as rectangles and circles. They are used to display data. For example, a rectangle (<rect>) SVG shape could create a bar in a bar chart. -
When you place a shape into the
svgarea, you can specify where it goes withxandycoordinates. The origin point of (0, 0) is in the upper-left corner. Positive values forxpush the shape to the right, and positive values forypush the shape down from the origin point. -
To place a shape in the middle of the 500 (width) x 100 (height)
svgfrom last challenge, thexcoordinate would be 250 and theycoordinate would be 50. -
An SVG
recthas four attributes. There are thexandycoordinates for where it is placed in thesvgarea. It also has aheightandwidthto specify the size. -
Example: Add a
rectshape to thesvgusingappend(), and give it awidthattribute of 25 andheightattribute of 100. Also, give therectxandyattributes each set to 0.
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.append("rect")
.attr("width", 25)
.attr("height", 100)
.attr("x", 0)
.attr("y", 0)
</script>
</body>- Example: Use the
data(),enter(), andappend()methods to create and append arectfor each item indataset. The bars should display all on top of each other, this will be fixed in the next challenge.
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 25)
.attr("height", 100);
</script>
</body>-
The last challenge created and appended a rectangle to the
svgelement for each point indatasetto represent a bar. Unfortunately, they were all stacked on top of each other. -
The placement of a rectangle is handled by the
xandyattributes. They tell D3 where to start drawing the shape in thesvgarea. The last challenge set them each to 0, so every bar was placed in the upper-left corner. -
For a bar chart, all of the bars should sit on the same vertical level, which means the
yvalue stays the same (at 0) for all bars. Thexvalue, however, needs to change as you add new bars. Remember that largerxvalues push items farther to the right. As you go through the array elements indataset, thexvalue should increase. -
The
attr()method in D3 accepts a callback function to dynamically set that attribute. The callback function takes two arguments, one for the data point itself (usuallyd) and one for the index of the data point in the array. The second argument for the index is optional. Here's the format:
selection.attr("property", (d, i) => {
/*
* d is the data point value
* i is the index of the data point in the array
*/
})-
It's important to note that you do NOT need to write a
forloop or useforEach()to iterate over the items in the data set. Recall that thedata()method parses the data set, and any method that's chained afterdata()is run once for each item in the data set. -
Example:
- Change the
xattribute callback function so it returns the index times 30. - Note: Each bar has a width of 25, so increasing each
xvalue by 30 adds some space between the bars. Any value greater than 25 would work in this example.
- Change the
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => {
return i*30
})
.attr("y", 0)
.attr("width", 25)
.attr("height", 100);
</script>
</body>- Example:
- Change the callback function for the
heightattribute to return the data value times 3. - Note: Remember that multiplying all data points by the same constant scales the data (like zooming in). It helps to see the differences between bar values in this example.
- Change the callback function for the
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", 0)
.attr("width", 25)
.attr("height", (d, i) => {
return d*3
});
</script>
</body>-
You may have noticed the bar chart looked like it's upside-down, or inverted. This is because of how SVG uses (x, y) coordinates.
-
In SVG, the origin point for the coordinates is in the upper-left corner. An
xcoordinate of 0 places a shape on the left edge of the SVG area. Aycoordinate of 0 places a shape on the top edge of the SVG area. Higherxvalues push the rectangle to the right. Higheryvalues push the rectangle down. -
To make the bars right-side-up, you need to change the way the
ycoordinate is calculated. It needs to account for both the height of the bar and the total height of the SVG area. -
The y coordinate that is
y = heightOfSVG - heightOfBarwould place the bars right-side-up. -
Example:
- Change the callback function for the
yattribute to set the bars right-side-up. Remember that theheightof the bar is 3 times the data valued. - Note: In general, the relationship is
y = h - m * d, wheremis the constant that scales the data points.
- Change the callback function for the
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => { return (h-d*3); })
.attr("width", 25)
.attr("height", (d, i) => 3 * d);
</script>
</body>-
In SVG, a
rectshape is colored with thefillattribute. It supports hex codes, color names, and rgb values, as well as more complex options like gradients and transparency. -
Example: Add an
attr()method to set the "fill" of all the bars to the color "navy".
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill","navy")
</script>
</body>-
D3 lets you label a graph element, such as a bar, using the SVG
textelement. -
Like the
rectelement, atextelement needs to havexandyattributes, to place it on the SVG canvas. It also needs to access the data to display those values. -
D3 gives you a high level of control over how you label your bars.
-
Example:
-
The code in the editor already binds the data to each new
textelement. First, appendtextnodes to thesvg. Next, add attributes for thexandycoordinates. They should be calculated the same way as therectones, except theyvalue for thetextshould make the label sit 3 units higher than the bar. Finally, use the D3text()method to set the label equal to the data point value. -
Note: For the label to sit higher than the bar, decide if the
yvalue for thetextshould be 3 greater or 3 less than theyvalue for the bar.
-
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill", "navy");
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h -3 * d -3)
.text(d => d)
</script>
<body>-
D3 methods can add styles to the bar labels. The
fillattribute sets the color of thetextfor a text node. Thestyle()method sets CSS rules for other styles, such as "font-family" or "font-size". -
Example: Set the
font-sizeof thetextelements to 25px, and the color of the text to red.
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => d * 3)
.attr("fill", "navy");
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => d)
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - (3 * d) - 3)
.attr("font-size","25px")
.attr("fill","red")
</script>
</body>-
It's possible to add effects that highlight a bar when the user hovers over it with the mouse. So far, the styling for the rectangles is applied with the built-in D3 and SVG methods, but you can use CSS as well.
-
You set the CSS class on the SVG elements with the
attr()method. Then the:hoverpseudo-class for your new class holds the style rules for any hover effects. -
Example: Use the
attr()method to add a class ofbarto all therectelements. This changes thefillcolor of the bar to brown when you mouse over it.
-
A tooltip shows more information about an item on a page when the user hovers over that item. There are several ways to add a tooltip to a visualization, this challenge uses the SVG
titleelement. -
titlepairs with thetext()method to dynamically add data to the bars. -
Example: Append a
titleelement under eachrectnode. Then call thetext()method with a callback function so the text displays the data value.
<style>
.bar:hover {
fill: brown;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 200;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => d * 3)
.attr("fill", "navy")
.attr("class", "bar")
.append("title")
.text((d) => d)
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => d)
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - (d * 3 + 3))
</script>
</body>-
A scatter plot is another type of visualization. It usually uses circles to map data points, which have two values each. These values tie to the
xandyaxes, and are used to position the circle in the visualization. -
SVG has a
circletag to create the circle shape. It works a lot like therectelements you used for the bar chart. -
Example:
-
Use the
data(),enter(), andappend()methods to binddatasetto newcircleelements that are appended to the SVG canvas. -
Note: The circles won't be visible because we haven't set their attributes yet. We'll do that in the next challenge.
-
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
</script>
</body>-
A
circlein SVG has three main attributes. Thecxandcyattributes are the coordinates. They tell D3 where to position the center of the shape on the SVG canvas. The radius (rattribute) gives the size of thecircle. -
Just like the
rectycoordinate, thecyattribute for acircleis measured from the top of the SVG canvas, not from the bottom. -
All three attributes can use a callback function to set their values dynamically. Remember that all methods chained after
data(dataset)run once per item indataset. Thedparameter in the callback function refers to the current item indataset, which is an array for each point. You use bracket notation, liked[0], to access the values in that array. -
Example: Add
cx,cy, andrattributes to thecircleelements. Thecxvalue should be the first number in the array for each item indataset. Thecyvalue should be based off the second number in the array, but make sure to show the chart right-side-up and not inverted. Thervalue should be 5 for all circles.
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d, i ) => d[0])
.attr("cy", (d, i) => h - d[1])
.attr("r","5")
</script>
</body>- Example: Label each point on the scatter plot using the
textelements. The text of the label should be the two values separated by a comma and a space. For example, the label for the first point is "34, 78". Set thexattribute so it's 5 units more than the value you used for thecxattribute on thecircle. Set theyattribute the same way that's used for thecyvalue on thecircle.
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d, i) => d[0])
.attr("cy", (d, i) => h - d[1])
.attr("r", 5);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("x", d => d[0] + 5)
.attr("y", d => h- d[1])
.text(d => `${d[0]}, ${d[1]}`)
</script>
</body>-
The bar and scatter plot charts both plotted data directly onto the SVG canvas. However, if the height of a bar or one of the data points were larger than the SVG height or width values, it would go outside the SVG area.
-
In D3, there are scales to help plot data.
Scalesare functions that tell the program how to map a set of raw data points onto the pixels of the SVG canvas. -
For example, say you have a 100x500-sized SVG canvas and you want to plot Gross Domestic Product (GDP) for a number of countries. The set of numbers would be in the billion or trillion-dollar range. You provide D3 a type of scale to tell it how to place the large GDP values into that 100x500-sized area.
-
It's unlikely you would plot raw data as-is. Before plotting it, you set the scale for your entire data set, so that the
xandyvalues fit your canvas width and height. -
D3 has several scale types. For a linear scale (usually used with quantitative data), there is the D3 method
scaleLinear():
const scale = d3.scaleLinear()- By default, a scale uses the identity relationship. The value of the input is the same as the value of the output. A separate challenge covers how to change this.
<body>
<script>
const scale = d3.scaleLinear(); // Create the scale here
const output = scale(50); // Call the scale with an argument here
d3.select("body")
.append("h2")
.text(output);
</script>
</body>-
By default, scales use the identity relationship - the input value maps to the output value. But scales can be much more flexible and interesting.
-
Say a data set has values ranging from 50 to 480. This is the input information for a scale, and is also known as the domain.
-
You want to map those points along the
xaxis on the SVG canvas, between 10 units and 500 units. This is the output information, which is also known as the range. -
The
domain()andrange()methods set these values for the scale. Both methods take an array of at least two elements as an argument. Here's an example:
// Set a domain
// The domain covers the set of input values
scale.domain([50, 480]);
// Set a range
// The range covers the set of output values
scale.range([10, 500]);
scale(50) // Returns 10
scale(480) // Returns 500
scale(325) // Returns 323.37
scale(750) // Returns 807.67
d3.scaleLinear()-
Notice that the scale uses the linear relationship between the domain and range values to figure out what the output should be for a given number. The minimum value in the domain (50) maps to the minimum value (10) in the range.
-
Example:
- Create a scale and set its domain to
[250, 500]and range to[10, 150]. - Note: You can chain the
domain()andrange()methods onto the scale variable.
- Create a scale and set its domain to
<body>
<script>
const scale = d3.scaleLinear();
scale
.domain([250, 500])
.range([10,150]);
const output = scale(50);
d3.select("body")
.append("h2")
.text(output);
</script>
</body>-
The D3 methods
domain()andrange()set that information for your scale based on the data. There are a couple methods to make that easier. -
Often when you set the domain, you'll want to use the minimum and maximum values within the data set. Trying to find these values manually, especially in a large data set, may cause errors.
-
D3 has two methods -
min()andmax()to return this information. Here's an example:
const exampleData = [34, 234, 73, 90, 6, 52];
d3.min(exampleData) // Returns 6
d3.max(exampleData) // Returns 234- A dataset may have nested arrays, like the [x, y] coordinate pairs that were in the scatter plot example. In that case, you need to tell D3 how to calculate the maximum and minimum. Fortunately, both the
min()andmax()methods take a callback function. In this example, the callback function's argumentdis for the current inner array. The callback needs to return the element from the inner array (the x or y value) over which you want to compute the maximum or minimum. Here's an example for how to find the min and max values with an array of arrays:
const locationData = [[1, 7],[6, 3],[8, 3]];
// Returns the smallest number out of the first elements
const minX = d3.min(locationData, (d) => d[0]);
// minX compared 1, 6, and 8 and is set to 1- Example: The
positionDataarray holds sub arrays of x, y, and z coordinates. Use a D3 method to find the maximum value of the z coordinate (the third value) from the arrays and save it in theoutputvariable.
<body>
<script>
const positionData = [[1, 7, -4],[6, 3, 8],[2, 9, 3]]
const output = d3.max(positionData, (d) => d[2]);
d3.select("body")
.append("h2")
.text(output)
</script>
</body>-
The D3
min()andmax()methods are useful to help set the scale. -
Given a complex data set, one priority is to set the scale so the visualization fits the SVG container's width and height. You want all the data plotted inside the SVG canvas so it's visible on the web page.
-
The example below sets the x-axis scale for scatter plot data. The
domain()method passes information to the scale about the raw data values for the plot. Therange()method gives it information about the actual space on the web page for the visualization. -
In the example, the domain goes from 0 to the maximum in the set. It uses the
max()method with a callback function based on the x values in the arrays. The range uses the SVG canvas' width (w), but it includes some padding, too. This puts space between the scatter plot dots and the edge of the SVG canvas.
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
// Padding between the SVG canvas boundary and the plot
const padding = 30;
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);-
The padding may be confusing at first. Picture the x-axis as a horizontal line from 0 to 500 (the width value for the SVG canvas). Including the padding in the
range()method forces the plot to start at 30 along that line (instead of 0), and end at 470 (instead of 500). -
Example:
- Use the
yScalevariable to create a linear y-axis scale. The domain should start at zero and go to the maximum y value in the set. The range should use the SVG height (h) and include padding. - Note: Remember to keep the plot right-side-up. When you set the range for the y coordinates, the higher value (height minus padding) is the first argument, and the lower value is the second argument.
- Use the
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
// Padding between the SVG canvas boundary and the plot
const padding = 30;
// Create an x and y scale
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0,d3.max(dataset, (d) => d[1])])
.range([h - padding, padding])
const output = yScale(411); // Returns 30
d3.select("body")
.append("h2")
.text(output)
</script>
</body>-
With the scales set up, it's time to map the scatter plot again. The scales are like processing functions that turn the x and y raw data into values that fit and render correctly on the SVG canvas. They keep the data within the screen's plotting area.
-
You set the coordinate attribute values for an SVG shape with the scaling function. This includes
xand y attributes forrectortextelements, orcxandcyforcircles. Here's an example:
shape
.attr("x", (d) => xScale(d[0]))-
Scales set shape coordinate attributes to place the data points onto the SVG canvas. You don't need to apply scales when you display the actual data value, for example, in the
text()method for a tooltip or label. -
Example:
-
Use
xScaleandyScaleto position both thecircleandtextshapes onto the SVG canvas. For thecircles, apply the scales to set thecxandcyattributes. Give them a radius of 5 units, too. -
For the
textelements, apply the scales to set thexandyattributes. The labels should be offset to the right of the dots. To do this, add 10 units to the x data value before passing it to thexScale.
-
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const padding = 60;
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h - padding, padding]);
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d[0]))
.attr("cy", (d) => yScale(d[1]))
.attr("r",5);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => (d[0] + ", " + d[1]))
.attr("x", (d) => xScale(d[0]+10))
.attr("y", (d) => yScale(d[1]))
</script>
</body>- D3 has two methods
axisLeft() andaxisBottom()to render the y and x axes, respectively. (Axes is the plural form of axis). Here's an example to create the x-axis based on thexScalein the previous challenges:
const xAxis = d3.axisBottom(xScale);-
The next step is to render the axis on the SVG canvas. To do so, you can use a general SVG component, the
gelement. Thegstands for group. -
Unlike
rect,circle, andtext, an axis is just a straight line when it's rendered. Because it is a simple shape, usinggworks. -
The last step is to apply a
transformattribute to position the axis on the SVG canvas in the right place. Otherwise, the line would render along the border of SVG canvas and wouldn't be visible. -
SVG supports different types of
transforms, but positioning an axis needstranslate. When it's applied to thegelement, it moves the whole group over and down by the given amounts. Here's an example:
const xAxis = d3.axisBottom(xScale);
svg.append("g")
.attr("transform", "translate(0, " + (h - padding) + ")")
.call(xAxis);-
The above code places the x-axis at the bottom of the SVG canvas. Then it's passed as an argument to the
call()method. The y-axis works in the same way, except thetranslateargument is in the form (x, 0). Becausetranslateis a string in theattr()method above, you can use concatenation to include variable values for its arguments. -
Example: The scatter plot now has an x-axis. Create a
y-axisin a variable named yAxis using theaxisLeft()method. Then render the axis using agelement. Make sure to use atransformattribute to translate the axis by the amount of padding units right, and 0 units down. Remember tocall()the axis.
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const padding = 60;
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h - padding, padding]);
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d[0]))
.attr("cy",(d) => yScale(d[1]))
.attr("r", (d) => 5);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => (d[0] + "," + d[1]))
.attr("x", (d) => xScale(d[0] + 10))
.attr("y", (d) => yScale(d[1]))
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(" + padding +", 0)")
.call(yAxis)
</script>
</body>










