File size: 8,244 Bytes
a71ca97
 
 
 
 
 
 
 
 
 
 
 
 
 
49f9ca5
 
 
a71ca97
 
 
 
 
 
 
 
 
 
 
 
 
 
d039217
 
a71ca97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d039217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a71ca97
29c9d98
80cf484
a71ca97
 
 
 
 
 
 
d039217
a71ca97
 
 
 
 
 
 
d039217
a71ca97
d039217
a71ca97
 
 
d039217
a71ca97
d039217
a71ca97
 
 
 
d039217
 
 
 
 
 
 
29c9d98
a71ca97
 
d039217
a71ca97
29c9d98
 
a71ca97
d039217
 
 
 
 
a71ca97
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import gradio as gr
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
import math
import random

# Load and preprocess data
df = pd.read_csv('Hurun_Global_Rich_List_2025_COORD.csv')
df['Wealth $B'] = pd.to_numeric(df['Wealth $B'], errors='coerce')
df = df.dropna(subset=['lat', 'lon'])
df['Log Wealth'] = np.log10(df['Wealth $B'])

# Ensure "Age" is an integer
df['Age'] = pd.to_numeric(df['Age'], errors='coerce').astype('Int64')

# Extract country from location
df['Country'] = df['Location'].str.split(',').str[-1].str.strip()

# Add jitter to coordinates to prevent overlapping
def add_jitter(coords, jitter_range=0.2):
    """Add random jitter to coordinates to prevent overlapping markers"""
    coord_counts = {}
    jittered_coords = []
    
    for coord in coords:
        rounded = (round(coord[0], 2), round(coord[1], 2))
        count = coord_counts.get(rounded, 0)
        coord_counts[rounded] = count + 1
        if count > 0:
            angle = 2 * math.pi * (count / 8)
            distance = jitter_range * min(0.5 + count/10, 1.5)
            jitter_lat = math.sin(angle) * distance
            jitter_lon = math.cos(angle) * distance
            jittered_coords.append((coord[0] + jitter_lat, coord[1] + jitter_lon))
        else:
            jittered_coords.append(coord)
    
    return jittered_coords

# Create logarithmic scale for visualization
def create_globe():
    df['Size'] = df['Log Wealth'] * 8
    df['Color Value'] = df['Log Wealth']
    coords = list(zip(df['lat'], df['lon']))
    jittered_coords = add_jitter(coords)
    df['lat_jitter'] = [c[0] for c in jittered_coords]
    df['lon_jitter'] = [c[1] for c in jittered_coords]
    
    min_log = df['Log Wealth'].min()
    max_log = df['Log Wealth'].max()
    wealth_ticks = [1, 10, 30, 100, 300, 1000]
    log_ticks = [math.log10(x) for x in wealth_ticks if x <= 10**max_log and x >= 10**min_log]
    tick_labels = [f"${x}B" for x in wealth_ticks if x <= 10**max_log and x >= 10**min_log]
    
    fig = go.Figure(go.Scattergeo(
        lon = df['lon_jitter'],
        lat = df['lat_jitter'],
        text = df.apply(lambda row: (
            f"<b>{row['Name']}</b><br>"
            f"Rank: {row['Rank']}<br>"
            f"Wealth: ${row['Wealth $B']}B<br>"
            f"Enterprise: {row['Enterprise']}<br>"
            f"Sector: {row['Sector']}<br>"
            f"Age: {row['Age']}<br>"
            f"Location: {row['Location']}"
        ), axis=1),
        marker = dict(
            size = df['Size'],
            color = df['Color Value'],
            colorscale = 'Turbo',
            opacity = 0.7,
            line_color='rgb(40,40,40)',
            line_width=0.5,
            sizemode = 'diameter',
            colorbar = dict(
                title="Wealth (Billion USD)",
                thickness=15,
                len=0.35,
                bgcolor='rgba(255,255,255,0.5)',
                tickvals = log_ticks,
                ticktext = tick_labels
            )
        ),
        hoverinfo = 'text',
        name = ''
    ))
    
    fig.update_geos(
        projection_type="orthographic",
        showcoastlines=True,
        coastlinecolor="RebeccaPurple",
        showland=True,
        landcolor="rgb(243, 243, 243)",
        showocean=True,
        oceancolor="rgb(217, 244, 252)"
    )
    
    fig.update_layout(
        height=500,
        margin={"r":0,"t":0,"l":0,"b":0},
        paper_bgcolor='rgba(0,0,0,0)',
        geo=dict(bgcolor='rgba(0,0,0,0)')
    )
    return fig

# Create distribution plots
def create_age_plot():
    fig = px.histogram(df, x='Age', nbins=20, 
                      title='Age Distribution',
                      color_discrete_sequence=['#636EFA'])
    fig.update_layout(height=300)
    return fig

def create_country_plot():
    top_countries = df['Country'].value_counts().head(10)
    fig = px.bar(top_countries, 
                title='Top 10 Countries',
                labels={'value': 'Count', 'index': 'Country'},
                color=top_countries.index,
                color_discrete_sequence=px.colors.qualitative.Pastel)
    fig.update_layout(height=300, showlegend=False)
    return fig

def create_gender_plot():
    gender_counts = df['Sex'].value_counts()
    fig = px.pie(gender_counts, 
                names=gender_counts.index, 
                values=gender_counts.values,
                title='Gender Distribution',
                hole=0.4)
    fig.update_layout(height=300)
    return fig

def create_sector_plot():
    top_sectors = df['Sector'].value_counts().head(10)
    fig = px.bar(top_sectors, 
                title='Top 10 Sectors',
                labels={'value': 'Count', 'index': 'Sector'},
                color=top_sectors.index,
                color_discrete_sequence=px.colors.qualitative.Pastel)
    fig.update_layout(height=300, showlegend=False)
    return fig

# Create display dataframe without extra columns
display_columns = ['Rank', 'Rank change', 'Wealth $B', 'Enterprise', 'Sector', 
                   'Name', 'Sex', 'Age', 'Sidekick', 'Location']
display_df = df[display_columns].sort_values('Rank').reset_index(drop=True)

# Function to update table based on search and sort
def update_table(search_term, sort_column, sort_order):
    temp_df = df[display_columns].copy()
    
    # Filter based on search term
    if search_term:
        search_term = search_term.lower()
        temp_df = temp_df[temp_df.apply(lambda row: search_term in ' '.join(map(str, row)).lower(), axis=1)]
    
    # Sort based on selected column and order
    if sort_column:
        ascending = (sort_order == "Ascending")
        temp_df = temp_df.sort_values(by=sort_column, ascending=ascending)
    
    return temp_df.reset_index(drop=True)

# Create Gradio interface
with gr.Blocks(theme='SebastianBravo/simci_css', css="#my_dataframe th { font-size: 12px !important; } #my_dataframe td { font-size: 10px !important; }") as demo:
    gr.Markdown("# Global Billionaires Dashboard 2025")
    gr.Markdown("Interactive visualisation of the world's wealthiest individuals")
    
    with gr.Row():
        with gr.Column():
            gr.Markdown("### 🌍 Global Wealth Distribution")
            gr.Markdown("Circle size and color intensity both represent wealth on a logarithmic scale")
            gr.Markdown("Nearby points are slightly randomized to prevent overlapping")
            globe = gr.Plot(create_globe(), show_label=False)
    
    with gr.Row():
        with gr.Column():
            gr.Markdown("### πŸ“Š Distribution Analysis")
    
    with gr.Row():
        with gr.Column():
            age_plot = gr.Plot(create_age_plot(), show_label=False)
        with gr.Column():
            country_plot = gr.Plot(create_country_plot(), show_label=False)
    
    with gr.Row():
        with gr.Column():
            gender_plot = gr.Plot(create_gender_plot(), show_label=False)
        with gr.Column():
            sector_plot = gr.Plot(create_sector_plot(), show_label=False)
    
    with gr.Row():
        with gr.Column():
            gr.Markdown("### πŸ“‹ Full Billionaire Data")
            # Add search and sort controls
            with gr.Row():
                search_box = gr.Textbox(label="Search", placeholder="Type to search")
                with gr.Column():
                    sort_dropdown = gr.Dropdown(choices=display_columns, label="Sort by", value="Rank")
                    sort_order = gr.Dropdown(choices=["Ascending", "Descending"], label="Sort order", value="Ascending")
            
            # Dataframe with custom elem_id
            data_table = gr.Dataframe(
                value=display_df,
                interactive=False,
                wrap=True,
                col_count=(len(display_columns), "fixed"),
                elem_id="my_dataframe"
            )
    
    # Set up event listeners for search and sort
    search_box.change(update_table, inputs=[search_box, sort_dropdown, sort_order], outputs=data_table)
    sort_dropdown.change(update_table, inputs=[search_box, sort_dropdown, sort_order], outputs=data_table)
    sort_order.change(update_table, inputs=[search_box, sort_dropdown, sort_order], outputs=data_table)

demo.launch()