Add context managers for implot#473
Conversation
|
(I will add context managers to implot3d in a different PR, once this one is OK) |
|
I'm back from my weekend. Thanks a lot for this PR and for your involvement :-) This is a nice idea, and I really appreciate the care you put into documenting the new API. Since this adds a user-facing API, I'd like to ask for a few changes to get it production-ready. Please bear with me, there are several of them, but most are quick, and the core of your PR is solid: the context managers themselves are well designed. Here's the list, roughly in order: 1. Rebase on the latest Your branch is currently based on git fetch upstream # or your remote pointing to pthom/imgui_bundle
git rebase upstream/mainI tried the rebase locally to gauge the risk, and the good news is that your context managers just forward their arguments to the underlying One thing to verify after rebasing: ImPlot v1.0 (Apr 2026) reworked the 2. Fix the mypy errors flagged by CI See this CI run: This comes from the class _PushColormap:
"""Internal, do not call this directly."""
def __init__(self, cmap_or_name: implot.Colormap | str) -> None:
self.cmap_or_name = cmap_or_name
def __enter__(self) -> _PushColormap:
implot.push_colormap(self.cmap_or_name)
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
implot.pop_colormap()
def __repr__(self) -> str:
return f"{self.__class__.__qualname__}()"
def push_colormap(cmap_or_name: implot.Colormap | str) -> _PushColormap:
"""Pushes a colormap onto the ImPlot stack, by enum (implot.Colormap_) or by name.
Automatically pops it at end.
Examples:
>>> with implot_ctx.push_colormap(implot.Colormap_.deep):
... ...
"""
return _PushColormap(cmap_or_name)3. Register Right now the module is added but never exposed, so from imgui_bundle import implot_ctx as implot_ctx # noqa: E402
__all__.extend(["implot", "implot_ctx"]) # replaces the existing __all__.extend(["implot"])4. Make the doc clearer about the boolean return (at the top of implot_ctx.py) Your examples already show 5. Trim the repeated body in the The same For these push/pop context managers the value is entirely in the Examples:
>>> with implot_ctx.push_colormap(implot.Colormap_.deep):
... ... # plot as usualNo need to invent a worked body for each one. Please keep the real example bodies on 6. Changes I'll do myself after your changes On my side, and after you made these changes, I'll take care of updating the demos to showcase both the classic and the context-manager style ( Also, I'll probably add a small test under Thanks again, this is a welcome addition. Let me know if anything is unclear or if you'd like me to take any of these items off your plate. |
❤️
Don't worry, I know what I'm getting into when I make a proposal 👍 Thanks for your very detailed review.
[...]
|
|
Thanks for this new round, it addressed most of the review nicely: the mypy fix, the doc note at the top of While preparing the follow-up work (demos, doc, tests, see below), I found one real issue and a few typos, so here is a (hopefully last!) round of changes on your side: 1. Right now it sits in the The reason is subtle: at that point of The fix is to place it at the end of the if has_submodule("implot"):
from imgui_bundle._imgui_bundle import implot as implot
_publish("implot", implot)
__all__.extend(["implot"])
# ... (existing Flag types lines) ...
from imgui_bundle import implot_ctx as implot_ctx # noqa: E402
__all__.extend(["implot_ctx"])2. Small docstring typos in
3. Please squash everything into one commit The branch currently has 4 commits including a merge. Since a merge commit makes git fetch upstream # "upstream" or your remote name, pointing to pthom/imgui_bundle
git reset --soft $(git merge-base upstream/main HEAD)
git commit -m "Add context managers for implot (implot_ctx)"
git push --force-with-leaseAnd a tip for next time (e.g. the On my side, I have the follow-up ready on a local branch, to be merged right after your PR. Sharing it here since it can serve as a model for the Demo (in def demo_implot_begin_plot():
# implot_ctx.begin_plot calls implot.end_plot() automatically.
# Testing `if plot:` is required: the plot may be collapsed or clipped.
x = np.arange(0, np.pi * 4, 0.01)
y = np.cos(x + imgui.get_time() * 4)
with implot_ctx.begin_plot("Wave", immapp.em_to_vec2(25, 15)) as plot:
if plot:
implot.setup_axes("x", "y")
implot.plot_line("cos", x, y)
def demo_implot_push_pop():
# The push_* context managers pop automatically: no need to test their value
x = np.arange(0, np.pi * 4, 0.01)
y = np.sin(x)
with implot_ctx.push_style_color(implot.Col_.plot_bg, ImVec4(0.3, 0.6, 0.3, 1.0)):
with implot_ctx.push_colormap(implot.Colormap_.cool):
with implot_ctx.begin_plot("Styled plot", immapp.em_to_vec2(25, 15)) as plot:
if plot:
implot.plot_line("sin", x, y)Doc: a new subsection "Python: context managers (implot_ctx)" in Tests: two smoke tests. A headless one in def test_implot_ctx_headless() -> None:
# We skip windows, see note at the top of lg_imgui_bundle_test.py
if sys.platform == "win32":
return
from imgui_bundle import imgui, implot, implot_ctx
imgui_context = imgui.create_context()
with implot_ctx.create_context():
# push/pop pairs which do not require a running frame
with implot_ctx.push_style_color(implot.Col_.plot_bg, (1.0, 0.0, 0.0, 1.0)):
pass
with implot_ctx.push_style_var(implot.StyleVar_.plot_border_size, 2.0):
pass
with implot_ctx.push_colormap(implot.Colormap_.deep):
pass
with implot_ctx.push_colormap("Paired"): # colormap by name
pass
imgui.destroy_context(imgui_context)And a GUI one in import numpy as np
from imgui_bundle import hello_imgui, imgui, implot, implot_ctx
def test_implot_ctx_gui() -> None:
results = {}
def gui() -> None:
x = np.arange(0, 10, 0.1)
y = np.sin(x)
with implot_ctx.begin_plot("Plot") as plot:
results["plot_visible"] = bool(plot)
if plot:
implot.plot_line("sin", x, y)
with implot_ctx.push_plot_clip_rect():
pass
with implot_ctx.begin_subplots("Subplots", 1, 2, hello_imgui.em_to_vec2(30, 10)) as subplots:
results["subplots_visible"] = bool(subplots)
if subplots:
for _ in range(2):
with implot_ctx.begin_plot("##sub") as plot:
if plot:
implot.plot_line("sin", x, y)
if imgui.get_frame_count() == 3:
hello_imgui.get_runner_params().app_shall_exit = True
# implot_ctx.create_context() is also exercised in real conditions
# (hello_imgui.run does not create an implot context by itself)
with implot_ctx.create_context():
hello_imgui.run(gui)
assert results["plot_visible"], "begin_plot should be visible in a fresh window"
assert results["subplots_visible"], "begin_subplots should be visible in a fresh window"To run them locally: About your kind offer to provide snippets for the manual: no need, the above covers it, thanks! So: once the |
Hi there !
Adding context managers, as discussed in #472.
Do tell if things are not to your liking.
About tests
I don't know how to add tests, as I have no idea how to use the Dear ImGui Test Engine with implot specifically.
I tested on a small sample project:
Note1: yes, I know immap has a
with_implotparam, this is done for the testNote2: yes, I know I should'nt be building np.arrays in iterating code