This article will chronicle my experiences studying D3.js, a JavaScript framework that produces high-quality diagrams and graphics fueled by data.
Although D3 is a JavaScript library, we will use TypeScript because it handles data and data types more efficiently than plain old JavaScript.
This tutorial will show you how to create visualizations using D3 and TypeScript. First, we’ll create a simple boilerplate project, add data, and then build a fully interactive example. We will host the code on CodePen for maximum interactivity as a stylistic decision.
Each example will run in the browser, saving you the fuss of installing anything onto your computer. Of course, you’ll be able to fork a Pen and work on your code version.
Jump ahead:
We will mainly focus on adding dependencies and getting acquainted with the environment with the boilerplate Pen. There will be HTML on the left, CSS in the middle, and TypeScript on the right. This is where most of the action will take place.
Next, ensure that the JavaScript pane is set to TypeScript and add D3 as a dependency. You can do this by selecting the gear icon on the top right.
Here’s an interactive example of the code:
See the Pen
Typescript + D3 Boilerplate by rosdec (@rosdec)
on CodePen.
The code is straightforward, so we will only focus on TypeScript because the HTML and CSS are irrelevant.
import * as d3 from "https://cdn.skypack.dev/[email protected]"; const svg = d3.select("body") .append("svg") .attr("width", 500) .attr("height", 500); svg .append("text") .attr("x", 100) .attr("y", 100) .text("Hello d3js"); svg .append("circle") .attr("r", 30) .attr("cx", 60) .attr("cy", 50);
The import addresses the D3 library dependency. In the following block of code, we added the svg
component to the body
tag to accommodate our graphics in each code example.
The last two lines of code add the text and circle to produce the results seen in the Pen. That is everything we need to set up the environment to play with D3.
To stay true to the D3 philosophy, we have to add data to our graphics. To do this, we’ll use the code below. This downloads a CSV file and manipulates it to visualize it as a scatter plot.
See the Pen
Typescript + D3 Interactive by rosdec (@rosdec)
on CodePen.
import * as d3 from "https://cdn.skypack.dev/[email protected]"; // set the dimensions and margins of the graph const margin = { top: 10, right: 30, bottom: 30, left: 60 }, width = 460 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; // append the svg object to the body of the page const svg = d3 .select("body") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left}, ${margin.top})`); // Read the data d3.csv( "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/iris.csv" ).then(function (csvdata) { // Add X axis const x = d3.scaleLinear().domain([3, 9]).range([0, width]); const xAxis = svg .append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x)); // Add Y axis const y = d3.scaleLinear().domain([0, 9]).range([height, 0]); svg.append("g").call(d3.axisLeft(y)); // Add dots svg.append("g") .selectAll("circle") .data(csvdata) .join("circle") .attr("cx", function (d) { return x(d.Sepal_Length); }) .attr("cy", function (d) { return y(d.Petal_Length); }) .attr("r", 5); });
After we’ve done the import, we will define the surface to draw our graphics on. We can do this by calculating the area’s width and height along with the margins. These values add svg
to the HTML body and specify a transformation. In this case, it’s a translate
operation to place g
under svg
.
Our last operation is to map the points in the scatterplot. The data is directly downloaded from a URL as a CSV file. D3 can manipulate CSV data, which comes in handy most of the time. Once the data is available, three operations are performed: adding the x-axis, adding the y-axis, and plotting the data.
The first two operations are performed consecutively to define the x and y-axes as scaleLinear
. Then, for each axis, the range is specified by the number of pixels that such axes will occupy. The x-axis will have a domain of 3
to 9
, and the y-axis will be 0
to 9
.
Adding the dots to the scatter plot may appear a little convoluted, but we can do it with a specific sequence of operations. The idea is to bind a graphic component — a circle, a point, and a line — to data. Then, the data will modify some aspects of the graphic component, such as color, thickness, pattern, and position. In the source code above, we:
.selectAll
to define the type of element that will join each element of the data.data
to define the array.join
to join the data and the graphics elements. This is where HTML
or SVG
are dynamically added and removedNow, we define how the value of the data influences the graphics element. In the example above, you can see that we .selectAll
circle elements, we call .data
to assign the csvdata
as the data source of the join, and then we .join
the data to the graphics element.
Our final and most creative step is defining how the data influence graphics
. At this point, we only have a circle for each entry in the csvdata
. To modify the circles, we use .attr
. The first two will modify the attributes cx
and cy
at the center of the circle coordinates, then modify the radius by setting it to 5
.
Next, let’s add an interaction to explore the real possibilities of D3. First, let’s look at the final result:
See the Pen
Typescript + D3 Interactive by rosdec (@rosdec)
on CodePen.
Together with the scatterplot, we have a button to remove one circle from the data. This is done in D3 without access to external sources. The button is added in HTML but is bound to the code it will execute in the TypeScript source.
The interaction occurs in popCircle()
and will remove the last element from the csvdata
array. This will then join the circles in the scatterplot to data, but with a little addition — the .exit
operation. This defines what happens when a datum exits the graphics array.
The semantics of the code is simple: everything after .exit()
will apply to the graphics elements exiting from the array. Here, we ask D3 to apply a transition on the circle radius specified by .attr
that will become 0
before removing graphics
from the representation.
The final effect is visually satisfying. Once you click the button, the scatterplot circles disappear, and the transition alerts the user to where modifications are happening.
In this tutorial, we learned how to set up a stripped-down project based on TypeScript and D3. We also saw how to go from a basic representation to an advanced and interactive data-driven one. Although the example is simple, I hope it clearly shows where to intervene to produce data-driven graphical representations.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.