Image
Code

DataViz Meetup HH: KP Brehmer

jasminskoenig

Last edited Jun 04, 2024
Created on Jun 04, 2024
Forked from Circles with D3

Recreating KP Brehmer's 'Korrektur der Nationalfarben'

Today, we are recreating one of KP Brehmer's most well-kown pieces: 'Korrektur der Nationalfarben'. KP Brehmer was an artist based in Hamburg, who - for a time - created a lot of creative data visualizations to show how easy it is to make statements and lie with data (to learn more, check out this book.

In the piece we recreate today, he adapted the German national flag to represent the share of wealth of different classes. We actually do not know whether he used real or made up data back then but we will create the same visual with up-to-date data for Germany in 2023.

Setting Up the Environment

We start with the index.js. This is where our code will live. Further, we need a package.json that defines D3 as a dependency (copied from Curran - if you want to learn more about the vizhub specifics for this also check out his project).

Now that D3 is defined as a dependency, we can import it in our main script.

Selecting with D3

When we work with D3, our output is html. Html is structured in a DOM (Document Object Model). To create a graph, we need to create an svg in the DOM. And we want to be able to update this svg whenever necessary.

This is where select comes in! We start by importing the function from D3.

import { select } from 'd3';

Now, we can select all svgs in our container. The first time we do this, the DOM element is created. From then on it's only updated.

export const main = (container) => {
  const svg = select(container) // select within container
    .selectAll('svg') // select svg
    .data([null])
    .join('svg');
};

Defining the size

We define the size of our chart by setting the dimension of our SVG element. Whenever we want to define an aspect of an html element through D3, we do this through the attr method. We first define that width and height have the same size as our container. We then add width and height to our D3 element, as well as a background color. If you'd like to see where our svg is, feel free to change the color from white to black.

const width = container.clientWidth;
const height = container.clientHeight;

svg
  .attr('width', width)
  .attr('height', height)
  .style('background', '#F0FFF4');

Explanation from Curran: We can use container.clientWidth and container.clientHeight to measure the container DOM element at page load time. This works because of an assumption that the parent DOM element has a defined width and height.

Data

Next up, is creating our data. We want to visualize the German flag and each color represents how much parts of society own. The upper class will be yellow, the middle class red, and the middle class black.

We define class, percentage for the width of the color and the fill color.

const data = [
  { class: 'middle', percentage: 36.44, fill: '#000A04' },
  { class: 'lower', percentage: 2.31, fill: '#DB2A40' },
  { class: 'upper', percentage: 61.25, fill: '#E4AF37' },
];

I've ordered the colors in the order they should appear, not by class. This will make it easier for us later on, since D3 will step through each observation and add a rectangle for it. If you import your data, you might have to re-order it before binding to the chart.

Drawing the Flag

The different parts of our flag are rectangles. So we want to select html rectangles through D3. We want to do this within the svg which is the parent to our rectangles. We do this with selectAll('rect').

We bind the data and for each observation in our data, we add a rectangle. We do this with the join method.

Setting attributes in rectangles is a little tricky. We need x, y, height, and width.

We only have one rectangle per line. So our rectangles should all go from 0 (top) to the full height (bottom) of the svg.

The width is a little more difficult. We have three rectangles which should each start where the last one ends. To adapt the width of each rectangle to the width of our svg, we first convert our percentage to a decimal (60% -> 0.6). We then calculate the width of our svg*0.6 to adapt the rectangles size to the correct share of the svg. This is the width of our rectangle.

To start the rectangle at the correct point, we need to use the sum of the shares of the rectangles left to the current one. To do this, we want to update currentHeight every time we'ves added a rectangle. We need to initialize this value before moving on to adding the rectangles.

let cumulativeWidth = 0;

In the beginning cumulativeWidth is 0. We use cumulativeWidth as the x position of each rectangle. AFTER having done that (const y = cumulativeWidth), we add the width of the current rectangle to cumulativeWidth.

Last, but not least, we add the fill color as stroke and fill.

svg
  .selectAll('rect')
  .data(data)
  .join('rect')
  .attr('y', 0)
  .attr('height', height)
  .attr(
    'width',
    (d) => (d.percentage / 100) * container.clientWidth,
  )
  .attr('x', (d, i) => {
    const y = cumulativeWidth;
    cumulativeWidth += (d.percentage / 100) * height;
    return y;
  })
  .attr('fill', (d) => d.fill)
  .attr('stroke', (d) => d.fill);

We've recreated KP Brehmer's flag - YAY!

MIT Licensed