diff --git a/docs/_gen_cmaps.py b/docs/_gen_cmaps.py index bcac41356..0ad0547bd 100644 --- a/docs/_gen_cmaps.py +++ b/docs/_gen_cmaps.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from cmap import _catalog from cmap import Colormap -from cmap._util import report +from cmap._util import report_cvds # TODO: convert to jinja TEMPLATE = """# {name} @@ -32,6 +32,29 @@ {{{{ cmap_gray: {name} 30 }}}} {{{{ cmap_sineramp: {name} }}}} +
+ ## Perceptual Lightness @@ -53,6 +76,19 @@ @@ -111,11 +147,13 @@ def build_catalog(catalog: "_catalog.Catalog") -> None: # write data used for charts cm = Colormap(name) cmap_data = { - k: np.around(v, 4).tolist() if isinstance(v, np.ndarray) else v - for k, v in report(cm).items() - if k in INCLUDE_DATA + cvd_type: { + k: np.around(v, 4).tolist() if isinstance(v, np.ndarray) else v + for k, v in report.items() + if k in INCLUDE_DATA + } + for cvd_type, report in report_cvds(cm).items() } - _aliases = [x for x in info.aliases if x != info.name] aliases = _make_aliases_md(_aliases) if _aliases else "" diff --git a/docs/javascripts/cmap_charts.js b/docs/javascripts/cmap_charts.js index 3643abed3..b16c6b9d1 100644 --- a/docs/javascripts/cmap_charts.js +++ b/docs/javascripts/cmap_charts.js @@ -22,14 +22,20 @@ async function initCharts() { chartElems[cmap_name] = [charts[i]]; } } + var cvd_type = document.getElementById("cvd").querySelector('input[name="cvd_button"]:checked')?.value; // Make all charts for each cmap name for (var cmap_name in chartElems) { // NOTE: we're using a global window variable here that will be // injected into the _gen_cmaps page... because it's much faster // on readthedocs than making an additional fetch request - var cmap_data = window.cmap_data[cmap_name]; + var cmap_data = window.cmap_data[cmap_name][cvd_type] || window.cmap_data[cmap_name]["normal"]; for (var i = 0; i < chartElems[cmap_name].length; i++) { var canv = chartElems[cmap_name][i]; + const chart = Chart.getChart(canv); + if (chart) { + chart.destroy(); + } + if (canv.classList.contains("rgb-chart")) { makeRGBChart(canv, cmap_data); } else if (canv.classList.contains("hsl-chart")) { diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index bf266d050..14a061686 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -176,3 +176,49 @@ body[data-md-color-scheme="slate"] .btn.active { #readthedocs-ea, .ethical-alabaster { display: none; } + +.radio-group { + display: flex; + gap: 1rem; + margin-bottom: 1rem; +} + +.radio-option { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5em; + border-radius: 0.5em; + cursor: pointer; + transition: background-color 0.2s, box-shadow 0.2s; +} + +.radio-option input[type="radio"] { + appearance: none; + position: absolute; + opacity: 0; + pointer-events: none; +} + +.radio-option .material-icons { + font-size: 1rem; + color: var(--md-default-fg-color); + transition: color 0.2s; +} + +.radio-option input[type="radio"]:checked ~ .material-icons, +.radio-option input[type="radio"]:checked ~ .radio-label { + color: var(--md-accent-fg-color); +} + + +.radio-option:hover { + background-color: var(--md-default-bg-color--lighter); + box-shadow: 0 0 0 2px var(--md-accent-fg-color); +} + +.radio-option .radio-label { + font-size: 0.65rem; + color: var(--md-default-fg-color); +} diff --git a/mkdocs.yml b/mkdocs.yml index 045ef4bd6..967c1f94d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -117,6 +117,7 @@ plugins: summary: true extra_css: + - https://fonts.googleapis.com/icon?family=Material+Icons - stylesheets/extra.css extra_javascript: diff --git a/src/cmap/_util.py b/src/cmap/_util.py index 26b183ad0..c1d0279a5 100644 --- a/src/cmap/_util.py +++ b/src/cmap/_util.py @@ -446,13 +446,27 @@ class ReportDict(TypedDict): lightness_derivs: np.ndarray -def report(cm: Colormap, n: int = 256, uniform_space: str = "CAM02-UCS") -> ReportDict: +class CVDReportDict(TypedDict): + normal: ReportDict + protan: ReportDict + deutan: ReportDict + tritan: ReportDict + + +def report( + cm: Colormap, + n: int = 256, + uniform_space: str = "CAM02-UCS", + initial_space: dict | None = None, +) -> ReportDict: """Generate a report of data describing a colormap. This is primarily used for generating charts in the documentation """ from colorspacious import cspace_convert + if initial_space is None: + initial_space = {"name": "sRGB1"} if len(cm.color_stops) >= 100: RGBA = np.asarray(cm.color_stops.color_array) n = RGBA.shape[0] @@ -462,6 +476,7 @@ def report(cm: Colormap, n: int = 256, uniform_space: str = "CAM02-UCS") -> Repo RGBA = cm(x) RGB = RGBA[:, :3] + RGB = np.clip(cast("np.ndarray", cspace_convert(RGB, initial_space, "sRGB1")), 0, 1) Jab = cast("np.ndarray", cspace_convert(RGB, "sRGB1", uniform_space)) local_deltas = np.sqrt(np.sum((Jab[:-1, :] - Jab[1:, :]) ** 2, axis=-1)) @@ -504,6 +519,48 @@ def report(cm: Colormap, n: int = 256, uniform_space: str = "CAM02-UCS") -> Repo # H -> hue composition +def report_cvds( + cm: Colormap, n: int = 256, uniform_space: str = "CAM02-UCS" +) -> CVDReportDict: + """Generate a report of data describing a colormap for different CVD types. + + This is primarily used for generating charts in the documentation + """ + return { + "normal": report(cm, n=n, uniform_space=uniform_space), + "protan": report( + cm, + n=n, + uniform_space=uniform_space, + initial_space={ + "name": "sRGB1+CVD", + "cvd_type": "protanomaly", + "severity": 100, + }, + ), + "deutan": report( + cm, + n=n, + uniform_space=uniform_space, + initial_space={ + "name": "sRGB1+CVD", + "cvd_type": "deuteranomaly", + "severity": 100, + }, + ), + "tritan": report( + cm, + n=n, + uniform_space=uniform_space, + initial_space={ + "name": "sRGB1+CVD", + "cvd_type": "tritanomaly", + "severity": 100, + }, + ), + } + + def to_mpl( value: ColorStopsLike, /,