Skip to content

Instantly share code, notes, and snippets.

@VolkerH
Forked from anttilipp/runZonalTempViz.py
Created May 30, 2019 07:08
Show Gist options
  • Save VolkerH/341298c8f2721a26d87c5e8f6c142211 to your computer and use it in GitHub Desktop.
Save VolkerH/341298c8f2721a26d87c5e8f6c142211 to your computer and use it in GitHub Desktop.
This is the Python code I used to make the visualization # "Temperature anomalies, zonal means, 1900 - 2016"
#
# Hi all,
# this is the Python code I used to make the visualization
# "Temperature anomalies, zonal means, 1900 - 2016"
# (https://twitter.com/anttilip/status/921809347658895361).
# Please be aware that originally I wrote this just to see how this type
# of visualization would work. The code was not ment to be published
# and therefore has only a couple of/no comments and is most likely
# not written in the most optimal way.
#
# Feel free to improve, modify, do whatever you want with it. If you decide
# to use the code, make an improved version of it, or it is useful for you
# in some another way I would be happy to know about it. You can contact me
# for example in Twitter (@anttilip).
#
# The data in this code is taken from NASA GISS GISTEMP website
# (https://data.giss.nasa.gov/gistemp/tabledata_v3/ZonAnn.Ts+dSST.txt).
# For data details, please see the GISTEMP website.
#
# Have fun!
# Antti
#
# ---------
#
# The script I used to construct the video:
# (needed files:
# runZonalTempViz.py (this)
# overlayZonalViz20171021.png (overlay png, can be found from the comments of this Github gist)
# )
#
# mkdir frames
# python runZonalTempViz.py
# mkdir finalFrames
# for FILE in $(ls frames)
# do
# if [ ! -f finalFrames/$FILE ]; then
# echo $FILE
# convert frames/$FILE[1800x1152+0+380] overlayZonalViz20171021.png -gravity center -composite -matte finalFrames/$FILE
# fi
# done
#
# # make the animation file
# ffmpeg -framerate 24 -pattern_type glob -i 'finalFrames/*.png' -c:v libx264 -pix_fmt yuv420p zonalTempViz.mp4
#
# ---------
#
# Copyright 2017 Antti Lipponen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from matplotlib.patches import Rectangle
# Data: NASA GISS GISTEMP
# https://data.giss.nasa.gov/gistemp/tabledata_v3/ZonAnn.Ts+dSST.txt
# data downloaded: 21 Oct 2017
data = np.array([
[-69, -9, -15, 6, 18, -31, -29, 25, 1900],
[-63, -4, -8, -8, -8, -29, -34, 24, 1901],
[-169, -42, -33, -9, -13, -30, -43, -7, 1902],
[-57, -39, -54, -28, -41, -37, -40, -48, 1903],
[-36, -57, -55, -50, -57, -39, -49, -130, 1904],
[-26, -19, -60, -19, -14, -35, -37, -32, 1905],
[-47, -1, -36, -24, -22, -22, -23, -69, 1906],
[-82, -71, -47, -34, -37, -22, -37, -118, 1907],
[-52, -53, -48, -46, -51, -29, -48, 69, 1908],
[-88, -52, -41, -47, -55, -38, -52, -55, 1909],
[-78, -17, -47, -53, -53, -33, -45, 18, 1910],
[-43, -37, -43, -43, -46, -43, -52, 7, 1911],
[-72, -74, -49, -28, -12, -36, -29, -161, 1912],
[-75, -27, -47, -46, -19, -28, -40, -102, 1913],
[-67, -9, -28, -14, -4, -16, -18, -34, 1914],
[-68, -8, -17, 0, -3, -15, -23, -196, 1915],
[-41, -44, -22, -47, -43, -22, -17, -103, 1916],
[-74, -38, -40, -73, -58, -23, -9, 8, 1917],
[-129, -17, -30, -36, -31, -6, -22, -26, 1918],
[-94, -45, -28, -23, -18, -15, -32, 22, 1919],
[-7, -7, -33, -37, -17, -20, -48, -113, 1920],
[-12, 26, -7, -27, -25, -24, -38, -82, 1921],
[-47, -30, -11, -32, -34, -26, -28, -26, 1922],
[5, -4, -23, -31, -28, -27, -41, -53, 1923],
[20, -16, -19, -24, -27, -40, -46, -51, 1924],
[-24, 26, -14, -27, -23, -25, -55, -84, 1925],
[40, 26, -21, 1, 4, -36, -45, -62, 1926],
[-13, -13, -12, -12, -19, -32, -45, -136, 1927],
[51, -8, -21, -14, -17, -30, -48, -210, 1928],
[-7, -52, -36, -27, -29, -43, -45, -106, 1929],
[36, 16, -8, -9, -11, -34, -47, -258, 1930],
[33, 8, -13, 6, 3, -42, -40, -39, 1931],
[16, 31, -18, -20, -18, -20, -53, -97, 1932],
[-49, -39, -11, -30, -31, -21, -49, -113, 1933],
[58, 40, -10, -29, -27, -16, -38, -55, 1934],
[11, 6, -14, -19, -24, -30, -42, -169, 1935],
[11, 19, -23, -6, -25, -27, -33, -2, 1936],
[96, 10, 0, 2, -15, -20, -25, 38, 1937],
[103, 45, 4, -20, -32, -6, -11, -85, 1938],
[43, 34, 9, -14, -17, -5, -19, -158, 1939],
[84, 5, -7, 24, 26, 3, -20, -4, 1940],
[-24, -5, 11, 54, 42, 4, -15, -45, 1941],
[22, -1, 9, 12, 3, 6, -5, -88, 1942],
[108, 29, 12, -11, -8, 8, 0, 116, 1943],
[93, 51, 11, 12, 26, 19, -9, -40, 1944],
[35, -2, 5, 11, 24, 4, 0, -196, 1945],
[-22, -1, 18, -1, -8, -22, -24, 2, 1946],
[85, -13, 4, -3, -10, -19, -8, -11, 1947],
[6, 34, 1, -23, -16, -19, -19, -120, 1948],
[12, 16, 6, -24, -17, -17, -15, -164, 1949],
[-2, -32, 3, -30, -29, -6, -16, -79, 1950],
[-3, 3, 13, 0, -13, -22, -16, -16, 1951],
[11, -7, 19, 0, 3, -9, -2, -36, 1952],
[81, 36, 16, 9, 5, -8, -24, -132, 1953],
[51, -22, 0, -14, -29, -17, -20, -76, 1954],
[-45, -8, 22, -29, -38, -15, -8, 128, 1955],
[-29, -47, -9, -28, -37, -13, 2, 66, 1956],
[4, 22, -10, 6, 9, -9, -1, 45, 1957],
[-9, 18, 13, 27, 15, -8, -8, -55, 1958],
[44, 17, 4, 7, 9, -3, -26, -37, 1959],
[33, -6, 11, 7, 0, -10, -7, -79, 1960],
[-14, 37, 17, -6, 1, 14, -18, 14, 1961],
[66, 33, 14, -4, -5, 9, -7, -82, 1962],
[1, 33, 13, 14, 11, -11, -17, 8, 1963],
[-67, -22, -11, -13, -13, -30, -7, -38, 1964],
[-19, -24, -11, -7, 2, -23, -3, -8, 1965],
[-69, -16, 7, 17, 0, -24, -14, 13, 1966],
[46, 26, -7, -11, -17, -9, -2, 28, 1967],
[-22, -2, -9, -1, -9, -14, 0, -11, 1968],
[5, -59, -16, 35, 31, 0, 13, 0, 1969],
[-17, -13, -6, 7, 7, 10, 3, 36, 1970],
[-7, 0, -10, -30, -23, 2, 15, 32, 1971],
[-40, -49, -23, 5, 27, 16, -2, 60, 1972],
[21, 26, 1, 6, 29, 21, 7, 27, 1973],
[-27, -10, -18, -24, -18, 22, -9, 55, 1974],
[19, 36, -6, -31, -17, 14, 14, 22, 1975],
[-5, -33, -28, -14, -11, 11, 22, -38, 1976],
[20, 22, 6, 8, 21, 21, 31, 14, 1977],
[-4, 0, -4, 9, 6, 21, 13, -11, 1978],
[-57, 6, 9, 24, 26, 29, 33, -24, 1979],
[34, 1, 2, 31, 27, 35, 37, 95, 1980],
[132, 77, 8, 21, 14, 28, 38, 52, 1981],
[-30, 7, -8, 24, 29, 19, 32, -24, 1982],
[33, 67, -5, 29, 57, 21, 41, -3, 1983],
[36, 8, -11, 7, 29, 16, 32, 59, 1984],
[38, -27, 0, 3, 14, 30, 44, 9, 1985],
[6, 25, 4, 16, 27, 25, 25, -4, 1986],
[-26, 14, 14, 55, 60, 32, 25, 9, 1987],
[80, 52, 23, 33, 43, 38, 20, 114, 1988],
[43, 67, 28, 9, 21, 36, 30, 35, 1989],
[65, 83, 48, 35, 37, 37, 37, 35, 1990],
[81, 61, 28, 33, 42, 32, 29, 101, 1991],
[-13, 37, -3, 20, 39, 21, 33, 41, 1992],
[68, 24, -9, 28, 36, 28, 39, -14, 1993],
[41, 47, 43, 26, 34, 27, 23, -11, 1994],
[140, 96, 33, 41, 45, 29, 15, 7, 1995],
[86, 21, 12, 31, 33, 34, 28, 109, 1996],
[79, 89, 26, 51, 52, 42, 36, 0, 1997],
[102, 94, 68, 60, 79, 39, 31, 18, 1998],
[50, 83, 76, 15, 27, 47, 19, 4, 1999],
[115, 76, 56, 20, 28, 42, 10, 34, 2000],
[109, 82, 71, 40, 43, 57, 24, 42, 2001],
[138, 98, 58, 54, 62, 47, 31, 82, 2002],
[157, 96, 52, 59, 64, 44, 24, 52, 2003],
[64, 94, 68, 55, 52, 48, 23, -3, 2004],
[205, 118, 56, 60, 61, 49, 18, 80, 2005],
[166, 107, 67, 54, 54, 51, 19, 30, 2006],
[195, 131, 69, 43, 46, 49, 7, 113, 2007],
[142, 104, 61, 32, 40, 53, 9, 44, 2008],
[128, 59, 68, 65, 69, 60, 15, 83, 2009],
[201, 87, 74, 71, 63, 63, 20, 37, 2010],
[212, 91, 57, 38, 33, 65, 18, 86, 2011],
[192, 90, 73, 46, 55, 56, 19, 29, 2012],
[122, 104, 68, 57, 55, 63, 22, 71, 2013],
[184, 114, 76, 69, 62, 71, 18, 51, 2014],
[172, 146, 101, 95, 89, 73, 18, -29, 2015],
[308, 143, 107, 93, 102, 65, 25, 41, 2016],
])
latitudeTick = [90, 64, 44, 24, 0, -24, -44, -64, -90]
latitudes = [
0.5 * (64 + 90),
0.5 * (44 + 64),
0.5 * (24 + 44),
0.5 * (24 + 0),
0.5 * (0 - 24),
0.5 * (-24 - 44),
0.5 * (-44 - 64),
0.5 * (-64 - 90)
]
Tnorm = mpl.colors.Normalize(vmin=-3.0, vmax=3.0)
colornorm = mpl.colors.Normalize(vmin=-2.0, vmax=2.0)
dTheta = 12.0
# A class to draw the lines
class line(object):
def __init__(self, theta, ts, vals, colormap, norm, cnorm, dTheta=20.0):
self.theta = theta
self.f = interp1d(ts, vals)
self.colormap = colormap
self.norm = norm
self.cnorm = cnorm
self.dTheta = dTheta
def draw(self, ax, t, alpha=1.0, zorder=100, point=False):
x0, y0 = np.sin(np.deg2rad(self.theta + 270.0)), np.cos(np.deg2rad(self.theta + 270.0))
val = self.f(t)
thetaVal = dTheta + (1 - self.norm(val)) * (180 - dTheta - dTheta)
x, y = np.sin(np.deg2rad(thetaVal)), np.cos(np.deg2rad(thetaVal))
c = self.colormap(self.cnorm(val))
xx = np.linspace(x0, x, 50)
coef = 0.86 # this coef controls the shape of the curve
p = np.polyfit([x0, coef * 0.5 * (x0 + x), x], [y0, coef * 0.5 * (y0 + y), y], 2)
yy = p[0] * xx**2 + p[1] * xx + p[2]
ax.plot(xx, yy, linewidth=1.5, linestyle='-', color=c, alpha=alpha, zorder=zorder)
if point:
ax.scatter([x], [y], s=60, c=c, marker='H', zorder=999)
# create line objects
cmap = plt.get_cmap('RdYlBu_r')
lines = []
for ii in range(8):
lines.append(line(latitudes[ii], data[:, -1], data[:, ii] * 0.01, cmap, Tnorm, colornorm, dTheta))
# some settings
backgroundcolor = '#1b2a3f'
fontti = 'Lato'
plt.rcParams['axes.facecolor'] = backgroundcolor
mpl.rcParams.update({'font.size': 22})
linecolor = '#f4f6f3'
timenorm = mpl.colors.Normalize(vmin=1900.0, vmax=2020.0)
Nframes = 750
# plot!
iiFrame = 1
for tt in 1900.0 + (2018 - 1900) * np.tanh(1.1 * np.linspace(0.0, 1.0, Nframes)) / np.tanh(1.1): # make the animation a little bit slower in the end
print(tt)
fig, ax = plt.subplots(figsize=(12, 12))
renderer = fig.canvas.get_renderer()
transf = ax.transData.inverted()
for line in lines:
line.draw(ax, np.clip(tt, a_min=1900.0, a_max=2016.0), 0.95, 100, point=True)
line.draw(ax, np.clip(tt - 0.02, a_min=1900.0, a_max=2016.0), 0.7, 99)
line.draw(ax, np.clip(tt - 0.04, a_min=1900.0, a_max=2016.0), 0.5, 98)
line.draw(ax, np.clip(tt - 0.06, a_min=1900.0, a_max=2016.0), 0.3, 97)
for ii in range(8):
x0, y0 = np.sin(np.deg2rad(latitudes[ii] + 270.0)), np.cos(np.deg2rad(latitudes[ii] + 270.0))
ax.scatter([x0], [y0], s=55, c='#e7e5e6', marker='o', zorder=999)
circthetas = np.linspace(0.0, 360.0, 360)
ax.plot(np.sin(np.deg2rad(circthetas)), np.cos(np.deg2rad(circthetas)), linewidth=2.75, c=linecolor, zorder=500)
vals1 = [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]
for v1 in vals1:
thetaV1 = dTheta + (1 - Tnorm(v1)) * (180 - dTheta - dTheta)
r0, r1 = 1.0, 1.05
ax.plot([r0 * np.sin(np.deg2rad(thetaV1)), r1 * np.sin(np.deg2rad(thetaV1))], [r0 * np.cos(np.deg2rad(thetaV1)), r1 * np.cos(np.deg2rad(thetaV1))], linewidth=1.0, linestyle='-', c=linecolor)
ax.plot([0.0, 0.0], [-1.2, 1.2], linewidth=0.5, linestyle='--', c=linecolor, alpha=0.7)
ax.plot([0.0, 1.0], [0.0, 0.0], linewidth=0.3, linestyle='-', c=linecolor, alpha=0.7)
for a in latitudeTick:
r0, r1 = 0.98, 1.02
ax.plot([r0 * np.sin(np.deg2rad(a + 270.0)), r1 * np.sin(np.deg2rad(a + 270.0))], [r0 * np.cos(np.deg2rad(a + 270.0)), r1 * np.cos(np.deg2rad(a + 270.0))], linewidth=1.0, linestyle='-', c=linecolor)
T0x = 1.6
T0y = -1.25
ax.plot([-T0x, T0x], [T0y, T0y], c=linecolor, linewidth=1.0, linestyle='-')
for YY in np.arange(1900.0, 2020.0 + 0.1, 10.0):
ax.plot([-T0x + 2 * T0x * timenorm(YY), -T0x + 2 * T0x * timenorm(YY)], [T0y, T0y - 0.1], linewidth=0.7, c=linecolor, linestyle='-')
plt.text(-T0x + 2 * T0x * timenorm(YY), T0y - 0.12, '{:.0f}'.format(YY), {'ha': 'center', 'va': 'top'}, fontsize=8, fontname=fontti, color=linecolor)
r = Rectangle((-T0x, T0y - 0.08), 2 * T0x * timenorm(np.clip(tt, a_min=1900.0, a_max=2016.0)), 0.08, color='#fcfcfc')
ax.add_patch(r)
ax.set_xlim([-2.0, 2.0])
ax.set_ylim([-2.0, 2.0])
plt.axis('off')
plt.savefig('frames/{:04d}.png'.format(iiFrame), facecolor=backgroundcolor, edgecolor='none', dpi=150)
plt.close()
iiFrame += 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment