import dash
from dash import dcc, html, Input, Output, State
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.express as px
from flask_caching import Cache
# Initialize app with caching
app = dash.Dash(
__name__,
external_stylesheets=[dbc.themes.JOURNAL],
# Assets will be automatically loaded from the assets folder
assets_folder='assets'
)
app.title = "Programming Languagues Trend"
# Setup cache
cache = Cache(app.server, config={
'CACHE_TYPE': 'SimpleCache',
})
# Cache timeout in seconds (10 minutes)
TIMEOUT = 600
# Custom color map - moved outside main logic
language_color_map = {
'Python': '#4B8BBE', 'Java': '#B07219', 'JavaScript': '#F7DF1E',
'C/C++': '#555555', 'C#': '#178600', 'PHP': '#4F5D95',
'Ruby': '#CC342D', 'Swift': '#F05138', 'Kotlin': '#A97BFF',
'Go': '#00ADD8', 'Rust': '#DEA584', 'TypeScript': '#007ACC',
'R': '#198CE7', 'Perl': '#0298c3', 'Scala': '#c22d40',
'Haskell': '#5e5086', 'Lua': '#000080', 'Groovy': '#e69f56',
'Julia': '#a270ba', 'Dart': '#00B4AB', 'Matlab': '#bb92ac',
'Abap': '#E8274B', 'Ada': '#02f88c', 'Cobol': '#8a1267',
'Delphi/Pascal': '#8F9EC7', 'Objective-C': '#438eff',
'Powershell': '#012456', 'VBA': '#867db1', 'Visual Basic': '#945db7'
}
# Cached data loading function
@cache.memoize(timeout=TIMEOUT)
def load_data():
# Load and preprocess main dataset
df = (pd.read_csv("Popularity of Programming Languages from 2004 to 2024.csv",
parse_dates=['Date'], date_format='mixed')
.assign(Year=lambda x: x['Date'].dt.year))
# Load language details
data = {
'Language': ['Abap', 'Ada', 'C/C++', 'C#', 'Cobol', 'Dart', 'Delphi/Pascal',
'Go', 'Groovy', 'Haskell', 'Java', 'JavaScript', 'Julia',
'Kotlin', 'Lua', 'Matlab', 'Objective-C', 'Perl', 'PHP',
'Powershell', 'Python', 'R', 'Ruby', 'Rust', 'Scala',
'Swift', 'TypeScript', 'VBA', 'Visual Basic'],
'Category': ['Enterprise Development', 'Systems', 'Systems', 'Multi-purpose',
'Specific Purpose (Finance)', 'Mobile', 'Desktop', 'Systems',
'Web', 'Functional', 'Multi-purpose', 'Multi-purpose', 'Data',
'Mobile', 'Specific Purpose (Games)', 'Data', 'Mobile', 'Scripting',
'Web', 'Scripting', 'Multi-purpose', 'Data', 'Web', 'Systems',
'Web', 'Mobile', 'Web', 'Scripting', 'Desktop'],
'Users/Professionals': ['SAP Consultants', 'Systems Engineers', 'Systems Engineers',
'Windows Developers', 'Legacy Systems Programmers',
'Flutter Developers', 'Desktop Developers', 'Systems Engineers',
'Web Developers', 'Researchers', 'Web Developers',
'Web Developers', 'Data Scientists', 'Android Developers',
'Game Developers', 'Engineers', 'iOS Developers',
'System Administrators', 'Web Developers', 'System Administrators',
'Data Scientists', 'Statisticians', 'Web Developers',
'Systems Engineers', 'Web Developers', 'iOS Developers',
'Web Developers', 'Microsoft Office Users', 'Desktop Developers'],
}
programs_df = pd.DataFrame(data)
return df, programs_df
# Get data and prepare language options
df, programs_df = load_data()
language_options = [{"label": lang, "value": lang} for lang in df.columns[1:] if lang != 'Year']
# Create language selection modal with improved layout
language_select_modal = dbc.Modal(
[
# dbc.ModalHeader("Select Programming Languages"),
dbc.ModalBody([
dcc.Checklist(
id='language-checklist',
options=language_options,
value=['Python', 'Java'], # Better default selection
inline=True,
labelStyle={'display': 'inline-block', 'margin-right': '15px', 'margin-bottom': '8px'},
className="horizontal-checklist"
),
html.Div(id="selected-count", className="mt-2 text-muted")
]),
dbc.ModalFooter([
dbc.Button("Clear All", id="clear-all", color="secondary", className="me-2"),
dbc.Button("Close", id="close-modal", color="primary")
]),
],
id="language-select-modal",
size="xl",
)
# Improved app layout with better component organization
app.layout = dbc.Container([
dbc.Row([
# Left Sidebar
dbc.Col([
html.Div(
id='language-info-cards',
className="left-sidebar"
)
], width=12, lg=3, className="mb-4 mb-lg-0"),
# Main content
dbc.Col([
html.Div([
html.H3("Code Chronicles: The Changing Landscape of Programming",
className="mb-3 mt-2",
style={'font-weight': '300', 'color': '#333'}),
html.P("Track the popularity trends of programming languages since 2004",
className="text-muted mb-3"),
html.Hr(),
# Language selection controls
dbc.Row([
dbc.Col([
dbc.Button(
[html.I(className="fas fa-filter me-2"), "Select Programming Languages"],
id="open-modal",
color="primary",
className="mb-3 w-100"
)
], width=12, md=6),
dbc.Col([
dbc.RadioItems(
id="chart-view",
options=[
{"label": "Trends over Years", "value": "line"},
{"label": "YoY Relative Change", "value": "bar"}
],
value="line",
inline=True,
className="mb-3"
)
], width=12, md=6, className="text-md-end")
]),
# Modal for language selection
language_select_modal,
# Dynamic graph display based on selected view
html.Div(id="chart-container")
]),
], width=12, lg=9, className="main-content"),
], className="g-0")
], fluid=True)
# Callback for toggling the modal
@app.callback(
Output("language-select-modal", "is_open"),
[Input("open-modal", "n_clicks"), Input("close-modal", "n_clicks")],
[State("language-select-modal", "is_open")],
)
def toggle_modal(n1, n2, is_open):
if n1 or n2:
return not is_open
return is_open
# Callback for Clear All button
@app.callback(
Output("language-checklist", "value"),
Input("clear-all", "n_clicks"),
prevent_initial_call=True
)
def clear_checklist(n_clicks):
return []
# Callback to show selected count
@app.callback(
Output("selected-count", "children"),
[Input("language-checklist", "value")]
)
def show_selection_count(selected):
count = len(selected) if selected else 0
return f"{count} languages selected"
# Callback for updating the chart container based on view type
@app.callback(
Output('chart-container', 'children'),
[Input('chart-view', 'value'),
Input('language-checklist', 'value')]
)
def update_chart_container(view_type, selected_languages):
if not selected_languages:
return html.Div(
dbc.Alert("Please select at least one programming language to display data.",
color="info"),
className="my-5 text-center"
)
if view_type == "line":
return dcc.Graph(
id='language-graph',
figure=create_line_chart(selected_languages),
config={'displayModeBar': False},
className="mb-4"
)
else:
return dcc.Graph(
id='pct-change-bar-chart',
figure=create_bar_chart(selected_languages),
config={'displayModeBar': False},
className="mb-4"
)
# Refactored chart creation functions
def create_line_chart(selected_languages):
data = df.groupby('Year', as_index=False)[selected_languages].mean()
fig = px.line(
data,
x='Year',
y=selected_languages,
labels={'value': 'Popularity (%)', 'Year': 'Year'},
template='plotly_white',
markers=True,
color_discrete_map={lang: language_color_map.get(lang, '#999999') for lang in selected_languages}
)
fig.update_layout(
title='Programming Language Popularity Trends',
title_font_size=16,
legend_title_text='',
font=dict(family="Arial, sans-serif", size=12),
margin=dict(l=40, r=40, t=40, b=40),
xaxis=dict(showgrid=False),
yaxis=dict(showgrid=True, gridcolor='#eee', title='Popularity (%)'),
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
# Add hover template with better formatting
for trace in fig.data:
trace.hovertemplate = '%{y:.1f}%<extra>%{fullData.name} (%{x})</extra>'
return fig
def create_bar_chart(selected_languages):
# Calculate yearly mean for the selected languages
yearly_data = df.groupby('Year', as_index=False)[selected_languages].mean()
# Calculate percentage change across years
yearly_data_pct_change = yearly_data[selected_languages].pct_change() * 100
yearly_data_pct_change['Year'] = yearly_data['Year']
# Remove first year (NaN values)
yearly_data_pct_change = yearly_data_pct_change.dropna()
fig = px.bar(
yearly_data_pct_change,
x='Year',
y=selected_languages,
title='Year-over-Year Relative Percentage Change',
labels={'value': 'Relative Change (%)', 'variable': 'Language'},
barmode='group',
template='plotly_white',
color_discrete_map={lang: language_color_map.get(lang, '#999999') for lang in selected_languages}
)
fig.update_layout(
title_font_size=16,
legend_title_text='',
font=dict(family="Arial, sans-serif", size=12),
margin=dict(l=40, r=40, t=50, b=40),
xaxis=dict(showgrid=False, title='Year'),
yaxis=dict(showgrid=True, gridcolor='#eee', title='Change (%)'),
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
# Add a reference line at y=0
fig.add_shape(
type="line", line=dict(dash="dot", width=1, color="#666"),
y0=0, y1=0, x0=yearly_data_pct_change['Year'].min(),
x1=yearly_data_pct_change['Year'].max()
)
# Add hover template with better formatting
for trace in fig.data:
trace.hovertemplate = '%{y:.1f}%<extra>%{fullData.name} (%{x})</extra>'
return fig
# Callback for updating cards in the left sidebar
@app.callback(
Output('language-info-cards', 'children'),
[Input('language-checklist', 'value')]
)
def update_cards(selected_languages):
if not selected_languages:
return html.Div([
html.H5("Programming Language Details",
style={'margin-bottom': '15px', 'color': '#444', 'font-weight': '300'}),
html.Hr(),
html.P("Select languages to see details",
className="text-muted text-center my-4")
])
cards = [
html.H5("Programming Language Details",
style={'margin-bottom': '15px', 'color': '#444', 'font-weight': '300'}),
html.Hr(),
html.Div(f"{len(selected_languages)} languages selected",
className="text-muted mb-3 small")
]
for lang in selected_languages:
# Get language info or default values
try:
lang_info = programs_df[programs_df['Language'] == lang].iloc[0]
category = lang_info['Category']
users = lang_info['Users/Professionals']
except (IndexError, KeyError):
category = "Unknown"
users = "Various developers"
# Get language color or default
lang_color = language_color_map.get(lang, '#666666')
card = dbc.Card(
dbc.CardBody([
html.H6(lang, style={'font-weight': '500', 'color': lang_color}),
html.P(f"Category: {category}",
style={'font-size': '0.8rem', 'color': '#666'}),
html.P(f"Users: {users}",
style={'font-size': '0.8rem', 'color': '#666'})
]),
style={
'margin-bottom': '8px',
'border': 'none',
'border-left': f'4px solid {lang_color}',
'border-radius': '0',
'box-shadow': '0 1px 2px rgba(0,0,0,0.05)'
},
className="compact-card"
)
cards.append(card)
return cards