jernwerber.dev: words & code

Hi! đź‘‹ My name is Jonathan and this is my little corner of the Internet. Please take your shoes off at the door.

About me ⚡️LIGHTNING ROUND EDITION⚡️

Coding

  • Front end: JavaScript · HTML5 · CSS3 · Markdown · Liquid
  • Back end: Python (with Django) · C# · SQL · Java & PHP (a little bit out of date 🤏)
  • Other ends (middle end?): MicroPython (with micro:bit) · C (with/for Arduino) · MakeCode (Arcade and micro:bit) · Processing for JavaScript (p5) · GitHub

Other skills

  • Digital productivity: Microsoft Office (Word, Excel, Forms, Access & PowerPoint) · Google Workspace (Docs, Sheets, Slides, Forms, Sites)
  • Instructional design: Adult learning · Technical training · Professional development · Adobe Captivate · Articulate Storyline & Rise
  • Live streaming / broadcasting: OBS Studio · Zoom · Google Meet
  • Graphic design and digital video production: Adobe Photoshop, Illustrator, Premiere & After Effects · Canva
  • Print layout: Adobe InDesign (multipage documents, mass publications, marketing & event materials, large format posters)
  • 3D & 3D design and fabrication: Autodesk Fusion 360 CAD · FDM/FFF 3D printing · LightBurn · Laser cutting
  • Educational technology: Micro:bit · Ozobot · Makey Makey · Lego Mindstorms
  • Research: IBM SPSS
  • Miscellaneous: Wood-working · Soldering · Basic circuit layout · MIG welding (beginner)

Recent posts

  • Data-Driven Disappointment in Pictures: Layoffs Ă— Hierarchy

    RECAP: We saw previously that 25 out of 42 staff, or approximately 59.5% of the entire staff (or, at any rate, of the staff listed on the web page), were laid off from Canada Learning Code in January of 2024. This is neatly visualized in the pie (donut?) chart below 👇:

    See the code:
    <script type="module">
        import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
      
        const data = await d3.tsv("https://jernwerber.dev/static/clc-laid-off-2024-01-20.tsv");
        
        const svgWidth = 700;
        const svgHeight = 500;
        
        const outerRadius = 150;
        const innerRadius = 100;
        
        const shiftX = 0;
        const shiftY = 40;
        
        const radiusOffset = 50;
        
        const baseColor = "crimson";
        const backgroundColor = "none";
        
        const pieGap = 0.04;
        const pieRotate = 0.2 * Math.PI;
        
        const CS = {
          HIERARCHY : {
            0 : "non-managers",
            1 : "managers",
            2 : "directors",
            3 : "C-level",
            4 : "CEO"
          },
         LAIDOFF: {
           "TRUE" : "Laid off",
           "FALSE" : "Not laid off"
         }
        }
        
        const groupedData = d3.group(data, d => d.LaidOff);
        const d3svg = d3.create("svg")
            .attr("aria-labelledby","chart-title")
            // .attr("width", svgWidth)
            // .attr("height", svgHeight)
            .attr("viewBox", [-svgWidth / 2, -svgHeight / 2, svgWidth, svgHeight])
            .style("background-color", backgroundColor)
            .style("margin-inline","auto")
        ;
        
        d3svg.append("text")
            .attr("id", "chart-title")
            .attr("text-anchor", "middle")
            .attr("transform",`translate(0 ${ -svgHeight/2.2 + shiftY })`)
            .style("font-size","24")
            .style("font-weight", "light")
            .style("font-style","italic")
        .selectAll("tspan")
        .data(["Canada Learning Code 2024 Layoffs:", "Employees LAID OFF"])
            .join("tspan")
                .text(d => d)
                .attr("x", 0)
                .attr("dy", (d,i) => 28 * i)
        ;
        
        d3svg.append("g") 
          .attr("transform",`translate(${shiftX},${shiftY})`)
          .selectAll("path")
          .data(
            d3.pie()
              .value(d => d[1].length)
              .startAngle(pieRotate)
              .endAngle(2*Math.PI + pieRotate)
              .sort(null)(groupedData)
          )
          .join("path")
             .attr("fill", (d, i) => d3.quantize(
                    d3.interpolateRgb("lightgrey","crimson"),2
                    ).reverse()[i]
                 )
            .attr("stroke", "none")
            .attr("d", d3.arc().innerRadius(innerRadius).outerRadius(outerRadius))
            .append("title")
              .text(d => d.value)
        ;
        
        d3svg.append("g")
          .attr("transform",`translate(${shiftX},${shiftY})`)
          .selectAll("g")
          .data(
            d3.pie()
              .value(d => d[1].length)
              .startAngle(pieRotate)
              .endAngle(2 * Math.PI + pieRotate)
              .sort(null)(groupedData)
          )
          .join("g")  
            .each(function (d, i) {
            const [x,y] = d3.arc().innerRadius(outerRadius).outerRadius(outerRadius + radiusOffset).centroid(d);
      
            const g = d3.select(this);
            const labelWidth = 36;
            const labelHeight = 24;
      
            g.append("rect")
                .attr("x", x - labelWidth/2)
                .attr("y", y - labelHeight/2)
                .attr("width",labelWidth)
                .attr("height",labelHeight)
                .attr("fill", "none")
            ;
      
              g.append("text")
                .attr("dy",6)
                .attr("text-anchor",(d.endAngle + d.startAngle)/2 < Math.PI ? "start" : "end" )
                .text(`${CS.LAIDOFF[d.data[0]]} `)
                .attr("x", x)
                .attr("y", y)
                .attr("fill","darkslategrey")
                .style("font-weight","light")
                .style("font-style", "italic")
                .style("font-family","Georgia")
                  .append("tspan")
                    .style("font-weight", "bold")
                    .style("cursor", "help")
                    .text(`${d.value}`)
                    .append("title")
                      .text(`${(d.value * 100 / d3.sum(groupedData, d => d[1].length)).toFixed(2)}%`)
            ;  
          })
        ;
        
        d3.select("#d3-container-997").node()
          .append(d3svg.node())
        ;
        
      </script>
      <div id="d3-container-997">
        <!-- this will hold whatever D3 generates -->
      </div>
    

    The overall proportion statistic, while staggering in its magnitude, doesn’t give us all that much more detail as to the impact of the layoffs on the company’s overall structure: We do know that the company didn’t shutdown, so there must be some intention to stick around at least for a little longer, but stick around for what purpose–who’s left and what do they do?

    To get a better handle on what happened, we need to ask more questions.

    Perhaps the next questions we might ask should be: Who was laid off? Or, how were layoffs distributed across the company?

  • Data-Driven Disappointment Part 4: Colour and title text

    In part 4 of this series on using the D3 JavaScript library (d3.js), we will look at how to use d3.color() and some of the built-in d3 color interpolators to specify fill colours for our pie slices. We will also be adding a title and labels to help make the data being presented a little clearer.

  • Data-Driven Disappointment Part 3: Dealing with data

    In part 3 of this series on using the D3 JavaScript library (d3.js), we will look at using the functionality provided by D3 to group our data, then use that grouped data as the basis for a simple pie chart showing the relative sizes (proportions) of the groups. We will look at a couple common D3 patterns, including select-data-join and creating and calling generated functions.

  • Data-Driven Disappointment Part 2: Getting set up and getting started

    In part 2 of this series on using the D3 Javascript library (d3.js), we will look at one way to setup data to be consumed by D3 via a tab-separated values data string copied directly out of Google Sheets. We will also be introduced to the dataset that we’ll be using throughout, a list of lay off casualties derived from the publicly available team information on the website of my former employer, Canada Learning Code.

  • Data-Driven Disappointment Part 1: Introduction

    This is part 1 of a work-in-progress series that’s a mix of Data-Driven Documents (D3/d3.js) tutorial and reflection on being laid off. I’m using this as a way to cope with job loss and as an excuse to dive much deeper into a JavaScript library that I’ve only ever scratched the surface of, D3. In my (meager) experience, there’s a steep learning curve to being able to use D3, requiring knowledge of more than a few idiosyncratic patterns to be able to get started. Hopefully, through the posts in this series, you’ll be able use my trials and tribulations to hit the ground running and not spend as much time hitting your head against your desk in frustration.

  • From the Archives: Troubleshooting a y-axis issue on the Flashforge Dreamer

    Another one from the archives, hence the date. This one might actually still be relevant, assuming that I can find and reupload the datasheet that was previously linked.

    I was having an issue with the y-axis in my 3D printer. It had suddenly started producing intense low frequency vibrations when running:

    That… doesn’t sound good.

  • From the Archives: Logging accelerometer data from the micro:bit

    Another one from the archives, hence the date. I’m republishing this one because it has some interesting information about the micro:bit’s accelerometer, though the actual data logging part might be a bit moot given the improvements that have occurred to MakeCode’s functionality.

    What would we need to do to have the micro:bit be a useful logger of acceleration data?

    1. Set a sampling rate
    2. Store data on the micro:bit’s local storage
    3. Play the data back or transfer it to a computer for analysis
  • From the Archives: The micro:bit and motors

  • From the Archives: Learning a new platform (micro:bit)!

    This is a post that’s been imported from an older blog, hence the date. Some of the information might be out of date, e.g. there’s now a new version of the micro:bit with some enhanced functionality. Still, I think the high-level/general concepts hold, as does much of the commentary, so I’m re-posting this with this disclaimer.

  • From the Archives: Living in interesting times

    This is an old post dug up from a previous personal blog, hence the date. I’m older now, and hopefully at least a little bit wiser. I wonder how past-me would react knowing *just how interesting* the world (and times) would become just a short few years later.

subscribe via RSS