Introducción a Weaverlet: Desarrollo de Aplicaciones de Visualización de Datos Complejas en Python

Dr. Alberto García Robledo

CentroGeo Querétaro

Escuela de Verano en Ciencias de Información Geoespacial

16 de julio de 2024

Observatorio Metropolitano CentroGeo

¿Qué es el Observatorio Metropolitano CentroGeo?

  • Es un colectivo de investigación (CI) formado en 2018 integrado por investigadores de diferentes disciplinas, incluyendo geografía humana, inteligencia artificial y ciencia de los datos.

Observatorio Metropolitano CentroGeo

Observatorio

Metropolitano

CentroGeo

¿Quiénes somos?

  • Los miembros del CI somos IxM adscritos a CentroGeo CDMX y Querétaro en el proyecto IxM #154 Observatorio Metropolitano.

Observatorio

Metropolitano

CentroGeo

¿Qué hacemos?

  • Realizamos actividades de investigación científica, formación de recursos humanos, desarrollo tecnológico, propuestas de intervención, y actividades de vinculación y difusión del conocimiento sobre aspectos estratégicos del fenómeno metropolitano y urbano.

Observatorio

Metropolitano

CentroGeo

Líneas de investigación

  • Agua y ciudades

  • Computación científica en problemas geoespaciales y sociales

  • Simulación y modelación numérica

  • Visualización y Big Data Geoespacial

Observatorio

Metropolitano

CentroGeo

¿Qué es un tablero de datos?

Un tablero de visualización, es una herramienta visual que permite representar y monitorear de manera centralizada información clave y métricas de rendimiento de una organización o sistema en tiempo real o en periodos definidos.

Observatorio

Metropolitano

CentroGeo

¿Qué es Dash?

  • Dash es un framework web escrito en Python para el desarrollo de tableros de visualización.

  • Está construido sobre Flask, Plotly.js y React.js

  • Permite el desarrollo de aplicaciones web de visualización de datos sin la necesidad de escribir HTML, JavaScript o CSS.

Observatorio

Metropolitano

CentroGeo

Estructura de una app Dash

Observatorio

Metropolitano

CentroGeo

  • Una aplicación Dash está integrada de dos partes:

    1. La descripción del UI (layout)

    2. La descripción de la interactividad (callbacks)

  • Un layout de Dash está integrado de uno o más componentes Dash anidados:

Estructura de una app Dash

Observatorio

Metropolitano

CentroGeo

Estructura de una app Dash

Observatorio

Metropolitano

CentroGeo

Estructura de una app Dash

Observatorio

Metropolitano

CentroGeo

# Author: Alberto Garcia-Robledo
# Copyright (c) 2020 Centro de Investigación en Ciencias de Información Geoespacial, A.C.

########################################################
# 1. Data loading and visualization setup
########################################################

# load edge and node data
nodes_data = pickle.load(open("data/nodes_data.p", "rb"))
edges_data = pickle.load(open("data/edges_data.p", "rb"))

# index edges_data by SUMO edge id
edges_data_index = {edge["data"]["id"]: edge for edge in edges_data}

# load CSV data
edge_vehicle_counts_df = pd.read_csv("csv_data/edge_vehicle_counts.csv")
vehicle_speeds_df = pd.read_csv("csv_data/vehicle_speeds.csv")
timestep_avg_vehicle_speeds_df = pd.read_csv(
    "csv_data/timestep_avg_vehicle_speeds.csv")
timestep_vehicle_counts_df = pd.read_csv(
    "csv_data/timestep_vehicle_counts.csv")

# get min and max simulation times for the time slider
timesteps = timestep_vehicle_counts_df["timestep"]
min_slider_val = timesteps.min()
max_slider_val = timesteps.max()

# set Sylvereye options
edge_options = copy.copy(DEFAULT_EDGE_OPTIONS)
edge_options["width_method"] = EdgeWidthMethod.DEFAULT
edge_options["width_scale_field"] = "weight"
edge_options["alpha_method"] = EdgeAlphaMethod.DEFAULT
edge_options["alpha_scale_field"] = "weight"
edge_options["alpha_min"] = 0.25/2
marker_options = copy.copy(DEFAULT_MARKER_OPTIONS)
marker_options["enable_tooltips"] = True


########################################################
# 2. App layout
########################################################

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.FLATLY]
                )

app.layout = dbc.Container([
    # navbar
    dbc.Row(
        dbc.Col(
            dbc.NavbarSimple(
                children=[
                    dbc.NavItem(dbc.NavLink("About", id="open", href="#")),
                    dbc.Modal(
                        [
                            dbc.ModalHeader("About"),
                            dbc.ModalBody(children=[
                                html.P("Author: Alberto Garcia-Robledo"),
                                html.P(
                                    "Copyright (c) 2020 Centro de Investigación en Ciencias de Información Geoespacial, A.C.")
                            ]),
                            dbc.ModalFooter(
                                dbc.Button("Close", id="close",
                                           className="ml-auto")
                            ),
                        ],
                        id="modal"
                    )
                ],
                brand="Sylvereye Dash simple SUMO dashboard",
                brand_href="#",
                color="primary",
                dark=True,
                fluid=True,
                sticky="top",
            ),
        ),
    ),

    html.Br(),

    dbc.Row([
        dbc.Col(
            # road network file
            html.H5(f"SUMO road network: {SUMONET_FILE}"),
            width=4,
            style={"text-align": "left"}
        ),
        dbc.Col(
            # network size
            html.H5(
                f"Junctions: {len(nodes_data):,} - Edges: {len(edges_data):,}"),
            width=4,
            style={"text-align": "center"}
        ),
        dbc.Col(
            # clicked edge
            html.H5(id="clicked-edge-h5"),
            width=4,
            style={"text-align": "right"}
        )
    ]),

    html.Br(),

    dbc.Row([
        dbc.Col(
            dbc.FormGroup(
                [
                    # show checklist
                    dbc.Checklist(
                        options=[
                            {"label": "Show tiles", "value": "show_tiles"},
                            {"label": "Show nodes", "value": "show_nodes"},
                            {"label": "Show markers", "value": "show_markers"}
                        ],
                        value=["show_tiles", "show_nodes", "show_markers"],
                        id="show-layers-checklist",
                        inline=True
                    )
                ]
            ),
            style={"text-align": "left"}
        ),

        dbc.Col(
            dbc.FormGroup(
                [
                    # scaling checklist
                    dbc.Checklist(
                        options=[
                            {"label": "Scale edges alpha",
                                "value": "scale_edge_alpha"},
                            {"label": "Scale edges width",
                                "value": "scale_edge_width"}
                        ],
                        value=[],
                        id="scale-by-checklist",
                        inline=True
                    )
                ]
            ),
            style={"text-align": "center"}
        ),

        dbc.Col(
            # dropdown
            dbc.Select(
                id="markers-at-select",
                bs_size="sm",
                value="markers_at_edges",
                options=[
                    {"label": "Markers at the Top-10 bussiest edges",
                        "value": "markers_at_edges"},
                    {"label": "Markers at the Top-10 slowest vehicles",
                        "value": "markers_at_vehicles"},
                ],
            ),
            style={"text-align": "right"}
        ),

    ]),

    dbc.Row(
        dbc.Col(
            # map
            ds.SylvereyeRoadNetwork(id='sylvereye-roadnet',
                                    tile_layer_url='//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png',
                                    tile_layer_subdomains='abcd',
                                    tile_layer_attribution='Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.',
                                    tile_layer_opacity=0.3,
                                    map_center=CENTER,
                                    map_max_zoom=MAX_ZOOM,
                                    map_min_zoom=MIN_ZOOM,
                                    map_zoom=ZOOM,
                                    map_style=STYLE,
                                    nodes_data=nodes_data,
                                    edges_data=edges_data,
                                    markers_data=[],
                                    show_nodes=True,
                                    show_edges=True,
                                    show_arrows=True,
                                    show_markers=True,
                                    node_options=DEFAULT_NODE_OPTIONS,
                                    edge_options=edge_options,
                                    marker_options=marker_options,
                                    debug_options=DEFAULT_DEBUG_OPTIONS
                                    ),
            width=12
        ),
    ),

    html.Br(),

    dbc.Row(
        dbc.Col(
            # slider
            dcc.Slider(
                id='time-slider',
                min=min_slider_val,
                max=max_slider_val,
                value=min_slider_val,
                tooltip={"placement": "bottom"},
                marks={key: key if i % 100 == 0 else "" for i,
                       key in enumerate(timesteps)}
            ),
            width=12
        )
    ),

    dbc.Row(
        dbc.Col(
            dbc.Label("Simulation time")
        ),
        style={"text-align": "center"}
    ),

    dbc.Row([
        dbc.Col(
            # top-10 edges with highest vehicle count
            dcc.Graph(id='top-edges-graph'),
            width=6
        ),
        dbc.Col(
            # evolution of the vehicle count
            dcc.Graph(id='load-totals-graph', figure={
                'data': [{
                    'x': timesteps,
                    'y': timestep_vehicle_counts_df["vehicle_count"],
                    'type': 'line'
                }],
                'layout': {
                    'title': 'Vehicle counts over time',
                    'xaxis': {
                        'title': 'Simulation time', 'automargin': True, 'autorange': True
                    },
                    'yaxis': {
                        'title': 'Vehicles'
                    },
                    'height': 400,
                    'template': 'plotly_dark'
                }
            }),
            width=6
        )
    ]),

    dbc.Row([
        dbc.Col(
            # vehicle speeds barplot
            dcc.Graph(id='top-vehicles-graph'),
            width=6
        ),
        dbc.Col(
            # average speed line plot
            dcc.Graph(id='vehicle-avg-speeds-graph', figure={
                'data': [{
                    'x': timesteps,
                    'y': timestep_avg_vehicle_speeds_df["avg_speed"],
                    'type': 'line'
                }],
                'layout': {
                    'title': 'Average vehicle speeds over time',
                    'xaxis': {
                        'title': 'Simulation time', 'automargin': True, 'autorange': True
                    },
                    'yaxis': {
                        'title': 'Average speed (m/s)'
                    },
                    'height': 400,
                    'template': 'plotly_dark'
                }
            }),
            width=6
        )
    ]),

    html.Br(),    

], style={"padding": "0px", 'width': "95%"}, fluid=True)


########################################################
# 3. Auxiliary functions
########################################################


def get_top10_bussiest_roads_markers_data(top10_timestep_edge_vehicle_counts_df):
    """
    Get the markers for the top-10 edges with highest vehicle count.
    """

    marker_coords = []
    marker_tooltips = []

    for _, row in top10_timestep_edge_vehicle_counts_df.iterrows():
        marker_coords.append(get_edge_middle_coords(
            edges_data_index[row["edge_id"]]))
        tooltip = f'Edge ID: {row["edge_id"]} - Load: {row["vehicle_count"]} vehicles'
        marker_tooltips.append(tooltip)

    markers_data = generate_markers_from_coords(
        marker_coords, tooltips=marker_tooltips)

    return markers_data


def get_top10_slowest_vehicles_markers_data(top10_timestep_vehicle_speeds_df):
    """
    Get the markers for the top-10 slowest vehicles.
    """

    marker_coords = []
    marker_tooltips = []

    for _, row in top10_timestep_vehicle_speeds_df.iterrows():
        marker_coords.append([row["lat"], row["lon"]])
        tooltip = f'Vehicle ID: {row["vehicle_id"]} - Speed: {row["speed"]} m/s'
        marker_tooltips.append(tooltip)

    markers_data = generate_markers_from_coords(
        marker_coords, tooltips=marker_tooltips)

    return markers_data


########################################################
# 4. Dash callbacks
########################################################

@ app.callback(
    [dash.dependencies.Output('sylvereye-roadnet', 'markers_data'),
     dash.dependencies.Output('sylvereye-roadnet', 'edges_data'),
     dash.dependencies.Output('top-edges-graph', 'figure'),
     dash.dependencies.Output('top-vehicles-graph', 'figure')],
    [dash.dependencies.Input('time-slider', 'value'),
     dash.dependencies.Input('markers-at-select', 'value')],
    [dash.dependencies.State('time-slider', 'value'),
     dash.dependencies.State('markers-at-select', 'value')])
def time_slider_callback(timestep, markers_at_value, current_timestep, current_markers_at_value):
    """
    Note: arguments "timestep" and "markers_at_value" are ignored. They are needed because
    the first two Inputs that react to changes in the time slider and the markers dropdown values. 
    Actual component values are taken from arguments "current_timestep" and "current_markers_at_value"
    """

    global edges_data
    triggered_by = dash.callback_context.triggered[0]["prop_id"]

    # filter data by timestep
    timestep_edge_vehicle_counts_df = edge_vehicle_counts_df[
        edge_vehicle_counts_df["timestep"] == current_timestep]
    timestep_vehicle_speeds_df = vehicle_speeds_df[vehicle_speeds_df["timestep"]
                                                   == current_timestep]

    # get top-10 edges with highest vehicle count
    top10_timestep_edge_vehicle_counts_df = timestep_edge_vehicle_counts_df.nlargest(
        10, "vehicle_count")[["edge_id", "vehicle_count"]]

    # get top-10 slowest vehicles
    top10_timestep_vehicle_speeds_df = timestep_vehicle_speeds_df.nsmallest(
        10, "speed")[["vehicle_id", "speed", "lat", "lon"]]

    # markers
    markers_data = []
    if current_markers_at_value == "markers_at_edges":
        markers_data = get_top10_bussiest_roads_markers_data(
            top10_timestep_edge_vehicle_counts_df)
    elif current_markers_at_value == "markers_at_vehicles":
        markers_data = get_top10_slowest_vehicles_markers_data(
            top10_timestep_vehicle_speeds_df)

    if triggered_by == "markers-at-select.value":

        return markers_data, dash.no_update, dash.no_update, dash.no_update

    elif triggered_by == "time-slider.value" or triggered_by == ".":

        # populate Sylvereye edges weigths
        for edge in edges_data:
            edge["weight"] = 1
        for _, row in timestep_edge_vehicle_counts_df.iterrows():
            edges_data_index[row["edge_id"]]["weight"] = row["vehicle_count"]

        # top-10 edges with highest vehicle count barplot
        loads_figure = {
            'data': [{
                'x': top10_timestep_edge_vehicle_counts_df["edge_id"],
                'y': top10_timestep_edge_vehicle_counts_df["vehicle_count"],
                'type': 'bar'
            }],
            'layout': {
                'title': 'Top-10 bussiest edges',
                'xaxis': {
                    'title': 'Edge ID', 'automargin': True, 'autorange': True
                },
                'yaxis': {
                    'title': 'Vehicles'
                },
                'height': 400
            }
        }

        # top10 slowest vehicles barplot
        vehicles_figure = {
            'data': [{
                'x': top10_timestep_vehicle_speeds_df["vehicle_id"],
                'y': top10_timestep_vehicle_speeds_df["speed"],
                'type': 'bar'
            }],
            'layout': {
                'title': 'Top-10 slowest vehicles',
                'xaxis': {
                    'title': 'Vehicle ID', 'automargin': True, 'autorange': True
                },
                'yaxis': {
                    'title': 'Speed (m/s)'
                },
                'height': 400
            }
        }

        return markers_data, edges_data, loads_figure, vehicles_figure


@ app.callback(
    [dash.dependencies.Output('sylvereye-roadnet', 'tile_layer_opacity'),
     dash.dependencies.Output('sylvereye-roadnet', 'show_nodes'),
     dash.dependencies.Output('sylvereye-roadnet', 'show_markers')],
    [dash.dependencies.Input('show-layers-checklist', 'value')])
def show_layers_checklist_callback(value):

    show_markers = False
    show_nodes = False
    tiles_opacity = 0

    if "show_tiles" in value:
        tiles_opacity = 0.3

    if "show_markers" in value:
        show_markers = True

    if "show_nodes" in value:
        show_nodes = True

    return tiles_opacity, show_nodes, show_markers


@ app.callback(
    dash.dependencies.Output('sylvereye-roadnet', 'edge_options'),
    [dash.dependencies.Input('scale-by-checklist', 'value')])
def update_markers_visibility(value):
    global edge_options

    if "scale_edge_alpha" in value:
        edge_options["alpha_method"] = EdgeAlphaMethod.SCALE
    else:
        edge_options["alpha_method"] = EdgeAlphaMethod.DEFAULT

    if "scale_edge_width" in value:
        edge_options["width_method"] = EdgeWidthMethod.SCALE
    else:
        edge_options["width_method"] = EdgeWidthMethod.DEFAULT

    return edge_options


@ app.callback(
    dash.dependencies.Output('clicked-edge-h5', 'children'),
    [dash.dependencies.Input('sylvereye-roadnet', 'clicked_edge'),
     dash.dependencies.Input('sylvereye-roadnet', 'clicked_node'),
     dash.dependencies.Input('sylvereye-roadnet', 'clicked_marker')],
    [dash.dependencies.State('time-slider', 'value')])
def handle_sylvereye_click(clicked_edge, clicked_node, clicked_marker, timestep):
    triggered_by = dash.callback_context.triggered[0]["prop_id"]
    print(triggered_by)
    if triggered_by == "sylvereye-roadnet.clicked_edge" and clicked_edge:
        edge_id = clicked_edge["data"]["data"]["id"]
        vehicle_count = edge_vehicle_counts_df[(edge_vehicle_counts_df["timestep"] == timestep) & (
            edge_vehicle_counts_df["edge_id"] == edge_id)]["vehicle_count"]
        if len(vehicle_count) > 0:
            return f'Edge ID: {edge_id} - Load: {vehicle_count.iloc[0]} vehicles'
        else:
            return f'Edge ID: {edge_id} - Load: 0 vehicles'
    elif triggered_by == "sylvereye-roadnet.clicked_node" and clicked_node:
        node_id = clicked_node["data"]["data"]["id"]
        return f'Junction ID: {node_id}'
    elif triggered_by == "sylvereye-roadnet.clicked_marker" and clicked_marker:
        tooltip = clicked_marker["marker"]["tooltip"]
        return f'Marker: {tooltip}'


@app.callback(
    dash.dependencies.Output("modal", "is_open"),
    [dash.dependencies.Input("open", "n_clicks"),
     dash.dependencies.Input("close", "n_clicks")],
    [dash.dependencies.State("modal", "is_open")],
)
def toggle_modal(n1, n2, is_open):
    if n1 or n2:
        return not is_open
    return is_open 

Arquitectura basada en componentes

Observatorio

Metropolitano

CentroGeo

  • Arquitectura basada en componentes: Enfoque de diseño de software que descompone sistemas complejos en unidades modulares y reutilizables denominadas componentes.

Arquitectura basada en componentes: características

Observatorio

Metropolitano

CentroGeo

  • Descomposición en Componentes: Divide sistemas complejos en unidades modulares y reutilizables.

  • Encapsulación de Funcionalidades: Cada componente gestiona una funcionalidad específica como renderizado de UI, interacciones del usuario, o manejo de estado.

  • Composición de Aplicaciones: Los componentes se pueden combinar para formar aplicaciones más complejas y sofisticadas.

Arquitectura basada en componentes: ventajas

Observatorio

Metropolitano

CentroGeo

  • Modularidad: Código más limpio y mantenible gracias a la encapsulación de funcionalidades.
  • Reusabilidad: Reduce la duplicación de código y acelera los ciclos de desarrollo.
  • Escalabilidad: Permite adaptar y extender las aplicaciones fácilmente conforme crecen.
  • Testabilidad: Facilita pruebas precisas y tempranas, mejorando la confiabilidad.
  • Colaboración: Promueve el trabajo en equipo eficiente y sin interferencias.

Frameworks basados en componentes

Observatorio

Metropolitano

CentroGeo

Lado del cliente

(front-end) 

Lado del servidor

(back-end) 

?

Weaverlet

Weaverlet es un framework ligero orientado a componentes del lado del servidor para construir aplicaciones de tableros web complejos en Python explotando Dash.

Observatorio

Metropolitano

CentroGeo

Weaverlet

  • Desarrollada en CentroGeo: Weaverlet es una biblioteca Python creada en el Observatorio Metropolitano CentroGeo.

  • Visualización de Datos Web: Permite construir aplicaciones web interactivas para visualización de datos.
     

Observatorio

Metropolitano

CentroGeo

+

Weaverlet

  • Programación Orientada a Componentes: Utiliza el paradigma de componentes del lado del servidor en Python, basándose en Dash.

  • Independencia de Front-End: No requiere el uso de JS, HTML, ni CSS por parte del desarrollador.

Observatorio

Metropolitano

CentroGeo

+

Características

Observatorio

Metropolitano

CentroGeo

  • Aplicaciones Dash multi-tablero.

  • Autenticación basada en sesiones.

  • Contexto compartido entre componentes Weaverlet.

  • Comunicación vertical y horizontal dentro de la jerarquía de componentes.

     

Usuarios

Observatorio

Metropolitano

CentroGeo

  • Científicos de datos que necesitan construir aplicaciones de visualización multi-tablero.

  • Desarrolladores Python familiarizados con Dash que necesiten desarrollar visualizaciones complejas.

  • Desarrolladores web familiarizados con el paradigma de programación orientada a componentes seguido por frameworks populares como React.

Instalación

  • Weaverlet está disponible en PyPi:

Observatorio

Metropolitano

CentroGeo

pip install weaverlet

Ejemplo 1: Componentes Weaverlet

from weaverlet.base import WeaverletComponent, WeaverletApp, Identifier
from dash.dependencies import Input, Output
from dash import html, dcc


class EchoComponent(WeaverletComponent):

    text_input_id = Identifier()    
    echo_div_id = Identifier()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def get_layout(self):
        return html.Div([
            dcc.Input(id=self.text_input_id,
                      type='text'),
            html.Div(id=self.echo_div_id)
        ])

    def register_callbacks(self, app):
        @app.callback(
            Output(self.echo_div_id, 'children'),
            Input(self.text_input_id, 'value')
        )
        def update_echo_div(text_value):
            return f'{text_value}'


greeting_component = EchoComponent()
wapp = WeaverletApp(root_component=greeting_component)
wapp.app.run_server()

Observatorio

Metropolitano

CentroGeo

Ejemplo 1: Componentes Weaverlet

  • Un componente Weaverlet es una simple clase que extiende a la clase WeaverletComponent

  • El layout está encapsulado en el método get_layout()

  • Los callbacks están encapsulados en el método register_callbacks()

  • Weaverlet provee la clase Identifier para crear identificadores que no colisionen entre componentes

Observatorio

Metropolitano

CentroGeo

Ejemplo 2: App multi-tablero

from weaverlet.base import WeaverletComponent, WeaverletApp
from weaverlet.components import SimpleRouterComponent
from dash import html


class PageAComponent(WeaverletComponent):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def get_layout(self, pathname, hash, href, search):
        return html.Div(f'hello from Page A!')


class PageBComponent(WeaverletComponent):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def get_layout(self, pathname, hash, href, search):
        return html.Div(f'hello from Page B!')


class PageNotFoundComponent(WeaverletComponent):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def get_layout(self, pathname):
        return html.Div(f'Page not found!')


page_a_component = PageAComponent()
page_b_component = PageBComponent()
not_found_page_component = PageNotFoundComponent()

routes = {
    '/': page_a_component,
    '/page_a': page_a_component,
    '/page_b': page_b_component
}
router_component = SimpleRouterComponent(
    routes=routes,
    not_found_page_component=not_found_page_component
)

wapp = WeaverletApp(root_component=router_component)
wapp.app.run_server()

Observatorio

Metropolitano

CentroGeo

Ejemplo 2: App multi-tablero

  • Diferentes páginas (tableros) pueden ser encapsulados en diferentes componentes Weaverlet.

    • Cada componente encapsula el layout y los callbacks de su página.

  • El componente SimpleRouterComponent(), incluido con Weaverlet, es utilizado para asignar URLs a las diferentes páginas.

Observatorio

Metropolitano

CentroGeo

Ejemplo 3: Cadena de señales

import random
from dash import html
from dash_extensions.enrich import Output, Trigger
from weaverlet.base import SignalOutput, SignalInput, WeaverletComponent, WeaverletApp, Identifier
from weaverlet.components import SignalComponent


class SignalChainComponent(WeaverletComponent):

    trigger_button_id = Identifier()
    label_p_id = Identifier()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.signal_1_component = SignalComponent()
        self.signal_2_component = SignalComponent()
        self.signal_3_component = SignalComponent()

    def get_layout(self):
        return html.Div(
            [
                self.signal_1_component(),
                self.signal_2_component(),
                self.signal_3_component(),
                html.Button('Click to trigger signal chain',
                            id=self.trigger_button_id),
                html.P(id=self.label_p_id)
            ]
        )

    def register_callbacks(self, app):

        @app.callback(
            SignalOutput(self.signal_1_component),
            Trigger(self.trigger_button_id, 'n_clicks')
        )
        def button_click():
            return {}

        @app.callback(
            SignalOutput(self.signal_2_component),
            SignalInput(self.signal_1_component)
        )
        def signal_1(signal_input):
            signal_input['random_number_1'] = random.random()
            return signal_input

        @app.callback(
            SignalOutput(self.signal_3_component),
            SignalInput(self.signal_2_component)
        )
        def signal_2(signal_input):
            signal_input['random_number_2'] = random.random()
            return signal_input

        @app.callback(
            Output(self.label_p_id, 'children'),
            SignalInput(self.signal_3_component)
        )
        def signal_3(signal_input):
            return f'Signal chain triggered! data: {signal_input}'


signal_trigger_component = SignalChainComponent()

wapp = WeaverletApp(root_component=signal_trigger_component)
wapp.app.run_server(port=8089)

Ejemplo 3: Cadena de señales

  • Una señal de Weaverlet (SignalComponent) permite a un componente Weaverlet comunicarse con otro.

  • Weaverlet provee entradas (SignalInput, SignalState) y salidas (SignalOutput) para definir callbacks que son disparados por señales y que disparan a otras señales de manera conveniente.

  • Las señales pueden encadenarse para distribuir la carga de procesamiento entre diferentes callbacks.

Observatorio

Metropolitano

CentroGeo

Ejemplo 4: Weaverlet y Bootstrap

# -*- coding: utf-8 -*-

import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from weaverlet.base import WeaverletApp, WeaverletComponent, Identifier
from weaverlet.components import SimpleRouterComponent


class PrimaryNavbarComponent(WeaverletComponent):  

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def get_layout(self, brand):
        layout = \
            dbc.NavbarSimple(
                children=[
                    dbc.NavItem(dbc.NavLink('Page A', href='/a')),
                    dbc.NavItem(dbc.NavLink('Page B', href='/b'))
                ],
                brand=brand,
                color='primary',
                dark=True
            )

        return layout


class MainPageComponent(WeaverletComponent):

    def __init__(self, brand, page_content_component):
        super().__init__()
        self.primary_navbar = PrimaryNavbarComponent(name='primary_navbar')
        self.page_content_component = page_content_component
        self.brand = brand

    def get_layout(self, pathname, hash, href, search):
        layout = \
            html.Div(
                [
                    self.primary_navbar(brand=self.brand),  # child component
                    dbc.Container(
                        [
                            self.page_content_component()  # child component
                        ],
                        fluid=True,
                        style={"padding": "0px", 'width': "100%"}
                    )
                ]
            )

        return layout


class ContentComponent(WeaverletComponent):

    def __init__(self, body, **kwargs):
        super().__init__(**kwargs)
        self.body = body

    def get_layout(self):
        layout = \
            dbc.Container([
                dcc.Markdown(self.body)
            ])

        return layout


class NotFoundPageComponent(WeaverletComponent):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def get_layout(self, pathname):
        layout = \
            dcc.Markdown(f"# Page {pathname} not found!")

        return layout


page_a_markdown = """
# Page A \

This is the content of Page A.
"""

page_b_markdown = """
# Page B \

This is the content of Page B.
"""

content_page_a = ContentComponent(body=page_a_markdown)
content_page_b = ContentComponent(body=page_b_markdown)
page_a_main_page = MainPageComponent(brand='Brand of Page A',
                                     page_content_component=content_page_a)
page_b_main_page = MainPageComponent(brand='Brand of Page B',
                                     page_content_component=content_page_b)
not_found_page = NotFoundPageComponent()

routes = {
    '/': page_a_main_page,
    '/a': page_a_main_page,
    '/b': page_b_main_page
}
router = SimpleRouterComponent(
    routes=routes,
    not_found_page_component=not_found_page
)
wapp = WeaverletApp(root_component=router,
                    title='Simple Weaverlet + DBC app',
                    external_stylesheets=[dbc.themes.COSMO])

wapp.app.run_server()

Ejemplo 4: Weaverlet y Bootstrap

  • Weaverlet puede ser combinado con Dash Bootstrap components para crear componentes de UI de alto nivel (e.g. barras de navegación personalizadas).

  • Estos componentes de UI pueden ser reusados entre proyectos, ayudando a la mantenibilidad y la reutilización de código.

Observatorio

Metropolitano

CentroGeo

Observatorio

Metropolitano

CentroGeo

Observatorio

Metropolitano

CentroGeo

Observatorio

Metropolitano

CentroGeo

Observatorio

Metropolitano

CentroGeo

Observatorio

Metropolitano

CentroGeo

Aplicaciones

Monitoreo Ambiental:

  • Visualización en tiempo real de la calidad del aire y niveles de agua.
  • Exploración interactiva por regiones geográficas.
  • Análisis de tendencias ambientales históricas.

Observatorio

Metropolitano

CentroGeo

Aplicaciones

Observatorio

Metropolitano

CentroGeo

Planificación Urbana y Territorial:

  • Visualización de mapas interactivos para análisis de uso del suelo y densidad poblacional.
  • Herramientas para visualizar infraestructura y propuestas de desarrollo.
  • Facilitación de decisiones informadas en urbanismo.

Aplicaciones

Gestión de Desastres:

  • Integración de datos geoespaciales en tiempo real sobre fenómenos naturales.
  • Herramientas para la coordinación de respuestas a emergencias.
  • Evaluación visual de daños en situaciones de desastre.

Observatorio

Metropolitano

CentroGeo

Aplicaciones

Agricultura de Precisión:

  • Integración de datos de satélite y sensores terrestres.
  • Optimización de la gestión del riego y uso de fertilizantes.
  • Detección y visualización de enfermedades en cultivos.

Observatorio

Metropolitano

CentroGeo

Aplicaciones

Conservación de la Biodiversidad:

  • Visualización de información sobre hábitats y niveles de biodiversidad.
  • Análisis del impacto del cambio climático en áreas protegidas.
  • Planificación de acciones de conservación mediante análisis espaciales.

Observatorio

Metropolitano

CentroGeo

Ecosistema de ciencia de datos del OMCGeo

Observatorio

Metropolitano

CentroGeo

Visualización interactiva de redes geoespaciales

Framework de visualización basado en componentes

Concentrador distribuido de tableros de datos

Procesamiento distribuido de redes sociales

Productos

  • 2 artículos de revista con IF (Dash Sylvereye y Whistlerlib)
  • 3 registros de derechos de autor INDAUTOR (Dash Sylvereye, Weaverlet y Whistlerlib)
  • Portal de visualización del OMCGeo (AlbaHub)
  • 4 charlas de divulgación (Dash Sylvereye, Whistlerlib y Weaverlet)
  • Curso de visualización de datos en el posgrado integrado del CentroGeo (Weaverlet)

Observatorio

Metropolitano

CentroGeo

¡Gracias!

 

 

agarcia@centrogeo.edu.mx

 

Observatorio Metropolitano CentroGeo

Escuela de Verano CentroGeo 2024

By Alberto Garcia-Robledo

Escuela de Verano CentroGeo 2024

  • 45