Figure Friday 2025 - week 10

join the Figure Friday session on March 14, at noon Eastern Time, to showcase your creation and receive feedback from the community.

What were the most popular programming languages since 2004? When did Matlab peak?

This week’s Figure-Friday builds on top of the Popular Programming Languages dataset. Thank you to community member, @Ester, for suggesting this dataset and to Muhammad Khalid for putting it together.

Program language values in the dataset are in percentage format. All rows should add up to 100%.

Things to consider:

  • what can you improve in the app or sample figure below (Line Chart)?
  • would you like to tell a different data story using a different graph?
  • can you create a different Dash app?

Sample figure:

Code for sample figure:
from dash import Dash, dcc
import dash_ag_grid as dag
import plotly.express as px
import pandas as pd

df = pd.read_csv("https://raw.githubusercontent.com/plotly/Figure-Friday/refs/heads/main/2025/week-10/Popularity%20of%20Programming%20Languages%20from%202004%20to%202024.csv")

fig = px.line(df, x="Date", y="C/C++")

grid = dag.AgGrid(
    rowData=df.to_dict("records"),
    columnDefs=[{"field": i, 'filter': True, 'sortable': True} for i in df.columns],
    dashGridOptions={"pagination": True}
)

app = Dash()
app.layout = [
    grid,
    dcc.Graph(figure=fig)
]


if __name__ == "__main__":
    app.run(debug=True)

Participation Instructions:

  • Create - use the weekly data set to build your own Plotly visualization or Dash app. Or, enhance the sample figure provided in this post, using Plotly or Dash.
  • Submit - post your creation to LinkedIn or Twitter with the hashtags #FigureFriday and #plotly by midnight Thursday, your time zone. Please also submit your visualization as a new post in this thread.
  • Celebrate - join the Figure Friday sessions to showcase your creation and receive feedback from the community.

:point_right: If you prefer to collaborate with others on Discord, join the Plotly Discord channel.

Data Source:

Thank you to Muhammad Khalid and Kaggle for the data.

2 Likes

I created this for week 10. My first serious Mantine experiment. I filtered the data down to only december of a certain year.

5 Likes

Looks Beautiful,

3 Likes

Thank you @Avacsiglo21 , difficult to find some colours which work good in dark and light mode. Dark mode is easier.

1 Like

I have a question. I noticed the y-axis scale is fixed. When selecting programming languages with small percentages, like Kotlin or Ruby, it’s difficult to see any changes. Is there a way to dynamically adjust the y-axis scale? I’m just curious. :stuck_out_tongue_winking_eye:

1 Like

I had the y-axis fixed as in selected programming languages as part of 100%., always 0-100.
I’'ve removed that line and now the y-axis extends based on the sum % of the programming languages selected. I don’t like it, what do you think? Best would be if you had a zoom possiblity, maybe that’s available in DMC, I did not search for it. Kotlin is seriously growing, the adjusted setting gives valid info for the smaller & upcoming languages. Going to watch a movie now :slight_smile: :waving_hand:

update: slightly changed the interface, a zoom button next to the dark mode/light mode switch. What it deos is y-axis fixed [0, 100} versus y axis max = stacked max.

You can now see this:

4 Likes

Lot of people learned about COBOL for the first time last month haha, if you know you know :wink:

7 Likes

I really liked this sunburnst chart for now, but I might change it later.

6 Likes

I know from the sideline. :slight_smile:

2 Likes

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:

  1. Main Structure: Uses Dash with Bootstrap components to create a modern, responsive interface.

  2. 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)
  3. User Interface: Consists of two main sections:

    • A left sidebar displaying information cards about selected languages
    • A main area containing charts and interactive controls
  4. 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
  5. Custom Visualizations: Uses Plotly to create interactive charts with a custom color map for each programming language.

  6. 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 :

  1. Based on Flask: I have added Flask caching to improve data loading performance
  2. 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

7 Likes

nice app, @marieanne . I much prefer dark mode as well.
And I like how you used the dmc.Areachart. It’s cool how one area is highlighted when you hover over the legend item.

I also prefer the zoomed version of the y-axis. If not, it’s hard to see the difference between the languages. It’s good that you gave the user the option to switch between the zoom and non-zoom. I would just place that switch closer to the graph or move the light/dark button to the upper right (because the zoom and the dropdown are tied to the graph while the light button is tied to the whole app).

1 Like

I’ll try another chart though :slight_smile:

4 Likes

Hey everyone!

I’ve just wrapped up an interactive dashboard that dives into the popularity of programming languages. It’s got a multi-line charts, stacked areas, scatter plots, bar charts, and even a correlation heatmap. Plus, there’s a neat forecasting feature using Prophet.

Check out the code on GitHub:

And Kaggle:

Would love to hear your thoughts and feedback!

4 Likes

Hello Eszter,

Just for curiosity the ‘x’ axis is count ?

1 Like

Popularity %. I changed the picture. I’ll upload the code soon, just to check it out.

2 Likes

Thank you for your feedback and you were absolutely right:
final version looks like this:

4 Likes

This dashboard produces a scatter plot, line plot and Dash AG table for 2 selected programming languages. This screen shot shows Python vs. Perl. As Python popularity has increased, Perl has decreased. Interesting circular patterns in regions where the curves are non-monotonic are similar to Lissajous figures.

A few limitations of this dashboard I hope to solve:

  1. The dash AG table has columns for all of the programming languages. I would like it to only show columns for the selected languages. A partial improvement would be to reorder columns in the table so that the user selected columns are moved to the left, next to the date column. For my screenshot, I manually moved the columns.

  2. The dropdown allows user to pick as many selections as they want. I want to limit it to 2 picks. I work around this by ignoring selections after the first 2 but would like to do this better.

I appreciate your comments or suggestions and will update this submission if any improvements are made.
Here is the screenshot:

Here is the code:

import polars as pl
import polars.selectors as cs
import plotly.express as px
from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc
import dash_ag_grid as dag
'''
    This dashboard shows popularity of programming languages from 2004 to 2024.
    All data are percentages, where the totals across each row should add up to 
    100%. This will be checked, exceptions will be noted. 
'''

#----- DEFINE CONSTANTS---------------------------------------------------------
citation = (
    'Thank you to Muhammad Khalid and Kaggle for the data, ' +
     'https://www.kaggle.com/datasets/muhammadkhalid/'   
)

style_space = {
    'border': 'none', 
    'height': '5px', 
    'background': 'linear-gradient(to right, #007bff, #ff7b00)', 
    'margin': '10px 0'
    }

#----- DEFINE FUNCTIONS---------------------------------------------------------
def create_line_plot(lang_1, lang_2):
    fig = px.line(
        df,
        'DATE',
        [lang_1, lang_2], 
        title=(f'{lang_1} vs. {lang_2}<br><sup>Popularity by Year</sup>'),
        template='simple_white',
    )
    fig.update_layout(
        yaxis_title='POPULARITY',
        xaxis_title='',   # that X is year is obvious, no label
        hovermode='x unified',
        legend_title_text='Prog. Lang.'
    )  
    return fig

def create_scatter_plot(lang_1, lang_2):
    fig = px.scatter(
        df,
        lang_1,
        lang_2,
        template='simple_white',
        title=(f'{lang_1} vs. {lang_2}<br><sup>Scatter Plot/Correlation</sup>'),
    )
    return fig

def get_table_data(lang_1, lang_2):
    df_table = (
        df
        .select(pl.col('DATE', lang_1, lang_2))
        .sort('DATE', descending=False)
    )
    return df_table

#----- LOAD AND CLEAN DATA -----------------------------------------------------
df = (
    pl.read_csv(
        'Popularity of Programming Languages from 2004 to 2024.csv',
        ignore_errors=True
        )
    .with_columns(DATE = pl.col('Date').str.to_date(format='%b-%y'))
    .select('DATE', pl.all().exclude('Date', 'DATE')) 
    
    # move DATE to 1st col, get rid of Date col (string)
    .with_columns(pl.all().fill_null(strategy="zero"))
    
    # use sum_horizontal to see how close row totals are to 100%
    .with_columns(
        PCT_TOT = (pl.sum_horizontal(cs.numeric()))
    )
)
print(f'{df['PCT_TOT'].min() = }')
print(f'{df['PCT_TOT'].max() = }')
print('hi')
print(df)

drop_down_list = sorted(list(df.columns)[1:-1])
print(f'{drop_down_list = }')

grid = dag.AgGrid(
    rowData=[],
    columnDefs=[{"field": i, 'filter': True, 'sortable': True} for i in df.columns],
    dashGridOptions={"pagination": True},
    id='data_table'
)

#----- DASH APP ----------------------------------------------------------------
app = Dash(external_stylesheets=[dbc.themes.SANDSTONE])

app.layout = dbc.Container([
    html.Hr(style=style_space),
    html.H2(
        'Programming Language Popularity, 2004 to 2024', 
        style={
            'text-align': 'left'
        }
    ),
    html.Hr(style=style_space),
    html.Div([
        html.P(
            f'Citation: {citation}',
            style={
                'text-align': 'left',
                'margin-top': '20px',
                'font-style':
                'italic','font-size':
                '16px',
                'color': 'gray'
                }
            ),
        html.Hr(style=style_space)
    ]),

    dbc.Row(
        [dcc.Dropdown(       
            drop_down_list,       # list of choices
            ['Python', 'Perl'],   # defaults
            id='lang_dropdown', 
            style={'width': '50%'},
            multi=True,  # first 2 are used, all other ignored
            ),
        ],
    ),

    html.Div(id='dd-output-container'),

    dbc.Row([
        dbc.Col([grid]),
    ]),

    dbc.Row([
        dbc.Col(dcc.Graph(id='scatter_plot')),    
        dbc.Col(dcc.Graph(id='line_plot'),),
    ])
])

@app.callback(
        Output('line_plot', 'figure'),
        Output('scatter_plot', 'figure'),
        Output('data_table', 'rowData'),
        Input('lang_dropdown', 'value'),
)

def update_dashboard(selected_values):
    df_selected = df.select(pl.col('DATE', selected_values[0], selected_values[1]))
    line_plot = create_line_plot(selected_values[0], selected_values[1])
    scatter_plot  = create_scatter_plot(selected_values[0], selected_values[1])
    return line_plot, scatter_plot, df_selected.to_dicts()

#----- RUN THE APP -------------------------------------------------------------
if __name__ == "__main__":
    app.run(debug=True)
5 Likes

In the end, I tried both charts. Python was the winner.


3 Likes

Hello Eszter nice work, just a question while the side bar shows the correct Popularity%, the ‘x’ scale is showing a sum of Popularity %, Did you notice?

:muscle: :muscle:

1 Like

Oh, sorry @Avacsiglo. Thank you very much the feedback.

2 Likes