A few months ago I gave a talk at my favourite meetup.

While uploading the slides on all the relevant services, I happened to land my eyes on this presentation by Lynn Fisher.

If you followed me somewhere on the internet, you might now by now that I have the tendency to get drawn into pet projects ideas and love to develop small websites mostly for the sake of it.

Lynn's presentation triggered me a lot, and one of their projects got my attention: https://airportcod.es/

I love everything of it, but mostly I was fascinated by the cataloging nature of the project. I immediately wanted to do something similar.

Colours

The first experiment of catalogue started off with a list of colours. I wrote a script to collect all the colour names and HEXs and I built colours.

I added a few niceties such as the background change on click/tap, but that was about it. When I showed it to my partner she immediately asked "why not sorting by colour?".

I facepalmed myself for not thinking about it: it was a brilliant idea.

Except it's really hard: I found an article that explains it much better than I ever could. Long story short, a colour is defined in a three dimensional space (hue, saturation and light, for example) and it's basically impossible to flatten them to a monodimensional sorting that would look like an organic progression at a human eye. I endend up providing a way to sort them by hue, which is the closest, using an npm module – which is less than ideal but would do.

The result is not too bad, but light and saturation jumps make it not organic at all

The best part of the project – nerd alert – was the refactoring: I started off with Create React App and I did everything (including inline styling and sorting logic) in the App.js stateful component. Once I was happy with how it worked and looked like, I introduced the css-in-js library Emotion and React Hooks.

I'm to old to be jumping on the hype train and start refactoring all the things at work to use them just yet, but this was a really good exercise to understand their power – and quirks. Hooks are a slightly different mental model and it will definitely change how we write React going forward.

Flags

I was at the peak of interest for the colours project, when I came across a wiki page about – nerd alert #2 – flags.

It's pretty obvious how this ended up, right?

Wikipedia is a goldmine of weird data about flags: it provides pages with aspect ratio, year of adoption, flag names, flags per continent.

I started collecting data, using axios and cheerio. As per the work with the colours the approach was quite straightforward, even though maybe not particularly elegant:

const {data} = await axios.get("https://en.wikipedia.org/wiki/Gallery_of_sovereign_state_flags")

const $ = cheerio.load(data);
const flagContainers = $("li.gallerybox").toArray();
const result = flagContainers.map(flagContainer => {
    const $flagContainer = $(flagContainer);
    const $link = $flagContainer.find(".gallerytext a");
    const country = $link
        .text()
        .replace("Flag of the", "")
        .replace("Flag of", "")
        .trim();

        const url = helpers.cleanUrl($link.attr("href"));

        return {
          country,
          url,
        };
      });

      fs.writeFileSync("./cache.json", JSON.stringify(result), "UTF-8");
    });

The main issue was to normalise data across the varios wiki pages as the user generated content is not often great and countries could be mentioned in a slightly different manner or linking different pages (wiki has an insane amount of redirects running behind the hood, to consolidate data).

Once I got most of the data I wanted from wikipedia, my flag objects looked like this

{
  "id":"albania",
  "country":"Albania",
  "url":"/wiki/flag_of_albania",
  "adoption":"1912",
  "ratio":"5:7",
  "name":"Shqiponja Dykrenare",
  "continents":["Europe"]
}

Please notice that the continent property is an array: Wiki has, correctly, some countries mapped in different continents, as well as some debatable decisions – for example why is Denmark in America thanks to Greenland, but France is not despite having quite a few islands off its coast? I decided to enforce some standards and make a call on the controversial ones. For example: Russia is arguably in 2 continents, but for the purpose of this project Denmark is not.

Anyway, I got to a point where I got all the data I needed and I wanted some sort of colour dominance / breakdown analysis.

Having all the flags in SVG, I could load them in a canvas and read the images data from there. The script uses node-canvas, creating a canvas of the same size of the SVG viewbox (to avoid resizing glitches) and reading each pixel colour.

const { createCanvas, loadImage } = require("canvas");
const parse = require("pixelbank");

flags = flags.map(flag => {
    const imageData = {};
    const file = `${__dirname}/flags/${flag.id}.svg`;
    
    // load the image and create a canvas of the same size
    const image = await loadImage(file);
    const canvas = createCanvas(image.width, image.height);
    
    // draw the image into the canvas
    const ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
    
    // read all the pixels /w https://npmjs.com/package/pixelbank
    const pixels = parse(
      ctx.getImageData(0, 0, canvas.width, canvas.height)
    );
    
    // create a map of each pixel based on their RGB values
    pixels.forEach(({color: {r, g, b}}) => {
      const key = `r${r}g${g}b${b}`;
      imageData[key] = (imageData[key] || 0) + 1;
    });
    
    // calculate the percentage for each colour
    const totalPx = pixels.length;
    const data = Object.keys(imageData).map(key => {
        const percent = (imageData[key] * 100) / totalPx;

        // nullify the colours with a minimal presence to filter them
        return (percent >= 0.74) && {
          hex: convertToHex(key),
          percent: Math.round(percent),
        };
      })
      .filter(color => !!color)
      // sort by dominant colour
      .sort((a, b) => (a.percent < b.percent) ? 1 : -1);

    return {
      ...flag,
      colors: data,
    };
})

At this point I focused a bit on the front end: I ended up creating a card flipping onhover/tap with the following front and back

I wanted to be able to filter the cards per continent as well as per colour group.

The continent was pretty easy considering the data available; the colour on the other hand, for the three dimensional space I mentioned above, could've been a little trickier.

Luckily I found a script on stack overflow that was on the lines of

const groupColours = ({ hue, sat, lgt }) => {
  if (lgt < 0.1) return "black";
  if (lgt > 0.8) return "white";
  if (sat < 0.25) return "gray";
  if (hue < 30) return "red";
  if (hue < 90) return "yellow";
  if (hue < 180) return "green";
  if (hue < 210) return "cyan";
  if (hue < 270) return "blue";
  if (hue < 330) return "magenta";
  return "red";
};

This is something that could lead me to a better sorting strategy in my previous project 😅

Iterations

I ended up iterating a lot on the flags.

I added sorting – by name, year of adoption and flag ratio –, a zoom view, the ability to navigate the website with the keyboard, the anthems of each country – initially from a Spotify playlist and then from YouTube – and the ability to add maps.

When I got to a point where I was reasonably comfortable with the features, I stared thinking about refactoring: I broke down App.js to several components and I introduced the useReducers hook. This part was pretty similar to the work I've done previously on Colours.

The most interesting bit was probably dealing with the URL: in my humble opinion websites pages should always be reproduceable given an URL.

The only update to the website was in the content, not in the structure of the page or the components, and hence it felt overkill to look at React Router. I opted for using useEffects in combination with what the useReducer already introduced:

import React, { useEffect, useState } from "react";
import App from "./App";

let historySetup = false,
export default () => {
  const [state, dispatch] = useReducer(reducers, getInitialState(props.data));

  useEffect(() => {
    if (!historySetup) {
      setupUrlBinding(dispatch);
    } else {
      const qsFromState = qs.fromState(state);
      const currentSearch = qs.getCurrent();
      if (qsFromState !== currentSearch) {
        updateUrl(qsFromState);
      }
    }
  }, [
    state.filters,
    state.sortBy,
    state.q,
    state.continent,
    state.detail,
    state.detailView,
    state.view,
    state.reversed,
  ]);

  return <App data={state} dispatch={dispatch} />
}

In case you are not too familiar with React Hooks, the only "magic" happening is that the function passed as first argument to useEffect would get executed only when any of the items in the array passed as second argument changes.

if (!historySetup) {
  setupUrlBinding(dispatch);
} else {
  const qsFromState = qs.fromState(state);
  const currentSearch = qs.getCurrent();

  if (qsFromState !== currentSearch) {
    updateUrl(qsFromState);
  }
}

What happens there, then, is that the first time the component gets mounted historySetup would be false and setupUrlBinding invoked.

const setupUrlBinding = dispatch => {
  const updateStore = () =>
    dispatch(action("updateFromUrl", qs.getParams(window.location.search)));

  window.onpopstate = e => e.type === "popstate" && updateStore();
  updateStore();

  historySetup = true;
};

The function creates a function to update the state from the url and assign it to window.onpopstate as well as executing it immediately, to account for loading a page with filters already defined in the URL.

The onpopstate handler deals only with events of type pop because the URL filters gets programmatically applied when a user interacts with the page. It could have been handled in a way where any interaction would only update the ULR and leave the onpopstate to do the heavy lifting no matter the nature of the history event.

const [state, dispatch] = useReducer(reducers, getInitialState(props.data));

User iteraction, in this case, triggers an action dispatch and hence an update to the state. If any of the relevant state properties changes, the effect would be triggered again. This time the effect would infer the query string from the state itself and update the URL if necessary.

const updateUrl = qsFromState => {
  const url = qsFromState ? `?${qsFromState}` : "/";
  window.history.pushState(null, "", url);
};

Flags sets

The countries flags are cool, but certain regional ones are fabolous and I wanted to add them. The process was pretty manual until I found the page with all the flags of country subdivisions. At that point it became a breeze.

Right now the only problem I'm facing is sorta semantic: I added a few sets – Scandinavian, most of the South Americans, Swiss, Japanese, German, Spanish, Italian and United States' – but it doesn't quite scale. Ideally I should be able to figure out a different grouping mechanism, keeping in mind I don't have all the regional flags, in order to add more and more without having the list growing indefinitely.

The truth is: I can't wait to start looking into micronations, sexual identities, religious groups and secessionist movements; in the end the goal is not to have the most comprehensive list of flags ever, but an interesting website where to spend some time navigating flags and looking at colours.

The Code

Colours: https://github.com/cedmax/colours
Flags: https://github.com/cedmax/flags