3

I would like to make a 3D plot using a function that returns a plot and the input argument it takes. This is my code for the function:

def cumulative(moment):
    bins = np.zeros(32)
    x = upper_bin
    for i in range(32):
        bins[i] = burst_average[moment, 0:i+1].sum()
    plt.ylim(ymax = 1000)
    plt.xlabel('grain size (um)')
    plt.ylabel('concentration (uL/L)')
    plt.title('grain size distribution over time')
    plt.plot(x, bins, c = 'b', label=dates[i])
    return 

import ipywidgets as widgets
from ipywidgets import interact

interact(cumulative, moment=widgets.FloatSlider(min = int(0), max = int(nr_burst-1), step = 1, description = 'moment'));

where x is a list of 32 values, bins is an array of 32 values as well that changes for every moment. In total, nr_burst plots are made, which is about 2017. The widget works, however I want to include this in my report, so I would like a 3D plot instead.

I tried something like

from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits import mplot3d    

b0 = np.linspace(0, nr_burst-1, nr_burst)
b= []
for i in range(len(b0)):
    b.append(int(b0[i]))

ax.scatter3D(cumulative(b), b)

This did not work, gave the error ValueError: Arguments 'xs' and 'ys' must be of same size.

I also tried the function to return x and b and plot like

ax.scatter3D(cumulative(b)[0], b, cumulative(b)[1])

Which gave the error TypeError: 'NoneType' object is not subscriptable.

Gsk
  • 2,929
  • 5
  • 22
  • 29

1 Answers1

1

After plotting your original data use:

ax = plt.gca() # get the axis handle of the current graphic artist

data_2d = ax.lines[0] # this just extracts the first dataset

x,y = data_2d.get_xdata(), data_2d.get_ydata() #this will be your x and y data

Using your original code this can be plugged in like:

ax.scatter3D(x, b, y)

Second option

Modify your original function to return the axis handle.

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def cumulative(moment):
    fig, ax = plt.subplots()
    bins = np.cumsum(np.arange(moment))
    x = np.arange(moment)
    ax.plot(x, bins, c = 'b')
    ax.set_xlabel('grain size (um)')
    ax.set_ylabel('concentration (uL/L)')
    ax.set_title('grain size distribution over time')
    ax.set_ylim(ymax = bins.max())
    return fig, ax

b = 32 #just a random scalar to test

fig, ax = cumulative(b) #call the function and assign the returning values

data_2d = ax.lines[0] # get your data
x,y = data_2d.get_xdata(), data_2d.get_ydata() #your data separated for x and y

plot3d = plt.figure()
ax3d = plot3d.add_subplot(111, projection='3d')
ax3d.scatter(x,b,y)
Fourier
  • 2,795
  • 3
  • 25
  • 39
  • 1
    Thanks for the suggestion. I do not know how to implement method 1 (it says list index out of range for data_2d = ax.lines[0]. I don't know how to use the output values of the 2nd option, could you elaborate a bit? – Rieneke van Noort Jun 05 '18 at 11:56
  • @RienekevanNoort The original function call will produce a plot object that should be accessible with the code provided. Your error indicates that the list is empty. Try to run `cumulative(b); ax = plt.gca()` in the same instance. – Fourier Jun 05 '18 at 12:16
  • 1
    I am unfamiliar with the method, I'm still not able to get it to work. When I try to implement the code from your 2nd suggestion (without altering except for cumulative(moment) becomes cumulative(b)), I try to run ax.scatter3D(x, y, b). I then get 'AxesSubplot' object has no attribute 'scatter3D' – Rieneke van Noort Jun 05 '18 at 12:32
  • @RienekevanNoort You have to create a new figure instance. `fig = plt.figure(); ax = Axes3D(fig); ax.scatter(x,y,b)` – Fourier Jun 05 '18 at 13:18
  • 1
    I do now get a graph for every input argument in fig, ax = cumulative(moment) for moment. But when I put fig, ax = cumulative(b) (where b is the moment at which I would like it to work), I get an error: Argument 'zs' must be of same size as 'xs' and 'ys' or of size 1. Why would zs have to be of the same size? It is just an input value of the function? – Rieneke van Noort Jun 05 '18 at 13:49
  • @RienekevanNoort See my updated answer. I removed parts of your code because the functions are missing. However, this works on my machine producing two plots as expected. – Fourier Jun 05 '18 at 16:32