Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 88f33c3

Browse filesBrowse files
committed
WIP on plot frame
1 parent 18d2389 commit 88f33c3
Copy full SHA for 88f33c3

File tree

7 files changed

+349
-1
lines changed
Filter options

7 files changed

+349
-1
lines changed

‎fastplotlib/layouts/_frame/__init__.py

Copy file name to clipboardExpand all lines: fastplotlib/layouts/_frame/__init__.py
Whitespace-only changes.
+89Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import numpy as np
2+
3+
from .._subplot import Subplot
4+
5+
class BaseFrame:
6+
"""Mixin class for Plot and GridPlot that gives them the toolbar"""
7+
def __init__(self, canvas, toolbar):
8+
"""
9+
10+
Parameters
11+
----------
12+
plot:
13+
`Plot` or `GridPlot`
14+
toolbar
15+
"""
16+
self._canvas = canvas
17+
self._toolbar = toolbar
18+
19+
# default points upwards
20+
self._y_axis: int = 1
21+
22+
self._plot_type = self.__class__.__name__
23+
24+
@property
25+
def selected_subplot(self) -> Subplot:
26+
if self._plot_type == "GridPlot":
27+
return self.toolbar.selected_subplot
28+
else:
29+
return self
30+
31+
@property
32+
def toolbar(self):
33+
return self._toolbar
34+
35+
@property
36+
def panzoom(self) -> bool:
37+
return self.selected_subplot.controller.enabled
38+
39+
@property
40+
def maintain_aspect(self) -> bool:
41+
return self.selected_subplot.camera.maintain_aspect
42+
43+
@property
44+
def y_axis(self) -> int:
45+
return int(np.sign(self.selected_subplot.camera.local.scale_y))
46+
47+
@y_axis.setter
48+
def y_axis(self, value: int):
49+
"""
50+
51+
Parameters
52+
----------
53+
value: 1 or -1
54+
1: points upwards, -1: points downwards
55+
56+
"""
57+
value = int(value) # in case we had a float 1.0
58+
59+
if value not in [1, -1]:
60+
raise ValueError("y_axis value must be 1 or -1")
61+
62+
sign = np.sign(self.selected_subplot.camera.local.scale_y)
63+
64+
if sign == value:
65+
# desired y-axis is already set
66+
return
67+
68+
# otherwise flip it
69+
self.selected_subplot.camera.local.scale_y *= -1
70+
71+
def render(self):
72+
raise NotImplemented
73+
74+
def _autoscale_init(self, maintain_aspect: bool):
75+
"""autoscale function that is called only during show()"""
76+
if self._plot_type == "GridPlot":
77+
for subplot in self:
78+
if maintain_aspect is None:
79+
_maintain_aspect = subplot.camera.maintain_aspect
80+
else:
81+
_maintain_aspect = maintain_aspect
82+
subplot.auto_scale(maintain_aspect=_maintain_aspect, zoom=0.95)
83+
else:
84+
if maintain_aspect is None:
85+
maintain_aspect = self.camera.maintain_aspect
86+
self.auto_scale(maintain_aspect=maintain_aspect, zoom=0.95)
87+
88+
def show(self):
89+
raise NotImplemented("Must be implemented in subclass")

‎fastplotlib/layouts/_frame/_frame_desktop.py

Copy file name to clipboardExpand all lines: fastplotlib/layouts/_frame/_frame_desktop.py
Whitespace-only changes.
+103Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import os
2+
3+
4+
from ._frame_base import BaseFrame
5+
from ._toolbar import ToolBar
6+
7+
8+
class FrameNotebook(BaseFrame):
9+
def show(
10+
self,
11+
autoscale: bool = True,
12+
maintain_aspect: bool = None,
13+
toolbar: bool = True,
14+
sidecar: bool = True,
15+
sidecar_kwargs: dict = None,
16+
vbox: list = None
17+
):
18+
"""
19+
Begins the rendering event loop and returns the canvas
20+
21+
Parameters
22+
----------
23+
autoscale: bool, default ``True``
24+
autoscale the Scene
25+
26+
maintain_aspect: bool, default ``True``
27+
maintain aspect ratio
28+
29+
toolbar: bool, default ``True``
30+
show toolbar
31+
32+
sidecar: bool, default ``True``
33+
display plot in a ``jupyterlab-sidecar``
34+
35+
sidecar_kwargs: dict, default ``None``
36+
kwargs for sidecar instance to display plot
37+
i.e. title, layout
38+
39+
vbox: list, default ``None``
40+
list of ipywidgets to be displayed with plot
41+
42+
Returns
43+
-------
44+
WgpuCanvas
45+
the canvas
46+
47+
"""
48+
49+
self._canvas.request_draw(self.render)
50+
51+
self._canvas.set_logical_size(*self._starting_size)
52+
53+
if autoscale:
54+
self._autoscale_init(maintain_aspect)
55+
56+
if "NB_SNAPSHOT" in os.environ.keys():
57+
# used for docs
58+
if os.environ["NB_SNAPSHOT"] == "1":
59+
return self.canvas.snapshot()
60+
61+
# check if in jupyter notebook, or if toolbar is False
62+
if (self.canvas.__class__.__name__ != "JupyterWgpuCanvas") or (not toolbar):
63+
return self.canvas
64+
65+
if self.toolbar is None:
66+
self.toolbar = ToolBar(self)
67+
self.toolbar.maintain_aspect_button.value = self[
68+
0, 0
69+
].camera.maintain_aspect
70+
71+
# validate vbox if not None
72+
if vbox is not None:
73+
for widget in vbox:
74+
if not isinstance(widget, Widget):
75+
raise ValueError(f"Items in vbox must be ipywidgets. Item: {widget} is of type: {type(widget)}")
76+
self.vbox = VBox(vbox)
77+
78+
if not sidecar:
79+
if self.vbox is not None:
80+
return VBox([self.canvas, self.toolbar.widget, self.vbox])
81+
else:
82+
return VBox([self.canvas, self.toolbar.widget])
83+
84+
# used when plot.show() is being called again but sidecar has been closed via "x" button
85+
# need to force new sidecar instance
86+
# couldn't figure out how to get access to "close" button in order to add observe method on click
87+
if self.plot_open:
88+
self.sidecar = None
89+
90+
if self.sidecar is None:
91+
if sidecar_kwargs is not None:
92+
self.sidecar = Sidecar(**sidecar_kwargs)
93+
self.plot_open = True
94+
else:
95+
self.sidecar = Sidecar()
96+
self.plot_open = True
97+
98+
with self.sidecar:
99+
if self.vbox is not None:
100+
return display(VBox([self.canvas, self.toolbar.widget, self.vbox]))
101+
else:
102+
return display(VBox([self.canvas, self.toolbar.widget]))
103+
+156Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
from datetime import datetime
2+
from itertools import product
3+
import traceback
4+
5+
from ipywidgets import HBox, Layout, Button, ToggleButton, VBox, Dropdown, Widget
6+
7+
from fastplotlib.layouts._subplot import Subplot
8+
9+
10+
class ToolBar:
11+
def __init__(self, plot):
12+
"""
13+
Basic toolbar for a GridPlot instance.
14+
15+
Parameters
16+
----------
17+
plot:
18+
"""
19+
self.plot = plot
20+
21+
self.autoscale_button = Button(
22+
value=False,
23+
disabled=False,
24+
icon="expand-arrows-alt",
25+
layout=Layout(width="auto"),
26+
tooltip="auto-scale scene",
27+
)
28+
self.center_scene_button = Button(
29+
value=False,
30+
disabled=False,
31+
icon="align-center",
32+
layout=Layout(width="auto"),
33+
tooltip="auto-center scene",
34+
)
35+
self.panzoom_controller_button = ToggleButton(
36+
value=True,
37+
disabled=False,
38+
icon="hand-pointer",
39+
layout=Layout(width="auto"),
40+
tooltip="panzoom controller",
41+
)
42+
self.maintain_aspect_button = ToggleButton(
43+
value=True,
44+
disabled=False,
45+
description="1:1",
46+
layout=Layout(width="auto"),
47+
tooltip="maintain aspect",
48+
)
49+
self.maintain_aspect_button.style.font_weight = "bold"
50+
self.flip_camera_button = Button(
51+
value=False,
52+
disabled=False,
53+
icon="arrow-up",
54+
layout=Layout(width="auto"),
55+
tooltip="y-axis direction",
56+
)
57+
58+
self.record_button = ToggleButton(
59+
value=False,
60+
disabled=False,
61+
icon="video",
62+
layout=Layout(width="auto"),
63+
tooltip="record",
64+
)
65+
66+
positions = list(product(range(self.plot.shape[0]), range(self.plot.shape[1])))
67+
values = list()
68+
for pos in positions:
69+
if self.plot[pos].name is not None:
70+
values.append(self.plot[pos].name)
71+
else:
72+
values.append(str(pos))
73+
self.dropdown = Dropdown(
74+
options=values,
75+
disabled=False,
76+
description="Subplots:",
77+
layout=Layout(width="200px"),
78+
)
79+
80+
self.widget = HBox(
81+
[
82+
self.autoscale_button,
83+
self.center_scene_button,
84+
self.panzoom_controller_button,
85+
self.maintain_aspect_button,
86+
self.flip_camera_button,
87+
self.record_button,
88+
self.dropdown,
89+
]
90+
)
91+
92+
self.panzoom_controller_button.observe(self.panzoom_control, "value")
93+
self.autoscale_button.on_click(self.auto_scale)
94+
self.center_scene_button.on_click(self.center_scene)
95+
self.maintain_aspect_button.observe(self.maintain_aspect, "value")
96+
self.flip_camera_button.on_click(self.flip_camera)
97+
self.record_button.observe(self.record_plot, "value")
98+
99+
self.plot.renderer.add_event_handler(self.update_current_subplot, "click")
100+
101+
@property
102+
def current_subplot(self) -> Subplot:
103+
# parses dropdown value as plot name or position
104+
current = self.dropdown.value
105+
if current[0] == "(":
106+
return self.plot[eval(current)]
107+
else:
108+
return self.plot[current]
109+
110+
def auto_scale(self, obj):
111+
current = self.current_subplot
112+
current.auto_scale(maintain_aspect=current.camera.maintain_aspect)
113+
114+
def center_scene(self, obj):
115+
current = self.current_subplot
116+
current.center_scene()
117+
118+
def panzoom_control(self, obj):
119+
current = self.current_subplot
120+
current.controller.enabled = self.panzoom_controller_button.value
121+
122+
def maintain_aspect(self, obj):
123+
current = self.current_subplot
124+
current.camera.maintain_aspect = self.maintain_aspect_button.value
125+
126+
def flip_camera(self, obj):
127+
current = self.current_subplot
128+
current.camera.local.scale_y *= -1
129+
if current.camera.local.scale_y == -1:
130+
self.flip_camera_button.icon = "arrow-down"
131+
else:
132+
self.flip_camera_button.icon = "arrow-up"
133+
134+
def update_current_subplot(self, ev):
135+
for subplot in self.plot:
136+
pos = subplot.map_screen_to_world((ev.x, ev.y))
137+
if pos is not None:
138+
# update self.dropdown
139+
if subplot.name is None:
140+
self.dropdown.value = str(subplot.position)
141+
else:
142+
self.dropdown.value = subplot.name
143+
self.panzoom_controller_button.value = subplot.controller.enabled
144+
self.maintain_aspect_button.value = subplot.camera.maintain_aspect
145+
146+
def record_plot(self, obj):
147+
if self.record_button.value:
148+
try:
149+
self.plot.record_start(
150+
f"./{datetime.now().isoformat(timespec='seconds').replace(':', '_')}.mp4"
151+
)
152+
except Exception:
153+
traceback.print_exc()
154+
self.record_button.value = False
155+
else:
156+
self.plot.record_stop()
File renamed without changes.

‎fastplotlib/layouts/_subplot.py

Copy file name to clipboardExpand all lines: fastplotlib/layouts/_subplot.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from ..graphics import TextGraphic
1818
from ._utils import make_canvas_and_renderer
19-
from ._base import PlotArea
19+
from ._plot_area import PlotArea
2020
from ._defaults import create_camera, create_controller
2121
from .graphic_methods_mixin import GraphicMethodsMixin
2222

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.