From 06451d8c9cf9b9fe09c2f008461fb69204e9fc3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jaworski?= Date: Tue, 16 Apr 2019 13:42:29 +0200 Subject: [PATCH 1/6] core: add plot_histogram widget and fix plot_lines docstring (with visual example) --- conftest.py | 11 +++- doc/source/gen_example.py | 14 +++-- imgui/core.pyx | 107 +++++++++++++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 5 deletions(-) diff --git a/conftest.py b/conftest.py index 05540c33..3a1a6bbc 100644 --- a/conftest.py +++ b/conftest.py @@ -18,6 +18,13 @@ def project_path(*paths): return os.path.join(PROJECT_ROOT_DIR, *paths) +def _ns(locals_, globals_): + ns = {} + ns.update(locals_) + ns.update(globals_) + return ns + + class SphinxDoc(pytest.File): def __init__(self, path, parent): # yuck! @@ -86,8 +93,10 @@ def exec_snippet(self, source): imgui.new_frame() + exec_ns = _ns(locals(), globals()) + try: - exec(code, locals(), globals()) + exec(code, exec_ns, exec_ns) except Exception as err: # note: quick and dirty way to annotate sources with error marker lines = source.split('\n') diff --git a/doc/source/gen_example.py b/doc/source/gen_example.py index 2cb3192f..5b6f4417 100644 --- a/doc/source/gen_example.py +++ b/doc/source/gen_example.py @@ -74,6 +74,13 @@ def filter_source_lines(source_lines): ] +def _ns(locals_, globals_): + ns = {} + ns.update(locals_) + ns.update(globals_) + return ns + + def split_sources(source): source_lines = filter_source_lines(source.split("\n")) @@ -131,8 +138,10 @@ def render_snippet( impl = GlfwRenderer(window) + exec_ns = _ns(globals(), locals()) + if init_source: - exec(init_code, locals(), globals()) + exec(init_code, exec_ns) glfw.poll_events() @@ -181,8 +190,7 @@ def render_snippet( pivot_x=0.5, pivot_y=0.5 ) - - exec(frame_code, locals(), globals()) + exec(frame_code, exec_ns) gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, offscreen_fb) diff --git a/imgui/core.pyx b/imgui/core.pyx index 53bcb2c1..24ad4ef5 100644 --- a/imgui/core.pyx +++ b/imgui/core.pyx @@ -5224,7 +5224,7 @@ def plot_lines( Args: label (str): A plot label that will be displayed on the plot's right - side. If you want the label to be visible, add :code:`"##"` + side. If you want the label to be invisible, add :code:`"##"` before the label's text: :code:`"my_label" -> "##my_label"` values (array of floats): the y-values. @@ -5250,6 +5250,20 @@ def plot_lines( entire array. * **stride** (*int*): Number of bytes to move to read next element. + .. visual-example:: + :auto_layout: + :width: 400 + :height: 130 + + from array import array + from math import sin + + plot_values = array('f', [sin(x * 0.1) for x in range(100)]) + + imgui.begin("Plot example") + imgui.plot_lines("Sin(t)", plot_values) + imgui.end() + .. wraps:: void PlotLines( const char* label, const float* values, int values_count, @@ -5284,6 +5298,97 @@ def plot_lines( ) +def plot_histogram( + str label not None, + const float[:] values not None, + int values_count = -1, + int values_offset = 0, + str overlay_text = None, + float scale_min = FLT_MAX, + float scale_max = FLT_MAX, + graph_size = (0, 0), + int stride = sizeof(float), + ): + """ + Plot a histogram of float values. + + Args: + label (str): A plot label that will be displayed on the plot's right + side. If you want the label to be invisible, add :code:`"##"` + before the label's text: :code:`"my_label" -> "##my_label"` + + values (array of floats): the y-values. + It must be a type that supports Cython's Memoryviews, + (See: http://docs.cython.org/en/latest/src/userguide/memoryviews.html) + for example a numpy array. + + overlay_text (str or None, optional): Overlay text. + + scale_min (float, optional): y-value at the bottom of the plot. + scale_max (float, optional): y-value at the top of the plot. + + graph_size (tuple of two floats, optional): plot size in pixels. + **Note:** In ImGui 1.49, (-1,-1) will NOT auto-size the plot. + To do that, use :func:`get_content_region_available` and pass + in the right size. + + **Note:** These low-level parameters are exposed if needed for + performance: + + * **values_offset** (*int*): Index of first element to display + * **values_count** (*int*): Number of values to display. -1 will use the + entire array. + * **stride** (*int*): Number of bytes to move to read next element. + + .. visual-example:: + :auto_layout: + :width: 400 + :height: 130 + + from array import array + from random import random + + histogram_values = array('f', [random() for _ in range(20)]) + + imgui.begin("Plot example") + imgui.plot_histogram("histogram(random())", histogram_values) + imgui.end() + + .. wraps:: + void PlotHistogram( + const char* label, const float* values, int values_count, + # note: optional + int values_offset, + const char* overlay_text, + float scale_min, + float scale_max, + ImVec2 graph_size, + int stride + ) + """ + if values_count == -1: + values_count = values.shape[0] + + # Would be nicer as something like + # _bytes(overlay_text) if overlay_text is not None else NULL + # but then Cython complains about either types or pointers to temporary references. + cdef const char* overlay_text_ptr = NULL + cdef bytes overlay_text_b + + if overlay_text is not None: + overlay_text_b = _bytes(overlay_text) # must be assigned to a variable + overlay_text_ptr = overlay_text_b # auto-convert bytes to char* + + cimgui.PlotHistogram( + _bytes(label), &values[0], values_count, + values_offset, + overlay_text_ptr, + scale_min, scale_max, + _cast_tuple_ImVec2(graph_size), + stride + ) + + def set_item_default_focus(): """Make last item the default focused item of a window. Please use instead of "if (is_window_appearing()) set_scroll_here()" to signify "default item". From 1076f66e938fab18f7de553c86a2a87b3580c1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jaworski?= Date: Tue, 16 Apr 2019 13:42:54 +0200 Subject: [PATCH 2/6] docs: add plot example --- doc/examples/plots.py | 95 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 doc/examples/plots.py diff --git a/doc/examples/plots.py b/doc/examples/plots.py new file mode 100644 index 00000000..0d0a6d66 --- /dev/null +++ b/doc/examples/plots.py @@ -0,0 +1,95 @@ +from array import array +from math import sin, pi +from random import random + +import glfw +import OpenGL.GL as gl +import imgui +from imgui.integrations.glfw import GlfwRenderer +from time import time + + +C = .01 +L = int(pi * 2 * 100) + + +def main(): + window = impl_glfw_init() + imgui.create_context() + impl = GlfwRenderer(window) + + plot_values = array('f', [sin(x * C) for x in range(L)]) + histogram_values = array('f', [random() for _ in range(20)]) + + while not glfw.window_should_close(window): + glfw.poll_events() + impl.process_inputs() + + imgui.new_frame() + + imgui.begin("Plot example") + imgui.plot_lines( + "Sin(t)", + plot_values, + overlay_text="SIN() over time", + # offset by one item every milisecond, plot values + # buffer its end wraps around + values_offset=int(time() * 100) % L, + # 0=autoscale => (0, 50) = (autoscale width, 50px height) + graph_size=(0, 50), + ) + + imgui.plot_histogram( + "histogram(random())", + histogram_values, + overlay_text="random histogram", + # offset by one item every milisecond, plot values + # buffer its end wraps around + graph_size=(0, 50), + ) + + + imgui.end() + + gl.glClearColor(1., 1., 1., 1) + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + + imgui.render() + impl.render(imgui.get_draw_data()) + glfw.swap_buffers(window) + + impl.shutdown() + glfw.terminate() + + +def impl_glfw_init(): + width, height = 1280, 720 + window_name = "minimal ImGui/GLFW3 example" + + if not glfw.init(): + print("Could not initialize OpenGL context") + exit(1) + + # OS X supports only forward-compatible core profiles from 3.2 + glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) + glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) + glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) + + glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, gl.GL_TRUE) + + # Create a windowed mode window and its OpenGL context + window = glfw.create_window( + int(width), int(height), window_name, None, None + ) + glfw.make_context_current(window) + + if not window: + glfw.terminate() + print("Could not initialize Window") + exit(1) + + return window + + +if __name__ == "__main__": + main() From 912bb4743227e62ece52fe39eda016883fa48983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jaworski?= Date: Tue, 16 Apr 2019 13:44:00 +0200 Subject: [PATCH 3/6] docs: add generated visual examples for plots --- doc/source/visual_examples/imgui.core.plot_histogram_0.png | 3 +++ doc/source/visual_examples/imgui.core.plot_lines_0.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 doc/source/visual_examples/imgui.core.plot_histogram_0.png create mode 100644 doc/source/visual_examples/imgui.core.plot_lines_0.png diff --git a/doc/source/visual_examples/imgui.core.plot_histogram_0.png b/doc/source/visual_examples/imgui.core.plot_histogram_0.png new file mode 100644 index 00000000..6ef711a6 --- /dev/null +++ b/doc/source/visual_examples/imgui.core.plot_histogram_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73bddd80aa02af22800c7e95f48ab677009d0a8843ce02daa098b013b483d8f8 +size 2734 diff --git a/doc/source/visual_examples/imgui.core.plot_lines_0.png b/doc/source/visual_examples/imgui.core.plot_lines_0.png new file mode 100644 index 00000000..38482067 --- /dev/null +++ b/doc/source/visual_examples/imgui.core.plot_lines_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d83fea8d17c432827a28d8d9a6a287df5d979eb4738936144431712ca14ba862 +size 4885 From 51eef9913f2350183d0f714b388a5202812132a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jaworski?= Date: Tue, 16 Apr 2019 13:56:44 +0200 Subject: [PATCH 4/6] docs: mark PlotHistogram as implemented [skip ci] --- imgui/cimgui.pxd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui/cimgui.pxd b/imgui/cimgui.pxd index c60566b0..f9cea5f2 100644 --- a/imgui/cimgui.pxd +++ b/imgui/cimgui.pxd @@ -645,7 +645,7 @@ cdef extern from "imgui.h" namespace "ImGui": int stride # = sizeof(float)) ) except + - void PlotHistogram( # ✗ + void PlotHistogram( # ✓ const char* label, const float* values, int values_count, # note: optional int values_offset, const char* overlay_text, float scale_min, From 55949a34aab74277777e21965529cbb3faba0f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jaworski?= Date: Sun, 19 May 2019 10:41:29 +0200 Subject: [PATCH 5/6] get rid of ns hacks and don't use list comprehensions --- conftest.py | 11 +---------- doc/source/gen_example.py | 13 ++----------- imgui/core.pyx | 9 +++++++-- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/conftest.py b/conftest.py index 3a1a6bbc..05540c33 100644 --- a/conftest.py +++ b/conftest.py @@ -18,13 +18,6 @@ def project_path(*paths): return os.path.join(PROJECT_ROOT_DIR, *paths) -def _ns(locals_, globals_): - ns = {} - ns.update(locals_) - ns.update(globals_) - return ns - - class SphinxDoc(pytest.File): def __init__(self, path, parent): # yuck! @@ -93,10 +86,8 @@ def exec_snippet(self, source): imgui.new_frame() - exec_ns = _ns(locals(), globals()) - try: - exec(code, exec_ns, exec_ns) + exec(code, locals(), globals()) except Exception as err: # note: quick and dirty way to annotate sources with error marker lines = source.split('\n') diff --git a/doc/source/gen_example.py b/doc/source/gen_example.py index 5b6f4417..289dc6c7 100644 --- a/doc/source/gen_example.py +++ b/doc/source/gen_example.py @@ -74,13 +74,6 @@ def filter_source_lines(source_lines): ] -def _ns(locals_, globals_): - ns = {} - ns.update(locals_) - ns.update(globals_) - return ns - - def split_sources(source): source_lines = filter_source_lines(source.split("\n")) @@ -138,10 +131,8 @@ def render_snippet( impl = GlfwRenderer(window) - exec_ns = _ns(globals(), locals()) - if init_source: - exec(init_code, exec_ns) + exec(init_code, locals(), globals()) glfw.poll_events() @@ -190,7 +181,7 @@ def render_snippet( pivot_x=0.5, pivot_y=0.5 ) - exec(frame_code, exec_ns) + exec(frame_code, locals(), globals()) gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, offscreen_fb) diff --git a/imgui/core.pyx b/imgui/core.pyx index 24ad4ef5..5573413a 100644 --- a/imgui/core.pyx +++ b/imgui/core.pyx @@ -5258,7 +5258,10 @@ def plot_lines( from array import array from math import sin - plot_values = array('f', [sin(x * 0.1) for x in range(100)]) + plot_values = array('f') + for x in range(100): + plot_values.append(sin(x * 0.1)) + imgui.begin("Plot example") imgui.plot_lines("Sin(t)", plot_values) @@ -5348,7 +5351,9 @@ def plot_histogram( from array import array from random import random - histogram_values = array('f', [random() for _ in range(20)]) + histogram_values = array('f') + for _ in range(20): + histogram_values.append(random()) imgui.begin("Plot example") imgui.plot_histogram("histogram(random())", histogram_values) From 2c7bec6163c3bc966009441193a66802b91e8a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jaworski?= Date: Sun, 19 May 2019 11:31:00 +0200 Subject: [PATCH 6/6] print traceback on error because it is possible that we've missed some logging when exception is raised during exception handling --- conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conftest.py b/conftest.py index 05540c33..6613eecc 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import os import sys +from traceback import print_exc import pytest from inspect import currentframe, getframeinfo @@ -90,6 +91,7 @@ def exec_snippet(self, source): exec(code, locals(), globals()) except Exception as err: # note: quick and dirty way to annotate sources with error marker + print_exc() lines = source.split('\n') lines.insert(sys.exc_info()[2].tb_next.tb_lineno, "^^^") self.code = "\n".join(lines)