Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Wireframe 3dplot colormap #3562

Open
hughesadam87 opened this issue Sep 25, 2014 · 15 comments
Open

Feature Request: Wireframe 3dplot colormap #3562

hughesadam87 opened this issue Sep 25, 2014 · 15 comments
Labels
keep Items to be ignored by the “Stale” Github Action New feature topic: mplot3d

Comments

@hughesadam87
Copy link
Contributor

Problem

Currently, ax.plot_wireframe() does not support a colormap; however, this is a very important use case for waterfall plots in spectroscopy.

A workaround was recently implemented in stackoverflow:

http://stackoverflow.com/questions/24909256/how-to-obtain-3d-colored-surface-via-python/26026556#26026556

However, this workaround does not work in the case for datasets in which X and Y are not of the same dimension. For example, if X is 100 points and Y is 50 points and Z(X,Y) is of shape (100X50) the workaround will fail.

I've looked into it, and the workaround fails because it depends on LineCollection._segment3d. In the case of equally space data (len(X) = len(Y)), _segment3d is ndim=3; otherwise it is ndim=1. This is the expected behavior of converting a list of list into a numpy array. plot_wireframe() passes a list of lists into LineCollection, and doing np.array() on a list of list will return a 1d array when the lists are of unequal length.

Ideas

To fix this either requires changing how plot_wireframe() sends data into LineCollection3d. If it were to pass an array-object somehow instead of a list of lists, this might alleviate the behavior. Alternatively, perhaps LineCollection._store3d could be expanded to handle this case.

Use cases

I'd expect both of these to work:

ax.plot_wireframe(X, Y, Z, cmap='jet')

and

wires = ax.plot_wireframe(X, Y, Z)
wires.set_cmap('jet')
#would wire.set_array() still be necessary?  

PS, this might be worth combining with related: #3554 . The resulting product could produce plots like this one from mathworks: http://www.mathworks.com/help/matlab/ref/waterfall_1.png

@tacaswell tacaswell added this to the v1.5.x milestone Sep 25, 2014
@pelson
Copy link
Member

pelson commented Sep 25, 2014

@tacaswell - I wonder if it is a bad idea to put this into a milestone like this - the label tells us it is a wishlist item, so it would get done if we had enough time, but the truth is, the lack of this feature wouldn't hold us back from an actual v1.5.x release. I'd be comfortable, if you want all issues to be milestoned, to have a the future milestone... 😄

@tacaswell
Copy link
Member

Fair enough. My view was that mile stones are flexible and make heavy use of punting to the next milestone, but I see why that is a bad thing.

@hughesadam87
Copy link
Contributor Author

PS,

I know this is a low priority, but if anyone has an outlined solution in mind, I can give it a shot.

@WeatherGod
Copy link
Member

Reading through your explanation, it makes me wonder if the problem is that _segments3d has different dimensionality in different cases. I can't remember for sure, but I don't think that is intentional (although, I can't imagine segments3d working as 1d -- did you mean 2d?).

@hughesadam87
Copy link
Contributor Author

_segments3d is created through through a call to np.asanyarray(). From Line3DCollection:

    self._segments3d = np.asanyarray(segments)

In the example workaround I posted, the user starts with X, Y mesh that is the same number of points; each has 100 points:

X.shape
(120, 120)
Y.shape
(120, 120)
Z.shape
(120, 120)

This results in a wire._segments_3d of array shape (26, 120, 3).

 type(wire._segments3d)
<type 'numpy.ndarray'>
wire._segments3d.shape
(26, 120, 3)

When I go and use my own data where X has 704 points, Y has 100 points and Z is 704 x 100, I get X, Y and Z as (704,100) shape. However, wire.segments3d is as follows following:

wire._segments3d.shape
(83,)
type(wire._segments3d)
<type 'numpy.ndarray'>

See, it's a 1D array of 83 items. However, each item is a (120,3) array:

wire._segments3d[0].shape
(120, 3)

This is merely the behavior of np.asanyarray when it gets input of even vs. uneven size.

See, it's a 1D array of 83 items. However, each item is a (120,3) array:

wire._segments3d[0].shape
(120, 3)

I'd posit that if instead it returned an (83, 120, 3) array, everything would work peachy. It doesn't seem intentional, as np.asanyarray() has this dual behavior.

When I try to reshape the array, I can't!

wire._segments3d.reshape(83, 120, 3)

Traceback (most recent call last):
  File "<string>", line 1, in <fragment>
ValueError: total size of new array must be unchanged

@WeatherGod
Copy link
Member

Holy crap! That is definitely not intended behavior. What version of Numpy
are you using? I had no clue asanyarray() could behave like that.

On Fri, Oct 3, 2014 at 12:46 PM, Adam Hughes notifications@github.com
wrote:

_secments3d is created through through np.asanyarray. From
Line3DCollection:

self._segments3d = np.asanyarray(segments)

In the example workaround I posted, the user starts with X, Y mesh that is
the same dimension:

X.shape
(120, 120)
Y.shape
(120, 120)
Z.shape
(120, 120)

This results in a wire._segments_3d of array shape (26, 120, 3).

type(wire._segments3d)
<type 'numpy.ndarray'>
wire._segments3d.shape
(26, 120, 3)

When I go and use my own data where X has 704 points, Y has 100 points and
Z is 704 x 100, I get the following:

wire._segments3d.shape
(83,)
type(wire._segments3d)
<type 'numpy.ndarray'>

See, it's a 1D array of 83 items. However, each item is a (120,3) array:

wire._segments3d[0].shape
(120, 3)

This is merely the behavior of np.asanyarray when it gets input of even
vs. uneven size.


Reply to this email directly or view it on GitHub
#3562 (comment)
.

@hughesadam87
Copy link
Contributor Author

1.8.2

I looked into it and it did seem to be the intended behavior of numpy; however, I can't find the discussion on the subject now that I'm looking for it again. Ya, it's wonky!

@WeatherGod
Copy link
Member

Well, more specifically, it might be an issue with the broadcasting. The input to asanyarray might be slightly malformed. It is entirely a coincidence that it would seem to work fine because, ultimately, the result could be represented as a perfectly normal 3d array (I am assuming that [wire._segments3d[i].shape for i in range(len(wire._segments3d))] would return identical shapes, right?)

@hughesadam87
Copy link
Contributor Author

I'm not sure. It turns out that each row in wire._segment3d is not an array, but a list! It's a 1-d array of 83 lists! I doubt anyone calling "asanyarray" intended for a 1d array of nested lists!

I also mispoke, the 1D array is not of shape (120,3) it's of shape (100,3).

I created a reproducible example on a small 5x5 mesh. Change the "RDIM" or "CDIM" attributes to be unequal (for example RDIM = 4) to get the errant behavior.

This still incorporates the workaround.

from mpl_toolkits.mplot3d import axes3d
from mpl_toolkits.mplot3d import art3d
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl


RDIM = 5
CDIM = 5

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
X, Y, Z = X[0:RDIM, 0:CDIM], Y[0:RDIM, 0:CDIM], Z[0:RDIM, 0:CDIM]
wire = ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)

# Retrive data from internal storage of plot_wireframe, then delete it
nx, ny, _  = np.shape(wire._segments3d)
wire_x = np.array(wire._segments3d)[:, :, 0].ravel()
wire_y = np.array(wire._segments3d)[:, :, 1].ravel()
wire_z = np.array(wire._segments3d)[:, :, 2].ravel()
wire.remove()

# create data for a LineCollection
wire_x1 = np.vstack([wire_x, np.roll(wire_x, 1)])
wire_y1 = np.vstack([wire_y, np.roll(wire_y, 1)])
wire_z1 = np.vstack([wire_z, np.roll(wire_z, 1)])
to_delete = np.arange(0, nx*ny, ny)
wire_x1 = np.delete(wire_x1, to_delete, axis=1)
wire_y1 = np.delete(wire_y1, to_delete, axis=1)
wire_z1 = np.delete(wire_z1, to_delete, axis=1)
scalars = np.delete(wire_z, to_delete)

segs = [list(zip(xl, yl, zl)) for xl, yl, zl in \
                 zip(wire_x1.T, wire_y1.T, wire_z1.T)]

# Plots the wireframe by a  a line3DCollection
my_wire = art3d.Line3DCollection(segs, cmap="hsv")
my_wire.set_array(scalars)
ax.add_collection(my_wire)

plt.colorbar(my_wire)
plt.show()

@hughesadam87
Copy link
Contributor Author

If we were able to get the segments3d to return the numpy array of correct dimension, do you think adding colormap support would be simple, or would that require a lot of non-trivial steps as well?

@WeatherGod
Copy link
Member

I think it would be rather simple

On Mon, Oct 6, 2014 at 2:57 PM, Adam Hughes notifications@github.com
wrote:

If we were able to get the segments3d to return the numpy array of correct
dimension, do you think adding colormap support would be simple, or would
that require a lot of non-trivial steps as well?


Reply to this email directly or view it on GitHub
#3562 (comment)
.

@hughesadam87
Copy link
Contributor Author

@WeatherGod

Hey guys. I did some followup on this, and found out that it's not really np.asanyarray() that is to blame. It turns out for an uneven mesh, we get inconsisent-length lists. I took the example above, instead of 5x5 I did a 2x2 mesh and a 2x3 mesh. Here is the structure of segments (list of list of tuples) and the subsequent array:

MESH: 2 x 2

segments
  [  [(-30.0, -30.0, -0.0098206401731603336), (-29.5, -30.0, -0.011395702503960076)], 
    [(-30.0, -29.5, -0.011395702503917006), (-29.5, -29.5, -0.013223377831641684)], 
    [(-30.0, -30.0, -0.0098206401731603336), (-30.0, -29.5, -0.011395702503917006)], 
    [(-29.5, -30.0, -0.011395702503960076), (-29.5, -29.5, -0.013223377831641684)]
  ]

Len 4 list. Each element is a list of 2 tuples. Each tuple is 3 elements.

np.array(segments)
array([[[ -3.00000000e+01,  -3.00000000e+01,  -9.82064017e-03],
    [ -2.95000000e+01,  -3.00000000e+01,  -1.13957025e-02]],

   [[ -3.00000000e+01,  -2.95000000e+01,  -1.13957025e-02],
    [ -2.95000000e+01,  -2.95000000e+01,  -1.32233778e-02]],

   [[ -3.00000000e+01,  -3.00000000e+01,  -9.82064017e-03],
    [ -3.00000000e+01,  -2.95000000e+01,  -1.13957025e-02]],

   [[ -2.95000000e+01,  -3.00000000e+01,  -1.13957025e-02],
    [ -2.95000000e+01,  -2.95000000e+01,  -1.32233778e-02]]])

This gives a nice array of correct shape.

 np.array(segments).shape
 (4, 2, 3)

MESH: 2x3

    segments
    [ [(-30.0, -30.0, -0.0098206401731603336), (-29.5, -30.0, -0.011395702503960076)],
      [(-30.0, -29.000000000000004, -0.013190360675610186), (-29.5, -29.000000000000004,         -0.015305868408668705)],
      [(-30.0, -30.0, -0.0098206401731603336), (-30.0, -29.5, -0.011395702503917006), (-30.0, -29.000000000000004, -0.013190360675610186)],
      [(-29.5, -30.0, -0.011395702503960076), (-29.5, -29.5, -0.013223377831641684), (-29.5, -29.000000000000004, -0.015305868408668705)]
    ]

Len 4 list. First two elements are list of 2 tuples. Second two are a list of 3 tuples!! Each tuple is 3 elements.

Segments is asymmetric, so of course np.array() is not going to cast it nicely. Any idea of if this is the way it should be for a non-symmetric mesh?

@hughesadam87
Copy link
Contributor Author

@WeatherGod

PS,

I made a spectroscopy 2d/3d plotting tutorial where I interface all of the mpl 3d plots for spectroscopy. I used a slice of the data so you can see how I'd envision the colormaps to work, and why I'm obviously eager to figure out this issue. Thought it would make a cool demo for 3d MPL:

http://nbviewer.ipython.org/github/hugadams/pyuvvis/blob/master/examples/Notebooks/plotting_2d3d.ipynb?create=1

@tacaswell tacaswell modified the milestones: 2.1 (next point release), 2.2 (next next feature release) Oct 3, 2017
@github-actions
Copy link

github-actions bot commented Mar 6, 2023

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Mar 6, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Apr 6, 2023
@rcomer rcomer added the status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. label May 30, 2023
@scottshambaugh scottshambaugh added keep Items to be ignored by the “Stale” Github Action and removed status: inactive Marked by the “Stale” Github Action status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. labels May 16, 2024
@scottshambaugh
Copy link
Contributor

I believe this can be done with the colors kwarg that is passed to Line3DCollection? But the link to the matlab waterfall plot is broken so I'm not completely sure what is being asked for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
keep Items to be ignored by the “Stale” Github Action New feature topic: mplot3d
Projects
None yet
Development

No branches or pull requests

7 participants