Skip to content

maplibre_widgets module

MapLibre widget classes for UI components.

This module contains the widget classes that support the MapLibreMap implementation, including layer management, styling, and container widgets.

Classes

CustomWidget: Generic expansion panel widget with dynamic content management. Container: Container widget for map display with optional sidebar. LayerStyleWidget: Interactive widget for styling map layers. LayerManagerWidget: Widget for managing map layers (visibility, opacity, removal).

These classes were extracted from maplibre.py to improve code organization and maintainability.

Container (Container)

A container widget for displaying a map with an optional sidebar.

This class creates a layout with a map on the left and a sidebar on the right. The sidebar can be toggled on or off and can display additional content.

Attributes:

Name Type Description
sidebar_visible bool

Whether the sidebar is visible.

min_width int

Minimum width of the sidebar in pixels.

max_width int

Maximum width of the sidebar in pixels.

map_container v.Col

The container for the map.

sidebar_content_box widgets.VBox

The container for the sidebar content.

toggle_icon v.Icon

The icon for the toggle button.

toggle_btn v.Btn

The button to toggle the sidebar.

sidebar v.Col

The container for the sidebar.

row v.Row

The main layout row containing the map and sidebar.

Source code in anymap/maplibre_widgets.py
class Container(v.Container):
    """
    A container widget for displaying a map with an optional sidebar.

    This class creates a layout with a map on the left and a sidebar on the right.
    The sidebar can be toggled on or off and can display additional content.

    Attributes:
        sidebar_visible (bool): Whether the sidebar is visible.
        min_width (int): Minimum width of the sidebar in pixels.
        max_width (int): Maximum width of the sidebar in pixels.
        map_container (v.Col): The container for the map.
        sidebar_content_box (widgets.VBox): The container for the sidebar content.
        toggle_icon (v.Icon): The icon for the toggle button.
        toggle_btn (v.Btn): The button to toggle the sidebar.
        sidebar (v.Col): The container for the sidebar.
        row (v.Row): The main layout row containing the map and sidebar.
    """

    def __init__(
        self,
        host_map: Optional[Any] = None,
        sidebar_visible: bool = True,
        min_width: int = 250,
        max_width: int = 300,
        sidebar_content: Optional[Union[widgets.VBox, List[widgets.Widget]]] = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        """
        Initializes the Container widget.

        Args:
            host_map (Optional[Any]): The map object to display in the container. Defaults to None.
            sidebar_visible (bool): Whether the sidebar is visible. Defaults to True.
            min_width (int): Minimum width of the sidebar in pixels. Defaults to 250.
            max_width (int): Maximum width of the sidebar in pixels. Defaults to 300.
            sidebar_content (Optional[Union[widgets.VBox, List[widgets.Widget]]]):
                The content to display in the sidebar. Defaults to None.
            *args (Any): Additional positional arguments for the parent class.
            **kwargs (Any): Additional keyword arguments for the parent class.
        """
        self.sidebar_visible = sidebar_visible
        self.min_width = min_width
        self.max_width = max_width
        self.host_map = host_map
        self.sidebar_widgets = {}

        # Map display container (left column)
        self.map_container = v.Col(
            class_="pa-1",
            style_="flex-grow: 1; flex-shrink: 1; flex-basis: 0;",
        )
        self.map_container.children = [host_map or self.create_map()]

        # Sidebar content container (mutable VBox)
        self.sidebar_content_box = widgets.VBox()
        if sidebar_content:
            self.set_sidebar_content(sidebar_content)

        # Toggle button
        if sidebar_visible:
            self.toggle_icon = v.Icon(children=["mdi-chevron-right"])
        else:
            self.toggle_icon = v.Icon(children=["mdi-chevron-left"])  # default icon
        self.toggle_btn = v.Btn(
            icon=True,
            children=[self.toggle_icon],
            style_="width: 48px; height: 48px; min-width: 48px;",
        )
        self.toggle_btn.on_event("click", self.toggle_sidebar)

        # Settings icon
        self.settings_icon = v.Icon(children=["mdi-wrench"])
        self.settings_btn = v.Btn(
            icon=True,
            children=[self.settings_icon],
            style_="width: 36px; height: 36px;",
        )
        self.settings_btn.on_event("click", self.toggle_width_slider)

        # Sidebar controls row (toggle + settings)
        self.sidebar_controls = v.Row(
            class_="ma-0 pa-0", children=[self.toggle_btn, self.settings_btn]
        )

        # Sidebar width slider (initially hidden)
        self.width_slider = widgets.IntSlider(
            value=self.max_width,
            min=200,
            max=1000,
            step=10,
            description="Width:",
            continuous_update=True,
        )
        self.width_slider.observe(self.on_width_change, names="value")

        self.settings_widget = CustomWidget(
            self.width_slider,
            widget_icon="mdi-cog",
            label="Sidebar Settings",
            host_map=self.host_map,
        )

        # Sidebar (right column)
        self.sidebar = v.Col(class_="pa-1", style_="overflow-y: hidden;")
        self.update_sidebar_content()

        # Main layout row
        self.row = v.Row(
            class_="d-flex flex-nowrap",
            children=[self.map_container, self.sidebar],
        )

        super().__init__(fluid=True, children=[self.row], *args, **kwargs)

    def create_map(self) -> Any:
        """
        Creates a default map object.

        Returns:
            Any: A default map object.
        """
        from .maplibre import MapLibreMap

        return MapLibreMap(center=[20, 0], zoom=2)

    def toggle_sidebar(self, *args: Any, **kwargs: Any) -> None:
        """
        Toggles the visibility of the sidebar.

        Args:
            *args (Any): Additional positional arguments.
            **kwargs (Any): Additional keyword arguments.
        """
        self.sidebar_visible = not self.sidebar_visible
        self.toggle_icon.children = [
            "mdi-chevron-right" if self.sidebar_visible else "mdi-chevron-left"
        ]
        self.update_sidebar_content()

    def update_sidebar_content(self) -> None:
        """
        Updates the content of the sidebar based on its visibility.
        If the sidebar is visible, it displays the toggle button and the sidebar content.
        If the sidebar is hidden, it only displays the toggle button.
        """
        if self.sidebar_visible:
            # Header row: toggle on the left, settings on the right
            header_row = v.Row(
                class_="ma-0 pa-0",
                align="center",
                justify="space-between",
                children=[self.toggle_btn, self.settings_btn],
            )

            children = [header_row]

            children.append(self.sidebar_content_box)

            self.sidebar.children = children
            self.sidebar.style_ = (
                f"min-width: {self.min_width}px; max-width: {self.max_width}px;"
            )
        else:
            self.sidebar.children = [self.toggle_btn]
            self.sidebar.style_ = "width: 48px; min-width: 48px; max-width: 48px;"

    def set_sidebar_content(
        self, content: Union[widgets.VBox, List[widgets.Widget]]
    ) -> None:
        """
        Replaces all content in the sidebar (except the toggle button).

        Args:
            content (Union[widgets.VBox, List[widgets.Widget]]): The new content for the sidebar.
        """
        if isinstance(content, (list, tuple)):
            self.sidebar_content_box.children = content
        else:
            self.sidebar_content_box.children = [content]

    def add_to_sidebar(
        self,
        widget: widgets.Widget,
        add_header: bool = True,
        widget_icon: str = "mdi-tools",
        close_icon: str = "mdi-close",
        label: str = "My Tools",
        background_color: str = "#f5f5f5",
        height: str = "40px",
        expanded: bool = True,
        host_map: Optional[Any] = None,
        **kwargs: Any,
    ) -> None:
        """
        Appends a widget to the sidebar content.

        Args:
            widget (Optional[Union[widgets.Widget, List[widgets.Widget]]]): Initial widget(s) to display in the content box.
            widget_icon (str): Icon for the header. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
            close_icon (str): Icon for the close button. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
            background_color (str): Background color of the header. Defaults to "#f5f5f5".
            label (str): Text label for the header. Defaults to "My Tools".
            height (str): Height of the header. Defaults to "40px".
            expanded (bool): Whether the panel is expanded by default. Defaults to True.
            *args (Any): Additional positional arguments for the parent class.
            **kwargs (Any): Additional keyword arguments for the parent class.
        """

        if label in self.sidebar_widgets:
            self.remove_from_sidebar(name=label)

        if add_header:
            widget = CustomWidget(
                widget,
                widget_icon=widget_icon,
                close_icon=close_icon,
                label=label,
                background_color=background_color,
                height=height,
                expanded=expanded,
                host_map=host_map,
                **kwargs,
            )

        self.sidebar_content_box.children += (widget,)
        self.sidebar_widgets[label] = widget

    def remove_from_sidebar(
        self, widget: widgets.Widget = None, name: str = None
    ) -> None:
        """
        Removes a widget from the sidebar content.

        Args:
            widget (widgets.Widget): The widget to remove from the sidebar.
            name (str): The name of the widget to remove from the sidebar.
        """
        key = None
        for key, value in self.sidebar_widgets.items():
            if value == widget or key == name:
                if widget is None:
                    widget = self.sidebar_widgets[key]
                break

        if key is not None and key in self.sidebar_widgets:
            self.sidebar_widgets.pop(key)
        self.sidebar_content_box.children = tuple(
            child for child in self.sidebar_content_box.children if child != widget
        )

    def set_sidebar_width(self, min_width: int = None, max_width: int = None) -> None:
        """
        Dynamically updates the sidebar's minimum and maximum width.

        Args:
            min_width (int, optional): New minimum width in pixels. If None, keep current.
            max_width (int, optional): New maximum width in pixels. If None, keep current.
        """
        if min_width is not None:
            if isinstance(min_width, str):
                min_width = int(min_width.replace("px", ""))
            self.min_width = min_width
        if max_width is not None:
            if isinstance(max_width, str):
                max_width = int(max_width.replace("px", ""))
            self.max_width = max_width
        self.update_sidebar_content()

    def toggle_width_slider(self, *args: Any) -> None:

        if self.settings_widget not in self.sidebar_content_box.children:
            self.add_to_sidebar(self.settings_widget, add_header=False)

    def on_width_change(self, change: dict) -> None:
        new_width = change["new"]
        self.set_sidebar_width(min_width=new_width, max_width=new_width)

__init__(self, host_map=None, sidebar_visible=True, min_width=250, max_width=300, sidebar_content=None, *args, **kwargs) special

Initializes the Container widget.

Parameters:

Name Type Description Default
host_map Optional[Any]

The map object to display in the container. Defaults to None.

None
sidebar_visible bool

Whether the sidebar is visible. Defaults to True.

True
min_width int

Minimum width of the sidebar in pixels. Defaults to 250.

250
max_width int

Maximum width of the sidebar in pixels. Defaults to 300.

300
sidebar_content Optional[Union[widgets.VBox, List[widgets.Widget]]]

The content to display in the sidebar. Defaults to None.

None
*args Any

Additional positional arguments for the parent class.

()
**kwargs Any

Additional keyword arguments for the parent class.

{}
Source code in anymap/maplibre_widgets.py
def __init__(
    self,
    host_map: Optional[Any] = None,
    sidebar_visible: bool = True,
    min_width: int = 250,
    max_width: int = 300,
    sidebar_content: Optional[Union[widgets.VBox, List[widgets.Widget]]] = None,
    *args: Any,
    **kwargs: Any,
) -> None:
    """
    Initializes the Container widget.

    Args:
        host_map (Optional[Any]): The map object to display in the container. Defaults to None.
        sidebar_visible (bool): Whether the sidebar is visible. Defaults to True.
        min_width (int): Minimum width of the sidebar in pixels. Defaults to 250.
        max_width (int): Maximum width of the sidebar in pixels. Defaults to 300.
        sidebar_content (Optional[Union[widgets.VBox, List[widgets.Widget]]]):
            The content to display in the sidebar. Defaults to None.
        *args (Any): Additional positional arguments for the parent class.
        **kwargs (Any): Additional keyword arguments for the parent class.
    """
    self.sidebar_visible = sidebar_visible
    self.min_width = min_width
    self.max_width = max_width
    self.host_map = host_map
    self.sidebar_widgets = {}

    # Map display container (left column)
    self.map_container = v.Col(
        class_="pa-1",
        style_="flex-grow: 1; flex-shrink: 1; flex-basis: 0;",
    )
    self.map_container.children = [host_map or self.create_map()]

    # Sidebar content container (mutable VBox)
    self.sidebar_content_box = widgets.VBox()
    if sidebar_content:
        self.set_sidebar_content(sidebar_content)

    # Toggle button
    if sidebar_visible:
        self.toggle_icon = v.Icon(children=["mdi-chevron-right"])
    else:
        self.toggle_icon = v.Icon(children=["mdi-chevron-left"])  # default icon
    self.toggle_btn = v.Btn(
        icon=True,
        children=[self.toggle_icon],
        style_="width: 48px; height: 48px; min-width: 48px;",
    )
    self.toggle_btn.on_event("click", self.toggle_sidebar)

    # Settings icon
    self.settings_icon = v.Icon(children=["mdi-wrench"])
    self.settings_btn = v.Btn(
        icon=True,
        children=[self.settings_icon],
        style_="width: 36px; height: 36px;",
    )
    self.settings_btn.on_event("click", self.toggle_width_slider)

    # Sidebar controls row (toggle + settings)
    self.sidebar_controls = v.Row(
        class_="ma-0 pa-0", children=[self.toggle_btn, self.settings_btn]
    )

    # Sidebar width slider (initially hidden)
    self.width_slider = widgets.IntSlider(
        value=self.max_width,
        min=200,
        max=1000,
        step=10,
        description="Width:",
        continuous_update=True,
    )
    self.width_slider.observe(self.on_width_change, names="value")

    self.settings_widget = CustomWidget(
        self.width_slider,
        widget_icon="mdi-cog",
        label="Sidebar Settings",
        host_map=self.host_map,
    )

    # Sidebar (right column)
    self.sidebar = v.Col(class_="pa-1", style_="overflow-y: hidden;")
    self.update_sidebar_content()

    # Main layout row
    self.row = v.Row(
        class_="d-flex flex-nowrap",
        children=[self.map_container, self.sidebar],
    )

    super().__init__(fluid=True, children=[self.row], *args, **kwargs)

add_to_sidebar(self, widget, add_header=True, widget_icon='mdi-tools', close_icon='mdi-close', label='My Tools', background_color='#f5f5f5', height='40px', expanded=True, host_map=None, **kwargs)

Appends a widget to the sidebar content.

Parameters:

Name Type Description Default
widget Optional[Union[widgets.Widget, List[widgets.Widget]]]

Initial widget(s) to display in the content box.

required
widget_icon str

Icon for the header. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.

'mdi-tools'
close_icon str

Icon for the close button. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.

'mdi-close'
background_color str

Background color of the header. Defaults to "#f5f5f5".

'#f5f5f5'
label str

Text label for the header. Defaults to "My Tools".

'My Tools'
height str

Height of the header. Defaults to "40px".

'40px'
expanded bool

Whether the panel is expanded by default. Defaults to True.

True
*args Any

Additional positional arguments for the parent class.

required
**kwargs Any

Additional keyword arguments for the parent class.

{}
Source code in anymap/maplibre_widgets.py
def add_to_sidebar(
    self,
    widget: widgets.Widget,
    add_header: bool = True,
    widget_icon: str = "mdi-tools",
    close_icon: str = "mdi-close",
    label: str = "My Tools",
    background_color: str = "#f5f5f5",
    height: str = "40px",
    expanded: bool = True,
    host_map: Optional[Any] = None,
    **kwargs: Any,
) -> None:
    """
    Appends a widget to the sidebar content.

    Args:
        widget (Optional[Union[widgets.Widget, List[widgets.Widget]]]): Initial widget(s) to display in the content box.
        widget_icon (str): Icon for the header. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
        close_icon (str): Icon for the close button. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
        background_color (str): Background color of the header. Defaults to "#f5f5f5".
        label (str): Text label for the header. Defaults to "My Tools".
        height (str): Height of the header. Defaults to "40px".
        expanded (bool): Whether the panel is expanded by default. Defaults to True.
        *args (Any): Additional positional arguments for the parent class.
        **kwargs (Any): Additional keyword arguments for the parent class.
    """

    if label in self.sidebar_widgets:
        self.remove_from_sidebar(name=label)

    if add_header:
        widget = CustomWidget(
            widget,
            widget_icon=widget_icon,
            close_icon=close_icon,
            label=label,
            background_color=background_color,
            height=height,
            expanded=expanded,
            host_map=host_map,
            **kwargs,
        )

    self.sidebar_content_box.children += (widget,)
    self.sidebar_widgets[label] = widget

create_map(self)

Creates a default map object.

Returns:

Type Description
Any

A default map object.

Source code in anymap/maplibre_widgets.py
def create_map(self) -> Any:
    """
    Creates a default map object.

    Returns:
        Any: A default map object.
    """
    from .maplibre import MapLibreMap

    return MapLibreMap(center=[20, 0], zoom=2)

remove_from_sidebar(self, widget=None, name=None)

Removes a widget from the sidebar content.

Parameters:

Name Type Description Default
widget widgets.Widget

The widget to remove from the sidebar.

None
name str

The name of the widget to remove from the sidebar.

None
Source code in anymap/maplibre_widgets.py
def remove_from_sidebar(
    self, widget: widgets.Widget = None, name: str = None
) -> None:
    """
    Removes a widget from the sidebar content.

    Args:
        widget (widgets.Widget): The widget to remove from the sidebar.
        name (str): The name of the widget to remove from the sidebar.
    """
    key = None
    for key, value in self.sidebar_widgets.items():
        if value == widget or key == name:
            if widget is None:
                widget = self.sidebar_widgets[key]
            break

    if key is not None and key in self.sidebar_widgets:
        self.sidebar_widgets.pop(key)
    self.sidebar_content_box.children = tuple(
        child for child in self.sidebar_content_box.children if child != widget
    )

set_sidebar_content(self, content)

Replaces all content in the sidebar (except the toggle button).

Parameters:

Name Type Description Default
content Union[widgets.VBox, List[widgets.Widget]]

The new content for the sidebar.

required
Source code in anymap/maplibre_widgets.py
def set_sidebar_content(
    self, content: Union[widgets.VBox, List[widgets.Widget]]
) -> None:
    """
    Replaces all content in the sidebar (except the toggle button).

    Args:
        content (Union[widgets.VBox, List[widgets.Widget]]): The new content for the sidebar.
    """
    if isinstance(content, (list, tuple)):
        self.sidebar_content_box.children = content
    else:
        self.sidebar_content_box.children = [content]

set_sidebar_width(self, min_width=None, max_width=None)

Dynamically updates the sidebar's minimum and maximum width.

Parameters:

Name Type Description Default
min_width int

New minimum width in pixels. If None, keep current.

None
max_width int

New maximum width in pixels. If None, keep current.

None
Source code in anymap/maplibre_widgets.py
def set_sidebar_width(self, min_width: int = None, max_width: int = None) -> None:
    """
    Dynamically updates the sidebar's minimum and maximum width.

    Args:
        min_width (int, optional): New minimum width in pixels. If None, keep current.
        max_width (int, optional): New maximum width in pixels. If None, keep current.
    """
    if min_width is not None:
        if isinstance(min_width, str):
            min_width = int(min_width.replace("px", ""))
        self.min_width = min_width
    if max_width is not None:
        if isinstance(max_width, str):
            max_width = int(max_width.replace("px", ""))
        self.max_width = max_width
    self.update_sidebar_content()

toggle_sidebar(self, *args, **kwargs)

Toggles the visibility of the sidebar.

Parameters:

Name Type Description Default
*args Any

Additional positional arguments.

()
**kwargs Any

Additional keyword arguments.

{}
Source code in anymap/maplibre_widgets.py
def toggle_sidebar(self, *args: Any, **kwargs: Any) -> None:
    """
    Toggles the visibility of the sidebar.

    Args:
        *args (Any): Additional positional arguments.
        **kwargs (Any): Additional keyword arguments.
    """
    self.sidebar_visible = not self.sidebar_visible
    self.toggle_icon.children = [
        "mdi-chevron-right" if self.sidebar_visible else "mdi-chevron-left"
    ]
    self.update_sidebar_content()

update_sidebar_content(self)

Updates the content of the sidebar based on its visibility. If the sidebar is visible, it displays the toggle button and the sidebar content. If the sidebar is hidden, it only displays the toggle button.

Source code in anymap/maplibre_widgets.py
def update_sidebar_content(self) -> None:
    """
    Updates the content of the sidebar based on its visibility.
    If the sidebar is visible, it displays the toggle button and the sidebar content.
    If the sidebar is hidden, it only displays the toggle button.
    """
    if self.sidebar_visible:
        # Header row: toggle on the left, settings on the right
        header_row = v.Row(
            class_="ma-0 pa-0",
            align="center",
            justify="space-between",
            children=[self.toggle_btn, self.settings_btn],
        )

        children = [header_row]

        children.append(self.sidebar_content_box)

        self.sidebar.children = children
        self.sidebar.style_ = (
            f"min-width: {self.min_width}px; max-width: {self.max_width}px;"
        )
    else:
        self.sidebar.children = [self.toggle_btn]
        self.sidebar.style_ = "width: 48px; min-width: 48px; max-width: 48px;"

CustomWidget (ExpansionPanels)

A custom expansion panel widget with dynamic widget management.

This widget allows for the creation of an expandable panel with a customizable header and dynamic content. Widgets can be added, removed, or replaced in the content box.

Attributes:

Name Type Description
content_box widgets.VBox

A container for holding the widgets displayed in the panel.

panel v.ExpansionPanel

The main expansion panel containing the header and content.

Source code in anymap/maplibre_widgets.py
class CustomWidget(v.ExpansionPanels):
    """
    A custom expansion panel widget with dynamic widget management.

    This widget allows for the creation of an expandable panel with a customizable header
    and dynamic content. Widgets can be added, removed, or replaced in the content box.

    Attributes:
        content_box (widgets.VBox): A container for holding the widgets displayed in the panel.
        panel (v.ExpansionPanel): The main expansion panel containing the header and content.
    """

    def __init__(
        self,
        widget: Optional[Union[widgets.Widget, List[widgets.Widget]]] = None,
        widget_icon: str = "mdi-tools",
        close_icon: str = "mdi-close",
        label: str = "My Tools",
        background_color: str = "#f5f5f5",
        height: str = "40px",
        expanded: bool = True,
        host_map: Optional[Any] = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        """
        Initializes the CustomWidget.

        Args:
            widget (Optional[Union[widgets.Widget, List[widgets.Widget]]]): Initial widget(s) to display in the content box.
            widget_icon (str): Icon for the header. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
            close_icon (str): Icon for the close button. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
            background_color (str): Background color of the header. Defaults to "#f5f5f5".
            label (str): Text label for the header. Defaults to "My Tools".
            height (str): Height of the header. Defaults to "40px".
            expanded (bool): Whether the panel is expanded by default. Defaults to True.
            *args (Any): Additional positional arguments for the parent class.
            **kwargs (Any): Additional keyword arguments for the parent class.
        """
        # Wrap content in a mutable VBox
        self.content_box = widgets.VBox()
        self.host_map = host_map
        if widget:
            if isinstance(widget, (list, tuple)):
                self.content_box.children = widget
            else:
                self.content_box.children = [widget]

        # Close icon button
        close_btn = v.Btn(
            icon=True,
            small=True,
            class_="ma-0",
            style_="min-width: 24px; width: 24px;",
            children=[v.Icon(children=[close_icon])],
        )
        close_btn.on_event("click", self._handle_close)

        header = v.ExpansionPanelHeader(
            style_=f"height: {height}; min-height: {height}; background-color: {background_color};",
            children=[
                v.Row(
                    align="center",
                    class_="d-flex flex-grow-1 align-center",
                    children=[
                        v.Icon(children=[widget_icon], class_="ml-1"),
                        v.Spacer(),  # push title to center
                        v.Html(tag="span", children=[label], class_="text-subtitle-2"),
                        v.Spacer(),  # push close to right
                        close_btn,
                        v.Spacer(),
                    ],
                )
            ],
        )

        self.panel = v.ExpansionPanel(
            children=[
                header,
                v.ExpansionPanelContent(children=[self.content_box]),
            ]
        )

        super().__init__(
            children=[self.panel],
            v_model=[0] if expanded else [],
            multiple=True,
            *args,
            **kwargs,
        )

    def _handle_close(self, widget=None, event=None, data=None):
        """Calls the on_close callback if provided."""

        if self.host_map is not None:
            self.host_map.remove_from_sidebar(self)
        # self.close()

    def add_widget(self, widget: widgets.Widget) -> None:
        """
        Adds a widget to the content box.

        Args:
            widget (widgets.Widget): The widget to add to the content box.
        """
        self.content_box.children += (widget,)

    def remove_widget(self, widget: widgets.Widget) -> None:
        """
        Removes a widget from the content box.

        Args:
            widget (widgets.Widget): The widget to remove from the content box.
        """
        self.content_box.children = tuple(
            w for w in self.content_box.children if w != widget
        )

    def set_widgets(self, widgets_list: List[widgets.Widget]) -> None:
        """
        Replaces all widgets in the content box.

        Args:
            widgets_list (List[widgets.Widget]): A list of widgets to set as the content of the content box.
        """
        self.content_box.children = widgets_list

__init__(self, widget=None, widget_icon='mdi-tools', close_icon='mdi-close', label='My Tools', background_color='#f5f5f5', height='40px', expanded=True, host_map=None, *args, **kwargs) special

Initializes the CustomWidget.

Parameters:

Name Type Description Default
widget Optional[Union[widgets.Widget, List[widgets.Widget]]]

Initial widget(s) to display in the content box.

None
widget_icon str

Icon for the header. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.

'mdi-tools'
close_icon str

Icon for the close button. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.

'mdi-close'
background_color str

Background color of the header. Defaults to "#f5f5f5".

'#f5f5f5'
label str

Text label for the header. Defaults to "My Tools".

'My Tools'
height str

Height of the header. Defaults to "40px".

'40px'
expanded bool

Whether the panel is expanded by default. Defaults to True.

True
*args Any

Additional positional arguments for the parent class.

()
**kwargs Any

Additional keyword arguments for the parent class.

{}
Source code in anymap/maplibre_widgets.py
def __init__(
    self,
    widget: Optional[Union[widgets.Widget, List[widgets.Widget]]] = None,
    widget_icon: str = "mdi-tools",
    close_icon: str = "mdi-close",
    label: str = "My Tools",
    background_color: str = "#f5f5f5",
    height: str = "40px",
    expanded: bool = True,
    host_map: Optional[Any] = None,
    *args: Any,
    **kwargs: Any,
) -> None:
    """
    Initializes the CustomWidget.

    Args:
        widget (Optional[Union[widgets.Widget, List[widgets.Widget]]]): Initial widget(s) to display in the content box.
        widget_icon (str): Icon for the header. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
        close_icon (str): Icon for the close button. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons.
        background_color (str): Background color of the header. Defaults to "#f5f5f5".
        label (str): Text label for the header. Defaults to "My Tools".
        height (str): Height of the header. Defaults to "40px".
        expanded (bool): Whether the panel is expanded by default. Defaults to True.
        *args (Any): Additional positional arguments for the parent class.
        **kwargs (Any): Additional keyword arguments for the parent class.
    """
    # Wrap content in a mutable VBox
    self.content_box = widgets.VBox()
    self.host_map = host_map
    if widget:
        if isinstance(widget, (list, tuple)):
            self.content_box.children = widget
        else:
            self.content_box.children = [widget]

    # Close icon button
    close_btn = v.Btn(
        icon=True,
        small=True,
        class_="ma-0",
        style_="min-width: 24px; width: 24px;",
        children=[v.Icon(children=[close_icon])],
    )
    close_btn.on_event("click", self._handle_close)

    header = v.ExpansionPanelHeader(
        style_=f"height: {height}; min-height: {height}; background-color: {background_color};",
        children=[
            v.Row(
                align="center",
                class_="d-flex flex-grow-1 align-center",
                children=[
                    v.Icon(children=[widget_icon], class_="ml-1"),
                    v.Spacer(),  # push title to center
                    v.Html(tag="span", children=[label], class_="text-subtitle-2"),
                    v.Spacer(),  # push close to right
                    close_btn,
                    v.Spacer(),
                ],
            )
        ],
    )

    self.panel = v.ExpansionPanel(
        children=[
            header,
            v.ExpansionPanelContent(children=[self.content_box]),
        ]
    )

    super().__init__(
        children=[self.panel],
        v_model=[0] if expanded else [],
        multiple=True,
        *args,
        **kwargs,
    )

add_widget(self, widget)

Adds a widget to the content box.

Parameters:

Name Type Description Default
widget widgets.Widget

The widget to add to the content box.

required
Source code in anymap/maplibre_widgets.py
def add_widget(self, widget: widgets.Widget) -> None:
    """
    Adds a widget to the content box.

    Args:
        widget (widgets.Widget): The widget to add to the content box.
    """
    self.content_box.children += (widget,)

remove_widget(self, widget)

Removes a widget from the content box.

Parameters:

Name Type Description Default
widget widgets.Widget

The widget to remove from the content box.

required
Source code in anymap/maplibre_widgets.py
def remove_widget(self, widget: widgets.Widget) -> None:
    """
    Removes a widget from the content box.

    Args:
        widget (widgets.Widget): The widget to remove from the content box.
    """
    self.content_box.children = tuple(
        w for w in self.content_box.children if w != widget
    )

set_widgets(self, widgets_list)

Replaces all widgets in the content box.

Parameters:

Name Type Description Default
widgets_list List[widgets.Widget]

A list of widgets to set as the content of the content box.

required
Source code in anymap/maplibre_widgets.py
def set_widgets(self, widgets_list: List[widgets.Widget]) -> None:
    """
    Replaces all widgets in the content box.

    Args:
        widgets_list (List[widgets.Widget]): A list of widgets to set as the content of the content box.
    """
    self.content_box.children = widgets_list

LayerManagerWidget (ExpansionPanels)

A widget for managing map layers.

This widget provides controls for toggling the visibility, adjusting the opacity, and removing layers from a map. It also includes a master toggle to turn all layers on or off.

Attributes:

Name Type Description
m Map

The map object to manage layers for.

layer_items Dict[str, Dict[str, widgets.Widget]]

A dictionary mapping layer names to their corresponding control widgets (checkbox and slider).

_building bool

A flag indicating whether the widget is currently being built.

master_toggle widgets.Checkbox

A checkbox to toggle all layers on or off.

layers_box widgets.VBox

A container for individual layer controls.

Source code in anymap/maplibre_widgets.py
class LayerManagerWidget(v.ExpansionPanels):
    """
    A widget for managing map layers.

    This widget provides controls for toggling the visibility, adjusting the opacity,
    and removing layers from a map. It also includes a master toggle to turn all layers
    on or off.

    Attributes:
        m (Map): The map object to manage layers for.
        layer_items (Dict[str, Dict[str, widgets.Widget]]): A dictionary mapping layer names
            to their corresponding control widgets (checkbox and slider).
        _building (bool): A flag indicating whether the widget is currently being built.
        master_toggle (widgets.Checkbox): A checkbox to toggle all layers on or off.
        layers_box (widgets.VBox): A container for individual layer controls.
    """

    def __init__(
        self,
        m: Any,
        expanded: bool = True,
        height: str = "40px",
        layer_icon: str = "mdi-layers",
        close_icon: str = "mdi-close",
        label="Layers",
        background_color: str = "#f5f5f5",
        groups: dict = None,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        """
        Initializes the LayerManagerWidget.

        Args:
            m (Any): The map object to manage layers for.
            expanded (bool): Whether the expansion panel should be expanded by default. Defaults to True.
            height (str): The height of the header. Defaults to "40px".
            layer_icon (str): The icon for the layer manager. Defaults to "mdi-layers".
            close_icon (str): The icon for the close button. Defaults to "mdi-close".
            label (str): The label for the layer manager. Defaults to "Layers".
            background_color (str): The background color of the header. Defaults to "#f5f5f5".
            groups (dict): A dictionary of layer groups, such as {"Group 1": ["layer1", "layer2"],
                "Group 2": ["layer3", "layer4"]}. A group layer toggle will be created for each group.
                Defaults to None.
            *args (Any): Additional positional arguments for the parent class.
            **kwargs (Any): Additional keyword arguments for the parent class.
        """
        self.m = m
        self.layer_items = {}
        self.groups = groups
        self._building = False

        # Master toggle
        style = {"description_width": "initial"}
        self.master_toggle = widgets.Checkbox(
            value=True, description="All layers on/off", style=style
        )
        self.master_toggle.observe(self.toggle_all_layers, names="value")

        self.group_toggles = widgets.VBox()
        if isinstance(groups, dict):
            for group_name, group_layers in groups.items():
                group_toggle = widgets.Checkbox(
                    value=True,
                    description=f"{group_name} group layers on/off",
                    style=style,
                )
                group_toggle.observe(self.toggle_group_layers, names="value")
                self.group_toggles.children += (group_toggle,)

        # Build individual layer rows
        self.layers_box = widgets.VBox()
        self.build_layer_controls()

        # Close icon button
        close_btn = v.Btn(
            icon=True,
            small=True,
            class_="ma-0",
            style_="min-width: 24px; width: 24px;",
            children=[v.Icon(children=[close_icon])],
        )
        close_btn.on_event("click", self._handle_close)

        header = v.ExpansionPanelHeader(
            style_=f"height: {height}; min-height: {height}; background-color: {background_color};",
            children=[
                v.Row(
                    align="center",
                    class_="d-flex flex-grow-1 align-center",
                    children=[
                        v.Icon(children=[layer_icon], class_="ml-1"),
                        v.Spacer(),  # push title to center
                        v.Html(tag="span", children=[label], class_="text-subtitle-2"),
                        v.Spacer(),  # push close to right
                        close_btn,
                        v.Spacer(),
                    ],
                )
            ],
        )

        panel = v.ExpansionPanel(
            children=[
                header,
                v.ExpansionPanelContent(
                    children=[
                        widgets.VBox(
                            [self.master_toggle, self.group_toggles, self.layers_box]
                        )
                    ]
                ),
            ]
        )

        if expanded:
            super().__init__(
                children=[panel], v_model=[0], multiple=True, *args, **kwargs
            )
        else:
            super().__init__(children=[panel], multiple=True, *args, **kwargs)

    def _handle_close(self, widget=None, event=None, data=None):
        """Calls the on_close callback if provided."""

        self.m.remove_from_sidebar(self)
        # self.close()

    def build_layer_controls(self) -> None:
        """
        Builds the controls for individual layers.

        This method creates checkboxes for toggling visibility, sliders for adjusting opacity,
        and buttons for removing layers.
        """
        self._building = True
        self.layer_items.clear()
        rows = []

        style = {"description_width": "initial"}
        padding = "0px 5px 0px 5px"

        for name, info in list(self.m.layer_dict.items()):
            # if name == "Background":
            #     continue

            visible = info.get("visible", True)
            opacity = info.get("opacity", 1.0)

            checkbox = widgets.Checkbox(value=visible, description=name, style=style)
            checkbox.layout.max_width = "150px"

            slider = widgets.FloatSlider(
                value=opacity,
                min=0,
                max=1,
                step=0.01,
                readout=False,
                tooltip="Change layer opacity",
                layout=widgets.Layout(width="150px", padding=padding),
            )

            settings = widgets.Button(
                icon="gear",
                tooltip="Change layer style",
                layout=widgets.Layout(width="38px", height="25px", padding=padding),
            )

            remove = widgets.Button(
                icon="times",
                tooltip="Remove layer",
                layout=widgets.Layout(width="38px", height="25px", padding=padding),
            )

            def on_visibility_change(change, layer_name=name):
                self.set_layer_visibility(layer_name, change["new"])

            def on_opacity_change(change, layer_name=name):
                self.set_layer_opacity(layer_name, change["new"])

            def on_remove_clicked(btn, layer_name=name, row_ref=None):
                if layer_name == "Background":
                    for layer in self.m.get_style_layers():
                        self.m.add_call("removeLayer", layer["id"])
                else:
                    self.m.remove_layer(layer_name)
                if row_ref in self.layers_box.children:
                    self.layers_box.children = tuple(
                        c for c in self.layers_box.children if c != row_ref
                    )
                self.layer_items.pop(layer_name, None)
                if f"Style {layer_name}" in self.m.sidebar_widgets:
                    self.m.remove_from_sidebar(name=f"Style {layer_name}")

            def on_settings_clicked(btn, layer_name=name):
                style_widget = LayerStyleWidget(self.m.layer_dict[layer_name], self.m)
                self.m.add_to_sidebar(
                    style_widget,
                    widget_icon="mdi-palette",
                    label=f"Style {layer_name}",
                )

            checkbox.observe(on_visibility_change, names="value")
            slider.observe(on_opacity_change, names="value")

            row = widgets.HBox(
                [checkbox, slider, settings, remove], layout=widgets.Layout()
            )

            remove.on_click(
                lambda btn, r=row, n=name: on_remove_clicked(
                    btn, layer_name=n, row_ref=r
                )
            )

            settings.on_click(
                lambda btn, n=name: on_settings_clicked(btn, layer_name=n)
            )

            rows.append(row)
            self.layer_items[name] = {"checkbox": checkbox, "slider": slider}

        self.layers_box.children = rows
        self._building = False

    def toggle_all_layers(self, change: Dict[str, Any]) -> None:
        """
        Toggles the visibility of all layers.

        Args:
            change (Dict[str, Any]): The change event from the master toggle checkbox.
        """
        if self._building:
            return
        for name, controls in self.layer_items.items():
            controls["checkbox"].value = change["new"]

        for widget in self.group_toggles.children:
            widget.value = change["new"]

    def toggle_group_layers(self, change: Dict[str, Any]) -> None:
        """
        Toggles the visibility of a group of layers.
        """
        if self._building:
            return
        group_name = change["owner"].description.split(" ")[0]
        group_layers = self.groups[group_name]
        for layer_name in group_layers:
            self.set_layer_visibility(layer_name, change["new"])
        self.refresh()

    def set_layer_visibility(self, name: str, visible: bool) -> None:
        """
        Sets the visibility of a specific layer.

        Args:
            name (str): The name of the layer.
            visible (bool): Whether the layer should be visible.
        """
        self.m.set_visibility(name, visible)

    def set_layer_opacity(self, name: str, opacity: float) -> None:
        """
        Sets the opacity of a specific layer.

        Args:
            name (str): The name of the layer.
            opacity (float): The opacity value (0 to 1).
        """
        self.m.set_opacity(name, opacity)

    def refresh(self) -> None:
        """
        Rebuilds the UI to reflect the current layers in the map.
        """
        self.build_layer_controls()

__init__(self, m, expanded=True, height='40px', layer_icon='mdi-layers', close_icon='mdi-close', label='Layers', background_color='#f5f5f5', groups=None, *args, **kwargs) special

Initializes the LayerManagerWidget.

Parameters:

Name Type Description Default
m Any

The map object to manage layers for.

required
expanded bool

Whether the expansion panel should be expanded by default. Defaults to True.

True
height str

The height of the header. Defaults to "40px".

'40px'
layer_icon str

The icon for the layer manager. Defaults to "mdi-layers".

'mdi-layers'
close_icon str

The icon for the close button. Defaults to "mdi-close".

'mdi-close'
label str

The label for the layer manager. Defaults to "Layers".

'Layers'
background_color str

The background color of the header. Defaults to "#f5f5f5".

'#f5f5f5'
groups dict

A dictionary of layer groups, such as {"Group 1": ["layer1", "layer2"], "Group 2": ["layer3", "layer4"]}. A group layer toggle will be created for each group. Defaults to None.

None
*args Any

Additional positional arguments for the parent class.

()
**kwargs Any

Additional keyword arguments for the parent class.

{}
Source code in anymap/maplibre_widgets.py
def __init__(
    self,
    m: Any,
    expanded: bool = True,
    height: str = "40px",
    layer_icon: str = "mdi-layers",
    close_icon: str = "mdi-close",
    label="Layers",
    background_color: str = "#f5f5f5",
    groups: dict = None,
    *args: Any,
    **kwargs: Any,
) -> None:
    """
    Initializes the LayerManagerWidget.

    Args:
        m (Any): The map object to manage layers for.
        expanded (bool): Whether the expansion panel should be expanded by default. Defaults to True.
        height (str): The height of the header. Defaults to "40px".
        layer_icon (str): The icon for the layer manager. Defaults to "mdi-layers".
        close_icon (str): The icon for the close button. Defaults to "mdi-close".
        label (str): The label for the layer manager. Defaults to "Layers".
        background_color (str): The background color of the header. Defaults to "#f5f5f5".
        groups (dict): A dictionary of layer groups, such as {"Group 1": ["layer1", "layer2"],
            "Group 2": ["layer3", "layer4"]}. A group layer toggle will be created for each group.
            Defaults to None.
        *args (Any): Additional positional arguments for the parent class.
        **kwargs (Any): Additional keyword arguments for the parent class.
    """
    self.m = m
    self.layer_items = {}
    self.groups = groups
    self._building = False

    # Master toggle
    style = {"description_width": "initial"}
    self.master_toggle = widgets.Checkbox(
        value=True, description="All layers on/off", style=style
    )
    self.master_toggle.observe(self.toggle_all_layers, names="value")

    self.group_toggles = widgets.VBox()
    if isinstance(groups, dict):
        for group_name, group_layers in groups.items():
            group_toggle = widgets.Checkbox(
                value=True,
                description=f"{group_name} group layers on/off",
                style=style,
            )
            group_toggle.observe(self.toggle_group_layers, names="value")
            self.group_toggles.children += (group_toggle,)

    # Build individual layer rows
    self.layers_box = widgets.VBox()
    self.build_layer_controls()

    # Close icon button
    close_btn = v.Btn(
        icon=True,
        small=True,
        class_="ma-0",
        style_="min-width: 24px; width: 24px;",
        children=[v.Icon(children=[close_icon])],
    )
    close_btn.on_event("click", self._handle_close)

    header = v.ExpansionPanelHeader(
        style_=f"height: {height}; min-height: {height}; background-color: {background_color};",
        children=[
            v.Row(
                align="center",
                class_="d-flex flex-grow-1 align-center",
                children=[
                    v.Icon(children=[layer_icon], class_="ml-1"),
                    v.Spacer(),  # push title to center
                    v.Html(tag="span", children=[label], class_="text-subtitle-2"),
                    v.Spacer(),  # push close to right
                    close_btn,
                    v.Spacer(),
                ],
            )
        ],
    )

    panel = v.ExpansionPanel(
        children=[
            header,
            v.ExpansionPanelContent(
                children=[
                    widgets.VBox(
                        [self.master_toggle, self.group_toggles, self.layers_box]
                    )
                ]
            ),
        ]
    )

    if expanded:
        super().__init__(
            children=[panel], v_model=[0], multiple=True, *args, **kwargs
        )
    else:
        super().__init__(children=[panel], multiple=True, *args, **kwargs)

build_layer_controls(self)

Builds the controls for individual layers.

This method creates checkboxes for toggling visibility, sliders for adjusting opacity, and buttons for removing layers.

Source code in anymap/maplibre_widgets.py
def build_layer_controls(self) -> None:
    """
    Builds the controls for individual layers.

    This method creates checkboxes for toggling visibility, sliders for adjusting opacity,
    and buttons for removing layers.
    """
    self._building = True
    self.layer_items.clear()
    rows = []

    style = {"description_width": "initial"}
    padding = "0px 5px 0px 5px"

    for name, info in list(self.m.layer_dict.items()):
        # if name == "Background":
        #     continue

        visible = info.get("visible", True)
        opacity = info.get("opacity", 1.0)

        checkbox = widgets.Checkbox(value=visible, description=name, style=style)
        checkbox.layout.max_width = "150px"

        slider = widgets.FloatSlider(
            value=opacity,
            min=0,
            max=1,
            step=0.01,
            readout=False,
            tooltip="Change layer opacity",
            layout=widgets.Layout(width="150px", padding=padding),
        )

        settings = widgets.Button(
            icon="gear",
            tooltip="Change layer style",
            layout=widgets.Layout(width="38px", height="25px", padding=padding),
        )

        remove = widgets.Button(
            icon="times",
            tooltip="Remove layer",
            layout=widgets.Layout(width="38px", height="25px", padding=padding),
        )

        def on_visibility_change(change, layer_name=name):
            self.set_layer_visibility(layer_name, change["new"])

        def on_opacity_change(change, layer_name=name):
            self.set_layer_opacity(layer_name, change["new"])

        def on_remove_clicked(btn, layer_name=name, row_ref=None):
            if layer_name == "Background":
                for layer in self.m.get_style_layers():
                    self.m.add_call("removeLayer", layer["id"])
            else:
                self.m.remove_layer(layer_name)
            if row_ref in self.layers_box.children:
                self.layers_box.children = tuple(
                    c for c in self.layers_box.children if c != row_ref
                )
            self.layer_items.pop(layer_name, None)
            if f"Style {layer_name}" in self.m.sidebar_widgets:
                self.m.remove_from_sidebar(name=f"Style {layer_name}")

        def on_settings_clicked(btn, layer_name=name):
            style_widget = LayerStyleWidget(self.m.layer_dict[layer_name], self.m)
            self.m.add_to_sidebar(
                style_widget,
                widget_icon="mdi-palette",
                label=f"Style {layer_name}",
            )

        checkbox.observe(on_visibility_change, names="value")
        slider.observe(on_opacity_change, names="value")

        row = widgets.HBox(
            [checkbox, slider, settings, remove], layout=widgets.Layout()
        )

        remove.on_click(
            lambda btn, r=row, n=name: on_remove_clicked(
                btn, layer_name=n, row_ref=r
            )
        )

        settings.on_click(
            lambda btn, n=name: on_settings_clicked(btn, layer_name=n)
        )

        rows.append(row)
        self.layer_items[name] = {"checkbox": checkbox, "slider": slider}

    self.layers_box.children = rows
    self._building = False

refresh(self)

Rebuilds the UI to reflect the current layers in the map.

Source code in anymap/maplibre_widgets.py
def refresh(self) -> None:
    """
    Rebuilds the UI to reflect the current layers in the map.
    """
    self.build_layer_controls()

set_layer_opacity(self, name, opacity)

Sets the opacity of a specific layer.

Parameters:

Name Type Description Default
name str

The name of the layer.

required
opacity float

The opacity value (0 to 1).

required
Source code in anymap/maplibre_widgets.py
def set_layer_opacity(self, name: str, opacity: float) -> None:
    """
    Sets the opacity of a specific layer.

    Args:
        name (str): The name of the layer.
        opacity (float): The opacity value (0 to 1).
    """
    self.m.set_opacity(name, opacity)

set_layer_visibility(self, name, visible)

Sets the visibility of a specific layer.

Parameters:

Name Type Description Default
name str

The name of the layer.

required
visible bool

Whether the layer should be visible.

required
Source code in anymap/maplibre_widgets.py
def set_layer_visibility(self, name: str, visible: bool) -> None:
    """
    Sets the visibility of a specific layer.

    Args:
        name (str): The name of the layer.
        visible (bool): Whether the layer should be visible.
    """
    self.m.set_visibility(name, visible)

toggle_all_layers(self, change)

Toggles the visibility of all layers.

Parameters:

Name Type Description Default
change Dict[str, Any]

The change event from the master toggle checkbox.

required
Source code in anymap/maplibre_widgets.py
def toggle_all_layers(self, change: Dict[str, Any]) -> None:
    """
    Toggles the visibility of all layers.

    Args:
        change (Dict[str, Any]): The change event from the master toggle checkbox.
    """
    if self._building:
        return
    for name, controls in self.layer_items.items():
        controls["checkbox"].value = change["new"]

    for widget in self.group_toggles.children:
        widget.value = change["new"]

toggle_group_layers(self, change)

Toggles the visibility of a group of layers.

Source code in anymap/maplibre_widgets.py
def toggle_group_layers(self, change: Dict[str, Any]) -> None:
    """
    Toggles the visibility of a group of layers.
    """
    if self._building:
        return
    group_name = change["owner"].description.split(" ")[0]
    group_layers = self.groups[group_name]
    for layer_name in group_layers:
        self.set_layer_visibility(layer_name, change["new"])
    self.refresh()

LayerStyleWidget (VBox)

A widget for styling map layers interactively.

Parameters:

Name Type Description Default
layer dict

The layer to style.

required
map_widget ipyleaflet.Map or folium.Map

The map widget to update.

required
widget_width str

The width of the widget. Defaults to "270px".

'270px'
label_width str

The width of the label. Defaults to "130px".

'130px'
Source code in anymap/maplibre_widgets.py
class LayerStyleWidget(widgets.VBox):
    """
    A widget for styling map layers interactively.

    Args:
        layer (dict): The layer to style.
        map_widget (ipyleaflet.Map or folium.Map): The map widget to update.
        widget_width (str, optional): The width of the widget. Defaults to "270px".
        label_width (str, optional): The width of the label. Defaults to "130px".
    """

    def __init__(
        self,
        layer: dict,
        map_widget: "MapLibreMap",
        widget_width: str = "270px",
        label_width: str = "130px",
    ):
        super().__init__()
        self.layer = layer
        self.map = map_widget
        self.layer_type = self._get_layer_type()
        self.layer_id = layer["layer"].id
        self.layer_paint = layer["layer"].paint
        self.original_style = self._get_current_style()
        self.widget_width = widget_width
        self.label_width = label_width

        # Create the styling widgets based on layer type
        self.style_widgets = self._create_style_widgets()

        # Create buttons
        self.apply_btn = widgets.Button(
            description="Apply",
            button_style="primary",
            tooltip="Apply style changes",
            layout=widgets.Layout(width="auto"),
        )

        self.reset_btn = widgets.Button(
            description="Reset",
            button_style="warning",
            tooltip="Reset to original style",
            layout=widgets.Layout(width="auto"),
        )

        self.close_btn = widgets.Button(
            description="Close",
            button_style="",
            tooltip="Close the widget",
            layout=widgets.Layout(width="auto"),
        )

        self.output_widget = widgets.Output()

        # Button container
        self.button_box = widgets.HBox([self.apply_btn, self.reset_btn, self.close_btn])

        # Add button callbacks
        self.apply_btn.on_click(self._apply_style)
        self.reset_btn.on_click(self._reset_style)
        self.close_btn.on_click(self._close_widget)

        # Layout
        self.layout = widgets.Layout(width="300px", padding="10px")

        # Combine all widgets
        self.children = [*self.style_widgets, self.button_box, self.output_widget]

    def _get_layer_type(self) -> str:
        """Determine the layer type."""
        return self.layer["type"]

    def _get_current_style(self) -> dict:
        """Get the current layer style."""
        return self.layer_paint

    def _create_style_widgets(self) -> List[widgets.Widget]:
        """Create style widgets based on layer type."""
        widgets_list = []

        if self.layer_type == "circle":
            widgets_list.extend(
                [
                    self._create_color_picker(
                        "Circle Color", "circle-color", "#3388ff"
                    ),
                    self._create_number_slider(
                        "Circle Radius", "circle-radius", 6, 1, 20
                    ),
                    self._create_number_slider(
                        "Circle Opacity", "circle-opacity", 0.8, 0, 1, 0.05
                    ),
                    self._create_number_slider(
                        "Circle Blur", "circle-blur", 0, 0, 1, 0.05
                    ),
                    self._create_color_picker(
                        "Circle Stroke Color", "circle-stroke-color", "#3388ff"
                    ),
                    self._create_number_slider(
                        "Circle Stroke Width", "circle-stroke-width", 1, 0, 5
                    ),
                    self._create_number_slider(
                        "Circle Stroke Opacity",
                        "circle-stroke-opacity",
                        1.0,
                        0,
                        1,
                        0.05,
                    ),
                ]
            )

        elif self.layer_type == "line":
            widgets_list.extend(
                [
                    self._create_color_picker("Line Color", "line-color", "#3388ff"),
                    self._create_number_slider("Line Width", "line-width", 2, 1, 10),
                    self._create_number_slider(
                        "Line Opacity", "line-opacity", 1.0, 0, 1, 0.05
                    ),
                    self._create_number_slider("Line Blur", "line-blur", 0, 0, 1, 0.05),
                    self._create_dropdown(
                        "Line Style",
                        "line-dasharray",
                        [
                            ("Solid", [1]),
                            ("Dashed", [2, 4]),
                            ("Dotted", [1, 4]),
                            ("Dash-dot", [2, 4, 8, 4]),
                        ],
                    ),
                ]
            )

        elif self.layer_type == "fill":
            widgets_list.extend(
                [
                    self._create_color_picker("Fill Color", "fill-color", "#3388ff"),
                    self._create_number_slider(
                        "Fill Opacity", "fill-opacity", 0.2, 0, 1, 0.05
                    ),
                    self._create_color_picker(
                        "Fill Outline Color", "fill-outline-color", "#3388ff"
                    ),
                ]
            )
        else:
            widgets_list.extend(
                [widgets.HTML(value=f"Layer type {self.layer_type} is not supported.")]
            )

        return widgets_list

    def _create_color_picker(
        self, description: str, property_name: str, default_color: str
    ) -> widgets.ColorPicker:
        """Create a color picker widget."""
        return widgets.ColorPicker(
            description=description,
            value=self.original_style.get(property_name, default_color),
            layout=widgets.Layout(
                width=self.widget_width, description_width=self.label_width
            ),
            style={"description_width": "initial"},
        )

    def _create_number_slider(
        self,
        description: str,
        property_name: str,
        default_value: float,
        min_val: float,
        max_val: float,
        step: float = 1,
    ) -> widgets.FloatSlider:
        """Create a number slider widget."""
        return widgets.FloatSlider(
            description=description,
            value=self.original_style.get(property_name, default_value),
            min=min_val,
            max=max_val,
            step=step,
            layout=widgets.Layout(
                width=self.widget_width, description_width=self.label_width
            ),
            style={"description_width": "initial"},
            continuous_update=False,
        )

    def _create_dropdown(
        self,
        description: str,
        property_name: str,
        options: List[Tuple[str, List[float]]],
    ) -> widgets.Dropdown:
        """Create a dropdown widget."""
        return widgets.Dropdown(
            description=description,
            options=options,
            value=self.original_style.get(property_name, options[0][1]),
            layout=widgets.Layout(
                width=self.widget_width, description_width=self.label_width
            ),
            style={"description_width": "initial"},
        )

    def _apply_style(self, _) -> None:
        """Apply the style changes to the layer."""
        new_style = {}

        for widget in self.style_widgets:
            if isinstance(widget, widgets.ColorPicker):
                property_name = widget.description.lower().replace(" ", "-")
                new_style[property_name] = widget.value
            elif isinstance(widget, widgets.FloatSlider):
                property_name = widget.description.lower().replace(" ", "-")
                new_style[property_name] = widget.value
            elif isinstance(widget, widgets.Dropdown):
                property_name = widget.description.lower().replace(" ", "-")
                new_style[property_name] = widget.value

        with self.output_widget:
            try:
                for key, value in new_style.items():
                    if key == "line-style":
                        key = "line-dasharray"
                    self.map.set_paint_property(self.layer["layer"].id, key, value)
            except Exception as e:
                print(e)

        self.map.layer_manager.refresh()

    def _reset_style(self, _) -> None:
        """Reset to original style."""

        # Update widgets to reflect original style
        for widget in self.style_widgets:
            if isinstance(
                widget, (widgets.ColorPicker, widgets.FloatSlider, widgets.Dropdown)
            ):
                property_name = widget.description.lower().replace(" ", "-")
                if property_name in self.original_style:
                    widget.value = self.original_style[property_name]

    def _close_widget(self, _) -> None:
        """Close the widget."""
        # self.close()
        self.map.remove_from_sidebar(name=f"Style {self.layer['layer'].id}")