HTML5 Boilerplate v7.1 Released

Hot on the heels of our last major release, we just released a new minor version of HTML5 Boilerplate, v7.1.0. The biggest changes in this release are an upgraded version of Modernizr (which is on a steady release schedule again) and an update to the Google Analytics snippet/docs.

Here are the full release notes:

  • Update Modernizr to 3.7.1 (#2121)
  • Update Analytics docs and snippet (#2118)
  • Minor docs updates (#2115)
  • Minor devdeps updates (#2114)
  • More succinct way of writing the IE conditional statement (#2113)

Download the latest from github or install it from npm.

HTML5 Boilerplate v7 released

After a few months of starts and stops while I wrestled my SVG book into submission and released main.css as a standalone project, I’m pleased to announce that HTML5 Boilerplate v7 was released on Friday February 8, 2019.

The biggest change is the way that we include the aforementioned main.css. Since it’s now a standalone project we include it as an npm dependency at build-time. This change still allows people (including HTML5 Boilerplate itself) to consume main.css as a whole, but also allows the component styles to be used individually in different, and hopefully interesting, ways. You can access the component styles to mix and match directly through the main.css npm package.

We also dropped support for IE9/10. That was not as cathartic as dropping support for IE6 or IE8, but it was still nice. It feels like we’re living in the future.

The docs also got a big upgrade. We could use more help there, but we’ve done a couple of really good passes at the documentation and I think it’s in a good place now.

Thanks again to Christian Oliff for his work on this release. He’s proven to be an invaluable team member over the past couple of years. I really appreciate his help on tasks and his attention to detail.

And, as always, thanks to our many contributors. You are the best!

Are you interested in helping out? Check out our current issues, submit an idea for a new feature or look at one of the other H5BP projects to see if there’s something else you’re interested in helping with. It’s fun.

Here’s the full release notes:

7.0.0 (February 8, 2019)

  • Drop support for IE9/IE10 (#2074)
  • Move the CSS to a separate repo (#2066)
  • Add theme-color meta tag to index.html (#2074)
  • Add ‘install with yarn’ steps to README (#2063)
  • Improved Webmanifest (#2060)
  • Upgrade Normalize to 8.0.1 (#2104)
  • Update .htaccess (#2110)
  • Remove instances of shrink-to-fit=no (#2103)
  • Removes “display”: “standalone” from manifest (#2096)
  • Big Docs update – Fixed links, removed IE9/IE10 specific info, made touch icons section more concise, add details on security.txt and more tidying up (#2074, #2065, #2062)

Download the latest from github or install it from npm.

Mastering SVG Bonus Content: A D3 Line Chart

My latest book, Mastering SVG, has a whole chapter on D3. It had been several years since I’d worked with D3 so it was a lot of fun to dive back in and write some D3 code for the book. I did three long examples. It was a lot of fun to come up with the visualizations.

It was so much fun, in fact, I’ve continued to work with D3, even though the book is out in the world. This article will go over one such example I did for my comics site.

This chart illustrates the progression of the record price paid for an American comic book over the past 50+ years. It’s a line chart and with some libraries, generating a basic line chart would be a few lines of code. This D3 example is more than a few lines of code, but it also illustrates how D3 offers control over every aspect of the visualization. It really shows the value of working with a rich API like the one offered by D3.

The code for this example is available on Github. You can view an interactive demo on github.io.

Let’s look at how this visualization was put together.

D3

In case you’re not familiar with it, D3 (for Data-Driven Documents) is a powerful library for working with SVG and doing data visualizations. It’s also one of the most popular open source projects in the world. It marries the inherent power of SVG with a number of data manipulation tools and a host of SVG-manipulation utilities to create one of the most robust libraries out there for work on the web.

It’s available as a download from d3js.org, via npm or via cdnjs.

If you’ve never worked with D3 then, be warned, this article is not a gentle introduction. I’s a full fledged visualization and not an intro demo. I do promise to go over everything in detail so hopefully you’ll be able to catch up even if you have no D3 experience. And, if you do catch on, you’ll actually have some experience with many core concepts, so you’ll be ready to tackle your own visualizations.

From an API perspective, if you’re familiar with chaining JavaScript method calls and have worked with a fluent interface like jQuery, then major parts of D3 should feel familiar to you. They even share some API signatures.

The Data

The data is as follows. sales is an array of objects representing each sale. The properties are as follows:

  • title The comic’s title
  • issue The comic’s issue number
  • grade The numerical grade of the comic. This can either be a grade on the modern 1-10 scale, a string describing the old scale (e.g. “NM” for Near Mint or VF for “Very Fine”) or another anecdote regarding the grade (“high grade”)
  • buyer If known, the buyer of the comic
  • note An optional note about the sale
  • date The date of the sale
  • seller The seller of the comic
  • price The price paid for the comic.
  • goodDate A Boolean indicating whether or not the date is precise. In putting this data together I’ve run into roadblocks getting precise dates on many of the sales.
{
  "sales": [
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "9.0",
      "buyer": "Metropolis (for Ayman Hariri)",
      "note":"",
      "date": "2014-08-24",
      "seller": "Pristine Comics on eBay",
      "price": "3207852",
      "goodDate": true
    },
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "9.0",
      "buyer": "Ayman Hariri",
      "note":"Cage Copy",
      "date": "2011-11-30",
      "seller": "ComicConnect",
      "price": "2161000",
      "goodDate": true
    },
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "8.5",
      "buyer": "",
      "note":"",
      "date": "2010-03-29",
      "seller": "ComicConnect",
      "price": "1500000",
      "goodDate": true
    },
    {
      "title": "Detective Comics",
      "issue": "27",
      "grade": "8.0",
      "buyer": "",
      "note":"",
      "date": "2010-02-25",
      "seller": "Heritage",
      "price": "1075000",
      "goodDate": true
    },
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "8.0",
      "buyer":"",
      "note": "Kansas City",
      "date": "2010-02-22",
      "seller": "ComicConnect",
      "price": "1000000",
      "goodDate": true
    },
    {
      "title": "Flash Comics",
      "issue": "1",
      "grade": "9.6",
      "note":"Church copy",
      "buyer": "JP the Mint",
      "date": "2004-01-01",
      "seller": "unknown",
      "price": "350000",
      "goodDate": false
    },
    {
      "title": "Marvel Comics",
      "issue": "1",
      "grade": "9.0",
      "note":"Pay Copy",
      "buyer": "JP the Mint",
      "date": "2003-01-01",
      "seller": "Steve Geppi",
      "price": "350000",
      "goodDate": true
    },
    {
      "title": "Captain America Comics",
      "issue": "1",
      "grade": "9.6",
      "note": "Allentown",
      "buyer": "John Verzyl",
      "date": "2001-01-01",
      "seller": "unknown",
      "price": "260000",
      "goodDate": false
    },
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "Now: CGC 8.5",
      "note":"current CGC 8.5 copy",
      "buyer": "Daniel Kramer",
      "date": "1995-01-01",
      "seller": "PCE",
      "price": "137500",
      "goodDate": false
    },
    {
      "title": "Detective Comics",
      "issue": "27",
      "grade": "8.5",
      "note": "Church",
      "buyer":"",
      "date": "1994-01-01",
      "seller": "Dave Anderson?",
      "price": "125000",
      "goodDate": false
    },
    {
      "title": "Detective Comics",
      "issue": "27",
      "grade": "high grade",
      "note": "'other high grade' copy",
      "buyer":"",
      "date": "1993-01-01",
      "seller": "unknown",
      "price": "101000",
      "goodDate": false
    },
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "78",
      "note":"",
      "buyer": "Metropolis (for actor Nic Cage)",
      "seller": "Sotheby's",
      "price": "82500",
      "date": "1992-09-30",
      "goodDate": true
    },
    {
      "title": "Detective Comics",
      "issue": "27",
      "grade": "NM-MT",
      "note": "Allentown",
      "buyer":"Dave Anderson",
      "price": "80000",
      "seller": "Metropolis",
      "date": "1990-01-01",
      "goodDate": false
    },
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "NM",
      "buyer":"Dave Anderson",
      "note": "Church Copy",
      "seller": "John Snyder",
      "price": "25000",
      "date": "1984-01-01",
      "goodDate": false
    },
    {
      "title": "Marvel Comics",
      "issue": "1",
      "grade": "",
      "note":"",
      "buyer": "Steve Geppi",
      "date": "1979-10-08",
      "seller": "John Snyder",
      "price": "17500",
      "goodDate": true
    },
    {
      "title": "Marvel Comics",
      "issue": "1",
      "grade": "",
      "note":"",
      "buyer": "John Snyder",
      "date": "1979-01-01",
      "seller": "unknown",
      "price": "13000",
      "goodDate": false
    },
    {
      "title": "Marvel Comics",
      "issue": "1",
      "grade": "",
      "note":"",
      "buyer": "",
      "date": "1977-01-01",
      "seller": "Robert Crestohl?",
      "price": "7500",
      "goodDate": false
    },
    {
      "title": "Motion Picture Funnies Weekly",
      "issue": "1",
      "grade": "",
      "buyer": "",
      "note":"",
      "date": "1976-01-01",
      "seller": "unknown",
      "price": "6300",
      "goodDate": false
    },
    {
      "title": "Whiz",
      "issue": "2",
      "grade": "",
      "note":"Reilly Copy",
      "buyer": "Burl Rowe",
      "date": "1974-01-04",
      "seller": "Comics & Comix",
      "price": "2000",
      "goodDate": true
    },
    {
      "title": "Action",
      "issue": "1",
      "grade": "",
      "note":"",
      "buyer": "Bruce Hamilton",
      "date": "1973-04-02",
      "seller": "Gene Henderson",
      "price": "1000",
      "goodDate": true
    },
    {
      "title": "Action",
      "issue": "1",
      "grade": "",
      "note":"",
      "buyer": "Theo Hostein",
      "date": "1973-04-22",
      "seller": "Bruce Hamilton",
      "price": "1500.00",
      "goodDate": true
    },
    {
      "title": "Action",
      "issue": "1",
      "grade": "",
      "note":"",
      "buyer": "Mitch Mehdy",
      "date": "1973-05-01",
      "seller": "Theo Hostein",
      "price": "1801.26",
      "goodDate": true
    },
    {
      "title": "Marvel Comics",
      "issue": "1",
      "grade": "",
      "buyer": "",
      "note":"",
      "date": "1968-01-01",
      "seller": "Howard Rogolfsky",
      "price": "330",
      "goodDate": false
    },
    {
      "title": "Action Comics",
      "issue": "1",
      "grade": "",
      "buyer": "",
      "note":"",
      "date": "1965-01-01",
      "seller": "unknown",
      "price": "250",
      "goodDate": false
    }
  ]
}

The HTML

Next up we have the markup. The head of the document this demo includes the Bootstrap CSS, the Raleway font, and a link to the the style sheet for this particular visualization, main.css.In the body there’s some simple markup that we will populate with JavaScript. The div#target is where the visualization will live. Next, are a couple of checkboxes used to toggle inflation adjusted/nominal results on or off. Finally, there’s a placeholder div#data for the data to be output as a friendly list.

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>A Timeline of World Record Comic Book Sales</title>

    <link href="_assets/css/normalize.css" rel="stylesheet">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
        crossorigin="anonymous">
    <link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
    <link href="_assets/css/main.css" rel="stylesheet">

</head>

<body>
    <div class="container-fluid">
        <div class="row">
            <div class="col-12 nominal" id="target">

            </div>
        </div>
        <div class ="row">
            <div class="col-12">
                  <div class="form-check">
                    <input class="form-check-input" type="checkbox" value="inflation" id="inflationValue">
                    <label class="form-check-label" for="inflationValue">
                      Show Inflation Adjusted Results
                    </label>
                  </div>
                  <div class="form-check">
                    <input class="form-check-input" type="checkbox" value="nominal" id="nominalValue" checked>
                    <label class="form-check-label" for="nominalValue">
                      Show Nominal Results
                    </label>
                  </div>
            </div>
        </div>
        <div class="row mt-5">
            <div class="col-12" id="data">
                <h1>The Data</h1>

            </div>
        </div>
    </div>


    <script src="node_modules/d3/dist/d3.min.js"></script>
    <script src="node_modules/d3-fetch/dist/d3-fetch.min.js"></script>
    <script src="_assets/js/inflation.js"></script>
    <script src="node_modules/moment/min/moment.min.js"></script>
    <script src="_assets/js/comics.js"></script>



</body>

</html>

The Styles

Next we have the styles for the visualization. There are basically three sets of styles:

  • Rules for the basic text styles. We use the Raleway font. I like Raleway.
  • Rules defining both nominal and inflation-adjusted fill and stroke styles for SVG elements.
  • Rules defining our Bootstrap tooltip styles
/* Text Styles */
text {
    font-family: Raleway;
    font-size: .8em;
    text-anchor: middle;
   
}
text.legend{
    font-size: 1.25em;
    fill: #000;
}
.y.axis .tick text {
    text-anchor: end;
    font-size: 12px;
}
/* lines and circles */
.line {
    fill:none;
    stroke: rgb(0, 100, 201);
    stroke-width: 2;
}
.line.inflation {
    stroke: rgba(0, 100, 201, .5);
}
.dot {
    fill: rgba(255, 165, 0, .5);
    stroke: rgb(100, 0, 201);
}
.axis .tick line {
    stroke: #ccc;
}
.dot,
.line {
    visibility: hidden;
}
.nominal .dot.nominal,
.nominal .line.nominal,
.inflation .dot.inflation,
.inflation .line.inflation {
    visibility: visible;
}
.form-check {
    margin-left: 100px;
}
/* tooltip customization */
.tooltip-inner {
    background-color: rgb(0, 100, 201);
}
.tooltip.bs-tooltip-right .arrow:before {
    border-right-color: rgb(0, 100, 201) !important;
}
.tooltip.bs-tooltip-left .arrow:before {
    border-right-color: rgb(0, 100, 201) !important;
}
.tooltip.bs-tooltip-bottom .arrow:before {
    border-right-color: rgb(0, 100, 201) !important;
   }

.tooltip.bs-tooltip-top .arrow:before {
    border-right-color: rgb(0, 100, 201) !important;
}

.inflation .tooltip-inner {
    background-color: rgba(0, 100, 201, .8);
}

.inflation.tooltip.bs-tooltip-right .arrow:before {
    border-right-color: rgba(0, 100, 201, .8) !important;
}

.inflation.tooltip.bs-tooltip-left .arrow:before {
    border-right-color: rgba(0, 100, 201, .8) !important;
}

.inflation.tooltip.bs-tooltip-bottom .arrow:before {
    border-right-color: rgba(0, 100, 201, .8) !important;
}

.inflation.tooltip.bs-tooltip-top .arrow:before {
    border-right-color: rgba(0, 100, 201, .8) !important;
}

The JavaScript

And now we get to the JavaScript. This file is big and I’ll go through the whole thing in a lot of detail, so please grab some popcorn, open up the source code in another window or in a code editor and let’s get started.

The top of the file includes many constants for use in the visualization.

  • margin defines a buffer around the visualization
  • width and height define the height and width of the SVG element. These are used in multiple calculations.
  • yMargin and xMargin arr two properties used to calculated metrics, inclusive of the defined x and y margins.
  • svg is our core D3 SVG element, created using the previously defined width and height. This illustrates the common pattern of working with D3. You have a selection (either a DOM element or a data selection) and you chain methods on it, manipulating the selection in a variety of ways.
  • g is a group element appended to the svg element, translated to the top/left of the visualization (as defined by the margin properties.)
  • radius is an arbitrary value for the radius of circles used in the visualization
  • parseTime is a local reference to the D3.timeParse method, configured with a pre-defined formatting string.
  • tooltip is a small function to append a Bootstrap tooltip into the DOM. This is done using D3’s select, append, attr and style utilities.
  • inflationTooltip is a small function to append a Bootstrap tooltip into the DOM, complete with styling for inflation. Yes, I’m being slightly lazy. I should have one function with an option for inflation. Sorry!
  • list is an unordered list element appended to the DOM, in order to hold the data written out in list form.
  • x is a configured D3 scale function to generate the time-based x axis of the visualization. Scales are the pixel representation of the data. All conversions from data to pixels are done using scales.
  • y is a configured D3 scale function to generate the y axis of the visualization.
  • line and inflationLine are two functions used to draw line graphs, based on the data set. This is where D3 actually draws the line charts for our visualization.

const margin = {
  'top': 100,
  'right': 20,
  'bottom': 30,
  'left': 100
};
const width = 1440;
const height = 768;
const yMargin = margin.top + margin.bottom;
const xMargin = margin.right + margin.left;

const svg = d3.select('#target')
  .append('svg')
  .attr('width', width)
  .attr('height', height);

const g = svg.append('g')
  .attr('transform', `translate(${margin.left},${margin.top})`);
const radius = 5;
const parseTime = d3.timeParse('%Y-%m-%d');

const tooltip = d3.select('body')
  .append('div')
  .attr('class', 'tooltip bs-tooltip-right')
  .style('opacity', 0);
tooltip.html(`
  <div class="arrow"></div>
  <div class="tooltip-inner">
  </div>`);
const inflationTooltip = d3.select('body')
  .append('div')
  .attr('class', 'tooltip bs-tooltip-right inflation')
  .style('opacity', 0);
inflationTooltip.html(`
  <div class="arrow"></div>
  <div class="tooltip-inner">
  </div>`);

const list = d3.select('#data')
  .append('ul').attr('class','list-group');

const x = d3.scaleTime()
  .rangeRound([0, width - xMargin]);

const y = d3.scaleLinear()
  .rangeRound([height - yMargin, 0]);


const line = d3.line()
  .x((d)=> {
    return x(d.date);
  })
  .y((d)=> {
    return y(d.price);
  });

const inflationLine = d3.line()
  .x((d)=> {
    return x(d.date);
  })
  .y((d)=> {
    return y(d.inflationAdjustedPrice);
  });

Almost the whole of the rest of the visualization happens inside a call to d3.json. d3.json fetches the data (literally, it’s a convenience method built on top of fetch) and then runs a callback function, with data as the single argument.


d3.json('data/books.json').then((data) => { /* everything happens here*/  }

Inside that callback function lots of things happen. We’ll go through each section individually.

Initially we take the data.sales array and sort it using the moment.diff function to compare each of the dates. Once they are sorted we manipulate the data in a forEach loop, adding an inflationAdjustedPrice for every member of the data set. This is done by passing the price and year into a function which returns an inflation adjusted value based on an amount and a timeframe.


  const sales = data.sales.sort((d1, d2) => moment.utc(d1.date).diff(moment.utc(d2.date)));

  sales.forEach((d) => {
    d.date = parseTime(d.date);
    d.price = parseInt(d.price);
    const year = parseInt(moment(d.date).format('YYYY'));
    d.inflationAdjustedPrice = inflation({'amount': d.price, 'year': year});
  });

In this next block we take the previously defined x and y scales and populate them with our data.

The x scale’s domain (the universe of the data being visualized) is set using the sales data, with the start of the earliest year as the minimum value (found using d3.min which returns the minimum value in a set) and the current date as the maximum value.

The y scale’s domain is set using 0 as the lower bound and the max value in the set (found using d3.max,) rounded up to the nearest 500,000. The rounding is done by dividing the max value (in this case just over 3.2 million) by 500,000. That returns around 6.4. Calling math.ceil on that, to get the next highest whole number, gives us 7. Multiplying that number by 500,000, gives us our top range of $3,500,000.

  x.domain([
    d3.min(sales, (d) => {
      return moment(d.date).startOf('year').toDate();
    }), moment().toDate()]);
  y.domain([0, d3.max(sales, (d) => {
    return Math.ceil(d.price / 500000) * 500000;
  })]);
  const years = parseInt(moment().format('YYYY')) - parseInt(moment(x.domain()[0]).format('YYYY'));

Now that the domains are set, it’s time to actually draw the axes. First we append an SVG g (group) element to the DOM and store it in a variable called axis. Then we append two more g elements for each individual axis.

Here is the first place in this demo where D3 really shows the complexity of its API. It’s also here where D3 shows its power.

First we create a g element for the y axis. Then two classes are added to it using D3.attr, “y” and “axis.” Then we use the D3 utility method, call. D3.call is a method that allows you to invoke a function on a D3 selection and then return the modified selection. This allows you to do some actions which might normally interrupt the flow of chained methods. In this case we pass in a call to d3.axisLeft with our previously configured y scale as the argument. axisLeft takes that linear scale, with our configured domain. and generates a legend on the left side of the visualization. We then further configure it, setting the tick size and formatting the tick legends.

Using d3.tickSize here we actually create the lines that run horizontally across the visualization. This is done by setting a negative width equal to the width of the SVG element minus the xMargin, which nicely fits these lines within the bounds of the core visualization. The negative length works here because the tick mark is actually drawn from the right edge of the axis. A positive value would draw from the right edge to the left. A negative value draws itself into the visualization itself.

We format the tick legends using tickFormat, which accepts d3.format as an argument. Here we pass a formatting string, “($~s” into d3.format. This formatting string indicates that d3 should use no sign for positive numbers and “(” for negative numbers (which aren’t relevant in this case, it’s just the indicator I would normally use;) should include the locale currency, “$” and with “s” that it should render the number in decimal notation with a locale specific prefix (for example, $3.5M for the top end of the scale.)

Describing what we just did sounds like this: We start with three chained method calls. The third of those three calls has an additional three chained method calls passed in as an argument. The final method call in that inner chain has a formatting function passed in as an argument. It’s complicated, sure, but because you have entry points to the data, the selection and the rendering at each of those points, you have pinpoint control over the visualization. That’s powerful.

Next we generate the x axis. As before we add a new g element, and add two classes, “x” and “axis.” Then we translate the g element to its place at the bottom of the visualization. Finally we use d3.call again, this time passing in d3.axisBottom with our x scale as the argument. Off of that, we chain d3.ticks, passing in years to generate ticks for each year in our range, d3.tickSize (once again using the negative length trick to create the gridlines in the visualization) and d3.tickPadding (which gives us a little padding to make the numbers more legible against the line.

  let axis = g.append('g')
    .attr('class', 'axis');

  axis.append('g')
    .attr('class', 'y axis')
    .call(d3.axisLeft(y)
      .tickSize(-(width - xMargin)).tickFormat(d3.format('($~s')));
  axis.append('g')
    .attr('class', 'x axis')
    .attr('transform', `translate(0, ${height - yMargin})`)
    .call(d3.axisBottom(x).ticks(years).tickSize(-(height - yMargin)).tickPadding(10));

The next section actually appends the line charts into the SVG element. This is one of the more straightforward interactions with D3 that you can have. This is because the generated line graph is simply points on an SVG path.

To start we append a new g element with a class of “paths.” Then we append two path elements to it. One of these is for inflation adjusted results and the other is for nominal results.

In each case we call the d3.data method on the returned path element. This method call loads the selection (in this case the previously created path element) with the current data. Then we add two classes: “line” and “nominal” or “inflation” depending on whether or not the line chart is using nominal or inflation adjusted numbers. Finally we populate the d attribute with the functions line and inflationLine. These instances of the D3 visualization method use the data attached to the current selection to generates a line graph.


  let paths = g.append('g')
    .attr('class', 'paths');

  paths.append('path')
    .data([sales])
    .attr('class', 'line nominal')
    .attr('d', line);

  paths.append('path')
    .data([sales])
    .attr('class', 'inflation line')
    .attr('d', inflationLine);

The next section is, once again, repetitive. Once again, both blocks do basically the same thing except for some differences relating to the data being inflation adjusted or nominal.

It also illustrates some important concepts in D3.

The first call should be familiar at this point. We append a new g element and give it a class of “dots.” That is stored as dots.

Next up, we do something new. We run d3.selectAll(".dot") d3.selectAll, at its most basic, works like jQuery’s core $ function or the core DOM method document.querySelectorAll. It takes in a CSS selector argument and returns a collection of elements that match that CSS selector. Like jQuery (but not like querySelectorAll) any subsequent calls will operate on all members of the collection.

In this case, there are no elements that match that “dot” class. So, what’s the point? The next two chained method calls, dots.selectAll(".dot").data(sales).enter() are the key. The call to data binds our current data set to the (still empty) selection. Subsequently calling enter populates the previously empty selection with a collection of elements corresponding to each data point. Every action after the call to enter operates on every member of the collection (which corresponds to every member of the data set. ) So when we call append, it adds a circle element for every member of the collection.

For each of those circles we then perform several operations. Let’s look at each

  • we add a couple of classes, dot and nominal/inflation
  • We set the cx, cy (center x and center y) using the x and y scales we previously created. Passing in properties of the d (data) object passed as an argument, returns pixel representations of the data points.

    These scales were also used to create the trendlines themselves in line and inflationLine, so this illustrates on the of the ways that D3 makes it easy to compose visualizations in a very powerful way. Whatever you’re doing, you now have the scale available to render elements on the screen using the same, predefined scale. The circle element we’re using here is a standard SVG element and all of the attributes we’re manipulating here are standard SVG attributes. But since we’re working with D3 we can easily integrate regular SVG elements into our visualization with ease.

  • Next we set the r (radius) of the circle using the radius constant.
  • Next we add some events using D3’s event handling system (selection.on(“eventName”, callbackFunction). On mouseover we populate the tooltip with information about the specific sale, including title, issue number grade and price, formatted as dollar denominated currency in the locale format for currency. On mouseout, we simply hide the tooltip.
    
      let dots = g.append('g')
        .attr('class', 'dots');
    
      dots.selectAll('.dot')
        .data(sales)
        .enter()
        .append('circle')
        .attr('class', 'dot nominal')
        .attr('cx',  (d)=> {
          return x(d.date);
        })
        .attr('cy',  (d)=> {
          return y(d.price);
        })
        .attr('r', radius)
        .on('mouseout', (d)=> {
          tooltip.style('opacity', 0);
        })
        .on('mouseover', (data)=> {
          tooltip.style('opacity', 1);
          tooltip.style('left', (d3.event.pageX + radius) + 'px')
            .style('top', (d3.event.pageY) + 'px');
          tooltip.select('.tooltip-inner')
            .text((d)=> {
              return `${data.title} #${data.issue} ${data.grade}  ${data.price.toLocaleString('us-EN', {style: 'currency', currency: 'USD'})}`;
            });
        });
      dots.selectAll('.inflation dot')
        .data(sales)
        .enter().append('circle')
        .attr('class', 'inflation dot')
        .attr('cx',(d)=> {
          return x(d.date);
        })
        .attr('cy',(d)=> {
          return y(d.inflationAdjustedPrice);
        })
        .attr('r', radius)
        .on('mouseout', ()=> {
          inflationTooltip.style('opacity', 0);
        })
        .on('mouseover', (data)=> {
          inflationTooltip.style('opacity', 1);
          inflationTooltip.style('left', (d3.event.pageX + radius) + 'px')
            .style('top', (d3.event.pageY) + 'px');
          inflationTooltip.select('.tooltip-inner')
            .text(()=> {
              return `${data.title} #${data.issue} ${data.grade}  ${data.inflationAdjustedPrice.toLocaleString('us-EN', {style: 'currency', currency: 'USD'})} (inflation adj.)`;
            });
        });
    

    The next block of code populates the section at the bottom of the screen where we list the sales in human language. It might look a little complicated, but it’s all just simple conditional logic designed to populate some strings. The initial set-up is familiar- we make a selection, of every “li” in the list variable we previously created. Then then call data on it to load it up with our data set and call enter on it to populate the list with entries for every member of the data set. We then append an li element for each member of the data set. Finally, we add a class of “list-group-item” to each.

    The next bit is a call to D3.text which sets the text value of a node. The function argument takes the data object for that member of the data set and eventually returns a formatted string representing that sale. Inside the callback we do a few things. We initially create several variables to hold our data. soldBy, soldTo and note are single spaced strings, so that if we fall through they still look okay in the sentence describing the sale. date is created with no initial value.

    Then we populate the strings with the best data we have for each field. If we have a seller, we use the seller name, otherwise we use the phrase ” by an unknown seller .” if a buyer exists, we update the soldTo variable to reference the buyer. If there’s a note, we update the string to reference the note. Finally, if we have a good date, we populate the string with a formatted date string which includes the month and year, in ${moment(d.date).format('MMMM of YYYY')}. If we only know the year, we populate the date string with a string that simple includes the year, `sometime in ${moment(d.date).format('YYYY')}`.

      list.selectAll('li')
        .data(sales)
        .enter()
        .append('li')
        .attr('class','list-group-item')
        .text((d)=>{
          let soldBy = ' ';
          let soldTo = ' ';
          let note = ' ';
          let date;
          if (d.seller !== '') {
            if (d.seller === 'unknown') {
              soldBy = ' by an unknown seller ';
            } else {
              soldBy = ` sold by ${d.seller} `;
            }
          }
          if (d.buyer !== '') {
            soldTo = ` to ${d.buyer} `;
          }
          if (d.note !== '') {
            note = ` (${d.note}) `;
          }
          if (d.goodDate) {
            date = `in ${moment(d.date).format('MMMM of YYYY')}`;
          } else {
            date = `sometime in ${moment(d.date).format('YYYY')}`;
          }
          return `${d.title} #${d.issue}${note}${soldBy} for ${d.price.toLocaleString('us-EN', {style: 'currency', currency: 'USD'})} (${d.inflationAdjustedPrice.toLocaleString('us-EN', {style: 'currency', currency: 'USD'})})${soldTo} ${date}`;
        });
    
    });
    

    The final block of code, simply toggles the classes indicating whether or not the inflation adjusted and/or nominal figures should be displayed. This visibility of the different versions of the chart are entirely run based on classes attached to the root SVG element. If the class is there, that set shows, if it isn’t there, that set doesn’t show.

    That said, I do actually have to walk through this a bit and the reason why has nothing to do with D3. This is an instance where some things you might know from old-school DOM manipulation
    might fail you when working with SVG. If you’ve ever manipulated the HTML DOM directly you’re likely used to working with the Element.className property. On HTML
    elements, the className property is a read/write string that maps to the class attribute on the HTML element. You can manipulate the string and changes are
    reflected in the DOM immediately.

    The DOM interface SVGElement does have a className property, but it isn’t a string you can simply manipulate. Under the hood it’s an SVGAnimatedString property with two string values AnimVal and BaseVal. That means the stuff you are used to doing won’t work here.

    The good news is, there’s a better, more modern way to manipulate classes. You can use the SVGElement.classList property to manipulate
    the CSS classes instead. classList is a structured interface to the CSS classes on an element. Accessed directly, classList is readonly, but there are methods available to query and manipulate the list of classes. That’s what we’re doing here, calling the toggle method to toggle the classes on an off.

    document.getElementById('inflationValue').addEventListener('change', () => {
      document.getElementById('target').classList.toggle('inflation');
    });
    document.getElementById('nominalValue').addEventListener('change', () => {
      document.getElementById('target').classList.toggle('nominal');
    });
    

    And with that, we’ve made it to the end of this rather long demo. I hope you’ve enjoyed it. It was fun to create and fun to write about. Mastering SVG was a challenging project since it covered the breadth of SVG. It was worth it for the ability to really dive into areas of SVG that interest me. D3 is one such area. Check the book out if you’re interested in getting full exposure to the world of SVG. It’s a lot of fun and opens up a number of possibilities on the web.

H5BP’s main.css Released as a Standalone Project

I just published the 1.0.0 release of main.css, a standalone version of the H5BP CSS file. The project serves as a repository for development of the file as well as a distribution channel for the component files as separate CSS files. The dist folder contains:

a list of files that ship with main.css- base.css, helpers.css, mqs.css, print.css  and main.css

This setup allows people (including HTML5 Boilerplate itself) to consume main.css as a whole, but also allows the component styles to be used individually in different, and hopefully interesting, ways.

Mastering SVG released

Mastering SVG was released today. It’s $10 at Packt right now.

That’s a crazy great price.

It’ll be on Amazon next week.

Wherever you buy it, you should buy it. It’s really great. I’ve been working at it for longer than I’d planned, but the end result is, I think, very good. SVG is a broad subject and I’ve delivered a 350+ page book that covers it from first principles all the way to intermediate/advanced topics (like the D3 visualization below.) If you’re looking to go from zero to SVG hero, this is the book for you.

HTML5 Boilerplate 6.1.0 Released

The news is coming fast and furious these days. Last week I offered up a big update on my SVG book (which I’m still in the process of finishing) and now I’m pleased to announce that we just released HTML5 Boilerplate 6.1.

In addition to the regular updates to dependencies, etc. the biggest change was moving to eslint for JavaScript linting. That was a lingering change we were unable to get into 6.0 and that change ended up being my biggest personal contribution to this release.

Speaking of contributions, Christian Oliff was instrumental in getting 6.1 out the door. I often woke up to a flurry of PRs as he threw together updates while I was busy sleeping, so he definitely kept this release on track. So, in addition to our ever-expanding cast of contributors, he definitely deserves big-time kudos for this release 🏆🏅✌️

Here’s the full release notes:

6.1.0 (May 1, 2018)

HTML5 Boilerplate 6.0 Released

drink

If you’ve been paying attention, you will have noticed that HTML5 Boilerplate 6.0.0 came out a few weeks ago. If not, it did. 6.0.1 has since been released.

It was a long time coming and I’m super happy to have it shipped. It was a lot of fun. Working with a project like this invariably means you have to do new things, so getting a major release out the door (where you have to touch everything) is a fun, educational experience. And, of course, working with the community is also pretty great.

Thanks!

Anyway, it was a big release and featured a lot of nifty stuff including:

  • We finally removed IE8 Support. This was a change that we had been discussing for some time and it was one of the first things I pushed through when I started taking a more active role on the project. Thanks to everyone for their help and input on this one.
  • We finally added a sample web app manifest file. That code had been percolating for years and it finally shipped.
  • We upgraded to Modernizr 3 and added a sample Mondernizr config so that people can do their own custom builds locally. Our Modernizr file is now created at build-time and I reworked the default detects to be more, er, modern.
  • We found out someone unaffiliated with the project had published the project to npm, so we took control of the package (thank you npm– support you were awesome) and published an official npm package.
  • And… lots of other great work by many contributors, including a ton of great work late in the process by Christian Oliff. Thanks to everyone for your contributions.

As a note, we still have an open bug that we’d love to get your input on– macOS – VoiceOver / Chrome announcing visually hidden text out of order · Issue #1985 · h5bp/html5-boilerplate. It’s an Apple bug with accessibility concerns that we’d like to work around.

As for what’s next… I’ll be opening up a couple of new issues for discussion this week, I think. So keep your eyes on the repo and join in on the fun.

Filling the Void Part 1: Random Projects and SSL via Let’s Encrypt

As I mentioned last month, my last long-term project ended in December. Between the holidays and then two separate instances of thinking I had something lined up and it falling through, I’ve only been working part-time over the past few weeks. While I’d prefer to be working full time (please reach out if you’re looking for help with anything) I have made myself useful over the past few weeks with side-projects and tinkering with new technology. I figured it might be fun to go through some of what I’ve been up to. This is the first of two posts detailing what I’ve done on my “winter break.”

SSL with Let’s Encrypt

One of my favorite projects has been switching several of my domains over to HTTPS using the free certs from Let’s Encrypt. I was really excited when my long-time host, FutureQuest announced support for Let’s Encrypt, including automatic updates. I love that this free path to secure communication exists and was excited to take advantage of it when my host offered it. Paying a one-time $25 setup fee is a lot better than the cost of an SSL certificate.

I was worried about what the drive to encryption by Google and others would do to smaller web publishers and businesses. I understand the need for and heartily support encrypted communication across all channels, I just hated the idea that small-fry publishers would get punished (in search ranking, etc.) for being insecure when the cost would be prohibitive for many publishers. Let’s Encrypt removes that monetary hurdle. Great stuff.

Futurequest’s implementation was pretty easy so the only difficulty was in getting WordPress working well with SSL (all three sites so far have been WordPress.) Generally, that was okay. A clean WordPress install is fine, but once you get into a real-world installation things get icky. Every migration included at least one instance where the site in question completely blew up because of one plugin or another. The good news is I was able to work around all those issues pretty easily (deleting plugins is especially easy) and am now running up and running on HTTPS on three of my sites.

Pretty sweet.

Time for a Refresh

One of the earliest projects I worked on was a refresh of the $100,000 Club and the All Time Record Comic Book Sales SVG visualization (a scatter plot.) Both of those projects are on Angular 1 and going back and updating them several years later was a lot of fun. I’d had a few things I wanted to do with the visualization for a few years and I jumped at the chance to implement them. It’s much nicer under the hood now.

All that code is on github.

Random Projects

As the above indicates, I do a lot of comic book related research and code. I have continued to document the Edgar Church Collection and have also started to document other named comic book collections. Free data for comic book people.

I’d like to do something interesting with the Edgar Church data this year. We’ll see what I come up with.


That’s round one. Round two, with Angular 2, React and Auerlia, will drop sometime next week.