<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jeff Palmer</title>
    <description>The latest articles on DEV Community by Jeff Palmer (@jeffreypalmer).</description>
    <link>https://dev.to/jeffreypalmer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F501998%2Fe39e57ad-0c20-4530-b13f-bf78cc725964.jpeg</url>
      <title>DEV Community: Jeff Palmer</title>
      <link>https://dev.to/jeffreypalmer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeffreypalmer"/>
    <language>en</language>
    <item>
      <title>Experimental Visualizations of Large Open Source Projects</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Thu, 17 Jun 2021 22:08:02 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/experimental-visualizations-of-large-open-source-projects-3lad</link>
      <guid>https://dev.to/jeffreypalmer/experimental-visualizations-of-large-open-source-projects-3lad</guid>
      <description>&lt;p&gt;Once I completed my &lt;a href="https://git-history.jpalmer.dev"&gt;interactive Git history&lt;/a&gt;, I started to think about a smaller visualization project that would allow me to experiment with new ways of visualizing development histories. I was looking through some photos of naturally occurring phenomena for inspiration, when I came across this image of particles moving in a cloud chamber:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k2U98YDW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/trajectories-in-a-cloud-chamber.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k2U98YDW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/trajectories-in-a-cloud-chamber.png" alt='"Trajectories in a Cloud Chamber" Image from Gordon Fraser/CERN'&gt;&lt;/a&gt;“Trajectories in a Cloud Chamber” Image from &lt;a href="(http://cerncourier.com/cws/article/cern/28742)"&gt;Gordon Fraser/CERN&lt;/a&gt; (&lt;a href="https://creativecommons.org/licenses/by/4.0/"&gt;CC BY 4.0&lt;/a&gt;)&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;There was something about it that sparked an idea in my head: “What if developer changes across the history of a source repository could be represented like this?”&lt;/p&gt;

&lt;p&gt;I started brainstorming possible transformations to the git source repository domain (author, date, number lines added/deleted, etc.) that might end up with a structure that was visually similar to that of particles in a cloud chamber. I considered simulating particles with forces over time, gravitational effects, etc. Nothing was off of the table.&lt;/p&gt;

&lt;p&gt;I eventually decided to start with a basic mapping, to try to get a sense of how that would look. (I can never never really guess how complex visualizations will look without seeing something based on actual data.)&lt;/p&gt;

&lt;p&gt;My basic mapping algorithm looked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a point for every contributor, starting in the center of a two-dimensional space&lt;/li&gt;
&lt;li&gt;For every contributor’s commit (in date order):

&lt;ul&gt;
&lt;li&gt;Move that contributor’s point according to:&lt;/li&gt;
&lt;li&gt;The date of the commit = a direction around a radial timeline, potentially inverting the direction based on timezone offset&lt;/li&gt;
&lt;li&gt;The number of lines changed = distance to move&lt;/li&gt;
&lt;li&gt;Create a dot in this adjusted position&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So basically every commit would “push” a contributor point around in 2d space.&lt;/p&gt;

&lt;p&gt;My guess at the beginning was that I would need to use some type of curve interpolation to make things interesting. I was expecting to need Bézier curves to make things look gracefully organic.&lt;/p&gt;

&lt;p&gt;When I rendered a first version (based on the Git VCS project history), this was the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nFDFoSYF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/cloud-chamber-experiment-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nFDFoSYF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/cloud-chamber-experiment-1.png" alt="Cloud Chamber Experiment One"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was a lot more interesting than I had expected!&lt;/p&gt;

&lt;p&gt;And, as a bonus, it actually seemed to have some of the organic feeling that I associated with the cloud chamber images that got me started. I decided to put the other more complex mapping strategies to the side to see where this simple version would lead.&lt;/p&gt;

&lt;p&gt;The first change that I made was to spread out the origin points for each contributor, based on the date of their first contribution. I decided to lay out these points on a circular timeline, because I’m a sucker for round visualizations.&lt;/p&gt;

&lt;p&gt;I also realized that I needed some way of introducing color, but I wasn’t sure yet how to do that. I started sorting the contributors by number of lines changed, and then used that to apply a linear color scale, just to see what would happen. Surprisingly, it wasn’t terrible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5eJyPkEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/cloud-chamber-experiment-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5eJyPkEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/cloud-chamber-experiment-2.png" alt="Cloud Chamber Experiment Two"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, so at this point the visualizations were looking like something that might actually come out of a cloud chamber, so I decided to add some controls to allow me to tweak the various parameters that shaped the rendering.&lt;/p&gt;

&lt;p&gt;Once I added these controls I realized that I needed to spend some time optimizing my code. The rendering was taking a very long time in-between changes, making iterative refinement a challenge.&lt;/p&gt;

&lt;p&gt;I spent some time looking into what was going on and realized that the quick hacks that I had used to assemble the first version were not scaling to the larger repositories that I was actually working with. I had to do some rethinking of how the render positions were calculated, and I also moved to using HTML canvas to avoid creating hundreds of thousands of DOM elements during SVG rendering.&lt;/p&gt;

&lt;p&gt;I then changed the color mapping to be based on the age of the commit, relative to “now,” so that newer commits were brighter and older commits faded away over the course of the project history, which worked well for animation (see below). With a little more tweaking and experimentation, I started to generate interesting results.&lt;/p&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/kubernetes/kubernetes"&gt;Kubernetes&lt;/a&gt; project history, with my standard render parameters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hIc4AaZQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/kubernetes-radial-static-circular.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hIc4AaZQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/kubernetes-radial-static-circular.png" alt="Kubernetes (2015-2021) - Radial timeline (clockwise) - One rotation per year"&gt;&lt;/a&gt;Kubernetes (2015-2021) - Radial timeline (clockwise) - One rotation per year &lt;/p&gt;

&lt;p&gt;This is the &lt;a href="https://git.kernel.org/pub/scm/git/git.git/"&gt;Git&lt;/a&gt; project repository (one of my favorites), with the entire project shown as a single timeline rotation. The long looping structure on the right side is the project maintainer, &lt;a href="https://www.facesofopensource.com/junio-hamano/"&gt;Junio C Hamano&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ltBmv8KP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/git-radial-project-2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ltBmv8KP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/git-radial-project-2k.png" alt="Git (2005-2021) - Radial timeline (clockwise) - One rotation for entire project duration"&gt;&lt;/a&gt;Git (2005-2021) - Radial timeline (clockwise) - One rotation for entire project duration &lt;/p&gt;

&lt;p&gt;Here’s the &lt;a href="https://github.com/tensorflow/tensorflow"&gt;TensorFlow&lt;/a&gt; project, but with the radial timeline replaced with a linear one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4ASuV465--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/tensorflow-linear-2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4ASuV465--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/tensorflow-linear-2k.png" alt="TensorFlow (2015-2021) - Linear timeline (left to right)"&gt;&lt;/a&gt;TensorFlow (2015-2021) - Linear timeline (left to right) &lt;/p&gt;

&lt;p&gt;By reducing how fast the angle changes based on commit date, you can smooth out the contributor curve which leads to an interesting effect, especially with a repository the size of the &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/"&gt;Linux kernel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3N8dDeFd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/linux-radial-static-20percent-2k%400.5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3N8dDeFd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/06/radial-animated-git-histories/linux-radial-static-20percent-2k%400.5x.png" alt="Linux (2005-2021) - Radial timeline (clockwise) - Flattened annual commit date curves"&gt;&lt;/a&gt;Linux (2005-2021) - Radial timeline (clockwise) - Flattened annual commit date curves &lt;/p&gt;

&lt;p&gt;Finally, I thought it would be interesting to try to animate these, so that it would be possible to see the evolution of project histories over time. I had never created an animation from a canvas rendering before, but I figured that there must be tools out there that would make it easy.&lt;/p&gt;

&lt;p&gt;It turns out that capturing an animation was more work than I expected. 😭&lt;/p&gt;

&lt;p&gt;I started by further optimizing my canvas rendering in order to get silky smooth animations, but then I realized that I wasn’t able to capture 4k video at 60 fps while rendering in realtime. At that point I decided to create the videos frame-by-frame using the &lt;a href="https://github.com/TrevorSundberg/h264-mp4-encoder"&gt;h264-mp4-encoder&lt;/a&gt; library that generates mp4 files directly from a sequence of frames. That actually worked really, really well.&lt;/p&gt;

&lt;p&gt;You can see the final result of these efforts in the following video:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/NJmBpEvN_00"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Thanks for reading! If you have any thoughts or feedback you’d like to share, please feel free to &lt;a href="https://twitter.com/jeffpalmer"&gt;contact me on Twitter&lt;/a&gt; or &lt;a href="https://jpalmer.dev/contact"&gt;via my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dataviz</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>d3js</category>
    </item>
    <item>
      <title>Git: An Interactive Development History</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Tue, 18 May 2021 22:12:16 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/git-an-interactive-development-history-3nmf</link>
      <guid>https://dev.to/jeffreypalmer/git-an-interactive-development-history-3nmf</guid>
      <description>&lt;p&gt;In April I set out to create an &lt;a href="https://git-history.jpalmer.dev"&gt;interactive visualization&lt;/a&gt; of the &lt;a href="https://git-scm.com"&gt;Git distributed version control system&lt;/a&gt;, and I’ve finally reached a point where it’s ready for others to see. Here’s a screenshot of the current version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://git-history.jpalmer.dev"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OurrFLdI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/interactive-git-history-overview.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This became one of my larger projects, so I thought I’d share my experiences building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Git is a &lt;a href="https://en.wikipedia.org/wiki/Distributed_version_control"&gt;distributed version control system&lt;/a&gt; that allows software developers to safely store historical changes and collaborate. Version control is a critical part of software development, and the introduction of Git in 2005 has had a major impact on how teams manage software version history.&lt;/p&gt;

&lt;p&gt;I’ve been around long enough to have developed in the world before Git, and have managed large source repository migrations to it from other tools. While it has its share of warts, like all tools, I’ve always found its abilities to far outmatch any difficult situations I’ve encountered. As a way of saying thanks to the Git community, I wanted to build something to recognize the efforts of the open source contributors that have turned Git into what it is today.&lt;/p&gt;

&lt;p&gt;At the same time, I had been creating static data visualizations from software development data and thought that I should try to create something that was more interactive. I had recently read &lt;a href="https://datasketch.es/"&gt;Data Sketches&lt;/a&gt;, by &lt;a href="https://visualcinnamon.com/"&gt;Nadieh Bremer&lt;/a&gt; and &lt;a href="https://shirleywu.studio/"&gt;Shirley Wu&lt;/a&gt;, and was inspired by the stories behind the creation of their (often complex) interactive visualizations. Their experiences felt very close to mine, but they had pushed to an interactive end product, whereas I had yet to do so.&lt;/p&gt;

&lt;p&gt;So, I decided to expand on &lt;a href="https://jpalmer.dev/2021/03/visualizing-the-change-history-of-the-git-repository/"&gt;my previous exploration of the Git development history&lt;/a&gt; and create a tool that would support the interactive exploration of the history of Git’s development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;p&gt;In the past when I’ve seen visualizations of development histories, they tend to be things like force-directed simulations of module evolution over time, or contributor graphs like those provided by GitHub:&lt;sup id="fnref:1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//github-contributor-graphs.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y4SKmaav--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/github-contributor-graphs_hue5b13663e8a99f1761da6efbc5ddf07d_142227_800x0_resize_box_2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, while these are interesting, they’re not quite what I had in mind. I was hoping to create something that encouraged interactive exploration of the data, providing additional details depending on what was selected.&lt;sup id="fnref:2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;To get things started, even though I didn’t have a concrete idea of what I was going to build, I decided on the following project goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide some type of visualization of the entirety of the development history&lt;/li&gt;
&lt;li&gt;Allow specific developers to be selected to understand their efforts&lt;/li&gt;
&lt;li&gt;Provide interactive point-in-time and cumulative statistics&lt;/li&gt;
&lt;li&gt;Show developer geographic distribution&lt;/li&gt;
&lt;li&gt;Annotate the timeline with historical project milestones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These provided some direction to my overall goal, which was to experiment with alternative ways of visualizing development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual Inspiration
&lt;/h2&gt;

&lt;p&gt;I started by doing an exploration of existing visualizations as a source of inspiration. Here’s a sampling of the many great ones that I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.madefromdata.com/"&gt;Paul Button&lt;/a&gt;’s &lt;a href="https://www.behance.net/gallery/46789913/Items-of-Interest"&gt;Items of Interest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://joshgowen.com/"&gt;Josh Gowen&lt;/a&gt;’s &lt;a href="https://www.behance.net/gallery/34493673/The-Data-Double"&gt;The Data Double&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://observablehq.com/@mbostock"&gt;Mike Bostock&lt;/a&gt;’s &lt;a href="https://observablehq.com/@mbostock/2018-box-office"&gt;2018 Box Office&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://susielu.com/"&gt;Susie Lu&lt;/a&gt;’s &lt;a href="https://susielu.com/data-viz/box-office"&gt;Strong Openers and Late Bloomers&lt;/a&gt; remake of the NYT original&lt;/li&gt;
&lt;li&gt;Many, many of the works from &lt;a href="//https:/pudding.cool/"&gt;The Pudding&lt;/a&gt; when it comes to telling a story with data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these ideas in my head I started gathering data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the Data: Part One
&lt;/h2&gt;

&lt;p&gt;Before I could do anything I needed to convert the Git project commit history into something that I could work with. Getting the Git repository was the easy part, thanks to the very nature of how Git works. Once I had the repository, I then needed author, time, and size data for every change (there were around sixty-two thousand changes in the project history at that time).&lt;/p&gt;

&lt;p&gt;Somewhat surprisingly, there wasn’t an “out-of-the-box” way to export commit history that included the number of files changed, as well as total lines added or removed. You can get use &lt;code&gt;git log&lt;/code&gt; to get something close, but I wasn’t able to find an incantation that provided the detail that I was looking for in an aggregated way. I ended up building a quick ruby script to parse the output of &lt;code&gt;git log&lt;/code&gt;, aggregating file change details so that for each commit I’d have total files changed, lines added and removed, in addition to the other commit data like author, date, tag, etc.&lt;/p&gt;

&lt;p&gt;I also looked into using &lt;code&gt;git annotate&lt;/code&gt; on the entire repository to understand the historical ‘legacy’ of various lines of code, but that turned out to be cost prohibitive to run for every commit. I decided to generate this information for the start of every year as a starting point to understand if there was anything actually interesting there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring the Data
&lt;/h2&gt;

&lt;p&gt;As I’ve mentioned in the past, my visualization workflow usually starts in &lt;a href="https://observablehq.com/"&gt;Observable&lt;/a&gt;. Their reactive notebooks make data exploration really easy. I generally upload the data, parse and transform it as necessary, and then use the &lt;a href="https://vega.github.io/vega-lite-api/"&gt;Vega Lite API&lt;/a&gt; (probably &lt;a href="https://observablehq.com/@observablehq/introducing-observable-plot"&gt;Plot&lt;/a&gt; soon) to interactively graph and explore the dataset.&lt;/p&gt;

&lt;p&gt;I started by trying to understand how contributors and contributions changed over the life of the project. For example, this chart shows net lines of code changed per month by contributor for the top 10% of contributors over the entire project:&lt;/p&gt;

&lt;p&gt;&lt;a href="//observable-git-loc-changed.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9vr1tlEG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/observable-git-loc-changed_hu2474c60b8a3522eef03762206ce9eeaa_167919_800x0_resize_box_2.png" alt="Top 10% of contributors"&gt;Top 10% of contributors &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sorting is sort of arbitrary, but you can see Linus Torvalds (he created the very first version of Git) at the top, with Junio C Hamano next (the project maintainer - he’s been very, very busy!). You can also see that there’s been a pretty consistent stream of new major contributors over the entire life of the project; active development has never really slowed down that much.&lt;/p&gt;

&lt;p&gt;Next I thought I’d created a stacked area chart showing contributions over time:&lt;/p&gt;

&lt;p&gt;&lt;a href="//git-author-contributions.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y9tlp36M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/git-author-contributions.png" alt="Early stacked area experiment"&gt;Early stacked area experiment &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was interesting. You can see overall periods of activity increase, and how individual contributors participated over time. It was during this investigation that I came across the notion of a &lt;a href="http://leebyron.com/streamgraph/"&gt;streamgraph&lt;/a&gt;, which seemed to present the data similarly while using some additional logic to improve the aesthetics of the final visual.&lt;/p&gt;

&lt;p&gt;The problem with both the stacked area chart and the streamgraph is that they make comparing quantities difficult, but for this visualization my goal wasn’t to be able to accurately discern who had more changes in a single month - I was interested in presenting the project history in a way that encouraged interaction. The streamgraph felt like it might support that goal, so that’s what I decided to use as the main history navigation element.&lt;/p&gt;

&lt;p&gt;Finally, I took a look at how code “ownership” had changed across the entire repository based on the yearly &lt;code&gt;git annotate&lt;/code&gt; output.&lt;/p&gt;

&lt;p&gt;&lt;a href="//contributor-legacy.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_jE6b1kO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/contributor-legacy.png" alt="Git contributor &amp;amp;lsquo;legacy&amp;amp;rsquo;"&gt;Git contributor ‘legacy’ &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While that was interesting, it wasn’t really that surprising. It seemed natural that ownership would dilute over time as more people participated. I decided to shelve that data for another day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Imagining the Layout
&lt;/h2&gt;

&lt;p&gt;At this point I started sketching (yes, on paper) what the overall layout might look like. In my mind I knew that I wanted a static sidebar and a scrollable timeline that would provide a way to select project time periods. Statistics about the currently selected person or time period would be in the sidebar, and I was hoping to also have a globe or map that showed the distribution of developers for a selected period of time. (I had never created any type of &lt;a href="https://en.wikipedia.org/wiki/Choropleth_map"&gt;choropleth&lt;/a&gt;, so I was interested in giving that a try, if possible.)&lt;/p&gt;

&lt;p&gt;I was also hoping to have a way to see zoomed-in views of the timeline so that I could highlight the first several weeks, etc. I didn’t have any idea how to lay that out, so I did some exploration of &lt;a href="https://pudding.cool/process/how-to-implement-scrollytelling/"&gt;scrollytelling&lt;/a&gt; to see if that might work. Ultimately I decided not to go down that path, as the scrollytelling approach was so completely foreign to me I had no idea how to fit it into the interaction model I had in my head. I will definitely return to it, however, when I have a more focused story to tell, as I think it can be really compelling.&lt;/p&gt;

&lt;p&gt;Finally, I began to consider using circles to represent total contributions for a contributor. I had the idea of animating contributor circles as various time periods were selected, perhaps with a force directed simulation. I had no idea whether or not that would work/be a good idea, but since I was brainstorming at that point it all went on the list.&lt;/p&gt;

&lt;p&gt;I came up with a rough three column layout, with the leftmost column for statistics, the contributor circles in the center, and finally the right column dedicated to a scrolling timeline streamgraph that would also somehow support annotations. With that loose structure, I was ready to start.&lt;/p&gt;

&lt;p&gt;Let me add one caveat to all of this - I am not an experienced frontend developer. Development of modern browser-based applications, especially regarding how to make a visualization responsive and accessible, is a real challenge. I lean very heavily on the tools that I use to help me with these areas, to the extent that they are able. I am also not a visual designer, which further complicates projects like this. All that is to say that if you notice anything that could be improved, please let me know. I am very open to feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools and Libraries
&lt;/h2&gt;

&lt;p&gt;I have been a fan of &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt; for a while now, and I’ve also been experimenting with &lt;a href="https://tailwindcss.com/"&gt;TailwindCSS&lt;/a&gt;, although when using Svelte the benefits aren’t as obvious. I ended up using it mostly for the structure it provided regarding font size consistency, spacing, etc. I also decided to try experimenting with &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; once the SvelteKit project moved over to it. I had been using Snowpack, but after a quick experiment and learning that it still used Rollup under the hood, I decided to try it.&lt;/p&gt;

&lt;p&gt;In addition to the frontend tech used to structure and build the site, the visualization was all done in (unsurprisingly?) &lt;a href="https://d3js.org/"&gt;D3.js&lt;/a&gt;,&lt;sup id="fnref:3"&gt;3&lt;/sup&gt; while all of the data transformation was done in &lt;a href="https://uwdata.github.io/arquero/"&gt;Arquero&lt;/a&gt;. Arequero is amazingly performant — I went into this expecting to have to pre-aggregate all of the data, but Arquero was fast enough that I haven’t yet needed to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the Data: Part Two
&lt;/h2&gt;

&lt;p&gt;As I started building the site, I realized that I wanted to do two things to further cleanse/prepare the data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove all email addresses from the source data set and replace them with unique ids.&lt;/li&gt;
&lt;li&gt;Retrieve GitHub account information for contributor avatars and locations so that I could create a high-level contributor map.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The unique id was required to be able to unambiguously refer to contributor data once everything was loaded.&lt;/p&gt;

&lt;p&gt;I wanted to remove the email address information because that just seemed like the right thing to do, even though that information is available directly in the Git repository.&lt;/p&gt;

&lt;p&gt;Getting useful GitHub account information was a little more of a stretch. First of all, GitHub account information isn’t available when you &lt;a href="https://docs.github.com/en/rest/reference/git#get-a-commit"&gt;retrieve commit data via the GitHub REST API&lt;/a&gt;, so you have to find some other means of getting it. I ultimately realized that GitHub provides detailed information for the &lt;a href="https://docs.github.com/en/rest/reference/repos#list-repository-contributors"&gt;top 500 contributors to a project&lt;/a&gt;, which seemed like a decent starting point since I wasn’t planning to show all contributor data (for performance and legibility reasons).&lt;/p&gt;

&lt;p&gt;Ha ha, joke’s on me. Turns out that the GitHub account information is completely divorced from the reality of the Git repository emails, etc. This makes sense, as the Git repository history represents fixed information from a point in time (email address, name, etc.), whereas GitHub is a service that people keep up-to-date. As a result, there’s no “easy” way to join these data sets, and I was forced to join them by hand using commit counts, email matching, etc. I only tried to match up the first 250 contributors, but even so, that was not a fun afternoon.&lt;/p&gt;

&lt;p&gt;Once that was done, I realized that out of almost 2,000 contributors, I could get location information for &lt;em&gt;maybe&lt;/em&gt; 5%; almost no one had any location data in their GitHub profiles. Well, that was that — the idea of creating a map of worldwide contributor distribution was a non-starter. At least I could show contributor avatars for my trouble.&lt;/p&gt;

&lt;p&gt;To minimize data duplication, I wrote a &lt;code&gt;node&lt;/code&gt; script that used Arquero to load both of these data sets and join them, generate unique ids, and then write them back out as normalized data that would be re-joined in memory at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I bumped into several issues while putting everything together. Most of the visualizations were pretty straightforward to create, but the two that caused the most grief were the streamgraph and the contributor circles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streamgraph
&lt;/h3&gt;

&lt;p&gt;The streamgraph consists of two main parts: the visualization of contributor activity, and the timeline annotations.&lt;/p&gt;

&lt;p&gt;Getting the data into the streamgraph wasn’t so bad (after browsing some Observable examples), but I quickly realized that it wouldn’t be possible to represent all contributors as separate curves without bringing the browser to its knees.&lt;/p&gt;

&lt;p&gt;I decided to segment the contributors into two groups: the top 20% of contributors by commit volume, and everyone else. The top contributors would be represented directly, while the remaining contributors would be aggregated and displayed as a single “Other Contributors” band. This is unfortunate, but at the time I couldn’t think of another way to address the issue. It’s possible that I might be able to make things more performant by using Canvas, but that wouldn’t change the fact that the small 80% contributor band would be comprised of about 1,600 contributors. That seems like an unmanageable number of details to put into a small space without some type of zoom-based interaction.&lt;/p&gt;

&lt;p&gt;The timeline annotations were also a challenge because it’s possible for two milestones to overlap, and I don’t have any logic for handling that. One possibility is to aggregate milestones when they overlap, but I haven’t yet had a chance to try that out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contributor Circles
&lt;/h3&gt;

&lt;p&gt;During my research into &lt;a href="https://github.com/d3/d3-force"&gt;force simulations&lt;/a&gt; in D3, I came across the idea of configuring the simulation such that a circle is attracted to a certain point on a timeline, but is free to move in the other dimension. I thought that I’d try to anchor the contributor circle to the date of the contributor’s first contribution, and the allow the simulation to move contributor around until things stabilized. I’d also increase the size of the circle to represent cumulative lines of code changed by contributor, so that the farther in the timeline you went, the more that a contributor’s circle would expand to represent overall project contributions.&lt;/p&gt;

&lt;p&gt;I did try animating the circles when time period changed, but for some reason I wasn’t able to get them to settle down into a stable configuration, so I went with the approach of running the simulation without animation and simply showing resulting positions after 500 iterations. That seems to work okay, but I’d like to revisit the animation at some time in the future, even though my gut tells me it’d probably be very distracting.&lt;/p&gt;

&lt;p&gt;The other issue I faced with was putting labels on the larger contributor circles. For a while I toyed with the idea of centering the name in the circle, but because SVG &lt;code&gt;text&lt;/code&gt; elements don’t wrap, getting them to look reasonable was difficult. I ended up using SVG &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath"&gt;&lt;code&gt;textPath&lt;/code&gt;&lt;/a&gt; to curve names around the bottom of the circle (but had to avoid the &lt;code&gt;path&lt;/code&gt; part of the spec since it wasn’t supported by Chrome, Edge, and Safari).&lt;/p&gt;

&lt;h3&gt;
  
  
  Color Palettes
&lt;/h3&gt;

&lt;p&gt;I ended up creating several color palettes during development, which was perhaps one of the most difficult parts of this entire project. When I started, the color palette was based on D3’s &lt;code&gt;interpolatePurples&lt;/code&gt;, which was fine for prototyping but wasn’t anything I was going to be able to keep:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FvV-wK1E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/repo-viz-experiment.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FvV-wK1E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/repo-viz-experiment.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After many, many iterations I landed on a light and dark mode palette that didn’t seem terrible. You can see how they turned out below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HSz8i1LF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/light-dark-mode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HSz8i1LF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/05/interactive-git-history/light-dark-mode.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile
&lt;/h3&gt;

&lt;p&gt;Once all of the layout and charting was completed, I decided to see how things looked on a mobile device. Not surprisingly they weren’t great. I had designed an interactive visualization that was meant for a desktop style of interaction, a fact that was obvious if you tried to use a mobile device to do anything. I tried to add some code to handle smaller resolutions - one of the biggest challenges was getting the SVG elements and HTML font sizes to align depending on the size/aspect ratio of the viewport. That wasn’t great, so I ended up putting a warning on the site if you visit from a small-ish device resolution, but with the ability to proceed after the disclaimer. I would like to revisit how to redesign to support mobile in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Left?
&lt;/h2&gt;

&lt;p&gt;Even though I had some issues during development, I am generally happy with how this initial version turned out. I’d like to be able to solve the timeline annotation spacing issue, and I think I have an approach to do so in my head.&lt;/p&gt;

&lt;p&gt;What I’d really like to add, however, is a more comprehensive set of project milestones. I’d really like this to be useful as a way to understand important aspects of Git’s development history, but at this point I haven’t been able to capture a lot of milestones outside of the ones that can be directly derived from the data.&lt;/p&gt;

&lt;p&gt;With that in mind, if you have any interesting information on the history of the Git project that you believe would make an interesting milestone, please don’t hesitate to contact me and I will happily consider adding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks!
&lt;/h2&gt;

&lt;p&gt;…for taking the time to read a little about this project. If you have any thoughts or feedback you’d like to share, please feel free to &lt;a href="https://twitter.com/jeffpalmer"&gt;contact me on Twitter&lt;/a&gt; or &lt;a href="https://jpalmer.dev/contact"&gt;directly on this site&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Which appear to be incorrect, now that I’m actually &lt;strong&gt;looking&lt;/strong&gt; at them. 😬 Junio C Hamano is the third Git contributor, but in this chart doesn’t have any recognized contributions until some time after 2007-ish. I assume this is due to the mismatch between GitHub accounts and the identities in the Git commit history, which I’ll cover in more detail later. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I find the GitHub visual presentation of historical data to be particularly lacking, as it immediately segments the data in a way that makes it hard to understand as a whole, while providing almost no tools for further investigation. Honestly this seems like a missed opportunity for them, but I am obsessed with this stuff so maybe I’m the outlier. They have &lt;em&gt;so much&lt;/em&gt; historical development data available to them it would seem like an interesting path to explore. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;As an aside, I’ve seen some online opinions stating that D3 is not a good choice for visualization in 2021, and while I do agree that if you’re putting basic charts on a page it’s probably overkill, for anything of any complexity it’s really an amazing toolkit. The key is to treat it as such. Amelia Wattenberger and Russell Goldenberg cover this well in their recent Svelte Summit talk: &lt;a href="https://www.youtube.com/watch?v=fnr9XWvjJHw&amp;amp;t=4806s"&gt;Declarative Data Visualization: Creating a bar chart race&lt;/a&gt;. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>git</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Visualizing Open Source Project Commit Histories</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Fri, 02 Apr 2021 03:05:59 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/visualizing-open-source-project-commit-histories-3da2</link>
      <guid>https://dev.to/jeffreypalmer/visualizing-open-source-project-commit-histories-3da2</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Recently, I came across this excellent interview with the graphic designer Peter Saville describing the origin of Joy Division’s famous cover for &lt;em&gt;Unknown Pleasures&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/reEQye0EOAw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I hadn’t realized that the image was &lt;a href="https://blogs.scientificamerican.com/sa-visual/pop-culture-pulsar-origin-story-of-joy-division-s-unknown-pleasures-album-cover-video/"&gt;a scientific visualization&lt;/a&gt; of the data from a pulsar (actually data from &lt;a href="https://en.wikipedia.org/wiki/PSR_B1919%2B21"&gt;the first discovered pulsar PSR B1919+21&lt;/a&gt;), but now that I know the history I somehow find it even more compelling.&lt;/p&gt;

&lt;p&gt;I decided to recreate the visual form of the image, but needed an appropriate data source. Since I’d been experimenting with the visualization of open source commit histories recently, I thought I’d start with that data and see how things looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach
&lt;/h2&gt;

&lt;p&gt;One of the challenges with data visualization (at least for me) is dealing with the mismatches that inevitably happen between the image I had in my mind and the one that the data actually creates. I guess that’s pretty common, as both &lt;a href="https://www.visualcinnamon.com/"&gt;Nadieh Bremer&lt;/a&gt; and &lt;a href="https://shirleywu.studio/"&gt;Shirley Wu&lt;/a&gt; both mention it in &lt;a href="https://www.datasketch.es/"&gt;Data Sketches&lt;/a&gt;. (The Data Sketches book is excellent, by the way. If you’re interested in data visualization you should &lt;strong&gt;definitely&lt;/strong&gt; get it.)&lt;/p&gt;

&lt;p&gt;Anyway, my intuition was that, if you aggregated the changes made over the history of the project, there would be some interesting patterns that would fit the pseudo-3D layout of the original image. So I set about to pull some data from various projects to see if that was true.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exploring the Data
&lt;/h3&gt;

&lt;p&gt;I usually start a new visualization project by putting a dataset into &lt;a href="https://observablehq.com/"&gt;Observable&lt;/a&gt; and then interactively experimenting with various visualizations to better understand the shape of the data. Because this is exploratory visualization, I want to be able to quickly create many different types of visualizations to open/close exploration options. I use &lt;a href="https://vega.github.io/vega-lite/"&gt;Vega Lite&lt;/a&gt; (specifically the &lt;a href="https://vega.github.io/vega-lite-api/"&gt;Vega Lite API JS wrapper&lt;/a&gt;) to do this. Take a look at &lt;a href="https://www.youtube.com/watch?v=L_5vavklnVI"&gt;this introduction&lt;/a&gt; if you’re interested in seeing how the combination of Observable and Vega Lite can really accelerate data exploration.&lt;/p&gt;

&lt;p&gt;I first exported the git commit history from the Visual Studio Code repository, taking care &lt;strong&gt;not&lt;/strong&gt; to normalize the commit times. I wanted the author’s local commit times so that they could be aggregated in a way that represented an author’s day, not the project-wide perspective. I was also curious as to whether there might be interesting information in the timezone offsets, so I exported the author commit time and parsed it by hand into these various components.&lt;/p&gt;

&lt;p&gt;I also needed to aggregate the commits in some way that made it possible to layer timespans on the y-axis. I started by counting the number of commits (circle radius) in each minute of the day (x-axis), and then combining the year and month as the y-axis. This looked promising:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oIH9LW-J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/observable-vscode-commit-data-wrangling.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oIH9LW-J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/observable-vscode-commit-data-wrangling.png" alt="Initial visualization of VS Code commits"&gt;&lt;/a&gt;Initial visualization of VS Code commits &lt;/p&gt;

&lt;p&gt;That is an amazingly consistent lunch hour! 😆&lt;/p&gt;

&lt;p&gt;Unfortunately, once I put this data into d3 to actually create the visualization, the results were not very compelling:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T73U-IyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/vscode-commits-poorly-aggregated.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T73U-IyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/vscode-commits-poorly-aggregated.png" alt="That&amp;amp;rsquo;s waaay too much detail!"&gt;&lt;/a&gt;That’s waaay too much detail! &lt;/p&gt;

&lt;p&gt;Even though I could see that there was a clear pattern to the data, aggregating by minute obscured it by providing too much detail. I experimented with a variety of aggregation windows until I landed on 30 minutes, which seemed to best capture the pattern that I saw in Observable while not smoothing things out to such an extent that it was no longer visually interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;A couple of titles later and I had the final version. Each line represents a full month of commits, broken into 30 minute bins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zltTtk1N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/vscode-hourly-commits-pulsar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zltTtk1N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/vscode-hourly-commits-pulsar.png" alt="Visual Studio Code - Monthly Commits by Hour"&gt;&lt;/a&gt;Visual Studio Code - Monthly Commits by Hour &lt;/p&gt;

&lt;p&gt;I thought that the clear separation between morning and afternoon was pretty interesting, and I was honestly surprised at the consistency over time. I realized that the reason for this was that, even though Visual Studio Code is an open source project, it’s primarily a Microsoft-driven effort.&lt;/p&gt;

&lt;p&gt;I decided to run the commit history of some other open source projects through the same pipeline, unmodified, to see how they compared.&lt;/p&gt;

&lt;h3&gt;
  
  
  React
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aTyXXLKl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/react-hourly-commits-pulsar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aTyXXLKl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/react-hourly-commits-pulsar.png" alt="React - Monthly Commits by Hour"&gt;&lt;/a&gt;React - Monthly Commits by Hour &lt;/p&gt;

&lt;p&gt;I started with &lt;a href="https://github.com/facebook/react"&gt;React&lt;/a&gt;. React’s repository has 14,010 commits over its eight year commit history, compared to Visual Studio’s 79,457 (as of this writing).&lt;/p&gt;

&lt;p&gt;I thought it was interesting to see how React seems to have had a period of time where contributions leveled off, and then increased dramatically again for another several months.&lt;sup id="fnref:1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The React team also seems to have a few more night owls working on it, with the far left side showing pretty regular post-midnight contributions.&lt;/p&gt;

&lt;h3&gt;
  
  
  TensorFlow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HlpOrbPd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/tensorflow-hourly-commits-pulsar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HlpOrbPd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/tensorflow-hourly-commits-pulsar.png" alt="TensorFlow - Monthly Commits by Hour"&gt;&lt;/a&gt;TensorFlow - Monthly Commits by Hour &lt;/p&gt;

&lt;p&gt;Then I decided to take a look at Google’s &lt;a href="https://github.com/tensorflow/tensorflow"&gt;TensorFlow&lt;/a&gt;. 108,084 commits, starting in November of 2015.&lt;/p&gt;

&lt;p&gt;The distribution here seems closer to that of Visual Studio, although without the very distinct morning/afternoon separation.&lt;/p&gt;

&lt;p&gt;I’d also guess that there’s something automated happening in the early morning. You can see it shift by an hour forward and backward every few months. Everyone’s favorite villain, daylight saving time. 😭&lt;/p&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZvI2UiKO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/git-hourly-commits-pulsar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZvI2UiKO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/04/commit-history-pulsar-visualizations/git-hourly-commits-pulsar.png" alt="Git - Monthly Commits by Hour"&gt;&lt;/a&gt;Git - Monthly Commits by Hour &lt;/p&gt;

&lt;p&gt;Finally I pulled in the &lt;a href="https://github.com/git/git"&gt;Git&lt;/a&gt; commit history, which I had just been digging through for my recent &lt;a href="https://jpalmer.dev/2021/03/visualizing-the-change-history-of-the-git-repository/"&gt;Git commit history visualization&lt;/a&gt;. 62,574 commits, starting in April of 2005.&lt;/p&gt;

&lt;p&gt;What I like about this is that it’s clear that the project has gone through some big changes over time. At the beginning, there was a lot of late night hacking happening, and the contributions seem pretty consistent over that time period.&lt;/p&gt;

&lt;p&gt;There’s a major change in contributions, however, starting about one third into the project history. You can see the number of contributions increase dramatically compared to the first third, and stay that way.&lt;/p&gt;

&lt;p&gt;It also seems not to have such a clear “company project” vibe, as the contribution times are all over the place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For the small-ish amount of work that I put into this, I found the results to be pretty interesting, and I’m glad I did it. Being able to compare various projects against one another was something that I hadn’t planned to do, but once the tools had been created, that was effectively free.&lt;/p&gt;

&lt;p&gt;I hope you found these interesting. As always, if you have any feedback please let me know on &lt;a href="https://twitter.com/jeffpalmer"&gt;Twitter&lt;/a&gt; or feel free to &lt;a href="https://jpalmer.dev/contact"&gt;contact me here&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;If I wasn’t creating a stylized visualization, I think it’d be helpful to have a margin histogram for each month showing the total contributions for that month. At the moment the aggregate contributions for the month are hard to see. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dataviz</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>d3js</category>
    </item>
    <item>
      <title>Prioritizing with Cost of Delay</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Tue, 30 Mar 2021 21:52:06 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/prioritizing-with-cost-of-delay-511b</link>
      <guid>https://dev.to/jeffreypalmer/prioritizing-with-cost-of-delay-511b</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x31Nybdo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/prioritizing-with-cost-of-delay/burning-money.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x31Nybdo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/prioritizing-with-cost-of-delay/burning-money.jpg" alt=""&gt;&lt;/a&gt;&lt;a href="https://unsplash.com/@jpvalery"&gt;Photo by Jp Valery&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can’t begin to count the number of times I have heard development teams say things like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“We don’t have time to fix that.”&lt;/li&gt;
&lt;li&gt;“Product won’t let us fix our technical debt.”&lt;/li&gt;
&lt;li&gt;“All we do is build new features.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which I mentally translate to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We don’t know how to explain the impact of our technical projects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is an attempt to explain how quantifying &lt;strong&gt;Cost of Delay&lt;/strong&gt; can help teams successfully understand and prioritize their technical project backlog.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Prioritization Problem
&lt;/h2&gt;

&lt;p&gt;Prioritization is key to ensuring that a company is investing in efforts that will sustainably grow the business. Product teams spend a tremendous amount of time working to understand and quantify project impacts so that financial benefits are maximized (or at least they should!).&lt;/p&gt;

&lt;p&gt;However, it’s not always the case that there is a corresponding understanding of the technical backlog — infrastructure projects, security upgrades, etc. Depending on the organizational structure, these projects may be in separate tools, spreadsheets, or simply in people’s heads. Even if there is a central technical project list, project benefits are often described in technical terms and may require in-depth system knowledge to appreciate. These inconsistencies make prioritizing technical projects &lt;strong&gt;within&lt;/strong&gt; development a real challenge.&lt;/p&gt;

&lt;p&gt;Outside of development, this lack of understanding becomes a bigger problem. When the Product team has already quantified the financial impacts of six months of feature development, hand-wavy technical project lists aren’t going to cut it.&lt;/p&gt;

&lt;p&gt;With that in mind the question becomes: How can the impacts of technical projects be quantified such that they can be directly compared to other projects?&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Cost of Delay
&lt;/h2&gt;

&lt;p&gt;The term “Cost of Delay” captures the idea that you can quantify the financial impact of delaying work. It is measured in &lt;em&gt;dollars / time unit&lt;/em&gt;, and should represent the total financial impact to the organization if a project is delayed for a single &lt;em&gt;time unit&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I first came across the concept in the writings of Donald Reinertsen, shown here describing how he came to recommend quantifying the Cost of Delay to his customers.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/du2WV1IbULU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Cost of Delay?
&lt;/h3&gt;

&lt;p&gt;I have found Cost of Delay to be an excellent means of prioritizing projects because it provides a consistent way to compare the impact of any type of project. Since Cost of Delay is measured in &lt;em&gt;dollars / time unit&lt;/em&gt;, there’s no ambiguity when comparing impacts. By quantifying Cost of Delay it’s possible to have a much more focused conversation about technical initiatives, and these can then be compared directly according to financial impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating Cost of Delay for a Project
&lt;/h2&gt;

&lt;p&gt;If you want to calculate the Cost of Delay for a project, you need a model that describes the impact of the project to the organization in financial terms. There are many ways to do this. Projects can reduce operational costs, reduce security risks, generate revenue, eliminate time-wasting development friction, etc. Depending on the needs of the organization, models can be basic so as to provide high-level directional guidance, or detailed to the point that historical operational cost data is included.&lt;/p&gt;

&lt;p&gt;Personally, I prefer to start with a simple model if at all possible. I’ve found that the prioritization conversation that happens across projects has the most impact, so I try to get there as quickly as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Reducing Build Time
&lt;/h3&gt;

&lt;p&gt;Let’s work through an example of how to calculate Cost of Delay for a single project: reducing build times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt; : Say you are part of a 100 person development organization where everyone is working in a monorepo. Build times average about 20 minutes, and people are tired of having to wait for builds during the Pull Request review process. Is it worth spending time to make the build faster?&lt;/p&gt;

&lt;p&gt;Because the goal of this project is to reduce build times, that impact can be modeled in (at least) two areas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reduction in continuous integration provider costs&lt;/li&gt;
&lt;li&gt;Reduction in developer “downtime” (i.e., waiting for builds to complete, context switching, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you make some assumptions about how many PRs each developer creates per week, and how many reviews on average each of those PRs requires, you can create a model that calculates the amount spent on CI infrastructure as well as the cost of having developers wait for builds to complete. (&lt;a href="https://docs.google.com/spreadsheets/d/1g9C10s5EBPN1TtB5JHDYhJV1Qz_Rc6sHXxl9PCIGlSg/edit?usp=sharing"&gt;A spreadsheet containing this model is available here&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Note: the goal of this model is not to perfectly reflect reality, but to represent the project’s impacts given a shared set of organizational assumptions. This consistency will make it possible to compare the impacts of one project to others.&lt;/p&gt;

&lt;p&gt;Based on this model, keeping the same build infrastructure and reducing build times by five minutes would result in a savings of nearly $1500 / week, or about $78,000 annually. Reducing build times by 10 minutes would have a savings of $3000/week, or $156,000 annually.&lt;/p&gt;

&lt;p&gt;Additionally, because this model scales with the number of developers, if the team were to start hiring aggressively (and who isn’t?) the weekly Cost of Delay would increase over time as more engineers join the organization. (You might choose to include this growth in the model, depending on the conversation.)&lt;/p&gt;

&lt;p&gt;Yes, there are valid questions as to the amount of downtime that developers actually incur when waiting for builds to complete, and you could certainly model things differently. To me that’s the beauty of a model with documented assumptions — your team can collaboratively determine which assumptions make the most sense for your organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: Using the Model to Explore Options
&lt;/h3&gt;

&lt;p&gt;Once a model has been created for a project, it can sometimes be used to explore various implementation options. For example, for this project there are at least two options to reduce build times:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Have someone with a detailed understanding of the build process work to manually optimize the build&lt;/li&gt;
&lt;li&gt;Throw hardware at it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since you now have a model that can give you a high level understanding of how these might work out in practice, this can be quite enlightening.&lt;/p&gt;

&lt;p&gt;For example, if it turns out that you can reduce build times by five minutes using a larger instance (say one that is 4 times more expensive), then while your CI provider costs might increase from $288 to $864, the overall cost to the organization would decrease from $6479 to $5628. In that case spending more money might actually save you money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritizing Projects with Cost of Delay
&lt;/h2&gt;

&lt;p&gt;Okay, so once you have the Cost of Delay for a project, how can that be used to prioritize?&lt;/p&gt;

&lt;p&gt;The recommended way to do this is to divide the Cost of Delay by the estimate of how long it will take to do the work. This is often referred to as &lt;a href="https://blackswanfarming.com/cost-of-delay-divided-by-duration/"&gt;&lt;strong&gt;CD3&lt;/strong&gt; ( &lt;strong&gt;C&lt;/strong&gt; ost of &lt;strong&gt;D&lt;/strong&gt; elay &lt;strong&gt;D&lt;/strong&gt; ivided by &lt;strong&gt;D&lt;/strong&gt; uration)&lt;/a&gt;, and using this combination of factors to order projects will optimize the value delivered over that time period. (For a detailed explanation as to why this optimizes value in this fashion,&lt;sup id="fnref:1"&gt;1&lt;/sup&gt; see Reinertsen’s &lt;a href="https://www.amazon.com/Principles-Product-Development-Flow-Generation/dp/1935401009"&gt;The Principles of Product Development Flow&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;For example, here are the CD3 values for several projects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Cost of Delay&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;th&gt;CD3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1a&lt;/td&gt;
&lt;td&gt;Reduce build time by 5 minutes (move from Large to 2X-large)&lt;/td&gt;
&lt;td&gt;$1,714/week&lt;/td&gt;
&lt;td&gt;1/5 week&lt;/td&gt;
&lt;td&gt;8570&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1b&lt;/td&gt;
&lt;td&gt;Reduce build time by 10 minutes (Manually optimize build)&lt;/td&gt;
&lt;td&gt;$2,997/week&lt;/td&gt;
&lt;td&gt;1 week&lt;/td&gt;
&lt;td&gt;2997&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Increase log retention from 7 to 30 days&lt;/td&gt;
&lt;td&gt;$37,860/week&lt;/td&gt;
&lt;td&gt;1 week&lt;/td&gt;
&lt;td&gt;37860&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even with this simple list there are some interesting results.&lt;/p&gt;

&lt;p&gt;Due to the large time impacts missing logs have on problem investigation efforts, the Cost of Delay for increasing log retention is very large relative to the other projects on this list. CD3 says that that project should be done first (if a single team has to choose). Only once that project is completed should the team experiment with other instance types to reduce build times, even though that build time reduction effort is only expected to take a day.&lt;/p&gt;

&lt;p&gt;Is this really the best order to complete these projects? If reducing build times by 5 minutes only takes a day, shouldn’t that get done first? Let’s see.&lt;/p&gt;

&lt;p&gt;If the team tried out new instance sizes first (project 1a), and then increased the log retention (project 2), they would incur the Cost of Delay of the both project 1a and project 2 for the first day, and then the cost of project 2 for the remainder of its implementation. That’s ($1,714 / 5) + ($37,860 / 5) + $37,860 = $45,775.&lt;/p&gt;

&lt;p&gt;If the team were to complete the projects in the order indicated by CD3, the costs would be $37,860 + $1,714 + ($1,714 / 5) = $39,917. The CD3 ordering saves $5,859 while delivering the same results in the same time period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope I’ve provided some insights into how Cost of Delay can help you understand project impacts and assist in project prioritization. Once your organization is able to clearly describe project impacts in financial terms, it becomes much easier to advocate for technical initiatives and discuss priority trade-offs with other teams.&lt;/p&gt;

&lt;p&gt;If you have any feedback, please let me know on &lt;a href="https://twitter.com/jeffpalmer"&gt;Twitter&lt;/a&gt; or feel free to &lt;a href="https://jpalmer.dev/contact"&gt;contact me here.&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: it minimizes the area under the project cost curve. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>management</category>
      <category>lean</category>
      <category>prioritization</category>
    </item>
    <item>
      <title>The 2013 Felton Volume Chart</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Fri, 26 Mar 2021 22:13:57 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/the-2013-felton-volume-chart-4de2</link>
      <guid>https://dev.to/jeffreypalmer/the-2013-felton-volume-chart-4de2</guid>
      <description>&lt;p&gt;This is the final post in my series on how I created the various visualizations in my &lt;a href="https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/"&gt;Visual Studio Code 2020 Year in Review&lt;/a&gt;. In this post I cover how I recreated the final visualization type from &lt;a href="http://feltron.com/FAR13.html"&gt;Nicholas Felton’s 2013 Annual Report&lt;/a&gt;, the “volume chart,” shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uVOyMIsu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/original-felton-2013-volume-chart.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uVOyMIsu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/original-felton-2013-volume-chart.png" alt="Volume chart detail from 2013 Annual Report"&gt;&lt;/a&gt;Volume chart detail from 2013 Annual Report&lt;a href="http://feltron.com/FAR13.html"&gt;(Nicholas Felton)&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This stacked set of charts breaks down each type of interaction and displays the aggregated volumes of those interactions by hour (y-axis) for each week in the year (x-axis). There is a histogram at the top of the visualization that provides summary context for the hourly measures below. Finally, a yellow highlight color represents the top 25% of volume measures for that type.&lt;/p&gt;

&lt;p&gt;The approach I took to build this visualization was to break it into two Svelte components: &lt;code&gt;WeeklyVolumeHeader&lt;/code&gt; and &lt;code&gt;HourlyVolume&lt;/code&gt;. &lt;code&gt;WeeklyVolumeHeader&lt;/code&gt; generates the weekly summary histogram SVG at the top of the visualization, and &lt;code&gt;HourlyVolume&lt;/code&gt; generates an SVG for each of the volume visualizations. This separation made it easy to repeat the volume visualization with a different slice of the dataset for each event.&lt;/p&gt;

&lt;p&gt;Before all of that, however, the data needed to be loaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transforming the Data
&lt;/h2&gt;

&lt;p&gt;As I &lt;a href="https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/"&gt;previously mentioned&lt;/a&gt;, all of the data used to create this visualization was obtained via queries to the &lt;a href="https://gh.clickhouse.tech/explorer/"&gt;ClickHouse GitHub archive&lt;/a&gt; that they have generously made available to the public.&lt;/p&gt;

&lt;p&gt;I used the following query to aggregate event counts by week and hour, and then save the data into a JSON file for use in this visualization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="n"&gt;toStartOfWeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CET'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toHour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CET'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;countIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PullRequestEvent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pull_requests&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;countIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'IssuesEvent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;countIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CreateEvent'&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'DeleteEvent'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;branches&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;github_events&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;repo_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'microsoft/vscode'&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;toYear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CET'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CreateEvent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DeleteEvent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PullRequestEvent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'IssuesEvent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;
&lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="s1"&gt;'/data/volume-weekly-activity-by-hour.json'&lt;/span&gt;
&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query produces a JSON file that has two top-level entries, &lt;code&gt;meta&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt;. The &lt;code&gt;data&lt;/code&gt; entry contains the query results, and is what needs to be processed to create usable data for visualization. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"week"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2019-12-29"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pull_requests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"issues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"branches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"week"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-08-02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pull_requests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"issues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"87"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"branches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ideally, the data would look like this:&lt;sup id="fnref:1"&gt;1&lt;/sup&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "week": "2019-12-29",
    "hour": 12,
    "event": "pull_requests",
    "count": 0
  },
  {
    "week": "2019-12-29",
    "hour": 12,
    "event": "issues",
    "count": 4
  },
  {
    "week": "2019-12-29",
    "hour": 12,
    "event": "branches",
    "count": 0
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that the original row that contained a column for every event has been “unrolled” into three rows, and the type of event is itself now a column. This type of format, where every observation is represented as a row in the dataset, is often referred to as “tidy” data. It was first described in &lt;a href="https://www.jstatsoft.org/article/view/v059i10"&gt;Tidy Data&lt;/a&gt;, by Hadley Wickham, and has since become a popular way of preparing/structuring data for analysis within the data science community.&lt;/p&gt;

&lt;p&gt;As more and more teams have been working with tidy data, a number of tools/libraries that simplify the process of “tidying” data have been developed. For TypeScript, I’ve been using a library called &lt;a href="https://uwdata.github.io/arquero/"&gt;Arquero&lt;/a&gt; (developed by the folks at the &lt;a href="http://idl.cs.washington.edu/"&gt;UW Interactive Data Lab&lt;/a&gt;), and I’ve been really pleased with it so far.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tidying the Data with D3 and Arquero
&lt;/h3&gt;

&lt;p&gt;To get things rolling, the data needs to be loaded from the JSON file into memory. I used &lt;code&gt;d3.json&lt;/code&gt; to load the JSON data from its file on disk, and then ran a series of data transformations on the data using Arquero to get it into the format I described above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aq&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arquero&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;loadData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data/volume-weekly-activity-by-hour.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;aq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d3_parse_date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aq&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pull_requests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;issues&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;branches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;derive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;aq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;week&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;aq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d3_parse_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;week&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;allData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In line 8 I extend the standard Arquero operators so that I can use the &lt;a href="https://github.com/d3/d3-time-format#timeParse"&gt;&lt;code&gt;d3.timeParse&lt;/code&gt;&lt;/a&gt; function instead of relying on the wonky default Javascript &lt;code&gt;Date&lt;/code&gt; constructor that Arquero uses internally (and &lt;a href="https://uwdata.github.io/arquero/api/op#parse_date"&gt;warns you about&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Lines 11-20 actually transform the data. Line 12 converts the JSON array into the Arquero table format, and lines 13-15 use the &lt;a href="https://uwdata.github.io/arquero/api/verbs#fold"&gt;&lt;code&gt;fold&lt;/code&gt;&lt;/a&gt; verb to convert column names and values into rows.&lt;/p&gt;

&lt;p&gt;Once that’s done, lines 16-19 convert strings to integers or dates, with line 18 using the &lt;code&gt;d3_parse_date&lt;/code&gt; custom operator that was defined above, and the cleanup is complete.&lt;/p&gt;

&lt;p&gt;In Svelte, you can await the result of an asynchronous call using the &lt;a href="https://svelte.dev/docs#await"&gt;&lt;code&gt;#await&lt;/code&gt; construct&lt;/a&gt;.&lt;sup id="fnref:2"&gt;2&lt;/sup&gt; This makes it easy to retrieve the data asynchronously directly in the markup, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {#await loadData() then data}
    &lt;span class="nt"&gt;&amp;lt;WeeklyVolumeHeader&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HourlyVolume&lt;/span&gt;
      &lt;span class="na"&gt;data=&lt;/span&gt;&lt;span class="s"&gt;{data.filter((d)&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; d.event === "pull_requests").objects()}
      {width}
      {highlightColor}
      {normalColor}
      title="PULL REQUESTS"
    /&amp;gt;
    &lt;span class="nt"&gt;&amp;lt;HourlyVolume&lt;/span&gt;
      &lt;span class="na"&gt;data=&lt;/span&gt;&lt;span class="s"&gt;{data.filter((d)&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; d.event === "issues").objects()}
      {width}
      {highlightColor}
      {normalColor}
      title="ISSUES"
    /&amp;gt;
    &lt;span class="nt"&gt;&amp;lt;HourlyVolume&lt;/span&gt;
      &lt;span class="na"&gt;data=&lt;/span&gt;&lt;span class="s"&gt;{data.filter((d)&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; d.event === "branches").objects()}
      {width}
      {highlightColor}
      {normalColor}
      title="BRANCHES"
    /&amp;gt;
  {/await}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that done, it was time to start building the weekly summary component.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Weekly Volume Header Histogram
&lt;/h2&gt;

&lt;p&gt;This histogram is a slightly modified version of the two histograms that I described in &lt;a href="https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/"&gt;earlier&lt;/a&gt; &lt;a href="https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/"&gt;posts&lt;/a&gt;. The new element in this version is the addition of a vertical dashed line indicating the month boundary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eg07xa1C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/original-felton-2013-volume-chart-detail_hucb4a73dbfd526abbf028174747e5eb3a_175142_800x0_resize_box_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eg07xa1C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/original-felton-2013-volume-chart-detail_hucb4a73dbfd526abbf028174747e5eb3a_175142_800x0_resize_box_2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each of these separators is a dashed line from the x-axis to the measurement at the time of the start of the month. However, because the solid line represents a histogram that is aligned with week boundaries, it’s necessary to find the weekly measure into which the start of the month falls. It turns out that you can use a &lt;a href="https://github.com/d3/d3-array#bisector"&gt;&lt;code&gt;d3.bisector&lt;/code&gt;&lt;/a&gt; to efficiently find these entries, which can then be used to find the height at the start of the month.&lt;/p&gt;

&lt;p&gt;A bisector function is used to search a sorted array for the location at which a new value should be inserted to keep all array values properly sorted. This search takes advantage of the sorted nature of the array and uses a binary search to find the insertion point, which is very efficient as the number of entries being searched increases.&lt;/p&gt;

&lt;p&gt;Here’s how it’s put into action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// generate a set of month boundary dates&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeMonth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;weeklyTotals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;weeklyTotals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;weeklyTotals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;week&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dateFinder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bisector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;findHeightAtDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dateFinder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provided with any date, &lt;code&gt;findHeightAtDate&lt;/code&gt; will provide the height of the histogram at that date. This can then be used to draw the dashed line that corresponds to the start of each month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 {width} {height}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {#each months as month}
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashed-line"&lt;/span&gt;
      &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;{xScale(month)}&lt;/span&gt;
      &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;{xScale(month)}&lt;/span&gt;
      &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;{yScale(0)}&lt;/span&gt;
      &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;{findHeightAtDate(weeklyTotals,&lt;/span&gt; &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="err"&gt;)}&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  {/each}
  &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"total"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;{yLine}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which results in this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--09ikvIYT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/weekly-histogram-problem-detail_hu6367cf45a215047257b48e391ed9f020_34967_800x0_resize_box_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--09ikvIYT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/weekly-histogram-problem-detail_hu6367cf45a215047257b48e391ed9f020_34967_800x0_resize_box_2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is quite close, but there’s a problem. The start of March happens to coincide with the transition element between histogram measurements, and as a result the dashed line extends above transition line. This problem is one of my own devising. 😬&lt;/p&gt;

&lt;p&gt;I decided that the easiest thing to do&lt;sup id="fnref:3"&gt;3&lt;/sup&gt; was to try to figure out a heuristic that would allow me to figure out when I needed to blend two histogram measurements together. In this case, the week boundaries are all on Sundays, so I was able to simply calculate an average measurement for any month that started on a Sunday.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;findHeightAtDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dateFinder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// if the current date is a sunday (the start of the week) we'll&lt;/span&gt;
  &lt;span class="c1"&gt;// have some issues with the Felton line transitions, so work around&lt;/span&gt;
  &lt;span class="c1"&gt;// that by averaging neighboring measure heights&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This result was good enough for me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gjdy1yQD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/weekly-histogram-fixed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gjdy1yQD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/weekly-histogram-fixed.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On to the volume visualization component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Volume Visualization
&lt;/h2&gt;

&lt;p&gt;It turns out that the &lt;code&gt;HourlyVolume&lt;/code&gt; component was a very straightforward application of several &lt;a href="https://github.com/d3/d3-scale"&gt;d3 scales&lt;/a&gt;. In the end this visualization required five separate scales, each of which was responsible for mapping a certain aspect of the volume measurement to its visual representation. These included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;xScale&lt;/code&gt;, &lt;code&gt;yScale&lt;/code&gt; to map weeks and hours to coordinates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;radiusScale&lt;/code&gt; to map count of events to circle size&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;colorScale&lt;/code&gt; to map count of events to a color fill&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;opacityScale&lt;/code&gt; to map count of events to an opacity&lt;sup id="fnref:4"&gt;4&lt;/sup&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sizing the circles
&lt;/h3&gt;

&lt;p&gt;I’ve covered how to create &lt;code&gt;xScale&lt;/code&gt; and &lt;code&gt;yScale&lt;/code&gt; scales in previous posts, so I’ll move straight to the &lt;code&gt;radiusScale&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When using a filled circle to visually represent a quantity, it’s important not to overstate amounts accidentally. With a circular representation this is easy to do because the area of a circle varies as the square of the radius. To address this, when using a filled circle the scale for the radius should use &lt;a href="https://github.com/d3/d3-scale#scaleSqrt"&gt;&lt;code&gt;d3.scaleSqrt&lt;/code&gt;&lt;/a&gt;, which will scale a value according to the square root of the parameter.&lt;/p&gt;

&lt;p&gt;I also wanted to ensure that outliers in the aggregated data didn’t end up overwhelming or obscuring parts of the the volume visualization. The easiest way to do that is to calculate a value that represents the high end of the collection of measurements without outliers, and use that instead of the maximum value. One way to do this is to calculate the 99th percentile for the dataset and use that as the maximum value for the domain of the scale. This can be done using &lt;a href="https://github.com/d3/d3-array#quantile"&gt;&lt;code&gt;d3.quantile&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, once the scale has been created, the &lt;a href="https://github.com/d3/d3-scale#continuous_clamp"&gt;&lt;code&gt;clamp&lt;/code&gt;&lt;/a&gt; function can be used to ensure that values that fall outside of the specified domain are clamped to the extents of the domain. This ensures that the largest radius the scale will generate is the one that is specified with &lt;code&gt;maxRadius&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxRadius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boundPercentile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// find an upper limit for the radius scale based on boundPercentile&lt;/span&gt;
&lt;span class="c1"&gt;// this allows us to use .clamp(true) to keep outliers from&lt;/span&gt;
&lt;span class="c1"&gt;// overwhelming the visual&lt;/span&gt;
&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sizeLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;boundPercentile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sizeAccessor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;radiusScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleSqrt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sizeLimit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRadius&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Note: I typically put a set of constant declarations for these types of fine-tuning variables at the top of my components, so that I can tweak them as necessary based on the data I’m actively trying to visualize.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coloring the circles
&lt;/h3&gt;

&lt;p&gt;Finally, in the original volume chart all aggregate values that were in the busiest 25% of activity were colored yellow, whereas in my VS Code visualization only the busiest 20% were highlighted. Both of these can be easily implemented via &lt;a href="https://github.com/d3/d3-scale#scaleThreshold"&gt;&lt;code&gt;d3.scaleThreshold&lt;/code&gt;&lt;/a&gt;. This scale type transforms a domain into sections based on specified thresholds, which makes it perfect for performing the described color mapping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p80&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sizeAccessor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;colorScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleThreshold&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;p80&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;normalColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;highlightColor&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opacityScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleThreshold&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;p80&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Both &lt;code&gt;normalColor&lt;/code&gt; and &lt;code&gt;highlightColor&lt;/code&gt; are &lt;a href="https://svelte.dev/docs#Attributes_and_props"&gt;Svelte component properties&lt;/a&gt; and are passed in from the parent component.)&lt;/p&gt;

&lt;p&gt;This creates a scale that is split at the 80th percentile point in the domain (&lt;code&gt;p80&lt;/code&gt;). The left and right sides of this point are colored according to the range &lt;code&gt;[normalColor, highlightColor]&lt;/code&gt;, meaning that any value that is above the 80th percentile in the domain will be highlighted, and any point below it will not be.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;opacityScale&lt;/code&gt; is the same, except that it maps to two opacity values instead of colors.&lt;/p&gt;

&lt;p&gt;Once all of these elements are in place, the actual drawing of the volume chart is pretty minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 {width} {height}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {#each weeks as week}
    {#each getHoursForWeek(data, xAccessor(week)) as hour}
      &lt;span class="nt"&gt;&amp;lt;circle&lt;/span&gt;
        &lt;span class="na"&gt;cx=&lt;/span&gt;&lt;span class="s"&gt;{xScale(xAccessor(hour))}&lt;/span&gt;
        &lt;span class="na"&gt;cy=&lt;/span&gt;&lt;span class="s"&gt;{yScale(yAccessor(hour))}&lt;/span&gt;
        &lt;span class="na"&gt;r=&lt;/span&gt;&lt;span class="s"&gt;{radiusScale(sizeAccessor(hour))}&lt;/span&gt;
        &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;{colorScale(sizeAccessor(hour))}&lt;/span&gt;
        &lt;span class="na"&gt;opacity=&lt;/span&gt;&lt;span class="s"&gt;{opacityScale(sizeAccessor(hour))}&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    {/each}
  {/each}
  &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"baseline"&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;{0}&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;{width}&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;{0}&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;{0}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{0}&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{15}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"time-label"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{width}&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{15}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;AM&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"time-label"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{width}&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{height&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;5}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;PM&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;getHoursForWeek&lt;/code&gt; is defined as the following, because apparently you can’t compare &lt;code&gt;Date&lt;/code&gt; objects for equality. Joy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getHoursForWeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;week&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;week&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Final Version
&lt;/h2&gt;

&lt;p&gt;Once this is all in place, the final version of the volume chart looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8gEni3Km--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/felton-2013-volume-chart-final.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8gEni3Km--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/felton-2013-volume-chart-final.png" alt="Final volume chart version"&gt;&lt;/a&gt;Final volume chart version &lt;/p&gt;

&lt;p&gt;The code I used to write this post is &lt;a href="https://github.com/JeffreyPalmer/svelte-d3-examples"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any feedback, please let me know on &lt;a href="https://twitter.com/jeffpalmer"&gt;Twitter&lt;/a&gt; or feel free to &lt;a href="https://jpalmer.dev/contact"&gt;contact me here.&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Yes, I could have written the query in such a way as to generate something much closer to this format from the beginning. I didn’t do that because of time constraints, and I’ll definitely try harder to export tidy data directly going forward. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;This is the short form of the &lt;code&gt;#await&lt;/code&gt; construct, which if fine when I’m prototyping ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;After trying to ignore it by increasing the &lt;code&gt;stroke-width&lt;/code&gt;. 😆 ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Strictly speaking, the &lt;code&gt;opacityScale&lt;/code&gt; was not required, as I could have not been lazy and chosen a dimmer color for this visual. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>showdev</category>
      <category>vscode</category>
      <category>svelte</category>
      <category>dataviz</category>
    </item>
    <item>
      <title>Visualizing the Change History of the Git Repository</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Wed, 24 Mar 2021 01:16:41 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/visualizing-the-change-history-of-the-git-repository-4mg5</link>
      <guid>https://dev.to/jeffreypalmer/visualizing-the-change-history-of-the-git-repository-4mg5</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JXqZ091Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/visualizing-the-change-history-of-the-git-repository/git-change-history-2005-2021-v1.0-overview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JXqZ091Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/visualizing-the-change-history-of-the-git-repository/git-change-history-2005-2021-v1.0-overview.png" alt="The Change History of the Git Repository from 2005-2021"&gt;&lt;/a&gt;The Change History of the Git Repository from 2005-2020 &lt;/p&gt;

&lt;p&gt;Recently I decided to continue my experiments visualizing the data produced by the software development process. This time I decided to dig into the git commit history of Git itself, with the goal of creating something interesting and creative that still provides information about the subject matter.&lt;/p&gt;

&lt;p&gt;This visualization is a radial representation of all changes stored in the Git git repository from inception to the end of 2020. Time flows clockwise and outward, starting from the first commit on 7 April 2005, until 31 December 2020. Circles represent the net lines of code added to the project on a single day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0t3-Co12--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/visualizing-the-change-history-of-the-git-repository/git-change-history-2005-2021-v1.0-detail.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0t3-Co12--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/visualizing-the-change-history-of-the-git-repository/git-change-history-2005-2021-v1.0-detail.png" alt="Detail View"&gt;&lt;/a&gt;Detail View &lt;/p&gt;

&lt;p&gt;It was a fun exercise and I’ll probably try to apply this approach to some other popular open source projects. I hope you enjoy it!&lt;/p&gt;

&lt;p&gt;As always, if you have any feedback please let me know on &lt;a href="https://twitter.com/jeffpalmer"&gt;Twitter&lt;/a&gt; or feel free to &lt;a href="https://jpalmer.dev/contact"&gt;contact me here.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>opensource</category>
      <category>d3js</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The 2013 Felton Histogram - Variant 2</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Mon, 15 Mar 2021 20:05:32 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/the-2013-felton-histogram-variant-2-4bke</link>
      <guid>https://dev.to/jeffreypalmer/the-2013-felton-histogram-variant-2-4bke</guid>
      <description>&lt;p&gt;Nicholas Felton’s &lt;a href="http://feltron.com/FAR13.html"&gt;2013 Annual Report&lt;/a&gt; contained two distinct histogram variants. This post covers how I created the second variant, which looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nyzPsj_v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/original-felton-histogram-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nyzPsj_v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/original-felton-histogram-2.png" alt="Histogram (Variant 2) detail from 2013 Annual Report"&gt;&lt;/a&gt;Histogram (Variant 2) detail from 2013 Annual Report&lt;a href="http://feltron.com/FAR13.html"&gt;(Nicholas Felton)&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This is similar to the first variant that I covered in &lt;a href="https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/"&gt;my last post&lt;/a&gt;, with the following changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two measures are plotted on the same x-axis, with their y-axes facing in opposite directions&lt;/li&gt;
&lt;li&gt;Irregular stippling is used to differentiate the lower measure&lt;/li&gt;
&lt;li&gt;Measure series labels are added, while value labels are removed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Plotting with Multiple Axes
&lt;/h2&gt;

&lt;p&gt;This histogram variant adds a second measure that has its y-axis pointing downward, in opposition to the y-axis of the top measure. Since the y-axis in SVG increases from top to bottom, the range of the top scale will be from the y midpoint to the top margin (from x-axis &lt;em&gt;upwards&lt;/em&gt;), and the range of the bottom scale will be from the y midpoint to the height minus the bottom margin (from x-axis &lt;em&gt;downwards&lt;/em&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;dateParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2020-01-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;dateParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2021-01-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// The top scale goes from midpoint to top margin&lt;/span&gt;
&lt;span class="c1"&gt;// Also add 10% of the max top value to the end of the domain&lt;/span&gt;
&lt;span class="c1"&gt;// to ensure there's headroom on the chart&lt;/span&gt;
&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleLinear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topAccessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// The bottom scale goes from midpoint to bottom margin&lt;/span&gt;
&lt;span class="c1"&gt;// Also add 10% of the max bottom value to the end of the domain&lt;/span&gt;
&lt;span class="c1"&gt;// to ensure there's footroom (?) on the chart&lt;/span&gt;
&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bottomScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleLinear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bottomAccessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these scales, the same line drawing techniques from the last post can be used to create the two measure lines.&lt;/p&gt;

&lt;p&gt;First, the line paths were created using the &lt;code&gt;generateFeltonLine&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;
  &lt;span class="nx"&gt;generateFeltonLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;augmentedData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;topScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;topAccessor&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bottomLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;
  &lt;span class="nx"&gt;generateFeltonLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;augmentedData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;bottomScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;bottomAccessor&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then they were placed within the SVG along with some CSS classes for styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 {width} {height}"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"top-line"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;{topLine}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bottom-line"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;{bottomLine}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Much of the boilerplate in the code has been omitted to keep things short. These omissions are represented by &lt;code&gt;...&lt;/code&gt; in the source snippets. See &lt;a href="https://github.com/JeffreyPalmer/svelte-d3-examples"&gt;the GitHub repository&lt;/a&gt; for the working implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Irregular Stippling
&lt;/h2&gt;

&lt;p&gt;The original histogram used a form of stippling to differentiate the bottom measure from the top measure. My goal was to find an algorithm to generate realistic-looking stippling, generate a polygon that represents the area to be shaded, and then use the polygon as a mask to determine which points should be shown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Aesthetically pleasing random point distribution
&lt;/h3&gt;

&lt;p&gt;It turns out that generating stippling that looks good is actually somewhat difficult. Luckily I was able to lean on the community to find an existing implementation of &lt;a href="https://observablehq.com/@mbostock/poisson-disc-distribution"&gt;Bridson’s Algorithm&lt;/a&gt;, which can be used to approximate human stippling. Thanks (again) &lt;a href="https://bost.ocks.org/mike/"&gt;Mike&lt;/a&gt; &lt;a href="https://observablehq.com/@mbostock"&gt;Bostock&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I added a version of this generator function to my code and was able to use it to generate a set of points like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;poissonDiscSampler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Use the generator to generate all of the points&lt;/span&gt;
&lt;span class="c1"&gt;// so that they can be iterated over by svelte&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generatePoissonPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sampler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;poissonDiscSampler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sampler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sampler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// generate points covering the entire bottom of the SVG&lt;/span&gt;
&lt;span class="c1"&gt;// they'll be filtered later&lt;/span&gt;
&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generatePoissonPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The actual radius value of &lt;code&gt;10&lt;/code&gt; that I used was the result of some visual trial and error.)&lt;/p&gt;

&lt;p&gt;If you were to plot these points without any filtering, you get this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZCI9Gi5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-no-polygon-bound_hu4838ea1efdfa98868426e0299ef5ad62_157717_800x0_resize_box_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZCI9Gi5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-no-polygon-bound_hu4838ea1efdfa98868426e0299ef5ad62_157717_800x0_resize_box_2.png" alt="Stippling without polygon containment checks"&gt;&lt;/a&gt;Stippling without polygon containment checks &lt;/p&gt;

&lt;p&gt;The stippling looks great! Now we just need to get rid of everything outside of the measure area.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the bounding polygon
&lt;/h3&gt;

&lt;p&gt;Unsurprisingly, D3 has the ability to perform polygon containment checks out of the box via &lt;a href="https://github.com/d3/d3-polygon#polygonContains"&gt;polygonContains&lt;/a&gt;. Since I had already created a function to generate the set of points that constitute the measure line, creating a polygon from those points was straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateClosedFeltonPolygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lineSegments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateFeltonLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// First point - on the origin&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="c1"&gt;// Now the generated points for the measure lines&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;lineSegments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Bring the line back to the x-axis&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="c1"&gt;// Now close the polygon by going back to the origin&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;closedPoly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateClosedFeltonPolygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;augmentedData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;bottomScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;bottomAccessor&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the polygon was created, it could be used to filter the point array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
  {#each points as point}
    {#if d3.polygonContains(closedPoly, [point[0], height / 2 + point[1]])}
      &lt;span class="nt"&gt;&amp;lt;circle&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"stipple"&lt;/span&gt;
        &lt;span class="na"&gt;cx=&lt;/span&gt;&lt;span class="s"&gt;{point[0]}&lt;/span&gt;
        &lt;span class="na"&gt;cy=&lt;/span&gt;&lt;span class="s"&gt;{height&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;point&lt;/span&gt;&lt;span class="err"&gt;[1]}&lt;/span&gt;
        &lt;span class="na"&gt;r=&lt;/span&gt;&lt;span class="s"&gt;{2.5}&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    {/if}
  {/each}
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the filtered histogram looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--myqlG_6S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-polygon-bound_hu4838ea1efdfa98868426e0299ef5ad62_99057_800x0_resize_box_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--myqlG_6S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-polygon-bound_hu4838ea1efdfa98868426e0299ef5ad62_99057_800x0_resize_box_2.png" alt="Stippling after implementing polygon containment checks"&gt;&lt;/a&gt;Stippling after implementing polygon containment checks &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Labels
&lt;/h2&gt;

&lt;p&gt;The final step was to add measure labels above and below the x-axis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"top-label"&lt;/span&gt;
  &lt;span class="na"&gt;text-anchor=&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt;
  &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{width&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.right&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{(height&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.bottom&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
     &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;textHeight&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Top&lt;span class="nt"&gt;&amp;lt;/text&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bottom-label"&lt;/span&gt;
  &lt;span class="na"&gt;text-anchor=&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt;
  &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{width&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.right&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{(height&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.bottom&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
     &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;textHeight&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Bottom&lt;span class="nt"&gt;&amp;lt;/text&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately the lower label was somewhat obscured by the stippling:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jhKZP-Z7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-no-text-filter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jhKZP-Z7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-no-text-filter.png" alt="Measure labels - detail"&gt;&lt;/a&gt;Measure labels - detail &lt;/p&gt;

&lt;p&gt;I experimented with CSS-based text shadows in an attempt to create an outline that would mask the nearby points, but that didn’t work as well as I hoped it would. I did a little more research and eventually came across &lt;a href="https://tympanus.net/codrops/2019/01/22/svg-filter-effects-outline-text-with-femorphology/"&gt;an article describing the &lt;code&gt;feMorphology&lt;/code&gt; filters available in SVGs&lt;/a&gt;. (It’s still amazing to me what’s built into SVG!)&lt;/p&gt;

&lt;p&gt;These filters can be used to define a series of image transformations, which in this case were used to implement a very clean text outline (see the linked article for a detailed description of how this actually works).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;feMorphology&lt;/span&gt;
      &lt;span class="na"&gt;in=&lt;/span&gt;&lt;span class="s"&gt;"SourceAlpha"&lt;/span&gt;
      &lt;span class="na"&gt;result=&lt;/span&gt;&lt;span class="s"&gt;"DILATED"&lt;/span&gt;
      &lt;span class="na"&gt;operator=&lt;/span&gt;&lt;span class="s"&gt;"dilate"&lt;/span&gt;
      &lt;span class="na"&gt;radius=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;feFlood&lt;/span&gt;
      &lt;span class="na"&gt;flood-color=&lt;/span&gt;&lt;span class="s"&gt;"rgba(55, 65, 81, 1)"&lt;/span&gt;
      &lt;span class="na"&gt;flood-opacity=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
      &lt;span class="na"&gt;result=&lt;/span&gt;&lt;span class="s"&gt;"BACKGROUND"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;feComposite&lt;/span&gt;
      &lt;span class="na"&gt;in=&lt;/span&gt;&lt;span class="s"&gt;"BACKGROUND"&lt;/span&gt;
      &lt;span class="na"&gt;in2=&lt;/span&gt;&lt;span class="s"&gt;"DILATED"&lt;/span&gt;
      &lt;span class="na"&gt;operator=&lt;/span&gt;&lt;span class="s"&gt;"in"&lt;/span&gt;
      &lt;span class="na"&gt;result=&lt;/span&gt;&lt;span class="s"&gt;"OUTLINE"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;feMerge&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;feMergeNode&lt;/span&gt; &lt;span class="na"&gt;in=&lt;/span&gt;&lt;span class="s"&gt;"OUTLINE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;feMergeNode&lt;/span&gt; &lt;span class="na"&gt;in=&lt;/span&gt;&lt;span class="s"&gt;"SourceGraphic"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/feMerge&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"top-label"&lt;/span&gt;
    &lt;span class="na"&gt;filter=&lt;/span&gt;&lt;span class="s"&gt;"url(#outline)"&lt;/span&gt;
    &lt;span class="na"&gt;text-anchor=&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt;
    &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{width&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.right&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{(height&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.bottom&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
       &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;textHeight&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Top&lt;span class="nt"&gt;&amp;lt;/text&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bottom-label"&lt;/span&gt;
    &lt;span class="na"&gt;filter=&lt;/span&gt;&lt;span class="s"&gt;"url(#outline)"&lt;/span&gt;
    &lt;span class="na"&gt;text-anchor=&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt;
    &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{width&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.right&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{(height&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.bottom&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
       &lt;span class="na"&gt;margins.top&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;textHeight&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Bottom&lt;span class="nt"&gt;&amp;lt;/text&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the SVG &lt;code&gt;text&lt;/code&gt; elements were altered to include a reference to the filter via &lt;code&gt;filter="url(#outline)"&lt;/code&gt;, the label text in the stippled area became significantly more legible:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fXDFjmv5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-text-filtered-detail.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fXDFjmv5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-text-filtered-detail.png" alt="SVG text outline via feMorphology filters - detail"&gt;&lt;/a&gt;SVG text outline via feMorphology filters - detail &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;At this point, the final version of the histogram was complete:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jxCk-Kuo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-final.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jxCk-Kuo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-final.png" alt="Final histogram version - Variant 2"&gt;&lt;/a&gt;Final histogram version - Variant 2 &lt;/p&gt;

&lt;p&gt;The code I used to write this post is &lt;a href="https://github.com/JeffreyPalmer/svelte-d3-examples"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any feedback, please let me know on &lt;a href="https://twitter.com/jeffpalmer"&gt;Twitter&lt;/a&gt; or feel free to &lt;a href="https://jpalmer.dev/contact"&gt;contact me here.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>vscode</category>
      <category>svelte</category>
      <category>dataviz</category>
    </item>
    <item>
      <title>The 2013 Felton Histogram - Variant 1</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Tue, 09 Mar 2021 20:51:43 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/the-2013-felton-histogram-variant-1-khf</link>
      <guid>https://dev.to/jeffreypalmer/the-2013-felton-histogram-variant-1-khf</guid>
      <description>&lt;p&gt;Nicholas Felton’s &lt;a href="http://feltron.com/FAR13.html"&gt;2013 Annual Report&lt;/a&gt; contained two distinct histogram variants. This post covers how I created the first variant, which looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cv0qXFO2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/original-felton-histogram-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cv0qXFO2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/original-felton-histogram-1.png" alt="Histogram (Variant 1) detail from 2013 Annual Report"&gt;&lt;/a&gt;Histogram (Variant 1) detail from 2013 Annual Report &lt;a href="http://feltron.com/FAR13.html"&gt;(Nicholas Felton)&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;A pretty typical histogram. However, the styling aspects are what I found most interesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Truncated measure labels with a scale factor in the upper-right corner&lt;/li&gt;
&lt;li&gt;Slanted measure transition lines that join the entire series of measurements into a single line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll cover how I achieved both of these below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Truncated Measure Labels
&lt;/h2&gt;

&lt;p&gt;This was pretty straightforward, as there’s a math function that can be used directly to determine how many significant digits you need to represent a number: &lt;code&gt;Math.log10&lt;/code&gt;. The &lt;code&gt;log10&lt;/code&gt; function calculates the base 10 logarithm of a number, which can be used to calculate the number of digits within it. For example,&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Math.log10(623) = 2.7944880466591697&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The scale factor is calculated by using the whole part of the result (using &lt;code&gt;Math.floor&lt;/code&gt;) as the exponent in a call to &lt;code&gt;Math.pow&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We know we're showing 12 months so divide into 24 intervals&lt;/span&gt;
&lt;span class="nx"&gt;labelXOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Calculate the scale factor used to get the most-significant digit&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yMin&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.0f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The minimum value was used to ensure that the labels weren’t unnecessarily “flattened” by a scale that was off by a factor of 10 (or more), and D3’s formatting functions were used to discard the fractional part of each measure.&lt;/p&gt;

&lt;p&gt;Inside the SVG, the measure labels were added using &lt;code&gt;text&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{#each data as datum}
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt;
    &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{xScale(xAccessor(datum))&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;labelXOffset&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{yScale(yAccessor(datum))&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelYOffset&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{formatter(yAccessor(datum) / scale)}&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
{/each}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The title and scale indicator was added using additional text elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{margins.left}&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{margins.top&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;textHeight&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  TOTAL MONTHLY MEASUREMENT
&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"scale-label"&lt;/span&gt;
  &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{width&lt;/span&gt; &lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;margins.right&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{margins.top&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;textHeight&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;times;&lt;/span&gt;{formatter(scale)}
&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measure Transitions
&lt;/h2&gt;

&lt;p&gt;I implemented two separate line styles while building this visualization. The first used the built-in D3 line generation tools, and the second used a custom line generator.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Attempt Using D3 Curves
&lt;/h3&gt;

&lt;p&gt;At first I tried to use the &lt;a href="https://github.com/d3/d3-shape#line"&gt;&lt;code&gt;line&lt;/code&gt;&lt;/a&gt; function to generate a segmented line that represented the measures in the histogram. There is a &lt;a href="https://github.com/d3/d3-shape#line_curve"&gt;&lt;code&gt;curve&lt;/code&gt;&lt;/a&gt; function that can be used to specify how points in a line should be joined, and in particular &lt;a href="https://github.com/d3/d3-shape#curveStepAfter"&gt;&lt;code&gt;curveStepAfter&lt;/code&gt;&lt;/a&gt; seemed like it might do the job.&lt;/p&gt;

&lt;p&gt;💣 However, in order for this to work I needed to ensure that there was one extra entry in the data, or D3 wouldn’t create the final flat value segment. I simply copied the last point and set the date to one month in the future.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Massage the data so that there are enough points to complete the path&lt;/span&gt;
&lt;span class="c1"&gt;// Copy the final entry in the array&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myClonedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// Now set the date to one month after&lt;/span&gt;
&lt;span class="nx"&gt;myClonedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2021-01-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;myClonedData&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// First attempt - using 'curveStepAfter'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;curveStepAfter&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resulted in the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H5fttHYa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/felton-histogram-1-first-attempt_hu55b1a074477979be86ca962ca86a249e_59152_800x0_resize_box_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H5fttHYa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/felton-histogram-1-first-attempt_hu55b1a074477979be86ca962ca86a249e_59152_800x0_resize_box_2.png" alt="First attempt using D3 line and curveStepAfter"&gt;&lt;/a&gt;First attempt using D3 line and curveStepAfter &lt;/p&gt;

&lt;p&gt;This was actually a pretty good starting point, and was what I used until I had time to revisit the visualization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second Attempt Using Custom Line Generator
&lt;/h3&gt;

&lt;p&gt;Once I had a little more time to revisit my first attempt, I decided to implement the slanted segment connectors that were used in the original.&lt;/p&gt;

&lt;p&gt;I had to replace the use of the &lt;code&gt;line&lt;/code&gt; built-in &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; functions with a custom function that took the spacing between measured values into consideration. My goal was to allocate 5% of each measure line at the start and end to be used for the transition. As a result, each line segment was drawn by creating a horizontal line for the measure itself, and then creating a line from the end of that measure to the start of the next. This logic is contained in the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateFeltonLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// this is only correct because of 0-based arrays&lt;/span&gt;
  &lt;span class="c1"&gt;// and # segments = # points - 1&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate displayed segment width and connector width&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;segmentWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connectorWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;segmentWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 5% on each side&lt;/span&gt;

  &lt;span class="c1"&gt;// start with the first point, as it (and the last point)&lt;/span&gt;
  &lt;span class="c1"&gt;// are special cases&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// now all of the interior points&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;connectorWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;connectorWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// add the final point&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
    &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that was done I could create the measure line as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Second attempt - Felton-style connectors&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;
    &lt;span class="nx"&gt;generateFeltonLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yAccessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resulting in a much-improved (in my opinion) final version of the visualization:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HTQds-rE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/felton-histogram-1-final.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HTQds-rE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/felton-histogram-1-final.png" alt="Final histogram version - variant 1"&gt;&lt;/a&gt;Final histogram version - variant 1 &lt;/p&gt;

&lt;p&gt;The code I used to write this post is &lt;a href="https://github.com/JeffreyPalmer/svelte-d3-examples"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please feel free to contact me if you have any questions or comments.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>vscode</category>
      <category>svelte</category>
      <category>dataviz</category>
    </item>
    <item>
      <title>The 2013 Felton Bar Chart</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Thu, 18 Feb 2021 23:51:42 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/the-2013-felton-bar-chart-5a8j</link>
      <guid>https://dev.to/jeffreypalmer/the-2013-felton-bar-chart-5a8j</guid>
      <description>&lt;p&gt;This bar chart was one of the more straightforward visualizations to implement, and it was a good place for me to get my feet wet with Svelte and D3.&lt;/p&gt;

&lt;p&gt;For reference, Nicholas Felton’s original bar chart looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SmH4dscT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/the-2013-felton-bar-chart/original-felton-bar-chart.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SmH4dscT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/the-2013-felton-bar-chart/original-felton-bar-chart.png" alt="Bar chart detail from 2013 Annual Report"&gt;&lt;/a&gt;Bar chart detail from 2013 Annual Report &lt;a href="http://feltron.com/FAR13.html"&gt;(Nicholas Felton)&lt;/a&gt;&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;Simple and effective.&lt;/p&gt;

&lt;p&gt;In order to recreate this, several things needed to be done:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up some sizing values to define the spacing of individual elements in each line.&lt;/li&gt;
&lt;li&gt;Calculate the height of each line, which is then used to determine the height of the SVG.&lt;/li&gt;
&lt;li&gt;Create a scale for the x-axis which will be used to draw both the solid and dashed lines.&lt;/li&gt;
&lt;li&gt;Parameterize the highlight color so that the chart can be re-used with different colors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ll cover how I did these things below.&lt;/p&gt;
&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;First of all, I’m sort of assuming that you have some idea of how Svelte works. If you don’t and are curious, you can take 30 minutes to work through the &lt;a href="https://svelte.dev/tutorial"&gt;Svelte Tutorial&lt;/a&gt;. While there are a bunch of other great resources available, I’ve found that Svelte is simple enough that the tutorial is actually a really great starting point.&lt;/p&gt;

&lt;p&gt;Also, I’m not going to cover the details of how Svelte and D3 interact. The key takeaway is that Svelte handles reactivity and DOM manipulation, and D3 is used for things like scales, etc. &lt;a href="https://www.youtube.com/playlist?list=PLXiRqCwhpnehz5z0gpsmcb2l8vRBSi14M"&gt;Paul Butler’s YouTube series about combining D3 with Svelte&lt;/a&gt; is a great starting point, as are Matthias Stahl’s presentations on &lt;a href="https://youtu.be/bnd64ZrHC0U"&gt;Svelte and D3&lt;/a&gt; and &lt;a href="https://youtu.be/xtTrcCp2aVU"&gt;How do you set up visualization with Svelte&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  SVG Sizing and Some CSS Gymnastics
&lt;/h2&gt;

&lt;p&gt;In order to create an SVG that will maintain its aspect ratio when placed into containers of various sizes, you need to use a &lt;code&gt;viewBox&lt;/code&gt; attribute to set the coordinate system of the SVG. This had to be one of the most confusing things I ran into when getting started. In fact, I’m still not sure that I have completely internalized the specifics of how SVG sizing/scaling works, so I’ll simply refer you to the resources I used to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.sarasoueidan.com/blog/svg-coordinate-systems/"&gt;Understanding SVG Coordinate Systems and Transformations&lt;/a&gt; ( &lt;strong&gt;amazing&lt;/strong&gt; )&lt;/li&gt;
&lt;li&gt;&lt;a href="https://css-tricks.com/scale-svg/"&gt;How to Scale SVG&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://xahlee.info/js/svg_font_size.html"&gt;SVG: Font Size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, one of the first things I needed to do was calculate the height of the overall bar chart based on its contents. I ended up explicitly setting the font size in my TypeScript code and using that to perform calculations with the text height, like calculating offsets, line heights, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sizing in SVG user space&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Font size in user space&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;textHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This needs to be made available to the CSS, but because Svelte doesn’t perform template substitution within the CSS &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; section, you need to &lt;a href="https://stackoverflow.com/questions/57174373/can-i-set-svelte-style-css-attribute-values-using-variables-passed-in-to-a-compo"&gt;do a little hoop jumping&lt;/a&gt; to get things to work. You can add a &lt;code&gt;style&lt;/code&gt; attribute on the SVG (or any container) that defines &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties"&gt;CSS custom properties&lt;/a&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--label-size: {textHeight}px; --highlight-color: {highlightColor};"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you’ve done this you can write CSS that uses those variables in your &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--label-size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.highlight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--highlight-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--highlight-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.measure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;stroke-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.dash&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;stroke-dasharray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;stroke-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it straightforward to change the font size if I’m using the chart in a different context. You can even make &lt;code&gt;textHeight&lt;/code&gt; a &lt;a href="https://svelte.dev/docs#Attributes_and_props"&gt;Svelte component property&lt;/a&gt; and pass it in as necessary. That said, ideally I’d be able to control all of this styling via CSS, so I’m still trying to figure out if you can do that. If you have suggestions on how to better manage consistent font sizes across both CSS and SVG text elements, please let me know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calculating the SVG Height
&lt;/h3&gt;

&lt;p&gt;Okay, so with the &lt;code&gt;textHeight&lt;/code&gt; available, we can calculate the height of an individual line, and then the overall SVG height.&lt;/p&gt;

&lt;p&gt;Each line in the visualization is comprised of two parts: the name and metric value on top, and the bar visualization of the metric value below. The spacing of a line is the height of the text line plus some padding below the text that contains the bar visualization. I used &lt;code&gt;linePadding&lt;/code&gt; and &lt;code&gt;barOffset&lt;/code&gt; to keep track of these spacing elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Margins within the SVG&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// How many lines should be displayed?&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Space between the bottom of the text and the next line&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;linePadding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Space between the bottom of the text and the metric bar&lt;/span&gt;
&lt;span class="c1"&gt;// *must be less than linePadding*&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;barOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lineHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;linePadding&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Compute the overall height&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;topN&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;lineHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we’ve calculated the height we can finally add the &lt;code&gt;viewBox&lt;/code&gt; attribute to set the aspect ratio of the SVG.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 {width} {height}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💣 One thing to keep in mind with SVGs is that the x-axis runs from left to right, but the y-axis runs from top to bottom, opposite of the coordinate system that most of us were taught in geometry. That means that (0, 0) is in the upper-left part of the SVG, and (100, 100) will be in the lower right. While not really a problem for this simple chart, this definitely messed with my head on another chart that had multiple y-axes pointing in different directions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing the Data
&lt;/h2&gt;

&lt;p&gt;Now we can move on to drawing the elements of the bar chart. The structure of the data that I used in this example is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Elias Vančo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total_actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;260&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sommer Wolfe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total_actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;337&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many D3 functions take accessor functions for data arrays, so I like to declare helper functions to access individual data elements so that I can easily change them later, if necessary.&lt;sup id="fnref:1"&gt;1&lt;/sup&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metricAccessor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total_actions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleAccessor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the X-Axis Scale
&lt;/h3&gt;

&lt;p&gt;D3 makes it easy to map values in your data &lt;em&gt;domain&lt;/em&gt; to the coordinate system of the SVG (the &lt;em&gt;range&lt;/em&gt;) through the use of &lt;a href="https://github.com/d3/d3-scale"&gt;scales&lt;/a&gt;. There are a number of different scales from which to choose, and for this visual I used &lt;code&gt;scaleLinear&lt;/code&gt; to perform a basic linear mapping from domain to range.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Grab the maximum value from the data for later use&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metricAccessor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a D3 scale that maps domain values (from 0 to xMax)&lt;/span&gt;
&lt;span class="c1"&gt;// to SVG coordinates, respecting margins&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleLinear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xMax&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Calculate the index-based offset from the top of the SVG&lt;/span&gt;
&lt;span class="c1"&gt;// Note: Add textHeight to shift text below the starting point&lt;/span&gt;
&lt;span class="c1"&gt;// as text is placed according the the baseline&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yOffsetFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lineHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;textHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;xScale&lt;/code&gt; is a function that calculates the absolute x-axis SVG positions for any data value in the domain. &lt;code&gt;yOffsetFn&lt;/code&gt; serves the same purpose as &lt;code&gt;xScale&lt;/code&gt; but calculates an index-based vertical offset for the y-axis to be used for line spacing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Bar Elements
&lt;/h3&gt;

&lt;p&gt;Once I had a scale that mapped domain values to SVG coordinates, I could iterate through the data to draw the bar chart.&lt;/p&gt;

&lt;p&gt;First, I sorted the data and pulled the &lt;code&gt;topN&lt;/code&gt; values into a separate variable using &lt;a href="https://ramdajs.com/"&gt;Ramda&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ramda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Sort and extract the first topN elements from the sorted collection&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;metricAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;metricAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topParticipants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then used the Svelte &lt;a href="https://svelte.dev/docs#each"&gt;&lt;code&gt;#each&lt;/code&gt;&lt;/a&gt; construct to iterate through the &lt;code&gt;topParticipants&lt;/code&gt; collection to generate SVG elements. In these elements I made use of the &lt;code&gt;xScale&lt;/code&gt; and &lt;code&gt;yOffsetFn&lt;/code&gt; functions to calculate absolute SVG positions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
{#each topParticipants as participant, i}
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt;
    &lt;span class="na"&gt;text-anchor=&lt;/span&gt;&lt;span class="s"&gt;"start"&lt;/span&gt;
    &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{xScale(0)}&lt;/span&gt;
    &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{yOffsetFn(i)}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{titleAccessor(participant)}&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"label highlight"&lt;/span&gt;
    &lt;span class="na"&gt;text-anchor=&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt;
    &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{xScale(xMax)}&lt;/span&gt;
    &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{yOffsetFn(i)}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{metricAccessor(participant)}&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dash"&lt;/span&gt;
    &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;{xScale(0)}&lt;/span&gt;
    &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;{xScale(xMax)}&lt;/span&gt;
    &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;{yOffsetFn(i)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;barOffset&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;{yOffsetFn(i)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;barOffset&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"measure highlight"&lt;/span&gt;
    &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;{xScale(0)}&lt;/span&gt;
    &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;{xScale(metricAccessor(participant))}&lt;/span&gt;
    &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;{yOffsetFn(i)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;barOffset&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;{yOffsetFn(i)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;barOffset&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
{/each}
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first &lt;code&gt;text&lt;/code&gt; element is the left-aligned participant name, using &lt;code&gt;xScale(0)&lt;/code&gt; for the &lt;code&gt;x&lt;/code&gt; position.&lt;/p&gt;

&lt;p&gt;The second &lt;code&gt;text&lt;/code&gt; element is the right-aligned count of total actions. To do this I needed to calculate the maximum &lt;code&gt;x&lt;/code&gt; position via &lt;code&gt;xScale(xMax)&lt;/code&gt;, and set the &lt;code&gt;text-anchor&lt;/code&gt; attribute of the &lt;code&gt;text&lt;/code&gt; element to &lt;code&gt;end&lt;/code&gt;. That moved the element all the way to the right, but lined up the end of the text with the provided &lt;code&gt;x&lt;/code&gt; coordinate.&lt;/p&gt;

&lt;p&gt;The first &lt;code&gt;line&lt;/code&gt; element is the dashed-line placeholder for the measure. The &lt;code&gt;y&lt;/code&gt; position is offset by the additional &lt;code&gt;barOffset&lt;/code&gt; to give the text above it a little breathing room. This line is drawn across the entire x-axis to produce visually consistent dashed lines across the entire chart. Since SVG elements are drawn in the order they are defined, with later elements overlapping earlier ones, this placeholder is hidden by the next element and there are no fancy line start/end calculations required.&lt;/p&gt;

&lt;p&gt;The second &lt;code&gt;line&lt;/code&gt; element is the bar representing the actual measure. It is drawn on the same &lt;code&gt;y&lt;/code&gt; position as the previous line, but is slightly thicker to completely obscure the dashed line.&lt;/p&gt;

&lt;p&gt;Once all of these elements were in place the entire visual was basically done. I did notice one detail that bothered me after a quick visual inspection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bp9sbshW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/the-2013-felton-bar-chart/left-to-right-bar-chart-detail.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bp9sbshW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/the-2013-felton-bar-chart/left-to-right-bar-chart-detail.png" alt="Right side dash mis-alignment"&gt;&lt;/a&gt;Right side dash mis-alignment &lt;/p&gt;

&lt;p&gt;The final dot at the end of the dashed placeholder lines didn’t match up to the right end of the scale. This was easily remedied by drawing the dashed line from right to left instead of left to right (by swapping the &lt;code&gt;x1&lt;/code&gt; and &lt;code&gt;x2&lt;/code&gt; values), which resulted in any left side alignment issues being hidden under the metric bars.&lt;/p&gt;

&lt;p&gt;Here’s the final result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pdxFRkXA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/the-2013-felton-bar-chart/bar-chart-final.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pdxFRkXA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/the-2013-felton-bar-chart/bar-chart-final.png" alt="Final bar chart version"&gt;&lt;/a&gt;Final bar chart version &lt;/p&gt;

&lt;p&gt;The code I used to write this post is &lt;a href="https://github.com/JeffreyPalmer/svelte-d3-examples"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please feel free to contact me if you have any questions or comments.&lt;/p&gt;




&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;If this were really a general purpose chart library, you’d need to pass these functions in to this component, in the same way that generic sorting algorithms take comparison function parameters. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>d3js</category>
      <category>visualization</category>
      <category>typescript</category>
      <category>svelte</category>
    </item>
    <item>
      <title>Creating the Visual Studio Code 2020 Year in Review</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Thu, 18 Feb 2021 23:45:40 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/creating-the-visual-studio-code-2020-year-in-review-jpo</link>
      <guid>https://dev.to/jeffreypalmer/creating-the-visual-studio-code-2020-year-in-review-jpo</guid>
      <description>&lt;p&gt;At the end of January I &lt;a href="https://twitter.com/jeffpalmer/status/1354263605902938112"&gt;tweeted out a visualization&lt;/a&gt; that I had put together over the course of the preceding week to celebrate &lt;a href="https://code.visualstudio.com/vscode-day"&gt;Visual Studio Code Day&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DDxhXNpX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/vs-code-2020-year-in-review-v1.2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DDxhXNpX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/vs-code-2020-year-in-review-v1.2.png" alt="2020 Visual Studio Code Year in Review"&gt;&lt;/a&gt;&lt;p&gt;&lt;br&gt;
&lt;a href="http://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/vs-code-2020-year-in-review-v1.2.png"&gt;2020 Visual Studio Code Year in Review&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;I received some positive feedback from the community, which was really great, and I also got some questions about how I’d made it. I promised to provide more detail in a blog post (on a blog that I didn’t actually have 😆), so here we are. Better late than never, I suppose.&lt;/p&gt;

&lt;p&gt;In this series of blog posts I’m going to cover why I made a 2020 VS Code Year in Review visualization, the inspiration behind it, the tools I used to build it, and some of the finer details of its construction. I hope you find it interesting!&lt;/p&gt;
&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;At the beginning of the year I was thinking about starting a new project that would allow me to experiment with a bunch of different technologies that I had been interested in. Specifically, I had been using Svelte for the last year and had really enjoyed it, and after watching &lt;a href="https://www.youtube.com/playlist?list=PLXiRqCwhpnehz5z0gpsmcb2l8vRBSi14M"&gt;Paul Butler’s YouTube series about combining D3 with Svelte&lt;/a&gt; I was eager to give it a try. Additionally, I had just heard about CSS Grid and thought that might be interesting to experiment with as a framework for structured visualizations.&lt;/p&gt;

&lt;p&gt;I had also just read &lt;a href="https://gh.clickhouse.tech/explorer/"&gt;Everything You Ever Wanted To Know About GitHub (But Were Afraid To Ask)&lt;/a&gt;&lt;sup id="fnref:1"&gt;1&lt;/sup&gt; and was excited by the prospect of digging into that dataset. The idea that the entire &lt;a href="https://www.gharchive.org/"&gt;GitHub Archive&lt;/a&gt; dataset was available in a &lt;a href="https://clickhouse.tech/"&gt;ClickHouse&lt;/a&gt; database that could be easily queried via SQL seemed too interesting to pass up.&lt;/p&gt;

&lt;p&gt;Finally, over the past year I had learned to love Visual Studio Code and had been using it daily (sorry Emacs!). After learning that VS Code was one of the most active open source projects on GitHub, I thought it might be interesting to use that project as a lens into the GitHub dataset. Once I started digging around I realized that Visual Studio Code Day was on January 26th, which left me about a week to pull something together. This timing seemed ideal, and at the same time doable, so I set that as my deadline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Understanding the Data
&lt;/h2&gt;

&lt;p&gt;Before I started coding I took a look at what data was available in the ClickHouse GitHub Archive. I wanted to be able to get data directly from ClickHouse and avoid having to write a bunch of glue code to pull in information from other sources. (For example, I thought of some really interesting &lt;code&gt;git&lt;/code&gt; commit history metrics that could be surfaced, but they would have required the processing and integration of data outside of ClickHouse.)&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://github-sql.github.io/explorer/#how-to-choose-the-structure-for-the-data"&gt;the end of the Alexey’s article&lt;/a&gt; there’s a section on how the data was processed and loaded. All of the &lt;a href="https://docs.github.com/en/developers/webhooks-and-events/github-event-types"&gt;GitHub events&lt;/a&gt; that were captured are described, as well as how they were mapped to the ClickHouse schema. I ended up choosing three types of events that all GitHub users would be familiar with:&lt;sup id="fnref:2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull Requests&lt;/li&gt;
&lt;li&gt;Branches&lt;/li&gt;
&lt;li&gt;Issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pull requests and branches seemed interesting as they’re the meat and potatoes of how work gets done on GitHub and have a direct relationship with code contributions.&lt;/p&gt;

&lt;p&gt;Issues, on the other hand, seemed like another story. I took a look at the VS Code repo and saw that they had 4.4k issues open, and over 100k closed! That is a ton of issue volume, and I was a little worried that there I was dealing with garbage data of some kind. Then I realized that, even if there is some bot activity causing those bug counts to be so high, that’s the reality of the project. So, issues it was.&lt;/p&gt;
&lt;h3&gt;
  
  
  Timezone Considerations
&lt;/h3&gt;

&lt;p&gt;I started by writing some ad-hoc SQL queries to understand data distributions across these events and to look for any oddities in the data. Part of this was to understand who the top contributors were, and how activity was spread across the hours of the day. It became pretty clear that I was likely dealing with a timezone-related issue, as the majority of project effort seemed to be happening in the middle of the night (according to my timezone, at least).&lt;/p&gt;

&lt;p&gt;Once I took a look at the profiles of the top project contributors I noticed something. For example, &lt;a href="https://github.com/bpasero"&gt;Benjamin Pasero&lt;/a&gt;, the primary Pull Request participant in 2020, had the following information on his GitHub profile:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am a software engineer at Microsoft in Zurich, Switzerland since 2011. Our team started VS Code when it was still called Monaco.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay, so it seemed likely that a large part of the team was in Europe. That would explain why the hourly workload appeared as it did. I re-pulled the hourly data, this time converting to CET during aggregation. The results looked a lot more like a normal workday, so I decided to use CET as the primary timezone for all data. Luckily ClickHouse &lt;a href="https://clickhouse.tech/docs/en/sql-reference/functions/date-time-functions/"&gt;makes this easy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, here’s the SQL to determine the date in 2020 with the most closed pull requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="n"&gt;toStartOfDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CET'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;most_closed_date&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_closed_actions&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;github_events&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;repo_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'microsoft/vscode'&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;toYear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CET'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'PullRequestEvent'&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'closed'&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;most_closed_date&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;total_closed_actions&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point I knew that I’d be able to get whatever data I needed out of ClickHouse. Now I needed to figure out how to represent it visually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Inspiration
&lt;/h2&gt;

&lt;p&gt;So, I had a timeline and a number of technical goals that I wanted to achieve. Given the short timeline I knew that I wouldn’t be able to successfully apply several new technologies while at the same time creating a compelling and innovative novel visualization. I needed to find an existing visualization to use as a template for the visual structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Felton 2013 Annual Report
&lt;/h3&gt;

&lt;p&gt;I’d always been a fan of &lt;a href="https://feltron.com"&gt;Nicholas Felton’s&lt;/a&gt; work focused on the “quantified self,” and in particular found his &lt;a href="http://feltron.com/FAR13.html"&gt;2013 Annual Report&lt;/a&gt; to be particularly compelling. I thought that there was likely a pretty direct mapping between the communication metrics he presented and those that I could calculate for the VS Code software development process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RZHydw5r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/FAR2013-P6_hua38caf160026ed823dc9a5e27c736e33_216735_800x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RZHydw5r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/FAR2013-P6_hua38caf160026ed823dc9a5e27c736e33_216735_800x0_resize_q75_box.jpg" alt="Detail page from the 2013 Felton Annual Report"&gt;&lt;/a&gt;&lt;p&gt;Detail page from the 2013 Felton Annual Report &lt;a href="http://feltron.com/FAR13.html"&gt;(Nicholas Felton)&lt;/a&gt;&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;Additionally, the report’s distinctive visual style would force me to pay attention to the micro-decisions that had made when during its creation, which would be a great way to structure my experiments with Svelte and D3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mapping the Specifics
&lt;/h3&gt;

&lt;p&gt;With the visual framework established, it was time to take a closer look to figure out which parts mapped best to the GitHub data.&lt;/p&gt;

&lt;p&gt;I decided to map the original data metrics as follows, using pull requests as an example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Original Metric&lt;/th&gt;
&lt;th&gt;VS Code Metric&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total participants&lt;/td&gt;
&lt;td&gt;&lt;em&gt;same&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top N participants&lt;/td&gt;
&lt;td&gt;&lt;em&gt;same&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date with most interactions&lt;/td&gt;
&lt;td&gt;Date with most opened PRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Days without interactions&lt;/td&gt;
&lt;td&gt;Date with most closed PRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate send/receive activity by hour&lt;/td&gt;
&lt;td&gt;Aggregate PR open/close activity by hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total sent&lt;/td&gt;
&lt;td&gt;Total opened PRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total received&lt;/td&gt;
&lt;td&gt;Total closed PRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate activity by month&lt;/td&gt;
&lt;td&gt;&lt;em&gt;same&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total senders&lt;/td&gt;
&lt;td&gt;Total PR openers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total receivers&lt;/td&gt;
&lt;td&gt;Total PR closers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Various type-specific metrics&lt;/td&gt;
&lt;td&gt;Reopened PRs&lt;sup id="fnref:3"&gt;3&lt;/sup&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Longest gap in interactions&lt;/td&gt;
&lt;td&gt;&lt;em&gt;same&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Getting the Data
&lt;/h2&gt;

&lt;p&gt;At this point I knew what data I needed, so I started writing SQL. One of the benefits of one time development is that you can make a mess and not really care. Well, I certainly made a mess of my data retrieval code, but it got the job done.&lt;sup id="fnref:4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Archive Data in ClickHouse
&lt;/h3&gt;

&lt;p&gt;Once I started getting query results, I needed to find a way to automatically save them to a format that could be easily parsed and integrated. It turns out you can write ClickHouse SQL statements that will save query results as JSON data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actor_login&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_participants&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;github_events&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;repo_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'microsoft/vscode'&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;toYear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CET'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CreateEvent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DeleteEvent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PullRequestEvent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'IssuesEvent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="s1"&gt;'/data/total-participants.json'&lt;/span&gt;
&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which will generate a JSON file that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"total_participants"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UInt64"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"total_participants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"13749"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"rows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"statistics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"elapsed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.001163885&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rows_read"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;611192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11183744&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seemed perfect until I remembered that I was running the ClickHouse client in Docker&lt;sup id="fnref:5"&gt;5&lt;/sup&gt; which meant that the generated files were ending up in the Docker container. Five minutes of Googling later and I’d remembered how to mount local directories in Docker and had added it to my wrapper script. Now my data was being written to a local &lt;code&gt;data&lt;/code&gt; directory on my Mac. 🎉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nv"&gt;LOCAL_DATA_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/data"&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DATA_DIR&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/data &lt;span class="se"&gt;\&lt;/span&gt;
    yandex/clickhouse-client:latest &lt;span class="nt"&gt;--secure&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ultimately this is how I obtained all of the data. Everything was saved off and then fed to TypeScript for parsing and further structuring/consolidation. Fine for a one-off, but as a result I couldn’t create visualizations for any other repository, even though I realized that that would be pretty interesting. I’ll likely revisit the data retrieval parts of this project in the near future to make that possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Augmenting User Data
&lt;/h3&gt;

&lt;p&gt;As I mentioned earlier, I wanted to avoid writing a bunch of glue code to integrate data from other sources. In the end I really only needed to do this to transform GitHub usernames into real names (and I didn’t really &lt;em&gt;need&lt;/em&gt; to do that—I just wanted the Top N lists to look a little more “human”). Given that I was only dealing with the top twenty-ish contributors, I ended up just doing this by hand using the &lt;a href="https://docs.github.com/en/rest/reference/users#get-a-user"&gt;GitHub &lt;code&gt;/users/{username}&lt;/code&gt; REST API endpoint&lt;/a&gt;. I manually ran the Top N user queries and stored the responses in a map, which I then joined to other data as necessary via TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;So at this point I had all of the data that I needed, and it was time to start building the visualizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS Grid
&lt;/h3&gt;

&lt;p&gt;The layout heavy lifting was done by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout"&gt;CSS Grid&lt;/a&gt;, which auto-sized the content in the All Events, Pull Requests, Issues, and Branches rows so that everything matched perfectly across columns. This was an absolute joy to work with, and once I got the initial structure into place I never really had to think about it again. I suppose most developers familiar with CSS are used to grids by now, but this was my first time using them and it certainly won’t be the last.&lt;/p&gt;

&lt;p&gt;As an example, this was the css grid structure for the columns to the right of the Volume visualization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.three-by-ten&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min-content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;370px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;grid-auto-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then simply had to drop &lt;code&gt;div&lt;/code&gt; elements into that container and everything flowed as expected. The only challenge that I remember having was wrapping my head around the column/row flow model at the very beginning. I found &lt;a href="https://css-tricks.com/snippets/css/complete-guide-grid/"&gt;this guide&lt;/a&gt; to be a really helpful reference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Svelte and D3
&lt;/h3&gt;

&lt;p&gt;Developing these visualizations using Svelte and D3 was the bulk of my effort. There were a lot of lessons learned which I’m going to cover in a series of other posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jpalmer.dev/2021/02/the-2013-felton-bar-chart/"&gt;Implementing the Felton 2013 Bar Chart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/"&gt;Implementing the Felton 2013 Histogram — Variant 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/"&gt;Implementing the Felton 2013 Histogram — Variant 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jpalmer.dev/2021/03/the-2013-felton-volume-chart/"&gt;Implementing the Felton 2013 Volume Chart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Formatting Libraries
&lt;/h3&gt;

&lt;p&gt;Aside from the visualization work done in D3, I used a couple of libraries to format some of the metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mastermunj/to-words"&gt;To Words&lt;/a&gt;: Converts numbers into words.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Nightapes/HumanizeDuration.ts"&gt;Humanize Duration (TypeScript)&lt;/a&gt;: Converts durations into words&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most everything else I used some variant of &lt;a href="https://github.com/d3/d3-format"&gt;D3 number formatters&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString"&gt;&lt;code&gt;Date.toLocaleString&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString"&gt;&lt;code&gt;Date.toLocaleTimeString&lt;/code&gt;&lt;/a&gt;.&lt;sup id="fnref:6"&gt;6&lt;/sup&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Typeface
&lt;/h3&gt;

&lt;p&gt;The typeface used in the report is &lt;a href="https://brick.im/fonts/sourcesanspro/"&gt;Source Sans Pro&lt;/a&gt;, designed by &lt;a href="https://github.com/pauldhunt"&gt;Paul D. Hunt&lt;/a&gt;. I tried a variety of typefaces during development and found that I preferred the number styling of Source Sans Pro, especially the dotted zero. I enabled this via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-variant-numeric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;slashed-zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order for this to work as expected, I needed to use a non-Google typeface CDN &lt;a href="https://github.com/google/fonts/issues/1335"&gt;due to apparent optimization on Google’s side&lt;/a&gt;. I ended up using &lt;a href="https://brick.im/"&gt;Brick&lt;/a&gt; because they host unmodified versions that support OpenType features and have &lt;a href="https://github.com/alfredxing/brick/wiki/Usage"&gt;an elegantly simple API&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color Palette
&lt;/h3&gt;

&lt;p&gt;Finally, I thought it would be interesting to use the color palette of the most popular VS Code theme as the color palette of the visualization. It’s pretty easy to see &lt;a href="https://marketplace.visualstudio.com/search?term=theme&amp;amp;target=VSCode&amp;amp;category=All%20categories&amp;amp;sortBy=Installs"&gt;themes ranked by installs&lt;/a&gt;, and at that time &lt;a href="https://binaryify.github.io/OneDark-Pro/"&gt;One Dark Pro&lt;/a&gt; was the most-installed non-icon theme.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cYetdFaa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/one-dark-pro-snippet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cYetdFaa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/creating-the-visual-studio-code-2020-year-in-review/one-dark-pro-snippet.png" alt="Example of the One Dark Pro theme"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Example of the One Dark Pro theme
&lt;a href="https://binaryify.github.io/OneDark-Pro/"&gt;(theme site)&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;As it turns out the color palettes used in One Dark Pro and the original report are quite similar, so it was an easy mapping that ended up looking great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Done!
&lt;/h2&gt;

&lt;p&gt;With that last change everything was ready. I shared the result on Twitter, sending it out the night before hoping that it might get seen before things got started. Things were a little quiet until the next morning when someone on the VS Code team retweeted my message, at which point I got some really great feedback.&lt;/p&gt;

&lt;p&gt;Thanks for taking the time to read a little about this project. If you have any thoughts or feedback you’d like to share, please feel free to contact me on Twitter or &lt;a href="https://jpalmer.dev/contact"&gt;directly on my site&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Milovidov A., 2020. Everything You Ever Wanted To Know About GitHub (But Were Afraid To Ask), &lt;a href="https://gh.clickhouse.tech/explorer/"&gt;https://gh.clickhouse.tech/explorer/&lt;/a&gt; ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I had hoped to include project releases as well, perhaps as an overlay on the various visualizations to provide additional insight into major project activities over the course of 2020, but I just wasn’t able to get something compelling completed in time. I’ll probably revisit that information in the future, as I feel like you can already see the build-up to major releases in the data, so it would be nice to be able to provide that additional context. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;There wasn’t an obvious “reopened” metric for branches, so I thought it might be interesting to see how many branches were opened by &lt;a href="https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/"&gt;dependabot&lt;/a&gt;, the dependency management assistant that’s now part of GitHub. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;TODO: Fix data retrieval 😭 ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;I forgot to mention that all of my data investigation takes place in Emacs, running &lt;a href="https://www.emacswiki.org/emacs/SqlMode"&gt;sql-mode&lt;/a&gt; with the &lt;a href="https://github.com/rschwarz/sql-clickhouse"&gt;ClickHouse&lt;/a&gt; extension. This is a little wonky, and because I’m running on a Mac I needed this wrapper script to make things work as expected. ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;I fully acknowledge that it is very weird to convert all data to CET and then choose to format all times with American AM/PM nonsense. My only excuse is that in the interest of time I discarded all locale considerations. 😬 ↩︎&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>vscode</category>
      <category>dataviz</category>
      <category>showdev</category>
      <category>github</category>
    </item>
    <item>
      <title>My Development Setup and Tools</title>
      <dc:creator>Jeff Palmer</dc:creator>
      <pubDate>Tue, 16 Feb 2021 19:35:53 +0000</pubDate>
      <link>https://dev.to/jeffreypalmer/my-development-setup-and-tools-5fmp</link>
      <guid>https://dev.to/jeffreypalmer/my-development-setup-and-tools-5fmp</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HS6Pke0l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/development-setup-and-tools/yancy-min-git-history.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HS6Pke0l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jpalmer.dev/2021/02/development-setup-and-tools/yancy-min-git-history.jpg" alt=""&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;
&lt;a href="https://unsplash.com/photos/842ofHC6MaI"&gt;Photo by Yancy Min&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;I’ve always found it interesting to see how others get things done, so here’s a little bit about my development setup, and how I like to go about building things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;I’ve been programming since I was about 12 years old, and I’ve learned some hard lessons about ergonomics in that time. At this point I optimize for a comfortable and quiet development environment, and that’s pretty much it. I use a &lt;a href="https://www.fully.com/standing-desks/jarvis.html"&gt;Jarvis standing desk&lt;/a&gt; along with a Herman Miller Aeron chair. My Mac is effectively silent, and I have a 27 inch 4K Dell monitor that makes screen reading very comfortable. I had a couple of different lighting options but have landed on the &lt;a href="https://www.benq.com/en-us/lamps/computer-desklamp/screenbar.html"&gt;BenQ ScreenBar&lt;/a&gt; which attaches to the top of my monitor and provides a surprisingly satisfying low-profile task lighting experience.&lt;/p&gt;

&lt;p&gt;Programming for extended periods led me to suffer from RSI, and in an attempt to alleviate these issues I started exploring the &lt;a href="https://www.reddit.com/r/MechanicalKeyboards/"&gt;mechanical keyboards rabbit hole&lt;/a&gt;. My first experiment was to build an &lt;a href="https://atreus.technomancy.us/"&gt;Atreus&lt;/a&gt;, which helped me learn about keyboard construction and the ins and outs of &lt;a href="https://qmk.fm/"&gt;QMK and custom layouts&lt;/a&gt;. It’s a great keyboard that I still really love, but the lack of ability to “tent” the keyboard to reflect my wrist orientation was keeping me from really getting the full ergonomic benefit. So, I looked for a similar keyboard that was physically split and ended up with my current model, the &lt;a href="https://keeb.io/collections/iris-split-ergonomic-keyboard"&gt;Keeb.io Iris&lt;/a&gt;. It works well for me since the separate sides can be easily adjusted to match the angle of my wrists, and it runs QMK so migrating my custom layout from the Atreus was a snap.&lt;/p&gt;

&lt;p&gt;I also swap between an Apple Magic Trackpad that I’ve had for ten years, and a &lt;a href="https://www.logitech.com/en-us/products/mice/mx-ergo-wireless-trackball-mouse.html"&gt;Logitech MX Ergo Trackball&lt;/a&gt;. The Logitech is amazing but since it is a right-handed model it can’t be swapped to my left side; eventually my right hand will bother me enough that I need to pull out the trackpad and switch to my left hand.&lt;/p&gt;

&lt;p&gt;Okay, on to the software side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software
&lt;/h2&gt;

&lt;p&gt;If you were raised on the command line like I was, &lt;a href="https://brew.sh/"&gt;HomeBrew&lt;/a&gt; is a requirement to even get started with software development on MacOS. I couldn’t live without it, to the point that I have created a &lt;a href="https://github.com/Homebrew/homebrew-bundle"&gt;HomeBrew Bundle&lt;/a&gt; of my “must-have” packages that I use whenever I’m configuring a new account. Takes a little of the guesswork out of getting started. The other tool that I’ve come to really love is &lt;a href="https://ohmyz.sh/"&gt;Oh My Zsh&lt;/a&gt;. I used to be a &lt;code&gt;bash&lt;/code&gt; user, but at some point a co-worker suggested that I give &lt;a href="https://en.wikipedia.org/wiki/Z_shell"&gt;&lt;code&gt;zsh&lt;/code&gt;&lt;/a&gt; a try and I’ve never looked back.&lt;/p&gt;

&lt;p&gt;For a long time I was an Emacs user, but these days Emacs has basically become an &lt;a href="https://orgmode.org/"&gt;Org Mode&lt;/a&gt; hosting environment. I use Org to keep track of everything (after experimenting with &lt;em&gt;a lot&lt;/em&gt; of alternatives), so that’s not going anywhere. For everything else I have moved to &lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt; (with &lt;a href="https://github.com/whitphx/vscode-emacs-mcx"&gt;an emacs keybinding extension&lt;/a&gt;, of course; I’m not a masochist). I’m still amazed at how fast VS Code became the amazing experience that it is. Really great work.&lt;/p&gt;

&lt;p&gt;I have some tips in the off-chance that they’re helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use a typeface that was developed specifically for programming. I use &lt;a href="https://www.jetbrains.com/lp/mono/"&gt;JetBrains Mono&lt;/a&gt;, but also really like &lt;a href="https://github.com/tonsky/FiraCode"&gt;Fira Code&lt;/a&gt;. They’ve been crafted with particular attention to the types of letter and symbol combinations that are common in programming, and in my opinion they really improve the day-to-day experience of reading code. Make sure to &lt;a href="https://stackoverflow.com/questions/56209769/how-do-i-setup-font-ligatures-for-visual-studio-code"&gt;enable ligatures&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disable mode-specific extensions globally, and only enable them in the workspaces where you need them. This avoids issues with competing extensions which can happen if you experiment with a lot of technologies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust the explorer zoom level as indicated in &lt;a href="https://stackoverflow.com/a/50814454"&gt;this Stack Overflow answer&lt;/a&gt; to change the size of the explorer to something that better suits how you want things. I have my explorer smaller than the default because I don’t actively use it, but when I do I want to see a lot of information.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other tool that I find myself reaching for frequently on a Mac is &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt;. Homebrew is great when you’re running things on your own machine, but when you need to start thinking about deployment, Docker makes that process a lot easier.&lt;/p&gt;

&lt;p&gt;Finally, I use &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; for blogging with my &lt;a href="https://github.com/JeffreyPalmer/blogophonic-hugo-theme"&gt;forked version&lt;/a&gt; of the &lt;a href="https://github.com/formspree/blogophonic-hugo"&gt;Blogophonic theme&lt;/a&gt; that allows it to be installed like a normal Hugo theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Miscellany
&lt;/h2&gt;

&lt;p&gt;I’ve been doing a lot of front-end focused work over the past year or so and it’s been really great to see how the Javascript ecosystem has evolved. The biggest impact for me was to start using &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt; for all of my front-end development. It is &lt;strong&gt;amazing&lt;/strong&gt;. I highly recommend that you check it out.&lt;/p&gt;

&lt;p&gt;There are a ton of javascript charting libraries out there (I’m partial to &lt;a href="https://vega.github.io/vega-lite/"&gt;Vega Lite&lt;/a&gt;), but recently I’ve been focused on building custom information visualizations. The tool that I use to do that is &lt;a href="https://d3js.org/"&gt;D3.js&lt;/a&gt;. When you pair D3 and Svelte’s reactivity it’s a really powerful and satisfying development experience.&lt;/p&gt;

&lt;p&gt;I’m a big fan of &lt;a href="https://bulma.io/"&gt;Bulma&lt;/a&gt; when I want to quickly prototype something. I’ve recently started using &lt;a href="https://tailwindcss.com/"&gt;Tailwind CSS&lt;/a&gt; and, although I haven’t used it enough to have a strong opinion, I’m liking what I have seen so far.&lt;/p&gt;

&lt;p&gt;So, that’s everything that I thought it might be interesting to cover. If you have any questions or thoughts, or would like to know more about an area that I didn’t cover, please don’t hesitate to &lt;a href="https://jpalmer.dev/contact"&gt;contact me&lt;/a&gt;!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
