Coding a Warming Gradient

Disclaimer: I'm terrible at math (such a shame for someone who likes CS) and this is my first time using Python, so I'm bound to have made some mistakes here. If you spot any, please do let me know!

One of my goals for 2025 is to do more side projects. And (oops) now it's December.

One project I've had scribbled in my notebooks for (literal) years now is to recreate Ed Hawkins' Warming Stripes as a CSS gradient. A month ago, I decided to finally get it out of my notebook and into code.

The goal

I wanted the Warming Stripes as a CSS gradient that I could use on the web. The iconic visualization shows global temperature changes as colored stripes (blue for cooler years, red for warmer ones), and I wanted to match the original as closely as possible.

Show Your Stripes

The easy stuff

Getting started was straightforward enough:

Colors: The Wikipedia page for Warming Stripes revealed that it uses two ColorBrewer color palettes. I grabbed those and confirmed they matched the Warming Stripes.

Data: For the global variant (which I decided I'd start with), I downloaded the HadCRUT5 dataset from the UK Met Office.

So far, so good!

Did I mention I'm not good at math?

Then came the real challenge. The Warming Stripes FAQ states:

"For each country and city the average temperature in 1961-2010 is set as the boundary between blue and red colours, and the colour scale varies from +/- 3.0 standard deviations of the annual average temperatures between 1901-2000. For the global average only, the UK Met Office HadCRUT5.0 dataset is used and the colour scale goes from -0.9°C to +0.9°C."

This is probably just my lack of math skills, but I had a very hard time understanding what this meant.

I'll explain it briefly, for myself in the first place because I'm very scared I'll forget again.

For countries and cities, we first calculate the average temperature for 1961-2010 (this becomes the "midpoint" between blue and red) then we calculate the standard deviation using data from 1901-2000. Color intensity is based on ±3.0 standard deviations from the mean.

For global data, we still use 1961-2010 as the midpoint between blue and red but instead of calculating standard deviations, we use a fixed scale of ±0.9°C.

What is that? Why am I even mentioning how we calculate the stripes for countries and cities, you ask? You thought I was only doing the global one for now, you say? Well, that's where my stupid head did stupid things. I tried using the deviation period from 1901-2000 WITH a fixed scale of ±0.9°C. This produced some very weird results.

Once I read the FAQ for the nth time and finally understood it, I felt like an idiot, but hey, that's what I love about programming.

A bonus is that I accidentally built in flexibility to handle both approaches. If you provide a second command-line argument, it treats it as a standard deviation multiplier (for countries/cities). Otherwise, it defaults to the fixed ±0.9°C scale for global data:

# If provided, retrieve standard multiples
if len(sys.argv) >= 3:
std_multiples = float(sys.argv[2])
use_fixed_scale = False
else:
std_multiples = 0.9 # Default for global
use_fixed_scale = True

When using the variable scale (for countries/cities), the code calculates the standard deviation from the 1901-2000 period:

# If not using fixed scale, calculate standard deviation
if not use_fixed_scale:
std_data = get_data_for_period(source_data, STANDARD_DEVIATION_PERIOD[0], STANDARD_DEVIATION_PERIOD[1])
std_mean = sum(std_data) / len(std_data)
squared_diffs = [(x - std_mean)**2 for x in std_data]
std_dev = math.sqrt(sum(squared_diffs) / len(std_data))

Technically, we should be able to just use country or city data to generate even more gradients with whatever scale we want (we'll use ±3.0 to match the original Warming Stripes). I haven't tested this yet, though, so I have no idea if this actually works. In theory, it should.

The fun part: Colors (finally)

Once I had the baseline and scale figured out, the rest was relatively straightforward:

Calculate the difference between each year's temperature and the baseline

Normalize that difference to a -1 to +1 range based on the scale

Map negative values to shades of blue (cooler) and positive values to shades of red (warmer)

The script outputs three files:

warming-gradient.css - A CSS file with classes for a smooth gradient and sharp stripes (the original look)

warming-gradient.svg - Smooth gradient as SVG

warming-stripes.svg - Sharp stripes

I decided to add the SVG functionality because I wanted an image for the README file. I quickly threw it together and it seems to work on the web, but not when I open it in Illustrator. This is almost certainly due to me not understanding how SVGs work, but I don't really care. "I'll fix it later," he said, knowing he wouldn't.

This actually works??

And voila, it's done! Somehow, this seems to work. Please do give the code a look over if you're interested and especially If you want to use it. I've provided instructions on how to do both on GitHub.

GitHub - fvoort/warming-gradient: Generates CSS gradients visualizing historical temperature data, inspired by Ed Hawkins' Warming Stripes.