Tutorials on D3

Learn about D3 from fellow newline community members!

  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL
  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL

Building a Choropleth Map with D3 and Svelte

In this article, we will create a data visualization that displays the ratio of Dunkin’ Donuts locations to Starbucks locations (by state) using D3 and Svelte. Which of America’s largest coffee chains keeps your state awake and ready for the 9-5 workday?Choropleth maps bring data to life. By projecting data onto a map, you can craft a captivating, visual narrative around your data that uncovers geographical patterns and insights. Choropleth maps color (or shade) geographical areas like countries and states based on numeric data values. The intensity of the color represents the magnitude of the data value in a specific geographical area. With a single glance, these colors allow us to easily identify regional hotspots, trends and disparities that are not immediately apparent from raw data. Think about the geographic distribution of registered Democrat and Republican voters across the United States. A state with an overwhelming majority of registered Democrat voters might be colored blue, whereas a state with an overwhelming majority of registered Republican voters might colored red. A state with a single-digit percentage difference between registered Democrat and Republican voters, such as Pennsylvania, would be colored blue purple. On the contrary, a state with a significantly larger ratio of registered Democrat voters to Republican voters, such as California, would be colored a more intense blue. Examining all of the states, you will recognize that registered Democrat voters primarily reside in states in the northeast region and along the western coastline of the United States. Choropleth maps let us answer geographic questions about our data and contextualize our data through the lens of our knowledge of the world. For example, looking at a choropleth map of registered Democrat and Republican voters in the United States on a state basis, it may make evident the differences in the laws and policies enacted across each state. Anyone who can read a map will have zero troubles navigating, understanding and deriving conclusions from choropleth maps. A common method for creating Choropleth maps for the web is D3, a popular JavaScript data visualization library. However, using just D3 to create choropleth maps comes with several downsides: And so, why not delegate the rendering logic to a declarative, UI framework like Svelte? Svelte surgically updates the DOM and produces highly optimized JavaScript code with zero runtime overhead. Additionally, Svelte components consist of three sections — script, styles and markup — to keep logic organized and consistent. By letting Svelte handle the rendering logic and D3 handle the data transformation logic (and difficult mathematical calculations), we can: Below, I'm going to show you how to build a choropleth map with D3 and Svelte. The choropleth map will display the ratio of Dunkin’ Donuts locations to Starbucks locations (by state). States with significantly more Starbucks locations than Dunkin’ Donuts locations will be colored green, and states with significantly more Dunkin’ Donuts locations than Starbucks locations will be colored orange. A legend will be added to map colors to magnitudes of location ratios. By the end of this tutorial, you will have built the following choropleth map: To set up a new Svelte project with Vite and TypeScript , run the command npm init vite . Note : You may generate a new Svelte application with SvelteKit, but this tutorial is only focused on building out a single Svelte component for the choropleth map. Therefore, it’s more preferred to use a lighter template so that you don’t need to mess around with extra project files. To obtain the number of Dunkin’ Donuts and Starbucks locations in those states, visit the following websites: And record the states and their location counts in two CSV files: dunkin_donuts_locations_counts.csv and starbucks_locations_counts.csv . Each CSV’s header row includes titles for two columns: state and count . The delimiter should be a comma. Then, within the public directory, create a data directory and place both CSV datasets in this new directory. To obtain a TopoJSON file of the geometries that represent US states, visit the U.S. Atlas TopoJSON GitHub repository ( https://github.com/topojson/us-atlas ). Then, scroll through the contents of the repository’s [README.md](http://README.md) file and download the states-albers-10m.json file. The state boundaries are drawn based on the 2017 edition of the Census Bureau’s cartographic state boundaries. Unlike the states-10m.json file, the geometries within this file have been projected to fit a 975 x 610 viewport. Once downloaded, rename the file as us_topojson.json and place it within the public/data directory. To create geographic features in an SVG canvas, D3 consumes GeoJSON data. Therefore, why are we downloading a TopoJSON file? TopoJSON is an extension of GeoJSON that eliminates redundancy in geometries via arcs . It’s more compact than GeoJSON (typically 80% smaller than their GeoJSON equivalents), and it preserves and encodes topology. For the choropleth map, it will download a TopoJSON file, not a GeoJSON file, of US states so that the choropleth map does not have to wait long . Then, we will leverage a module, topojson-client , to convert TopoJSON features to GeoJSON features for D3 to work with. For the choropleth map, we will need to install four specific D3 modules and a related module that’s also from the creator of D3: Run the following command to install these D3 modules and their type definitions in the Svelte project. First, delete the src/lib directory and src/app.css file. Then, in src/main.ts , omit the import './app.css' statement at the top of the file. In the src/App.svelte file, clear out the contents of the script, style and markup sections. Within the script section, let’s add the import statement for the <ChoroplethMap /> component and declare two variables: ( src/App.svelte ) Within the style section, let’s add some minor styles to horizontally center the <ChoroplethMap /> component in the <main /> element. ( src/App.svelte ) Note : Styles defined in the <App /> component won’t leak into other Svelte components. Within the <main /> element of the markup section, call the <ChoroplethMap /> component. Also, pass datasets to the datasets prop and colors to the colors prop of the ChoroplethMap /> component, like so: ( src/App.svelte ) Within the src directory, create a new folder named components . This folder will contain any reusable components used in this Svelte application. In this case, there will only be one component in this directory: ChoroplethMap.svelte . Create this file inside of the src/components directory. Within the src/components/ChoroplethMap.svelte file, begin with an empty script section for the <ChoroplethMap /> component: ( src/components/ChoroplethMap.svelte ) At the top of the script section, import several methods from the installed D3 modules: ( src/components/ChoroplethMap.svelte ) Then, declare the datasets and colors props that the <ChoroplethMap /> component currently accepts. Set their default values to empty arrays when no value is passed to either prop. ( src/components/ChoroplethMap.svelte ) d3-fetch comes with a convenient method for fetching and parsing CSV files: csv() . This method accepts, as arguments, a URL to a CSV dataset and a callback function that maps each row’s values to actual data values. For example, since numeric values in a CSV file will initially be represented as strings, they must be parsed as numbers. In our case, we want to parse count as a number. In a Svelte component, we will need to use the onMount lifecycle method to fetch data after the component gets rendered to the DOM for the first time, like so: For us to load both datasets, we can: Note : We’re flattening the returned data so that we can later group the data by state and calculate the location ratio on a per state basis. d3-fetch comes with a convenient method for fetching and parsing JSON files: json() . For the choropleth map, we will only want the method to accept, as an argument, a URL to the TopoJSON file with the geometry collection for US states. We will need to add this line of code to the onMount lifecycle method so that the TopoJSON data gets fetched alongside the CSV data, like so: To convert TopoJSON data to GeoJSON data, we will need to… Add these lines of code to the onMount lifecycle method, like so: Like with any D3 data visualization, you need to define its dimensions . Let’s define the choropleth map’s width, height and margins, like so: In the <ChoroplethMap /> component’s markup section, add an <svg /> element and set its width , height and viewBox using the values from dimensions . Within this <svg /> element, add a <g /> element that will group the <path /> elements that will represent the states and the internal borders between them. Back in the script section of the <ChoroplethMap /> component, create a new geographic path generator via the geoPath() method, like so: path is a function that turns GeoJSON data into a string that defines the path to be drawn for a <path /> element. In other words, this function, when called with stateMesh or a feature object from statesFeatures , will return a string that we can set to the d attribute of a <path /> element to render the internal borders between states or a state respectively. Here, we’ll render the internal borders between states and use an each block to loop over the feature objects in statesFeatures and render the states inside of the <g /> element, like so: Since stateMesh and statesFeatures are declared within the onMount lifecycle method, we’ll have to move the declarations to the top-level of the script section to ensure that these values can be used in the markup section of the <ChoroplethMap /> component. When you run the project in development via npm run dev , you should see a choropleth map that looks like the following: To adjust the fill color of each state by location ratio, first locally declare two variables at the top-level of the script section: Note : <string, string> corresponds to <Range, Output> . The Range generic represents the type of the range data. The Output generic represents the type of the output data (what’s outputted when calling scale() ). Within the onMount lifecycle method, using the d3-array 's rollup() method, group the data by state name, and map each state name to a ratio of Dunkin’ Donuts locations in the state to Starbucks locations in the state. Then, get the maximum location ratio from ratios via D3’s extent() method. Since the method only accepts an array as an argument, you will need to first convert the map to an array via Array.from() . Then, set scale to a linear scale that maps the ratios to colors passed into the colors prop. The max value corresponds to the first color in the colors list, an orange color. Any state that’s colored orange will indicate a higher ratio of Dunkin’ Donuts locations to Starbucks locations. Additionally, any state that’s colored green will indicate a lower ratio of Dunkin’ Donuts locations to Starbucks locations. A 1:1 ratio ( 1 in the domain) denotes an equal number of Dunkin’ Donuts locations to Starbucks locations. Note : A quantized scale would be better suited. However, the domain of scaleQuantize() accepts only two arguments, a minimum and maximum value. This means you cannot define your own threshold values ( scaleQuantize() automatically creates its own threshold values from the provided minimum and maximum values). Within the markup section of the <ChoroplethMap /> component, replace the currently set fill of "green" to scale(ratios.get(feature.properties.name)) . Upon saving these changes, you should see the colors of the states update. Wow, it seems Dunkin’ Donuts keeps the northeast of the US awake! The colors chosen for this data visualization are based on the official branding colors of Dunkin’ Donuts and Starbucks. For folks who might not be familiar with Dunkin’ Donuts and Starbucks official branding colors, let’s create a simple legend for the choropleth map so they know which states have a higher concentration of Starbucks locations and which states have a higher concentration of Dunkin’ Donuts locations. First, let’s locally declare a variable categories that maps datasets to an array that contains only the labels of the datasets. Then, create a new file in the src/components directory: Legend.svelte . This <Legend /> component will accept three props: dimensions , colors and categories . Given that we only want two labels for the legend, one for the first color in colors and one for the last color in colors , we create the labels by setting the first item in labels to categories[0] (”Dunkin’ Donuts”) and the last item in labels to categories[1] (”Starbucks”). Then, we leave the middle three labels undefined. This way, we can render the colors and labels one-to-one in the markup section. ( src/components/Legend.svelte ) Back in the <ChoroplethMap /> component, we can import the <Legend /> component and render it within the <svg /> element like so: Upon saving these changes, you should see the legend appear in the bottom-right corner of the choropleth map. Try customizing the choropleth map with your own location counts data. If you find yourself stuck at any point while working through this tutorial, then feel free to check out the project's GitHub repository or a live demo of this project in the following CodeSandbox: If you want to learn more about building visualizations with D3 and Svelte, then check out the Better Data Visualizations with Svelte course by Connor Rothschild, a partner and data visualization engineer at Moksha Data Studio.

Thumbnail Image of Tutorial Building a Choropleth Map with D3 and Svelte

Building a Bar Chart Race with D3 and Svelte

In this article, we will create a data visualization that animates the changes in the stargazer counts of popular front-end library/framework GitHub repositories over the past 15 years. Which front-end libraries/frameworks currently dominate the web development landscape? Which front-end libraries/frameworks used to dominate web development landscape?Bar chart races make boring bar charts dynamic and fun. Unlike regular bar charts, bar chart races show the growth and decline (the fluctuations) in the relative values of categories over time. Each bar represents a category, and the bar grows or shrinks in length with respect to its corresponding value at a given time and an ever-changing scale. The bars reposition themselves, commonly, in descending order of values. Depending on the maximum number of bars that can be shown in the bar chart race, you may occasionally see a bar drop off at or re-emerge from the bottom of the visualization. Due to the animation aspect of bar chart races (the racing effect created by animated bars), they have become popular in recent years on social media platforms. They turn vast amounts of complex data into a captivating, easy-to-digest medium. Bar chart races reveal trends that emerged or fell off across intervals of time. For example, if you created a bar chart race of browser usage over the 1990s to the present day, then you may initially see the rise of Internet Explorer, followed by its gradual decline as browsers like Chrome and Firefox became dominate forces in the browser market. D3 is great at tracking elements in an animation and animating enter and exit transitions. However, its imperative .join() approach to data-binding and managing enter, update and exit animations separately is not as expressive as Svelte’s declarative approach via reactivity, dynamic attributes (via curly braces) and built-in animation and transition directives. Below, I'm going to show you how to build a bar chart race with D3 and Svelte. The bar chart race will show the rate of growth in each GitHub repository’s stargazer count from April 2009 to the present day. By the end of this tutorial, you will have build the following bar chart race: To set up a new Svelte project with Vite and TypeScript , run the command npm init vite . Note : You may generate a new Svelte application with SvelteKit, but this tutorial is only focused on building out a single Svelte component for the bar chart race. Therefore, it’s more preferred to use a lighter template so that you don’t need to mess around with extra project files. Currently, you cannot query GitHub’s GraphQL API for a GitHub repository’s stargazer counts history. However, there’s an open source project that maintains records of repositories’ stargazer counts through the years: Star History. To get a CSV of historical stargazer counts for a GitHub repository, enter the both the username of the GitHub repository’s author and the name of the GitHub repository, delimited by a / . For example, facebook/react for React.js. Once you’ve clicked on the “View star history” button and waited for the chart to be generated, click on the CSV button to download this data into a CSV file. You can add more GitHub repositories to the chart so that the CSV will contain data for all of these GitHub repositories. For the bar chart race, we will be visualizing the historical stargazer counts for the following repositories: Once downloaded, rename the file as frontend-libraries-frameworks.csv and place it within the public/data directory. Since the data is incomplete, we will be interpolating stargazer counts for unknown dates. Additionally, from the dates, omit the day of week, the time and the time zone from the values of the second column (e.g., Thu Feb 11 2016 12:06:18 GMT-0500 (Eastern Standard Time) → Feb 11 2016 ). At the top of the CSV, add a header row to label the columns: “name,date,value.” For the bar chart race, we will need to install five specific D3 modules: Run the following command to install these D3 modules and their type definitions in the Svelte project. First, delete the src/lib directory and src/app.css file. Then, in src/main.ts , omit the import './app.css' statement at the top of the file. In the src/App.svelte file, clear out the contents of the script, style and markup sections. Within the script section, let’s add the import statement for the <BarChartRace /> component and two variables: ( src/App.svelte ) Within the style section, let’s add some minor styles to horizontally center the <BarChartRace /> component in the <main /> element. ( src/App.svelte ) Note : Styles defined in the <App /> component won’t leak into other Svelte components. Within the <main /> element of the markup section, call the <BarChartRace /> component. Also, pass datasetUrl to the datasetUrl prop and maxBars to the maxBars prop of the <BarChartRace /> component, like so: ( src/App.svelte ) Then, create a types folder under the src directory. Within this folder, create an index.ts file and define and export two interfaces: Record and KeyframeRecord . ( types/index.ts ) We will annotate the records from the raw CSV dataset with Record , and we will annotate the records stored in a “keyframe” (we will cover this later in this tutorial) with KeyframeRecord . Within the src directory, create a new folder named components . This folder will contain any reusable components used in this Svelte application. In this case, there will only be one component in this directory: BarChartRace.svelte . Create this file inside of the src/components directory. Within the src/components/BarChartRace.svelte file, begin with an empty script section for the <BarChartRace /> component: ( src/components/BarChartRace.svelte ) At the top of the script section, import several methods from the installed D3 modules: ( src/components/BarChartRace.svelte ) Then, declare the datasetUrl and maxBars props that the <BarChartRace /> component currently accepts. Additionally, locally declare three variables: ( src/components/BarChartRace.svelte ) d3-fetch comes with a convenient method for fetching and parsing CSV files: csv() . This method accepts, as arguments, a URL to a CSV dataset and a callback function that maps each row’s values to actual data values. All values in the CSV dataset are represented as strings. For the bar chart race, we need to parse value as a number and date as a Date object. To parse date as a Date object, create a parser by calling the timeParse() method with the structure of the stringified date (so that the parser understands how to parse the date string). Since date is formatted as <abbreviated month name> <zero-padded day of the month> <year with century> (e.g., Feb 11 2016 ), we pass the specifier string of "%b %d %Y" to the timeParse() method. In a Svelte component, we will need to use the onMount lifecycle method to fetch data after the component gets rendered to the DOM for the first time, like so: The bar chart race’s animation iterates over a series of keyframes. Each keyframe represents exactly one moment of the bar chart race; it contains data of the GitHub repositories’ stargazer counts at a given date. Because the source dataset from Star History doesn’t contain stargazer counts at every single date from April 2009 (the month of the earliest known stargazer count) to the present day, we will need to interpolate between data points (estimate stargazer counts for unknown dates) to guarantee that there’s enough keyframes to make the animation run smoothly. Every x milliseconds, we can update the animation with the data from the next keyframe until we run out of keyframes, at which point, the animation will stop at the current day stargazer counts for the GitHub repositories. To create these keyframes, we need to: Like with any D3 data visualization, you need to define its dimensions . Let’s define the bar chart race’s width, height and margins, like so: In the <BarChartRace /> component’s markup section, add an <svg /> element and set its width , height and viewBox using the values from dimensions . Within this <svg /> element, add a <g /> element that will group the <rect /> elements that will represent the bars. The x-scale maps a domain of stargazer counts to the horizontal dimensions of the bar chart race. You’re probably wondering why the maximum value of the domain is 1 despite our source dataset shows that the maximum stargazer count is 210,325. This domain serves as a placeholder for the x-scale’s domain. When we’re animating the bar chart race by iterating over the keyframes, we will adjust the x-scale’s domain based on the current keyframe’s data. This way, during the animation, the maximum stargazer count will always span the entire width ( dimensions.width - dimensions.margin.right ) of the bar chart race. On the other hand, the y-scale maps a domain of visible bar indices to the vertical dimension of the bar chart race. The domain specifies 1 more than the maximum number of visible bars since we want to be able to transition between the bottom-most visible bar and the hidden bar beneath it smoothly. Note : <number> corresponds to <Range> . This generic represents the data type of the domain values. Then, define a color scheme. Initialize it as a function that returns “#FFFFFF.” This function will serve as a placeholder function until we actually fetch the CSV dataset, at which point, we can reassign the color scheme to map each GitHub repository to a specific color. Note : _d ensures that the function signature matches the function signature of the function that will override this placeholder function. In the onMount lifecycle method, after fetching the CSV dataset and creating a set of GitHub repository names from the source data, assign a new color scheme that assigns each GitHub repository name to a specific color, like so: To animate the bar chart race, first locally declare a variable keyframeItems at the top-level of the script section: keyframeItems will hold a keyframe’s list of the GitHub repositories and their stargazer counts and ranks. By reassigning this variable for each keyframe, Svelte’s reactivity will automatically update the bars’ widths and positions. Additionally, at the top-level of the script section, call the timeFormat() method with a string that describes how to format the date based on an input Date object. This way, the formatter knows what to output when given an input Date (e.g., “Jul 2023”). In the onMount lifecycle method, once the keyframes have been created, set up a setInterval() function that… Note : In a future tutorial, I will show you how to re-implement this with requestAnimationFrame . Within the <g /> element in the markup section of the <BarChartRace /> component, use an each block to loop over keyframeItems and render a <rect /> for each visible bar. The items are keyed by the GitHub repositories’ names so that Svelte knows not to recreate the bars anytime keyframeItems gets updated and to just continue modifying properties of the existing bars. The in and out directives allow us to control the enter and exit animations of the bars. For example, in corresponds to an enter animation, and out corresponds to an exit animation. To keeps things simple, we’ll have the bar fade out when it exits and fade in when it enters. Finally, add an axis line to show the bars left-aligned and the ticker to the <svg /> element. When you run the project in development via npm run dev , you should see a bar chart race that looks like the following: Try customizing the bar chart race for your own historical count data. If you find yourself stuck at any point while working through this tutorial, then feel free to check out the project's GitHub repository or a live demo of this project in the following CodeSandbox: If you want to learn more about building visualizations with D3 and Svelte, then check out the Better Data Visualizations with Svelte course by Connor Rothschild, a partner and data visualization engineer at Moksha Data Studio.

Thumbnail Image of Tutorial Building a Bar Chart Race with D3 and Svelte

I got a job offer, thanks in a big part to your teaching. They sent a test as part of the interview process, and this was a huge help to implement my own Node server.

This has been a really good investment!

Advance your career with newline Pro.

Only $30 per month for unlimited access to over 60+ books, guides and courses!

Learn More

Building a Word Cloud with D3 and Svelte

In this article, we will create a data visualization that displays the frequency of words in the lyrics of a song under the Billboard Hot 100 list, Vampire, by Olivia Rodrigo, using D3 and Svelte. Which words do you think catapult a song to the Billboard Hot 100 list?When repeated enough times, words become memorable. Anytime you listen to a speech, notice how frequently certain words come up, how the repetition helps you recognize the importance of the speaker’s message. If you happen to only have a transcript of the speech, then you would need to read/skim through paragraphs of text to grasp the essence of the speaker's words and gain a complete understanding of the message being conveyed. With word clouds (also known as tag clouds ), you can visualize the frequency of words. Words are arranged in a cloud-shaped formation, and each word is sized and colored based on its frequency (or importance) in a given text. The more frequently a word appears, the larger (or more intense color-wise) it appears in the word cloud. This makes it easier to visually identify critical keywords and themes in textual content. Simultaneously, word clouds capture and summarize the essence of textual content in a single glance. Whether you are interested in seeing what trending topics are being discussed in online communities or what words leaders use to inspire their nations, a word cloud offers a clear window into any textual content. There’s a D3 module that’s available for generating word clouds: d3-cloud . This module automatically takes a mapping of words and their frequencies and determines how to properly size and position them in a word cloud with minimal collisions. However, since the pure D3 implementation of a word cloud involves appending an SVG <text /> element, one by one, each time a word gets processed: What happens if we want to update the word cloud using another set of words? Rather than having to manually manage the DOM using D3’s imperative API (i.e., manually removing all of the previous SVG <text /> elements, re-appending new SVG <text /> elements, etc.), we can let Svelte render elements to the DOM and keep the DOM in sync with our data via reactivity . This way, anytime our data changes, Svelte automatically updates the DOM accordingly. In Svelte, all assignments are reactive. If we wanted to mark any number of top-level statements reactive, like the above code snippet, then all we have to do is wrap them in curly braces and prefix the block with the $ label syntax. This results in reactive statements . Any values within the reactive block become dependencies of the reactive statement. When any of these values change, the reactive statement gets re-run. This is perfect in case we want our word cloud to update anytime we provide a different set of words. Below, I'm going to show you how to build a word cloud with D3 and Svelte. The word cloud will display the frequency of words in the lyrics of of a song under the Billboard Hot 100 list, Vampire, by Olivia Rodrigo. The larger the word, and the less faded the word is, the greater the frequency of word in the lyrics. By the end of this tutorial, you will have built the following word cloud: To set up a new Svelte project with Vite and TypeScript , run the command npm init vite . Note : You may generate a new Svelte application with SvelteKit, but this tutorial is only focused on building out a single Svelte component for the word cloud. Therefore, it’s more preferred to use a lighter template so that you don’t need to mess around with extra project files. For the word cloud visualization, we will need to install two specific D3 modules: Run the following command to install these D3 modules and their type definitions in the Svelte project. First, delete the src/lib directory and src/app.css file. Then, in src/main.ts , omit the import './app.css' statement at the top of the file. In the src/App.svelte file, clear out the contents of the script, style and markup sections. Within the script section, let’s add the import statement for the <WordCloud /> component and a variable named lyrics that’s set to the lyrics of the song Vampire, like so: ( src/App.svelte ) Within the style section, let’s add some minor styles to horizontally center the <WordCloud /> component in the <main /> element. ( src/App.svelte ) Note : Styles defined in the <App /> component won’t leak into other Svelte components. Within the <main /> element of the markup section, call the <WordCloud /> component. Also, pass lyrics to the text prop of the <WordCloud /> component, like so: ( src/App.svelte ) Within the src directory, create a new folder named components . This folder will contain any reusable components used in this Svelte application. In this case, there will only be one component in this directory: WordCloud.svelte . Create this file inside of the src/components directory. Within the src/components/WordCloud.svelte file, begin with an empty script section for the <WordCloud /> component: ( src/components/WordCloud.svelte ) At the top of the script section, import d3Cloud from the d3-cloud module. d3Cloud instantiates a new cloud layout instance, and it comes with chainable methods for configuring: Additionally, import three methods from the d3-array module: ( src/components/WordCloud.svelte ) Then, declare the text prop that the <WordCloud /> component currently accepts. Set its default value to an empty string if no value is passed to the text prop. ( src/components/WordCloud.svelte ) d3Cloud comes with a chainable method called .words() . This method accepts the words and their frequencies as an array of objects with two properties: To turn the string of text into an array of objects with these properties, we’ll need to: Add these lines of code to the script section of the <WordCloud /> component, like so: ( src/components/WordCloud.svelte ) Like with any D3 data visualization, you need to define its dimensions. The dimensions consist of: In the <WordCloud /> component’s markup section, add an <svg /> element and set its width , height and viewBox using the values from dimensions . Since the words will be displayed using the Helvetica font family, let’s set font-family to “Helvetica.” Note : text-anchor="middle" aligns the middle of the text to the text’s position. This is important since the layout algorithm determines positions using the middle of the text as the reference. By default, the start of the text gets aligned to the text’s position. Next, define a wordPadding variable that specifies the numerical padding to apply to each word in the word cloud. Since d3-cloud internally uses an HTML5 <canvas /> element to simulate the layout algorithm, this padding (in pixels) gets multiplied by 2, and this product gets set to the lineWidth property of the canvas’s drawing context. For now, we’ll set wordPadding to 2. Add these lines of code to the script section of the <WordCloud /> component, like so: ( src/components/WordCloud.svelte ) With all of the necessary variables set, let’s call d3Cloud() and configure it using the following chainable methods: Anytime a word is successfully placed in the canvas that’s used to simulate the layout algorithm, push an object with the calculated font size ( size ), coordinates ( x and y ), rotation ( rotate ) and the word itself to an array named cloudWords . Once everything is set up, call the .start() method on cloud to run the layout algorithm. However, remember that Svelte’s reactivity only gets triggered on assignments. Since the .push() method mutates the array, we cannot use cloudWords to render the list of words in the markup section of the <WordCloud /> component. Therefore, once the layout algorithm finishes running, assign cloudWords to words . Then, within the <svg /> element in the markup section of the <WordCloud /> component, use an each block to loop over the list of words and render a list of <text /> elements inside of a <g /> element (for grouping the <text /> elements), like so: Add these lines of code to the script section of the <WordCloud /> component, like so: ( src/components/WordCloud.svelte ) When you run the project in development via npm run dev , you should see a word cloud that looks like the following: Currently, the size of a word communicates its frequency in a block of text. The larger the word, the more frequent the word appears in the block of text. However, what if we wanted to also communicate a word’s frequency based on the word’s opacity? For example, the more faded a word is in the word cloud, the less frequent it appears in the block of text. To do this, we’ll need to use the extent() method from the d3-array module to determine the maximum frequency. Then, by dividing a word’s frequency from the maximum frequency, we get decimal values that can be set to the word’s <text /> element’s opacity attribute, like so: Try customizing the word cloud for your own textual data. If you find yourself stuck at any point while working through this tutorial, then feel free to check out the live demo of this project in the following CodeSandbox: If you want to learn more about building visualizations with D3 and Svelte, then check out the Better Data Visualizations with Svelte course by Connor Rothschild, a partner and data visualization engineer at Moksha Data Studio.

Thumbnail Image of Tutorial Building a Word Cloud with D3 and Svelte

Interaction Testing in Storybook

When it comes to visually testing a collection of components, no other tool stands out quite like Storybook . Not only does Storybook provide an isolated environment for developing components and display their different rendered states, but Storybook also integrates with Testing Library to simulate and test component functionality that's triggered by a user interaction. For example, instead of just checking if a form renders correctly, you can also check if behavior like form submission and validation work correctly. This integration with Testing Library allows Storybook to capture all component states, those that can be reproduced solely with props and those that require user interaction to be reached. Conventionally, developers run and receive feedback on tests written in Jest and Testing Library in the CLI. It prints several lines of text that report which assertions ( expect statements) passed and failed. However, it never shows what the component looks like when rendered. With Storybook and a Storybook addon called @storybook/addon-interactions , you get to view and interact with the rendered component in Storybook's Canvas. Plus, you get to see line-by-line reporting of the simulated user interactions and passed/failed assertions in Storybook's Interactions panel. By having everything under one roof this way, you only need to set up decorators like theming and routing just once, and the Storybook GUI trumps over the CLI for debugging UI issues. In v6.4.0 , Storybook introduced browser-compatible wrappers of Testing Library and Jest that power interactive stories. These type of stories automate user interactions using a play function. A new feature to Component Story Format (CSF) 3.0, the play function executes after the component has rendered in Storybook. Using methods from the Testing Library, the play function dispatches events, such as userEvent.click / fireEvent.click for clicking an element, that simulate user interactions in the browser. Below, I'm going to show you how to write interaction tests directly in Storybook. For this tutorial, we will be testing the functionality of a line chart built with React and D3: Specifically, we will test... To get started, clone the following repository: This repository contains a visualization component library with a customizable, multi-line chart written in React, D3 and TypeScript. Inside of the project directory, install the dependencies: Then, run Storybook to view and interact with the multi-line chart component <TimelineChart /> : Currently, there are three stories defined for this component in Storybook: The Default and Legend stories visualize the closing prices of several popular technology stocks from May 2013 to May 2022: Apple (AAPL), Amazon (AMZN), Facebook (FB), Google (GOOGL), Netflix (NFLX) and Tesla (TSLA). Before it renders a story, Storybook fetches the stock data with loaders . Once fetched, the loaded data gets injected into the story via loaded field on the story context, like so: For interaction testing, we must have three addons/integrations installed: Since initializing Storybook for a React project (via the sb init command) automatically installs @storybook/addon-interactions and @storybook/testing-library , all that's left to install is @storybook/jest . Note : The React project must have react and react-dom installed prior to running sb init for the command to choose the correct Storybook project type and install these Storybook addons/integrations. When you open the .storybook/main.ts file, you will find the @storybook/addon-interactions addon already registered. To enable the addon's experimental step debugging features, set interactionsDebugger to true , like so: ( .storybook/main.ts ) Let's write some interaction tests for the <TimelineChart /> component. First, we will write an interaction test for validating the toggling behavior in the chart's legend. We expect the user to be able to toggle lines in the chart by checking/unchecking their corresponding legend keys. Initially, all the lines will be visible in the chart. The play function will simulate a user unchecking all of the legend keys but the first one. By the end, only the line corresponding to the first legend key that's left checked will remain rendered in the chart. The other lines will have been removed. Start by defining a story named ToggledLines . Since the chart requires a legend for toggling, the story will accept the same set of arguments as the Legend story. ( src/stories/TimelineChart.stories.tsx ) Next, let's define a play function on this story, like so: ( src/stories/TimelineChart.stories.tsx ) The play function receives a context object that contains lots of information about the story. For most interaction tests, you will be concerned with only three pieces of information: Now, at the top of the src/stories/TimelineChart.stories.tsx file, import several methods and objects from @storybook/testing-library and @storybook/jest to help simulate user interactions and perform assertions in the play function: ( src/stories/TimelineChart.stories.tsx ) Within the play function... ( src/stories/TimelineChart.stories.tsx ) Challenge : Change the play function so that it asserts that the lines corresponding to all but the first legend key no longer exist within the document. When you visit the ToggledLines story in the Storybook UI, you will notice that Storybook successfully simulated the user interactions and the assertion passed. Here, the canvas displays the component with only one line, the one that corresponds to the first legend key, which we left checked. Let's move on to the next interaction test. For this interaction test, we will validate the behavior of the chart's bisector line and cursor. When the user clicks on a line, the other lines fade out, and the bisector line and cursor appears and attaches to the clicked line. As the user moves their cursor, the bisector's infobox should display the exact value based on the nearest x-axis value. Start by defining a story named SelectedLine . Since multiple lines should fade out when the user clicks on a line, and coloring each line helps the user track the stock that they're interested in, the story will accept the same set of arguments as the Legend story. ( src/stories/TimelineChart.stories.tsx ) Next, let's define a play function on this story, like so: ( src/stories/TimelineChart.stories.tsx ) Since userEvent lacks support for a mousemove event, we need to import fireEvent from @storybook/testing-library . ( src/stories/TimelineChart.stories.tsx ) Within the play function... ( src/stories/TimelineChart.stories.tsx ) When you visit the SelectedLine story in the Storybook UI, you will notice that Storybook successfully simulated the user interactions and the assertions passed. Here, the canvas displays the component with only one line selected, the one that corresponds to the first legend key. A bisector line and cursor is attached to this line and is positioned in the center of the chart's bounded area. On September 1, 2017, the closing price of Apple stock (AAPL) was ~$41.01. What if you wanted to test a scenario in which the user unchecks all but the first legend key, clicks on the line that corresponds to the first legend key and moves their mouse to the center of the chart's bounded area to see what the stock's closing price was at that date within the bisector's infobox? A story that tests this scenario is a combination of the ToggledLines and SelectedLine stories. Rather than duplicating the simulated user interactions and assertions in these stories' play functions, you can re-use these stories' play functions and compose them to simulate complex scenarios like the one just mentioned, like so: ( src/stories/TimelineChart.stories.tsx ) All you need to do is pass the story's play function's context object to both play functions and call them in succession. When you visit the SelectedLine story in the Storybook UI, you will notice that Storybook successfully simulated the user interactions and the assertions passed. Here, the canvas displays exactly what the canvas for the SelectedLine story displayed, but with only one line shown, the one that corresponds to the first legend key. This is the final code for the src/stories/TimelineChart.stories.tsx file. If you find yourself stuck at any point while working through this tutorial, then feel free to visit the main branch of this GitHub repository here for the code. Try migrating your own project's testing suite over to Storybook. If you want to learn more advanced techniques with Storybook, then check out the Storybook for React Apps course by Yann Braga, a senior software engineer at Chromatic and a maintainer of Storybook: As a member of the Storybook team, Yann also worked on many of Storybook's testing-related packages, such as @storybook/addon-interactions , @storybook/test-runner , @storybook/testing-react , @storybook/testing-library  and @storybook/jest .

Thumbnail Image of Tutorial Interaction Testing in Storybook

Writing Custom React Hooks for D3 Visualizations

In late 2018, the React development team introduced hooks into version 16.8 of the React library. Shifting from class components to functional components, hooks provide a cleaner pattern for reusing stateful logic between components without relying on higher-order components (also known as "wrappers"), which add unnecessary levels to the component hierarchy, and render props , which are messy because of the amount of code they add to the JSX returned by the render method. In class components, lifecycle methods and component state are rigidly bound to a particular component. Since class components define each lifecycle method only once, a lifecycle method often contains lots of unrelated logic, such as the componentDidMount lifecycle method setting up different event listeners and fetching data from a remote API endpoint. Performing all of these tasks within a single lifecycle method increases the likelihood of introducing bugs and unintended behavior. However, in functional components, all of this logic can be distributed into separate hooks to share this logic with other functional components. Built-in hooks, such as useState and useEffect , handle component state and side-effects ( useEffect consolidates multiple lifecycle methods into a single hook) respectively and make it easy to extract reusable snippets of code into smaller units of functionality. They serve as the building blocks for composing custom hooks. As a React application grows and becomes more complex to accommodate new features, writing components as functional components and delegating shareable logic to custom hooks allow components to not only be lighter, but also, flexible and maintainable. Below, I'm going to show you how to write custom React hooks. When writing custom hooks, there are some best practices that improve the clarity of these hooks for other developers. By convention, the name of a custom hook follows camel casing and starts with the use prefix. Additionally, the name of the hook should describe its purpose. For example, a hook named useCsv implies a hook that fetches (and processes) CSV data and notifies the component when the data is available for the component's rendering. The signature of a custom hook does not adhere to any strict rules. It can accept any number of arguments of any type and return any number of values of any type. Custom hooks follow all of the same rules as built-in hooks: Whenever a hook is called within a component, it has isolated local state that is independent of other hook calls. If you call the twice in a single component or call it in two different components, then each call yields state values unaffected by other calls. In this tutorial, we will create multiple draggable, resizable widgets, which are common in analytics dashboard. Each widget displays a simple data visualization. To keep the example simple, there will only be two widgets: one displays a bar chart that shows the frequency of a categorical variable (flower species) and one displays a scatterplot that plots two quantitative variables (petal length against petal width). Both of these visualizations consume the Iris flower data set . Try it out in the CodeSandbox demo below: Often, functional components in React applications contain similar pieces of functionality such as subscribing to a window event. In this example, both widgets possess the following characteristics, which all require stateful logic: If we extract these pieces of functionality and refactor them into custom hooks, then we can keep our code DRY and leverage them in future visualization components. With custom hooks, we can cleanly express complex logic. For the characteristics listed above, let's create five custom hooks to encapsulate them: When the user resizes a widget by dragging its bottom-right corner, the dimensions of the widget's visualization scale proportionally based on the new widget's new dimensions. When the height of the widget exceeds its base height, then the visualization's height will increase proportionally while preserving its base aspect ratio. When the height of the widget shrinks below its base height, then the visualization will maintain its base height (and width), and the user would need to scroll within the widget to view the hidden portions of the visualization. The useDimensions hook requires only one argument: baseDimensions . baseDimensions contains the starting dimensions (height, width and margins) of the visualization and the height of the drag bar. By knowing the starting dimensions of the visualization, the hook can calculate its aspect ratio and preserve the visualization's aspect ratio anytime the widget is resized. To listen for the resize event, the useDimensions hook creates a new instance of ResizeObserver within useEffect to observe for changes on a target, which in this case is the widget. Inside of the ResizeObserver callback, the widget is referenced as entry . When resizing the widget, the current width and height of the widget are referenced as entry.contentRect.width and entry.contentRect.height respectively. However, when determining the current dimensions of the visualization, the drag bar height must be subtracted from the widget height to obtain the new visualization height. This height is multiplied by the base aspect ratio to obtain the new visualization width. This hook returns the new visualization dimensions and a ref to set to the resizable element. This element happens to be a <div /> within the <VizResizer /> component, which represents the widget itself. ( src/hooks/useDimensions.js ) When the user clicks on the dark-gray bar within a widget and drags on it, the widget drags with respect to the mouse cursor's position. When the user releases it, the widget remains in the spot it was released at. If multiple widgets happen to overlap one another, then the most recently dragged widget appears above the others. The useDragDrop hook requires one argument: ref . ref contains a reference to the widget. Having this reference allows the hook to update the widget's position by setting its position to absolute and changing its left and top CSS properties. To calculate the widget's position (with respect to its top-left corner): To listen for dragging events, register event listeners on the events mousedown , mousemove , mouseup and dragstart within useEffect to capture the new position of the mouse cursor and change the position of the widget using the above mentioned calculation. When the widget component is unmounted, the hook will unregister these event listeners to avoid memory leaks. The useDragDrop hook returns triggerRef , which the component can set to a reference of the element dedicated to triggering the drag event, which is the dark-gray bar. ( src/hooks/useDragDrop.js ) To create data visualizations, you must provide them with data. d3 provides a csv method from its d3-fetch submodule to fetch CSV data from a remote source and parse it into an array of objects, which each represent a row in the CSV data. While waiting for the data to be fetched, a flag should be set to tell the component that the data is being fetched. Once the data is fetched, the flag should be toggled to tell the component that the data is available and ready to be used for rendering the data visualization. The useCsv hook requires three arguments: url , formatRows , transformResponse . url represents the URL of the CSV data. formatRows is a function that performs a transformation on each row of the CSV data and returns the transformed row. transformResponse is an optional function that performs a transformation on the entirety of the CSV data. This hook initializes the values of the state variables data and isLoading to an empty array and true respectively. Both of these values are returned by the hook to be accessed by the component that calls it. When the hook is first called, the data has not yet been fetched, so to indicate that it is being fetched, isLoading is set to true . Within the useEffect hook, the hook fetches the CSV data. The fetchData memoized function calls the csv method and waits for the data to be received. Once received, this data is processed and the data state variable is set to this data. After the data state variable is set or an error is encountered while fetching the CSV data, set the isLoading state variable to false to indicate that the CSV data has either been fetched or failed to be fetched. When the csv method fails to fetch the CSV data, data remains set to an empty array, so the resulting visualization will be empty. ( src/hooks/useCsv.js ) When integrating D3 into React, it is recommended to use D3 to perform calculations and use React to render the visualizations. When creating an axis, plotting data points at specific coordinates within a scatterplot or determining the height of a bar within a bar chart, we need a scale function that maps values from the data ("domain") to pixel values within the SVG canvas ("range"). For the scale function to extrapolate an appropriate pixel value for a value that does not exist within the data, you must pick a D3 scale function that accurately depicts the relationship between both sets of values. For a scatterplot, continuous quantitative data should be based on a linear scale ( scaleLinear ). For a bar chart, categorical data should be based on an ordinal scale ( scaleOrdinal ). The useScale hook requires only one argument: params . params contains the values needed to create a scale. By default, by only specifying the data , accessorKey and range options, the hook constructs a linear continuous scale, which scatterplots commonly use for plotting data points and generating axes. The accessorKey determines which specific field's value should be extracted from each record of the data to serve as the scale's domain. The range specifies the values the domain should be mapped to. Alternatively, you can customize the scale by specifying a different scale function ( scaleFn ) or explicitly passing a domain ( domain ). To apply rounding and padding to the range of a band scale (used for charts with an ordinal/categorical dimension), specify the isRangeRound flag to add spacing between bands (bars in a bar chart). To avoid recreating the scale whenever an unrelated state/prop value changes in the component calling the useScale hook, memoize the scale with useMemo , and only recreate the scale when the value of the params argument changes. ( src/hooks/useScale.js ) To distinguish different categories in a visualization, map each category to a unique color. For example, the Iris data contains observations for multiple flower species. If the observations are plotted onto a scatterplot, then coloring each point helps to differentiate the observations by flower species. Visually, colors make it easy to identify patterns and clusters. To create a color scale that maps each category to a unique color, use an ordinal scale, which maps a set of discrete values to a set of visual attributes. This scale should accept the flower species as the domain and the colors as the range. Since we already have a custom hook for creating scales, we can call useScale within the useColorScale custom hook to generate this color scale by passing it a unique set of options. The useColorScale hook requires only one argument, but its fields are unpacked within the hook's signature: data , key and colors . key represents the field within a record that contains the categorical value. To obtain a list of categories, extract the categorical value from each record, pass this list of values to a Set to remove duplicates and convert the set back to an array with the Array.from method. These categories will be memoized to avoid recreating them whenever an unrelated state/prop value changes in the component calling the useColorScale hook. This hook not only returns a color scale, but also the list of categories in case you want to render a legend within the visualization. Explore the demo to see how these custom hooks are used in the <BarChart /> and <Scatterplot /> components. Try writing some custom hooks for your React applications!

Thumbnail Image of Tutorial Writing Custom React Hooks for D3 Visualizations