ouhenio commited on
Commit
4777956
verified
1 Parent(s): 0f592a0

Update template.txt

Browse files
Files changed (1) hide show
  1. template.txt +174 -170
template.txt CHANGED
@@ -3,12 +3,8 @@
3
  <head>
4
  <meta charset="utf-8">
5
  <title>Cartograf铆a de anotaci贸n</title>
6
- <!-- Use specific D3 modules instead of the full library -->
7
- <script src="https://d3js.org/d3-selection.v2.min.js"></script>
8
- <script src="https://d3js.org/d3-geo.v2.min.js"></script>
9
- <script src="https://d3js.org/d3-fetch.v2.min.js"></script>
10
- <script src="https://d3js.org/d3-scale.v2.min.js"></script>
11
- <script src="https://d3js.org/d3-array.v2.min.js"></script>
12
  <style>
13
  body {
14
  margin: 0;
@@ -178,145 +174,98 @@
178
  // Country data from Python - will be replaced
179
  const countryData = COUNTRY_DATA_PLACEHOLDER;
180
 
181
- // Pre-calculate statistics to avoid repeated calculations
182
- const totalDocs = Object.values(countryData).reduce((sum, country) => {
183
- return sum + (country.documents || 0);
184
- }, 0);
185
-
186
- const avgPercent = Object.values(countryData).reduce((sum, country) => {
187
- return sum + country.percent;
188
- }, 0) / Object.values(countryData).length;
189
-
190
- // Pre-sort countries by document count
191
- const countriesWithDocs = Object.keys(countryData).map(code => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  return {
193
- code: code,
194
- name: countryData[code].name,
195
- percent: countryData[code].percent,
196
- documents: countryData[code].documents || 0
197
  };
198
- }).sort((a, b) => b.documents - a.documents);
199
-
200
- // Take the top 5
201
- const topCountries = countriesWithDocs.slice(0, 5);
202
 
203
- // Use a local variable for relevant country codes to avoid object lookups
204
- const relevantCountryCodes = Object.keys(countryData);
205
-
206
- // Create a mapping function for faster lookups during rendering
207
- const countryDataMap = new Map();
208
- relevantCountryCodes.forEach(code => {
209
- countryDataMap.set(code, countryData[code]);
210
- });
211
 
212
- // Define the render function outside the load event for better organization
213
- function renderMap() {
214
- const container = document.getElementById('map-container');
215
- const width = container.clientWidth;
216
- const height = container.clientHeight;
217
-
218
- // Use a lightweight SVG creation approach
219
- const svg = d3.select('#map-container')
220
- .append('svg')
221
- .attr('width', width)
222
- .attr('height', height);
223
 
224
- // Create color scale - define outside to prevent recreation
225
- const colorScale = d3.scaleLinear()
226
- .domain([0, 100])
227
- .range(['#4a1942', '#f32b7b']);
228
 
229
- // Optimize projection settings for South America focus
230
- const projection = d3.geoMercator()
231
- .center([-60, -15])
232
- .scale(width / 4)
233
- .translate([width / 2, height / 2]);
234
 
235
- const path = d3.geoPath().projection(projection);
 
 
 
 
 
 
 
 
 
236
 
237
- // Cache tooltip element to avoid repeated DOM lookups
 
 
 
 
 
238
  const tooltip = d3.select('#tooltip');
239
 
240
- // Use a faster GeoJSON source if available or host your own copy
241
- const geoJsonUrl = 'https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson';
242
-
243
- // Add ocean background
244
- svg.append('rect')
245
- .attr('width', width)
246
- .attr('height', height)
247
- .attr('fill', '#0f1218');
248
-
249
- // Function to show tooltip - defined outside loop for better performance
250
- function showTooltip(event, iso) {
251
  d3.select(event.currentTarget)
252
  .attr('stroke', '#fff')
253
  .attr('stroke-width', 1.5);
254
 
255
- const countryInfo = countryDataMap.get(iso);
 
256
 
 
257
  tooltip.style('opacity', 1)
258
  .style('left', (event.pageX + 15) + 'px')
259
  .style('top', (event.pageY + 15) + 'px')
260
- .html(`<strong>${countryInfo.name}</strong><br/>` +
261
- `Preguntas totales: ${countryInfo.total_questions}<br/>` +
262
- `Preguntas respondidas: ${countryInfo.answered_questions}<br/>` +
263
- `Progreso: ${countryInfo.percent}%`);
264
- }
265
-
266
- // Add legend first so it appears above the map
267
- addLegend(svg, width);
268
-
269
- // Fetch the GeoJSON data
270
- d3.json(geoJsonUrl)
271
- .then(function(data) {
272
- // Hide loading indicator
273
- document.getElementById('loading-indicator').style.display = 'none';
274
-
275
- // Filter features once before rendering
276
- const relevantFeatures = data.features.filter(d =>
277
- relevantCountryCodes.includes(d.id)
278
- );
279
-
280
- // Create a document fragment for better performance
281
- const countries = svg.selectAll('.country')
282
- .data(relevantFeatures)
283
- .enter()
284
- .append('path')
285
- .attr('class', 'country')
286
- .attr('d', path)
287
- .attr('fill', function(d) {
288
- const iso = d.id;
289
- return colorScale(countryDataMap.get(iso).percent);
290
- })
291
- .attr('stroke', '#0f1218')
292
- .attr('stroke-width', 1);
293
-
294
- // Add event listeners
295
- countries
296
- .on('mouseover', function(event, d) {
297
- showTooltip(event, d.id);
298
- })
299
- .on('mousemove', function(event) {
300
- tooltip
301
- .style('left', (event.pageX + 15) + 'px')
302
- .style('top', (event.pageY + 15) + 'px');
303
- })
304
- .on('mouseout', function() {
305
- d3.select(this)
306
- .attr('stroke', '#0f1218')
307
- .attr('stroke-width', 1);
308
-
309
- tooltip.style('opacity', 0);
310
- });
311
- })
312
- .catch(function(error) {
313
- console.error('Error loading or rendering the map:', error);
314
- container.innerHTML = '<div style="color: white; text-align: center; padding: 20px;">Error loading map: ' + error.message + '</div>';
315
- });
316
-
317
- return { svg, projection, path }; // Return for resize handler
318
  }
319
 
 
320
  function addLegend(svg, width) {
321
  const legendWidth = 200;
322
  const legendHeight = 15;
@@ -387,85 +336,140 @@
387
  .text('100%');
388
  }
389
 
390
- function updateStatistics() {
391
- // Update the stats with pre-calculated values
392
- document.getElementById('total-docs').textContent = totalDocs.toLocaleString();
393
- document.getElementById('avg-percent').textContent = avgPercent.toFixed(1) + '%';
394
 
395
- // Update the top countries list
396
- const topCountriesList = document.getElementById('top-countries-list');
397
- topCountriesList.innerHTML = '';
 
398
 
399
- // Create DOM elements more efficiently
400
- const fragment = document.createDocumentFragment();
 
 
 
401
 
402
- topCountries.forEach(country => {
403
- const countryDiv = document.createElement('div');
404
- countryDiv.className = 'country-stat';
405
-
406
- countryDiv.innerHTML = `
407
- <span>${country.name}</span>
408
- <div class="country-bar">
409
- <div class="country-bar-fill" style="width: ${country.percent}%;"></div>
410
- </div>
411
- <span class="country-value">${country.documents.toLocaleString()}</span>
412
- `;
413
-
414
- fragment.appendChild(countryDiv);
415
- });
416
 
417
- topCountriesList.appendChild(fragment);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  }
419
-
420
- // Track resize state to avoid excessive redraws
421
- let resizeTimer;
422
- let mapElements;
423
 
424
- // Handle window resize with debouncing
425
- function setupResizeHandler() {
 
 
426
  window.addEventListener('resize', function() {
427
- // Clear the previous timeout
428
  clearTimeout(resizeTimer);
429
 
430
- // Set a timeout to run after resizing ends
431
  resizeTimer = setTimeout(function() {
432
  const container = document.getElementById('map-container');
433
  const width = container.clientWidth;
434
  const height = container.clientHeight;
435
 
436
- if (!mapElements) return;
437
-
438
  // Update SVG dimensions
439
- d3.select('svg')
440
  .attr('width', width)
441
  .attr('height', height);
442
 
443
  // Update projection
444
- mapElements.projection
445
  .scale(width / 4)
446
  .translate([width / 2, height / 2]);
447
 
448
  // Update paths
449
- d3.selectAll('.country').attr('d', mapElements.path);
450
 
451
  // Update legend position
452
  const legendX = width - 220;
453
  d3.select('.legend')
454
  .attr('transform', 'translate(' + legendX + ',20)');
455
- }, 250); // 250ms debounce
456
  });
457
  }
458
 
459
- // Execute non-DOM manipulation code immediately
460
  document.addEventListener('DOMContentLoaded', function() {
461
- // Render map - this is the most time-consuming operation
462
- mapElements = renderMap();
463
-
464
- // Update statistics
465
  updateStatistics();
466
 
467
- // Set up resize handler
468
- setupResizeHandler();
 
 
 
469
  });
470
  </script>
471
  </body>
 
3
  <head>
4
  <meta charset="utf-8">
5
  <title>Cartograf铆a de anotaci贸n</title>
6
+ <!-- Use the full D3 library to avoid dependency issues -->
7
+ <script src="https://d3js.org/d3.v7.min.js"></script>
 
 
 
 
8
  <style>
9
  body {
10
  margin: 0;
 
174
  // Country data from Python - will be replaced
175
  const countryData = COUNTRY_DATA_PLACEHOLDER;
176
 
177
+ // Pre-calculate statistics once at page load
178
+ const calculateStats = () => {
179
+ // Calculate total documents
180
+ const totalDocs = Object.values(countryData).reduce((sum, country) => {
181
+ return sum + (country.documents || 0);
182
+ }, 0);
183
+
184
+ // Calculate average percentage
185
+ const avgPercent = Object.values(countryData).reduce((sum, country) => {
186
+ return sum + country.percent;
187
+ }, 0) / Object.values(countryData).length;
188
+
189
+ // Create an array of countries with document counts
190
+ const countriesWithDocs = Object.keys(countryData).map(code => {
191
+ return {
192
+ code: code,
193
+ name: countryData[code].name,
194
+ percent: countryData[code].percent,
195
+ documents: countryData[code].documents || 0
196
+ };
197
+ });
198
+
199
+ // Sort by document count descending
200
+ countriesWithDocs.sort((a, b) => b.documents - a.documents);
201
+
202
+ // Take the top 5
203
+ const topCountries = countriesWithDocs.slice(0, 5);
204
+
205
  return {
206
+ totalDocs,
207
+ avgPercent,
208
+ topCountries
 
209
  };
210
+ };
 
 
 
211
 
212
+ // Store the calculated stats
213
+ const stats = calculateStats();
 
 
 
 
 
 
214
 
215
+ // Function to update the DOM with the statistics
216
+ function updateStatistics() {
217
+ // Update the stats
218
+ document.getElementById('total-docs').textContent = stats.totalDocs.toLocaleString();
219
+ document.getElementById('avg-percent').textContent = stats.avgPercent.toFixed(1) + '%';
 
 
 
 
 
 
220
 
221
+ // Update the top countries list - use document fragment for performance
222
+ const topCountriesList = document.getElementById('top-countries-list');
223
+ const fragment = document.createDocumentFragment();
 
224
 
225
+ stats.topCountries.forEach(country => {
226
+ const countryDiv = document.createElement('div');
227
+ countryDiv.className = 'country-stat';
 
 
228
 
229
+ countryDiv.innerHTML = `
230
+ <span>${country.name}</span>
231
+ <div class="country-bar">
232
+ <div class="country-bar-fill" style="width: ${country.percent}%;"></div>
233
+ </div>
234
+ <span class="country-value">${country.documents.toLocaleString()}</span>
235
+ `;
236
+
237
+ fragment.appendChild(countryDiv);
238
+ });
239
 
240
+ topCountriesList.innerHTML = '';
241
+ topCountriesList.appendChild(fragment);
242
+ }
243
+
244
+ // Optimized function to show tooltip
245
+ function createTooltipHandler() {
246
  const tooltip = d3.select('#tooltip');
247
 
248
+ return function(event, iso) {
249
+ // Update the current country style
 
 
 
 
 
 
 
 
 
250
  d3.select(event.currentTarget)
251
  .attr('stroke', '#fff')
252
  .attr('stroke-width', 1.5);
253
 
254
+ // Get country data
255
+ const country = countryData[iso];
256
 
257
+ // Update tooltip content and position
258
  tooltip.style('opacity', 1)
259
  .style('left', (event.pageX + 15) + 'px')
260
  .style('top', (event.pageY + 15) + 'px')
261
+ .html(`<strong>${country.name}</strong><br/>` +
262
+ `Preguntas totales: ${country.total_questions}<br/>` +
263
+ `Preguntas respondidas: ${country.answered_questions}<br/>` +
264
+ `Progreso: ${country.percent}%`);
265
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  }
267
 
268
+ // Add the legend to the map
269
  function addLegend(svg, width) {
270
  const legendWidth = 200;
271
  const legendHeight = 15;
 
336
  .text('100%');
337
  }
338
 
339
+ // Main function to render the map
340
+ function initMap() {
341
+ // Cache the relevant country codes for filtering
342
+ const relevantCountryCodes = Object.keys(countryData);
343
 
344
+ // Update dimensions with container size
345
+ const container = document.getElementById('map-container');
346
+ const width = container.clientWidth;
347
+ const height = container.clientHeight;
348
 
349
+ // Set up the SVG
350
+ const svg = d3.select('#map-container')
351
+ .append('svg')
352
+ .attr('width', width)
353
+ .attr('height', height);
354
 
355
+ // Add ocean background first (layering matters)
356
+ svg.append('rect')
357
+ .attr('width', width)
358
+ .attr('height', height)
359
+ .attr('fill', '#0f1218');
 
 
 
 
 
 
 
 
 
360
 
361
+ // Define the color scale
362
+ const colorScale = d3.scaleLinear()
363
+ .domain([0, 100])
364
+ .range(['#4a1942', '#f32b7b']);
365
+
366
+ // Set up the projection
367
+ const projection = d3.geoMercator()
368
+ .center([-60, -15]) // Centered on South America
369
+ .scale(width / 4)
370
+ .translate([width / 2, height / 2]);
371
+
372
+ const path = d3.geoPath().projection(projection);
373
+
374
+ // Add the legend before loading data
375
+ addLegend(svg, width);
376
+
377
+ // Create the tooltip handler
378
+ const showTooltip = createTooltipHandler();
379
+ const tooltip = d3.select('#tooltip');
380
+
381
+ // Store reference for resize handler
382
+ const mapState = { svg, projection, path };
383
+
384
+ // Load GeoJSON data with a smaller, faster source if possible
385
+ d3.json('https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson')
386
+ .then(function(data) {
387
+ // Hide loading indicator
388
+ document.getElementById('loading-indicator').style.display = 'none';
389
+
390
+ // Filter only relevant countries for faster rendering
391
+ const relevantFeatures = data.features.filter(d =>
392
+ relevantCountryCodes.includes(d.id)
393
+ );
394
+
395
+ // Add country paths
396
+ svg.selectAll('.country')
397
+ .data(relevantFeatures)
398
+ .enter()
399
+ .append('path')
400
+ .attr('class', 'country')
401
+ .attr('d', path)
402
+ .attr('fill', d => colorScale(countryData[d.id].percent))
403
+ .attr('stroke', '#0f1218')
404
+ .attr('stroke-width', 1)
405
+ .on('mouseover', function(event, d) {
406
+ showTooltip(event, d.id);
407
+ })
408
+ .on('mousemove', function(event) {
409
+ tooltip
410
+ .style('left', (event.pageX + 15) + 'px')
411
+ .style('top', (event.pageY + 15) + 'px');
412
+ })
413
+ .on('mouseout', function() {
414
+ d3.select(this)
415
+ .attr('stroke', '#0f1218')
416
+ .attr('stroke-width', 1);
417
+
418
+ tooltip.style('opacity', 0);
419
+ });
420
+ })
421
+ .catch(function(error) {
422
+ console.error('Error loading map data:', error);
423
+ document.getElementById('loading-indicator').style.display = 'none';
424
+ container.innerHTML = '<div style="color: white; text-align: center; padding: 20px;">Error loading map: ' + error.message + '</div>';
425
+ });
426
+
427
+ return mapState;
428
  }
 
 
 
 
429
 
430
+ // Debounced resize handler
431
+ function setupResizeHandler(mapState) {
432
+ let resizeTimer;
433
+
434
  window.addEventListener('resize', function() {
 
435
  clearTimeout(resizeTimer);
436
 
 
437
  resizeTimer = setTimeout(function() {
438
  const container = document.getElementById('map-container');
439
  const width = container.clientWidth;
440
  const height = container.clientHeight;
441
 
 
 
442
  // Update SVG dimensions
443
+ mapState.svg
444
  .attr('width', width)
445
  .attr('height', height);
446
 
447
  // Update projection
448
+ mapState.projection
449
  .scale(width / 4)
450
  .translate([width / 2, height / 2]);
451
 
452
  // Update paths
453
+ d3.selectAll('.country').attr('d', mapState.path);
454
 
455
  // Update legend position
456
  const legendX = width - 220;
457
  d3.select('.legend')
458
  .attr('transform', 'translate(' + legendX + ',20)');
459
+ }, 250); // Debounce 250ms
460
  });
461
  }
462
 
463
+ // Initialize on page load
464
  document.addEventListener('DOMContentLoaded', function() {
465
+ // Update the statistics first
 
 
 
466
  updateStatistics();
467
 
468
+ // Initialize the map
469
+ const mapState = initMap();
470
+
471
+ // Setup resize handler
472
+ setupResizeHandler(mapState);
473
  });
474
  </script>
475
  </body>