Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pydatalab/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ classifiers = [
requires-python = ">= 3.10, < 3.12"

dependencies = [
"bokeh ~= 2.4, < 3.0",
"matplotlib ~= 3.8",
"periodictable ~= 1.7",
"pydantic[email, dotenv] < 2.0",
"pint ~= 0.24",
"pandas[excel] ~= 2.2",
"pymongo ~= 4.7",
"bokeh==3.4.3",
]

[project.urls]
Expand Down
6 changes: 3 additions & 3 deletions pydatalab/src/pydatalab/apps/echem/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ def plot_cycle(self):
if layout is not None:
# Don't overwrite the previous plot data in cases where the plot is not generated
# for a 'normal' reason
self.data["bokeh_plot_data"] = bokeh.embed.json_item(
layout, theme=bokeh_plots.DATALAB_BOKEH_THEME
)
script, div = bokeh.embed.components(layout, theme=bokeh_plots.DATALAB_BOKEH_THEME)

self.data["bokeh_plot_data"] = {"script": script, "div": div}
return

@property
Expand Down
4 changes: 3 additions & 1 deletion pydatalab/src/pydatalab/apps/eis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ def generate_eis_plot(self):
tools=HoverTool(tooltips=[("Frequency [Hz]", "@{Frequency [Hz]}")]),
)

self.data["bokeh_plot_data"] = bokeh.embed.json_item(plot, theme=DATALAB_BOKEH_THEME)
script, div = bokeh.embed.components(plot, theme=DATALAB_BOKEH_THEME)

self.data["bokeh_plot_data"] = {"script": script, "div": div}
4 changes: 3 additions & 1 deletion pydatalab/src/pydatalab/apps/ftir/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,6 @@ def generate_ftir_plot(self):

if ftir_data is not None:
layout = self._format_ftir_plot(ftir_data)
self.data["bokeh_plot_data"] = bokeh.embed.json_item(layout, theme=DATALAB_BOKEH_THEME)
script, div = bokeh.embed.components(layout, theme=DATALAB_BOKEH_THEME)

self.data["bokeh_plot_data"] = {"script": script, "div": div}
5 changes: 3 additions & 2 deletions pydatalab/src/pydatalab/apps/nmr/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def generate_nmr_plot(self, parse: bool = True):
self.data["bokeh_plot_data"] = self.make_nmr_plot(df, self.data["metadata"])

@classmethod
def make_nmr_plot(cls, df: pd.DataFrame, metadata: dict[str, Any]) -> str:
def make_nmr_plot(cls, df: pd.DataFrame, metadata: dict[str, Any]) -> dict[str, str]:
"""Create a Bokeh plot for the NMR data stored in the dataframe and metadata."""
nucleus_label = metadata.get("nucleus") or ""
# replace numbers with superscripts
Expand Down Expand Up @@ -270,4 +270,5 @@ def make_nmr_plot(cls, df: pd.DataFrame, metadata: dict[str, Any]) -> str:
# of the layout in the current implementation, but this could be fragile.
bokeh_layout.children[1].x_range.flipped = True

return bokeh.embed.json_item(bokeh_layout, theme=DATALAB_BOKEH_THEME)
script, div = bokeh.embed.components(bokeh_layout, theme=DATALAB_BOKEH_THEME)
return {"script": script, "div": div}
4 changes: 3 additions & 1 deletion pydatalab/src/pydatalab/apps/raman/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,6 @@ def generate_raman_plot(self):
point_size=3,
)

self.data["bokeh_plot_data"] = bokeh.embed.json_item(p, theme=DATALAB_BOKEH_THEME)
script, div = bokeh.embed.components(p, theme=DATALAB_BOKEH_THEME)

self.data["bokeh_plot_data"] = {"script": script, "div": div}
7 changes: 5 additions & 2 deletions pydatalab/src/pydatalab/apps/tga/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def _plot_ms_data(cls, ms_data):
max_vals: list[tuple[str, float]] = []

data_key: str = (
"Partial pressure [mbar] or Ion Current [A]" # default value for data key if missing
# default value for data key if missing
"Partial pressure [mbar] or Ion Current [A]"
)

for species in ms_data["data"]:
Expand Down Expand Up @@ -100,4 +101,6 @@ def _plot_ms_data(cls, ms_data):
grid.append(plots[i : i + M])
p = gridplot(grid, sizing_mode="scale_width", toolbar_location="below")

return bokeh.embed.json_item(p, theme=DATALAB_BOKEH_GRID_THEME)
script, div = bokeh.embed.components(p, theme=DATALAB_BOKEH_GRID_THEME)

return {"script": script, "div": div}
4 changes: 3 additions & 1 deletion pydatalab/src/pydatalab/apps/uvvis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,6 @@ def generate_absorbance_plot(self):
_names = [Path(file["location"]).name for file in file_info[1:]]
if len(absorbance_data) > 0:
layout = self._format_UV_Vis_plot(absorbance_data, names=_names)
self.data["bokeh_plot_data"] = bokeh.embed.json_item(layout, theme=DATALAB_BOKEH_THEME)
script, div = bokeh.embed.components(layout, theme=DATALAB_BOKEH_THEME)

self.data["bokeh_plot_data"] = {"script": script, "div": div}
7 changes: 5 additions & 2 deletions pydatalab/src/pydatalab/apps/xrd/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ def load_pattern(
df, peak_data = compute_cif_pxrd(
location, wavelength=wavelength or cls.defaults["wavelength"]
)
theoretical = True # Track whether this is a computed PXRD that does not need background subtraction
# Track whether this is a computed PXRD that does not need background subtraction
theoretical = True

else:
columns = ["twotheta", "intensity", "error"]
Expand Down Expand Up @@ -271,4 +272,6 @@ def generate_xrd_plot(self) -> None:
point_size=3,
)

self.data["bokeh_plot_data"] = bokeh.embed.json_item(p, theme=DATALAB_BOKEH_THEME)
script, div = bokeh.embed.components(p, theme=DATALAB_BOKEH_THEME)

self.data["bokeh_plot_data"] = {"script": script, "div": div}
4 changes: 3 additions & 1 deletion pydatalab/src/pydatalab/blocks/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,6 @@ def plot_df(self):
show_table=True,
)

self.data["bokeh_plot_data"] = bokeh.embed.json_item(plot, theme=DATALAB_BOKEH_THEME)
script, div = bokeh.embed.components(plot, theme=DATALAB_BOKEH_THEME)

self.data["bokeh_plot_data"] = {"script": script, "div": div}
149 changes: 118 additions & 31 deletions pydatalab/src/pydatalab/bokeh_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,42 @@
COLORS = Dark2[8]
TOOLS = "box_zoom, reset, tap, crosshair, save"

PLOT_WIDTH = 620
PLOT_HEIGHT = 410
PLOT_MIN_WIDTH = 620
PLOT_MIN_HEIGHT = 410

DEFAULT_FIGURE_CONFIG = {
"width": PLOT_WIDTH,
"height": PLOT_HEIGHT,
"min_width": PLOT_MIN_WIDTH,
"min_height": PLOT_MIN_HEIGHT,
"sizing_mode": "scale_width",
"toolbar_location": "above",
"tools": TOOLS,
}

GRID_PLOT_CONFIG = {
"width": PLOT_WIDTH // 2 - 20,
"height": PLOT_HEIGHT,
"min_width": PLOT_MIN_WIDTH // 2 - 20,
"min_height": PLOT_MIN_HEIGHT,
"sizing_mode": "scale_width",
"toolbar_location": "above",
"tools": TOOLS,
}

FULL_WIDTH_CONFIG = {
"width": PLOT_WIDTH,
"height": PLOT_HEIGHT - 40,
"min_width": PLOT_MIN_WIDTH,
"min_height": PLOT_MIN_HEIGHT - 40,
"sizing_mode": "scale_width",
"toolbar_location": "above",
"tools": TOOLS,
}


SELECTABLE_CALLBACK_x = """
var column = cb_obj.value;
if (circle1) {circle1.glyph.x.field = column;}
Expand Down Expand Up @@ -66,7 +102,7 @@
style = {
"attrs": {
# apply defaults to Figure properties
"Figure": {
"figure": {
"toolbar_location": "above",
"outline_line_color": None,
"min_border_right": 10,
Expand Down Expand Up @@ -96,12 +132,11 @@
}
}


"""Additional style suitable for grid plots"""
grid_style = {
"attrs": {
# apply defaults to Figure properties
"Figure": {
"figure": {
"toolbar_location": "above",
"outline_line_color": None,
"min_border_right": 10,
Expand Down Expand Up @@ -131,11 +166,64 @@
}
}


DATALAB_BOKEH_THEME = Theme(json=style)
DATALAB_BOKEH_GRID_THEME = Theme(json=grid_style)


def create_standard_figure(**kwargs):
"""
Creates a Bokeh figure with standardized dimensions.

Args:
**kwargs: Additional parameters that override the default values.

Returns:
Bokeh figure with standardized dimensions.
"""
config = DEFAULT_FIGURE_CONFIG.copy()
config.update(kwargs)

p = figure(**config)
p.toolbar.logo = "grey"
return p


def create_grid_figure(**kwargs):
"""
Creates a Bokeh figure for grid use with adapted dimensions.

Args:
**kwargs: Additional parameters that override the default values.

Returns:
Bokeh figure with responsive dimensions for grid layout.
"""
config = GRID_PLOT_CONFIG.copy()
config.update(kwargs)

p = figure(**config)
p.toolbar.logo = "grey"
return p


def create_full_width_figure(**kwargs):
"""
Creates a full-width Bokeh figure (for tables, timelines, etc.).

Args:
**kwargs: Additional parameters that override the default values.

Returns:
Full-width responsive Bokeh figure.
"""
config = FULL_WIDTH_CONFIG.copy()
config.update(kwargs)

p = figure(**config)
p.toolbar.logo = "grey"
return p


def selectable_axes_plot(
df: dict[str, pd.DataFrame] | list[pd.DataFrame] | pd.DataFrame,
x_options: list[str] | None = None,
Expand Down Expand Up @@ -225,16 +313,12 @@ def selectable_axes_plot(
x_axis_label = x_default if label_x else ""
y_axis_label = y_label if label_y else ""

p = figure(
sizing_mode="scale_width",
aspect_ratio=kwargs.pop("aspect_ratio", 1.5),
p = create_standard_figure(
x_axis_label=x_axis_label,
y_axis_label=y_axis_label,
tools=TOOLS,
title=plot_title,
**kwargs,
)
p.toolbar.logo = "grey"

if tools:
if isinstance(tools, list):
Expand Down Expand Up @@ -299,7 +383,7 @@ def selectable_axes_plot(
y_default = y_default[0]

circles = (
p.circle(
p.scatter(
x=x_default,
y=y_default,
source=source,
Expand Down Expand Up @@ -431,9 +515,6 @@ def double_axes_echem_plot(

x_options = [opt for opt in x_options if opt in df.columns]

common_options = {"aspect_ratio": 1.5, "tools": TOOLS}
common_options.update(**kwargs)

if mode == "normal":
mode = None

Expand All @@ -452,34 +533,36 @@ def double_axes_echem_plot(
# normal plot
# x_label = "Capacity (mAh/g)" if x_default == "Capacity normalized" else x_default
x_label = x_default
p1 = figure(x_axis_label=x_label, y_axis_label="voltage (V)", **common_options)

if mode == "dQ/dV":
p1 = create_grid_figure(x_axis_label=x_label, y_axis_label="voltage (V)", **kwargs)
else:
p1 = create_standard_figure(x_axis_label=x_label, y_axis_label="voltage (V)", **kwargs)

p1.xaxis.ticker.desired_num_ticks = 5
plots.append(p1)

# the differential plot
if mode in ("dQ/dV", "dV/dQ"):
if mode == "dQ/dV":
p2 = figure(
x_axis_label=mode,
y_axis_label="voltage (V)",
y_range=p1.y_range,
**common_options,
p2 = create_grid_figure(
x_axis_label=mode, y_axis_label="voltage (V)", y_range=p1.y_range, **kwargs
)
p2.xaxis.ticker.desired_num_ticks = 3
else:
p2 = figure(
x_axis_label=x_default, y_axis_label=mode, x_range=p1.x_range, **common_options
p2 = create_standard_figure(
x_axis_label=x_default, y_axis_label=mode, x_range=p1.x_range, **kwargs
)
p2.xaxis.ticker.desired_num_ticks = 5
plots.append(p2)

elif mode == "final capacity" and cycle_summary is not None:
palette = Accent[3]

p3 = figure(
p3 = create_standard_figure(
x_axis_label="Cycle number",
y_axis_label="capacity (mAh/g)" if normalized else "capacity (mAh)",
**common_options,
**kwargs,
)

p3.line(
Expand All @@ -490,7 +573,7 @@ def double_axes_echem_plot(
line_width=2,
color=palette[0],
)
p3.circle(
p3.scatter(
x="full cycle",
y="charge capacity (mAh/g)" if normalized else "charge capacity (mAh)",
source=cycle_summary,
Expand All @@ -509,10 +592,11 @@ def double_axes_echem_plot(
line_width=2,
color=palette[2],
)
p3.triangle(
p3.scatter(
x="full cycle",
y="discharge capacity (mAh/g)" if normalized else "discharge capacity (mAh)",
source=cycle_summary,
marker="triangle",
fill_color="white",
hatch_color=palette[2],
line_width=2,
Expand Down Expand Up @@ -564,7 +648,7 @@ def double_axes_echem_plot(

peaks, _ = find_peaks(dvdq_array, prominence=5)
peak_locs = group.iloc[peaks]
p2.circle(x=x, y=y, source=peak_locs)
p2.scatter(x=x, y=y, source=peak_locs)

if ind == 0:
lines.append(line)
Expand Down Expand Up @@ -614,9 +698,10 @@ def double_axes_echem_plot(
p.js_on_event(DoubleTap, CustomJS(args=dict(p=p), code="p.reset.emit()"))

if mode == "dQ/dV":
grid = [[p1, p2], [xaxis_select]]
grid = gridplot([[p1, p2], [xaxis_select]], sizing_mode="scale_width", merge_tools=False)

elif mode == "dV/dQ":
grid = [[p1], [p2]]
grid = gridplot([[p1], [p2]], sizing_mode="scale_width", merge_tools=False)
elif mode == "final capacity":
if cycle_summary is not None:
save_data = Button(label="Download .csv", button_type="primary", width_policy="min")
Expand All @@ -625,11 +710,13 @@ def double_axes_echem_plot(
code=GENERATE_CSV_CALLBACK,
)
save_data.js_on_click(save_data_callback)
grid = [[save_data], [p3]]
grid = gridplot([[save_data], [p3]], sizing_mode="scale_width", merge_tools=False)
else:
warnings.warn("Unable to generate cycle summary plot for this dataset.")
return None
else:
grid = [[p1], [xaxis_select], [yaxis_select]]
grid = gridplot(
[[p1], [xaxis_select], [yaxis_select]], sizing_mode="scale_width", merge_tools=False
)

return gridplot(grid, sizing_mode="scale_width", toolbar_location="below")
return grid
Loading
Loading