openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

198 lines
9.1 KiB

import dearpygui.dearpygui as dpg
from openpilot.tools.jotpluggler.data import DataManager
from openpilot.tools.jotpluggler.views import TimeSeriesPanel
class PlotLayoutManager:
def __init__(self, data_manager: DataManager, playback_manager, scale: float = 1.0):
self.data_manager = data_manager
self.playback_manager = playback_manager
self.scale = scale
self.container_tag = "plot_layout_container"
self.active_panels = []
initial_panel = TimeSeriesPanel(data_manager, playback_manager)
self.active_panels.append(initial_panel)
self.layout = {"type": "panel", "panel": initial_panel}
def create_ui(self, parent_tag: str):
if dpg.does_item_exist(self.container_tag):
dpg.delete_item(self.container_tag)
with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True):
container_width, container_height = dpg.get_item_rect_size(self.container_tag)
self._create_ui_recursive(self.layout, self.container_tag, [], container_width, container_height)
def on_viewport_resize(self):
self._resize_splits_recursive(self.layout, [])
def split_panel(self, panel_path: list[int], orientation: str):
current_layout = self._get_layout_at_path(panel_path)
existing_panel = current_layout["panel"]
new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager)
self.active_panels.append(new_panel)
parent, child_index = self._get_parent_and_index(panel_path)
if parent is None: # Root split
self.layout = {
"type": "split",
"orientation": orientation,
"children": [{"type": "panel", "panel": existing_panel}, {"type": "panel", "panel": new_panel}],
"proportions": [0.5, 0.5],
}
self._rebuild_ui_at_path([])
elif parent["type"] == "split" and parent["orientation"] == orientation:
parent["children"].insert(child_index + 1, {"type": "panel", "panel": new_panel})
parent["proportions"] = [1.0 / len(parent["children"])] * len(parent["children"])
self._rebuild_ui_at_path(panel_path[:-1])
else:
new_split = {"type": "split", "orientation": orientation, "children": [current_layout, {"type": "panel", "panel": new_panel}], "proportions": [0.5, 0.5]}
self._replace_layout_at_path(panel_path, new_split)
self._rebuild_ui_at_path(panel_path)
def delete_panel(self, panel_path: list[int]):
if not panel_path: # Root deletion
old_panel = self.layout["panel"]
old_panel.destroy_ui()
self.active_panels.remove(old_panel)
new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager)
self.active_panels.append(new_panel)
self.layout = {"type": "panel", "panel": new_panel}
self._rebuild_ui_at_path([])
return
parent, child_index = self._get_parent_and_index(panel_path)
layout_to_delete = parent["children"][child_index]
self._cleanup_ui_recursive(layout_to_delete)
parent["children"].pop(child_index)
parent["proportions"].pop(child_index)
if len(parent["children"]) == 1: # remove parent and collapse
remaining_child = parent["children"][0]
if len(panel_path) == 1: # parent is at root level - promote remaining child to root
self.layout = remaining_child
self._rebuild_ui_at_path([])
else: # replace parent with remaining child in grandparent
grandparent_path = panel_path[:-2]
parent_index = panel_path[-2]
self._replace_layout_at_path(grandparent_path + [parent_index], remaining_child)
self._rebuild_ui_at_path(grandparent_path + [parent_index])
else: # redistribute proportions
equal_prop = 1.0 / len(parent["children"])
parent["proportions"] = [equal_prop] * len(parent["children"])
self._rebuild_ui_at_path(panel_path[:-1])
def update_all_panels(self):
for panel in self.active_panels:
panel.update()
def _get_layout_at_path(self, path: list[int]) -> dict:
current = self.layout
for index in path:
current = current["children"][index]
return current
def _get_parent_and_index(self, path: list[int]) -> tuple:
return (None, -1) if not path else (self._get_layout_at_path(path[:-1]), path[-1])
def _replace_layout_at_path(self, path: list[int], new_layout: dict):
if not path:
self.layout = new_layout
else:
parent, index = self._get_parent_and_index(path)
parent["children"][index] = new_layout
def _path_to_tag(self, path: list[int], prefix: str = "") -> str:
path_str = "_".join(map(str, path)) if path else "root"
return f"{prefix}_{path_str}" if prefix else path_str
def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int):
if layout["type"] == "panel":
self._create_panel_ui(layout, parent_tag, path)
else:
self._create_split_ui(layout, parent_tag, path, width, height)
def _create_panel_ui(self, layout: dict, parent_tag: str, path: list[int]):
panel_tag = self._path_to_tag(path, "panel")
with dpg.child_window(tag=panel_tag, parent=parent_tag, border=True, width=-1, height=-1, no_scrollbar=True):
with dpg.group(horizontal=True):
dpg.add_input_text(default_value=layout["panel"].title, width=int(100 * self.scale), callback=lambda s, v: setattr(layout["panel"], "title", v))
dpg.add_combo(items=["Time Series"], default_value="Time Series", width=int(100 * self.scale))
dpg.add_button(label="Clear", callback=lambda: self._clear_panel(layout["panel"]), width=int(50 * self.scale))
dpg.add_button(label="Delete", callback=lambda: self.delete_panel(path), width=int(50 * self.scale))
dpg.add_button(label="Split H", callback=lambda: self.split_panel(path, "horizontal"), width=int(50 * self.scale))
dpg.add_button(label="Split V", callback=lambda: self.split_panel(path, "vertical"), width=int(50 * self.scale))
dpg.add_separator()
content_tag = self._path_to_tag(path, "content")
with dpg.child_window(tag=content_tag, border=False, height=-1, width=-1, no_scrollbar=True):
layout["panel"].create_ui(content_tag)
def _create_split_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int):
split_tag = self._path_to_tag(path, "split")
is_horizontal = layout["orientation"] == "horizontal"
with dpg.group(tag=split_tag, parent=parent_tag, horizontal=is_horizontal):
for i, (child_layout, proportion) in enumerate(zip(layout["children"], layout["proportions"], strict=True)):
child_path = path + [i]
container_tag = self._path_to_tag(child_path, "container")
if is_horizontal:
child_width = max(100, int(width * proportion))
with dpg.child_window(tag=container_tag, width=child_width, height=-1, border=False, no_scrollbar=True):
self._create_ui_recursive(child_layout, container_tag, child_path, child_width, height)
else:
child_height = max(100, int(height * proportion))
with dpg.child_window(tag=container_tag, width=-1, height=child_height, border=False, no_scrollbar=True):
self._create_ui_recursive(child_layout, container_tag, child_path, width, child_height)
def _rebuild_ui_at_path(self, path: list[int]):
layout = self._get_layout_at_path(path)
if not path: # Root update
dpg.delete_item(self.container_tag, children_only=True)
container_width, container_height = dpg.get_item_rect_size(self.container_tag)
self._create_ui_recursive(layout, self.container_tag, path, container_width, container_height)
else:
container_tag = self._path_to_tag(path, "container")
if dpg.does_item_exist(container_tag):
self._cleanup_ui_recursive(layout)
dpg.delete_item(container_tag, children_only=True)
width, height = dpg.get_item_rect_size(container_tag)
self._create_ui_recursive(layout, container_tag, path, width, height)
def _cleanup_ui_recursive(self, layout: dict):
if layout["type"] == "panel":
panel = layout["panel"]
panel.destroy_ui()
if panel in self.active_panels:
self.active_panels.remove(panel)
else:
for child in layout["children"]:
self._cleanup_ui_recursive(child)
def _resize_splits_recursive(self, layout: dict, path: list[int]):
if layout["type"] == "split":
split_tag = self._path_to_tag(path, "split")
if dpg.does_item_exist(split_tag):
parent_tag = dpg.get_item_parent(split_tag)
available_width, available_height = dpg.get_item_rect_size(parent_tag)
for i, proportion in enumerate(layout["proportions"]):
child_path = path + [i]
container_tag = self._path_to_tag(child_path, "container")
if dpg.does_item_exist(container_tag):
if layout["orientation"] == "horizontal":
dpg.configure_item(container_tag, width=max(100, int(available_width * proportion)))
else:
dpg.configure_item(container_tag, height=max(100, int(available_height * proportion)))
self._resize_splits_recursive(layout["children"][i], child_path)
def _clear_panel(self, panel):
if hasattr(panel, 'clear_all_series'):
panel.clear_all_series()