diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 2b725089deb0b41a7911b434783611d1a4da114a..1ada55a4bbf5f30a6ce0527e69c34bf0ce2deafd 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -360,6 +360,41 @@ class Schedule:
         self._schedule_time = self._schedule_time // factor
         return self
 
+    def move_y_location(self, graph_id: GraphID, new_y: int, insert: bool = False):
+        """
+        Move operation in y-direction and remove any empty rows.
+
+        Parameters
+        ----------
+        graph_id : GraphID
+            The GraphID of the operation to move.
+        new_y : int
+           The new y-position of the operation.
+        insert : bool, optional
+            If True, all operations on that y-position will be moved one position.
+            The default is False.
+
+        """
+        if insert:
+            for gid, y_location in self._y_locations.items():
+                if y_location >= new_y:
+                    self._y_locations[gid] += 1
+        self._y_locations[graph_id] = new_y
+        used_locations = {*self._y_locations.values()}
+        possible_locations = set(range(max(used_locations) + 1))
+        if not possible_locations - used_locations:
+            return
+        remapping = dict()
+        offset = 0
+        for loc in possible_locations:
+            if loc in used_locations:
+                remapping[loc] = loc - offset
+            else:
+                offset += 1
+
+        for gid, y_location in self._y_locations.items():
+            self._y_locations[gid] = remapping[self._y_locations[gid]]
+
     def move_operation(self, graph_id: GraphID, time: int) -> "Schedule":
         """
         Move an operation in the schedule.
diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index 8d56996ee9f47834d1dfb87ead0c0173cbd50dd5..28c6e5507154b42270a21e4656cf75d00d51c173 100644
--- a/b_asic/scheduler_gui/main_window.py
+++ b/b_asic/scheduler_gui/main_window.py
@@ -482,9 +482,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
         self._graph._signals.schedule_time_changed.connect(
             self.info_table_update_schedule
         )
+        self._graph._signals.redraw_all.connect(self._redraw_all)
         self.info_table_fill_schedule(self._schedule)
         self.update_statusbar(self.tr("Schedule loaded successfully"))
 
+    def _redraw_all(self) -> None:
+        self._graph._redraw_all()
+
     def update_statusbar(self, msg: str) -> None:
         """
         Write *msg* to the statusbar with temporarily policy.
diff --git a/b_asic/scheduler_gui/scheduler_event.py b/b_asic/scheduler_gui/scheduler_event.py
index 37add599d26dc9ddf1492c64e6179cf4f322be5b..22b17511d523bbbc875ace536f75fed68b2a2f7a 100644
--- a/b_asic/scheduler_gui/scheduler_event.py
+++ b/b_asic/scheduler_gui/scheduler_event.py
@@ -44,6 +44,7 @@ class SchedulerEvent:  # PyQt5
         component_selected = Signal(str)
         schedule_time_changed = Signal()
         component_moved = Signal(str)
+        redraw_all = Signal()
 
     _axes: Optional[AxesItem]
     _current_pos: QPointF
@@ -243,18 +244,22 @@ class SchedulerEvent:  # PyQt5
         if pos_x > self._schedule.schedule_time:
             pos_x = pos_x % self._schedule.schedule_time
             redraw = True
-        if self._schedule._y_locations[item.operation.graph_id] % 1:
-            # TODO: move other operations
-            self._schedule._y_locations[item.operation.graph_id] = math.ceil(
-                self._schedule._y_locations[item.operation.graph_id]
+        # Check move in y-direction
+        if (
+            self._schedule._y_locations[item.operation.graph_id]
+            != self._old_op_position
+        ):
+            self._schedule.move_y_location(
+                item.operation.graph_id,
+                math.ceil(self._schedule._y_locations[item.operation.graph_id]),
+                (self._schedule._y_locations[item.operation.graph_id] % 1) != 0,
             )
-            pos_y = item.y() + (OPERATION_GAP + OPERATION_HEIGHT) / 2
-            item.setY(pos_y)
-            redraw = True
+            self._signals.redraw_all.emit()
+        # Operation has been moved in x-direction
         if redraw:
             item.setX(pos_x)
             self._redraw_lines(item)
-        self._signals.component_moved.emit(item.graph_id)
+            self._signals.component_moved.emit(item.graph_id)
 
     def operation_mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None:
         ...
diff --git a/b_asic/scheduler_gui/scheduler_item.py b/b_asic/scheduler_gui/scheduler_item.py
index 0e8a04430598f900c56253be51c2782df3881b7b..6e23142689c8dfc8d9210b79f5c3b9ee39d6fcdf 100644
--- a/b_asic/scheduler_gui/scheduler_item.py
+++ b/b_asic/scheduler_gui/scheduler_item.py
@@ -233,6 +233,12 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup):  # PySide2 / PyQt5
             self._set_position(graph_id)
         self._redraw_all_lines()
 
+    def _redraw_all(self) -> None:
+        for graph_id in self._operation_items:
+            self._set_position(graph_id)
+        self._redraw_all_lines()
+        self._update_axes()
+
     def _update_axes(self, build=False) -> None:
         # build axes
         schedule_time = self.schedule.schedule_time