DatavizGroup/Final_version01
0
1# Imports.2import streamlit as st3import seaborn as sns4import pandas as pd5import matplotlib.pyplot as plt6import altair as alt7from pathlib import Path8import plotly.express as px9import geopandas as gpd10import folium11from streamlit_folium import st_folium12import json13import os14from pathlib import Path15import circlify16import plotly.graph_objects as go17 18# point Streamlit at a writable folder19os.environ["STREAMLIT_CONFIG_DIR"] = "/tmp/.streamlit"20Path(os.environ["STREAMLIT_CONFIG_DIR"]).mkdir(parents=True, exist_ok=True)21 22 23# Define paths.24# Path for geo_json.25GEOJSON_PATH = Path(__file__).parent / "County_Boundary.geojson"26 27# Handle the error for geo_json.28try:29 gdf_counties = gpd.read_file(GEOJSON_PATH)30except FileNotFoundError:31 st.error("Error: 'County_Boundary.geojson' file not found in the /app/src/ directory. Please ensure the file is included in the project.")32 st.stop()33 34# Path for crime data.35DATA_PATH = Path(__file__).parent / "crime_data.csv" # /app/src/crime_data.csv36REGION_DATA_PATH = Path(__file__).parent / "area_lookup.csv"37 38# ── 0. Page configuration ──39st.set_page_config(40 page_title="Analyze Crime Distributions", 41 page_icon="📊", 42 layout="wide"43)44st.markdown("""45 <style>46 .title {47 text-align: center;48 padding: 25px;49 color: #2c3e50;50 font-family: 'Source Sans Pro', sans-serif;51 }52 /* Paragraph/write-up styling */53 .description {54 font-size: 18px; /* comfortable reading size */55 line-height: 1.6; /* good spacing */56 color: #4b4b4b; /* dark grey text */57 text-align: justify; /* nice full-justified look */58 padding: 0 10px 20px; /* side & bottom padding */59 font-family: 'Helvetica Neue', Arial, sans-serif;60 }61 .sectionheader {62 font-family: 'Source Sans Pro', sans-serif;63 font-size: 32px;64 color: #2c3e50;65 margin-top: 15px;66 margin-bottom: 10px;67 border-bottom: 3px solid #ccc;68 padding-bottom: 8px;69 }70 </style>71 """, unsafe_allow_html=True)72 73# 1. Page title74st.markdown("<div class='title'><h1> 🚔 Crime Pulse: LAPD Incident Explorer 🔎 </h1></div>", unsafe_allow_html=True)75st.markdown("<div class='title'><h3>Group 9: Vighnesh Gosavi, Vivian Lin, Chun-Wen Liou, Shivam Patel, Jinwen Zhang</h3></div>", unsafe_allow_html=True)76 77st.markdown("""<div class='description'> This application provides a suite of interactive visualizations—pie charts, 78bar charts, scatter plots, and more—that let you explore crime patterns in the LAPD dataset from multiple angles. 79Quickly see which offense categories dominate, compare arrest rates against non-arrests, track how crime volumes change over time, and examine geographic hotspots. 80These insights can help police departments, community organizations, and policymakers allocate resources more effectively and 81design targeted strategies to improve public safety.</div>""",unsafe_allow_html=True)82 83# 2. Data info & load84st.markdown("<div class='sectionheader'> Dataset Information </div>", unsafe_allow_html=True)85st.markdown(86 """87 <div class="description">88 <ul>89 <li><strong>Source:</strong> LAPD crime incidents dataset</li>90 <li><strong>Rows:</strong> one incident per row</li>91 <li><strong>Columns:</strong> e.g. <code>crm_cd_desc</code> (crime type), <code>arrest</code> (boolean), <code>year</code>, <code>location_description</code>, etc.</li>92 <li><strong>Purpose:</strong> Interactive exploration of top crime categories and arrest rates.</li>93 </ul>94 </div>95 """,96 unsafe_allow_html=True97)98 99# # Define paths.100# # Path for geo_json.101# GEOJSON_PATH = Path(__file__).parent / "County_Boundary.geojson"102 103# # Handle the error for geo_json.104# try:105# gdf_counties = gpd.read_file(GEOJSON_PATH)106# except FileNotFoundError:107# st.error("Error: 'County_Boundary.geojson' file not found in the /app/src/ directory. Please ensure the file is included in the project.")108# st.stop()109 110# # Path for crime data.111# DATA_PATH = Path(__file__).parent / "crime_data.csv" # /app/src/crime_data.csv112 113@st.cache_data114def load_data():115 return pd.read_csv(DATA_PATH)116def region_load_data():117 return pd.read_csv(REGION_DATA_PATH) 118 119if st.button("🔄 Refresh Data"):120 st.cache_data.clear() # Clear the cache121 st.toast("Data is refreshed",icon="✅") # Reload the data122 123# 2. Load and early‐exit if missing124df = load_data()125lookup = region_load_data()126map_region = dict(zip(lookup["OBJECTID"], lookup["APREC"]))127map_precinct = dict(zip(lookup["OBJECTID"], lookup["PREC"]))128 129if df.empty:130 st.stop()131 132# Map into new columns133df["RegionName"] = df["area"].map(map_region)134df["PrecinctCode"] = df["area"].map(map_precinct)135 136# 3. Data preview137st.markdown("<div class='sectionheader'> Data Preview </div>", unsafe_allow_html=True)138st.markdown(139 f"<div class='description'>"140 f"Total records: <strong>{df.shape[0]:,}</strong> | "141 f"Total columns: <strong>{df.shape[1]:,}</strong>"142 f"</div>",143 unsafe_allow_html=True144)145st.dataframe(df.head())146 147# Pie Chart 1: Top 10 Crime Types148st.markdown("<div class='sectionheader'> Top 10 Crime Types by Year </div>", unsafe_allow_html=True)149 150years = sorted(df["year"].dropna().astype(int).unique())151# Prepend an “All” option152options = ["All"] + years153selected_year = st.selectbox("Select Year", options, index=0) 154# # Year filter (shorter, above chart)155# col_empty, col_filter = st.columns([3,1])156# with col_filter:157# selected_year = st.selectbox(158# "Select Year",159# options=options,160# index=0, # default to “All”161# key="year_filter"162# )163 164# Filter according to selection165if selected_year == "All":166 filtered = df.copy()167else:168 filtered = df[df["year"] == selected_year]169 170# Compute top 10 crime types for that year ──171top_crimes = (172 filtered["crm_cd_desc"]173 .value_counts()174 .nlargest(10)175 .rename_axis("Crime Type")176 .reset_index(name="Count")177)178top_crimes["Percentage"] = top_crimes["Count"] / top_crimes["Count"].sum()179 180#Key Metrics181st.markdown("### Key Metrics", unsafe_allow_html=True)182col1, col2, col3 = st.columns(3)183col1.metric(184 label="Total Incidents",185 value=f"{len(filtered):,}"186)187col2.metric(188 label="Unique Crime Types",189 value=f"{filtered['crm_cd_desc'].nunique():,}"190)191# compute share of the top crime192top_share = top_crimes.iloc[0]["Percentage"] 193col3.metric(194 label=f"Share of Top Crime ({top_crimes.iloc[0]['Crime Type']})",195 value=f"{top_share:.1%}"196)197 198 199# -------------------------------- Plot 1: Pie(Donut) Chart --------------------------------200fig = px.pie(201 top_crimes,202 names="Crime Type",203 values="Count",204 hole=0.4,205 color_discrete_sequence=px.colors.sequential.Agsunset,206 title=" "207)208 209fig.update_traces(210 textposition="outside",211 textinfo="label+percent",212 pull=[0.02] * len(top_crimes),213 marker=dict(line=dict(color="white", width=1))214)215 216fig.update_layout(217 legend_title_text="Crime Type",218 margin=dict(t=40, b=40, l=20, r=20),219 height=600,220 width=450,221 title_x=0.5222)223 224# Display the plot.225st.plotly_chart(fig, use_container_width=True)226 227# Description.228st.markdown("""<div class="description"> The donut chart elegantly shows how ten key crime categories divide the incidents for the selected year into distinct slices. 229Circular rings highlight property crimes especially vehicle theft as the most common offenses, while smaller wedges represent less frequent incidents such as vandalism, 230criminal threats and minor burglary. Violent acts such as simple assault and robbery occupy medium sized segments, creating a clear visual hierarchy of frequency without 231relying on specific numbers. By pairing each slice with its label, the chart provides an immediate intuitive understanding of which crime types contribute most to overall 232volume and which are comparatively rare, helping stakeholders focus on the offenses that matter most.</div>""",unsafe_allow_html=True)233 234# -------------------------------- Plot : Bubble Map of Incident Counts by Region --------------------------------235# st.markdown("<div class='sectionheader'>Crime Hotspots by Region</div>", unsafe_allow_html=True)236 237# # 1. Aggregate counts and centroids238# region_stats = (239# df240# .groupby("RegionName")241# .agg(242# Count = pd.NamedAgg(column="crm_cd_desc", aggfunc="size"),243# Latitude = pd.NamedAgg(column="lat", aggfunc="mean"),244# Longitude = pd.NamedAgg(column="lon", aggfunc="mean")245# )246# .reset_index()247# )248 249# # 2. Build the bubble map250# fig = px.scatter_mapbox(251# region_stats,252# lat="Latitude",253# lon="Longitude",254# size="Count", # bubble size ~ incident volume255# color="Count", # color gradient for emphasis256# hover_name="RegionName",257# hover_data={"Count":True, "Latitude":False, "Longitude":False},258# size_max=30, # max bubble diameter259# zoom=10, # adjust to focus your city260# mapbox_style="open-street-map",261# title="Crime Volume by Region (Bubble Map)"262# )263 264# # 3. Tidy layout265# fig.update_layout(266# margin=dict(t=50, b=0, l=0, r=0),267# legend_title_text="Incident Count",268# title_x=0.5269# )270 271# # 4. Render272# st.plotly_chart(fig, use_container_width=True)273 274# -------------------------------- Plot 2: Stacked Bar Charts for Regions --------------------------------275st.markdown("<div class='sectionheader'>Crime Composition by Region: Top 5 Offenses </div>", unsafe_allow_html=True)276# 1. Compute counts per region and crime277counts = (278 df279 .groupby(['RegionName', 'crm_cd_desc'])280 .size()281 .reset_index(name='Count')282)283 284# 2. For each region, keep only its top 5 crime types285top5_per_region = (286 counts287 .groupby('RegionName', group_keys=False)288 .apply(lambda grp: grp.nlargest(5, 'Count'))289)290 291# 3. Draw a stacked bar chart292fig = px.bar(293 top5_per_region,294 x='RegionName',295 y='Count',296 color='crm_cd_desc',297 color_discrete_sequence=px.colors.sequential.Agsunset,298 title='Top 5 Crimes by Region',299 labels={'crm_cd_desc': 'Crime Type'},300 height=600301)302 303# 4. Tweak layout for readability304fig.update_layout(305 barmode='stack', 306 xaxis_tickangle=-45,307 xaxis_title='', 308 yaxis_title='Incident Count',309 legend_title_text='Crime Type',310 margin=dict(t=50, b=150, l=50, r=50)311)312 313# 5. Render in Streamlit314st.plotly_chart(fig, use_container_width=True)315# Description.316st.markdown("""<div class="description"> This stacked‐bar chart breaks down each region’s crime profile by its five most common offenses. The bars’ 317layers show how certain neighborhoods are dominated by property crimes (like vehicle theft and petty theft), whereas others carry a heavier share of 318violent or specialty offenses. By grouping all five slices together, the visualization highlights both the volume and mix of crimes in each area—revealing, 319for example, precincts where assault plays a disproportionately large role versus those driven mainly by theft. This makes it straightforward to compare how 320offense patterns differ from one region to the next.</div>""",unsafe_allow_html=True)321 322# -------------------------------- Plot 3: Line Chart for Incident Counts by Region --------------------------------323st.markdown("<div class='sectionheader'>Incidents Trends over Time </div>", unsafe_allow_html=True)324# 1. Aggregate total incidents by year325yearly_region = (326 df327 .groupby(["year", "RegionName"])328 .size()329 .reset_index(name="Count")330)331 332# 2. Let the user pick one region to highlight333regions = sorted(yearly_region["RegionName"].unique())334sel_region = st.selectbox("Select Region", ["All"] + regions, index=0)335 336if sel_region != "All":337 yearly_region = yearly_region[yearly_region["RegionName"] == sel_region]338 339# 3. Plot a smooth line per region (or just the one selected)340fig = px.line(341 yearly_region,342 x="year",343 y="Count",344 color="RegionName",345 title=(" "),346 labels={"year":"Year", "Count":"Incident Count"}347)348 349# 4. Add LOWESS smoothing (optional)350for trace in fig.data:351 trace.update(mode="lines") # remove markers352 353st.plotly_chart(fig, use_container_width=True)354# Description.355st.markdown("""<div class="description"> This multi‐line chart tracks how total crime incidents have evolved across LAPD regions from 2020 356through 2025. Each colored line represents a different precinct, letting you compare their trajectories side by side. You’ll notice that most 357areas rose to a peak around 2022 before tapering off, while a handful of regions bucked the trend—either holding steady or dipping earlier. The 358clear visual of converging and diverging lines makes it easy to spot which precincts saw the sharpest upticks, which managed to keep incidents 359relatively flat, and how the overall pattern shifted over the five‐year span.</div>""",unsafe_allow_html=True)360 361 362 363# -------------------------------- Plot : Bubble Map of Incident Counts by Region NO MAP --------------------------------364# st.markdown("<div class='sectionheader'>Crime Hotspots by Region NO MAP</div>", unsafe_allow_html=True)365 366# # 1. Aggregate total incidents by region and pick top 10367# region_counts = (368# df369# .groupby("RegionName") # group by your text field370# .size() # count rows371# .reset_index(name="Count") # turn it into a DataFrame with columns RegionName & Count372# )373# top_regions = region_counts.head(10)374# # 2. Build the bubble chart375# fig = px.scatter(376# top_regions,377# x='Count',378# y='RegionName',379# size='Count', # bubble area ∝ incident count380# color='Count', # color scale also shows volume381# hover_name='RegionName', # show region on hover382# hover_data={'Count':True},383# size_max=60, # max bubble diameter384# title='Top 10 Regions by Crime Volume (Bubble Chart)'385# )386 387# # 3. Tweak layout388# fig.update_layout(389# xaxis_tickangle=-45, # tilt x-labels so they’re legible390# margin=dict(t=50, b=100),391# yaxis_title='Incident Count',392# xaxis_title=''393# )394 395# # 4. Render in Streamlit396# st.plotly_chart(fig, use_container_width=True)397 398# -------------------------------- Plot 4: Heat Map --------------------------------399st.markdown("<div class='sectionheader'> HeatMap </div>", unsafe_allow_html=True)400# Count the crime type and list out the top 10 crime type that have the most cases.401top_crimes = df['crm_cd_desc'].value_counts().nlargest(10).index402df_top = df[df['crm_cd_desc'].isin(top_crimes)]403 404# Group by crime type and year.405heatmap1_data = df_top.groupby(['crm_cd_desc', 'year']).size().unstack(fill_value=0)406 407# Create the heat map.408fig, ax = plt.subplots(figsize=(8, 4))409 410# 2. Draw into that Axes411sns.heatmap(412 heatmap1_data,413 annot=True,414 fmt="d",415 cmap="YlOrRd",416 ax=ax,417 annot_kws={"size": 6}, # smaller numbers in cells418 cbar_kws={"shrink": 0.5} # shrink the colorbar419)420 421# 3. Set titles/labels with a smaller font422ax.set_title("Top 10 Crime Types by Year", fontsize=10, pad=8)423ax.set_xlabel("Year", fontsize=8, labelpad=6)424ax.set_ylabel("Crime Type", fontsize=8, labelpad=6)425 426# Shrink the tick labels427ax.tick_params(axis='x', labelsize=10, rotation=0) # no rotation, smaller font428ax.tick_params(axis='y', labelsize=10) # smaller font429 430# 4. Tight layout431fig.tight_layout()432 433# 5. Render in Streamlit434st.pyplot(fig)435 436# Description.437st.markdown("""<div class="description">438This heatmap shows the frequency of the top 10 crimes from 2020 to 2025. The x axis is year and the y axis is crime type. The colormap is 'YlOrRd' to create a distinct visual difference in number of incidents. Dark red means that the incident frequency is high while light yellow means that the incident frequency is low. 'Vehicle Stolen' seems to be the most prevalent crime for all five years, given its values are highlighted in deeper shades of red. 'Vehicle Stolen' also seems to fluctuate between 20000 and 24000 throughout the five years. 'Thief of identity' also saw a spike in incident frequency for 2022, recording 21251 crimes. Limiting the heatmap to top 10 crimes addressed the most prominent crimes in LA. Since 2025 is not over, data for that year is still relatively inclusive. This visualization can help law enforcement easily detect trends of different crimes for a specific year. This data may allow them to predict future rates and be able to allocate resources accordingly to mitigate these crimes.439</div>""",unsafe_allow_html=True)440 441 442# -------------------------------- Plot 5: Line Chart --------------------------------443st.markdown("<div class='sectionheader'> Line Chart </div>", unsafe_allow_html=True)444# Filter out the year 2025 since it is not the end, so that the trend can't be see.445df = df[df['year'] != 2025]446 447# Group the each crime type by year.448yearly_crime_counts = (449 df.groupby(["year", "crm_cd_desc"])450 .size()451 .reset_index(name="Count")452)453 454# Filter the crime types that have the most top 5 cases.455top5_crimes = df["crm_cd_desc"].value_counts().nlargest(5).index456filtered_crimes = yearly_crime_counts[yearly_crime_counts["crm_cd_desc"].isin(top5_crimes)]457 458# Plot the line plot.459line_chart = alt.Chart(filtered_crimes).mark_line(point=True).encode(460 x=alt.X("year:O", title="Year"),461 y=alt.Y("Count:Q", title="Number of Incidents"),462 color=alt.Color("crm_cd_desc:N", title="Crime Type"),463 tooltip=["year", "crm_cd_desc", "Count"]464).properties(465 title="Yearly Trends of Top 5 Crime Types",466 width=700, 467 height=400468)469 470# Display the plot.471line_chart472 473# Description.474st.markdown("""<div class="description">475This plot is a line chart visualizing the annual number of incidents for the top 5 most frequent crime types over a five-year period, from 2020 to 2024. 476Each line represents a distinct crime type, allowing for easy comparison of trends across different categories. 477The x-axis represents the year, the y-axis indicates the number of incidents, and a legend identifies the color corresponding to each specific 478crime type: Battery - Simple Assault, Burglary From Vehicle, Theft of Identity, Vandalism - Felony , and Vehicle - Stolen. The plot highlights 479the fluctuations and overall trajectories of these major crime categories across the years.</div>""",unsafe_allow_html=True)480 481 482# -------------------------------- Plot 6: Map --------------------------------483st.markdown("<div class='sectionheader'> Explore LA Crime Patterns: An Interactive Folium Map </div>", unsafe_allow_html=True)484# Load the data.485with open(GEOJSON_PATH, "r", encoding="utf-8") as f:486 geojson_data = json.load(f)487 488# Identify top 10 crime types489top_10_crimes = df['crm_cd_desc'].value_counts().nlargest(10).index.tolist()490 491# Filter the main DataFrame to include only top 10 crimes492df_top = df[df['crm_cd_desc'].isin(top_10_crimes)]493 494# Creat dropdown menu495years = sorted(df['year'].unique())496year_dropdown = st.selectbox("Year: ", years)497crime_dropdown = st.selectbox("Crime Type: ", top_10_crimes)498 499# Filter data.500df_filtered = df[(df['year'] == year_dropdown) & (df['crm_cd_desc'] == crime_dropdown)].sample(n=300, random_state=1)501 502# Create the new folium map to make the map more interactive.503# Method comes from: https://folium.streamlit.app/.504new_map = folium.Map(location=[df_filtered['lat'].mean(), df_filtered['lon'].mean()], zoom_start=10)505 506# Add county boundary507folium.GeoJson(geojson_data, name="County Boundaries").add_to(new_map)508 509# # Create the map.510# def crime_map(year, crime):511# df_filtered = df[(df['year'] == year) & (df['crm_cd_desc'] == crime)].sample(n=300, random_state=1)512# gdf_points = gpd.GeoDataFrame(513# df_filtered,514# geometry=gpd.points_from_xy(df_filtered['lon'], df_filtered['lat']),515# crs="EPSG:4326"516# )517 518# fig, ax = plt.subplots(figsize=(10, 10))519# gdf_counties.plot(ax=ax, color='lightgray', edgecolor='white')520# gdf_points.plot(ax=ax, color='red', markersize=10, alpha=0.6)521# ax.set_title(f"{crime} - {year}")522# ax.set_xlabel("Longitude")523# ax.set_ylabel("Latitude")524# plt.grid(True)525# st.pyplot(fig)526 527# # Call the function with selected values528# crime_map(year_dropdown, crime_dropdown)529 530# Using for-loop to add the crime points531for _, row in df_filtered.iterrows():532 folium.CircleMarker(533 location=[row['lat'], row['lon']],534 radius=3,535 color='red',536 fill=True,537 fill_opacity=0.6,538 popup=row['crm_cd_desc']539 ).add_to(new_map)540 541# Display the new map.542st_folium(new_map, width=1000, height=500, use_container_width=True)543 544# Description.545st.markdown("""<div class="description">546This visualization uses Folium to build an interactive map of crime distribution in Los Angeles, highlighting the geospatial clustering characteristics of different years and crime types, and emphasizing the user's experience of freely exploring the map. The base map uses real streets and geographic backgrounds to enhance the spatial visualization of the image. The map shows the administrative boundaries of Los Angeles County in blue polygons, which are loaded with GeoJSON data and overlaid on the map to specify the geographic boundaries of crime locations. The red dots on the map represent the location of individual crimes, and the system samples no more than 300 data items from this category for visualization, with each dot pinpointed by latitude and longitude coordinates. The map supports full Leaflet.js functionality, including zooming, dragging, layer control, and other operations, which greatly enhances the flexibility of data exploration. A drop-down menu in the upper left corner of the page allows users to customize filters for specific years and crime types, enabling instant updates to the map content.547</div>""",unsafe_allow_html=True)548 549# -------------------------------- Plot 7: Stacked Bar Chart --------------------------------550st.markdown("<div class='sectionheader'>Trends in Top 10 Crime Types (2020–2024)</div>", unsafe_allow_html=True)551# Group by crime type and year.552stacked_year_df = df_top.groupby(['year', 'crm_cd_desc']).size().reset_index(name='count')553 554# Create the stacked bar chart.555bar_chart = alt.Chart(stacked_year_df).mark_bar().encode(556 x=alt.X('year:O', title='Year'),557 y=alt.Y('count:Q', stack='zero', title='Number of Incidents'),558 color=alt.Color('crm_cd_desc:N', title='Crime Type'),559 tooltip=['year', 'crm_cd_desc', 'count']560).properties(561 width=600,562 height=400,563 title='Stacked Crime Composition by Year (Top 10 Crime Types)'564)565 566# Display the plot.567st.altair_chart(bar_chart, use_container_width=True)568 569# Description.570st.markdown("""<div class="description">571Description: Our stacked bar chart shows the number of reported crimes for the top 10 most common crime types from 2020 to 2024. Each bar represents a year, and the different colors in the bars show different types of crimes, like stolen vehicles, burglary, vandalism, and assault. The taller the colored section, the more incidents of that crime there were in that year.572 573By observing the plot, we can find out that 2022 had the most crimes, the year had the second most crimes is 2023, and etc. Besides that, we can also find out that some crimes, like vehicle theft, petty theft, and burglary from vehicles, happened a lot every year and make up a big part of the total.574</div>""",unsafe_allow_html=True)575 576# -------------------------------- Plot 8: Bar Chart --------------------------------577st.markdown("<div class='sectionheader'>Crime Rankings for Selected Year</div>", unsafe_allow_html=True)578# Group by crime type and year.579heatmap1_df = df_top.groupby(['crm_cd_desc', 'year']).size().reset_index(name='count')580 581# Create the slider based on the previous heatmap.582year_slider = alt.binding_range(min=heatmap1_df['year'].min(), max=heatmap1_df['year'].max(), step=1)583year_select = alt.selection_point(fields=['year'], bind=year_slider, value = 2022, name="Select")584 585# Convert the heatmap into bar chart.586barchart = alt.Chart(heatmap1_df).mark_bar().encode(587 x=alt.X('crm_cd_desc:N', title='Crime Type', sort='-y'),588 y=alt.Y('count:Q', title='Number of Incidents'),589 color=alt.Color('crm_cd_desc:N', title='Crime Type'),590 tooltip=['crm_cd_desc', 'count']591).transform_filter(592 year_select593).add_params(594 year_select595).properties(596 width=600,597 height=400,598 title='Top 10 Crime Types (Filtered by Year)'599)600 601# Display the plot.602barchart603 604# Description.605st.markdown("""<div class="description"> This interactive bar chart allows users to explore the most frequently reported crime types in Los Angeles by year. By adjusting the slider below the chart, the visualization updates in real time to show the top ten crime categories for the selected year. Each bar represents the total number of incidents, with color coding used to distinguish different crime types and a legend on the right for reference.606This visualization makes it easy to compare how the composition of major crime types evolves over time and to detect emerging issues that may require further investigation or policy response.607</div>""",unsafe_allow_html=True)608 609st.markdown("<div class='title'><h4>Reference: LAPD Crime Data</h4></div>", unsafe_allow_html=True)