Skip to content

Commit

Permalink
Merge pull request #228 from knaaptime/colors
Browse files Browse the repository at this point in the history
reimplement rgbas
  • Loading branch information
knaaptime authored Jul 25, 2024
2 parents 0a56da1 + 3f53485 commit 666accc
Show file tree
Hide file tree
Showing 4 changed files with 43,135 additions and 35 deletions.
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ Utilities
mapclassify.Pooled
mapclassify.classify
mapclassify.gadf
mapclassify.util.get_color_array
18 changes: 10 additions & 8 deletions mapclassify/tests/test_rgba.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import geopandas
import numpy as np
from mapclassify.util import get_rgba
from numpy.testing import assert_array_equal
from mapclassify.util import get_color_array

world = geopandas.read_file(
"https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip"
)


def test_rgba():
colors = get_rgba(world.area, cmap="viridis")[0]
assert colors == [
np.float64(68.08602),
np.float64(1.24287),
np.float64(84.000825),
np.float64(255.0),
]
colors = get_color_array(world.area, cmap="viridis")[0]
assert_array_equal(colors, np.array([68, 1, 84, 255]))


def test_rgba():
colors = get_color_array(world.area, cmap="viridis", as_hex=True)[0]
assert_array_equal(colors,'#440154')

62 changes: 35 additions & 27 deletions mapclassify/util.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
import numpy as np

from ._classify_API import classify as _classify


def get_rgba(
def get_color_array(
values,
classifier="quantiles",
scheme="quantiles",
cmap="viridis",
alpha=1,
nan_color=[255, 255, 255, 255],
as_hex=False,
**kwargs,
):
"""Convert array of values into RGBA colors using a colormap and classifier.
"""Convert array of values into RGBA or hex colors using a colormap and classifier.
This function is useful for visualization libraries that require users to provide
an array of colors for each object (like pydeck or lonboard) but can also be used
to create a manual column of colors passed to matplotlib.
Parameters
----------
values : list-like
array of input values
classifier : str, optional
string description of a mapclassify classifier, by default "quantiles"
scheme : str, optional
string description of a mapclassify classifier, by default `"quantiles"`
cmap : str, optional
name of matplotlib colormap to use, by default "viridis"
alpha : float
alpha parameter that defines transparency. Should be in the range [0,1]
nan_color : list, optional
RGBA color to fill NaN values, by default [255, 255, 255, 255]
as_hex: bool, optional
if True, return a (n,1)-dimensional array of hexcolors instead of a (n,4)
dimensional array of RGBA values.
kwargs : dict
additional keyword arguments are passed to `mapclassify.classify`
Returns
-------
numpy.array
array of lists with each list containing four values that define a color using
RGBA specification.
numpy array (aligned with the input array) defining a color for each row. If
`as_hex` is False, the array is :math:`(n,4)` holding an array of RGBA values in
each row. If `as_hex` is True, the array is :math:`(n,1)` holding a hexcolor in
each row.
"""
try:
import pandas as pd
from matplotlib import cm
from matplotlib.colors import Normalize
from matplotlib import colormaps
from matplotlib.colors import Normalize, to_hex
except ImportError as e:
raise ImportError("This function requires pandas and matplotlib") from e
if not (alpha <= 1) and (alpha >= 0):
Expand All @@ -46,28 +58,24 @@ def get_rgba(
# only operate on non-NaN values
v = pd.Series(values, dtype=object)
legit_indices = v[~v.isna()].index.values

legit_vals = v.dropna().values
# transform (non-NaN) values into class bins
bins = _classify(v.dropna().values, scheme=classifier, **kwargs).yb
bins = _classify(legit_vals, scheme=scheme, **kwargs).yb

# create a normalizer using the data's range (not strictly 1-k...)
# normalize using the data's range (not strictly 1-k if classifier is degenerate)
norm = Normalize(min(bins), max(bins))
normalized_vals = norm(bins)

# map values to colors
n_cmap = cm.ScalarMappable(norm=norm, cmap=cmap)

# create array of RGB values (lists of 4) of length n
vals = [n_cmap.to_rgba(i, alpha=alpha) for i in bins]

# convert decimals to whole numbers
rgbas = []
for val in vals:
# convert each value in the array of lists
rgbas.append([i * 255 for i in val])
# generate RBGA array and convert to series
rgbas = colormaps[cmap](normalized_vals, bytes=True, alpha=alpha)
colors = pd.Series(list(rgbas), index=legit_indices).apply(np.array)

# replace non-nan values with colors
colors = pd.Series(rgbas, index=legit_indices)
# put colors in their correct places and fill empty with designated color
v.update(colors)
v = v.fillna(f"{nan_color}").apply(list)
v = v.fillna(f"{nan_color}").apply(np.array)

return v.values
# convert to hexcolors if preferred
if as_hex:
colors = colors.apply(lambda x: to_hex(x / 255.0))
return colors.values
return np.stack(v.values)
43,089 changes: 43,089 additions & 0 deletions notebooks/08_manual_coloring.ipynb

Large diffs are not rendered by default.

0 comments on commit 666accc

Please sign in to comment.