From 8c6df3e431cf6212786ea5fc6f3c2bce0f5575d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Wed, 26 Nov 2025 19:30:06 +0100 Subject: [PATCH 01/12] sankey plots and their tests --- ehrapy/plot/__init__.py | 1 + ehrapy/plot/_sankey.py | 182 ++++++++++++++++++++++++++++++++++++++ tests/plot/test_sankey.py | 120 +++++++++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 ehrapy/plot/_sankey.py create mode 100644 tests/plot/test_sankey.py diff --git a/ehrapy/plot/__init__.py b/ehrapy/plot/__init__.py index aa8026a10..b09a02e7a 100644 --- a/ehrapy/plot/__init__.py +++ b/ehrapy/plot/__init__.py @@ -1,6 +1,7 @@ from ehrapy.plot._catplot import catplot from ehrapy.plot._colormaps import * # noqa: F403 from ehrapy.plot._missingno import * # noqa: F403 +from ehrapy.plot._sankey import plot_sankey, plot_sankey_time from ehrapy.plot._scanpy_pl_api import * # noqa: F403 from ehrapy.plot._survival_analysis import cox_ph_forestplot, kaplan_meier, ols from ehrapy.plot.causal_inference._dowhy import causal_effect diff --git a/ehrapy/plot/_sankey.py b/ehrapy/plot/_sankey.py new file mode 100644 index 000000000..d3567a606 --- /dev/null +++ b/ehrapy/plot/_sankey.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, List + +import ehrdata as ed +import holoviews as hv +import numpy as np +import pandas as pd +from holoviews import opts + +hv.extension("bokeh") + +if TYPE_CHECKING: + from anndata import AnnData + from ehrdata import EHRData + + +def plot_sankey( + edata: EHRData | AnnData, + *, + columns: list[str], + show: bool = False, + **kwargs, +) -> hv.Sankey: + """Create a Sankey diagram showing relationships across observation columns. + + Args: + edata : Central data object containing observation data + columns : Column names from edata.obs to visualize + show: If True, display the plot immediately. If False, only return the plot object without displaying. + **kwargs: Additional styling options passed to `holoviews.opts.Sankey`. See HoloViews Sankey documentation for full list of options. + + Returns: + holoviews.Sankey + + Examples: + >>> import ehrdata as ed + >>> edata = ed.dt.diabetes_130_fairlearn(columns_obs_only=["gender", "race"]) + >>> ep.pl.plot_sankey(edata, columns=["gender", "race"]) + + """ + df = edata.obs[columns] + + labels = [] + for col in columns: + labels.extend([f"{col}: {val}" for val in df[col].unique()]) + labels = list(dict.fromkeys(labels)) # keep order & unique + + # Build links between consecutive columns + sources, targets, values = [], [], [] + source_levels, target_levels = [], [] + for i in range(len(columns) - 1): + col_from, col_to = columns[i], columns[i + 1] + flows = df.groupby([col_from, col_to]).size().reset_index(name="count") + for _, row in flows.iterrows(): + source = f"{col_from}: {row[col_from]}" + target = f"{col_to}: {row[col_to]}" + sources.append(source) + targets.append(target) + values.append(row["count"]) + source_levels.append(col_from) + target_levels.append(col_to) + + sankey_df = pd.DataFrame( + { + "source": sources, + "target": targets, + "value": values, + "source_level": source_levels, + "target_level": target_levels, + } + ) + + sankey = hv.Sankey(sankey_df, kdims=["source", "target"], vdims=["value"]) + default_opts = {"label_position": "right", "show_values": True, "title": f"Patient flows: {columns[0]} over time"} + + default_opts.update(kwargs) + + sankey = sankey.opts(opts.Sankey(**default_opts)) + + if show: + from IPython.display import display + + display(sankey) + + return sankey + + +def plot_sankey_time( + edata: EHRData | AnnData, + *, + columns: list[str], + layer: str, + state_labels: dict[int, str] | None = None, + show: bool = False, + **kwargs, +) -> hv.Sankey: + """Create a Sankey diagram showing patient state transitions over time. + + This function visualizes how patients transition between different states + (e.g., disease severity, treatment status) across consecutive time points. + Each node represents a state at a specific time point, and flows show the + number of patients transitioning between states. + + Args: + edata: Central data object containing observation data + columns: Column names from edata.obs to visualize + layer: Name of the layer in `edata.layers` containing the feature data to visualize. + state_labels: Mapping from numeric state values to readable labels. If None, state values + will be displayed as strings of their numeric codes (e.g., "0", "1", "2"). Default: "None" + show: If True, display the plot immediately. If False, only return the plot object without displaying. + **kwargs: Additional styling options passed to `holoviews.opts.Sankey`. See HoloViews Sankey documentation for full list of options. + + Returns: + holoviews.Sankey + + Examples: + >>> import numpy as np + >>> import pandas as pd + >>> import ehrdata as ed + >>> + >>> layer = np.array( + ... [ + ... [[1, 0, 1], [0, 1, 0]], # patient 1: treatment, disease_flare + ... [[0, 1, 1], [1, 0, 0]], # patient 2: treatment, disease_flare + ... [[1, 1, 0], [0, 0, 1]], # patient 3: treatment, disease_flare + ... ] + ... ) + >>> + >>> edata = ed.EHRData( + ... layers={"layer_1": layer}, + ... obs=pd.DataFrame(index=["patient_1", "patient_2", "patient_3"]), + ... var=pd.DataFrame(index=["treatment", "disease_flare"]), + ... tem=pd.DataFrame(index=["visit_0", "visit_1", "visit_2"]), + ... ) + >>> + >>> plot_sankey_time(edata, columns=["disease_flare"], layer="layer_1", state_labels={0: "no flare", 1: "flare"}) + + + """ + flare_data = edata[:, edata.var_names.isin(columns), :].layers[layer][:, 0, :] + + time_steps = edata.tem.index.tolist() + # states = edata.var.loc[columns[0]].values + + if state_labels is None: + unique_states = np.unique(flare_data) + unique_states = unique_states[~np.isnan(unique_states)] + state_labels = {int(state): str(state) for state in unique_states} + # state_labels = {int(state): states[int(state)] for state in unique_states} if the categorical variables values are also in layer + + state_values = sorted(state_labels.keys()) + state_names = [state_labels[val] for val in state_values] + + sources, targets, values = [], [], [] + for t in range(len(time_steps) - 1): + for s_from_idx, s_from_val in enumerate(state_values): + for s_to_idx, s_to_val in enumerate(state_values): + count = np.sum((flare_data[:, t] == s_from_val) & (flare_data[:, t + 1] == s_to_val)) + if count > 0: + source_label = f"{state_names[s_from_idx]} ({time_steps[t]})" + target_label = f"{state_names[s_to_idx]} ({time_steps[t + 1]})" + sources.append(source_label) + targets.append(target_label) + values.append(int(count)) + + sankey_df = pd.DataFrame({"source": sources, "target": targets, "value": values}) + + sankey = hv.Sankey(sankey_df, kdims=["source", "target"], vdims=["value"]) + + default_opts = {"label_position": "right", "show_values": True, "title": f"Patient flows: {columns[0]} over time"} + + default_opts.update(kwargs) + + sankey = sankey.opts(opts.Sankey(**default_opts)) + + if show: + from IPython.display import display + + display(sankey) + + return sankey diff --git a/tests/plot/test_sankey.py b/tests/plot/test_sankey.py new file mode 100644 index 000000000..85f369b41 --- /dev/null +++ b/tests/plot/test_sankey.py @@ -0,0 +1,120 @@ +from pathlib import Path + +import ehrdata as ed +import holoviews as hv +import numpy as np +import pandas as pd +import pytest + +import ehrapy as ep + +CURRENT_DIR = Path(__file__).parent +_TEST_IMAGE_PATH = f"{CURRENT_DIR}/_images" + + +@pytest.fixture +def ehr_3d_mini(): + layer = np.array( + [ + [[1, 0, 1], [0, 1, 0]], + [[0, 1, 1], [1, 0, 0]], + [[1, 1, 0], [0, 0, 1]], + ] + ) + + edata = ed.EHRData( + layers={"layer_1": layer}, + obs=pd.DataFrame(index=["patient 1", "patient 2", "patient 3"]), + # obs_names=["patient 1", "patient 2", "patient 3"], + var=pd.DataFrame(index=["treatment", "disease_flare"]), + # var_names=["treatment", "disease_flare"], + tem=pd.DataFrame(index=["visit_0", "visit_1", "visit_2"]), + ) + return edata + + +@pytest.fixture +def diabetes_130_fairlearn_sample_100(): + edata = ed.dt.diabetes_130_fairlearn( + columns_obs_only=[ + "race", + "gender", + ] + )[:100] + + return edata + + +def test_sankey_plot(diabetes_130_fairlearn_sample_100): + edata = diabetes_130_fairlearn_sample_100.copy() + + sankey = ep.pl.plot_sankey(edata, columns=["gender", "race"]) + + assert isinstance(sankey, hv.Sankey) + + data = sankey.data + required_columns = ["source", "target", "value"] + for col in required_columns: + assert col in data.columns + + assert len(data) > 0 # at least one flow + assert (data["value"] > 0).all() # flow values positive + assert data["value"].sum() == len(edata.obs) # total flow must match total obs + + # each flow matches the expected count + for _, row in data.iterrows(): + gender_value = row["source"].split(": ")[1] + race_value = row["target"].split(": ")[1] + flow_value = row["value"] + + expected_count = len(edata.obs[(edata.obs["gender"] == gender_value) & (edata.obs["race"] == race_value)]) + + assert flow_value == expected_count + + for source in data["source"].unique(): + assert source.startswith("gender:") # sources have the correct prefix + for target in data["target"].unique(): + assert target.startswith("race:") # targets have the correct prefix + + +def test_sankey_time_plot(ehr_3d_mini): + edata = ehr_3d_mini + sankey = ep.pl.plot_sankey_time( + edata, columns=["disease_flare"], layer="layer_1", state_labels={0: "no flare", 1: "flare"} + ) + + assert isinstance(sankey, hv.Sankey) + + data = sankey.data + required_columns = ["source", "target", "value"] + for col in required_columns: + assert col in data.columns + + assert len(data) > 0 + assert (data["value"] > 0).all() + + # check that sources and targets contain state labels + state_labels = ["no flare", "flare"] + for source in data["source"].unique(): + assert any(label in source for label in state_labels) + assert "(" in source and ")" in source + for target in data["target"].unique(): + assert any(label in target for label in state_labels) + assert "(" in target and ")" in target + + # check conservation of patients across time points + time_steps = edata.tem.index.tolist() + first_time = time_steps[0] + last_time = time_steps[-1] + + outflow_first = data[data["source"].str.contains(f"\\({first_time}\\)", regex=True)]["value"].sum() + inflow_last = data[data["target"].str.contains(f"\\({last_time}\\)", regex=True)]["value"].sum() + + assert outflow_first == inflow_last + + # total flow equals number of transitions + n_patients = edata.n_obs + n_transitions = len(time_steps) - 1 + expected_total_flow = n_patients * n_transitions + + assert data["value"].sum() == expected_total_flow From d59b90d7fae52b8350575a23243cab64173336f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Wed, 26 Nov 2025 22:58:12 +0100 Subject: [PATCH 02/12] pyproject.toml dependency added and AnnData from typehints removed --- ehrapy/plot/_sankey.py | 7 +++---- pyproject.toml | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ehrapy/plot/_sankey.py b/ehrapy/plot/_sankey.py index d3567a606..601144ba0 100644 --- a/ehrapy/plot/_sankey.py +++ b/ehrapy/plot/_sankey.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING import ehrdata as ed import holoviews as hv @@ -11,12 +11,11 @@ hv.extension("bokeh") if TYPE_CHECKING: - from anndata import AnnData from ehrdata import EHRData def plot_sankey( - edata: EHRData | AnnData, + edata: EHRData, *, columns: list[str], show: bool = False, @@ -87,7 +86,7 @@ def plot_sankey( def plot_sankey_time( - edata: EHRData | AnnData, + edata: EHRData, *, columns: list[str], layer: str, diff --git a/pyproject.toml b/pyproject.toml index 81b62f2e4..4b6373853 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,8 @@ dependencies = [ "leidenalg", # careful - GPL "igraph", # careful - GPL "fast-array-utils[sparse,accel]", - "tslearn" + "tslearn", + "holoviews" ] [project.optional-dependencies] From dcbdaf98f805a6bbf42bdc9e26ba570cd0f4065f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Thu, 27 Nov 2025 12:50:01 +0100 Subject: [PATCH 03/12] small fix --- ehrapy/plot/_sankey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehrapy/plot/_sankey.py b/ehrapy/plot/_sankey.py index 601144ba0..b7ee3d3fd 100644 --- a/ehrapy/plot/_sankey.py +++ b/ehrapy/plot/_sankey.py @@ -71,7 +71,7 @@ def plot_sankey( ) sankey = hv.Sankey(sankey_df, kdims=["source", "target"], vdims=["value"]) - default_opts = {"label_position": "right", "show_values": True, "title": f"Patient flows: {columns[0]} over time"} + default_opts = {"label_position": "right", "show_values": True, "title": f"Flow across: {columns}"} default_opts.update(kwargs) From 3a9c5f8185319a6e94304e210300a2c5b8b898bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Wed, 3 Dec 2025 16:23:40 +0100 Subject: [PATCH 04/12] backend option matplotlib in addition to bokeh --- ehrapy/plot/_sankey.py | 23 + tests/_scripts/sankey_expected.ipynb | 1776 +++++++++++++++++++ tests/plot/_images/sankey_expected.png | Bin 0 -> 50476 bytes tests/plot/_images/sankey_time_expected.png | Bin 0 -> 37730 bytes tests/plot/test_sankey.py | 41 +- 5 files changed, 1836 insertions(+), 4 deletions(-) create mode 100644 tests/_scripts/sankey_expected.ipynb create mode 100644 tests/plot/_images/sankey_expected.png create mode 100644 tests/plot/_images/sankey_time_expected.png diff --git a/ehrapy/plot/_sankey.py b/ehrapy/plot/_sankey.py index b7ee3d3fd..a47ef6c96 100644 --- a/ehrapy/plot/_sankey.py +++ b/ehrapy/plot/_sankey.py @@ -14,10 +14,26 @@ from ehrdata import EHRData +def _set_hv_backend(backend: str | None) -> str: + """Ensure a valid HoloViews backend is active and return its name.""" + if backend is None: + backend = "matplotlib" # default + + backend = backend.lower() + if backend not in {"bokeh", "matplotlib"}: + raise ValueError(f"Unsupported backend '{backend}'. Use 'bokeh' or 'matplotlib'.") + + if hv.Store.current_backend != backend: + hv.extension(backend) + + return backend + + def plot_sankey( edata: EHRData, *, columns: list[str], + backend: str | None = None, show: bool = False, **kwargs, ) -> hv.Sankey: @@ -26,6 +42,7 @@ def plot_sankey( Args: edata : Central data object containing observation data columns : Column names from edata.obs to visualize + backend: HoloViews backend to use ("matplotlib" or"bokeh"). Default: "matplotlib" show: If True, display the plot immediately. If False, only return the plot object without displaying. **kwargs: Additional styling options passed to `holoviews.opts.Sankey`. See HoloViews Sankey documentation for full list of options. @@ -38,6 +55,8 @@ def plot_sankey( >>> ep.pl.plot_sankey(edata, columns=["gender", "race"]) """ + _set_hv_backend(backend) + df = edata.obs[columns] labels = [] @@ -91,6 +110,7 @@ def plot_sankey_time( columns: list[str], layer: str, state_labels: dict[int, str] | None = None, + backend: str | None = None, show: bool = False, **kwargs, ) -> hv.Sankey: @@ -107,6 +127,7 @@ def plot_sankey_time( layer: Name of the layer in `edata.layers` containing the feature data to visualize. state_labels: Mapping from numeric state values to readable labels. If None, state values will be displayed as strings of their numeric codes (e.g., "0", "1", "2"). Default: "None" + backend: HoloViews backend to use ("matplotlib" or"bokeh"). Default: "matplotlib" show: If True, display the plot immediately. If False, only return the plot object without displaying. **kwargs: Additional styling options passed to `holoviews.opts.Sankey`. See HoloViews Sankey documentation for full list of options. @@ -137,6 +158,8 @@ def plot_sankey_time( """ + _set_hv_backend(backend) + flare_data = edata[:, edata.var_names.isin(columns), :].layers[layer][:, 0, :] time_steps = edata.tem.index.tolist() diff --git a/tests/_scripts/sankey_expected.ipynb b/tests/_scripts/sankey_expected.ipynb new file mode 100644 index 000000000..fe894da12 --- /dev/null +++ b/tests/_scripts/sankey_expected.ipynb @@ -0,0 +1,1776 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "42a55492", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "8fc37fff-4ea7-47a5-86a5-9c7da3739e93" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ehrdata as ed\n", + "import holoviews as hv\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import ehrapy as ep" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8104f266", + "metadata": {}, + "outputs": [], + "source": [ + "current_notebook_dir = %pwd\n", + "\n", + "_TEST_IMAGE_PATH = f\"{current_notebook_dir}/../plot/_images\"" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e242cbd4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[93m!\u001b[0m File ehrapy_data\\diabetes_130_fairlearn.csv already exists! Using already downloaded dataset...\n" + ] + } + ], + "source": [ + "edata = ed.dt.diabetes_130_fairlearn(columns_obs_only=[\"gender\", \"race\"])[:100]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3351e8d3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "09d6f56c-5fae-4f48-a37d-aa19a3e0f86c" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sankey = ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], backend=\"matplotlib\", show=False)\n", + "fig = hv.render(sankey, backend=\"matplotlib\")\n", + "\n", + "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_expected.png\", dpi=80)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4b735603", + "metadata": {}, + "outputs": [ + { + "data": {}, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 9, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "c5a92859-0d7c-4c73-829e-94e05cc73cd1" + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "sankey" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "53b26cc8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "bff7933a-7268-435a-b674-4a963bda2ddd" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": {}, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 5, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "2f36f865-d75c-46e1-ad54-c38e44ce6c9f" + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], backend=\"bokeh\", show=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a59e9a13", + "metadata": {}, + "outputs": [], + "source": [ + "layer = np.array(\n", + " [\n", + " [[1, 0, 1], [0, 1, 0]], # patient 1: treatment, disease_flare\n", + " [[0, 1, 1], [1, 0, 0]], # patient 2: treatment, disease_flare\n", + " [[1, 1, 0], [0, 0, 1]], # patient 3: treatment, disease_flare\n", + " ]\n", + ")\n", + "\n", + "edata_time = ed.EHRData(\n", + " layers={\"layer_1\": layer},\n", + " obs=pd.DataFrame(index=[\"patient_1\", \"patient_2\", \"patient_3\"]),\n", + " var=pd.DataFrame(index=[\"treatment\", \"disease_flare\"]),\n", + " tem=pd.DataFrame(index=[\"visit_0\", \"visit_1\", \"visit_2\"]),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "dfb9988e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "df920d20-8dbb-4ca9-8dac-4d8cd7b5af9a" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sankey_time = ep.pl.plot_sankey_time(\n", + " edata_time,\n", + " columns=[\"disease_flare\"],\n", + " layer=\"layer_1\",\n", + " state_labels={0: \"no flare\", 1: \"flare\"},\n", + " backend=\"matplotlib\",\n", + ")\n", + "\n", + "fig = hv.render(sankey_time, backend=\"matplotlib\")\n", + "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_time_expected.png\", dpi=80)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c9b9fb79", + "metadata": {}, + "outputs": [ + { + "data": {}, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 10, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "05cb3ba7-6101-4037-baeb-a51a1b457da2" + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "sankey_time" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "38907509", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "3a9abedd-73f5-4726-91d0-ed70606c5d0f" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": {}, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 8, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "ecca8dc7-ad9f-4bb9-a04c-e79cd259eee0" + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "ep.pl.plot_sankey_time(\n", + " edata_time, columns=[\"disease_flare\"], layer=\"layer_1\", state_labels={0: \"no flare\", 1: \"flare\"}, backend=\"bokeh\"\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/plot/_images/sankey_expected.png b/tests/plot/_images/sankey_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..5c16c9376b1fd794decffe5ffd9df6fb2fc20624 GIT binary patch literal 50476 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 literal 0 HcmV?d00001 diff --git a/tests/plot/_images/sankey_time_expected.png b/tests/plot/_images/sankey_time_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..39c205bf8f9bc29fa175c6c222163287bda87c74 GIT binary patch literal 37730 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 v-=*W< Date: Wed, 10 Dec 2025 10:20:39 +0100 Subject: [PATCH 05/12] sankey added to init.py --- ehrapy/plot/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ehrapy/plot/__init__.py b/ehrapy/plot/__init__.py index c6d24b4ec..c6beca5da 100644 --- a/ehrapy/plot/__init__.py +++ b/ehrapy/plot/__init__.py @@ -12,5 +12,6 @@ from ehrapy.plot._survival_analysis import cox_ph_forestplot, kaplan_meier, ols from ehrapy.plot.causal_inference._dowhy import causal_effect from ehrapy.plot.feature_ranking._feature_importances import rank_features_supervised +from ehrapy.plot._sankey import plot_sankey, plot_sankey_time hv.extension("bokeh") From 542a9fccfce072d5cdee11e2b5c26587d8f538da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Wed, 10 Dec 2025 13:32:33 +0100 Subject: [PATCH 06/12] sankey_time_plot test more complex with more timeseries and more states --- ehrapy/plot/_sankey.py | 1 - tests/_scripts/sankey_expected.ipynb | 286 +++++--------------- tests/plot/_images/sankey_time_expected.png | Bin 37730 -> 54348 bytes tests/plot/test_sankey.py | 33 ++- 4 files changed, 96 insertions(+), 224 deletions(-) diff --git a/ehrapy/plot/_sankey.py b/ehrapy/plot/_sankey.py index a47ef6c96..db61ad0e5 100644 --- a/ehrapy/plot/_sankey.py +++ b/ehrapy/plot/_sankey.py @@ -8,7 +8,6 @@ import pandas as pd from holoviews import opts -hv.extension("bokeh") if TYPE_CHECKING: from ehrdata import EHRData diff --git a/tests/_scripts/sankey_expected.ipynb b/tests/_scripts/sankey_expected.ipynb index fe894da12..396085771 100644 --- a/tests/_scripts/sankey_expected.ipynb +++ b/tests/_scripts/sankey_expected.ipynb @@ -50,12 +50,12 @@ "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ - "
\n", - "
\n", + "
\n", + "
\n", "
\n", "" + "" ], "text/plain": [ ":Sankey [source,target] (value)" ] }, - "execution_count": 9, + "execution_count": 5, "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "c5a92859-0d7c-4c73-829e-94e05cc73cd1" - } + "application/vnd.holoviews_exec.v0+json": {} }, "output_type": "execute_result" } @@ -669,7 +592,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "53b26cc8", "metadata": {}, "outputs": [ @@ -717,12 +640,12 @@ "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ - "
\n", - "
\n", + "
\n", + "
\n", "
\n", "" + "" ], "text/plain": [ ":Sankey [source,target] (value)" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "05cb3ba7-6101-4037-baeb-a51a1b457da2" - } + "application/vnd.holoviews_exec.v0+json": {} }, "output_type": "execute_result" } @@ -1421,7 +1279,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "38907509", "metadata": {}, "outputs": [ @@ -1469,12 +1327,12 @@ "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ - "
\n", - "
\n", + "
\n", + "
\n", "
\n", "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "1ca26b78-2b9d-4973-bed6-c78e6bb73cd0" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ehrdata as ed\n", + "import holoviews as hv\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import ehrapy as ep" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c7cd28a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "b6c17037-60cd-4bb6-9c89-5803c9428023" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "hv.extension(\"bokeh\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8104f266", + "metadata": {}, + "outputs": [], + "source": [ + "current_notebook_dir = %pwd\n", + "\n", + "_TEST_IMAGE_PATH = f\"{current_notebook_dir}/../plot/_images\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e242cbd4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[93m!\u001b[0m File ehrapy_data\\diabetes_130_fairlearn.csv already exists! Using already downloaded dataset...\n" + ] + } + ], + "source": [ + "edata = ed.dt.diabetes_130_fairlearn(columns_obs_only=[\"gender\", \"race\"])[:100]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "53b26cc8", + "metadata": {}, + "outputs": [ + { + "data": {}, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 5, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "7d8f45f1-4346-4a72-b3f1-a9a31712494c" + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], backend=\"bokeh\", show=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a59e9a13", + "metadata": {}, + "outputs": [], + "source": [ + "layer = np.array(\n", + " [\n", + " [[0, 1, 2, 1, 2], [1, 2, 1, 2, 0]],\n", + " [[1, 2, 0, 2, 1], [2, 1, 2, 1, 2]],\n", + " [[2, 0, 1, 2, 0], [2, 0, 1, 1, 2]],\n", + " [[1, 2, 1, 0, 1], [0, 2, 1, 2, 0]],\n", + " [[0, 2, 1, 2, 2], [1, 2, 1, 0, 2]],\n", + " ]\n", + ")\n", + "\n", + "\n", + "edata_time = ed.EHRData(\n", + " layers={\"layer_1\": layer},\n", + " obs=pd.DataFrame(index=[\"patient_1\", \"patient_2\", \"patient_3\", \"patient_4\", \"patient_5\"]),\n", + " var=pd.DataFrame(index=[\"treatment\", \"disease_flare\"]),\n", + " tem=pd.DataFrame(index=[\"visit_0\", \"visit_1\", \"visit_2\", \"visit_3\", \"visit_4\"]),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "38907509", + "metadata": {}, + "outputs": [ + { + "data": {}, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 7, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "1290fb3c-ccad-4063-8943-8b7f1e22382d" + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "ep.pl.plot_sankey_time(\n", + " edata_time,\n", + " columns=[\"disease_flare\"],\n", + " layer=\"layer_1\",\n", + " state_labels={0: \"no flare\", 1: \"mid flare\", 2: \"severe flare\"},\n", + " backend=\"bokeh\",\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/_scripts/sankey_expected.ipynb b/tests/_scripts/sankey_expected.ipynb deleted file mode 100644 index 396085771..000000000 --- a/tests/_scripts/sankey_expected.ipynb +++ /dev/null @@ -1,1634 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "42a55492", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "d292dc13-3464-42db-859f-422302252fd7" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import ehrdata as ed\n", - "import holoviews as hv\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "import ehrapy as ep" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "8104f266", - "metadata": {}, - "outputs": [], - "source": [ - "current_notebook_dir = %pwd\n", - "\n", - "_TEST_IMAGE_PATH = f\"{current_notebook_dir}/../plot/_images\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e242cbd4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[93m!\u001b[0m File ehrapy_data\\diabetes_130_fairlearn.csv already exists! Using already downloaded dataset...\n" - ] - } - ], - "source": [ - "edata = ed.dt.diabetes_130_fairlearn(columns_obs_only=[\"gender\", \"race\"])[:100]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "3351e8d3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "3861f770-ce95-460e-8f26-539b59c8eecd" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sankey = ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], backend=\"matplotlib\", show=False)\n", - "fig = hv.render(sankey, backend=\"matplotlib\")\n", - "\n", - "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_expected.png\", dpi=80)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "4b735603", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 5, - "metadata": { - "application/vnd.holoviews_exec.v0+json": {} - }, - "output_type": "execute_result" - } - ], - "source": [ - "sankey" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "53b26cc8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "4b623405-3adc-45f6-a42e-a95b0e06a313" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 6, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "4e054e45-2bea-484d-a8cc-4268c182c4f8" - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], backend=\"bokeh\", show=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "a59e9a13", - "metadata": {}, - "outputs": [], - "source": [ - "layer = np.array(\n", - " [\n", - " [[0, 1, 2, 1, 2], \n", - " [1, 2, 1, 2, 0]], \n", - " \n", - " [[1, 2, 0, 2, 1],\n", - " [2, 1, 2, 1, 2]],\n", - " \n", - " [[2, 0, 1, 2, 0],\n", - " [2, 0, 1, 1, 2]],\n", - " \n", - " [[1, 2, 1, 0, 1],\n", - " [0, 2, 1, 2, 0]],\n", - "\n", - " [[0, 2, 1, 2, 2],\n", - " [1, 2, 1, 0, 2]],\n", - " ]\n", - " )\n", - "\n", - "\n", - "edata_time = ed.EHRData(\n", - " layers={\"layer_1\": layer},\n", - " obs=pd.DataFrame(index=[\"patient_1\", \"patient_2\", \"patient_3\", \"patient_4\", \"patient_5\"]),\n", - " var=pd.DataFrame(index=[\"treatment\", \"disease_flare\"]),\n", - " tem=pd.DataFrame(index=[\"visit_0\", \"visit_1\", \"visit_2\", \"visit_3\", \"visit_4\"]),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "dfb9988e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "7ad0e6b7-efa5-46b9-a68d-08c8c6a54534" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sankey_time = ep.pl.plot_sankey_time(\n", - " edata_time,\n", - " columns=[\"disease_flare\"],\n", - " layer=\"layer_1\",\n", - " state_labels={0: \"no flare\", 1: \"mid flare\", 2: \"severe flare\"},\n", - " backend=\"matplotlib\",\n", - ")\n", - "\n", - "fig = hv.render(sankey_time, backend=\"matplotlib\")\n", - "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_time_expected.png\", dpi=80)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "c9b9fb79", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 9, - "metadata": { - "application/vnd.holoviews_exec.v0+json": {} - }, - "output_type": "execute_result" - } - ], - "source": [ - "sankey_time" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "38907509", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "9b1a6df2-903d-4967-9384-104ce84a0cea" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 10, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "fb57819f-f792-458d-87fb-43d4a5875885" - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "ep.pl.plot_sankey_time(\n", - " edata_time, columns=[\"disease_flare\"], layer=\"layer_1\", state_labels={0: \"no flare\", 1: \"mid flare\", 2: \"severe flare\"}, backend=\"bokeh\"\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tests/_scripts/sankey_matplotlib_expected.ipynb b/tests/_scripts/sankey_matplotlib_expected.ipynb new file mode 100644 index 000000000..d72909844 --- /dev/null +++ b/tests/_scripts/sankey_matplotlib_expected.ipynb @@ -0,0 +1,696 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "42a55492", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "fc1e6f31-f5ae-4bf2-bc38-a391d3d64d89" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ehrdata as ed\n", + "import holoviews as hv\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import ehrapy as ep" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8e89fc6d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", + "application/vnd.holoviews_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.holoviews_exec.v0+json": "", + "text/html": [ + "
\n", + "
\n", + "
\n", + "" + ] + }, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "a0d3e5ad-430a-4a56-8a43-a6f0948382a6" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "hv.extension(\"matplotlib\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8104f266", + "metadata": {}, + "outputs": [], + "source": [ + "current_notebook_dir = %pwd\n", + "\n", + "_TEST_IMAGE_PATH = f\"{current_notebook_dir}/../plot/_images\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e242cbd4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[93m!\u001b[0m File ehrapy_data\\diabetes_130_fairlearn.csv already exists! Using already downloaded dataset...\n" + ] + } + ], + "source": [ + "edata = ed.dt.diabetes_130_fairlearn(columns_obs_only=[\"gender\", \"race\"])[:100]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3351e8d3", + "metadata": {}, + "outputs": [], + "source": [ + "sankey = ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], show=False)\n", + "fig = hv.render(sankey, backend=\"matplotlib\")\n", + "\n", + "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_expected.png\", dpi=80)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4b735603", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 6, + "metadata": { + "application/vnd.holoviews_exec.v0+json": {} + }, + "output_type": "execute_result" + } + ], + "source": [ + "sankey" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a59e9a13", + "metadata": {}, + "outputs": [], + "source": [ + "layer = np.array(\n", + " [\n", + " [[0, 1, 2, 1, 2], [1, 2, 1, 2, 0]],\n", + " [[1, 2, 0, 2, 1], [2, 1, 2, 1, 2]],\n", + " [[2, 0, 1, 2, 0], [2, 0, 1, 1, 2]],\n", + " [[1, 2, 1, 0, 1], [0, 2, 1, 2, 0]],\n", + " [[0, 2, 1, 2, 2], [1, 2, 1, 0, 2]],\n", + " ]\n", + ")\n", + "\n", + "\n", + "edata_time = ed.EHRData(\n", + " layers={\"layer_1\": layer},\n", + " obs=pd.DataFrame(index=[\"patient_1\", \"patient_2\", \"patient_3\", \"patient_4\", \"patient_5\"]),\n", + " var=pd.DataFrame(index=[\"treatment\", \"disease_flare\"]),\n", + " tem=pd.DataFrame(index=[\"visit_0\", \"visit_1\", \"visit_2\", \"visit_3\", \"visit_4\"]),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "dfb9988e", + "metadata": {}, + "outputs": [], + "source": [ + "sankey_time = ep.pl.plot_sankey_time(\n", + " edata_time,\n", + " columns=[\"disease_flare\"],\n", + " layer=\"layer_1\",\n", + " state_labels={0: \"no flare\", 1: \"mid flare\", 2: \"severe flare\"},\n", + " backend=\"matplotlib\",\n", + ")\n", + "\n", + "fig = hv.render(sankey_time, backend=\"matplotlib\")\n", + "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_time_expected.png\", dpi=80)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c9b9fb79", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + ":Sankey [source,target] (value)" + ] + }, + "execution_count": 9, + "metadata": { + "application/vnd.holoviews_exec.v0+json": {} + }, + "output_type": "execute_result" + } + ], + "source": [ + "sankey_time" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/plot/test_sankey.py b/tests/plot/test_sankey.py index dcee7540b..8358df90c 100644 --- a/tests/plot/test_sankey.py +++ b/tests/plot/test_sankey.py @@ -16,20 +16,11 @@ def ehr_3d_mini(): layer = np.array( [ - [[0, 1, 2, 1, 2], - [1, 2, 1, 2, 0]], - - [[1, 2, 0, 2, 1], - [2, 1, 2, 1, 2]], - - [[2, 0, 1, 2, 0], - [2, 0, 1, 1, 2]], - - [[1, 2, 1, 0, 1], - [0, 2, 1, 2, 0]], - - [[0, 2, 1, 2, 2], - [1, 2, 1, 0, 2]], + [[0, 1, 2, 1, 2], [1, 2, 1, 2, 0]], + [[1, 2, 0, 2, 1], [2, 1, 2, 1, 2]], + [[2, 0, 1, 2, 0], [2, 0, 1, 1, 2]], + [[1, 2, 1, 0, 1], [0, 2, 1, 2, 0]], + [[0, 2, 1, 2, 2], [1, 2, 1, 0, 2]], ] ) @@ -57,9 +48,10 @@ def diabetes_130_fairlearn_sample_100(): def test_sankey_plot(diabetes_130_fairlearn_sample_100, check_same_image): + hv.extension("matplotlib") edata = diabetes_130_fairlearn_sample_100.copy() - sankey = ep.pl.plot_sankey(edata, columns=["gender", "race"], backend="matplotlib") + sankey = ep.pl.plot_sankey(edata, columns=["gender", "race"]) fig = hv.render(sankey, backend="matplotlib") check_same_image( @@ -69,10 +61,30 @@ def test_sankey_plot(diabetes_130_fairlearn_sample_100, check_same_image): ) +def test_sankey_time_plot(ehr_3d_mini, check_same_image): + hv.extension("matplotlib") + edata = ehr_3d_mini + sankey_time = ep.pl.plot_sankey_time( + edata, + columns=["disease_flare"], + layer="layer_1", + state_labels={0: "no flare", 1: "mid flare", 2: "severe flare"}, + ) + + fig = hv.render(sankey_time, backend="matplotlib") + + check_same_image( + fig=fig, + base_path=f"{_TEST_IMAGE_PATH}/sankey_time", + tol=2e-1, + ) + + def test_sankey_bokeh_plot(diabetes_130_fairlearn_sample_100): + hv.extension("bokeh") edata = diabetes_130_fairlearn_sample_100.copy() - sankey = ep.pl.plot_sankey(edata, columns=["gender", "race"], backend="bokeh") + sankey = ep.pl.plot_sankey(edata, columns=["gender", "race"]) assert isinstance(sankey, hv.Sankey) @@ -101,36 +113,15 @@ def test_sankey_bokeh_plot(diabetes_130_fairlearn_sample_100): assert target.startswith("race:") # targets have the correct prefix -def test_sankey_time_plot(ehr_3d_mini, check_same_image): +def test_sankey_time_bokeh_plot(ehr_3d_mini): + hv.extension("bokeh") edata = ehr_3d_mini - sankey_time = ep.pl.plot_sankey_time( + sankey = ep.pl.plot_sankey_time( edata, columns=["disease_flare"], layer="layer_1", - state_labels={0: "no flare", 1: "mid flare", 2: "severe flare"}, - backend="matplotlib", + state_labels={0: "no flare", 1: " mid flare", 2: "severe flare"}, ) - - fig = hv.render(sankey_time, backend="matplotlib") - - check_same_image( - fig=fig, - base_path=f"{_TEST_IMAGE_PATH}/sankey_time", - tol=2e-1, - ) - - - -def test_sankey_time_bokeh_plot(ehr_3d_mini): - edata = ehr_3d_mini - sankey = ep.pl.plot_sankey_time( - edata, - columns=["disease_flare"], - layer="layer_1", - state_labels={0: "no flare", 1: " mid flare", 2: "severe flare"}, - backend="bokeh" - ) - assert isinstance(sankey, hv.Sankey) data = sankey.data From c9e6b01d7da885f858942cba2b3bfd90b62b0bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Fri, 12 Dec 2025 09:35:05 +0100 Subject: [PATCH 09/12] fix --- tests/_scripts/sankey_expected.ipynb | 1629 -------------------------- tests/plot/test_sankey.py | 24 - 2 files changed, 1653 deletions(-) delete mode 100644 tests/_scripts/sankey_expected.ipynb diff --git a/tests/_scripts/sankey_expected.ipynb b/tests/_scripts/sankey_expected.ipynb deleted file mode 100644 index a9e9f170b..000000000 --- a/tests/_scripts/sankey_expected.ipynb +++ /dev/null @@ -1,1629 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "42a55492", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "d292dc13-3464-42db-859f-422302252fd7" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import ehrdata as ed\n", - "import holoviews as hv\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "import ehrapy as ep" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "8104f266", - "metadata": {}, - "outputs": [], - "source": [ - "current_notebook_dir = %pwd\n", - "\n", - "_TEST_IMAGE_PATH = f\"{current_notebook_dir}/../plot/_images\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e242cbd4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[93m!\u001b[0m File ehrapy_data\\diabetes_130_fairlearn.csv already exists! Using already downloaded dataset...\n" - ] - } - ], - "source": [ - "edata = ed.dt.diabetes_130_fairlearn(columns_obs_only=[\"gender\", \"race\"])[:100]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "3351e8d3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "3861f770-ce95-460e-8f26-539b59c8eecd" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sankey = ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], backend=\"matplotlib\", show=False)\n", - "fig = hv.render(sankey, backend=\"matplotlib\")\n", - "\n", - "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_expected.png\", dpi=80)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "4b735603", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 5, - "metadata": { - "application/vnd.holoviews_exec.v0+json": {} - }, - "output_type": "execute_result" - } - ], - "source": [ - "sankey" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "53b26cc8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "4b623405-3adc-45f6-a42e-a95b0e06a313" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 6, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "4e054e45-2bea-484d-a8cc-4268c182c4f8" - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "ep.pl.plot_sankey(edata, columns=[\"gender\", \"race\"], backend=\"bokeh\", show=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "a59e9a13", - "metadata": {}, - "outputs": [], - "source": [ - "layer = np.array(\n", - " [\n", - " [[0, 1, 2, 1, 2], [1, 2, 1, 2, 0]],\n", - " [[1, 2, 0, 2, 1], [2, 1, 2, 1, 2]],\n", - " [[2, 0, 1, 2, 0], [2, 0, 1, 1, 2]],\n", - " [[1, 2, 1, 0, 1], [0, 2, 1, 2, 0]],\n", - " [[0, 2, 1, 2, 2], [1, 2, 1, 0, 2]],\n", - " ]\n", - ")\n", - "\n", - "\n", - "edata_time = ed.EHRData(\n", - " layers={\"layer_1\": layer},\n", - " obs=pd.DataFrame(index=[\"patient_1\", \"patient_2\", \"patient_3\", \"patient_4\", \"patient_5\"]),\n", - " var=pd.DataFrame(index=[\"treatment\", \"disease_flare\"]),\n", - " tem=pd.DataFrame(index=[\"visit_0\", \"visit_1\", \"visit_2\", \"visit_3\", \"visit_4\"]),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "dfb9988e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "7ad0e6b7-efa5-46b9-a68d-08c8c6a54534" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sankey_time = ep.pl.plot_sankey_time(\n", - " edata_time,\n", - " columns=[\"disease_flare\"],\n", - " layer=\"layer_1\",\n", - " state_labels={0: \"no flare\", 1: \"mid flare\", 2: \"severe flare\"},\n", - " backend=\"matplotlib\",\n", - ")\n", - "\n", - "fig = hv.render(sankey_time, backend=\"matplotlib\")\n", - "fig.savefig(f\"{_TEST_IMAGE_PATH}/sankey_time_expected.png\", dpi=80)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "c9b9fb79", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 9, - "metadata": { - "application/vnd.holoviews_exec.v0+json": {} - }, - "output_type": "execute_result" - } - ], - "source": [ - "sankey_time" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "38907509", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.8.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n const url = js_exports[name];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) >= 0 || root[name] != null) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.holoviz.org/panel/1.8.3/dist/bundled/reactiveesm/es-module-shims@^1.10.0/dist/es-module-shims.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.8.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.8.1.min.js\", \"https://cdn.holoviz.org/panel/1.8.3/dist/panel.min.js\"];\n const js_modules = [];\n const js_exports = {};\n const css_urls = [];\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (let i = 0; i < inline_js.length; i++) {\n try {\n inline_js[i].call(root, root.Bokeh);\n } catch(e) {\n if (!reloading) {\n throw e;\n }\n }\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n var NewBokeh = root.Bokeh;\n if (Bokeh.versions === undefined) {\n Bokeh.versions = new Map();\n }\n if (NewBokeh.version !== Bokeh.version) {\n Bokeh.versions.set(NewBokeh.version, NewBokeh)\n }\n root.Bokeh = Bokeh;\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n // If the timeout and bokeh was not successfully loaded we reset\n // everything and try loading again\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n root._bokeh_is_loading = 0\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n const bokeh_loaded = root.Bokeh != null && (root.Bokeh.version === py_version || (root.Bokeh.versions !== undefined && root.Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n if (root.Bokeh) {\n root.Bokeh = undefined;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n let retries = 0;\n const open = () => {\n if (comm.active) {\n comm.open();\n } else if (retries > 3) {\n console.warn('Comm target never activated')\n } else {\n retries += 1\n setTimeout(open, 500)\n }\n }\n if (comm.active) {\n comm.open();\n } else {\n setTimeout(open, 500)\n }\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n })\n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", - "application/vnd.holoviews_load.v0+json": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ] - }, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "9b1a6df2-903d-4967-9384-104ce84a0cea" - } - }, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ], - "text/plain": [ - ":Sankey [source,target] (value)" - ] - }, - "execution_count": 10, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "fb57819f-f792-458d-87fb-43d4a5875885" - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "ep.pl.plot_sankey_time(\n", - " edata_time,\n", - " columns=[\"disease_flare\"],\n", - " layer=\"layer_1\",\n", - " state_labels={0: \"no flare\", 1: \"mid flare\", 2: \"severe flare\"},\n", - " backend=\"bokeh\",\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tests/plot/test_sankey.py b/tests/plot/test_sankey.py index 9a8da91dc..6e296fbc1 100644 --- a/tests/plot/test_sankey.py +++ b/tests/plot/test_sankey.py @@ -118,25 +118,6 @@ def test_sankey_bokeh_plot(diabetes_130_fairlearn_sample_100): assert target.startswith("race:") # targets have the correct prefix -def test_sankey_time_plot(ehr_3d_mini, check_same_image): - edata = ehr_3d_mini - sankey_time = ep.pl.plot_sankey_time( - edata, - columns=["disease_flare"], - layer="layer_1", - state_labels={0: "no flare", 1: "mid flare", 2: "severe flare"}, - backend="matplotlib", - ) - - fig = hv.render(sankey_time, backend="matplotlib") - - check_same_image( - fig=fig, - base_path=f"{_TEST_IMAGE_PATH}/sankey_time", - tol=2e-1, - ) - - def test_sankey_time_bokeh_plot(ehr_3d_mini): hv.extension("bokeh") edata = ehr_3d_mini @@ -145,11 +126,6 @@ def test_sankey_time_bokeh_plot(ehr_3d_mini): columns=["disease_flare"], layer="layer_1", state_labels={0: "no flare", 1: " mid flare", 2: "severe flare"}, - backend="bokeh", - edata, - columns=["disease_flare"], - layer="layer_1", - state_labels={0: "no flare", 1: " mid flare", 2: "severe flare"}, ) assert isinstance(sankey, hv.Sankey) From cae40f66792e42e0252e0e3202ce71098466a37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Fri, 12 Dec 2025 09:39:09 +0100 Subject: [PATCH 10/12] fix --- tests/plot/test_sankey.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/plot/test_sankey.py b/tests/plot/test_sankey.py index 6e296fbc1..8358df90c 100644 --- a/tests/plot/test_sankey.py +++ b/tests/plot/test_sankey.py @@ -21,11 +21,6 @@ def ehr_3d_mini(): [[2, 0, 1, 2, 0], [2, 0, 1, 1, 2]], [[1, 2, 1, 0, 1], [0, 2, 1, 2, 0]], [[0, 2, 1, 2, 2], [1, 2, 1, 0, 2]], - [[0, 1, 2, 1, 2], [1, 2, 1, 2, 0]], - [[1, 2, 0, 2, 1], [2, 1, 2, 1, 2]], - [[2, 0, 1, 2, 0], [2, 0, 1, 1, 2]], - [[1, 2, 1, 0, 1], [0, 2, 1, 2, 0]], - [[0, 2, 1, 2, 2], [1, 2, 1, 0, 2]], ] ) From abcb9bae5b26e03cafa7c3cee37fd05ae10b4133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Fri, 12 Dec 2025 12:30:39 +0100 Subject: [PATCH 11/12] deleted outcommented code and formatting fixed --- ehrapy/plot/_sankey.py | 21 ++------------------- tests/plot/test_sankey.py | 2 -- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/ehrapy/plot/_sankey.py b/ehrapy/plot/_sankey.py index 223d8942a..2bd0f24bf 100644 --- a/ehrapy/plot/_sankey.py +++ b/ehrapy/plot/_sankey.py @@ -12,21 +12,6 @@ from ehrdata import EHRData -def _set_hv_backend(backend: str | None) -> str: - """Ensure a valid HoloViews backend is active and return its name.""" - if backend is None: - backend = "matplotlib" # default - - backend = backend.lower() - if backend not in {"bokeh", "matplotlib"}: - raise ValueError(f"Unsupported backend '{backend}'. Use 'bokeh' or 'matplotlib'.") - - if hv.Store.current_backend != backend: - hv.extension(backend) - - return backend - - def plot_sankey( edata: EHRData, *, @@ -36,7 +21,7 @@ def plot_sankey( ) -> hv.Sankey: """Create a Sankey diagram showing relationships across observation columns. - Please run `hv.extension('matplotlib')` or `hv.extension('bokeh')` before using this function to determine the backend. + Please call :func:`holoviews.extension` with ``"matplotlib"`` or ``"bokeh"`` before using this function to select the backend. Args: @@ -117,7 +102,7 @@ def plot_sankey_time( Each node represents a state at a specific time point, and flows show the number of patients transitioning between states. - Please run `hv.extension('matplotlib')` or `hv.extension('bokeh')` before using this function to determine the backend. + Please call :func:`holoviews.extension` with ``"matplotlib"`` or ``"bokeh"`` before using this function to select the backend. Args: edata: Central data object containing observation data @@ -159,13 +144,11 @@ def plot_sankey_time( flare_data = edata[:, edata.var_names.isin(columns), :].layers[layer][:, 0, :] time_steps = edata.tem.index.tolist() - # states = edata.var.loc[columns[0]].values if state_labels is None: unique_states = np.unique(flare_data) unique_states = unique_states[~np.isnan(unique_states)] state_labels = {int(state): str(state) for state in unique_states} - # state_labels = {int(state): states[int(state)] for state in unique_states} if the categorical variables values are also in layer state_values = sorted(state_labels.keys()) state_names = [state_labels[val] for val in state_values] diff --git a/tests/plot/test_sankey.py b/tests/plot/test_sankey.py index 8358df90c..fa11d0ecb 100644 --- a/tests/plot/test_sankey.py +++ b/tests/plot/test_sankey.py @@ -27,9 +27,7 @@ def ehr_3d_mini(): edata = ed.EHRData( layers={"layer_1": layer}, obs=pd.DataFrame(index=["patient 1", "patient 2", "patient 3", "patient 4", "patient 5"]), - # obs_names=["patient 1", "patient 2", "patient 3"], var=pd.DataFrame(index=["treatment", "disease_flare"]), - # var_names=["treatment", "disease_flare"], tem=pd.DataFrame(index=["visit_0", "visit_1", "visit_2", "visit_3", "visit_4"]), ) return edata From 36987348c33d065ce7a21acb82c51420a48b848f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96yk=C3=BC=20S=C3=BCoglu?= Date: Fri, 12 Dec 2025 12:47:10 +0100 Subject: [PATCH 12/12] formatting fix --- ehrapy/plot/_sankey.py | 7 ++----- tests/plot/test_sankey.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ehrapy/plot/_sankey.py b/ehrapy/plot/_sankey.py index 2bd0f24bf..fc176bc2f 100644 --- a/ehrapy/plot/_sankey.py +++ b/ehrapy/plot/_sankey.py @@ -2,7 +2,6 @@ from typing import TYPE_CHECKING -import ehrdata as ed import holoviews as hv import numpy as np import pandas as pd @@ -23,7 +22,6 @@ def plot_sankey( Please call :func:`holoviews.extension` with ``"matplotlib"`` or ``"bokeh"`` before using this function to select the backend. - Args: edata : Central data object containing observation data columns : Column names from edata.obs to visualize @@ -106,11 +104,10 @@ def plot_sankey_time( Args: edata: Central data object containing observation data - columns: Column names from edata.obs to visualize + columns: Column names from edata.var_names to visualize layer: Name of the layer in `edata.layers` containing the feature data to visualize. state_labels: Mapping from numeric state values to readable labels. If None, state values - will be displayed as strings of their numeric codes (e.g., "0", "1", "2"). Default: "None" - backend: HoloViews backend to use ("matplotlib" or"bokeh"). Default: "matplotlib" + will be displayed as strings of their numeric codes (e.g., "0", "1", "2"). Default: None show: If True, display the plot immediately. If False, only return the plot object without displaying. **kwargs: Additional styling options passed to `holoviews.opts.Sankey`. See HoloViews Sankey documentation for full list of options. diff --git a/tests/plot/test_sankey.py b/tests/plot/test_sankey.py index fa11d0ecb..f6065cb1f 100644 --- a/tests/plot/test_sankey.py +++ b/tests/plot/test_sankey.py @@ -118,7 +118,7 @@ def test_sankey_time_bokeh_plot(ehr_3d_mini): edata, columns=["disease_flare"], layer="layer_1", - state_labels={0: "no flare", 1: " mid flare", 2: "severe flare"}, + state_labels={0: "no flare", 1: "mid flare", 2: "severe flare"}, ) assert isinstance(sankey, hv.Sankey)