Table of Contents for
Learning D3.js 4 Mapping - Second Edition

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Learning D3.js 4 Mapping - Second Edition by Lars Verspohl Published by Packt Publishing, 2017
  1. Learning D3.js 4 Mapping, Second Edition
  2. Title Page
  3. Second Edition
  4. Copyright
  5. Learning D3.js 4 Mapping
  6. Second Edition
  7. Credits
  8. About the Authors
  9. About the Reviewers
  10. www.PacktPub.com
  11. Why subscribe?
  12. Customer Feedback
  13. Table of Contents
  14. Preface
  15. What this book covers
  16. What you need for this book
  17. Who this book is for
  18. Conventions
  19. Reader feedback
  20. Customer support
  21. Downloading the example code
  22. Downloading the color images of this book 
  23. Errata
  24. Piracy
  25. Questions
  26. Gathering Your Cartography Toolbox
  27. Quick bootstrap
  28. Step-by-step bootstrap
  29. A lightweight web server
  30. Using the web browser as a development tool
  31. Installing the sample code
  32. Working with the developer tools
  33. Summary
  34. Creating Images from Simple Text
  35. The SVG coordinate system
  36. Line
  37. Rectangle
  38. Circle
  39. Polygon
  40. Path
  41. Experiment
  42. Paths with curves
  43. Transform
  44. Translate
  45. Scale
  46. Grouping
  47. Text
  48. Summary
  49. Producing Graphics from Data - the Foundations of D3
  50. Creating basic SVG elements
  51. The enter() function
  52. The update function
  53. The exit() function
  54. AJAX
  55. Summary
  56. Creating a Map
  57. Foundation - creating your basic map
  58. Including the dataset
  59. Experiment 1 – adjusting the bounding box
  60. Experiment 2 – creating choropleths
  61. Experiment 3 – adding click events to our visualization
  62. Experiment 4 – using updates and transitions to enhance our visualization
  63. Experiment 5 – adding points of interest
  64. Experiment 6 – adding visualizations as a point of interest
  65. Summary
  66. Click-Click Boom! Applying Interactivity to Your Map
  67. Events and how they occur
  68. Experiment 1 – hover events and tooltips
  69. Experiment 2 – tooltips with visualizations
  70. Experiment 3 – panning and zooming
  71. Experiment 4 – orthographic projections
  72. Experiment 5 – rotating orthographic projections
  73. Experiment 6 – dragging orthographic projections
  74. Summary
  75. Finding and Working with Geographic Data
  76. Geodata file types
  77. What are shapefiles and how do I get them?
  78. Acquiring shapefiles for a specific country
  79. GeoJSON
  80. A quick map in D3 with only GeoJSON
  81. TopoJSON basics
  82. TopoJSON command-line tips
  83. Preserving specific attributes
  84. Simplification
  85. Merging files
  86. Summary
  87. Testing
  88. Code organization and reusable assets
  89. Project structure
  90. Exploring the code directory
  91. Other administrative files
  92. Writing testable code
  93. Keeping methods/functions small
  94. Preventing side effects
  95. An example with viz.js
  96. Unit testing
  97. Creating resilient visualization code
  98. Adding a new test case
  99. Summary
  100. Drawing with Canvas and D3
  101. Introducing Canvas
  102. Drawing with Canvas
  103. The three drawing steps of every Canvas visual
  104. Drawing various shapes with Canvas
  105. Animating the Canvas
  106. Animating the Canvas way
  107. Getting a general overview
  108. Preparing the rain data
  109. Updating each drop
  110. Drawing frame by frame
  111. Canvas and D3
  112. Getting an overview of our experiment
  113. The data
  114. Updating each drop
  115. Binding the data
  116. Drawing the data
  117. Running the app
  118. Summary
  119. Mapping with Canvas and D3
  120. Choosing Canvas or SVG
  121. Reasons to choose SVG
  122. Reasons to choose Canvas
  123. Visualizing flight paths with Canvas and D3
  124. The data
  125. Building the flight path map in SVG
  126. Measuring the performance
  127. Building the flight path map in Canvas
  128. Setting up the map
  129. Drawing the map and listening for user input
  130. Preparing and drawing with Canvas
  131. Drawing the background scene
  132. Defining the planes
  133. Calculating the plane's positions
  134. Animating the plane
  135. Measuring the performance
  136. Optimizing performance
  137. Continuing with measuring performance
  138. Summary
  139. Adding Interactivity to Your Canvas Map
  140. Why Canvas interaction is different
  141. Drawing the world on a Canvas
  142. Setting up
  143. Drawing the world
  144. Making the world move
  145. Setting up the behavior
  146. Handling zoom and rotation
  147. Finding the Canvas object under the mouse - Picking
  148. Picking, the theory
  149. Creating all things hidden
  150. Drawing the hidden Canvas
  151. Picking the values
  152. Storing more data and using a lookup array
  153. Highlighting the country on mouse over
  154. Visualizing data per country and adding a tooltip
  155. Adding new data to our old globe
  156. Coloring the globe
  157. Adding a tooltip
  158. The HTML
  159. Building the static parts of the tooltip
  160. Showing and hiding the tooltip
  161. Summary
  162. Shaping Maps with Data - Hexbin Maps
  163. Reviewing map visualization techniques
  164. Choropleth maps
  165. Cartograms
  166. Dot density maps
  167. Value and use of the hexagon
  168. Making a hexbin map
  169. Reviewing the hexbin algorithm
  170. Setting it up
  171. Drawing the map
  172. Drawing a point grid for our hexagons
  173. Keeping only the points within the map
  174. Making the hex tile
  175. Retrieving the hexagon center points
  176. Drawing the hex tiles
  177. Joining data points to the layout points
  178. Dressing our data for the final act
  179. Turning our visual into an interactive app
  180. Adding additional information on hover and click
  181. Changing the hexagon size
  182. Changing the color scale interpolator
  183. Browsing different datasets
  184. Encoding data as hexagon size
  185. Summary
  186. Publishing Your Visualization with Github Pages
  187. What we will publish
  188. Understanding the type of content you can publish
  189. Hosting your code on GitHub
  190. Making sense of some key terms and concepts
  191. Tracking historic changes of your files
  192. Collaborating on a project
  193. Working on project branches
  194. Setting up a GitHub account
  195. Creating a repository
  196. Editing a file on GitHub
  197. Uploading files to the repository
  198. Publishing your project on GitHub Pages
  199. Preparing the files for publishing
  200. Keeping your paths absolute
  201. Changing the main HTML filename to index.html
  202. Publishing your project
  203. Summary

Dressing our data for the final act

You have some real data about farmer's markets joined with the hexagons, but you can’t use it yet. All your data is still tucked away in the array of objects per hexagon. Let’s roll this data up.

The measure we want to visualize is the number of farmer's markets in each hexagonal area. Hence, all we need to do is to count the objects that have their datapoint value set to 1. While we’re at it, let’s also remove the layout point objects, that is, the objects with datapoint value 0; we won’t need them anymore.

We will add our task to the ready() function:

function ready(error, us) {
// … previous steps

var hexPointsRolledup = rollupHexPoints(hexPoints);
}

Primarily, rollupHexPoints() will roll up the number of markets per hex point. It will turn the upper hexagon data into the lower hexagon data of the following figure:

The hexagon data before and after roll-up

rollupHexPoints() will perform the following things in an order:

  1. Remove the layout grid points.
  2. Count the number of datapoints and add the count as a new property called datapoints.
  3. Collect key markets data in single array called markets for easy interaction access.
  4. Finally, it will produce a color scale we so dearly need for the hexagon coloring.

Here we go:

function rollupHexPoints(data) {
var maxCount = 0;

We start by initializing a maxCount variable that will later have the maximum number of farmers' markets in a single hexagon. We’ll need this for the color scale.

Next, we’ll loop through all the layout and data points:

  data.forEach(function(el) {

for (var i = el.length - 1; i >= 0; --i) {
if (el[i].datapoint === 0) {
el.splice(i, 1);
}
}

First, we will get rid of all the layout point objects with splice() if the datapoint property holds a 0.

Next, we will create the rolled-up data. There will be two rolled-up data elements: an integer representing the total count of farmers' markets within the hexagon and an array of market data we can use for later interaction. First, we will set up the variables:

    var count = 0,
markets = [];

el.forEach(function(elt) {
count++;
var obj = {};
obj.name = elt.name;
obj.state = elt.state;
obj.city = elt.city;
obj.url = elt.url;
markets.push(obj);
});

el.datapoints = count;
el.markets = markets;

We loop through each object within the hexagon array of objects, and once we’ve collected the data, we add it as keys to the array. This data is now on the same level as the x and y coordinates for the hex points.

Note that we could have taken a shortcut to summarize the count of markets. Our datapoints property just counts the number of elements in the array. This is exactly the same as what the in-built Array.length property does. However, this is a more conscious and descriptive way of doing it without adding much more complexity.

The last thing we do in the loop is to update maxCount if the count value of this particular hexagon is higher than the maxCount value of all previous hexagons we looped through:

    maxCount = Math.max(maxCount, count);

}); // end of loop through hexagons

colorScale = d3.scaleSequential(d3.interpolateViridis)
.domain([maxCount, 1]);

return data;

} // end of rollupHexPoints()

The last thing we do in our roll-up function is to create our colorScale. We’re using the Viridis color scale, which has great properties for visualizing count data. Note that Viridis maps low numbers to purple and high numbers to yellow. However, we want high numbers to be darker (more purple) and low numbers to be lighter (more yellow). We will achieve this by just flipping our domain mapping.

The way scales work internally is that each value we feed from our domain will be normalized to a value between 0 and 1. The first number we set in the array we pass to .domain() will be normalized to 0—that's maxCount or 169 in our case. The second number (1) will be normalized to 1. The output range will also be mapped to the range from 0 to 1, which for Viridis means 0 = purple and 1 = yellow. When we send a value to our scale, it will normalize the value and return the corresponding range value between 0 and 1. Here is what happens when we feed it the number 24:

  1. The scale receives 24 as an input (as in colorScale(24)).
  2. According to the .domain() input ([max, min] rather than [min, max]), the scale normalizes 24 to 0.84.
  3. Next, the scale queries the Viridis interpolator about which color corresponds to the value of 0.84 on the Viridis color scale. The interpolator comes back with the color #a2da37, which is a light green. This makes sense, as 0.84 is closer to 1, which represents yellow. Light green is obviously closer to yellow than to dark purple, which is encoded as 0 by the interpolator.

That was is it!

Nearly. The very last thing we have to do is to jump into our drawHexmap() function and change the hexagon coloring to our colorScale:

function drawHexmap(points) {
var hexes = svg.append('g').attr('id', 'hexes')
.selectAll('.hex').data(points)
.enter().append('path')
.attr('class', 'hex')
.attr('transform', function(d) {
return 'translate(' + d.x + ', ' + d.y +')';
})
.attr('d', hexbin.hexagon())
.style('fill', function(d) {
return d.datapoints === 0 ? 'none' : colorScale(d.datapoints);
})
.style('stroke', '#ccc')
.style('stroke-width', 1);
}

If the hexagons don’t cover any markets, their data points property will be 0 and we won’t color it. Otherwise, we pick the appropriate Viridis color.

Here it is:

A very yellow hexbin map

Looks pretty yellow, doesn’t it? The problem is that we have a few outliers in our data. That single dark purple dot on the East Coast is New York, which has significantly more farmers' markets than any other area (169). Washington and Boston are busy as well. However, that makes our visual less interesting. Looking at the distribution of numbers tells us that most hexagons enclose 20 or less markets:

Number of farmers' markets per hexagon

The highest number of markets per hexagon, however, is currently 169. We can do two things here. We can either choose a lower value as our maximum color scale value, say 20. That would only scale our values from 1 to 20 to the Viridis spectrum. All hexagons with higher values would receive the maximum colour (purple) by default.

A more elegant alternative is to use an exponential interpolator for the color scale. Our domain would map not linearly but exponentially to our color output, effectively reaching the end of our color spectrum (purple) with much lower values. To achieve this, we just need a new color scale with a custom interpolator. Let's take a look at the code first:

colorScale = d3.scaleSequential(function(t) {

var tNew = Math.pow(t,10);
return d3.interpolateViridis(tNew)

}).domain([maxCount, 1]);

What exactly are we doing here? Let's reconsider the scaling steps we went through in the preceding code:

  1. The scale receives a number 24 (as in colorScale(24)).
  2. According to the .domain() input ([max, min] rather than [min, max]), the scale normalizes 24 to 0.84. No change for points 1 and 2.
  3. With the old colorScale, we just waved through this linearly normalized value between 1 and 0 without us interfering. Now, we catch it as an argument to a callback. Convention lets us call this t. Now, we can use and transform this however we desire. As we saw previously, many hexagons encircle 1 to 20 markets, very few encircle more. So we want to traverse the majority of the Viridis color space in the lower range of our values so that the color scale encodes the interesting part of our data. How do we do this?
  4. Before we pass t to our color interpolator, we set it to the power of 10. We can use a different exponent, but 10 works fine. In general, taking the power of a number between 0 and 1 returns a smaller number. The higher the power, the smaller the output will be. Our linear t was 0.84; our exponential tNew equals 0.23.
  5. Finally, we pass tNew to the Viridis interpolator, which spits out the respective—much darkercolor.

Let's graph this transformation to clarify:

Linear versus exponential color interpolation

The x axis shows the input values, the y axis shows our scale-normalized value t that we send to the interpolator to retrieve a corresponding color. The left graph shows what a linear interpolation does. It linearly translates the increase of values to the decrease in t. The curve in the right graph shows us how our adjusted tNew behaves after setting t to the power of 10: we enter the lower regions of t (the more purple regions) with much smaller input values. Put differently, we traverse the color space from yellow to purple in a much smaller range of domain values. Piping our example value of 24 through a linear interpolation would return a yellowish green; piping it through our exponential interpolation already returns a purple value from the end of the color spectrum.

The main win this brings is that color differences can be seen where the data is rather than where the gap between the main data cluster and the outlier is. Here is our hexbin map with an exponential scale:

A more interestingly colored hexbin map
View this step in the browser at https://larsvers.github.io/learning-d3-mapping-11-6 the and code example at 11_06.html.

Let’s just revel in our achievement for a moment, but are we done? We’re itching to explore this map a little more. After all, people are used to playing with maps, trying to locate themselves in them or move from one area to the other with ease. That’s what we will allow for in our last step.