Hello Everyone, Excellent week for all,
Here my web app of the Figure Friday 10 week ‘Programming Languagues growth since 2004’,
Here’s a summary of the web app:
This is an interactive web application built with Dash (based on Flask) that visualizes programming language popularity trends from 2004 to 2024.
The key features include:
-
Main Structure: Uses Dash with Bootstrap components to create a modern, responsive interface.
-
Data Management: Implements a caching system for performance optimization when loading two datasets:
- Historical data on programming language popularity
- Detailed information about each language (category and typical users)
-
User Interface: Consists of two main sections:
- A left sidebar displaying information cards about selected languages
- A main area containing charts and interactive controls
-
Interactive Features:
- A modal dialog for selecting/deselecting programming languages
- Option to switch between two visualization types:
- Line chart for long-term trends
- Bar chart for year-over-year percentage changes
-
Custom Visualizations: Uses Plotly to create interactive charts with a custom color map for each programming language.
-
Reactive Design: Implements multiple Dash callbacks to handle interactivity, including:
- Opening/closing the selection modal
- Dynamically updating charts based on selected languages
- Updating information cards in the sidebar
The application allows users to visually explore how different programming languages have evolved in popularity over two decades,
with the flexibility to select specific languages to compare their trends and relative growth rates.
A couple of questions to clarify :
- Based on Flask: I have added Flask caching to improve data loading performance
- To provide context for each language, a dataframe is included with details on language categories and typical user profiles. This aims to give a broad understanding of who commonly uses each language and for what purposes.
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": "Line Chart", "value": "line"},
{"label": "YoY 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 Percentage Change',
labels={'value': '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
# Run the app
if __name__ == '__main__':
app.run_server(debug=True,jupyter_mode='external',)
As usual any comments/suggestions are more than welcome
Best Regards