Created
October 22, 2017 11:12
-
-
Save anttilipp/cdd91faecf3d7ab1329a816cd1cc20fa 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"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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
This is the overlay png file. It would not have been necessary to add the static texts as png but this time it was just the quick way of doing it instead of doing it in the Python code.