diff --git a/examples/gridplot/gridplot.py b/examples/gridplot/gridplot.py index 5edd6a845..1aa8c8083 100644 --- a/examples/gridplot/gridplot.py +++ b/examples/gridplot/gridplot.py @@ -30,4 +30,4 @@ # See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) - fpl.loop.run() + fpl.loop.run() \ No newline at end of file diff --git a/examples/scatter/scatter_colorslice_iris.py b/examples/scatter/scatter_colorslice_iris.py index 425a2f1ff..d9dc3053f 100644 --- a/examples/scatter/scatter_colorslice_iris.py +++ b/examples/scatter/scatter_colorslice_iris.py @@ -23,7 +23,8 @@ data=data[:, :-1], sizes=6, alpha=0.7, - colors=colors # use colors from the list of strings + alpha_mode="weighted_blend", # blend overlapping dots + colors=colors, # use colors from the list of strings ) figure.show() diff --git a/examples/screenshots/line_collection_slicing.png b/examples/screenshots/line_collection_slicing.png index 428e6c33d..9a163bdd4 100644 --- a/examples/screenshots/line_collection_slicing.png +++ b/examples/screenshots/line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a88282f40fb1c02dfe7921793128e9b7abe1bbb85de0e12abc20cb23d3ecc720 -size 73949 +oid sha256:3283a717635f0e39c3da66e705dc988db4054d9210761265bde2f48f8e8c4fce +size 74585 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index d7271ce71..5c5d6d4be 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4f95d45d58779e7afe2b6898621c3e8d46c2dadc04406ed9ffa09c8c84c19b3 -size 25582 +oid sha256:9549c19c4d9ad6804036a4b1018ae438793cb350228d7f00fc0e38f74cd058b9 +size 25893 diff --git a/fastplotlib/graphics/_axes.py b/fastplotlib/graphics/_axes.py index 8063e2819..6847c2770 100644 --- a/fastplotlib/graphics/_axes.py +++ b/fastplotlib/graphics/_axes.py @@ -183,14 +183,22 @@ def __init__( } # create ruler for each dim - self._x = pygfx.Ruler(alpha_mode="solid", **x_kwargs) - self._y = pygfx.Ruler(alpha_mode="solid", **y_kwargs) - self._z = pygfx.Ruler(alpha_mode="solid", **z_kwargs) + self._x = pygfx.Ruler( + alpha_mode="solid", render_queue=RenderQueue.axes, **x_kwargs + ) + self._y = pygfx.Ruler( + alpha_mode="solid", render_queue=RenderQueue.axes, **y_kwargs + ) + self._z = pygfx.Ruler( + alpha_mode="solid", render_queue=RenderQueue.axes, **z_kwargs + ) # We render the lines and ticks as solid, but enable aa for text for prettier glyphs for ruler in self._x, self._y, self._z: + ruler.line.material.depth_compare = "<=" + ruler.points.material.depth_compare = "<=" + ruler.text.material.depth_compare = "<=" ruler.text.material.alpha_mode = "auto" - ruler.text.material.render_queue = RenderQueue.auto + 50 ruler.text.material.aa = True self._offset = offset diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 4a33d2c1d..1eaf54bb6 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -324,9 +324,6 @@ def add_linear_selector( self._plot_area.add_graphic(selector, center=False) - # place selector above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_linear_region_selector( @@ -402,9 +399,6 @@ def add_linear_region_selector( self._plot_area.add_graphic(selector, center=False) - # place above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_rectangle_selector( @@ -447,9 +441,6 @@ def add_rectangle_selector( self._plot_area.add_graphic(selector, center=False) - # place above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_polygon_selector( @@ -485,7 +476,4 @@ def add_polygon_selector( self._plot_area.add_graphic(selector, center=False) - # place above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index bd3bbc397..f2d862067 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -96,6 +96,7 @@ def __init__( if thickness < 1.1: MaterialCls = pygfx.LineThinMaterial + aa = True else: MaterialCls = pygfx.LineMaterial @@ -110,6 +111,7 @@ def __init__( color=self.colors, pick_write=True, thickness_space=self.size_space, + depth_compare="<=", ) else: material = MaterialCls( @@ -118,6 +120,7 @@ def __init__( color_mode="vertex", pick_write=True, thickness_space=self.size_space, + depth_compare="<=", ) geometry = pygfx.Geometry( positions=self._data.buffer, colors=self._colors.buffer @@ -179,9 +182,6 @@ def add_linear_selector( self._plot_area.add_graphic(selector, center=False) - # place selector above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_linear_region_selector( @@ -238,9 +238,6 @@ def add_linear_region_selector( self._plot_area.add_graphic(selector, center=False) - # place selector below this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] - 1) - # PlotArea manages this for garbage collection etc. just like all other Graphics # so we should only work with a proxy on the user-end return selector diff --git a/fastplotlib/graphics/line_collection.py b/fastplotlib/graphics/line_collection.py index 3d183593a..275cc1e47 100644 --- a/fastplotlib/graphics/line_collection.py +++ b/fastplotlib/graphics/line_collection.py @@ -378,9 +378,6 @@ def add_linear_selector( self._plot_area.add_graphic(selector, center=False) - # place selector above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_linear_region_selector( @@ -435,9 +432,6 @@ def add_linear_region_selector( self._plot_area.add_graphic(selector, center=False) - # place selector below this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] - 1) - # PlotArea manages this for garbage collection etc. just like all other Graphics # so we should only work with a proxy on the user-end return selector diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 73095714b..6bff05fa5 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -97,10 +97,7 @@ def __init__( aa = kwargs.get("alpha_mode", "auto") in ("blend", "weighted_blend") geo_kwargs = {"positions": self._data.buffer} - material_kwargs = dict( - pick_write=True, - aa=aa, - ) + material_kwargs = dict(pick_write=True, aa=aa, depth_compare="<=") self._size_space = SizeSpace(size_space) if uniform_color: diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index feda52930..7b5c7952a 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -10,6 +10,7 @@ from ._utils import create_controller from ..graphics._base import Graphic +from ..graphics import ImageGraphic from ..graphics.selectors._base_selector import BaseSelector from ._graphic_methods_mixin import GraphicMethodsMixin from ..legends import Legend @@ -394,6 +395,29 @@ def remove_animation(self, func): if func in self._animate_funcs_post: self._animate_funcs_post.remove(func) + def _sort_images_by_depth(self): + """ + In general, we want to avoid setting the offset of a graphic, because the + z-dimension may actually mean something; we cannot know whether the user is + building a 3D scene or not. We could check whether the 3d dimension of line/point data + is all zeros, but maybe this is intended, and *other* graphics in the same scene + may be actually 3D. We could check camera.fov being zero, but maybe the user + switches to a 3D camera later, or uses a 3D orthographic camera. + + The one exception, kindof, is images, which are inherently 2D, and for which + layering helps a lot to get things rendered correctly. So we basically layer the + images, in the order that they were added, pushing older images backwards (away + from the camera). + """ + count = 0 + for graphic in self._graphics: + if isinstance(graphic, ImageGraphic): + count += 1 + auto_depth = -count + user_changed_depth = graphic.offset[2] % 1 > 0.0 # i.e. is not integer + if not user_changed_depth: + graphic.offset = (*graphic.offset[:-1], auto_depth) + def add_graphic(self, graphic: Graphic, center: bool = True): """ Add a Graphic to the scene @@ -416,10 +440,8 @@ def add_graphic(self, graphic: Graphic, center: bool = True): self._add_or_insert_graphic(graphic=graphic, center=center, action="add") - if self.camera.fov == 0: - # for orthographic positions stack objects along the z-axis - # for perspective projections we assume the user wants full 3D control - graphic.offset = (*graphic.offset[:-1], len(self)) + if isinstance(graphic, ImageGraphic): + self._sort_images_by_depth() def insert_graphic( self, @@ -458,17 +480,14 @@ def insert_graphic( graphic=graphic, center=center, action="insert", index=index ) - if self.camera.fov == 0: - # for orthographic positions stack objects along the z-axis - # for perspective projections we assume the user wants full 3D control - if auto_offset: - graphic.offset = (*graphic.offset[:-1], index) + if isinstance(graphic, ImageGraphic): + self._sort_images_by_depth() def _add_or_insert_graphic( self, graphic: Graphic, center: bool = True, - action: str = Literal["insert", "add"], + action: Literal["insert", "add"] = "add", index: int = 0, ): """Private method to handle inserting or adding a graphic to a PlotArea.""" diff --git a/fastplotlib/legends/legend.py b/fastplotlib/legends/legend.py index b24bcac58..9da836fd7 100644 --- a/fastplotlib/legends/legend.py +++ b/fastplotlib/legends/legend.py @@ -71,11 +71,9 @@ def __init__( # construct Line WorldObject data = np.array([[0, 0, 0], [3, 0, 0]], dtype=np.float32) - material = pygfx.LineMaterial - self._line_world_object = pygfx.Line( geometry=pygfx.Geometry(positions=data), - material=material( + material=pygfx.LineMaterial( alpha_mode="blend", render_queue=RenderQueue.overlay, thickness=8, @@ -112,7 +110,6 @@ def __init__( self._label_world_object.world.x = position[0] + 10 self.world_object.world.y = position[1] - self.world_object.world.z = 2 self.world_object.add_event_handler( partial(self._highlight_graphic, graphic), "click" diff --git a/fastplotlib/utils/enums.py b/fastplotlib/utils/enums.py index 5de3b2e0a..3901b082c 100644 --- a/fastplotlib/utils/enums.py +++ b/fastplotlib/utils/enums.py @@ -9,6 +9,7 @@ class RenderQueue(IntEnum): auto = 2600 transparent = 3000 overlay = 4000 - # For selectors we use a render_queue of 3500, which is at the end of what is considered the group of transparent objects. - # So it's rendered later than the normal scene (2000 - 3000), but before overlays like legends and tooltips. - selector = 3500 + # For axes and selectors we use a higher render_queue, so they get rendered later than + # the graphics. Axes (rulers) have depth_compare '<=' and selectors don't compare depth. + axes = 3400 # still in 'object' group + selector = 3600 # considered in 'overlay' group