GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Customizable camera linking does not work · Issue #783 · fastplotlib/fastplotlib

Customizable camera linking does not work #783

New issue
Copy link
New issue
Copy link
Closed
#938
Closed
Customizable camera linking does not work#783
#938
Copy link

Description

@kushalkolar
kushalkolar
opened on Mar 31, 2025
Issue body actions

Not sure why, pygfx demo works, fpl is doing something additional I think:

import numpy as np
import fastplotlib as fpl
import pygfx

xs = np.linspace(0, 2 * np.pi, 100)
ys = np.sin(xs)

ys_big = np.random.rand(100) * 10

# create cameras, fov=0 means Orthographic projection
camera1 = pygfx.PerspectiveCamera(fov=0)
camera2 = pygfx.PerspectiveCamera(fov=0)

# create controllers, first add the "main" camera for the subplot
controller1 = pygfx.PanZoomController(camera1)
controller2 = pygfx.PanZoomController(camera2)

# add the other camera to each controller, but only include the 'x' state, i.e. 'y' for height is not included
# this must be done only after adding the "main" cameras to the controller as done above
controller1.add_camera(camera2, include_state={"x", "width"})
controller2.add_camera(camera1, include_state={"x", "width"})

# create figure using these cameras and controllers
figure = fpl.Figure(
    shape=(2, 1),
    cameras=[camera1, camera2],
    controllers=[controller1, controller2],
    size=(700, 560)
)


figure[0, 0].add_line(np.column_stack([xs, ys_big]))
figure[1, 0].add_line(np.column_stack([xs, ys]))

print(controller1._cameras)
print(controller2._cameras)

for subplot in figure:
    subplot.camera.zoom = 1.0

figure.show(maintain_aspect=False, autoscale=False)

I did make the following change to PlotArea:

Index: fastplotlib/layouts/_plot_area.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py
--- a/fastplotlib/layouts/_plot_area.py	
+++ b/fastplotlib/layouts/_plot_area.py	(date 1743405901011)
@@ -79,7 +79,9 @@
         self._camera = camera
         self._controller = controller
 
-        self.controller.add_camera(self._camera)
+        if self.camera not in self.controller.cameras:
+            self.controller.add_camera(self._camera)
+
         self.controller.register_events(
             self.viewport,
         )

Activity

  1. lkeegan
    added 2 commits that reference this issue on Jul 18, 2025

    WIP replace pyqtgraph with fastplotlib

    e9e5fe9

    Replace pyqtgraph with fastplotlib

    a4be744
  2. lkeegan
    added 2 commits that reference this issue on Jul 31, 2025

    Replace pyqtgraph with fastplotlib

    7bbbf82

    ROUGH IDEA

    9a33c93
  3. kushalkolar commented on Nov 12, 2025

    @kushalkolar
    kushalkolar
    on Nov 12, 2025
    MemberAuthor
    More actions

    OK this seems to work now, I didn't change make any specific change to fpl for this 🤔 . Maybe the change I suggested is what broke things. I made some changes to auto_scale and auto_center so it works for these usecases, PR incoming.

  4. kushalkolar commented on Nov 12, 2025

    @kushalkolar
    kushalkolar
    on Nov 12, 2025
    MemberAuthor
    More actions

    I'm in another branch so I'm going to checkout a new branch of main and copy paste this:

        def center_scene(self, *, zoom: float = 1.0):
            """
            Auto-center the scene, does not scale.
    
            Parameters
            ----------
            zoom: float
                apply a zoom after centering the scene
    
            """
    
            if not len(self._fpl_graphics_scene.children) > 0:
                return
    
            if self.parent.__class__.__name__.endswith("Figure"):
                # always use figure._subplots.ravel() in internal fastplotlib code
                # otherwise if we use `for subplot in figure`, this could conflict
                # with a user's iterator where they are doing `for subplot in figure` !!!
                for subplot in self.parent._subplots.ravel():
                    # scale all cameras associated with this controller
                    if subplot.camera in self.controller.cameras:
                        # scale the cameras in other subplots w.r.t. the scene in those subplots
                        self._auto_center_scene(
                            subplot.camera,
                            subplot._fpl_graphics_scene,
                            zoom
                        )
            else:
                # just change for this plot area
                # this is probably a dock area
                self._auto_center_scene(self.camera, self._fpl_graphics_scene, zoom)
    
        def _auto_center_scene(self, camera: pygfx.PerspectiveCamera, scene: pygfx.Scene, zoom: float):
            camera.show_object(scene)
            # camera.show_object can cause the camera width and height to increase so apply a zoom to compensate
            # probably because camera.show_object uses bounding sphere
            camera.zoom = zoom
    
        def auto_scale(
            self,
            *,  # since this is often used as an event handler, don't want to coerce maintain_aspect = True
            maintain_aspect: None | bool = None,
            zoom: float = 0.75,
        ):
            """
            Auto-scale the camera w.r.t to the scene
    
            Parameters
            ----------
            maintain_aspect: ``None`` or bool, default ``None``
                Maintain the camera aspect ratio for all dimensions. If ``None``, the aspect is left unchanged.
                if ``False`` the camera is scaled to the bounding box of the current scene.
    
            zoom: float
                zoom value for the camera after auto-scaling
    
            """
    
            if not len(self._fpl_graphics_scene.children) > 0:
                return
    
            self.center_scene()
    
            if maintain_aspect is None:  # if not provided keep current setting
                # use the same maintain apsect for all other cameras that this controller manages
                # I think this make sense for most use cases, even when the other controllers are
                # only managing one or 2 axes
                maintain_aspect = self.camera.maintain_aspect
    
            if self.parent.__class__.__name__.endswith("Figure"):
                    # always use figure._subplots.ravel() in internal fastplotlib code
                    # otherwise if we use `for subplot in figure`, this could conflict
                    # with a user's iterator where they are doing `for subplot in figure` !!!
                    for subplot in self.parent._subplots.ravel():
    
                        # scale all cameras associated with this controller
                        if subplot.camera in self.controller.cameras:
                            camera = subplot.camera
                            self._auto_scale_scene(camera, subplot._fpl_graphics_scene, zoom, maintain_aspect)
            else:
                # just change for this plot area, this is probably a dock area
                self._auto_scale_scene(self.camera, self._fpl_graphics_scene, zoom, maintain_aspect)
    
        def _auto_scale_scene(self, camera: pygfx.PerspectiveCamera, scene: pygfx.Scene, zoom: float, maintain_aspect: bool):
            camera.maintain_aspect = maintain_aspect
    
            if len(scene.children) > 0:
                width, height, depth = np.ptp(
                    scene.get_world_bounding_box(), axis=0
                )
            else:
                width, height, depth = (1, 1, 1)
    
            # make sure width and height are non-zero
            if width < 0.01:
                width = 1
            if height < 0.01:
                height = 1
    
            camera.width = width
            camera.height = height
    
            camera.zoom = zoom
    
  5. clewis7
    closed this as completedin #938on Nov 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.