Skip to content

ScalarMappable should copy its input and allow non-arrays #18841

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

Closed
anntzer opened this issue Oct 29, 2020 · 2 comments · Fixed by #18870
Closed

ScalarMappable should copy its input and allow non-arrays #18841

anntzer opened this issue Oct 29, 2020 · 2 comments · Fixed by #18870
Milestone

Comments

@anntzer
Copy link
Contributor

anntzer commented Oct 29, 2020

Problem

Recently I wanted to draw some colormapped vlines. As LineCollection inherits from ScalarMappable, one can indeed write e.g.

from pylab import *
xs = vals = np.arange(10)
plt.vlines(xs, 0, 1, array=vals)
plt.show()

and get
test

Unfortunately,

  1. The array kwarg only accepts ndarrays, and not e.g. nested lists: plt.vlines(np.arange(10), 0, 1, array=[*range(10)]) crashes (at draw time, which is even worse) with "AttributeError: 'list' object has no attribute 'ndim'". This is unlike most other Axes/pyplot APIs (e.g. imshow), which happily accept lists (nested/2D lists, for imshow).
  2. The array kwarg is not copied, so later changes to it get reflected back into the artist (i.e. doing vals[5] = 45 after the call to vlines still affects the line colors). Again, this is unlike other Axes/pyplot APIs (e.g. imshow), which insulate the artist from later changes to the input.

Proposed Solution

Make a copy of the input and cast it to arrays. (Well, modulo unit handling...)

Additional context and prior art

Behave like imshow.

@aitikgupta
Copy link
Contributor

I'd love to take this up! :)
Initial scanning leads to the following points:

  • In case of imshow, _ImageBase class inherits from ScalarMappable, but overrides its set_array function as:
    def set_array(self, A):
    """
    Retained for backwards compatibility - use set_data instead.
    Parameters
    ----------
    A : array-like
    """
    # This also needs to be here to override the inherited
    # cm.ScalarMappable.set_array method so it is not invoked by mistake.
    self.set_data(A)
  • imshow doesn't raise this error as its set_data (called from set_array) copies the original array and checks for numpy casts:
    self._A = cbook.safe_masked_invalid(A, copy=True)
    if (self._A.dtype != np.uint8 and
    not np.can_cast(self._A.dtype, float, "same_kind")):
    raise TypeError("Image data of dtype {} cannot be converted to "
    "float".format(self._A.dtype))
  • In case of vlines, ScalarMappable is inherited again, but there's no overriding its set_array function, which evidently just accepts ndarray:
    def set_array(self, A):
    """
    Set the image array from numpy array *A*.
    Parameters
    ----------
    A : ndarray
    """
    self._A = A
    self._update_dict['array'] = True

@aitikgupta
Copy link
Contributor

To solve this, I added set_array to Collection class, overriding it again. It will need some reviews, I'll issue a PR for the same.

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

Successfully merging a pull request may close this issue.

3 participants