ouhenio commited on
Commit
dbf76b6
·
verified ·
1 Parent(s): e872da8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +223 -221
app.py CHANGED
@@ -32,239 +32,241 @@ def generate_data():
32
  "CU": {"name": "Cuba", "percent": random.randint(10, 90)}
33
  }
34
 
35
- # Route to serve the map visualization
36
- @app.get("/d3-map")
37
- async def serve_map():
38
- # Generate random data
39
- country_data = generate_data()
40
-
41
- # Convert to JSON for JavaScript
42
- country_data_json = json.dumps(country_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
- html_content = f"""
45
- <!DOCTYPE html>
46
- <html>
47
- <head>
48
- <meta charset="utf-8">
49
- <title>Latin America & Spain Map</title>
50
- <script src="https://d3js.org/d3.v7.min.js"></script>
51
- <style>
52
- body {{
53
- margin: 0;
54
- padding: 20px;
55
- background-color: #111;
56
- color: #fff;
57
- font-family: sans-serif;
58
- }}
59
- h1 {{
60
- margin-bottom: 20px;
61
- }}
62
- #map-container {{
63
- width: 100%;
64
- height: 600px;
65
- position: relative;
66
- }}
67
- #tooltip {{
68
- position: absolute;
69
- background-color: rgba(0, 0, 0, 0.8);
70
- border-radius: 5px;
71
- padding: 8px;
72
- color: white;
73
- font-size: 12px;
74
- pointer-events: none;
75
- opacity: 0;
76
- transition: opacity 0.3s;
77
- border: 1px solid rgba(255, 255, 255, 0.2);
78
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
79
- }}
80
- </style>
81
- </head>
82
- <body>
83
- <h1>Latin America & Spain Progress Map</h1>
84
- <div id="map-container"></div>
85
- <div id="tooltip"></div>
86
 
87
- <script>
88
- // Country data from Python
89
- const countryData = {country_data_json};
 
 
90
 
91
- document.addEventListener('DOMContentLoaded', function() {{
92
- // Set up dimensions
93
- const container = document.getElementById('map-container');
94
- const width = container.clientWidth;
95
- const height = container.clientHeight;
96
-
97
- // Create SVG
98
- const svg = d3.select('#map-container')
99
- .append('svg')
100
- .attr('width', width)
101
- .attr('height', height)
102
- .attr('viewBox', `0 0 ${{width}} ${{height}}`);
103
-
104
- // Create color scale
105
- const colorScale = d3.scaleLinear()
106
- .domain([0, 100])
107
- .range(['#4a1942', '#f32b7b']);
108
-
109
- // Set up projection focused on Latin America and Spain
110
- const projection = d3.geoMercator()
111
- .center([-60, 0])
112
- .scale(width / 5)
113
- .translate([width / 2, height / 2]);
114
-
115
- const path = d3.geoPath().projection(projection);
116
 
117
- // Tooltip setup
118
- const tooltip = d3.select('#tooltip');
 
 
 
119
 
120
- // Load GeoJSON data
121
- d3.json('https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson')
122
- .then(function(data) {{
123
- // Draw countries
124
- svg.selectAll('path')
125
- .data(data.features)
126
- .enter()
127
- .append('path')
128
- .attr('d', path)
129
- .attr('stroke', '#f32b7b')
130
- .attr('stroke-width', 1)
131
- .attr('fill', d => {{
132
- // Get the ISO code from the properties
133
- const iso = d.properties.iso_a2;
134
-
135
- if (countryData[iso]) {{
136
- return colorScale(countryData[iso].percent);
137
- }}
138
- return '#2d3748'; // Default gray for other countries
139
- }})
140
- .on('mouseover', function(event, d) {{
141
- const iso = d.properties.iso_a2;
142
-
143
- d3.select(this)
144
- .attr('stroke', '#4a1942')
145
- .attr('stroke-width', 2);
146
-
147
- if (countryData[iso]) {{
148
- tooltip.style('opacity', 1)
149
- .style('left', (event.pageX + 15) + 'px')
150
- .style('top', (event.pageY + 15) + 'px')
151
- .html(`
152
- <strong>${{countryData[iso].name}}</strong><br/>
153
- Progress: ${{countryData[iso].percent}}%
154
- `);
155
- }}
156
- }})
157
- .on('mousemove', function(event) {{
158
- tooltip.style('left', (event.pageX + 15) + 'px')
159
- .style('top', (event.pageY + 15) + 'px');
160
- }})
161
- .on('mouseout', function() {{
162
- d3.select(this)
163
- .attr('stroke', '#f32b7b')
164
- .attr('stroke-width', 1);
165
-
166
- tooltip.style('opacity', 0);
167
- }});
168
 
169
- // Add a legend
170
- const legendWidth = Math.min(width - 40, 200);
171
- const legendHeight = 15;
172
- const legendX = width - legendWidth - 20;
173
-
174
- const legend = svg.append('g')
175
- .attr('transform', `translate(${{legendX}}, 30)`);
176
-
177
- // Create gradient for legend
178
- const defs = svg.append('defs');
179
- const gradient = defs.append('linearGradient')
180
- .attr('id', 'dataGradient')
181
- .attr('x1', '0%')
182
- .attr('y1', '0%')
183
- .attr('x2', '100%')
184
- .attr('y2', '0%');
185
-
186
- gradient.append('stop')
187
- .attr('offset', '0%')
188
- .attr('stop-color', '#4a1942');
189
-
190
- gradient.append('stop')
191
- .attr('offset', '100%')
192
- .attr('stop-color', '#f32b7b');
193
-
194
- // Add legend title
195
- legend.append('text')
196
- .attr('x', legendWidth / 2)
197
- .attr('y', -10)
198
- .attr('text-anchor', 'middle')
199
- .attr('font-size', '12px')
200
- .attr('fill', '#f1f5f9')
201
- .text('Progress');
202
-
203
- // Add legend rectangle
204
- legend.append('rect')
205
- .attr('width', legendWidth)
206
- .attr('height', legendHeight)
207
- .attr('rx', 2)
208
- .attr('ry', 2)
209
- .style('fill', 'url(#dataGradient)');
210
-
211
- // Add legend labels
212
- legend.append('text')
213
- .attr('x', 0)
214
- .attr('y', legendHeight + 15)
215
- .attr('text-anchor', 'start')
216
- .attr('font-size', '10px')
217
- .attr('fill', '#94a3b8')
218
- .text('0%');
219
-
220
- legend.append('text')
221
- .attr('x', legendWidth / 2)
222
- .attr('y', legendHeight + 15)
223
- .attr('text-anchor', 'middle')
224
- .attr('font-size', '10px')
225
- .attr('fill', '#94a3b8')
226
- .text('50%');
227
 
228
- legend.append('text')
229
- .attr('x', legendWidth)
230
- .attr('y', legendHeight + 15)
231
- .attr('text-anchor', 'end')
232
- .attr('font-size', '10px')
233
- .attr('fill', '#94a3b8')
234
- .text('100%');
235
- })
236
- .catch(function(error) {{
237
- console.error('Error loading or rendering the map:', error);
238
- container.innerHTML = `<div style="color: white; text-align: center;">Error loading map: ${{error.message}}</div>`;
239
- }});
240
 
241
- // Handle window resize
242
- window.addEventListener('resize', function() {{
243
- const width = container.clientWidth;
244
 
245
- // Update SVG dimensions
246
- d3.select('svg')
247
- .attr('width', width)
248
- .attr('viewBox', `0 0 ${{width}} ${{height}}`);
 
 
 
 
249
 
250
- // Update projection
251
- projection.scale(width / 5)
252
- .translate([width / 2, height / 2]);
253
 
254
- // Update paths
255
- d3.selectAll('path').attr('d', path);
 
256
 
257
- // Update legend position
258
- const legendWidth = Math.min(width - 40, 200);
259
- const legendX = width - legendWidth - 20;
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
261
- d3.select('g').attr('transform', `translate(${{legendX}}, 30)`);
262
- }});
263
- }});
264
- </script>
265
- </body>
266
- </html>
267
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
  return fastapi.responses.HTMLResponse(content=html_content)
270
 
@@ -272,7 +274,7 @@ async def serve_map():
272
  def create_iframe():
273
  # Add a random parameter to force reload
274
  random_param = random.randint(1, 10000)
275
- return f'<iframe src="/d3-map?t={random_param}" style="width:100%; height:650px; border:none;"></iframe>'
276
 
277
  # Create the Gradio blocks
278
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo:
 
32
  "CU": {"name": "Cuba", "percent": random.randint(10, 90)}
33
  }
34
 
35
+ # HTML template - avoiding f-strings with JavaScript template literals
36
+ HTML_TEMPLATE = """
37
+ <!DOCTYPE html>
38
+ <html>
39
+ <head>
40
+ <meta charset="utf-8">
41
+ <title>Latin America & Spain Map</title>
42
+ <script src="https://d3js.org/d3.v7.min.js"></script>
43
+ <style>
44
+ body {
45
+ margin: 0;
46
+ padding: 20px;
47
+ background-color: #111;
48
+ color: #fff;
49
+ font-family: sans-serif;
50
+ }
51
+ h1 {
52
+ margin-bottom: 20px;
53
+ }
54
+ #map-container {
55
+ width: 100%;
56
+ height: 600px;
57
+ position: relative;
58
+ }
59
+ #tooltip {
60
+ position: absolute;
61
+ background-color: rgba(0, 0, 0, 0.8);
62
+ border-radius: 5px;
63
+ padding: 8px;
64
+ color: white;
65
+ font-size: 12px;
66
+ pointer-events: none;
67
+ opacity: 0;
68
+ transition: opacity 0.3s;
69
+ border: 1px solid rgba(255, 255, 255, 0.2);
70
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
71
+ }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <h1>Latin America & Spain Progress Map</h1>
76
+ <div id="map-container"></div>
77
+ <div id="tooltip"></div>
78
 
79
+ <script>
80
+ // Country data from Python - will be replaced
81
+ const countryData = COUNTRY_DATA_PLACEHOLDER;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
+ document.addEventListener('DOMContentLoaded', function() {
84
+ // Set up dimensions
85
+ const container = document.getElementById('map-container');
86
+ const width = container.clientWidth;
87
+ const height = container.clientHeight;
88
 
89
+ // Create SVG
90
+ const svg = d3.select('#map-container')
91
+ .append('svg')
92
+ .attr('width', width)
93
+ .attr('height', height)
94
+ .attr('viewBox', '0 0 ' + width + ' ' + height);
95
+
96
+ // Create color scale
97
+ const colorScale = d3.scaleLinear()
98
+ .domain([0, 100])
99
+ .range(['#4a1942', '#f32b7b']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
+ // Set up projection focused on Latin America and Spain
102
+ const projection = d3.geoMercator()
103
+ .center([-60, 0])
104
+ .scale(width / 5)
105
+ .translate([width / 2, height / 2]);
106
 
107
+ const path = d3.geoPath().projection(projection);
108
+
109
+ // Tooltip setup
110
+ const tooltip = d3.select('#tooltip');
111
+
112
+ // Load GeoJSON data
113
+ d3.json('https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson')
114
+ .then(function(data) {
115
+ // Draw countries
116
+ svg.selectAll('path')
117
+ .data(data.features)
118
+ .enter()
119
+ .append('path')
120
+ .attr('d', path)
121
+ .attr('stroke', '#f32b7b')
122
+ .attr('stroke-width', 1)
123
+ .attr('fill', function(d) {
124
+ // Get the ISO code from the properties
125
+ const iso = d.properties.iso_a2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ if (countryData[iso]) {
128
+ return colorScale(countryData[iso].percent);
129
+ }
130
+ return '#2d3748'; // Default gray for other countries
131
+ })
132
+ .on('mouseover', function(event, d) {
133
+ const iso = d.properties.iso_a2;
134
+
135
+ d3.select(this)
136
+ .attr('stroke', '#4a1942')
137
+ .attr('stroke-width', 2);
138
+
139
+ if (countryData[iso]) {
140
+ tooltip.style('opacity', 1)
141
+ .style('left', (event.pageX + 15) + 'px')
142
+ .style('top', (event.pageY + 15) + 'px')
143
+ .html('<strong>' + countryData[iso].name + '</strong><br/>' +
144
+ 'Progress: ' + countryData[iso].percent + '%');
145
+ }
146
+ })
147
+ .on('mousemove', function(event) {
148
+ tooltip.style('left', (event.pageX + 15) + 'px')
149
+ .style('top', (event.pageY + 15) + 'px');
150
+ })
151
+ .on('mouseout', function() {
152
+ d3.select(this)
153
+ .attr('stroke', '#f32b7b')
154
+ .attr('stroke-width', 1);
155
+
156
+ tooltip.style('opacity', 0);
157
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ // Add a legend
160
+ const legendWidth = Math.min(width - 40, 200);
161
+ const legendHeight = 15;
162
+ const legendX = width - legendWidth - 20;
 
 
 
 
 
 
 
 
163
 
164
+ const legend = svg.append('g')
165
+ .attr('transform', 'translate(' + legendX + ', 30)');
 
166
 
167
+ // Create gradient for legend
168
+ const defs = svg.append('defs');
169
+ const gradient = defs.append('linearGradient')
170
+ .attr('id', 'dataGradient')
171
+ .attr('x1', '0%')
172
+ .attr('y1', '0%')
173
+ .attr('x2', '100%')
174
+ .attr('y2', '0%');
175
 
176
+ gradient.append('stop')
177
+ .attr('offset', '0%')
178
+ .attr('stop-color', '#4a1942');
179
 
180
+ gradient.append('stop')
181
+ .attr('offset', '100%')
182
+ .attr('stop-color', '#f32b7b');
183
 
184
+ // Add legend title
185
+ legend.append('text')
186
+ .attr('x', legendWidth / 2)
187
+ .attr('y', -10)
188
+ .attr('text-anchor', 'middle')
189
+ .attr('font-size', '12px')
190
+ .attr('fill', '#f1f5f9')
191
+ .text('Progress');
192
+
193
+ // Add legend rectangle
194
+ legend.append('rect')
195
+ .attr('width', legendWidth)
196
+ .attr('height', legendHeight)
197
+ .attr('rx', 2)
198
+ .attr('ry', 2)
199
+ .style('fill', 'url(#dataGradient)');
200
 
201
+ // Add legend labels
202
+ legend.append('text')
203
+ .attr('x', 0)
204
+ .attr('y', legendHeight + 15)
205
+ .attr('text-anchor', 'start')
206
+ .attr('font-size', '10px')
207
+ .attr('fill', '#94a3b8')
208
+ .text('0%');
209
+
210
+ legend.append('text')
211
+ .attr('x', legendWidth / 2)
212
+ .attr('y', legendHeight + 15)
213
+ .attr('text-anchor', 'middle')
214
+ .attr('font-size', '10px')
215
+ .attr('fill', '#94a3b8')
216
+ .text('50%');
217
+
218
+ legend.append('text')
219
+ .attr('x', legendWidth)
220
+ .attr('y', legendHeight + 15)
221
+ .attr('text-anchor', 'end')
222
+ .attr('font-size', '10px')
223
+ .attr('fill', '#94a3b8')
224
+ .text('100%');
225
+ })
226
+ .catch(function(error) {
227
+ console.error('Error loading or rendering the map:', error);
228
+ container.innerHTML = '<div style="color: white; text-align: center;">Error loading map: ' + error.message + '</div>';
229
+ });
230
+
231
+ // Handle window resize
232
+ window.addEventListener('resize', function() {
233
+ const width = container.clientWidth;
234
+
235
+ // Update SVG dimensions
236
+ d3.select('svg')
237
+ .attr('width', width)
238
+ .attr('viewBox', '0 0 ' + width + ' ' + height);
239
+
240
+ // Update projection
241
+ projection.scale(width / 5)
242
+ .translate([width / 2, height / 2]);
243
+
244
+ // Update paths
245
+ d3.selectAll('path').attr('d', path);
246
+
247
+ // Update legend position
248
+ const legendWidth = Math.min(width - 40, 200);
249
+ const legendX = width - legendWidth - 20;
250
+
251
+ d3.select('g').attr('transform', 'translate(' + legendX + ', 30)');
252
+ });
253
+ });
254
+ </script>
255
+ </body>
256
+ </html>
257
+ """
258
+
259
+ # Route to serve the map visualization
260
+ @app.get("/d3-map")
261
+ async def serve_map():
262
+ # Generate random data
263
+ country_data = generate_data()
264
+
265
+ # Convert to JSON for JavaScript
266
+ country_data_json = json.dumps(country_data)
267
+
268
+ # Replace the placeholder with actual data
269
+ html_content = HTML_TEMPLATE.replace("COUNTRY_DATA_PLACEHOLDER", country_data_json)
270
 
271
  return fastapi.responses.HTMLResponse(content=html_content)
272
 
 
274
  def create_iframe():
275
  # Add a random parameter to force reload
276
  random_param = random.randint(1, 10000)
277
+ return '<iframe src="/d3-map?t={}" style="width:100%; height:650px; border:none;"></iframe>'.format(random_param)
278
 
279
  # Create the Gradio blocks
280
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo: