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?
Observatorio
Metropolitano
CentroGeo
Estructura de una app Dash
Observatorio
Metropolitano
CentroGeo
-
Una aplicación Dash está integrada de dos partes:
-
La descripción del UI (layout)
-
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