<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://wabain.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://wabain.github.io/" rel="alternate" type="text/html" /><updated>2026-03-17T20:54:48+00:00</updated><id>https://wabain.github.io/feed.xml</id><title type="html">William Bain</title><author><name>William Bain</name><email>bain.william.a@gmail.com</email></author><entry><title type="html">Working notes: Animating k-d trees</title><link href="https://wabain.github.io/2020/01/05/animating-kd-trees.html" rel="alternate" type="text/html" title="Working notes: Animating k-d trees" /><published>2020-01-05T00:00:00+00:00</published><updated>2020-01-05T00:00:00+00:00</updated><id>https://wabain.github.io/2020/01/05/animating-kd-trees</id><content type="html" xml:base="https://wabain.github.io/2020/01/05/animating-kd-trees.html"><![CDATA[<p>Amid the post–New Year spate of project roundups, I discovered Luke Pattons’ Canvas Cards project, and <a href="https://canvas-cards.glitch.me/#boxer-selector" target="_blank" rel="noopener">through it</a> some mesmerizing procedural animations by Raven Kwok.</p>

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/jpyc9hBLxD" target="_blank" rel="noopener">pic.twitter.com/jpyc9hBLxD</a></p>&mdash; Raven Kwok (@RavenKwok) <a href="https://twitter.com/RavenKwok/status/1117347303105294337?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">April 14, 2019</a></blockquote>

<blockquote class="twitter-tweet" data-conversation="none"><p lang="en" dir="ltr">Pushed it to three-dimension. Tolerable, yet still much room for improvement regarding vertical displacement. <a href="https://t.co/Zdt0Aa8ZQL" target="_blank" rel="noopener">pic.twitter.com/Zdt0Aa8ZQL</a></p>&mdash; Raven Kwok (@RavenKwok) <a href="https://twitter.com/RavenKwok/status/1118036493623185409?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">April 16, 2019</a></blockquote>

<p>I didn’t find much on how Kwok implemented the animations beyond his comment that <a href="https://twitter.com/RavenKwok/status/1118034980037283840" target="_blank" rel="noopener">“the visual’s core structure is a K-D tree”</a>. So I decided to experiment with <em>k</em>-d tree animation myself.</p>

<p>A <em>k</em>-d tree <a href="https://en.wikipedia.org/wiki/K-d_tree" target="_blank" rel="noopener">is a binary tree structure</a> that encodes <em>k</em>-dimensional points to define a partitioning of a <em>k</em>-dimensional space. Sequential levels of the tree contain the median points of subsets of the points in alternating dimensions, carving up progressively smaller areas. The trees are commonly used for nearest-neighbor search, but here our goal is to use them to drive visually interesting transformations of geometrical shapes.</p>

<figure>
  <img src="/home-assets/posts/animating-kd-trees/Kdtree_2d.png" alt="Diagram of a 2-d k-d tree" />
  <figcaption>
    An example of a 2-dimensional <i>k</i>-d tree (via <a href="https://commons.wikimedia.org/wiki/File:Kdtree_2d.svg" target="_blank" rel="noopener">Wikipedia</a>)
  </figcaption>
</figure>

<p>It was pretty easy to implement some basic logic to render a 2-d tree to an HTML5 canvas; Wikipedia has some working code snippets which build the tree by recursing over the nodes. In my first attempt to animate it I picked two arrays of points, interpolating between them and rendering the <em>k</em>-d tree that results from each interpolation:</p>

<figure>
  <video controls="" autoplay="" muted="" loop="" playsinline="">
    <source src="/home-assets/posts/animating-kd-trees/anim-first-try.webm" type="video/webm" />
    <source src="/home-assets/posts/animating-kd-trees/anim-first-try.mp4" type="video/mp4" />
    Sorry, your browser doesn't support embedded videos.
  </video>
  <figcaption>
    My first attempt
  </figcaption>
</figure>

<p>The results are not great, since the lines of the tree are not continuous throughout the animation—as points shift, their positions in the tree will eventually change, causing the lines to jump.</p>

<p>My next idea was to interpolate the values in the tree directly. I build <em>k</em>-d trees for an initial and final set of points and then render trees which tween the values of the isomorphic nodes. The interpolated trees aren’t proper <em>k</em>-d trees, but they’re visually similar and give a nice, continuous animation.</p>

<figure>
  <video controls="" autoplay="" muted="" loop="" playsinline="">
    <source src="/home-assets/posts/animating-kd-trees/anim-second-try.webm" type="video/webm" />
    <source src="/home-assets/posts/animating-kd-trees/anim-second-try.mp4" type="video/mp4" />
    Sorry, your browser doesn't support embedded videos.
  </video>
  <figcaption>
    My second attempt
  </figcaption>
</figure>

<p>By the end of what had turned out to be a rather long Friday night of experimentation, I had built a moderately engaging little animation. First I switched to coloring the rectangles formed by the tree rather than the lines. Then I dialed up the number of points used to generate the tree and punched up the timings. Finally I switched from the RGB palette I’d used for testing to something a bit easier on the eyes.</p>

<figure>
  <video controls="" autoplay="" muted="" loop="" playsinline="">
    <source src="/home-assets/posts/animating-kd-trees/anim-third-try.webm" type="video/webm" />
    <source src="/home-assets/posts/animating-kd-trees/anim-third-try.mp4" type="video/mp4" />
    Sorry, your browser doesn't support embedded videos.
  </video>
  <figcaption>
    The outcome
  </figcaption>
</figure>

<p>Having made it this far brought home to me the range of choices that influence these animations’ feel. The selection of color, gradient and texture (there’s a graininess superimposed on Kwok’s images), along with the use of negative space and staggered transitions, do a lot to make Kwok’s animations engaging. The ability to incorporate improper trees in the animation also intrigues me—it’s a natural step from interpolating points between <em>k</em>-d trees to decomposing arbitrary shapes into <em>k</em>-d–defined blocks and back again. My next explorations, if I come back to this concept, will probably take that idea further.</p>]]></content><author><name>William Bain</name><email>bain.william.a@gmail.com</email></author><category term="javascript" /><category term="web-design" /><category term="animation" /><summary type="html"><![CDATA[Amid the post–New Year spate of project roundups, I discovered Luke Pattons’ Canvas Cards project, and through it some mesmerizing procedural animations by Raven Kwok.]]></summary></entry><entry><title type="html">CSS Trivia: Rotated table headers in 2019</title><link href="https://wabain.github.io/2019/10/13/css-rotated-table-header.html" rel="alternate" type="text/html" title="CSS Trivia: Rotated table headers in 2019" /><published>2019-10-13T00:00:00+00:00</published><updated>2019-10-13T00:00:00+00:00</updated><id>https://wabain.github.io/2019/10/13/css-rotated-table-header</id><content type="html" xml:base="https://wabain.github.io/2019/10/13/css-rotated-table-header.html"><![CDATA[<p>Recently I built an HTML report to aggregate the results of some automated jobs. The table headings being much longer than the table’s contents, I wanted to set the headings at a 45° angle. When I Googled for how to do this with CSS, the answers I got were all years old and involved some complications that seem to have become unnecessary. Since I didn’t find a current guide, I wanted to record what I settled on.</p>

<p>The top Google result for “css rotate table header 45 degrees” is a 2014 <a href="https://css-tricks.com/rotated-table-column-headers/" target="_blank">CSS Tricks article</a> which rather breezily describes a variation on an earlier technique from another blog post.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> The trail of posts traces back to one <a href="http://itpastorn.blogspot.com/2009/05/rotating-column-headers-using-css-only.html" target="_blank">from 2009</a>, a very different era in web development. At the time, the best technique was to rotate the header with its top left corner staying fixed, and then to apply what the CSS Tricks article calls “some math stuff using <code class="language-plaintext highlighter-rouge">tan()</code> and <code class="language-plaintext highlighter-rouge">cos()</code>” to figure out what translation needed to be applied in order for the bottom right of the rotated header to meet the top right-hand corner of the cell below.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></p>

<p>The key to the updated approach is that instead of rotating from the top left and then correcting the horizontal and vertical positioning, we can keep the <em>bottom</em> left point of the header fixed by setting <code class="language-plaintext highlighter-rouge">transform-origin</code>. Then we only need to offset the horizontal position of the text by the width of the table column, a constant we’ll already have in our CSS.</p>

<style>
    /*
     * Common
     */
    .tablerot {
        display: flex;
        justify-content: space-around;
        flex-wrap: wrap;
    }

    .tablerot figcaption {
        text-align: center;
    }

    .tablerot-cell {
        overflow: visible;
    }

    .tablerot-cell-outline {
        fill: white;
        stroke: #ccc;
        stroke-dasharray: 80, 60, 20;
    }
    .tablerot-cell-head .tablerot-cell-outline {
        stroke-dasharray: none;
    }

    .tablerot-cell-placeholder {
        fill: #aaa;
        stroke: none;
    }
    .tablerot-cell-head .tablerot-cell-placeholder {
        fill: #555;
    }

    /*
     * 2009
     */
    .tablerot-old .tablerot-group-head {
        animation: tablerot-old-group-head 3s ease-in-out infinite;
    }
    @keyframes tablerot-old-group-head {
        0%, 45% { transform: translate(0, 0) }
        /*
         * Start the rotation 2% after tablerot-new so that they don't appear
         * to be overly synchronized
         */
        55%, 62% {
            transform: translate(0, 5.85786437627px);
            animation-timing-function: ease-in;
        }
        72% { transform: translate(50px, 5.85786437627px) }
        77% { transform: translate(42px, 5.85786437627px) }
        82% { transform: translate(47px, 5.85786437627px) }
        87%, 100% { transform: translate(45.8578643763px, 5.85786437627px) }
    }

    .tablerot-old use[href="#tablerot-table-head"] {
        animation: tablerot-old-head 3s ease-in infinite;
        transform-origin: 20px 30px;  /* here origin is relative to svg */
    }
    @keyframes tablerot-old-head {
        0% { transform: rotate(0) }
        25%, 100% { transform: rotate(-45deg) }
    }

    .tablerot-trig {
        animation: tablerot-trig 3s ease-in infinite;
        stroke: #E53E3E;
        stroke-dasharray: 50;
        stroke-dashoffset: 50;
        fill: none;
    }
    @keyframes tablerot-trig {
        0%, 25% { stroke-dashoffset: 50; }
        40% { stroke-dashoffset: 0; opacity: 1; }
        55%, 100% { opacity: 0; }
    }

    /*
     * 2019
     */
    .tablerot-new use[href="#tablerot-table-head"] {
        animation: tablerot-new-head 3s ease-in-out infinite;
        transform-origin: 20px 50px;  /* here origin is relative to svg */
    }
    @keyframes tablerot-new-head {
        0% { transform: rotate(0); animation-timing-function: ease-in; }
        25%, 60% { transform: rotate(-45deg); animation-timing-function: ease-in; }
        70% { transform: translateX(65px) rotate(-45deg); }
        75% { transform: translateX(58px) rotate(-45deg); }
        80% { transform: translateX(61px) rotate(-45deg); }
        85%, 100% { transform: translateX(60px) rotate(-45deg); }
    }
</style>

<svg class="hidden">
  <defs>
    <symbol id="tablerot-table-head">
      <svg class="tablerot-cell tablerot-cell-head" x="20" y="30">
        <rect class="tablerot-cell-outline" width="60" height="20" />
        <rect class="tablerot-cell-placeholder" x="5" y="6" width="42" height="8" />
      </svg>
    </symbol>
    <symbol id="tablerot-table-body">
      <svg class="tablerot-cell" x="20" y="50">
        <rect class="tablerot-cell-outline" x="0" y="0" width="60" height="20" />
        <rect class="tablerot-cell-placeholder" x="15" y="6" width="39" height="8" />
      </svg>
      <svg class="tablerot-cell" x="20" y="70">
        <rect class="tablerot-cell-outline" x="0" y="0" width="60" height="20" />
        <rect class="tablerot-cell-placeholder" x="12" y="6" width="42" height="8" />
      </svg>
      <svg class="tablerot-cell" x="20" y="90">
        <rect class="tablerot-cell-outline" x="0" y="0" width="60" height="20" />
        <rect class="tablerot-cell-placeholder" x="18" y="6" width="36" height="8" />
      </svg>
      <svg class="tablerot-cell" x="20" y="110">
        <rect class="tablerot-cell-outline" x="0" y="0" width="60" height="20" />
        <rect class="tablerot-cell-placeholder" x="16" y="6" width="38" height="8" />
      </svg>
      <svg class="tablerot-cell" x="20" y="130">
        <rect class="tablerot-cell-outline" x="0" y="0" width="60" height="20" />
        <rect class="tablerot-cell-placeholder" x="12" y="6" width="42" height="8" />
      </svg>
    </symbol>
  </defs>
</svg>

<div class="tablerot">
  <figure class="tablerot-old">
    <svg width="150" height="150" viewBox="-30 -20 160 160" xmlns="http://www.w3.org/2000/svg">
      <g class="tablerot-group-head">
        <path class="tablerot-trig" d="M 25,30 A 5 5 0 0 1 20 35 L 20,30 v 14.1421356237 h 14.1421356237" />
        <use href="#tablerot-table-head" />
      </g>
      <use href="#tablerot-table-body" />
    </svg>
    <figcaption>2009</figcaption>
  </figure>
  <figure class="tablerot-new">
    <svg width="150" height="150" viewBox="-30 -20 160 160" xmlns="http://www.w3.org/2000/svg">
      <use href="#tablerot-table-head" />
      <use href="#tablerot-table-body" />
    </svg>
    <figcaption>2019</figcaption>
  </figure>
</div>

<p>This neatly takes care of the core problem of positioning the headings, although we’ll still hit some awkwardness related to having transformed elements in the CSS layout. The example below demonstrates the technique:</p>

<div id="root-demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc">
<style>
  #root-demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc .demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-scrollable {
    overflow: auto;
  }

  #root-demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc .demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header th {
    height: 240px;
    vertical-align: bottom;
    text-align: left;
    line-height: 1;
  }

  #root-demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc .demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-container {
    width: 75px;
  }

  #root-demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc .demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-content {
    width: 300px;
    transform-origin: bottom left;
    transform: translateX(75px) rotate(-45deg);
  }

  #root-demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc .demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header td:not(:first-child) {
    text-align: right;
  }
</style>

<figure class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-scrollable">
  <table class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header">
    <thead>
      <tr>
        <th>Year</th>
        <th>
          <div class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-container">
            <div class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-content">Splines reticulated</div>
          </div>
        </th>
        <th>
          <div class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-container">
            <div class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-content">Ipsums loremmed</div>
          </div>
        </th>
        <th>
          <div class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-container">
            <div class="demo-Ojt_Ru7NxYIUQA-cFpLt9n5KrE0qVJfastZuJF5FPZc-rotated-header-content">
              Observations on the theory and practice of landscape gardening
            </div>
          </div>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>2016</td> <td>120</td> <td>1,900</td> <td>25</td>
      </tr>
      <tr>
        <td>2017</td> <td>3,002</td> <td>14,000</td> <td>16</td>
      </tr>
      <tr>
        <td>2018</td> <td>20,124</td> <td>980</td> <td>48</td>
      </tr>
    </tbody>
  </table>
</figure>
</div>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style&gt;</span>
  <span class="nc">.scrollable</span> <span class="p">{</span>
    <span class="nl">overflow</span><span class="p">:</span> <span class="nb">auto</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.rotated-header</span> <span class="nt">th</span> <span class="p">{</span>
    <span class="nl">height</span><span class="p">:</span> <span class="m">240px</span><span class="p">;</span>
    <span class="nl">vertical-align</span><span class="p">:</span> <span class="nb">bottom</span><span class="p">;</span>
    <span class="nl">text-align</span><span class="p">:</span> <span class="nb">left</span><span class="p">;</span>
    <span class="nl">line-height</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.rotated-header-container</span> <span class="p">{</span>
    <span class="nl">width</span><span class="p">:</span> <span class="m">75px</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.rotated-header-content</span> <span class="p">{</span>
    <span class="nl">width</span><span class="p">:</span> <span class="m">300px</span><span class="p">;</span>
    <span class="nl">transform-origin</span><span class="p">:</span> <span class="nb">bottom</span> <span class="nb">left</span><span class="p">;</span>
    <span class="nl">transform</span><span class="p">:</span> <span class="n">translateX</span><span class="p">(</span><span class="m">75px</span><span class="p">)</span> <span class="n">rotate</span><span class="p">(</span><span class="m">-45deg</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nc">.rotated-header</span> <span class="nt">td</span><span class="nd">:not</span><span class="o">(</span><span class="nd">:first-child</span><span class="o">)</span> <span class="p">{</span>
    <span class="nl">text-align</span><span class="p">:</span> <span class="nb">right</span><span class="p">;</span>
  <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>

<span class="nt">&lt;figure</span> <span class="na">class=</span><span class="s">"scrollable"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"rotated-header"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;thead&gt;</span>
      <span class="nt">&lt;tr&gt;</span>
        <span class="nt">&lt;th&gt;</span>Year<span class="nt">&lt;/th&gt;</span>
        <span class="nt">&lt;th&gt;</span>
          <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"rotated-header-container"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"rotated-header-content"</span><span class="nt">&gt;</span>Splines reticulated<span class="nt">&lt;/div&gt;</span>
          <span class="nt">&lt;/div&gt;</span>
        <span class="nt">&lt;/th&gt;</span>
        <span class="nt">&lt;th&gt;</span>
          <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"rotated-header-container"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"rotated-header-content"</span><span class="nt">&gt;</span>Ipsums loremmed<span class="nt">&lt;/div&gt;</span>
          <span class="nt">&lt;/div&gt;</span>
        <span class="nt">&lt;/th&gt;</span>
        <span class="nt">&lt;th&gt;</span>
          <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"rotated-header-container"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"rotated-header-content"</span><span class="nt">&gt;</span>
              Observations on the theory and practice of landscape gardening
            <span class="nt">&lt;/div&gt;</span>
          <span class="nt">&lt;/div&gt;</span>
        <span class="nt">&lt;/th&gt;</span>
      <span class="nt">&lt;/tr&gt;</span>
    <span class="nt">&lt;/thead&gt;</span>
    <span class="nt">&lt;tbody&gt;</span>
      <span class="nt">&lt;tr&gt;</span>
        <span class="nt">&lt;td&gt;</span>2016<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>120<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>1,900<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>25<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;/tr&gt;</span>
      <span class="nt">&lt;tr&gt;</span>
        <span class="nt">&lt;td&gt;</span>2017<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>3,002<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>14,000<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>16<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;/tr&gt;</span>
      <span class="nt">&lt;tr&gt;</span>
        <span class="nt">&lt;td&gt;</span>2018<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>20,124<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>980<span class="nt">&lt;/td&gt;</span> <span class="nt">&lt;td&gt;</span>48<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;/tr&gt;</span>
    <span class="nt">&lt;/tbody&gt;</span>
  <span class="nt">&lt;/table&gt;</span>
<span class="nt">&lt;/figure&gt;</span>
</code></pre></div></div>

<p>There are a few points worth noting:</p>

<ul>
  <li>
    <p>In this demo I’m using two <code class="language-plaintext highlighter-rouge">divs</code> inside each <code class="language-plaintext highlighter-rouge">th</code>: a wrapper to set the width of the header cell and an inner div for the rotated text. In theory I think it should be possible to fix the dimensions of the cell by styling the <code class="language-plaintext highlighter-rouge">th</code> itself, but I haven’t dug into the specifics of CSS table layout rules to figure out how to make it work.</p>
  </li>
  <li>
    <p>I rely on a <code class="language-plaintext highlighter-rouge">vertical-align</code> rule to push the header text to the bottom of the fixed-size <code class="language-plaintext highlighter-rouge">th</code> elements. Something similar can also be done with flexbox.</p>
  </li>
  <li>
    <p>I’ve set the hardcoded height of the header heuristically; in principle one could do “math stuff” to get the minimum height that includes the rotated header but in this demo the height of the rotated header is determined by the height of multiple lines of text; getting the necessary parameters dynamically is probably possible on the client side, but requires knowing exactly how the header text will be laid out. This is tricky, particularly if the font size or leading change responsively based on the window size.</p>
  </li>
  <li>
    <p>Part of the rotated headers extend outside the area of the table itself. In this demo I’ve wrapped the table in a scrollable container and made it left-aligned. While the scrollable area of the container includes the protruding headers, if the table is centered within the scrollable container it will be centered according to its own size, excluding the headers. This can push the headers outside the area which is visible without scrolling.</p>
  </li>
</ul>

<h2 id="bonus-fallbacks">Bonus: Fallbacks</h2>

<p>Support for CSS transforms is pretty robust, but what happens if a user agent lacks it? In my case, the problem child was the Outlook web client, which allows embedded CSS in emails, but strips transform styles for what I imagine are security reasons. Without my making special provisions, this resulted in the unrotated row headers overlapping and becoming unreadable.</p>

<p>Fortunately, there’s a simple fix: wrap all of the rules related to header rotation in a <code class="language-plaintext highlighter-rouge">@supports(transform: ...)</code> query. Clients not supporting the transform will render an unwieldy but basically readable table. With support for <code class="language-plaintext highlighter-rouge">@supports</code> being <a href="https://caniuse.com/#feat=css-featurequeries" target="_blank" rel="noopener">a bit spottier</a> than support for the CSS transforms themselves, this probably results in some extra user agents rendering the fallback, but for me that’s an acceptable cost for what is basically a progressive enhancement.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>CSS Tricks is an excellent resource; the post I stumbled on is the result of having a deep back catalog. As I was preparing this post I used posts from the same author to refresh my memory on how to animate SVG. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>The older posts also skew the shape of the header in order to have its horizontal lines stay parallel with the horizontal lines of the body rows. This is less relevant now that tables with external borders are largely out of style. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>William Bain</name><email>bain.william.a@gmail.com</email></author><category term="css" /><category term="trivia" /><category term="web-design" /><category term="best-practices" /><summary type="html"><![CDATA[Recently I built an HTML report to aggregate the results of some automated jobs. The table headings being much longer than the table’s contents, I wanted to set the headings at a 45° angle. When I Googled for how to do this with CSS, the answers I got were all years old and involved some complications that seem to have become unnecessary. Since I didn’t find a current guide, I wanted to record what I settled on.]]></summary></entry><entry><title type="html">Fixing C arrays, four decades later</title><link href="https://wabain.github.io/2019/08/10/c-arrays.html" rel="alternate" type="text/html" title="Fixing C arrays, four decades later" /><published>2019-08-10T00:00:00+00:00</published><updated>2019-08-10T00:00:00+00:00</updated><id>https://wabain.github.io/2019/08/10/c-arrays</id><content type="html" xml:base="https://wabain.github.io/2019/08/10/c-arrays.html"><![CDATA[<p>One of the greatest challenges in teaching C is the idiosyncratic rules that govern string and array variables. While teachers can delay this hurdle until the second or third week by focusing on programs that use <code class="language-plaintext highlighter-rouge">printf</code> with static strings, it can’t be put off much further. <!--more--> Any C program that takes command line arguments needs to grapple with how arrays, pointers, and strings relate. The first Google result for “C main” <a href="https://wwwx.cs.unc.edu/~sparkst/howto/cpp_main.php" target="_blank">introduces</a> main functions with a signature that highlights the pointer/array duality:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span> <span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[]</span> <span class="p">);</span>
</code></pre></div></div>

<p>Explaining how to work with the elements of this signature—how to manipulate the string <code class="language-plaintext highlighter-rouge">char* argv</code>, the array <code class="language-plaintext highlighter-rouge">char* argv[]</code>, or the equivalents <code class="language-plaintext highlighter-rouge">char** argv</code> and <code class="language-plaintext highlighter-rouge">char argv[][]</code>—requires teaching not only the runtime semantics of pointer access, but also the particularities of C array variables and the rules that govern their conversion to pointers, an entirely orthogonal concept which is slippery in its own right. When I finally came across an explanation of the rationale for C’s array semantics in <a href="http://www.bell-labs.com/usr/dmr/www/chist.html" target="_blank">a retrospective by Dennis Ritchie</a>, I found that while C’s approach is tied up with the legacy of long-ago PDP-11 code, it also addresses an inherently tricky language design problem that has taken decades to unravel.<sup id="fnref:precedents" role="doc-noteref"><a href="#fn:precedents" class="footnote" rel="footnote">1</a></sup></p>

<p>When Ritchie developed the pre-C variant NB, he allocated two separate areas in storage for an array declaration like <code class="language-plaintext highlighter-rouge">char carray[10]</code>: first, the backing array of ten <code class="language-plaintext highlighter-rouge">char</code>’s, and second a pointer to them, which the variable <code class="language-plaintext highlighter-rouge">carray</code> refers to. The problem with this scheme, it turned out, was that it couldn’t readily be applied to declaring the fields of structured types:</p>

<blockquote>
  <p>For example, the directory entries of early Unix systems might be described in C as</p>

  <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="p">{</span>
	<span class="kt">int</span>	<span class="n">inumber</span><span class="p">;</span>
	<span class="kt">char</span>	<span class="n">name</span><span class="p">[</span><span class="mi">14</span><span class="p">];</span>
<span class="p">};</span>
</code></pre></div>  </div>

  <p>I wanted the structure not merely to characterize an abstract object but also to describe a collection of bits that might be read from a directory. Where could the compiler hide the pointer to name that the semantics demanded?</p>
</blockquote>

<p>Constrained by the need to mostly preserve the semantics of existing programs, the solution Ritchie hit on was to manifest the pointer at the point of use: only one storage area, for the array content, would be allocated, but an array-type variable in an expression would be treated as a pointer to the first value in the array.</p>

<p>This rule leaves arrays variables, in some ways, as awkward second-class citizens. In proto-C, given arrays defined as <code class="language-plaintext highlighter-rouge">char a[10], b[10]</code> it was possible to write <code class="language-plaintext highlighter-rouge">a = b</code> to point <code class="language-plaintext highlighter-rouge">a</code> to the array defined for <code class="language-plaintext highlighter-rouge">b</code>. In the world after Ritchie’s retrofit, this is problematic: according to the rules, <code class="language-plaintext highlighter-rouge">b</code> should be treated as a pointer, but if <code class="language-plaintext highlighter-rouge">a</code> were treated the same way there would be no coherent way to define assignment to a struct member; in the expression <code class="language-plaintext highlighter-rouge">s.a = b</code>, there is no pointer in the struct to act as the target of the assignment. This restriction manifests in the C89 specification as a <a href="http://port70.net/~nsz/c/c89/c89-draft.html#3.2.2.1" target="_blank">rather inconspicuous clause</a> restricting the applicability of array variable expressions:</p>

<blockquote>
  <p>Except when it is the operand of the sizeof operator or the unary &amp; operator, or is a character string literal used to initialize an array of character type … an lvalue that has type “array of type” is converted to an expression that has type “pointer to type” that points to the initial member of the array object <strong>and is not an lvalue</strong>.</p>
</blockquote>

<p>This excerpt also highlights the other set of restrictions that came with Ritchie’s rule. For the <code class="language-plaintext highlighter-rouge">sizeof</code> operator, the array is not treated as a pointer, since that would leave no way to determine the size of the underlying storage. That means that when switching a variable from a statically sized array to an unsized pointer, the meaning of the sizeof operator changes, a trap that makes it easy to introduce bugs when refactoring. C compounds the problem by introducing the empty array notation, as in <code class="language-plaintext highlighter-rouge">argv[][]</code>, as syntactic sugar for a pointer <code class="language-plaintext highlighter-rouge">**argv</code>, an affordance that Ritchie ultimately concluded “serves as much to confuse the learner as to alert the reader.”</p>

<p>It might seem that, like many of C’s quirks, the array semantics are just an unfortunate legacy. But I think Ritchie was right to insist that, relatively speaking, C’s semantics ultimately provide “a uniform and simple mechanism” for array manipulation. Inspection of how some more recent languages handle arrays reveals a fundamental impedance between modeling arrays as scalar pointers and as sized regions of memory.<sup id="fnref:vla" role="doc-noteref"><a href="#fn:vla" class="footnote" rel="footnote">2</a></sup></p>

<p>Go follows C in allowing for arrays to be declared on the stack or inline within a structure, without extra pointer indirection. Unlike C, it distinguishes clearly between array-typed variables and pointers to arrays, defining array assignment as a value copy which works for both variables and struct members (see below).</p>

<p>However, Go’s uniform treatment of arrays breaks down when moving from statically sized arrays to dynamically sized slices. While they borrow the C-style empty array syntax, Go treats slices as a fundamentally different entity. Assigning the value of one slice to another happens by reference. As with C’s <code class="language-plaintext highlighter-rouge">sizeof</code> operator, this is a common vector for bugs: a simple refactoring to remove a constant bound, if not done carefully, can completely change a program’s semantics.</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="s">"fmt"</span>

<span class="k">type</span> <span class="n">aggregate</span> <span class="k">struct</span><span class="p">{</span> <span class="n">i</span> <span class="p">[</span><span class="m">4</span><span class="p">]</span><span class="kt">int</span> <span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="k">var</span> <span class="n">a1</span><span class="p">,</span> <span class="n">a2</span> <span class="n">aggregate</span>
	<span class="k">var</span> <span class="n">t1</span><span class="p">,</span> <span class="n">t2</span> <span class="p">[</span><span class="m">4</span><span class="p">]</span><span class="kt">int</span>
	<span class="k">var</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span> <span class="p">[]</span><span class="kt">int</span>

	<span class="c">// structs: copy by value</span>
	<span class="n">a1</span><span class="o">.</span><span class="n">i</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">=</span> <span class="m">5</span>
	<span class="n">a2</span><span class="o">.</span><span class="n">i</span> <span class="o">=</span> <span class="n">a1</span><span class="o">.</span><span class="n">i</span>
	<span class="n">a1</span><span class="o">.</span><span class="n">i</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">=</span> <span class="m">10</span>

	<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"%v; %v</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">a1</span><span class="p">,</span> <span class="n">a2</span><span class="p">)</span>
	<span class="c">// ↪ {[10 0 0 0]}; {[5 0 0 0]}</span>

	<span class="c">// arrays: copy by value</span>
	<span class="n">t1</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">=</span> <span class="m">5</span>
	<span class="n">t2</span> <span class="o">=</span> <span class="n">t1</span>
	<span class="n">t1</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">=</span> <span class="m">10</span>

	<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"%v; %v</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">)</span>
	<span class="c">// ↪ [10 0 0 0]; [5 0 0 0]</span>

	<span class="c">// slices: copy by reference</span>
	<span class="n">s1</span> <span class="o">=</span> <span class="n">t1</span><span class="p">[</span><span class="o">:</span><span class="p">]</span>

	<span class="n">s1</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">=</span> <span class="m">15</span>
	<span class="n">s2</span> <span class="o">=</span> <span class="n">s1</span>
	<span class="n">s1</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">=</span> <span class="m">20</span>

	<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"%v; %v</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span><span class="p">)</span>
	<span class="c">// ↪ [20 0 0 0]; [20 0 0 0]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>There is an alternative, which, to my knowledge, is best exemplified by Rust. Like Go, Rust allows fixed-sized arrays to be stack allocated or directly embedded in an aggregate type. Fundamentally, it treats dynamically sized arrays—including subarrays of a fixed-size array—as ordinary values, with no automatic pointerization. It accomplishes this by restricting how those variables are actually used, forbidding values of types that do not have the <code class="language-plaintext highlighter-rouge">Sized</code> trait from being allocated on the stack. The result is that the alias-instead-of-copy behavior of slices ends up being explicit, and therefore also benefits directly from Rust’s checks against modification of aliased data:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(Default)]</span>
<span class="k">struct</span> <span class="n">Aggregate</span> <span class="p">{</span>
    <span class="n">i</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="mi">4</span><span class="p">],</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// structs: copy by value</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">a1</span><span class="p">:</span> <span class="n">Aggregate</span> <span class="o">=</span> <span class="nn">Default</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">a2</span><span class="p">:</span> <span class="n">Aggregate</span> <span class="o">=</span> <span class="nn">Default</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>

    <span class="n">a1</span><span class="py">.i</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
    <span class="n">a2</span><span class="py">.i</span> <span class="o">=</span> <span class="n">a1</span><span class="py">.i</span><span class="p">;</span>
    <span class="n">a1</span><span class="py">.i</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}; {:?}"</span><span class="p">,</span> <span class="n">a1</span><span class="py">.i</span><span class="p">,</span> <span class="n">a2</span><span class="py">.i</span><span class="p">);</span>
    <span class="c1">// ↪ [10, 0, 0, 0]; [5, 0, 0, 0]</span>

    <span class="c1">// arrays: copy by value</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">t1</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="nn">Default</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">t2</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="mi">4</span><span class="p">];</span>

    <span class="n">t1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
    <span class="n">t2</span> <span class="o">=</span> <span class="n">t1</span><span class="p">;</span>
    <span class="n">t1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}; {:?}"</span><span class="p">,</span> <span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">);</span>
    <span class="c1">// ↪ [10, 0, 0, 0]; [5, 0, 0, 0]</span>

    <span class="c1">// slices: borrowed reference</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">s1</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span><span class="p">[</span><span class="nb">u8</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">a1</span><span class="py">.i</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">s2</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span><span class="p">[</span><span class="nb">u8</span><span class="p">];</span>

    <span class="n">s1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
    <span class="n">s2</span> <span class="o">=</span> <span class="n">s1</span><span class="p">;</span>
<span class="c1">//  ------- borrow of `*s1` occurs here</span>
    <span class="n">s1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
<span class="c1">//  cannot use `*s1` because it was mutably borrowed</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}, {:?}"</span><span class="p">,</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Rust’s approach does come at the expense of an expansion of the language space; beside the restrictions on unsized type usage, both the the type system and the runtime representation of pointers <a href="http://smallcultfollowing.com/babysteps/blog/2014/01/05/dst-take-5/" target="_blank">had to be extended</a> to accommodate these types, the former to handle values with attributes (array size or concrete type) left indeterminate and the latter to allow fat pointers to expose the missing information at runtime. However, it creates semantics that effectively generalize fixed size, dynamically sized, and embedded arrays, with the special-casing visible to the user mostly restricted to the hard constraints of the computer architecture.</p>

<p>After some reflection, I believe Rust’s approach is about the most general possible in a language that exposes array allocation on the stack. Ritchie sacrificed clarity, as well as generality of the assignment operator, for a backwards-compatible and uniform treatment of variable and struct fields. Go sacrifices a uniform treatment of arrays and slices in order to maintain primitives that can be efficiently manipulated. Rust restricts only the use, but not the expression, of patterns that can’t be easily accommodated by conventional stack allocation.</p>

<p>As an academic exercise, it is possible to imagine a fully general array semantics in a language, like Go, that does not have a strict stack/heap distinction. In Go, locally declared variables are already heap-allocated if they could be used by reference outside of the local stack frame. It’s possible to contemplate leveraging this to implement copy-based semantics for dynamically sized slices, with non-pointer assignments copying to the heap and users taking the address of a slice explicitly to assign by reference. The ABI <a href="https://groups.google.com/d/msg/golang-dev/HDLMMYQv7Ak/3z8tL8WxBgAJ" target="_blank">already supports</a> passing pointers for nominally non-pointer values in order to support closures; this could be extended to accommodate heap pointers and concrete sizes to return dynamically sized slices. However, given the impracticality of a language that prioritizes the (potentially much more) expensive access pattern by default, I think the design space Ritchie wrestled with has reached a stable point.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:precedents" role="doc-endnote">
      <p>This is not to make any absolute claims to priority. Ritchie notes that languages contemporary to C, like Pascal and Algol 68, also had problematic array facilities, and no mainstream language in the interim that I am aware of has made the kind of design choices I describe here—not Ada or C++ or D, not reference-centric languages like Java and most scripting languages, and not the mostly immutable functional languages and Lisps. I’d be interested to hear about earlier precedents. <a href="#fnref:precedents" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:vla" role="doc-endnote">
      <p>For the purpose of this post, I’m going to ignore stack-allocated variable-sized arrays, although Ritchie alludes to them briefly and Rust is growing <a href="https://github.com/rust-lang/rust/issues/48055" target="_blank">partial support</a> to match C’s. Suffice it to say that because variably sized allocations can overflow the stack—with their safety depending on non-local factors like function parameters and the use of temporaries—they should be treated as a much more niche tool than array allocation on the heap or static stack allocation. I also consider the optimization complexity that arises from C’s propensity for pointer aliasing, another concern Ritchie highlights, to be more of a problem with pointers than with array semantics. <a href="#fnref:vla" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>William Bain</name><email>bain.william.a@gmail.com</email></author><category term="language-design" /><category term="c" /><category term="go" /><category term="rust" /><summary type="html"><![CDATA[One of the greatest challenges in teaching C is the idiosyncratic rules that govern string and array variables. While teachers can delay this hurdle until the second or third week by focusing on programs that use printf with static strings, it can’t be put off much further.]]></summary></entry></feed>