Skip to content

Conversation

@cvanelteren
Copy link
Collaborator

@cvanelteren cvanelteren commented Jan 16, 2026

Add layered Sankey diagram support with flow-aware label placement, expose the layered Sankey API with rc defaults and tests, and include a new gallery example in docs/examples/plot_types to showcase publication-ready Sankey styling and labels.

Closes #457
image

import ultraplot as uplt

fig, ax = uplt.subplots(figsize=(5, 2))
nodes = [
    "Budget",
    "Ops",
    "R&D",
    "Marketing",
    "Infra",
    "People",
    "Product",
    "Campaigns",
    "Savings",
]
flows = [
    dict(source="Budget", target="Ops", value=5, label="Ops", group="ops"),
    dict(source="Budget", target="R&D", value=3, label="R&D", group="rnd"),
    dict(source="Budget", target="Marketing", value=2, label="Mkt", group="mkt"),
    dict(source="Ops", target="Infra", value=2.5, label="Infra", group="ops"),
    dict(source="Ops", target="People", value=2.0, label="People", group="ops"),
    dict(source="R&D", target="Product", value=2.2, label="Prod", group="rnd"),
    dict(source="R&D", target="Savings", value=0.8, label="Save", group="rnd"),
    dict(source="Marketing", target="Campaigns", value=1.7, label="Camps", group="mkt"),
    dict(source="Marketing", target="Savings", value=0.2, label="Save", group="mkt"),
]

ax.sankey(
    nodes=nodes,
    flows=flows,
    style="budget",
    node_label_box=True,  # default rounded white box
    node_label_kw=dict(fontsize=9, color="0.15"),
    flow_labels=True,
    value_format="{:.1f}",
    group_cycle=["#f2a65a", "#7fb069", "#3c91e6"],
    flow_other=0.25,
    other_label="Other",
    node_label_outside="auto",
)
uplt.show(block=1)

beckermr and others added 30 commits February 1, 2025 06:27
* fix: correct chain of logic for publish workflow

* Update publish-pypi.yml

* Add verbose flag to PyPI publish and update build commands
* fix: get pypi publish working

* prod: run packaging build on PRs to check that it works

* fix: try to get git config correct

* fix: get prod too

* fix: ignore race conditions

* fix: ignore race conditions
---------

Co-authored-by: Matthew R. Becker <beckermr@users.noreply.github.com>
* fix: ensure pypi readme works ok

* test: publish to test pypi once to ensure it is working

* test: turn off local versions

* test: hack it in once

* fix: remove hacks for testing
* feat: deduplicate pypi publish workflow

* see what is there

* fix: download to path

* fix: need test then prod

* fix: remove testing items

* style: remove extra newline

* style: ensure consistent caps

* prod: cancel in progress jobs for ci
updates:
- [github.com/psf/black-pre-commit-mirror: 24.8.0 → 25.1.0](psf/black-pre-commit-mirror@24.8.0...25.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* use norm explicitly

* remove debug statement

* add second test

* edge case when norm is string

* removed additional space

* make test into img compare

* updated warning message

* remove debug statement
* use seed for reproducibility

* use seed for reproducibility
- pass colormaps as dictionary to demo functions
* allows cycle to be a tuple

* allow iterable

* add colormap tests

* added high level unittest

* added low level tests for cycle and colormap

* added com. function for cycler object

* remove erroneous logic when plotting singular data and a new cycler is used

* black formatting

* remove print from test

* removed spurious input

* replace logic with numpy

* updated singular comment

* remove type hinting

* correct value error

* rm another print statement

* rm comment

* reorder cases

* styling the comments
* fix and slightly refactor

* black formatting

* added unittest

* fix typo

* add nans
Fixes an issue where autoapi and autodoc would interfere. Links to the docs were not shown properly.
* explicitly override minor locator if given

* removed print statement

* added comment

* black formatting

* added comments to unittest

* add return
* filter out property if not set

* add tests for empty

* reshuffle the tests

* add explicit cycle test

* Add Python build commands and new color cycle test

---------

Co-authored-by: Matthew R. Becker <beckermr@users.noreply.github.com>
* removes error in geoaxes

* install different package

* add font-cache refresh
* glyph not available in text gyro; setting fallback font
* only build when code changed

* fix: try a workflow that runs on no-changes

* fix: rename for clarity

* fix: clarify why things are passing

---------

Co-authored-by: beckermr <becker.mr@gmail.com>
Co-authored-by: Matthew R. Becker <beckermr@users.noreply.github.com>
* rm baseline from code

* rm baseline from code
* Only include failed tests when uploading

* test: try a failure live

* Update ultraplot/tests/test_1dplots.py

---------

Co-authored-by: Matthew R. Becker <beckermr@users.noreply.github.com>
* prod: add me to maintainers

* prod: mambaforge is deprecated

* prod: they do not support miniforge
cvanelteren and others added 22 commits January 9, 2026 08:32
…onkey patches (#454)

* Refactor geo gridliner adapters

* Add adapter-focused geographic tests

* Refine geo gridliner helpers and constants

* Add gridliner adapter tests

* Refactor cartopy gridliner overrides

* Tweak gridliner subclass wording

* Refactor GeoAxes.format into helpers

* Document GeoAxes.format flow

* Restore gridliner toggle call for empty labels
* replace images with hashes

* hashes need baselines

* use github cache

* add pytest-xdist

* dummy commit

* add xdist to tests

* rm threading on test

* Fix mpl baseline path in CI

* add xdist

* Fix hash library regen and add pytest -x

* Version hash library by Python and Matplotlib
* Fix 'What's New' page formatting and generation

Improves the RST conversion logic in 'fetch_releases.py' to correctly handle Markdown headers, code blocks, images, and HTML details tags. Also updates 'conf.py' to use 'sys.executable' for robust script execution.

* Switch to m2r2 for Markdown to RST conversion

Replaces custom manual parsing with m2r2 library for more robust Markdown to ReStructuredText conversion in 'fetch_releases.py'. Adds 'm2r2' to 'environment.yml' dependencies.

* Add lxml-html-clean dependency

Fixes ImportError: lxml.html.clean module is now a separate project. This is required by nbsphinx.
…ment (#461)

* Fix SubplotGrid indexing and allow legend placement decoupling

* Add ref argument to fig.legend, support 1D slicing, and intelligent placement inference

* Add ref argument to fig.legend and fig.colorbar, support 1D slicing, intelligent placement, and robust checks

* Remove xdist from image compare
* Add ridgeline plot feature with histogram support

Implements ridgeline plots (also known as joyplots) for visualizing
distributions of multiple datasets as stacked, overlapping density curves.

Features:
- Support for both vertical (traditional) and horizontal orientations
- Kernel density estimation (KDE) for smooth curves
- Histogram mode for binned bar charts (hist=True)
- Customizable overlap between ridges
- Color specification via colormap or custom colors
- Integration with UltraPlot's color cycle
- Transparent error handling for invalid distributions
- Follows UltraPlot's docstring snippet manager pattern

Methods added:
- ridgeline(): Create vertical ridgeline plots
- ridgelineh(): Create horizontal ridgeline plots
- _apply_ridgeline(): Internal implementation

Tests added:
- test_ridgeline_basic: Basic KDE functionality
- test_ridgeline_colormap: Colormap support
- test_ridgeline_horizontal: Horizontal orientation
- test_ridgeline_custom_colors: Custom color specification
- test_ridgeline_histogram: Histogram mode
- test_ridgeline_histogram_colormap: Histogram with colormap
- test_ridgeline_comparison_kde_vs_hist: KDE vs histogram comparison
- test_ridgeline_empty_data: Error handling for empty data
- test_ridgeline_label_mismatch: Error handling for label mismatch

Docstrings registered with snippet manager following UltraPlot conventions.

* Fix ridgeline plot outline to exclude baseline

The ridge outlines now only trace the top curve of each distribution,
not the baseline. This is achieved by:
- Using fill_between/fill_betweenx with edgecolor='none'
- Drawing a separate plot() line on top for the outline
- Proper z-ordering to ensure outline appears above fill

This creates cleaner ridgeline plots where the baseline doesn't have
a visible edge line connecting the endpoints.

* Improve z-ordering for ridgeline plots

Implements explicit z-ordering to ensure proper layering:
- Each ridge i gets: fill at base+i*2, outline at base+i*2+1
- Later ridges appear on top of earlier ridges
- Outline always appears on top of its corresponding fill
- Base zorder defaults to 2 (above grid/axes elements)
- User can override base zorder via zorder parameter

This ensures clean visual layering even with high overlap values
and when other plot elements are present (e.g., grids).

* Fix z-ordering: lower ridges now correctly appear in front

Reversed the z-order assignment so that visually lower ridges
(smaller index, closer to viewer) have higher z-order values.

Z-order formula: fill_zorder = base + (n_ridges - i - 1) * 2

This ensures proper visual layering where:
- Ridge 0 (bottom, front) has highest z-order
- Ridge n-1 (top, back) has lowest z-order

This prevents ridges from incorrectly popping in front of others
when overlap is high, maintaining the correct visual depth.

* Add kde_kw parameter for flexible KDE control

Replaced explicit bandwidth/weights parameters with a more flexible
kde_kw dictionary that passes all kwargs to scipy.stats.gaussian_kde.

Features:
- kde_kw: dict parameter for passing any KDE arguments (bw_method, weights, etc.)
- points: int parameter to control number of evaluation points (default 200)
- More maintainable and extensible than exposing individual parameters
- Follows UltraPlot's convention of using *_kw parameters

Example usage:
- Custom bandwidth: kde_kw={'bw_method': 0.5}
- With weights: kde_kw={'weights': weight_array}
- Silverman method: kde_kw={'bw_method': 'silverman'}
- Smoother curves: points=500

Tests added:
- test_ridgeline_kde_kw: Tests various kde_kw configurations
- test_ridgeline_points: Tests points parameter

* Add continuous coordinate-based positioning for scientific ridgeline plots

Implements two distinct positioning modes for ridgeline plots:

1. Categorical Positioning (default): Evenly-spaced ridges with discrete labels
   - Uses overlap parameter to control spacing
   - Traditional 'joyplot' aesthetic

2. Continuous Positioning: Ridges anchored to specific Y-coordinates
   - Enabled by providing 'positions' parameter
   - 'height' parameter controls ridge height in Y-axis units
   - Essential for scientific plots where Y-axis represents physical variables
   - Supports: time series, depth profiles, redshift distributions, etc.

Parameters:
- positions: Array of Y-coordinates for each ridge
- height: Ridge height in Y-axis units (auto-determined if not provided)

Scientific use cases:
- Ocean temperature profiles vs depth
- Galaxy distributions vs redshift
- Climate data over time
- Atmospheric profiles vs altitude
- Any data where the vertical axis has physical meaning

Tests added:
- test_ridgeline_continuous_positioning: Visual test of continuous mode
- test_ridgeline_continuous_vs_categorical: Side-by-side comparison
- test_ridgeline_continuous_errors: Error handling validation
- test_ridgeline_continuous_auto_height: Auto height calculation

* Add user guide documentation for ridgeline plots and fix deprecated API

- Add comprehensive ridgeline plot examples to docs/stats.py
- Include examples for KDE vs histogram modes
- Demonstrate categorical vs continuous positioning for scientific use cases
- Replace deprecated mcm.get_cmap() with constructor.Colormap()
- All 15 ridgeline tests still passing
* Fix formatting of :rc: role in axes/base.py docstrings

Removes the leading hyphen from the :rc: entry which was causing incorrect rendering.
Also includes other docstring fixes and updates present in the working directory.

* Fix wrong formatting in docs

* Make use of backtick  consistent
* Fix legend span inference with panels

Legend span inference used panel-inflated indices after prior legends added panel rows/cols, yielding invalid gridspec indices for list refs.

Decode subplot indices to non-panel grid before computing span and add regression tests for multi-legend ordering.

* Restore tests

* Document legend span decode fallback

Add a brief note that decoding panel indices can fail for panel or nested subplot specs, so we fall back to raw indices.

* Add legend span/selection regression tests

Cover best-axis selection for left/right/top/bottom and the decode-index fallback path to raise coverage around Figure.legend panel inference.

* Extend legend coverage for edge ref handling

Add tests that cover span inference with invalid ref entries, best-axis fallback on inset locations, and the empty-iterable ref fallback path.
* Lazy-load top-level imports

* Use warnings module in internals

* Add eager import option and tests

* Cover eager setup and benchmark imports

* Add tests for lazy import coverage

* update docs

* refactor: Automate lazy loading and fix build error

Refactored the lazy loading mechanism in ultraplot/__init__.py to be automated and convention-based. This simplifies the process of adding new modules and makes the system more maintainable.

Fixed a documentation build error caused by the previous lazy loading implementation.

Added documentation for the new lazy loading system in docs/lazy_loading.rst.

* update instructions

* fix issues

* attempt fix

* attempt fix

* Fix lazy import clobbering figure

* Add regression test for figure lazy import

* fixed

* Refactor lazy loader into helper module

* bump

* bump

* resolve namespace collision

* resolve namespace collision

* mv docs

* Update lazy-loading contributor docs
Co-authored-by: Casper van Elteren <caspervanelteren@gmail.com>
@cvanelteren cvanelteren changed the title Sankey labels Feature: Sankey diagrams Jan 16, 2026
@cvanelteren cvanelteren marked this pull request as draft January 16, 2026 20:57
@cvanelteren
Copy link
Collaborator Author

Closing in favor of PR from sankey-labels-clean (clean history).

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.

Feature request: Add Sankey Diagram

7 participants