diff --git a/src/plopp/backends/matplotlib/image.py b/src/plopp/backends/matplotlib/image.py index 5b61eef3..31d2a9d2 100644 --- a/src/plopp/backends/matplotlib/image.py +++ b/src/plopp/backends/matplotlib/image.py @@ -9,6 +9,7 @@ from ...core.utils import coord_as_bin_edges, merge_masks, repeat, scalar_to_string from ...graphics.bbox import BoundingBox, axis_bounds +from ...graphics.colormapper import ColorMapper from ..common import check_ndim from .canvas import Canvas @@ -63,9 +64,13 @@ class Image: Parameters ---------- canvas: - The canvas that will display the line. + The canvas that will display the image. + colormapper: + The colormapper to use for the image. data: - The initial data to create the line from. + The initial data to create the image from. + uid: + The unique identifier of the artist. If None, a random UUID is generated. shading: The shading to use for the ``pcolormesh``. rasterized: @@ -77,16 +82,19 @@ class Image: def __init__( self, canvas: Canvas, + colormapper: ColorMapper, data: sc.DataArray, + uid: str | None = None, shading: str = 'auto', rasterized: bool = True, **kwargs, ): check_ndim(data, ndim=2, origin='Image') + self.uid = uid if uid is not None else uuid.uuid4().hex self._canvas = canvas + self._colormapper = colormapper self._ax = self._canvas.ax self._data = data - self._id = uuid.uuid4().hex # Because all keyword arguments from the figure are forwarded to both the canvas # and the line, we need to remove the arguments that belong to the canvas. kwargs.pop('ax', None) @@ -129,7 +137,10 @@ def __init__( rasterized=rasterized, **kwargs, ) + + self._colormapper.add_artist(self.uid, self) self._mesh.set_array(None) + self._update_colors() for xy, var in string_labels.items(): getattr(self._ax, f'set_{xy}ticks')(np.arange(float(var.shape[0]))) @@ -163,17 +174,24 @@ def data(self): ) return out - def set_colors(self, rgba: np.ndarray): + def notify_artist(self, message: str) -> None: """ - Set the mesh's rgba colors: + Receive notification from the colormapper that its state has changed. + We thus need to update the colors of the mesh. Parameters ---------- - rgba: - The array of rgba colors. + message: + The message from the colormapper. + """ + self._update_colors() + + def _update_colors(self): + """ + Update the mesh colors. """ + rgba = self._colormapper.rgba(self.data) self._mesh.set_facecolors(rgba.reshape(np.prod(rgba.shape[:-1]), 4)) - self._canvas.draw() def update(self, new_values: sc.DataArray): """ @@ -187,6 +205,7 @@ def update(self, new_values: sc.DataArray): check_ndim(new_values, ndim=2, origin='Image') self._data = new_values self._data_with_bin_edges.data = new_values.data + self._update_colors() def format_coord( self, xslice: tuple[str, sc.Variable], yslice: tuple[str, sc.Variable] @@ -223,3 +242,10 @@ def bbox(self, xscale: Literal['linear', 'log'], yscale: Literal['linear', 'log' **{**axis_bounds(('xmin', 'xmax'), image_x, xscale)}, **{**axis_bounds(('ymin', 'ymax'), image_y, yscale)}, ) + + def remove(self): + """ + Remove the image artist from the canvas. + """ + self._mesh.remove() + self._colormapper.remove_artist(self.uid) diff --git a/src/plopp/backends/matplotlib/line.py b/src/plopp/backends/matplotlib/line.py index 298964e0..e8b45810 100644 --- a/src/plopp/backends/matplotlib/line.py +++ b/src/plopp/backends/matplotlib/line.py @@ -32,21 +32,29 @@ class Line: The canvas that will display the line. data: The initial data to create the line from. + uid: + The unique identifier of the artist. If None, a random UUID is generated. artist_number: The canvas keeps track of how many lines have been added to it. This number is used to set the color and marker parameters of the line. + errorbars: + Whether to add error bars to the line. + mask_color: + The color of the masked points. """ def __init__( self, canvas: Canvas, data: sc.DataArray, + uid: str | None = None, artist_number: int = 0, errorbars: bool = True, mask_color: str = 'black', **kwargs, ): check_ndim(data, ndim=1, origin='Line') + self.uid = uid if uid is not None else uuid.uuid4().hex self._canvas = canvas self._ax = self._canvas.ax self._data = data @@ -64,7 +72,6 @@ def __init__( self._dim = self._data.dim self._unit = self._data.unit self._coord = self._data.coords[self._dim] - self._id = uuid.uuid4().hex aliases = {'ls': 'linestyle', 'lw': 'linewidth', 'c': 'color'} for key, alias in aliases.items(): diff --git a/src/plopp/backends/matplotlib/scatter.py b/src/plopp/backends/matplotlib/scatter.py index 047cca90..75e36634 100644 --- a/src/plopp/backends/matplotlib/scatter.py +++ b/src/plopp/backends/matplotlib/scatter.py @@ -10,13 +10,41 @@ from ...core.utils import merge_masks from ...graphics.bbox import BoundingBox, axis_bounds +from ...graphics.colormapper import ColorMapper from ..common import check_ndim from .canvas import Canvas from .utils import parse_dicts_in_kwargs class Scatter: - """ """ + """ + Artist to represent a two-dimensional scatter plot. + + Parameters + ---------- + canvas: + The canvas that will display the scatter plot. + data: + The initial data to create the line from. + x: + The name of the coordinate that is to be used for the X positions. + y: + The name of the coordinate that is to be used for the Y positions. + uid: + The unique identifier of the artist. If None, a random UUID is generated. + size: + The size of the markers. + color: + The color of the markers (this is ignored if a colorbar is used). + artist_number: + Number of the artist (can be used to set the color of the artist). + colormapper: + The colormapper to use for the scatter plot. + mask_color: + The color of the masked points. + cbar: + Whether to use a colorbar. + """ def __init__( self, @@ -24,19 +52,23 @@ def __init__( data: sc.DataArray, x: str = 'x', y: str = 'y', + uid: str | None = None, size: str | float | None = None, artist_number: int = 0, + colormapper: ColorMapper | None = None, mask_color: str = 'black', cbar: bool = False, **kwargs, ): check_ndim(data, ndim=1, origin='Scatter') + self.uid = uid if uid is not None else uuid.uuid4().hex self._canvas = canvas self._ax = self._canvas.ax self._data = data self._x = x self._y = y self._size = size + self._colormapper = colormapper # Because all keyword arguments from the figure are forwarded to both the canvas # and the line, we need to remove the arguments that belong to the canvas. kwargs.pop('ax', None) @@ -70,6 +102,9 @@ def __init__( label=self.label, **merged_kwargs, ) + if self._colormapper is not None: + self._colormapper.add_artist(self.uid, self) + self._scatter.set_array(None) xmask = self._data.coords[self._x].values.copy() ymask = self._data.coords[self._y].values.copy() @@ -91,6 +126,24 @@ def __init__( visible=visible_mask, ) + def notify_artist(self, message: str) -> None: + """ + Receive notification from the colormapper that its state has changed. + We thus need to update the colors of the points. + + Parameters + ---------- + message: + The message from the colormapper. + """ + self._update_colors() + + def _update_colors(self): + """ + Update the colors of the scatter points. + """ + self._scatter.set_facecolors(self._colormapper.rgba(self.data)) + def update(self, new_values: sc.DataArray): """ Update the x and y positions of the data points from new data. @@ -110,6 +163,8 @@ def update(self, new_values: sc.DataArray): ) if isinstance(self._size, str): self._scatter.set_sizes(self._data.coords[self._size].values) + if self._colormapper is not None: + self._update_colors() def remove(self): """ @@ -117,11 +172,8 @@ def remove(self): """ self._scatter.remove() self._mask.remove() - - def set_colors(self, rgba: np.ndarray): - if self._scatter.get_array() is not None: - self._scatter.set_array(None) - self._scatter.set_facecolors(rgba) + if self._colormapper is not None: + self._colormapper.remove_artist(self.uid) @property def data(self): diff --git a/src/plopp/backends/plotly/line.py b/src/plopp/backends/plotly/line.py index 64780774..093a93da 100644 --- a/src/plopp/backends/plotly/line.py +++ b/src/plopp/backends/plotly/line.py @@ -37,6 +37,8 @@ class Line: The canvas that will display the line. data: The initial data to create the line from. + uid: + The unique identifier of the artist. If None, a random UUID is generated. artist_number: The canvas keeps track of how many lines have been added to it. This number is used to set the color and marker parameters of the line. @@ -54,6 +56,7 @@ def __init__( self, canvas: Canvas, data: sc.DataArray, + uid: str | None = None, artist_number: int = 0, errorbars: bool = True, mask_color: str = 'black', @@ -62,6 +65,7 @@ def __init__( **kwargs, ): check_ndim(data, ndim=1, origin='Line') + self.uid = uid if uid is not None else uuid.uuid4().hex self._fig = canvas.fig self._data = data @@ -75,7 +79,6 @@ def __init__( self._dim = self._data.dim self._unit = self._data.unit self._coord = self._data.coords[self._dim] - self._id = uuid.uuid4().hex line_data = make_line_data(data=self._data, dim=self._dim) @@ -162,12 +165,12 @@ def __init__( self._error = self._fig.data[-1] self._fig.add_trace(self._mask) self._mask = self._fig.data[-1] - self._line._plopp_id = self._id + self._line._plopp_id = self.uid self.line_mask = sc.array(dims=['x'], values=~np.isnan(line_data['mask']['y'])) - self._mask._plopp_id = self._id + self._mask._plopp_id = self.uid if self._error is not None: - self._error._plopp_id = self._id + self._error._plopp_id = self.uid def update(self, new_values: sc.DataArray): """ @@ -209,7 +212,7 @@ def remove(self): Remove the line, masks and errorbar artists from the canvas. """ self._fig.data = [ - trace for trace in list(self._fig.data) if trace._plopp_id != self._id + trace for trace in list(self._fig.data) if trace._plopp_id != self.uid ] @property diff --git a/src/plopp/backends/pythreejs/mesh3d.py b/src/plopp/backends/pythreejs/mesh3d.py index e5214cc1..ade4c238 100644 --- a/src/plopp/backends/pythreejs/mesh3d.py +++ b/src/plopp/backends/pythreejs/mesh3d.py @@ -10,6 +10,7 @@ from ...core.limits import find_limits from ...graphics.bbox import BoundingBox +from ...graphics.colormapper import ColorMapper from .canvas import Canvas @@ -29,9 +30,13 @@ class Mesh3d: - vertices: a DataArray with the vertices of the mesh. - faces: a DataArray with the faces of the mesh. + uid: + The unique identifier of the artist. If None, a random UUID is generated. color: The color of the mesh. If None, the mesh will be colored according to the artist number. + colormapper: + The colormapper to use for the mesh. opacity: The opacity of the mesh. edgecolor: @@ -46,17 +51,20 @@ def __init__( *, canvas: Canvas, data: sc.DataArray, + uid: str | None = None, color: str | None = None, + colormapper: ColorMapper | None = None, opacity: float = 1, edgecolor: str | None = None, artist_number: int = 0, ): import pythreejs as p3 + self.uid = uid if uid is not None else uuid.uuid4().hex self._data = data self._canvas = canvas + self._colormapper = colormapper self._artist_number = artist_number - self._id = uuid.uuid4().hex # Note: index *must* be unsigned! index = p3.BufferAttribute( @@ -76,14 +84,19 @@ def __init__( ] ).T ) + + if self._colormapper is not None: + self._colormapper.add_artist(self.uid, self) + colors = self._colormapper.rgba(self.data)[..., :3].astype('float32') + else: + colors = np.broadcast_to( + np.array(to_rgb(f'C{artist_number}' if color is None else color)), + (self._data.coords["x"].shape[0], 3), + ).astype('float32') + attributes = { 'position': p3.BufferAttribute(array=pos), - 'color': p3.BufferAttribute( - array=np.broadcast_to( - np.array(to_rgb(f'C{artist_number}' if color is None else color)), - (self._data.coords["x"].shape[0], 3), - ).astype('float32') - ), + 'color': p3.BufferAttribute(array=colors), } self.geometry = p3.BufferGeometry(index=index, attributes=attributes) @@ -112,18 +125,25 @@ def __init__( if self.edges is not None: self._canvas.add(self.edges) - def set_colors(self, rgba): + def notify_artist(self, message: str) -> None: """ - Set the mesh's rgba colors: + Receive notification from the colormapper that its state has changed. + We thus need to update the colors of the points. Parameters ---------- - rgba: - The array of rgba colors. + message: + The message from the colormapper. """ - self.geometry.attributes["color"].array = rgba[..., :3].astype( - 'float32', copy=False - ) + self._update_colors() + + def _update_colors(self): + """ + Set the point cloud's rgba colors: + """ + self.geometry.attributes["color"].array = self._colormapper.rgba(self.data)[ + ..., :3 + ].astype('float32') def update(self, new_values): """ @@ -135,6 +155,8 @@ def update(self, new_values): New data to update the mesh values from. """ self._data = new_values + if self._colormapper is not None: + self._update_colors() # TODO: for now we only update the data values of the artist. # Updating the positions of the vertices is doable but is made more complicated # by the edges geometry, whose positions cannot just be updated. @@ -184,3 +206,11 @@ def data(self): Get the mesh data. """ return self._data + + def remove(self) -> None: + """ + Remove the mesh from the canvas. + """ + self._canvas.remove(self.points) + if self._colormapper is not None: + self._colormapper.remove_artist(self.uid) diff --git a/src/plopp/backends/pythreejs/scatter3d.py b/src/plopp/backends/pythreejs/scatter3d.py index eae6d6be..ca232c01 100644 --- a/src/plopp/backends/pythreejs/scatter3d.py +++ b/src/plopp/backends/pythreejs/scatter3d.py @@ -10,6 +10,7 @@ from ...core.limits import find_limits from ...graphics.bbox import BoundingBox +from ...graphics.colormapper import ColorMapper from ..common import check_ndim from .canvas import Canvas @@ -20,6 +21,8 @@ class Scatter3d: Parameters ---------- + canvas: + The canvas that will display the scatter plot. x: The name of the coordinate that is to be used for the X positions. y: @@ -28,10 +31,16 @@ class Scatter3d: The name of the coordinate that is to be used for the Z positions. data: The initial data to create the line from. + uid: + The unique identifier of the artist. If None, a random UUID is generated. size: The size of the markers. color: The color of the markers (this is ignored if a colorbar is used). + colormapper: + The colormapper to use for the scatter plot. + artist_number: + Number of the artist (can be used to set the color of the artist). opacity: The opacity of the points. pixel_size: @@ -46,24 +55,24 @@ def __init__( y: str, z: str, data: sc.DataArray, + uid: str | None = None, size: sc.Variable | float = 1, color: str | None = None, + colormapper: ColorMapper | None = None, artist_number: int = 0, opacity: float = 1, pixel_size: sc.Variable | float | None = None, ): - """ - Make a point cloud using pythreejs - """ import pythreejs as p3 check_ndim(data, ndim=1, origin='Scatter3d') + self.uid = uid if uid is not None else uuid.uuid4().hex self._canvas = canvas + self._colormapper = colormapper self._data = data self._x = x self._y = y self._z = z - self._id = uuid.uuid4().hex # TODO: remove pixel_size in the next release self._size = size if pixel_size is None else pixel_size @@ -79,6 +88,15 @@ def __init__( dtype=float, unit=self._data.coords[x].unit ).value + if self._colormapper is not None: + self._colormapper.add_artist(self.uid, self) + colors = self._colormapper.rgba(self.data)[..., :3].astype('float32') + else: + colors = np.broadcast_to( + np.array(to_rgb(f'C{artist_number}' if color is None else color)), + (self._data.coords[self._x].shape[0], 3), + ).astype('float32') + self.geometry = p3.BufferGeometry( attributes={ 'position': p3.BufferAttribute( @@ -90,14 +108,7 @@ def __init__( ] ).T ), - 'color': p3.BufferAttribute( - array=np.broadcast_to( - np.array( - to_rgb(f'C{artist_number}' if color is None else color) - ), - (self._data.coords[self._x].shape[0], 3), - ).astype('float32') - ), + 'color': p3.BufferAttribute(array=colors), } ) @@ -114,16 +125,25 @@ def __init__( self.points = p3.Points(geometry=self.geometry, material=self.material) self._canvas.add(self.points) - def set_colors(self, rgba): + def notify_artist(self, message: str) -> None: """ - Set the point cloud's rgba colors: + Receive notification from the colormapper that its state has changed. + We thus need to update the colors of the points. Parameters ---------- - rgba: - The array of rgba colors. + message: + The message from the colormapper. + """ + self._update_colors() + + def _update_colors(self): + """ + Set the point cloud's rgba colors: """ - self.geometry.attributes["color"].array = rgba[..., :3].astype('float32') + self.geometry.attributes["color"].array = self._colormapper.rgba(self.data)[ + ..., :3 + ].astype('float32') def update(self, new_values): """ @@ -136,6 +156,8 @@ def update(self, new_values): """ check_ndim(new_values, ndim=1, origin='Scatter3d') self._data = new_values + if self._colormapper is not None: + self._update_colors() @property def opacity(self): @@ -186,3 +208,5 @@ def remove(self) -> None: Remove the point cloud from the canvas. """ self._canvas.remove(self.points) + if self._colormapper is not None: + self._colormapper.remove_artist(self.uid) diff --git a/src/plopp/graphics/colormapper.py b/src/plopp/graphics/colormapper.py index 63df59ac..a953a753 100644 --- a/src/plopp/graphics/colormapper.py +++ b/src/plopp/graphics/colormapper.py @@ -98,13 +98,14 @@ def __init__( nan_color: str | None = None, figsize: tuple[float, float] | None = None, ): - self.cax = canvas.cax if hasattr(canvas, 'cax') else None + self._canvas = canvas + self.cax = self._canvas.cax if hasattr(self._canvas, 'cax') else None self.cmap = _get_cmap(cmap, nan_color=nan_color) self.mask_cmap = _get_cmap(mask_cmap, nan_color=nan_color) self.user_vmin = vmin self.user_vmax = vmax - self.vmin = np.inf - self.vmax = -np.inf + self._vmin = np.inf + self._vmax = -np.inf self.norm = norm self.set_colors_on_update = True @@ -127,11 +128,11 @@ def __init__( self.colorbar = ColorbarBase(self.cax, cmap=self.cmap, norm=self.normalizer) self.cax.yaxis.set_label_coords(-0.9, 0.5) - def __setitem__(self, key: str, artist: Any): + def add_artist(self, key: str, artist: Any): self.artists[key] = artist - def __getitem__(self, key: str) -> Any: - return self.artists[key] + def remove_artist(self, key: str): + del self.artists[key] def to_widget(self): """ @@ -177,56 +178,69 @@ def autoscale(self): vmin = reduce(min, [v[0] for v in limits]) vmax = reduce(max, [v[1] for v in limits]) if self.user_vmin is not None: - self.vmin = self.user_vmin + self._vmin = self.user_vmin else: - self.vmin = vmin.value + self._vmin = vmin.value if self.user_vmax is not None: - self.vmax = self.user_vmax + self._vmax = self.user_vmax else: - self.vmax = vmax.value + self._vmax = vmax.value - if self.vmin >= self.vmax: + if self._vmin >= self._vmax: if self.user_vmax is not None: - self.vmax = self.user_vmax - self.vmin = self.user_vmax - abs(self.user_vmax) * 0.1 + self._vmax = self.user_vmax + self._vmin = self.user_vmax - abs(self.user_vmax) * 0.1 else: - self.vmin = self.user_vmin - self.vmax = self.user_vmin + abs(self.user_vmin) * 0.1 + self._vmin = self.user_vmin + self._vmax = self.user_vmin + abs(self.user_vmin) * 0.1 + self.apply_limits() + + def apply_limits(self): # Synchronize the underlying normalizer limits to the current state. # Note that the order matters here, as for a normalizer vmin cannot be set above # the current vmax. - if self.vmin >= self.normalizer.vmax: - self.normalizer.vmax = self.vmax - self.normalizer.vmin = self.vmin + if self._vmin >= self.normalizer.vmax: + self.normalizer.vmax = self._vmax + self.normalizer.vmin = self._vmin else: - self.normalizer.vmin = self.vmin - self.normalizer.vmax = self.vmax + self.normalizer.vmin = self._vmin + self.normalizer.vmax = self._vmax - self._set_artists_colors() if self.colorbar is not None: self._update_colorbar_widget() + self.notify_artists() - def _set_artists_colors(self): + def notify_artists(self): + """ + Notify the artists that the state of the colormapper has changed. """ - Update the colors of all the artists apart from the one that triggered the - update, as those get updated by the figure. + for artist in self.artists.values(): + artist.notify_artist('colormap changed') - Parameters - ---------- - keys: - List of artists to update. + @property + def vmin(self) -> float: """ - for k in self.artists.keys(): - self.artists[k].set_colors(self.rgba(self.artists[k].data)) + Get or set the minimum value of the colorbar. + """ + return self._vmin + + @vmin.setter + def vmin(self, vmin: sc.Variable | float): + self._vmin = maybe_variable_to_number(vmin, unit=self._unit) + self.apply_limits() - def update(self): + @property + def vmax(self) -> float: """ - Update the colors of all artists, if the `self.set_colors_on_update` attribute - is set to `True`. + Get or set the maximum value of the colorbar. """ - if self.set_colors_on_update: - self._set_artists_colors() + return self._vmax + + @vmax.setter + def vmax(self, vmax: sc.Variable | float): + self._vmax = maybe_variable_to_number(vmax, unit=self._unit) + self.apply_limits() @property def unit(self) -> str | None: @@ -262,8 +276,10 @@ def toggle_norm(self): """ self.norm = "log" if self.norm == 'linear' else 'linear' self.normalizer = _get_normalizer(self.norm) - self.vmin = np.inf - self.vmax = -np.inf + self._vmin = np.inf + self._vmax = -np.inf if self.colorbar is not None: self.colorbar.mappable.norm = self.normalizer self.autoscale() + if self._canvas is not None: + self._canvas.draw() diff --git a/src/plopp/graphics/graphicalview.py b/src/plopp/graphics/graphicalview.py index af203c79..8ea921e0 100644 --- a/src/plopp/graphics/graphicalview.py +++ b/src/plopp/graphics/graphicalview.py @@ -80,8 +80,8 @@ def __init__( cax=cax, ) - self.colormapper = ( - ColorMapper( + if colormapper: + self.colormapper = ColorMapper( cmap=cmap, cbar=cbar, mask_cmap=mask_cmap, @@ -91,9 +91,9 @@ def __init__( canvas=self.canvas, figsize=getattr(self.canvas, "figsize", None), ) - if colormapper - else None - ) + self._kwargs['colormapper'] = self.colormapper + else: + self.colormapper = None if len(self._dims) == 1: self.canvas.yscale = norm @@ -194,21 +194,16 @@ def update(self, *args, **kwargs) -> None: if key not in self.artists: self.artists[key] = self._artist_maker( + uid=key, canvas=self.canvas, data=new_values, artist_number=len(self.artists), **self._kwargs, ) - if self.colormapper is not None: - self.colormapper[key] = self.artists[key] - need_legend_update = getattr(self.artists[key], "label", False) - - self.artists[key].update(new_values=new_values) - - if self.colormapper is not None: - self.colormapper.update() + else: + self.artists[key].update(new_values=new_values) if need_legend_update: self.canvas.update_legend() diff --git a/tests/graphics/colormapper_test.py b/tests/graphics/colormapper_test.py index fdc40e6a..067e8395 100644 --- a/tests/graphics/colormapper_test.py +++ b/tests/graphics/colormapper_test.py @@ -20,15 +20,20 @@ def string_similarity(a, b): class DummyChild: - def __init__(self, data): + def __init__(self, data, colormapper): self._data = data + self._colormapper = colormapper self._colors = None - def set_colors(self, colors): - self._colors = colors + def notify_artist(self, _): + self._update_colors() + + def _update_colors(self): + self._colors = self._colormapper.rgba(self._data) def update(self, data): self._data = data + self._update_colors() @property def data(self): @@ -54,8 +59,8 @@ def test_norm(): def test_autoscale(): da = data_array(ndim=2, unit='K') mapper = ColorMapper() - artist = DummyChild(da) - mapper['data'] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() assert mapper.vmin == da.min().value @@ -79,8 +84,8 @@ def test_autoscale(): def test_update_without_autoscale_does_not_change_limits(): da = data_array(ndim=2, unit='K') mapper = ColorMapper() - artist = DummyChild(da) - mapper['data'] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() assert mapper.vmin == da.min().value @@ -91,7 +96,6 @@ def test_update_without_autoscale_does_not_change_limits(): # Limits grow const = 2.3 artist.update(da * const) - mapper.update() assert mapper.vmin != (da.min() * const).value assert mapper.vmin == backup[0] assert mapper.vmax != (da.max() * const).value @@ -100,7 +104,6 @@ def test_update_without_autoscale_does_not_change_limits(): # Limits shrink const = 0.5 artist.update(da * const) - mapper.update() assert mapper.vmin != da.min().value * const assert mapper.vmin == backup[0] assert mapper.vmax != da.max().value * const @@ -110,8 +113,8 @@ def test_update_without_autoscale_does_not_change_limits(): def test_correct_normalizer_limits(): da = sc.DataArray(data=sc.array(dims=['y', 'x'], values=[[1, 2], [3, 4]])) mapper = ColorMapper() - artist = DummyChild(da) - mapper['data'] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() assert mapper.vmin == da.min().value assert mapper.vmax == da.max().value @@ -128,8 +131,8 @@ def test_vmin_vmax(): vmin = sc.scalar(-0.1, unit='K') vmax = sc.scalar(3.5, unit='K') mapper = ColorMapper(vmin=vmin, vmax=vmax) - artist = DummyChild(da) - mapper['data'] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.unit = 'K' mapper.autoscale() assert mapper.user_vmin == vmin.value @@ -143,8 +146,8 @@ def test_vmin_vmax_no_variable(): vmin = -0.1 vmax = 3.5 mapper = ColorMapper(vmin=vmin, vmax=vmax) - artist = DummyChild(da) - mapper['data'] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.unit = 'K' mapper.autoscale() assert mapper.user_vmin == vmin @@ -156,7 +159,7 @@ def test_vmin_vmax_no_variable(): def test_toggle_norm(): mapper = ColorMapper() da = data_array(ndim=2, unit='K') - mapper['child1'] = DummyChild(da) + mapper.add_artist('child1', DummyChild(data=da, colormapper=mapper)) mapper.autoscale() assert mapper.norm == 'linear' assert isinstance(mapper.normalizer, Normalize) @@ -173,8 +176,8 @@ def test_toggle_norm(): def test_update_changes_limits(): da = data_array(ndim=2, unit='K') mapper = ColorMapper() - artist = DummyChild(da) - mapper['data'] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() assert mapper.normalizer.vmin == da.min().value @@ -204,9 +207,8 @@ def test_rgba_with_masks(): def test_colorbar_updated_on_rescale(): da = data_array(ndim=2, unit='K') mapper = ColorMapper() - artist = DummyChild(da) - key = 'data' - mapper[key] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() _ = mapper.to_widget() @@ -232,9 +234,8 @@ def test_colorbar_updated_on_rescale(): def test_colorbar_does_not_update_if_no_autoscale(): da = data_array(ndim=2, unit='K') mapper = ColorMapper() - artist = DummyChild(da) - key = 'data' - mapper[key] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() _ = mapper.to_widget() @@ -243,17 +244,14 @@ def test_colorbar_does_not_update_if_no_autoscale(): # Update with the same values artist.update(da) - mapper.update() assert old_image is mapper.widget.value # Update with a smaller range artist.update(da * 0.8) - mapper.update() assert old_image is mapper.widget.value # Update with larger range artist.update(da * 2.3) - mapper.update() assert old_image_array is mapper.widget.value @@ -275,9 +273,8 @@ class Canvas: def test_autoscale_vmin_set(): da = data_array(ndim=2, unit='K') mapper = ColorMapper(vmin=-0.5) - artist = DummyChild(da) - key = 'data' - mapper[key] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() assert mapper.vmin == -0.5 assert mapper.vmax == da.max().value @@ -291,9 +288,8 @@ def test_autoscale_vmin_set(): def test_autoscale_vmax_set(): da = data_array(ndim=2, unit='K') mapper = ColorMapper(vmax=0.5) - artist = DummyChild(da) - key = 'data' - mapper[key] = artist + artist = DummyChild(data=da, colormapper=mapper) + mapper.add_artist('data', artist) mapper.autoscale() assert mapper.vmax == 0.5 assert mapper.vmin == da.min().value