Data-driven visuals with React

Tom Valorsa
Digital News
Published in
7 min readOct 22, 2019

Our software team in London recently submitted a couple of entries for the Kantar Information is Beautiful Awards 2019. One of these was Exoflags, a system for generating flags for newly discovered exoplanets using data collected by NASA.

In simple terms the properties of a planet, such as the planetary mass and radius, were encoded into graphical elements on a flag. Each flag generated was a unique but relative visual representation of the data collected for each planet.

Today, we’re going to build a <Flag /> component using React that takes in values as props and translates them into visual elements, similar to the one we used to create flags in our award submission. If you just want to get straight into the code you can find it here. The commits roughly match up to the flow of this post, so you can also use the code for reference.

Let’s get started.

Setup

We’re going to use create-react-app to get going quickly. In your terminal type following:

npx create-react-app flags-tutorialcd flags-tutorial

In src/index.css let’s add some global style rules to make positioning things a bit easier:

* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
html,
body,
#root {
height: 100%;
}

Lastly for setup, we’re going to create a layout component for the demo. I’m going to use emotion to create styled DOM elements. You can use whatever you like for this.

import styled from '@emotion/styled';const Page = styled.div`
height: 100%;
width: 100%;
background-color: #16324f; /* coolors.co space cadet */
display: flex;
color: #fff;
`;
export default Page;

Creating the Flag component

Our end goal is to have a flag with some different visual elements based on data we pass to it. The first step is to make a container for these graphical elements.

import React from 'react';
import styled from '@emotion/styled';
const Wrapper = styled.div`
background-color: blue;
/* 3:5 aspect ratio */
height: 300px;
width: 500px;
`;
const Flag = () => (
<Wrapper>

</Wrapper>
);
export default Flag;

We’ll fix the dimensions in this example for simplicity. The important part here is the aspect ratio — this is a common ratio for real-life flags (there are many!). We’ve also added a basic background colour here which will later become dynamic. Render this component inside of the <App /> component along with the <Page /> component:

import React from 'react';
import Page from './components/Page';
import Flag from './components/Flag';
function App() {
return (
<Page>
<Flag />
</Page>
);
}
export default App;

At this point you should have something that looks like this:

A basic flag shape

Visual elements

Before we start creating visual elements to place on the flag, we need to add another line of CSS to the <Wrapper /> of our <Flag /> component:

position: relative;

This will create a new stacking context, which will be essential for positioning the flag’s visual elements. If you want to read more about stacking contexts, here’s a great post from Philip Walton’s blog.

For the purpose of this tutorial we’re going to encode 5 different types of information in our flag which will be represented by: the flag’s background colour, the size of 2 triangles, and the opacity of the same 2 triangles. We’ll achieve this by passing props to the flag’s <Wrapper /> component, and by creating two components for the triangles which will also use props to update their styles.

const FLAG_WIDTH = 500;
const FLAG_HEIGHT= 300;
const LeftTriangle = styled.div`
z-index: 2;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%;
border-style: solid;
border-width: ${FLAG_HEIGHT}px 0 0 ${FLAG_WIDTH}px;
border-color: transparent transparent transparent rgba(0, 0, 0, 1);
`;
const RightTriangle = styled.div`
z-index: 1;
position: absolute;
bottom: 0;
right: 0;
height: 50%;
width: 100%;
border-style: solid;
border-width: ${FLAG_HEIGHT}px ${FLAG_WIDTH}px 0 0;
border-color: transparent rgba(255, 255, 255, 1) transparent transparent;
`;
const Flag = () => (
<Wrapper>
<LeftTriangle />
<RightTriangle />
</Wrapper>
);

We want the left triangle to sit above the right triangle, so it needs a larger z-index value. While we can use any numbers we want here, I generally try to keep them as low as possible, which works well when paired with creating different stacking contexts where necessary. That way we know our <RightTriangle /> is at z-index 1 in this stacking context, but we can also start counting from 1 again if we need to position an element in a different stacking context — seeing another 1 value for a z-index in a different component is also a clue that we’re dealing with another stacking context.

The only other thing to note here is that we’re using the classic “make triangles out of borders” CSS trick (for more info check out this explanation on Stack Overflow). The left triangle will span the entire height of the flag and will eventually have a variable width, while the right triangle will span the width of the flag and vary in height. We’re using hard-coded values at the moment but we’ll switch these up in the next section. You should now have something that looks a bit like this:

A flag with some visual elements stacked on top

An an aside, you can now remove the position: relative; rule we added to the flag’s wrapper <div /> to see how things get weird and why it’s important.

Using scales

Now we should start considering input values. The aim here is to take a set of values and translate them into values that can be used in our CSS. First we’ll need to add some props to our <Flag /> component:

const Flag = ({
leftSize = 50,
leftOpacity = 50,
rightSize = 50,
rightOpacity = 50,
bgColor = 50
}) => (
// …
);

I’m going to give these props boring names so the code is easier to follow — in the Exoflags site these were based around properties of planets such as planetary radius and neighbouring constellations. I’m also going to work with the assumption that each of these values being passed in with range from 0 to 100, but feel free to change these up.

To take these values and turn them into something we can style our visual elements with, we’re going to use scales from d3-scale. If you’re not familiar with scales in D3, have a read of this post.

npm install d3-scale

Now we can import scaleLinear and make some basic scales:

import { scaleLinear } from 'd3-scale';// …const sizeScale = scaleLinear()
.domain([0, 100])
.range([0, 100]);
const opacityScale = scaleLinear()
.domain([0, 100])
.range([0, 1]);
const bgColorScale = scaleLinear()
.domain([0, 100])
.range([‘#0000FF’, ‘#FF0000’]);

sizeScale will translate a value from 1–100 into a percentage of width or height for each of the triangles. (N.B. While the domain and range are identical here, making this a bit pointless in its current state, we have the freedom to change the range here later on so that we can ensure that there is always some triangle displayed for the minimum value)

opacityScale will translate a value from 1–100 into a value ranging from 0–1, which we’ll use to specify a value for the opacity of each triangle.

bgColorScale will translate a value from 1–100 into a colour from blue to red, giving us a smooth gradient between the two for the flag’s background colour.

Finally in this step, we’re going to pass the prop values into the scaling functions and pass those into the visual components we’ve created.

const LeftTriangle = styled.div`
// …
width: ${props => props.size}%;
border-width: ${props => `
${FLAG_HEIGHT}px
0
0
${FLAG_WIDTH * (props.size / 100)}px
`};
border-color: ${props => `
transparent
transparent
transparent
rgba(0, 0, 0, ${props.opacity})
`};
`;
const RightTriangle = styled.div`
// …
height: ${props => props.size}%;
border-width: ${props => `
${FLAG_HEIGHT * (props.size / 100)}px
${FLAG_WIDTH}px
0
0
`};
border-color: ${props => `
transparent
rgba(255, 255, 255, ${props.opacity})
transparent
transparent
`};
`;
// …const Flag = ({
leftSize = 50,
leftOpacity = 50,
rightSize = 50,
rightOpacity = 50,
bgColor = 50
}) => (
<Wrapper bgColor={bgColorScale(bgColor)}>
<LeftTriangle
size={sizeScale(leftSize)}
opacity={opacityScale(leftOpacity)}
/>
<RightTriangle
size={sizeScale(rightSize)}
opacity={opacityScale(rightOpacity)}
/>
</Wrapper>
);

Now you can pass different values from 1–100 into any of these props on the <Flag /> component inside <App /> and see how the graphical elements of the flag change.

Visual elements are now being generated based on data passed into the component

Extras

At this point we’ve created our basic flag component! In the demo repo there is a bit of extra code. This covers:

  • setting up state to keep track of the flag values and passing them to the <Flag /> component
  • adding a set of sliders, one for each property in state
  • adding a randomise button to generate new flags

This should give a better idea of how the flag updates when the inputs are changed. Here’s how the finished product looks:

Visual elements can be adjusted or randomised to create new flags!

This could be extended in all kinds of ways, e.g. different kinds of visual elements, by removing default props and not rendering the corresponding elements if there is no value, and by playing around with scale ranges for different effects.

Once again, if you’re interested in looking at the code for this tutorial you can find it here. You can also check out our submission to the Information is Beautiful Awards here. Thanks for reading.

--

--

Tom Valorsa
Digital News

Web developer, football fan, tech enthusiast. London/Sydney.