MaxAtoms
Re-add shared.py
d59e9e4
import faicons as fa
from ipyleaflet import Map, Marker, LayerGroup, Circle, Icon, AwesomeIcon, DivIcon, basemaps, GeoJSON
import matplotlib.pyplot as plt
from pandas.core.frame import functools
# Load data and compute static values
from borgarlina3_leaflet import load_and_preprocess_data
from shared import app_dir
from shinywidgets import render_widget
from shiny import reactive, render
from shiny.express import input, ui
# Import from backend
from data_processing.data_provider import Data_provider
initBackend = Data_provider()
def generateStops(year):
geojson_file = f"given_data/cityline_geojson/cityline_{year}.geojson"
pop_file = "given_data/ibuafjoldi.csv"
smallarea_file = "given_data/smasvaedi_2021.json"
dwellings_file = "given_data/ibudir.csv"
gpdStops, _, all_small_areas = load_and_preprocess_data(geojson_file, pop_file, smallarea_file, dwellings_file)
points = []
# Assuming your GeoDataFrame is named 'gdf'
for _, row in gpdStops.iterrows():
point = row["geometry"]
color = row["line"]
if color not in ["red", "yellow", "blue", "green", "purple", "orange"]:
color = color.split("/")
points.append(((point.y, point.x), color))
return points, all_small_areas
@reactive.effect
@reactive.event(input.show)
def show_important_message():
m = ui.modal(
ui.div(
ui.div(
ui.p(
"Our team’s solution for evaluating the Borgarlína bus stop placements was awarded the best solution at the "
"Gagnavist 2024 conference. Learn more about the event here: ",
ui.a(
"Datathon 2024",
href="https://www.ihpc.is/events/radstefna-um-islenska-gagnavistkerfid---gagnathon-2024",
target="_blank"
)
),
ui.div(
ui.p("Highlights:"),
ui.div("- Integrated datasets: Income deciles, population density, and age distribution."),
ui.div("- Developed the solution over a single weekend!"),
),
ui.p(""),
ui.div(
ui.p("Special thanks to:"),
ui.div("- Statistics Iceland"),
ui.div("- The Ministry of Finance and Economic Affairs"),
ui.div("- The Ministry of Higher Education, Innovation, and Industry"),
),
ui.p(""),
ui.p("Looking forward to participating again next year!"),
ui.p("Alexander, Birgir, Elvar, Thomas, and Jan")
),
),
size="xl",
easy_close=True,
footer=None,
title="Datathon 2024: A Winning Experience!"
)
ui.modal_show(m)
# Add page title and sidebar
ui.page_opts(title="Borgarlínan", fillable=True)
with ui.sidebar(open="open"):
ui.input_select("year", "Year:", {2025: "2025", 2029: "2029", 2030: "2030"})
ui.input_slider("rad", "Stop reach radius:", min=200, max=1000, value=400),
# station_coord, w_density=1, w_income=1, w_age=1):
ui.input_numeric("w_density", "Density Weight", "1")
ui.input_numeric("w_income", "Income Weight", "1")
ui.input_numeric("w_age", "Age Weight", "1")
ui.input_action_button("reset", "Reset zoom")
ui.input_action_button("show", "About Us")
with ui.layout_columns(col_widths=[8, 4]):
with ui.card(full_screen=True):
ui.card_header("Capital area")
@render_widget
def map():
return Map(
basemap=basemaps.CartoDB.Positron)
with ui.layout_column_wrap(width="450px"):
with ui.layout_columns(col_widths=(6, 6), min_height="450px"):
with ui.value_box(theme="text-red", showcase=fa.icon_svg("bus", width="50px"),):
"Average score"
@render.text
def render_line_score():
score = lineScore().get("red", 0)
if score:
return str(int(score))
else:
return str("Pending")
with ui.value_box(theme="text-blue",showcase=fa.icon_svg("bus", width="50px"),):
"Average score"
@render.text
def render_line_score1():
score = lineScore().get("blue", 0)
if score:
return str(int(score))
else:
return str("Pending")
with ui.value_box(theme="text-yellow" ,showcase=fa.icon_svg("bus", width="50px"),):
"Average score"
@render.text
def render_line_score2():
score = lineScore().get("orange", 0)
if score:
return str(int(score))
else:
return str("Pending")
with ui.value_box(theme="text-green",showcase=fa.icon_svg("bus", width="50px"),):
"Average score"
@render.text
def render_line_score4():
score = lineScore().get("green", 0)
if score:
return str(int(score))
else:
return str("Pending")
with ui.value_box(theme="text-purple",showcase=fa.icon_svg("bus", width="50px"),):
"Average score"
@render.text
def render_line_score3():
score = lineScore().get("purple", 0)
if score:
return str(int(score))
else:
return str("Pending")
with ui.value_box(theme="text-black",showcase=fa.icon_svg("route", width="50px")):
"Total average score"
@render.text
def render_line_score5():
return str(int(sum(lineScore().get(color, 0) for color in ["red", "blue", "orange", "purple", "green"])))
with ui.card(min_height="450px"):
with ui.navset_pill(id="tab"):
with ui.nav_panel("Score"):
@render.text
def totalScore():
score = scores()
return "Total score: " + str(int(score["total_score"]))
@render.plot(alt="A pie chart of score contributions from age, income, and density.")
def contribution_pie_chart():
print("Generating pie chart of contributions")
# Get score components
score = scores()
age_contribution = score["age_score"]
income_contribution = score["income_score"]
density_contribution = score["density_score"]
# Data for the pie chart
contributions = [age_contribution, income_contribution, density_contribution]
labels = ["Age", "Income", "Density"]
colors = ["#FD4D86", "#36DEC2", "#704CB0"] # Custom colors for the segments
# Create a Matplotlib figure
fig, ax = plt.subplots(figsize=(5, 5))
# Create the pie chart
ax.pie(
contributions,
labels=labels,
autopct='%1.1f%%',
startangle=90,
colors=colors,
textprops={'fontsize': 12}
)
# Add a title
ax.set_title("Score Contributions", fontsize=16)
# Return the figure for rendering in Shiny
return fig
with ui.nav_panel("Income"):
"Income Score"
@render.text
def incomeScore():
score = scores()
return score["income_score"]
@render.plot(alt="A chart of income distribution.")
def income_plot():
print("Generating income distribution bar chart")
# Get selected stop coordinates
x, y = stop.get()
station_coord = (y, x)
# Fetch income distribution data from the Data_provider instance
income_data = initBackend.get_station_score(station_coord, radius=input.rad())['income_data'] # Assume this returns a dictionary
# Example structure: {1: 150, 2: 200, 3: 180, ...}
income_brackets = list(income_data.keys())
populations = list(income_data.values())
# Create a Matplotlib figure
fig, ax = plt.subplots(figsize=(6, 3))
# Create the bar chart
ax.bar(income_brackets, populations, color='#36DEC2')
# Customize the plot
ax.set_title("Population by Income Bracket")
ax.set_xlabel("Income Bracket")
ax.set_ylabel("Population")
ax.set_xticks(income_brackets)
ax.set_xticklabels(income_brackets, rotation=45, ha="right")
# Return the figure for rendering in Shiny
return fig
with ui.nav_panel("Age"):
"Age Score"
@render.text
def ageScore():
score = scores()
return score["age_score"]
@render.plot(alt="A bar chart of age distribution.")
def age_plot():
print("Generating age distribution bar chart")
# Get selected stop coordinates
x, y = stop.get()
station_coord = (y, x)
# Fetch age distribution data from the Data_provider instance
age_data = initBackend.get_station_score(station_coord, radius=input.rad())['age_data'] # Assume this returns a dictionary
# Example structure: {'0-4 ára': 120, '5-9 ára': 140, ...}
age_brackets = list(age_data.keys())
populations = list(age_data.values())
# Create a Matplotlib figure
fig, ax = plt.subplots(figsize=(6, 3))
# Create the bar chart with custom colors
ax.bar(age_brackets, populations, color='#FD4D86')
# Customize the plot
ax.set_title("Population by Age Bracket", fontsize=14)
ax.set_xlabel("Age Bracket", fontsize=12)
ax.set_ylabel("Population", fontsize=12)
ax.set_xticks(range(len(age_brackets)))
ax.set_xticklabels(age_brackets, rotation=45, ha="right", fontsize=10)
# Return the figure for rendering in Shiny
return fig
with ui.nav_panel("Density"):
"Density"
@render.text
def sensityScoer():
score = scores()
return float(score["density_score"] * 1000000)
@render.plot(alt="A bar chart of density scores for all areas within the radius.")
def density_plot():
print("Generating density score bar chart")
# Get selected stop coordinates
x, y = stop.get()
station_coord = (y, x)
# Fetch small area contributions from the Data_provider instance
small_area_contributions = initBackend.get_station_score(
station_coord,
radius=input.rad(),
w_density=input.w_density(),
w_income=input.w_income(),
w_age=input.w_age()
)['small_area_contributions']
# Extract density scores for each small area
area_ids = [area_id for area_id in small_area_contributions.keys()]
density_scores = [area_data['density_score'] for area_data in small_area_contributions.values()]
# Create a Matplotlib figure
fig, ax = plt.subplots(figsize=(6, 3))
# Create the bar chart
ax.bar(area_ids, density_scores, color='#704CB0')
# Customize the plot
ax.set_title("Density Scores of Small Areas", fontsize=14)
ax.set_xlabel("Small Area ID", fontsize=12)
ax.set_ylabel("Density Score", fontsize=12)
ax.set_xticks(range(len(area_ids)))
ax.set_xticklabels(area_ids, rotation=45, ha="right", fontsize=10)
# Return the figure for rendering in Shiny
return fig
ui.include_css(app_dir / "styles.css")
# --------------------------------------------------------
# Reactive calculations and effects
# --------------------------------------------------------
@reactive.effect
def _():
year = input.year()
stops, small_areas = generateStops(year)
rad = input.rad()
markers = []
circles = []
for layer in map.widget.layers[:]:
if layer.name in ["stops", "radius", "polygons", "heatmap"]:
map.widget.remove_layer(layer)
# Add polygons from small_areas
polygons_layer = []
for _, area in small_areas.iterrows():
geojson_data = area["geometry"].__geo_interface__
geojson_dict = {
"type": "Feature",
"properties": {},
"geometry": geojson_data
}
geojson = GeoJSON(
data=geojson_dict, # Pass the dictionary here
style={
"color": "#005485", # Border color
"fillColor": "white", # Fill color
"opacity": 0.5, # Border opacity
"weight": 1.0, # Border thickness
"dashArray": "5, 5", # Optional dashed border
"fillOpacity": 0.3 # Fill opacity
},
hover_style={"color": "#005485", "weight": 1}, # Highlight on hover
name="polygons"
)
polygons_layer.append(geojson)
i = 0
for stop, color in stops:
if type(color) == list:
smallerRad = 0
for c in color:
circle = Circle()
circle.location = stop
circle.radius = rad - smallerRad
circle.color = c
circle.fill_opacity = 0.1
circle.name = str(i)
circles.append(circle)
smallerRad =+ 50
else:
circle = Circle()
circle.location = stop
circle.radius = rad
circle.color = color
circle.fill_color = color
circle.fill_opacity = 0.1
circle.name = str(i)
circles.append(circle)
icon = AwesomeIcon(name="bus", marker_color="black", icon_color="white")
# icon1 = DivIcon(html = '<div style="border-radius:50%;background-color: black; width: 10px; height: 10px;"></div>')
# icon2 = Icon(icon_url="marker.png")
marker = Marker(location=stop,
icon=icon,
icon_anchor=(10,10),
icon_size=(0,0),
draggable=True)
marker.name = str(i)
marker.on_click(functools.partial(create_marker_callback, id=stop))
marker.on_move(functools.partial(reset_marker, index=i))
markers.append(marker)
i += 1
layerGroup = LayerGroup(layers=markers, name="stops")
layerGroup2 = LayerGroup(layers=circles, name="radius")
map.widget.add(layerGroup)
map.widget.add(layerGroup2)
# Add polygon layers to the map
polygon_group = LayerGroup(layers=polygons_layer, name="polygons")
map.widget.add(polygon_group)
stop = reactive.value()
def create_marker_callback(id, **kwargs):
# We can also get coordinates of the marker here
rad = input.rad()
zoom = 15.0
if rad > 500:
zoom = 14.8
map.widget.zoom = zoom
map.widget.center = kwargs["coordinates"]
stop.set(id)
def reset_marker(index, **kwargs):
cord = kwargs["location"]
x = cord[0]
y = cord[1]
for layer in map.widget.layers:
if layer.name == "radius": # Check for the correct LayerGroup
for circle in layer.layers:
if circle.name == str(index): # Match the Circle by name
circle.location = [x, y] # Update the Circle's location
stop.set((x,y))
@reactive.effect
def centerMap():
_ = input.reset()
map.widget.zoom = 11.8
map.widget.center = (64.11,-21.90)
@reactive.calc
def scores():
x, y = stop.get()
score = initBackend.get_station_score(station_coord=(y, x), w_density=input.w_density(), w_income=input.w_income(), w_age=input. w_age(), radius=input.rad())
return score
@reactive.calc
def lineScore():
listOfStops, _ = generateStops(input.year())
listOflines = {}
# Handle stops with single and multiple colors
for stop, color in listOfStops:
x, y = stop
# If the color is a list (multiple colors), iterate through it
if isinstance(color, list):
for single_color in color:
if single_color not in listOflines:
listOflines[single_color] = []
listOflines[single_color].append((y, x))
else:
# If it's a single color, process it normally
if color not in listOflines:
listOflines[color] = []
listOflines[color].append((y, x))
# Calculate scores for each line
lines = {}
for key, val in listOflines.items():
score = initBackend.line_score(
val,
w_density=input.w_density(),
w_income=input.w_income(),
w_age=input.w_age(),
radius=input.rad()
)
lines[key] = score["final_score"]
return lines