Visualization

Overview

Visualization poses a significant challenge for the space weather community: output from models and data are very domain-specific, both in content (coordinate systems, units) and in representation (file formats and data structures). On the other hand, science users also have their preferred context for analyzing these results - for instance, they may only want simulation results interpolated on a satellite trajectory and in a specific coordinate system with their own prefered units.

Kamodo aims to strike a balance between the intent of the model (or data) provider and the goals of the user, by making it easy for developers to provide context for their output and for users to easily change that context. It accomplishes this in two ways:

  • By leveraging default arguments given by model and data providers
  • By mapping the shape of function inputs and output to certain registered plot types

This strategy allows Kamodo to automatically generate plots for arbitrary model output and data sources, while still allowing for customization by the end user.

Available Plot Types

Kamodo keeps a registry of plotting functions, indexed by argument shape and output shape.

When a user tries to plot a given variable, a lookup is made into the table above and the corresponding plotting function is used to generate the output. Examples below demonstrate the intended workflow.

1-Dimensional line plots

from kamodo import Kamodo, kamodofy
kamodo = Kamodo('g_N[kg] = x_N**2')
kamodo['f[g]'] = 'g_N'
kamodo
kj/filesystem-disk-unix.c++:1690: warning: PWD environment variable doesn't match current directory; pwd = /Users/asherp/git/ensemblegovservices/kamodo-core

\begin{equation}\operatorname{g_{N}}{\left(x_{N} \right)}[kg] = x_{N}^{2}\end{equation} \begin{equation}f{\left(x_{N} \right)}[g] = 1000 \operatorname{g_{N}}{\left(x_{N} \right)}\end{equation}

Here we have defined a function which returns an array of shape . As input, it takes one argument which also has size . We also generate a function which is the same as but with a different units.

Note

We could have named the function instead of . The variable names have no bearing on the resulting plots - only the argument input shapes and output shapes matter.

When we call Kamodo's plot function, we define which variable we are plotting and domain over which the arguments are applied:

import numpy as np
import plotly.io as pio

fig = kamodo.plot(f = dict(x_N = np.linspace(-4, 3, 30)))
# pio.write_image(fig, 'images/1d-line.svg')

1d

This is the graph for .

Time series data

The process for time series data is the same, except we use a pandas datetime index for the input argument.

import pandas as pd
t_N = pd.date_range('Nov 9, 2018', 'Nov 20, 2018', freq = 'H')
@kamodofy(units = 'kg/m**3')
def rho_N(t_N = t_N):
    dt_days = (t_N - t_N[0]).total_seconds()/(24*3600)
    return 1+np.sin(dt_days) + .1*np.random.random(len(dt_days))

kamodo = Kamodo(rho_N = rho_N, verbose = False)
kamodo
\begin{equation}\rho_{N}{\left(t_{N} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(t_{N} \right)}\end{equation}
fig = kamodo.plot('rho_N')

In this case, we only need to name the variable we wish to plot, because we have already defined a function with a default parameter for .

# pio.write_image(fig, 'images/1d-time-series.svg')

timeseries

Note

By providing default parameters, the function author can insure that anyone plotting the variable will not need to know where to place resolution!

2-D Parametric charts

For 2-D Plots, the output function must have input shape and output shape .

from kamodo import Kamodo
@kamodofy(units = 'cm')
def x_Ncomma2(theta_N = np.linspace(0,6*np.pi, 200)):
    r = theta_N
    x = r*np.cos(theta_N)
    y = r*np.sin(theta_N)
    return np.array(list(zip(x,y)))

kamodo = Kamodo(x_Ncomma2 = x_Ncomma2)
kamodo
\begin{equation}\operatorname{x_{N,2}}{\left(\theta_{N} \right)}[cm] = \lambda{\left(\theta_{N} \right)}\end{equation}

Here, we again provide a default array for so the end user does not need to:

fig = kamodo.plot('x_Ncomma2')
# pio.write_image(fig, 'images/fig-2d.svg')

param2d

3-Dimensional parametric curves

For 3-D parametric curves, the output function must have input shape and output shape .

@kamodofy(units = 'km')
def x_Ncomma3(t_N = pd.date_range('Nov 12, 2018', 'Dec 30, 2018', freq = '4 H')):
    dt_days = (t_N - t_N[0]).total_seconds()/(24*3600)
    theta = dt_days*np.pi/5
    r = theta
    x = r*np.cos(theta)
    y = r*np.sin(theta)
    z = r
    return np.array(list(zip(x,y,z)))

kamodo = Kamodo(x_Ncomma3 = x_Ncomma3)
kamodo
\begin{equation}\operatorname{x_{N,3}}{\left(t_{N} \right)}[km] = \lambda{\left(t_{N} \right)}\end{equation}
fig = kamodo.plot('x_Ncomma3')
# pio.write_image(fig, 'images/3d-line.svg')

3d-line

Functions of three N-d arrays are also interpreted as 3D parametric plots, but with an additonal color component.

s = np.linspace(0, 8*np.pi, 100)
x = 10*np.sin(s/8)
y = 10*np.sin(s)
z = s

@kamodofy(units = 'kg')
def f_N(x_N = x, y_N = y, z_N = z):
    return x_N**2+y_N**2+z_N**2

kamodo = Kamodo(f_N = f_N)
kamodo
\begin{equation}\operatorname{f_{N}}{\left(x_{N},y_{N},z_{N} \right)}[kg] = \lambda{\left(x_{N},y_{N},z_{N} \right)}\end{equation}
fig = kamodo.plot('f_N')
# pio.write_image(fig, 'images/3d-points.svg')

3d-points

Vector fields

Kamodo generates a 2-d vector (quiver) plot for functions of one variable, if both the input and output have shape (N,2). The input positions are assumed to be , and the output vectors are assumed to be ,

theta_N = np.linspace(0,6*np.pi, 200)
r = theta_N
x = r*np.cos(theta_N)
y = r*np.sin(theta_N)
points = np.array(list(zip(x,y)))

@kamodofy(units = 'cm')
def fvec_Ncomma2(rvec_Ncomma2 = points):
    return rvec_Ncomma2

kamodo = Kamodo(fvec_Ncomma2 = fvec_Ncomma2)
kamodo
\begin{equation}\vec{f}_{N,2}{\left(\vec{r}_{N,2} \right)}[cm] = \lambda{\left(\vec{r}_{N,2} \right)}\end{equation}
fig = kamodo.plot('fvec_Ncomma2')
# pio.write_image(fig, 'images/fig2d-vector.svg')

fig2dvec

If we wish to represent a grid of vectors, we must first unravel the grid as a string of points.

x = np.linspace(-np.pi, np.pi, 25)
y = np.linspace(-np.pi, np.pi, 30)
xx, yy = np.meshgrid(x,y)
points = np.array(list(zip(xx.ravel(), yy.ravel())))

def fvec_Ncomma2(rvec_Ncomma2 = points):
    ux = np.sin(rvec_Ncomma2[:,0])
    uy = np.cos(rvec_Ncomma2[:,1])
    return np.vstack((ux,uy)).T

kamodo = Kamodo(fvec_Ncomma2 = fvec_Ncomma2)
kamodo
\begin{equation}\vec{f}_{N,2}{\left(\vec{r}_{N,2} \right)} = \lambda{\left(\vec{r}_{N,2} \right)}\end{equation}
fig = kamodo.plot('fvec_Ncomma2')
# pio.write_image(fig, 'images/fig2d-vector-field.svg')

fig2dvec

3D vector fields

Functions representing 3D vector fields should have one argument of shape (N,3) and an output shape of (N,3)

x, y, z = np.meshgrid(np.linspace(-2,2,4),
                      np.linspace(-3,3,6),
                      np.linspace(-5,5,10))
points = np.array(list(zip(x.ravel(), y.ravel(), z.ravel())))
def fvec_Ncomma3(rvec_Ncomma3 = points):
    return rvec_Ncomma3

kamodo = Kamodo(fvec_Ncomma3 = fvec_Ncomma3)
kamodo
\begin{equation}\vec{f}_{N,3}{\left(\vec{r}_{N,3} \right)} = \lambda{\left(\vec{r}_{N,3} \right)}\end{equation}
fig = kamodo.plot('fvec_Ncomma3')
# pio.write_image(fig, 'images/fig3d-vector.svg')

fig3dvec

Contour plots

Scalar functions of two variables of size (N) and (M) and output size (N,M) will generate contour plots. Kamodo can handle both ij indexing and xy indexing.

from kamodo import Kamodo
@kamodofy(units = 'cm^2')
def f_NcommaM(x_N = np.linspace(0, 8*np.pi,100), y_M = np.linspace(0, 5, 90)):
    x, y = np.meshgrid(x_N, y_M, indexing = 'xy')
    return np.sin(x)*y

kamodo = Kamodo(f_NcommaM = f_NcommaM)
kamodo
\begin{equation}\operatorname{f_{N,M}}{\left(x_{N},y_{M} \right)}[cm^{2}] = \lambda{\left(x_{N},y_{M} \right)}\end{equation}
fig = kamodo.plot('f_NcommaM')
# pio.write_image(fig, 'images/fig2d-contour.svg')

contour

Since and have differnt sizes, we could have used indexing=ij as an argument to meshgrid and kamodo would have produced the same figure - Kamodo swaps the ordering where appropriate. In the event that both arguments have the same size, we can pass an indexing argument as an option to the plot function.

@kamodofy(units = 'cm**2')
def f_NN(x_N = np.linspace(0, 8*np.pi, 90), y_N = np.linspace(0, 5, 90)):
    x, y = np.meshgrid(x_N, y_N, indexing = 'xy')
    return np.sin(x)*y

kamodo = Kamodo(f_NN = f_NN)
kamodo
\begin{equation}\operatorname{f_{NN}}{\left(x_{N},y_{N} \right)}[cm^{2}] = \lambda{\left(x_{N},y_{N} \right)}\end{equation}
fig = kamodo.plot(f_NN = dict(indexing = 'xy'))
# pio.write_image(fig, 'images/fig2d-contour-xy.svg')

fig2dcontourxy

Skew (Carpet) Plots

Functions of two arguments each having shape (N,M) matching the output shape will produce skewed contour plots, whereby the x and y components of the grid are independent.

r = np.linspace(1, 3, 20)
theta = np.linspace(0, np.pi, 14)
r_, theta_ = np.meshgrid(r,theta)
XX = r_*np.cos(theta_)
YY = r_*np.sin(theta_)

@kamodofy(units = 'cm**2')
def f_NM(x_NM = XX, y_NM = YY):
    return np.sin(x_NM)+y_NM

kamodo = Kamodo(f_NM = f_NM)
kamodo
\begin{equation}\operatorname{f_{NM}}{\left(x_{NM},y_{NM} \right)}[cm^{2}] = \lambda{\left(x_{NM},y_{NM} \right)}\end{equation}
fig = kamodo.plot('f_NM')
# pio.write_image(fig, 'images/fig2d-skew.svg')

fig2dskew

Parametric surfaces

To generate a purely geometrical parametric surface, supply a functions of three variables, each of size (N,M) and of output shape (1).

from kamodo import Kamodo
u = np.linspace(-2, 2, 40)
v = np.linspace(-2, 2, 50)
uu, vv = np.meshgrid(u,v)

@kamodofy(units = 'cm')
def parametric(x_NM = uu*np.sin(vv*np.pi), 
               y_NM = vv, 
               z_NM = np.exp(-uu**2-vv**2)):
    return np.array([1])
kamodo = Kamodo(p = parametric)
kamodo
\begin{equation}p{\left(x_{NM},y_{NM},z_{NM} \right)}[cm] = \lambda{\left(x_{NM},y_{NM},z_{NM} \right)}\end{equation}
fig = kamodo.plot('p')
# pio.write_image(fig, 'images/3d-parametric.svg')

3dparametric

To control the color of the parametric surface, have the output shape be (N,M).

R = 1
theta = np.linspace(.2*np.pi, .8*np.pi, 40)
phi = np.linspace(0, 2*np.pi, 50)
theta_, phi_ = np.meshgrid(theta, phi)
r = (R +.1*(np.cos(10*theta_)*np.sin(14*phi_)))

xx = r*np.sin(theta_)*np.cos(phi_)
yy = r*np.sin(theta_)*np.sin(phi_)
zz = r*np.cos(theta_)

@kamodofy(units = 'cm')
def spherelike(x_NM = xx, y_NM = yy, z_NM = zz):
    return .1*x_NM + x_NM**2 + y_NM**2 + z_NM**2

kamodo = Kamodo(h_NM = spherelike)
kamodo
\begin{equation}\operatorname{h_{NM}}{\left(x_{NM},y_{NM},z_{NM} \right)}[cm] = \lambda{\left(x_{NM},y_{NM},z_{NM} \right)}\end{equation}
fig = kamodo.plot('h_NM')
# pio.write_image(fig, 'images/3d-parametric-color.svg')

parametric3dcolor

Map-to-plane

We often need to produce slices through a volumetric grid of data. This may be accomplished through the use of volumetric grid interpolators equipped with default values for each of the input arguments. Suppose such a function has default input arguments of size (L), (M), (N), and output shape (L,M,N), then a cartesian plane will be generated if the user overrides one of these defaults (e.g. setting ).

@kamodofy(units = 'g/cm**3')
def f_LMN(
      x_L = np.linspace(-5, 5, 50), 
      y_M = np.linspace(0, 10, 75), 
      z_N = np.linspace(-20, 20, 100)):
    xx, yy, zz = np.meshgrid(x_L,y_M,z_N, indexing = 'xy')
    return xx + yy + zz

kamodo = Kamodo(f_LMN = f_LMN)
kamodo
\begin{equation}\operatorname{f_{LMN}}{\left(x_{L},y_{M},z_{N} \right)}[\frac{g}{cm^{3}}] = \lambda{\left(x_{L},y_{M},z_{N} \right)}\end{equation}
fig = kamodo.plot(f_LMN = dict(z_N = -5))
# pio.write_image(fig,'images/fig2d-map-to-plane.svg')

maptoplane

Tip

By providing appropriate defaults for the undelying grid structure, the interpolator author can ensure that the user can generate figures with optimal resolution!

Multiple traces

Kamodo supports multiple traces in the same figure. Simply provide plot with multiple function-argument pairs.

from kamodo import Kamodo
t_N = pd.date_range('Nov 9, 2018', 'Nov 20, 2018', freq = 'H')

@kamodofy(units = 'kg/m**3')
def rho_N(t_N = t_N):
    dt_days = (t_N - t_N[0]).total_seconds()/(24*3600)
    return 1+np.sin(dt_days) + .1*np.random.random(len(dt_days))

@kamodofy(units = 'nPa')
def p_N(t_N = t_N):
    dt_days = (t_N - t_N[0]).total_seconds()/(24*3600)
    return 1+np.sin(2*dt_days) + .1*np.random.random(len(dt_days))


kamodo = Kamodo(rho_N = rho_N, p_N = p_N, verbose = False)
kamodo
\begin{equation}\rho_{N}{\left(t_{N} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(t_{N} \right)}\end{equation} \begin{equation}\operatorname{p_{N}}{\left(t_{N} \right)}[nPa] = \lambda{\left(t_{N} \right)}\end{equation}
fig = kamodo.plot('p_N','rho_N')
# pio.write_image(fig, 'images/multi-trace.svg')

multitrace

Note

Plot types must be compatible for kamodo to plot different variables on the same axes.

Kamodo can also handle multiple traces in 3D

from kamodo import Kamodo, kamodofy

@kamodofy(units = 'g/cm**3')
def f_LMN(
      x_L = np.linspace(-5, 5, 50), 
      y_M = np.linspace(0, 10, 75), 
      z_N = np.linspace(-20, 20, 100)):
    xx, yy, zz = np.meshgrid(x_L,y_M,z_N, indexing = 'xy')
    return xx + yy + zz

kamodo = Kamodo(f_LMN = f_LMN, g_LMN = f_LMN)
kamodo
\begin{equation}\operatorname{f_{LMN}}{\left(x_{L},y_{M},z_{N} \right)}[\frac{g}{cm^{3}}] = \lambda{\left(x_{L},y_{M},z_{N} \right)}\end{equation} \begin{equation}\operatorname{g_{LMN}}{\left(x_{L},y_{M},z_{N} \right)}[\frac{g}{cm^{3}}] = \lambda{\left(x_{L},y_{M},z_{N} \right)}\end{equation}
fig = kamodo.plot(f_LMN = dict(z_N = 0), 
                  g_LMN = dict(y_M = 5))
# pio.write_image(fig, 'images/multi-trace3d.svg')

multi3d

Bug

Multiple traces results in different colorbars which may overlap. More control over the layout will be available in future updates.

Interactive Plotting

For interactive 3d plots, we take advantage of Plotly's in-browser plotting library.

from plotly.offline import iplot, plot, init_notebook_mode

To generate a separate interactive html page, use iplot instead of plot:

# plot(fig, filename = 'sample_plot.html') #uncomment to render 3D interactive plot in this cell 

navigate to the 3d interactive plot: sample_plot.html.

Alternatively, you may work with interactive plots directly in jupyter notebooks:

# init_notebook_mode() # uncomment to initialize plotly for notebook
# iplot(fig) #uncomment to render 3D interactive plot in this cell 

Note

We have commented out the above lines because they do not render properly on the documentation server, but rest assured they do work!