Skip to content

[Bug] set_3d_properties type error in Matplotlib 3.5.1 #22308

Closed

Description

@jiashuhan

Bug summary

I had a code that uses animation.FuncAnimation to generate an animation, where I update a mpl_toolkits.mplot3d.art3d.Line3D object using set_data(a, b) and set_3d_properties(c) where a, b, and c are of type numpy.float64. This code works perfectly in Matplotlib 3.4.3. However, after I upgraded to Matplotlib 3.5.1, it fails with the following error

  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/mpl_toolkits/mplot3d/art3d.py", line 175, in set_3d_properties
    zs = np.broadcast_to(zs, len(xs))
TypeError: object of type 'numpy.float64' has no len()

Source

I checked the source code of mpl_toolkits/mplot3d/art3d.py and found that in Matplotlib 3.4.3, line 175 (the line where the error occurred) was zs = np.broadcast_to(zs, xs.shape) instead, and this avoids the error. I was able to make my code run again in Matplotlib 3.5.1 by changing my code to set_data([a], [b]) and set_3d_properties([c]), but as expected, this would not run in 3.4.3. I am using Python 3.10.

Was this change in line 175 of mpl_toolkits/mplot3d/art3d.py intended?

Activity

  1. QuLogic commented on Jan 25, 2022

    @QuLogic
    Member

    Can you please provide a simple self-contained example?

  2. tacaswell commented on Jan 25, 2022

    @tacaswell
    Member

    Looks like that change was from #20555 which fixed one bug but introduced another.

  3. jiashuhan commented on Jan 25, 2022

    @jiashuhan
    Author

    Can you please provide a simple self-contained example?

    Here's an example. If you use the first two lines in update, it works fine in earlier versions but throws a TypeError in Matplotlib 3.5.1 because len() doesn't support numpy.float64. This is the same issue pointed out by @christofhapp in #20555.

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    import mpl_toolkits.mplot3d.axes3d as p3
    
    def update(step, xs, ys, zs, point):
        # use the following two lines for matplotlib 3.4.3
        point.set_data(xs[step], ys[step])
        point.set_3d_properties(zs[step])
        
        # use the following two lines for matplotlib 3.5.1
        #point.set_data([xs[step]], [ys[step]])
        #point.set_3d_properties([zs[step]])
        return point
    
    fig = plt.figure()
    ax = p3.Axes3D(fig, auto_add_to_figure=False)
    fig.add_axes(ax)
    
    point = ax.plot([], [], [], '.')[0]
    
    xs = np.arange(100)
    ys = np.arange(100)
    zs = np.arange(100)
    
    ax.set_xlim3d([0,100])
    ax.set_ylim3d([0,100])
    ax.set_zlim3d([0,100])
    
    anim = animation.FuncAnimation(fig, update, 100,
                    fargs=(xs, ys, zs, point), interval=1, blit=False)
    
    plt.show()

    Expected outcome

    In v3.4.3

    bug_test.mp4

    Actual outcome

    In v3.5.1

      File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/mpl_toolkits/mplot3d/art3d.py", line 175, in set_3d_properties
        zs = np.broadcast_to(zs, len(xs))
    TypeError: object of type 'numpy.float64' has no len()
  4. added a commit that references this issue on Jan 26, 2022
  5. tacaswell commented on Jan 27, 2022

    @tacaswell
    Member
        point.set_data(xs[step:step+1], ys[step:step+1])
        point.set_3d_properties(zs[step:step+1])

    will work correctly on all version of Matplotlib.

    Previously, numpy scalars would work, but if you use built in floats

        point.set_data(float(xs[step]), float(ys[step]))
        point.set_3d_properties(float(zs[step]))

    will fail on 3.4.3.

  6. added this to the v3.5.2 milestone on Jan 27, 2022
  7. QuLogic commented on Jan 27, 2022

    @QuLogic
    Member

    The error is that xs is not a sequence, but they must be a sequence in 2D, so that should be required in 3D as well. This may have been a regression, but it is how it's supposed to work (it is required when initializing, but .set_data is not also validating.)

  8. christofhapp commented on Jan 27, 2022

    @christofhapp

    No! It absolutely makes sense to plot a scalar (single point). A scalar should be interpreted as an array with one element aswell. Because it's what it is mathematically. Don't throw a failure, make it plot a single point.

  9. tacaswell commented on Jan 27, 2022

    @tacaswell
    Member

    A scalar should be interpreted as an array with one element aswell. Because it's what it is mathematically.

    I disagree that this is cut and dry, there is a significant amount of degeneracy at the scalar<->array boundary which is difficult to model correctly in software (see the difficulties / confusion that comes up around numpy scalars). I do not think you are going to have much success changing anyone's mind with this argument.

    If your argument is "It is very convenient for me to pass scalars into these methods and have mpl do the scalar -> length 1 1D array", that is a reasonable position and one I find persuasive because we automatically up-cast on ax.plot for example.

  10. christofhapp commented on Jan 27, 2022

    @christofhapp

    @tacaswell I don't know what you mean by up-cast.
    Yes, you're right, I didn't want to say a scalar should be of type array. What i wanted to say is more:
    I want to be able to pass a scalar and the target function interprets that as an array if it is appropriate for the functionality. e.g. I want to plot points in ax.plot and my "sequence" can have any number of points including 1 and 0. I don't want to write a case distinction for that.

  11. QuLogic commented on Jan 27, 2022

    @QuLogic
    Member

    Passing a scalar to Axes.plot is fine, and it adjusts single values into a sequence. But a Line2D is an object that represents a line, and it requires sequences. This is enforced on creation, and the lack of enforcement on set_data is a bug.

  12. jiashuhan commented on Jan 27, 2022

    @jiashuhan
    Author

    Passing a scalar to Axes.plot is fine, and it adjusts single values into a sequence. But a Line2D is an object that represents a line, and it requires sequences. This is enforced on creation, and the lack of enforcement on set_data is a bug.

    I agree with this assessment. I initially reported this because I want to know if the issue was due to a lack of enforcement for sequences or a lack of support for scalars. Clearly Line2D and Line3D objects are intended for sequences.

  13. christofhapp commented on Jan 27, 2022

    @christofhapp

    But if i make a point plot with more than 1 point with ax.plot it becomes a Line2D automatically and if i want to update that plot with a point vector of length 1 later, it's not possible, because it's a Line2D.
    And it's not consistent with the official matplotlib description on matplotlib.org: "Plot y versus x as lines and/or markers."
    the resulting object should be able to contain and update markers of length 1.

  14. 8 remaining items

  15. jklymak commented on Jun 20, 2022

    @jklymak
    Member

    This works as documented - if someone wants to pass a singleton, they need only wrap in a list or array. Our casting to 1-D objects is very convoluted, and I'd be very 👎 on moving it into Line2D.set_data.

  16. anntzer commented on Jun 20, 2022

    @anntzer
    Contributor

    Independently of whether we actually want to support that, I think the "correct" (i.e., not triggering this issue) way to spell zs = np.broadcast_to(zs, len(xs)) (and the similar other uses of broadcast_to in the same module) would have been _, zs = np.broadcast_arrays(xs, zs)?

  17. tacaswell commented on Jun 20, 2022

    @tacaswell
    Member

    As you bring in the restriction of having to contain at least 2 points because we decide for being a Line2D shown as a Marker Plot, ...

    This is incorrect, everyone agrees that [1,] should work (and it does), the question is should 1 work (and lets not bring numpy scalar into this ;) ).

  18. modified the milestones: v3.5.3, v3.5.4 on Jul 21, 2022
  19. modified the milestones: v3.5.4, v3.6.1 on Sep 25, 2022
  20. modified the milestones: v3.6.1, v3.6.2 on Oct 6, 2022
  21. modified the milestones: v3.6.2, v3.6.3 on Oct 27, 2022
  22. modified the milestones: v3.6.3, v3.7.0 on Dec 23, 2022
  23. Telemaharry commented on Jun 22, 2023

    @Telemaharry

    Was this issue fixed? I am having the same issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions