diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5704acb661c701a2c4c0f07afe14fb81986ba24d..606af51b44df4745ca63568185f3869bc5933686 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,15 +9,11 @@ before_script:
   - apt-get install -y libxcb-cursor-dev
   - python -m pip install --upgrade pip
   - python --version
-  - pip install .[$QT_API]
   - git fetch --tags
+  - pip install -v .[$QT_API,test]
   # - export CXXFLAGS='--coverage'
-  # Install without dependencies to make sure that requirements.txt is up-to-date
-  - pip install --no-deps -ve .
   - pip show b_asic
   - export QT_API=$QT_API
-  # Install test dependencies
-  - pip install .[test]
   - export PYTEST_QT_API=$QT_API
 
 .run-test:
diff --git a/b_asic/architecture.py b/b_asic/architecture.py
index c641e95a930d87e0b203a5db8ec5a595ebfb55fd..d7b41982f81e2f162057bbca884fef33bde25f67 100644
--- a/b_asic/architecture.py
+++ b/b_asic/architecture.py
@@ -260,7 +260,7 @@ class Resource(HardwareBlock):
         **kwargs
             Passed to :meth:`~b_asic.resources.ProcessCollection.plot`.
         """
-        fig, ax = plt.subplots()
+        fig, ax = plt.subplots(layout="constrained")
         self.plot_content(ax, **kwargs)
         if title:
             fig.suptitle(title)
@@ -294,7 +294,7 @@ class Resource(HardwareBlock):
         This is visible in enriched shells, but the object itself has no further
         meaning (it is a Matplotlib Figure).
         """
-        fig, ax = plt.subplots()
+        fig, ax = plt.subplots(layout="constrained")
         self.plot_content(ax)
         return fig
 
diff --git a/b_asic/gui_utils/mpl_window.py b/b_asic/gui_utils/mpl_window.py
index 1a6db6785c5dcf473b767a23adf4b00994d6c2bd..2a3500114c4bb511f3d8259c82fdd3f717a4bf68 100644
--- a/b_asic/gui_utils/mpl_window.py
+++ b/b_asic/gui_utils/mpl_window.py
@@ -25,7 +25,7 @@ class MPLWindow(QDialog):
         self._dialog_layout = QVBoxLayout()
         self.setLayout(self._dialog_layout)
 
-        self._plot_fig = Figure(figsize=(5, 4), layout="compressed")
+        self._plot_fig = Figure(figsize=(5, 4), layout="constrained")
         self._plot_axes = self._plot_fig.subplots(*subplots)
 
         self._plot_canvas = FigureCanvas(self._plot_fig)
diff --git a/b_asic/resources.py b/b_asic/resources.py
index e54322bc8aa6799374ec4abf98ca1ba48a6e5bb9..6f979cee72e28331bef301fb52958c3eaf51cd02 100644
--- a/b_asic/resources.py
+++ b/b_asic/resources.py
@@ -580,7 +580,7 @@ class ProcessCollection:
 
         # Set up the Axes object
         if ax is None:
-            _, _ax = plt.subplots()
+            _, _ax = plt.subplots(layout="constrained")
         else:
             _ax = ax
 
@@ -724,7 +724,7 @@ class ProcessCollection:
         title : str, optional
             Figure title.
         """
-        fig, ax = plt.subplots()
+        fig, ax = plt.subplots(layout="constrained")
         self.plot(
             ax=ax,
             show_name=show_name,
@@ -1122,7 +1122,7 @@ class ProcessCollection:
 
         This is automatically displayed in e.g. Jupyter Qt console.
         """
-        fig, ax = plt.subplots()
+        fig, ax = plt.subplots(layout="constrained")
         self.plot(ax=ax, show_markers=False)
         f = io.StringIO()
         fig.savefig(f, format="svg")  # type: ignore
@@ -1628,7 +1628,7 @@ class ProcessCollection:
         title : str
             Figure title.
         """
-        fig, axes = plt.subplots(3, 1)
+        fig, axes = plt.subplots(3, 1, layout="constrained")
         self.plot_port_accesses(axes)
         if title:
             fig.suptitle(title)
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index a20410d40df40616eaa76d5fb9f96d696f715864..9f349f0bcbb01b674ab9bd666ef03b8996384921 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -18,7 +18,6 @@ from matplotlib.lines import Line2D
 from matplotlib.patches import PathPatch, Polygon
 from matplotlib.path import Path
 from matplotlib.ticker import MaxNLocator
-from matplotlib.transforms import Bbox, TransformedBbox
 
 from b_asic import Signal
 from b_asic._preferences import (
@@ -1067,24 +1066,66 @@ class Schedule:
         return operation_gap + y_location * (operation_height + operation_gap)
 
     def sort_y_locations_on_start_times(self):
+        """
+        Sort the y-locations of the schedule based on start times of the operations.
+
+        Inputs, outputs, dontcares, and sinks are located adjacent to the operations that
+        they are connected to.
+        """
         for i, graph_id in enumerate(
             sorted(self._start_times, key=self._start_times.get)
         ):
             self.set_y_location(graph_id, i)
         for graph_id in self._start_times:
             op = cast(Operation, self._sfg.find_by_id(graph_id))
-            if isinstance(op, Output):
-                self.move_y_location(
-                    graph_id,
-                    self.get_y_location(op.preceding_operations[0].graph_id) + 1,
-                    True,
-                )
-            if isinstance(op, DontCare):
-                self.move_y_location(
-                    graph_id,
-                    self.get_y_location(op.subsequent_operations[0].graph_id),
-                    True,
-                )
+            # Position Outputs and Sinks adjacent to the operation generating them
+            if isinstance(op, (Output, Sink)):
+                gen_op: Operation = op.preceding_operations[0]
+                if (
+                    gen_op.output_count == 1
+                    or op.input(0).signals[0].source.index >= gen_op.output_count / 2
+                ):
+                    # Single output operation generating or lower half of outputs
+                    # Put below
+                    self.move_y_location(
+                        graph_id,
+                        self.get_y_location(gen_op.graph_id) + 1,
+                        True,
+                    )
+                else:
+                    # Put above
+                    self.move_y_location(
+                        graph_id,
+                        self.get_y_location(gen_op.graph_id),
+                        True,
+                    )
+            # Position DontCares and Inputs adjacent to the operation using them
+            if isinstance(op, (DontCare, Input)):
+                # Find the "top" connected operation (for DontCare there is always one, but use general case there as well)
+                # TODO: Only check conconnected operations that do not have a lap
+                dest_ops = {
+                    sub_op: self.get_y_location(sub_op.graph_id)
+                    for sub_op in op.subsequent_operations
+                }
+                dest_op = min(dest_ops, key=dest_ops.get)
+                if (
+                    dest_op.input_count == 1
+                    or op.output(0).signals[0].destination.index
+                    < dest_op.input_count / 2
+                ):
+                    # For single input operation consuming or upper half, position above
+                    self.move_y_location(
+                        graph_id,
+                        self.get_y_location(dest_op.graph_id),
+                        True,
+                    )
+                else:
+                    # Position below
+                    self.move_y_location(
+                        graph_id,
+                        self.get_y_location(dest_op.graph_id) + 1,
+                        True,
+                    )
 
     def _plot_schedule(self, ax: Axes, operation_gap: float = OPERATION_GAP) -> None:
         """Draw the schedule."""
@@ -1192,12 +1233,10 @@ class Schedule:
             y = np.array(_y)
             xvalues = x + op_start_time
             xy = np.stack((xvalues, y + y_pos))
-            p = ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR))
-            p.set_clip_box(TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes))
+            ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR))
             if any(xvalues > self.schedule_time) and not isinstance(operation, Output):
                 xy = np.stack((xvalues - self.schedule_time, y + y_pos))
-                p = ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR))
-                p.set_clip_box(TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes))
+                ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR))
             if isinstance(operation, Input):
                 ax.annotate(
                     graph_id,
@@ -1224,7 +1263,7 @@ class Schedule:
                     linewidth=3,
                 )
                 if any(xvalues > self.schedule_time) and not isinstance(
-                    operation, Output
+                    operation, (Output, Sink)
                 ):
                     ax.plot(
                         xvalues - self.schedule_time,
@@ -1271,7 +1310,7 @@ class Schedule:
             + 1
             + (OPERATION_GAP if operation_gap is None else operation_gap)
         )
-        ax.axis([-1, self._schedule_time + 1, y_position_max, 0])  # Inverted y-axis
+        ax.axis([-0.8, self._schedule_time + 0.8, y_position_max, 0])  # Inverted y-axis
         ax.xaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=1))
         ax.axvline(
             0,
@@ -1335,8 +1374,8 @@ class Schedule:
         -------
         The Matplotlib Figure.
         """
-        height = len(self._start_times) * 0.3 + 2
-        fig, ax = plt.subplots(figsize=(12, height))
+        height = len(self._start_times) * 0.3 + 0.7
+        fig, ax = plt.subplots(figsize=(12, height), layout="constrained")
         self._plot_schedule(ax, operation_gap=operation_gap)
         return fig
 
@@ -1345,8 +1384,8 @@ class Schedule:
         Generate an SVG of the schedule. This is automatically displayed in e.g.
         Jupyter Qt console.
         """
-        height = len(self._start_times) * 0.3 + 2
-        fig, ax = plt.subplots(figsize=(12, height))
+        height = len(self._start_times) * 0.3 + 0.7
+        fig, ax = plt.subplots(figsize=(12, height), layout="constrained")
         self._plot_schedule(ax)
         buffer = io.StringIO()
         fig.savefig(buffer, format="svg")
diff --git a/examples/auto_scheduling_with_custom_io_times.py b/examples/auto_scheduling_with_custom_io_times.py
index 4df8bb7a332d2259b0163ded5952bc99cec0720b..d25fc8f1460b66244221d2b9a58cc5048f597ee5 100644
--- a/examples/auto_scheduling_with_custom_io_times.py
+++ b/examples/auto_scheduling_with_custom_io_times.py
@@ -27,15 +27,15 @@ sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
 
 # %%
 # Generate an ASAP schedule for reference.
-schedule = Schedule(sfg, scheduler=ASAPScheduler())
-schedule.show()
+schedule1 = Schedule(sfg, scheduler=ASAPScheduler())
+schedule1.show()
 
 # %%
 # Generate a non-cyclic Schedule from HybridScheduler with custom IO times.
 resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1}
 input_times = {f"in{i}": i for i in range(points)}
 output_delta_times = {f"out{i}": i for i in range(points)}
-schedule = Schedule(
+schedule2 = Schedule(
     sfg,
     scheduler=HybridScheduler(
         resources,
@@ -43,11 +43,11 @@ schedule = Schedule(
         output_delta_times=output_delta_times,
     ),
 )
-schedule.show()
+schedule2.show()
 
 # %%
 # Generate a new Schedule with cyclic scheduling enabled.
-schedule = Schedule(
+schedule3 = Schedule(
     sfg,
     scheduler=HybridScheduler(
         resources,
@@ -57,11 +57,11 @@ schedule = Schedule(
     schedule_time=14,
     cyclic=True,
 )
-schedule.show()
+schedule3.show()
 
 # %%
 # Generate a new Schedule with even less scheduling time.
-schedule = Schedule(
+schedule4 = Schedule(
     sfg,
     scheduler=HybridScheduler(
         resources,
@@ -71,11 +71,11 @@ schedule = Schedule(
     schedule_time=13,
     cyclic=True,
 )
-schedule.show()
+schedule4.show()
 
 # %%
 # Try scheduling for 12 cycles, which gives full butterfly usage.
-schedule = Schedule(
+schedule5 = Schedule(
     sfg,
     scheduler=HybridScheduler(
         resources,
@@ -85,4 +85,4 @@ schedule = Schedule(
     schedule_time=12,
     cyclic=True,
 )
-schedule.show()
+schedule5.show()
diff --git a/examples/latency_offset_scheduling.py b/examples/latency_offset_scheduling.py
index 39c21b72346350ef9af0336433dcd12922275cbc..67fff73b44c8388c5af004d3cf648c52a47224a8 100644
--- a/examples/latency_offset_scheduling.py
+++ b/examples/latency_offset_scheduling.py
@@ -28,35 +28,35 @@ sfg
 
 # %%
 # Create an ASAP schedule for reference.
-schedule = Schedule(sfg, scheduler=ASAPScheduler())
-schedule.show()
+schedule1 = Schedule(sfg, scheduler=ASAPScheduler())
+schedule1.show()
 
 # %%
 # Create an ALAP schedule for reference.
-schedule = Schedule(sfg, scheduler=ALAPScheduler())
-schedule.show()
+schedule2 = Schedule(sfg, scheduler=ALAPScheduler())
+schedule2.show()
 
 # %%
 # Create a resource restricted schedule.
-schedule = Schedule(sfg, scheduler=HybridScheduler())
-schedule.show()
+schedule3 = Schedule(sfg, scheduler=HybridScheduler())
+schedule3.show()
 
 # %%
 # Create another schedule with shorter scheduling time by enabling cyclic.
-schedule = Schedule(
+schedule4 = Schedule(
     sfg,
     scheduler=HybridScheduler(),
     schedule_time=49,
     cyclic=True,
 )
-schedule.show()
+schedule4.show()
 
 # %%
 # Push the schedule time to the rate limit for one MADS operator.
-schedule = Schedule(
+schedule5 = Schedule(
     sfg,
     scheduler=HybridScheduler(),
     schedule_time=15,
     cyclic=True,
 )
-schedule.show()
+schedule5.show()
diff --git a/examples/memory_constrained_scheduling.py b/examples/memory_constrained_scheduling.py
index c1fad1dd8dbd0e5cda6be3f99c9775675f53e3fe..c459a9720cf546ad3684d6975b94ea8f26d25877 100644
--- a/examples/memory_constrained_scheduling.py
+++ b/examples/memory_constrained_scheduling.py
@@ -28,22 +28,22 @@ sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
 
 # # %%
 # Generate an ASAP schedule for reference
-schedule = Schedule(sfg, scheduler=ASAPScheduler())
-schedule.show()
+schedule1 = Schedule(sfg, scheduler=ASAPScheduler())
+schedule1.show()
 
 # %%
 # Generate a PE constrained HybridSchedule
 resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1}
-schedule = Schedule(sfg, scheduler=HybridScheduler(resources))
-schedule.show()
+schedule2 = Schedule(sfg, scheduler=HybridScheduler(resources))
+schedule2.show()
 
 # %% Print the max number of read and write port accesses to non-direct memories
-direct, mem_vars = schedule.get_memory_variables().split_on_length()
+direct, mem_vars = schedule2.get_memory_variables().split_on_length()
 print("Max read ports:", mem_vars.read_ports_bound())
 print("Max write ports:", mem_vars.write_ports_bound())
 
 # %%
-operations = schedule.get_operations()
+operations = schedule2.get_operations()
 bfs = operations.get_by_type_name(Butterfly.type_name())
 bfs.show(title="Butterfly executions")
 const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
@@ -59,7 +59,7 @@ mul_pe = ProcessingElement(const_muls, entity_name="mul")
 pe_in = ProcessingElement(inputs, entity_name='input')
 pe_out = ProcessingElement(outputs, entity_name='output')
 
-mem_vars = schedule.get_memory_variables()
+mem_vars = schedule2.get_memory_variables()
 mem_vars.show(title="All memory variables")
 direct, mem_vars = mem_vars.split_on_length()
 mem_vars.show(title="Non-zero time memory variables")
@@ -89,21 +89,21 @@ arch
 # %%
 # Generate another HybridSchedule but this time constrain the amount of reads and writes to reduce the amount of memories
 resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1}
-schedule = Schedule(
+schedule3 = Schedule(
     sfg,
     scheduler=HybridScheduler(
         resources, max_concurrent_reads=2, max_concurrent_writes=2
     ),
 )
-schedule.show()
+schedule3.show()
 
 # %% Print the max number of read and write port accesses to non-direct memories
-direct, mem_vars = schedule.get_memory_variables().split_on_length()
+direct, mem_vars = schedule3.get_memory_variables().split_on_length()
 print("Max read ports:", mem_vars.read_ports_bound())
 print("Max write ports:", mem_vars.write_ports_bound())
 
 # %% Proceed to construct PEs and plot executions and non-direct memory variables
-operations = schedule.get_operations()
+operations = schedule3.get_operations()
 bfs = operations.get_by_type_name(Butterfly.type_name())
 bfs.show(title="Butterfly executions")
 const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
diff --git a/test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png b/test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png
index 79d2b4f1028378e9045b83c4a697ef6e00d801ec..4df499c2345dca70a074532c6af6050c2d91b9fc 100644
Binary files a/test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png and b/test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png differ
diff --git a/test/unit/test_schedule.py b/test/unit/test_schedule.py
index 6b499bd7b2fda26b744789f2ad59e9d922e74a54..f712a96e1879365b7188020c89dd5b28551af506 100644
--- a/test/unit/test_schedule.py
+++ b/test/unit/test_schedule.py
@@ -843,18 +843,18 @@ class TestYLocations:
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
         schedule = Schedule(sfg_simple_filter, ASAPScheduler())
 
-        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 2, "out0": 3}
+        assert schedule._y_locations == {"in0": 1, "cmul0": 0, "add0": 2, "out0": 3}
         schedule.move_y_location("add0", 1, insert=True)
-        assert schedule._y_locations == {"in0": 0, "cmul0": 2, "add0": 1, "out0": 3}
+        assert schedule._y_locations == {"in0": 2, "cmul0": 0, "add0": 1, "out0": 3}
         schedule.move_y_location("out0", 1)
-        assert schedule._y_locations == {"in0": 0, "cmul0": 2, "add0": 1, "out0": 1}
+        assert schedule._y_locations == {"in0": 2, "cmul0": 0, "add0": 1, "out0": 1}
 
     def test_reset(self, sfg_simple_filter):
         sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1)
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
         schedule = Schedule(sfg_simple_filter, ASAPScheduler())
 
-        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 2, "out0": 3}
+        assert schedule._y_locations == {"in0": 1, "cmul0": 0, "add0": 2, "out0": 3}
         schedule.reset_y_locations()
         assert schedule._y_locations["in0"] is None
         assert schedule._y_locations["cmul0"] is None