Currently, the Muk3D plot window can't be automated and any actions done with the plot won't be recorded in script. To get around this, Muk3D's Python API has a class that allows for the creation of plots within a script. The plotting engine used is called Matplotlib and documentation for it can be found here.
The examples in this tutorial require Muk3D v2019.4.1 or higher to use.
Matplotlib
Matplotlib is a widely used plotting library for Python. While Matplotlib is included in the Muk3D Python libraries, it can't be directly used as many of the examples on their website demonstrate. Instead, a class has been created in Muk3D that allows for Matplotlib code to be run, with some minor modifications.
The plot class
To create a plot, the class muk3d.ui.plot.PlotWindow needs to be sub-classed. This means that a new class is created that inherits PlotWindow, with the user overriding a class method that will create the plot.
A simple example of doing this is shown below.
1 2 3 4 5 6 7 8 9 10 11 12 |
from muk3d.ui.plot import PlotWindow
class BasicPlot(PlotWindow):
def create_plot(self, figure):
x = [1,2,3,4,5]
y = [5,1,2,4,3]
axes = figure.subplots()
axes.scatter(x, y)
plot = BasicPlot()
plot.show()
|
Line 1: The PlotWindow class is imported into the script.
Line 3 - 9: A subclass of PlotWindow is created.
Line 4 - 9: The class method create_plot is overridden with code that creates the plot.
Line 5, 6: X and Y values for the plot are created.
Line 8: The method subplots creates a single plot axes.
Line 9: A scattergram is created using the method scatter and passing X and Y values as arguments.
Line 11: An instance of BasicPlot is created.
Line 12: The plot window is shown.
The create_plot method is provided with a single argument called figure. This is an instance of a Matplotlib Figure class. This is the basic plot element within Matplotlib that holds one or more plot axes. Some examples of how this can be used are shown below.
Types of plots
Some of the tutorials for Matplotlib are shown, adapted for Muk3D's PlotWindow. The headers for each section are hyperlinks to the relevant tutorial on the Matplotlib website.
Line plots
This example shows a simple line plot.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import numpy as np
from muk3d.ui.plot import PlotWindow
class Plot(PlotWindow):
def create_plot(self, figure):
# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
ax = figure.subplots()
ax.plot(t, s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='About as simple as it gets, folks')
ax.grid()
figure.savefig("test.png")
plot = Plot()
plot.show()
|
In this example, data for plotting is created in Lines 8 & 9, and then the plot method is called to add a line plot to the axes. A grid is added using the grid method in Line 16.
This example also saves the plot as a PNG file using the Figure method savefig.
Scattergrams
In this example. some share price data is loaded from a sample dataset. The one day price differential is calculated, and then the data is plotted with trading price and trading volume used to set the marker size and colour.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import numpy as np
import matplotlib.cbook as cbook
from muk3d.ui.plot import PlotWindow
class Scatter(PlotWindow):
def create_plot(self, figure):
# Load a numpy record array from yahoo csv data with fields date, open, close,
# volume, adj_close from the mpl-data/example directory. The record array
# stores the date as an np.datetime64 with a day unit ('D') in the date column.
with cbook.get_sample_data('goog.npz') as datafile:
price_data = np.load(datafile)['price_data'].view(np.recarray)
price_data = price_data[-250:] # get the most recent 250 trading days
delta1 = np.diff(price_data.adj_close) / price_data.adj_close[:-1]
# Marker size in units of points^2
volume = (15 * price_data.volume[:-2] / price_data.volume[0])**2
close = 0.003 * price_data.close[:-2] / 0.003 * price_data.open[:-2]
ax = figure.subplots()
ax.scatter(delta1[:-1], delta1[1:], c=close, s=volume, alpha=0.5)
ax.set_xlabel(r'$\Delta_i$', fontsize=15)
ax.set_ylabel(r'$\Delta_{i+1}$', fontsize=15)
ax.set_title('Volume and percent change')
ax.grid(True)
figure.tight_layout()
scatter = Scatter()
scatter.show()
|
In Lines 10 & 11, sample data is loaded and the last 250 points extracted in Line 13. The price differential is calculated in Line 15 based on the adj_close values of the dataset. Volume and close values are used to create values used for the size and colour of the scattergram markers in Lines 18 and 19.
The scattergram is created in Line 22 by calling the Axes method scatter with the plot data, and colour/size data. Axes labels are set in Lines 24 & 25, and the plot title set in Line 26.
In Line 29, the Figure method tight_layout adds a small amount of padding around the outside of the plot.
Histograms
This example shows how to create a histogram.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import numpy as np
from muk3d.ui.plot import PlotWindow
class Plot(PlotWindow):
def create_plot(self, figure):
np.random.seed(19680801)
# example data
mu = 100.0 # mean of distribution
sigma = 15.0 # standard deviation of distribution
x = mu + sigma * np.random.randn(437)
num_bins = 50
ax = figure.subplots()
# the histogram of the data
n, bins, patches = ax.hist(x, num_bins, density=True)
# add a 'best fit' line
y = ((1.0 / (np.sqrt(2 * np.pi) * sigma)) *
np.exp(-0.5 * (1.0 / sigma * (bins - mu))**2))
ax.plot(bins, y, '--')
ax.set_xlabel('Smarts')
ax.set_ylabel('Probability density')
ax.set_title(r'Histogram of IQ: $\mu=100$, $\sigma=15$')
# Tweak spacing to prevent clipping of ylabel
figure.tight_layout()
plot = Plot()
plot.show()
|
In Lines 6 - 11 plot data is created. The histogram is added to the plot using the Axes method hist. This method takes the values, the number of bins to create, and in this case, the keyword argument density that specifies whether to create a probability density function or not.
In Line 21, and approximate line of best fit is calculated, and added to the plot, overlaying the histogram, in Line 23.
Multiple subplots
This example shows how multiple subplots can be created within a single figure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import numpy as np
from muk3d.ui.plot import PlotWindow
class Plot(PlotWindow):
def create_plot(self, figure):
x1 = np.linspace(0.0, 5.0)
x2 = np.linspace(0.0, 2.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
y2 = np.cos(2 * np.pi * x2)
ax1, ax2 = figure.subplots(2, 1)
ax1.plot(x1, y1, 'o-')
ax1.set_title('A tale of 2 subplots')
ax1.set_ylabel('Damped oscillation')
ax2.plot(x2, y2, '.-')
ax2.set_xlabel('time (s)')
ax2.set_ylabel('Undamped')
plt = Plot()
plt.show()
|
In Line 12, the Figure method subplots is used to create 2 rows and 1 column of plots. This is returned and unpacked into the variables ax1 and ax2. In Lines 14 - 16, data is added to the first plot, along with a title and y axis label. In Lines 18 - 20, data is added to the second plot, along with x and y axis labels.
3D plots
This example shows how a 3D plot can be created and displayed. Once displayed, it can be maniulated with the mouse by left-dragging in the plot window.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
from muk3d.ui.plot import PlotWindow
from matplotlib import cbook
from matplotlib import cm
from matplotlib.colors import LightSource
import numpy as np
class Plot(PlotWindow):
def create_plot(self, figure):
with cbook.get_sample_data('jacksboro_fault_dem.npz') as file, \
np.load(file) as dem:
z = dem['elevation']
nrows, ncols = z.shape
x = np.linspace(dem['xmin'], dem['xmax'], ncols)
y = np.linspace(dem['ymin'], dem['ymax'], nrows)
x, y = np.meshgrid(x, y)
region = np.s_[5:50, 5:50]
x, y, z = x[region], y[region], z[region]
ax = figure.subplots(subplot_kw=dict(projection='3d'))
ls = LightSource(270, 45)
# To use a custom hillshading mode, override the built-in shading and pass
# in the rgb colors of the shaded surface calculated from "shade".
rgb = ls.shade(z, cmap=cm.gist_earth, vert_exag=0.1, blend_mode='soft')
surf = ax.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=rgb,
linewidth=0, antialiased=False, shade=False)
ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])
figure.savefig("surface3d_frontpage.png", dpi=25) # results in 160x120 px image
plot = Plot()
plot.show()
|
In Lines 10 - 19, an example DEM is loaded and the x/y coordinates for each Z value are created. In Line 21, a plot axes is created and a keyword argument called projection is passed with the value of '3d'. This creates a 3D plot axes which is returned into the ax variable.
For 3D surface plots, a light source needs to be created otherwise the surface will not be easily visible. This light source basically creates surface colours representing the shading from a light at a particular location. This is done in Line 26 - the rgb variable is the face colour for each square in the surface.
The 3D surface plot is then created in Line 27 with the method plot_surface.
XKCD style plots
XKCD is a web-comic that often has some interesting graphs. Matplotlib has the ability to replicate this style (for that hand drawn look).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
from muk3d.ui.plot import PlotWindow
import matplotlib.pyplot as plt
import numpy as np
class Plot(PlotWindow):
def create_plot(self, figure):
with plt.xkcd():
# Based on "Stove Ownership" from XKCD by Randall Munroe
# https://xkcd.com/418/
ax = figure.add_axes((0.1, 0.2, 0.8, 0.7))
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.set_xticks([])
ax.set_yticks([])
ax.set_ylim([-30, 10])
data = np.ones(100)
data[70:] -= np.arange(30)
ax.annotate(
'THE DAY I REALIZED\nI COULD COOK BACON\nWHENEVER I WANTED',
xy=(70, 1), arrowprops=dict(arrowstyle='->'), xytext=(15, -10))
ax.plot(data)
ax.set_xlabel('time')
ax.set_ylabel('my overall health')
figure.text(
0.5, 0.05,
'"Stove Ownership" from xkcd by Randall Munroe',
ha='center')
plot = Plot()
plot.show()
|
The example below shows a barchart.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from muk3d.ui.plot import PlotWindow
import matplotlib.pyplot as plt
import numpy as np
class Plot(PlotWindow):
def create_plot(self, figure):
with plt.xkcd():
ax = figure.add_axes((0.1, 0.2, 0.8, 0.7))
ax.bar([0, 1], [0, 100], 0.25)
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.set_xticks([0, 1])
ax.set_xticklabels(['CONFIRMED BY\nEXPERIMENT', 'REFUTED BY\nEXPERIMENT'])
ax.set_xlim([-0.5, 1.5])
ax.set_yticks([])
ax.set_ylim([0, 110])
ax.set_title("CLAIMS OF SUPERNATURAL POWERS")
figure.text(
0.5, 0.05,
'"The Data So Far" from xkcd by Randall Munroe',
ha='center')
plot = Plot()
plot.show()
|