Skip to content

Custom limits to MappedColormaps - #246

Open
vbrennsteiner wants to merge 2 commits into
mainfrom
mapped_colormaps_transform
Open

Custom limits to MappedColormaps#246
vbrennsteiner wants to merge 2 commits into
mainfrom
mapped_colormaps_transform

Conversation

@vbrennsteiner

@vbrennsteiner vbrennsteiner commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Problem addressed by this PR:

A common task of depicting numerical data is mapping values to colors along a consecutive gradient using a colorbar legend. This presents a key challenge: both the scatterplot and the colorbar need to "know" which data value should be mapped to which color. This is commonly solved with a matplotlib.ScalarMappable, which can map a numerical array to a range of colors.

Our MappedColormaps can produce ScalarMappable instances with the added benefit of adding a percentile cutoff to the upper and lower ends, providing a solution to outliers dominating the scale and compressing the color mapping of other points (see basic plotting tutorial notebook).

However, until now, MappedColormaps had a key problem that made it insufficient for producing reusable color mappings: it always mapped the colormap to between the lowest and highest point of the actual data, with no way of setting custom limits. This makes it awkward to produce colorbars, since they start and end at serendipitous values, and it was impossible to fit a colormap to between suitable bounds and easily apply that mapping to data values to be used e.g. in the color_column argument of scatter plots, since any call to fit_transform would automatically reset the mapping.

Fix:

This PR fixes those limitations by 1.) adding a vmin and vmax argument to MappedColormaps.fit_transform to solve the issue with non-ideal ranges, and 2.) adding a MappedColormaps.transform() method that only applies an existing scale to data, without re-fitting to the data's scale.

Notes:

Existing functionality of MappedColormaps stays untouched, and unit tests cover the new functionality; additionally, a .fit method has been added to fit data + custom vmin/vmax values, completing the sklearn-like 'fit/transform/fit_transform' interface.

…stom limits instead of fixed, data-derived limits; add fit/transform pattern to MappedColormaps: leaves current functionality as is but allows for transforming a datarange to colors WITHOUT refitting the color scale to the data range - e.g. we now can fit the colorscale on the 0-1 range and fit arrays ranging from e.g. 0.3-0-95 as well as 0.01 - 0.89 to exactly the same values
@vbrennsteiner vbrennsteiner self-assigned this Jul 4, 2026
@vbrennsteiner vbrennsteiner changed the title Mapped colormaps transform Custom limits to MappedColormaps Jul 5, 2026
of RGBA tuples.
vmin : float, optional
Minimum value for normalization when values is an array. If None, uses the minimum of the array.
vmax : float, optional

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read "maximum velocity here" :-D
min/max_value?


vmin, vmax = np.nanmin(values), np.nanmax(values)
if vmin is None:
vmin = np.nanmin(values)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit)
vmin = vmin if vmin is not None else np.nanmin(values)


if self.vmin is None or self.vmax is None:
raise ValueError("fit() requires `data`, or both `vmin` and `vmax`, to set the bounds.")
return self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to return something?

return colormap


class MappedColormaps:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should move all colormap-related code into a dedicated module

Comment on lines 900 to 912
if self.percentile is not None:
self.vmin = np.nanpercentile(data, self.percentile[0])
self.vmax = np.nanpercentile(data, self.percentile[1])
else:
self.vmin = np.nanmin(data)
self.vmax = np.nanmax(data)

data = np.clip(data, self.vmin, self.vmax)

rgba = _get_colors_from_cmap(self.cmap, data)

if as_hex:
return np.apply_along_axis(mpl_colors.to_hex, -1, rgba, keep_alpha=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't fit_transform() just call fit() and transform() now?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants