From 2df9ee455b7dc6bea22786ed9b2623f772ece18d Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 11 Oct 2022 01:05:15 -0400 Subject: [PATCH 1/6] some basic ideas for sub_viewports --- fastplotlib/subplot.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/fastplotlib/subplot.py b/fastplotlib/subplot.py index 374198751..6d9a384e1 100644 --- a/fastplotlib/subplot.py +++ b/fastplotlib/subplot.py @@ -61,6 +61,26 @@ def __init__( self.renderer.add_event_handler(self._produce_rect, "resize") + self.sub_viewports: Dict[str, Dict[str, Union[pygfx.Viewport, int]]] = dict() + + # self.add_sub_viewport("right", size=60) + + def add_sub_viewport(self, position: str, size: int): + # TODO: Generalize to all directions + valid_positions = ["right"] # only support right for now + if position not in valid_positions: + raise ValueError(f"`position` argument must be one of {valid_positions}") + + if position in self.sub_viewports.keys(): + raise KeyError(f"sub_viewport already exists at position: {position}") + + self.sub_viewports[position] = {"viewport": pygfx.Viewport(self.renderer)} + self.sub_viewports[position]["size"] = size + + # TODO: add camera and controller to sub_viewports + # self.sub_viewports[position]['camera'] + # self.sub_viewports[position]['controller'] + def _produce_rect(self, *args):#, w, h): i, j = self.position @@ -71,14 +91,25 @@ def _produce_rect(self, *args):#, w, h): self.viewport.rect = [ ((w / self.ncols) + ((j - 1) * (w / self.ncols))) + spacing, ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, - (w / self.ncols) - spacing, + (w / self.ncols) - spacing - self.sub_viewports["right"]["size"], (h / self.nrows) - spacing ] + # TODO: Generalize to all directions + for svp in self.sub_viewports.keys(): + self.sub_viewports["right"]["viewport"].rect = [ + (w / self.ncols) - self.sub_viewports["right"]["size"], + ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, + (w / self.ncols) - spacing, + (h / self.nrows) - spacing + ] + def animate(self, canvas_dims: Tuple[int, int] = None): self.controller.update_camera(self.camera) self.viewport.render(self.scene, self.camera) + # self.sub_viewports["right"]["viewport"].render() + for f in self._animate_funcs: f() From 75a1ab47e81b5473f00073ea2b1fde227db1516a Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Fri, 18 Nov 2022 23:46:11 -0500 Subject: [PATCH 2/6] viewports within subplots working for right side --- examples/gridplot_simple.py | 42 ++++++++ fastplotlib/__init__.py | 1 + fastplotlib/subplot.py | 205 +++++++++++++++++++++++++++++++++--- 3 files changed, 235 insertions(+), 13 deletions(-) create mode 100644 examples/gridplot_simple.py diff --git a/examples/gridplot_simple.py b/examples/gridplot_simple.py new file mode 100644 index 000000000..82a49b053 --- /dev/null +++ b/examples/gridplot_simple.py @@ -0,0 +1,42 @@ +import numpy as np +from wgpu.gui.auto import WgpuCanvas +import pygfx as gfx +from fastplotlib import GridPlot, Line, Scatter, Histogram +from fastplotlib import Image, GridPlot, run + +# GridPlot of shape 2 x 3 +grid_plot = GridPlot(shape=(2, 3)) + +image_graphics = list() + +hist_data1 = np.random.normal(0, 256, 2048) +hist_data2 = np.random.poisson(0, 256) + +# Make a random image graphic for each subplot +for i, subplot in enumerate(grid_plot): + img = np.random.rand(512, 512) * 255 + ig = Image(data=img, vmin=0, vmax=255, cmap='gnuplot2') + image_graphics.append(ig) + + # add the graphic to the subplot + subplot.add_graphic(ig) + + histogram = Histogram(data=hist_data1, bins=100) + subplot.docked_viewports["right"].add_graphic(histogram) + # subplot.docked_viewports["right"]._refresh_camera() + + +# Define a function to update the image graphics +# with new randomly generated data +def set_random_frame(): + for ig in image_graphics: + new_data = np.random.rand(512, 512) * 255 + ig.update_data(data=new_data) + + +# add the animation +grid_plot.add_animations([set_random_frame]) + +grid_plot.show() + +run() diff --git a/fastplotlib/__init__.py b/fastplotlib/__init__.py index 23c7f3b13..dd2c7d7ed 100644 --- a/fastplotlib/__init__.py +++ b/fastplotlib/__init__.py @@ -3,6 +3,7 @@ from .subplot import Subplot from .plot import Plot from pathlib import Path +from wgpu.gui.auto import run with open(Path(__file__).parent.joinpath("VERSION"), "r") as f: diff --git a/fastplotlib/subplot.py b/fastplotlib/subplot.py index 6d9a384e1..3fc4a8b53 100644 --- a/fastplotlib/subplot.py +++ b/fastplotlib/subplot.py @@ -4,6 +4,7 @@ from typing import * from wgpu.gui.auto import WgpuCanvas from warnings import warn +import numpy as np class Subplot: @@ -63,8 +64,16 @@ def __init__( self.sub_viewports: Dict[str, Dict[str, Union[pygfx.Viewport, int]]] = dict() + self.docked_viewports = dict() + for pos in ["left", "top", "right", "bottom"]: + self.docked_viewports[pos] = _AnchoredViewport(self, pos, size=0) + + self.docked_viewports["right"].size = 60 + # self.add_sub_viewport("right", size=60) + self._produce_rect() + def add_sub_viewport(self, position: str, size: int): # TODO: Generalize to all directions valid_positions = ["right"] # only support right for now @@ -81,28 +90,49 @@ def add_sub_viewport(self, position: str, size: int): # self.sub_viewports[position]['camera'] # self.sub_viewports[position]['controller'] - def _produce_rect(self, *args):#, w, h): + def get_rect_vector(self): i, j = self.position - w, h = self.renderer.logical_size spacing = 2 # spacing in pixels - self.viewport.rect = [ + return np.array([ ((w / self.ncols) + ((j - 1) * (w / self.ncols))) + spacing, ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, - (w / self.ncols) - spacing - self.sub_viewports["right"]["size"], + (w / self.ncols) - spacing, (h / self.nrows) - spacing - ] + ]) - # TODO: Generalize to all directions - for svp in self.sub_viewports.keys(): - self.sub_viewports["right"]["viewport"].rect = [ - (w / self.ncols) - self.sub_viewports["right"]["size"], - ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, - (w / self.ncols) - spacing, - (h / self.nrows) - spacing - ] + def _produce_rect(self, *args):#, w, h): + i, j = self.position + + w, h = self.renderer.logical_size + + spacing = 2 # spacing in pixels + + rect = self.get_rect_vector() + + for dv in self.docked_viewports.values(): + rect = rect - dv.get_parent_rect_removal() + # print(dv.get_parent_rect_removal()) + + self.viewport.rect = rect + + # self.viewport.rect = np.array([ + # ((w / self.ncols) + ((j - 1) * (w / self.ncols))) + spacing, + # ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, + # (w / self.ncols) - spacing - self.sub_viewports["right"]["size"], + # (h / self.nrows) - spacing + # ]) + + # # # TODO: Generalize to all directions + # for svp in self.sub_viewports.keys(): + # self.sub_viewports["right"]["viewport"].rect = [ + # (w / self.ncols) - self.sub_viewports["right"]["size"], + # ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, + # (w / self.ncols) - spacing, + # (h / self.nrows) - spacing + # ] def animate(self, canvas_dims: Tuple[int, int] = None): self.controller.update_camera(self.camera) @@ -113,6 +143,9 @@ def animate(self, canvas_dims: Tuple[int, int] = None): for f in self._animate_funcs: f() + for dv in self.docked_viewports.values(): + dv.render() + def add_animations(self, funcs: List[callable]): self._animate_funcs += funcs @@ -171,3 +204,149 @@ def set_grid_visibility(self, visible: bool): def remove_graphic(self, graphic): self.scene.remove(graphic.world_object) + + +class _AnchoredViewport: + _valid_positions = [ + "right", + "left", + "top", + "bottom" + ] + # define the index of the parent.viewport.rect vector + # where this viewport's size should be subtracted + # from the parent.viewport.rect + _remove_from_parent = { + "left": 0, + "bottom": 1, + "right": 2, + "top": 3 + } + + _add_to_parent = { + "left": 2, + "bottom": 3, + "right": 0, + "top": 0 + } + + # the vector to encode this viewport's position + _viewport_rect_pos = { + "left": 0, + "bottom": 0, + "right": 1, + "top": 0 + } + + def __init__( + self, + parent: Subplot, + position: str, + size: int, + camera: str = "2d", + ): + if position not in self._valid_positions: + raise ValueError(f"the `position` of an AnchoredViewport must be one of: {self._valid_positions}") + + self.parent = parent + self.position = position + self._size = size + + self.viewport = pygfx.Viewport(self.parent.renderer) + + self.scene = pygfx.Scene() + self.scene.add( + pygfx.Background(None, pygfx.BackgroundMaterial((0.2, 0.0, 0, 1), (0, 0.0, 0.2, 1))) + ) + + self.camera = pygfx.OrthographicCamera() + self.controller = pygfx.PanZoomController() + + self.controller.add_default_event_handlers( + self.viewport, + self.camera + ) + + self._produce_rect() + self.parent.renderer.add_event_handler(self._produce_rect, "resize") + + @property + def size(self) -> int: + return self._size + + @size.setter + def size(self, s: int): + self._size = s + self._produce_rect() + + def _produce_rect(self, *args): + if self.size == 0: + self.viewport.rect = None + return + + i, j = self.parent.position + w, h = self.parent.renderer.logical_size + + spacing = 2 # spacing in pixels + + parent_rect = self.parent.get_rect_vector() + + v = np.zeros(4) + v[self._viewport_rect_pos[self.position]] = 1 + + # self.viewport.rect = parent_rect - (v * self.size) + + self.viewport.rect = [ + (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + (w / self.parent.ncols) - self.size, + ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, + self.size, + (h / self.parent.nrows) - spacing + ] + + print(self.viewport.rect) + + def get_parent_rect_removal(self): + ix = self._remove_from_parent[self.position] + ix2 = self._add_to_parent[self.position] + + v = np.zeros(4) + v[ix] = 1 + + # v[ix2] = - 1 + + return v * self.size + + def render(self): + if self.size == 0: + return + + # self._produce_rect() + self.controller.update_camera(self.camera) + self.viewport.render(self.scene, self.camera) + + def add_graphic(self, graphic, center: bool = True): + self.scene.add(graphic.world_object) + + if center: + self.center_graphic(graphic) + + def _refresh_camera(self): + self.controller.update_camera(self.camera) + # if sum(self.renderer.logical_size) > 0: + # scene_lsize = self.viewport.rect[2], self.viewport.rect[3] + # else: + # scene_lsize = (1, 1) + # + # self.camera.set_view_size(*scene_lsize) + self.camera.update_projection_matrix() + + def center_graphic(self, graphic, zoom: float = 1.3): + if not isinstance(self.camera, pygfx.OrthographicCamera): + warn("`center_graphic()` not yet implemented for `PerspectiveCamera`") + return + + self._refresh_camera() + + self.controller.show_object(self.camera, graphic.world_object) + + self.controller.zoom(zoom) From 0900bde6b732218a05b36b21cd0cae3f55599919 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sat, 19 Nov 2022 01:28:08 -0500 Subject: [PATCH 3/6] 'docked viewports' within subplots now working well on all sides --- examples/gridplot_simple.py | 14 ++- fastplotlib/subplot.py | 188 ++++++++++++++++-------------------- 2 files changed, 94 insertions(+), 108 deletions(-) diff --git a/examples/gridplot_simple.py b/examples/gridplot_simple.py index 82a49b053..90c1feb99 100644 --- a/examples/gridplot_simple.py +++ b/examples/gridplot_simple.py @@ -3,6 +3,7 @@ import pygfx as gfx from fastplotlib import GridPlot, Line, Scatter, Histogram from fastplotlib import Image, GridPlot, run +from math import sin, cos, radians # GridPlot of shape 2 x 3 grid_plot = GridPlot(shape=(2, 3)) @@ -22,9 +23,18 @@ subplot.add_graphic(ig) histogram = Histogram(data=hist_data1, bins=100) - subplot.docked_viewports["right"].add_graphic(histogram) - # subplot.docked_viewports["right"]._refresh_camera() + histogram.world_object.rotation.w = cos(radians(45)) + histogram.world_object.rotation.z = sin(radians(45)) + histogram.world_object.scale.y = 1 + histogram.world_object.scale.x = 8 + + for dv_position in ["right", "top", "bottom", "left"]: + h2 = Histogram(data=hist_data1, bins=100) + + subplot.docked_viewports[dv_position].size = 60 + subplot.docked_viewports[dv_position].add_graphic(h2) +# # Define a function to update the image graphics # with new randomly generated data diff --git a/fastplotlib/subplot.py b/fastplotlib/subplot.py index 3fc4a8b53..e078b28ed 100644 --- a/fastplotlib/subplot.py +++ b/fastplotlib/subplot.py @@ -60,37 +60,15 @@ def __init__( self._animate_funcs = list() - self.renderer.add_event_handler(self._produce_rect, "resize") - - self.sub_viewports: Dict[str, Dict[str, Union[pygfx.Viewport, int]]] = dict() + self.renderer.add_event_handler(self.set_viewport_rect, "resize") self.docked_viewports = dict() for pos in ["left", "top", "right", "bottom"]: - self.docked_viewports[pos] = _AnchoredViewport(self, pos, size=0) - - self.docked_viewports["right"].size = 60 - - # self.add_sub_viewport("right", size=60) - - self._produce_rect() - - def add_sub_viewport(self, position: str, size: int): - # TODO: Generalize to all directions - valid_positions = ["right"] # only support right for now - if position not in valid_positions: - raise ValueError(f"`position` argument must be one of {valid_positions}") - - if position in self.sub_viewports.keys(): - raise KeyError(f"sub_viewport already exists at position: {position}") + self.docked_viewports[pos] = _DockedViewport(self, pos, size=0) - self.sub_viewports[position] = {"viewport": pygfx.Viewport(self.renderer)} - self.sub_viewports[position]["size"] = size + self.set_viewport_rect() - # TODO: add camera and controller to sub_viewports - # self.sub_viewports[position]['camera'] - # self.sub_viewports[position]['controller'] - - def get_rect_vector(self): + def get_rect(self): i, j = self.position w, h = self.renderer.logical_size @@ -103,43 +81,18 @@ def get_rect_vector(self): (h / self.nrows) - spacing ]) - def _produce_rect(self, *args):#, w, h): - i, j = self.position - - w, h = self.renderer.logical_size - - spacing = 2 # spacing in pixels - - rect = self.get_rect_vector() + def set_viewport_rect(self, *args): + rect = self.get_rect() for dv in self.docked_viewports.values(): - rect = rect - dv.get_parent_rect_removal() - # print(dv.get_parent_rect_removal()) + rect = rect + dv.get_parent_rect_adjust() self.viewport.rect = rect - # self.viewport.rect = np.array([ - # ((w / self.ncols) + ((j - 1) * (w / self.ncols))) + spacing, - # ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, - # (w / self.ncols) - spacing - self.sub_viewports["right"]["size"], - # (h / self.nrows) - spacing - # ]) - - # # # TODO: Generalize to all directions - # for svp in self.sub_viewports.keys(): - # self.sub_viewports["right"]["viewport"].rect = [ - # (w / self.ncols) - self.sub_viewports["right"]["size"], - # ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, - # (w / self.ncols) - spacing, - # (h / self.nrows) - spacing - # ] - def animate(self, canvas_dims: Tuple[int, int] = None): self.controller.update_camera(self.camera) self.viewport.render(self.scene, self.camera) - # self.sub_viewports["right"]["viewport"].render() - for f in self._animate_funcs: f() @@ -206,37 +159,13 @@ def remove_graphic(self, graphic): self.scene.remove(graphic.world_object) -class _AnchoredViewport: +class _DockedViewport: _valid_positions = [ "right", "left", "top", "bottom" ] - # define the index of the parent.viewport.rect vector - # where this viewport's size should be subtracted - # from the parent.viewport.rect - _remove_from_parent = { - "left": 0, - "bottom": 1, - "right": 2, - "top": 3 - } - - _add_to_parent = { - "left": 2, - "bottom": 3, - "right": 0, - "top": 0 - } - - # the vector to encode this viewport's position - _viewport_rect_pos = { - "left": 0, - "bottom": 0, - "right": 1, - "top": 0 - } def __init__( self, @@ -267,8 +196,8 @@ def __init__( self.camera ) - self._produce_rect() - self.parent.renderer.add_event_handler(self._produce_rect, "resize") + self.parent.renderer.add_event_handler(self.set_viewport_rect, "resize") + self.set_viewport_rect() @property def size(self) -> int: @@ -277,9 +206,10 @@ def size(self) -> int: @size.setter def size(self, s: int): self._size = s - self._produce_rect() + self.parent.set_viewport_rect() + self.set_viewport_rect() - def _produce_rect(self, *args): + def _get_rect(self, *args): if self.size == 0: self.viewport.rect = None return @@ -289,38 +219,84 @@ def _produce_rect(self, *args): spacing = 2 # spacing in pixels - parent_rect = self.parent.get_rect_vector() - - v = np.zeros(4) - v[self._viewport_rect_pos[self.position]] = 1 - - # self.viewport.rect = parent_rect - (v * self.size) - - self.viewport.rect = [ - (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + (w / self.parent.ncols) - self.size, - ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, - self.size, - (h / self.parent.nrows) - spacing - ] - - print(self.viewport.rect) + if self.position == "right": + r = [ + (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + (w / self.parent.ncols) - self.size, + ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, + self.size, + (h / self.parent.nrows) - spacing + ] + + elif self.position == "left": + r = [ + (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)), + ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, + self.size, + (h / self.parent.nrows) - spacing + ] + + elif self.position == "top": + r = [ + (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + spacing, + ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, + (w / self.parent.ncols) - spacing, + self.size + ] + + elif self.position == "bottom": + r = [ + (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + spacing, + ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + (h / self.parent.nrows) - self.size, + (w / self.parent.ncols) - spacing, + self.size + ] + else: + raise ValueError("invalid position") - def get_parent_rect_removal(self): - ix = self._remove_from_parent[self.position] - ix2 = self._add_to_parent[self.position] + return r - v = np.zeros(4) - v[ix] = 1 + def set_viewport_rect(self, *args): + rect = self._get_rect() - # v[ix2] = - 1 + self.viewport.rect = rect - return v * self.size + def get_parent_rect_adjust(self): + if self.position == "right": + return np.array([ + 0, # parent subplot x-position is same + 0, + -self.size, # width of parent subplot is `self.size` smaller + 0 + ]) + + elif self.position == "left": + return np.array([ + self.size, # `self.size` added to parent subplot x-position + 0, + -self.size, # width of parent subplot is `self.size` smaller + 0 + ]) + + elif self.position == "top": + return np.array([ + 0, + self.size, # `self.size` added to parent subplot y-position + 0, + -self.size, # height of parent subplot is `self.size` smaller + ]) + + elif self.position == "bottom": + return np.array([ + 0, + 0, # parent subplot y-position is same, + 0, + -self.size, # height of parent subplot is `self.size` smaller + ]) def render(self): if self.size == 0: return - # self._produce_rect() self.controller.update_camera(self.camera) self.viewport.render(self.scene, self.camera) From 4662d3f668e201c5a94f73b67a4239953bd6fc60 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sat, 19 Nov 2022 20:21:52 -0500 Subject: [PATCH 4/6] make viewport rect code more readable --- fastplotlib/subplot.py | 63 ++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/fastplotlib/subplot.py b/fastplotlib/subplot.py index e078b28ed..090f85d9b 100644 --- a/fastplotlib/subplot.py +++ b/fastplotlib/subplot.py @@ -69,16 +69,21 @@ def __init__( self.set_viewport_rect() def get_rect(self): - i, j = self.position - w, h = self.renderer.logical_size + row_ix, col_ix = self.position + width_canvas, height_canvas = self.renderer.logical_size spacing = 2 # spacing in pixels + x_pos = ((width_canvas / self.ncols) + ((col_ix - 1) * (width_canvas / self.ncols))) + spacing + y_pos = ((height_canvas / self.nrows) + ((row_ix - 1) * (height_canvas / self.nrows))) + spacing + width_subplot = (width_canvas / self.ncols) - spacing + height_suplot = (height_canvas / self.nrows) - spacing + return np.array([ - ((w / self.ncols) + ((j - 1) * (w / self.ncols))) + spacing, - ((h / self.nrows) + ((i - 1) * (h / self.nrows))) + spacing, - (w / self.ncols) - spacing, - (h / self.nrows) - spacing + x_pos, + y_pos, + width_subplot, + height_suplot ]) def set_viewport_rect(self, *args): @@ -214,46 +219,38 @@ def _get_rect(self, *args): self.viewport.rect = None return - i, j = self.parent.position - w, h = self.parent.renderer.logical_size + row_ix_parent, col_ix_parent = self.parent.position + width_canvas, height_canvas = self.parent.renderer.logical_size spacing = 2 # spacing in pixels if self.position == "right": - r = [ - (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + (w / self.parent.ncols) - self.size, - ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, - self.size, - (h / self.parent.nrows) - spacing - ] + x_pos = (width_canvas / self.parent.ncols) + ((col_ix_parent - 1) * (width_canvas / self.parent.ncols)) + (width_canvas / self.parent.ncols) - self.size + y_pos = ((height_canvas / self.parent.nrows) + ((row_ix_parent - 1) * (height_canvas / self.parent.nrows))) + spacing + width_viewport = self.size + height_viewport = (height_canvas / self.parent.nrows) - spacing elif self.position == "left": - r = [ - (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)), - ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, - self.size, - (h / self.parent.nrows) - spacing - ] + x_pos = (width_canvas / self.parent.ncols) + ((col_ix_parent - 1) * (width_canvas / self.parent.ncols)) + y_pos = ((height_canvas / self.parent.nrows) + ((row_ix_parent - 1) * (height_canvas / self.parent.nrows))) + spacing + width_viewport = self.size + height_viewport = (height_canvas / self.parent.nrows) - spacing elif self.position == "top": - r = [ - (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + spacing, - ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + spacing, - (w / self.parent.ncols) - spacing, - self.size - ] + x_pos = (width_canvas / self.parent.ncols) + ((col_ix_parent - 1) * (width_canvas / self.parent.ncols)) + spacing + y_pos = ((height_canvas / self.parent.nrows) + ((row_ix_parent - 1) * (height_canvas / self.parent.nrows))) + spacing + width_viewport = (width_canvas / self.parent.ncols) - spacing + height_viewport = self.size elif self.position == "bottom": - r = [ - (w / self.parent.ncols) + ((j - 1) * (w / self.parent.ncols)) + spacing, - ((h / self.parent.nrows) + ((i - 1) * (h / self.parent.nrows))) + (h / self.parent.nrows) - self.size, - (w / self.parent.ncols) - spacing, - self.size - ] + x_pos = (width_canvas / self.parent.ncols) + ((col_ix_parent - 1) * (width_canvas / self.parent.ncols)) + spacing + y_pos = ((height_canvas / self.parent.nrows) + ((row_ix_parent - 1) * (height_canvas / self.parent.nrows))) + (height_canvas / self.parent.nrows) - self.size + width_viewport = (width_canvas / self.parent.ncols) - spacing + height_viewport = self.size else: raise ValueError("invalid position") - return r + return [x_pos, y_pos, width_viewport, height_viewport] def set_viewport_rect(self, *args): rect = self._get_rect() From 021ef62f7289c77d866913625cf914220d2b02bd Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 11 Dec 2022 00:43:44 -0500 Subject: [PATCH 5/6] new PlotArea base class, subclassed by Subplot and DockedViewport, works --- examples/gridplot_simple.py | 13 +- fastplotlib/layouts/_base.py | 177 ++++++++++++++++++++++++++ fastplotlib/layouts/_gridplot.py | 9 +- fastplotlib/layouts/_subplot.py | 212 ++++++------------------------- fastplotlib/plot.py | 6 +- 5 files changed, 229 insertions(+), 188 deletions(-) create mode 100644 fastplotlib/layouts/_base.py diff --git a/examples/gridplot_simple.py b/examples/gridplot_simple.py index 90c1feb99..382f2b486 100644 --- a/examples/gridplot_simple.py +++ b/examples/gridplot_simple.py @@ -1,8 +1,9 @@ import numpy as np from wgpu.gui.auto import WgpuCanvas import pygfx as gfx -from fastplotlib import GridPlot, Line, Scatter, Histogram -from fastplotlib import Image, GridPlot, run +from fastplotlib.layouts import GridPlot +from fastplotlib.graphics import ImageGraphic, LineGraphic, HistogramGraphic +from fastplotlib import run from math import sin, cos, radians # GridPlot of shape 2 x 3 @@ -16,13 +17,13 @@ # Make a random image graphic for each subplot for i, subplot in enumerate(grid_plot): img = np.random.rand(512, 512) * 255 - ig = Image(data=img, vmin=0, vmax=255, cmap='gnuplot2') + ig = ImageGraphic(data=img, vmin=0, vmax=255, cmap='gnuplot2') image_graphics.append(ig) # add the graphic to the subplot subplot.add_graphic(ig) - histogram = Histogram(data=hist_data1, bins=100) + histogram = HistogramGraphic(data=hist_data1, bins=100) histogram.world_object.rotation.w = cos(radians(45)) histogram.world_object.rotation.z = sin(radians(45)) @@ -30,7 +31,7 @@ histogram.world_object.scale.x = 8 for dv_position in ["right", "top", "bottom", "left"]: - h2 = Histogram(data=hist_data1, bins=100) + h2 = HistogramGraphic(data=hist_data1, bins=100) subplot.docked_viewports[dv_position].size = 60 subplot.docked_viewports[dv_position].add_graphic(h2) @@ -45,7 +46,7 @@ def set_random_frame(): # add the animation -grid_plot.add_animations([set_random_frame]) +# grid_plot.add_animations(set_random_frame) grid_plot.show() diff --git a/fastplotlib/layouts/_base.py b/fastplotlib/layouts/_base.py new file mode 100644 index 000000000..0aedcf87e --- /dev/null +++ b/fastplotlib/layouts/_base.py @@ -0,0 +1,177 @@ +from pygfx import Scene, OrthographicCamera, PerspectiveCamera, PanZoomController, OrbitController, \ + Viewport, WgpuRenderer +from wgpu.gui.auto import WgpuCanvas +from warnings import warn +from ..graphics._base import Graphic +from typing import * + + +class PlotArea: + def __init__( + self, + parent, + position: Any, + camera: Union[OrthographicCamera, PerspectiveCamera], + controller: Union[PanZoomController, OrbitController], + scene: Scene, + canvas: WgpuCanvas, + renderer: WgpuRenderer, + name: str = None, + ): + self._parent: PlotArea = parent + self._position = position + + self._scene = scene + self._canvas = canvas + self._renderer = renderer + if parent is None: + self._viewport: Viewport = Viewport(renderer) + else: + self._viewport = Viewport(parent.renderer) + + self._camera = camera + self._controller = controller + + self.controller.add_default_event_handlers( + self.viewport, + self.camera + ) + + self.renderer.add_event_handler(self.set_viewport_rect, "resize") + + self._graphics: List[Graphic] = list() + + self.name = name + + # need to think about how to deal with children better + self.children = list() + + self.set_viewport_rect() + + # several read-only properties + @property + def parent(self): + return self._parent + + @property + def position(self) -> Union[Tuple[int, int], Any]: + """Used by subclass based on its referencing system""" + return self._position + + @property + def scene(self) -> Scene: + return self._scene + + @property + def canvas(self) -> WgpuCanvas: + return self._canvas + + @property + def renderer(self) -> WgpuRenderer: + return self._renderer + + @property + def viewport(self) -> Viewport: + return self._viewport + + @property + def camera(self) -> Union[OrthographicCamera, PerspectiveCamera]: + return self._camera + + @property + def controller(self) -> Union[PanZoomController, OrbitController]: + return self._controller + + def get_rect(self) -> Tuple[float, float, float, float]: + """allows setting the region occupied by the viewport w.r.t. the parent""" + raise NotImplementedError("Must be implemented in subclass") + + def set_viewport_rect(self, *args): + self.viewport.rect = self.get_rect() + + def render(self): + self.controller.update_camera(self.camera) + self.viewport.render(self.scene, self.camera) + + for child in self.children: + child.render() + + def add_graphic(self, graphic, center: bool = True): + if graphic.name is not None: # skip for those that have no name + graphic_names = list() + + for g in self._graphics: + graphic_names.append(g.name) + + if graphic.name in graphic_names: + raise ValueError(f"graphics must have unique names, current graphic names are:\n {graphic_names}") + + self._graphics.append(graphic) + self.scene.add(graphic.world_object) + + if center: + self.center_graphic(graphic) + + def _refresh_camera(self): + self.controller.update_camera(self.camera) + if sum(self.renderer.logical_size) > 0: + scene_lsize = self.viewport.rect[2], self.viewport.rect[3] + else: + scene_lsize = (1, 1) + + self.camera.set_view_size(*scene_lsize) + self.camera.update_projection_matrix() + + def center_graphic(self, graphic, zoom: float = 1.3): + if not isinstance(self.camera, OrthographicCamera): + warn("`center_graphic()` not yet implemented for `PerspectiveCamera`") + return + + self._refresh_camera() + + self.controller.show_object(self.camera, graphic.world_object) + + self.controller.zoom(zoom) + + def center_scene(self, zoom: float = 1.3): + if not len(self.scene.children) > 0: + return + + if not isinstance(self.camera, OrthographicCamera): + warn("`center_scene()` not yet implemented for `PerspectiveCamera`") + return + + self._refresh_camera() + + self.controller.show_object(self.camera, self.scene) + + self.controller.zoom(zoom) + + def get_graphics(self): + return self._graphics + + def remove_graphic(self, graphic): + self.scene.remove(graphic.world_object) + + def __getitem__(self, name: str): + for graphic in self._graphics: + if graphic.name == name: + return graphic + + graphic_names = list() + for g in self._graphics: + graphic_names.append(g.name) + raise IndexError(f"no graphic of given name, the current graphics are:\n {graphic_names}") + + def __repr__(self): + newline = "\n\t" + if self.name is None: + name = "" + else: + name = self.name + + return f"{name}: {self.__class__.__name__} @ {hex(id(self))} \n" \ + f"parent: {self.parent}\n" \ + f"children: {self.children}\n" \ + f"Graphics: \n\t" \ + f"{newline.join(graphic.__repr__() for graphic in self.get_graphics())}" diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index e5e4884e4..f6899c485 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -154,9 +154,9 @@ def __getitem__(self, index: Union[Tuple[int, int], str]): else: return self._subplots[index[0], index[1]] - def animate(self): + def render(self): for subplot in self: - subplot.animate(self.canvas.get_logical_size()) + subplot.render() for f in self._animate_funcs: f() @@ -173,7 +173,7 @@ def add_animations(self, *funcs: callable): self._animate_funcs += funcs def show(self): - self.canvas.request_draw(self.animate) + self.canvas.request_draw(self.render) for subplot in self: subplot.center_scene() @@ -193,6 +193,3 @@ def __next__(self) -> Subplot: def __repr__(self): return f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n" - - - diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py index 615a5c57b..292b4d4c6 100644 --- a/fastplotlib/layouts/_subplot.py +++ b/fastplotlib/layouts/_subplot.py @@ -7,9 +7,10 @@ from warnings import warn import numpy as np from math import copysign +from ._base import PlotArea -class Subplot: +class Subplot(PlotArea): def __init__( self, position: Tuple[int, int] = None, @@ -18,49 +19,29 @@ def __init__( controller: Union[pygfx.PanZoomController, pygfx.OrbitOrthoController] = None, canvas: WgpuCanvas = None, renderer: pygfx.Renderer = None, + name: str = None, **kwargs ): - self.scene: pygfx.Scene = pygfx.Scene() - - self._graphics = list() - if canvas is None: canvas = WgpuCanvas() if renderer is None: renderer = pygfx.renderers.WgpuRenderer(canvas) - self.canvas = canvas - self.renderer = renderer - - if "name" in kwargs.keys(): - self.name = kwargs["name"] - else: - self.name = None - if position is None: position = (0, 0) - self.position: Tuple[int, int] = position if parent_dims is None: parent_dims = (1, 1) self.nrows, self.ncols = parent_dims - self.camera: Union[pygfx.OrthographicCamera, pygfx.PerspectiveCamera] = create_camera(camera) - if controller is None: controller = create_controller(camera) - self.controller: Union[pygfx.PanZoomController, pygfx.OrbitOrthoController] = controller - # might be better as an attribute of GridPlot - # but easier to iterate when in same object as camera and scene - self.viewport: pygfx.Viewport = pygfx.Viewport(renderer) + self.docked_viewports = dict() - self.controller.add_default_event_handlers( - self.viewport, - self.camera - ) + self.spacing = 2 self._axes: AxesHelper = AxesHelper(size=100) for arrow in self._axes.children: @@ -70,50 +51,49 @@ def __init__( self._animate_funcs = list() - self.renderer.add_event_handler(self.set_viewport_rect, "resize") + super(Subplot, self).__init__( + parent=None, + position=position, + camera=create_camera(camera), + controller=controller, + scene=Scene(), + canvas=canvas, + renderer=renderer, + name=name + ) - self.docked_viewports = dict() for pos in ["left", "top", "right", "bottom"]: - self.docked_viewports[pos] = _DockedViewport(self, pos, size=0) - - self.set_viewport_rect() + dv = _DockedViewport(self, pos, size=0) + self.docked_viewports[pos] = dv + self.children.append(dv) def get_rect(self): row_ix, col_ix = self.position width_canvas, height_canvas = self.renderer.logical_size - spacing = 2 # spacing in pixels - - x_pos = ((width_canvas / self.ncols) + ((col_ix - 1) * (width_canvas / self.ncols))) + spacing - y_pos = ((height_canvas / self.nrows) + ((row_ix - 1) * (height_canvas / self.nrows))) + spacing - width_subplot = (width_canvas / self.ncols) - spacing - height_suplot = (height_canvas / self.nrows) - spacing + x_pos = ((width_canvas / self.ncols) + ((col_ix - 1) * (width_canvas / self.ncols))) + self.spacing + y_pos = ((height_canvas / self.nrows) + ((row_ix - 1) * (height_canvas / self.nrows))) + self.spacing + width_subplot = (width_canvas / self.ncols) - self.spacing + height_suplot = (height_canvas / self.nrows) - self.spacing - return np.array([ + rect = np.array([ x_pos, y_pos, width_subplot, height_suplot ]) - def set_viewport_rect(self, *args): - rect = self.get_rect() - for dv in self.docked_viewports.values(): rect = rect + dv.get_parent_rect_adjust() - self.viewport.rect = rect + return rect - def animate(self, canvas_dims: Tuple[int, int] = None): - self.controller.update_camera(self.camera) - self.viewport.render(self.scene, self.camera) + def render(self): + super(Subplot, self).render() for f in self._animate_funcs: f() - for dv in self.docked_viewports.values(): - dv.render() - def add_animations(self, *funcs: callable): for f in funcs: if not callable(f): @@ -123,59 +103,11 @@ def add_animations(self, *funcs: callable): self._animate_funcs += funcs def add_graphic(self, graphic, center: bool = True): - if graphic.name is not None: # skip for those that have no name - graphic_names = list() - - for g in self._graphics: - graphic_names.append(g.name) - - if graphic.name in graphic_names: - raise ValueError(f"graphics must have unique names, current graphic names are:\n {graphic_names}") - - self._graphics.append(graphic) - self.scene.add(graphic.world_object) + super(Subplot, self).add_graphic(graphic, center) if isinstance(graphic, HeatmapGraphic): self.controller.scale.y = copysign(self.controller.scale.y, -1) - if center: - self.center_graphic(graphic) - - def _refresh_camera(self): - self.controller.update_camera(self.camera) - if sum(self.renderer.logical_size) > 0: - scene_lsize = self.viewport.rect[2], self.viewport.rect[3] - else: - scene_lsize = (1, 1) - - self.camera.set_view_size(*scene_lsize) - self.camera.update_projection_matrix() - - def center_graphic(self, graphic, zoom: float = 1.3): - if not isinstance(self.camera, pygfx.OrthographicCamera): - warn("`center_graphic()` not yet implemented for `PerspectiveCamera`") - return - - self._refresh_camera() - - self.controller.show_object(self.camera, graphic.world_object) - - self.controller.zoom(zoom) - - def center_scene(self, zoom: float = 1.3): - if not len(self.scene.children) > 0: - return - - if not isinstance(self.camera, pygfx.OrthographicCamera): - warn("`center_scene()` not yet implemented for `PerspectiveCamera`") - return - - self._refresh_camera() - - self.controller.show_object(self.camera, self.scene) - - self.controller.zoom(zoom) - def set_axes_visibility(self, visible: bool): if visible: self.scene.add(self._axes) @@ -188,35 +120,8 @@ def set_grid_visibility(self, visible: bool): else: self.scene.remove(self._grid) - def get_graphics(self): - return self._graphics - - def remove_graphic(self, graphic): - self.scene.remove(graphic.world_object) - - def __getitem__(self, name: str): - for graphic in self._graphics: - if graphic.name == name: - return graphic - - graphic_names = list() - for g in self._graphics: - graphic_names.append(g.name) - raise IndexError(f"no graphic of given name, the current graphics are:\n {graphic_names}") - - def __repr__(self): - newline = "\n " - if self.name is not None: - return f"'{self.name}' fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n" \ - f"Graphics: \n " \ - f"{newline.join(graphic.__repr__() for graphic in self.get_graphics())}" - else: - return f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))} \n" \ - f"Graphics: \n " \ - f"{newline.join(graphic.__repr__() for graphic in self.get_graphics())}" - -class _DockedViewport: +class _DockedViewport(PlotArea): _valid_positions = [ "right", "left", @@ -234,28 +139,22 @@ def __init__( if position not in self._valid_positions: raise ValueError(f"the `position` of an AnchoredViewport must be one of: {self._valid_positions}") - self.parent = parent - self.position = position self._size = size - self.viewport = pygfx.Viewport(self.parent.renderer) + super(_DockedViewport, self).__init__( + parent=parent, + position=position, + camera= OrthographicCamera(), + controller=PanZoomController(), + scene=Scene(), + canvas=parent.canvas, + renderer=parent.renderer + ) - self.scene = pygfx.Scene() self.scene.add( pygfx.Background(None, pygfx.BackgroundMaterial((0.2, 0.0, 0, 1), (0, 0.0, 0.2, 1))) ) - self.camera = pygfx.OrthographicCamera() - self.controller = pygfx.PanZoomController() - - self.controller.add_default_event_handlers( - self.viewport, - self.camera - ) - - self.parent.renderer.add_event_handler(self.set_viewport_rect, "resize") - self.set_viewport_rect() - @property def size(self) -> int: return self._size @@ -266,7 +165,7 @@ def size(self, s: int): self.parent.set_viewport_rect() self.set_viewport_rect() - def _get_rect(self, *args): + def get_rect(self, *args): if self.size == 0: self.viewport.rect = None return @@ -304,11 +203,6 @@ def _get_rect(self, *args): return [x_pos, y_pos, width_viewport, height_viewport] - def set_viewport_rect(self, *args): - rect = self._get_rect() - - self.viewport.rect = rect - def get_parent_rect_adjust(self): if self.position == "right": return np.array([ @@ -346,32 +240,4 @@ def render(self): if self.size == 0: return - self.controller.update_camera(self.camera) - self.viewport.render(self.scene, self.camera) - - def add_graphic(self, graphic, center: bool = True): - self.scene.add(graphic.world_object) - - if center: - self.center_graphic(graphic) - - def _refresh_camera(self): - self.controller.update_camera(self.camera) - # if sum(self.renderer.logical_size) > 0: - # scene_lsize = self.viewport.rect[2], self.viewport.rect[3] - # else: - # scene_lsize = (1, 1) - # - # self.camera.set_view_size(*scene_lsize) - self.camera.update_projection_matrix() - - def center_graphic(self, graphic, zoom: float = 1.3): - if not isinstance(self.camera, pygfx.OrthographicCamera): - warn("`center_graphic()` not yet implemented for `PerspectiveCamera`") - return - - self._refresh_camera() - - self.controller.show_object(self.camera, graphic.world_object) - - self.controller.zoom(zoom) + super(_DockedViewport, self).render() diff --git a/fastplotlib/plot.py b/fastplotlib/plot.py index 491b223ec..4cb3365bc 100644 --- a/fastplotlib/plot.py +++ b/fastplotlib/plot.py @@ -39,14 +39,14 @@ def _create_graphic(self, graphic_class, *args, **kwargs): return graphic - def animate(self): - super(Plot, self).animate(canvas_dims=None) + def render(self): + super(Plot, self).render(canvas_dims=None) self.renderer.flush() self.canvas.request_draw() def show(self): - self.canvas.request_draw(self.animate) + self.canvas.request_draw(self.render) self.center_scene() return self.canvas From 6611a6cd93d58221470e3b07ee6440362275dd27 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 11 Dec 2022 01:14:29 -0500 Subject: [PATCH 6/6] bug fixes, cleanup, PlotArea repr and str, everything tested and works, no broken functionality --- fastplotlib/layouts/_base.py | 21 +++++++++++++-------- fastplotlib/layouts/_subplot.py | 29 ++++++++++++++--------------- fastplotlib/plot.py | 2 +- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/fastplotlib/layouts/_base.py b/fastplotlib/layouts/_base.py index 0aedcf87e..68221ad11 100644 --- a/fastplotlib/layouts/_base.py +++ b/fastplotlib/layouts/_base.py @@ -78,6 +78,7 @@ def viewport(self) -> Viewport: def camera(self) -> Union[OrthographicCamera, PerspectiveCamera]: return self._camera + # in the future we can think about how to allow changing the controller @property def controller(self) -> Union[PanZoomController, OrbitController]: return self._controller @@ -163,15 +164,19 @@ def __getitem__(self, name: str): graphic_names.append(g.name) raise IndexError(f"no graphic of given name, the current graphics are:\n {graphic_names}") - def __repr__(self): - newline = "\n\t" + def __str__(self): if self.name is None: - name = "" + name = "unnamed" else: name = self.name - return f"{name}: {self.__class__.__name__} @ {hex(id(self))} \n" \ - f"parent: {self.parent}\n" \ - f"children: {self.children}\n" \ - f"Graphics: \n\t" \ - f"{newline.join(graphic.__repr__() for graphic in self.get_graphics())}" + return f"{name}: {self.__class__.__name__} @ {hex(id(self))}" + + def __repr__(self): + newline = "\n\t" + + return f"{self}\n" \ + f" parent: {self.parent}\n" \ + f" Graphics:\n" \ + f"\t{newline.join(graphic.__repr__() for graphic in self.get_graphics())}" \ + f"\n" diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py index 292b4d4c6..bb847c4ec 100644 --- a/fastplotlib/layouts/_subplot.py +++ b/fastplotlib/layouts/_subplot.py @@ -1,10 +1,9 @@ -import pygfx -from pygfx import Scene, OrthographicCamera, PerspectiveCamera, PanZoomController, Viewport, AxesHelper, GridHelper +from pygfx import Scene, OrthographicCamera, PanZoomController, OrbitOrthoController, \ + AxesHelper, GridHelper, WgpuRenderer, Background, BackgroundMaterial from ..graphics import HeatmapGraphic from ._defaults import create_camera, create_controller from typing import * from wgpu.gui.auto import WgpuCanvas -from warnings import warn import numpy as np from math import copysign from ._base import PlotArea @@ -16,9 +15,9 @@ def __init__( position: Tuple[int, int] = None, parent_dims: Tuple[int, int] = None, camera: str = '2d', - controller: Union[pygfx.PanZoomController, pygfx.OrbitOrthoController] = None, + controller: Union[PanZoomController, OrbitOrthoController] = None, canvas: WgpuCanvas = None, - renderer: pygfx.Renderer = None, + renderer: WgpuRenderer = None, name: str = None, **kwargs ): @@ -26,7 +25,7 @@ def __init__( canvas = WgpuCanvas() if renderer is None: - renderer = pygfx.renderers.WgpuRenderer(canvas) + renderer = WgpuRenderer(canvas) if position is None: position = (0, 0) @@ -64,6 +63,7 @@ def __init__( for pos in ["left", "top", "right", "bottom"]: dv = _DockedViewport(self, pos, size=0) + dv.name = pos self.docked_viewports[pos] = dv self.children.append(dv) @@ -95,12 +95,12 @@ def render(self): f() def add_animations(self, *funcs: callable): - for f in funcs: - if not callable(f): - raise TypeError( - f"all positional arguments to add_animations() must be callable types, you have passed a: {type(f)}" - ) - self._animate_funcs += funcs + if not all([callable(f) for f in funcs]): + raise TypeError( + f"all positional arguments to add_animations() must be callable types" + ) + + self._animate_funcs += funcs def add_graphic(self, graphic, center: bool = True): super(Subplot, self).add_graphic(graphic, center) @@ -134,7 +134,6 @@ def __init__( parent: Subplot, position: str, size: int, - camera: str = "2d", ): if position not in self._valid_positions: raise ValueError(f"the `position` of an AnchoredViewport must be one of: {self._valid_positions}") @@ -144,7 +143,7 @@ def __init__( super(_DockedViewport, self).__init__( parent=parent, position=position, - camera= OrthographicCamera(), + camera=OrthographicCamera(), controller=PanZoomController(), scene=Scene(), canvas=parent.canvas, @@ -152,7 +151,7 @@ def __init__( ) self.scene.add( - pygfx.Background(None, pygfx.BackgroundMaterial((0.2, 0.0, 0, 1), (0, 0.0, 0.2, 1))) + Background(None, BackgroundMaterial((0.2, 0.0, 0, 1), (0, 0.0, 0.2, 1))) ) @property diff --git a/fastplotlib/plot.py b/fastplotlib/plot.py index 4cb3365bc..7fc26ecd3 100644 --- a/fastplotlib/plot.py +++ b/fastplotlib/plot.py @@ -40,7 +40,7 @@ def _create_graphic(self, graphic_class, *args, **kwargs): return graphic def render(self): - super(Plot, self).render(canvas_dims=None) + super(Plot, self).render() self.renderer.flush() self.canvas.request_draw()