|
|
const canvas = document.getElementById('mapCanvas');
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
const img = document.querySelector('.map-container img');
|
|
|
canvas.width = img.clientWidth;
|
|
|
canvas.height = img.clientHeight;
|
|
|
|
|
|
const mapElement = document.getElementById('vietnam-map');
|
|
|
|
|
|
let areas = [];
|
|
|
let locationsData = [];
|
|
|
|
|
|
fetch('provinces.json')
|
|
|
.then(response => {
|
|
|
if (!response.ok) {
|
|
|
throw new Error('Network response was not ok');
|
|
|
}
|
|
|
return response.json();
|
|
|
})
|
|
|
.then(data => {
|
|
|
locationsData = data;
|
|
|
areas = locationsData.map(location => {
|
|
|
if (!location.coords) {
|
|
|
console.error('Missing coords for location:', location);
|
|
|
return { coords: [], shape: 'poly' };
|
|
|
}
|
|
|
const coords = location.coords.split(',').map(Number);
|
|
|
const shape = 'poly';
|
|
|
return { coords, shape };
|
|
|
});
|
|
|
createMapAreas();
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('Error loading the JSON file:', error);
|
|
|
});
|
|
|
|
|
|
function updateCanvasSize() {
|
|
|
|
|
|
canvas.width = img.offsetWidth;
|
|
|
canvas.height = img.offsetHeight;
|
|
|
canvas.style.width = `${img.offsetWidth}px`;
|
|
|
canvas.style.height = `${img.offsetHeight}px`;
|
|
|
}
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
updateCanvasSize();
|
|
|
createMapAreas();
|
|
|
});
|
|
|
|
|
|
|
|
|
img.addEventListener('load', () => {
|
|
|
updateCanvasSize();
|
|
|
createMapAreas();
|
|
|
});
|
|
|
|
|
|
|
|
|
function createMapAreas() {
|
|
|
mapElement.innerHTML = '';
|
|
|
const scaleX = img.offsetWidth / img.naturalWidth;
|
|
|
const scaleY = img.offsetHeight / img.naturalHeight;
|
|
|
|
|
|
areas.forEach((area) => {
|
|
|
const scaledCoords = area.coords.map((val, index) => {
|
|
|
return index % 2 === 0
|
|
|
? Math.round(val * scaleX)
|
|
|
: Math.round(val * scaleY);
|
|
|
});
|
|
|
|
|
|
const areaElement = document.createElement('area');
|
|
|
areaElement.setAttribute('shape', area.shape);
|
|
|
areaElement.setAttribute('coords', scaledCoords.join(','));
|
|
|
areaElement.setAttribute('href', 'javascript:void(0)');
|
|
|
mapElement.appendChild(areaElement);
|
|
|
});
|
|
|
|
|
|
setupEventListeners();
|
|
|
}
|
|
|
|
|
|
const drawOutline = (coords) => {
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
ctx.beginPath();
|
|
|
const scaleX = canvas.width / img.naturalWidth;
|
|
|
const scaleY = canvas.height / img.naturalHeight;
|
|
|
|
|
|
ctx.moveTo(coords[0] * scaleX, coords[1] * scaleY);
|
|
|
for (let i = 2; i < coords.length; i += 2) {
|
|
|
ctx.lineTo(coords[i] * scaleX, coords[i + 1] * scaleY);
|
|
|
}
|
|
|
|
|
|
ctx.closePath();
|
|
|
|
|
|
|
|
|
ctx.fillStyle = 'rgba(255, 165, 0, 0.3)';
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
|
ctx.strokeStyle = 'red';
|
|
|
ctx.lineWidth = 2;
|
|
|
ctx.stroke();
|
|
|
};
|
|
|
|
|
|
const clearOutline = () => {
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
};
|
|
|
|
|
|
|
|
|
const tooltip = document.querySelector('.tooltip-box');
|
|
|
|
|
|
const showTooltip = (data, event) => {
|
|
|
tooltip.style.display = 'block';
|
|
|
|
|
|
let nameText = '';
|
|
|
let areaText = '';
|
|
|
let areaTotal = 0;
|
|
|
let popText = '';
|
|
|
let popTotal = 0;
|
|
|
|
|
|
if (typeof data.name === 'object' && Object.keys(data.name).length > 1) {
|
|
|
|
|
|
nameText = Object.values(data.name).join(' + ');
|
|
|
|
|
|
|
|
|
for (const key in data.naturalArea) {
|
|
|
const value = parseFloat(data.naturalArea[key].replace('.', '').replace(',', '.'));
|
|
|
areaText += `<p>${data.name[key]}: ${data.naturalArea[key]}</p>`;
|
|
|
areaTotal += value;
|
|
|
}
|
|
|
|
|
|
|
|
|
for (const key in data.population) {
|
|
|
const value = parseFloat(data.population[key].replace('.', '').replace(',', '.'));
|
|
|
popText += `<p>${data.name[key]}: ${data.population[key]}</p>`;
|
|
|
popTotal += value;
|
|
|
}
|
|
|
|
|
|
tooltip.innerHTML = `
|
|
|
<h2>${nameText}</h2>
|
|
|
<hr>
|
|
|
<h3>DIỆN TÍCH TỰ NHIÊN (KM²)</h3>
|
|
|
${areaText}
|
|
|
<p><strong>Tổng diện tích:</strong> ${areaTotal.toLocaleString('vi-VN')}</p>
|
|
|
<hr>
|
|
|
<h3>QUY MÔ DÂN SỐ (NGHÌN NGƯỜI)</h3>
|
|
|
${popText}
|
|
|
<p><strong>Tổng dân số:</strong> ${popTotal.toLocaleString('vi-VN')}</p>
|
|
|
<hr>
|
|
|
<p>Tên tỉnh, thành sau sáp nhập: <span class="highlight">${data.mergedName}</span></p>
|
|
|
<p>Trung tâm chính trị - hành chính: <span class="highlight">${data.center}</span></p>
|
|
|
`;
|
|
|
} else {
|
|
|
|
|
|
tooltip.innerHTML = `
|
|
|
<h2>${data.name}</h2>
|
|
|
<hr>
|
|
|
<h3>DIỆN TÍCH TỰ NHIÊN (KM²)</h3>
|
|
|
<p>${data.name}: ${data.naturalArea}</p>
|
|
|
<p><strong>Tổng diện tích:</strong> ${data.naturalArea}</p>
|
|
|
<hr>
|
|
|
<h3>QUY MÔ DÂN SỐ (NGHÌN NGƯỜI)</h3>
|
|
|
<p>${data.name}: ${data.population}</p>
|
|
|
<p><strong>Tổng dân số:</strong> ${data.population}</p>
|
|
|
<hr>
|
|
|
<!-- <p>Tên tỉnh, thành sau sáp nhập: <span class="highlight">${data.mergedName}</span></p>
|
|
|
<p>Trung tâm chính trị - hành chính: <span class="highlight">${data.center}</span></p>
|
|
|
-->
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
tooltip.style.visibility = 'hidden';
|
|
|
tooltip.style.display = 'block';
|
|
|
|
|
|
const tooltipWidth = tooltip.offsetWidth;
|
|
|
const tooltipHeight = tooltip.offsetHeight;
|
|
|
|
|
|
|
|
|
const viewportWidth = window.innerWidth;
|
|
|
const viewportHeight = window.innerHeight;
|
|
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
|
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
|
|
|
|
|
|
|
|
let left = event.pageX + 10;
|
|
|
let top = event.pageY + 10;
|
|
|
|
|
|
|
|
|
if (left + tooltipWidth > viewportWidth + scrollLeft) {
|
|
|
left = event.pageX - tooltipWidth - 10;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (top + tooltipHeight > viewportHeight + scrollTop) {
|
|
|
top = event.pageY - tooltipHeight - 10;
|
|
|
|
|
|
|
|
|
if (top < scrollTop) {
|
|
|
|
|
|
top = scrollTop + (viewportHeight - tooltipHeight) / 2;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
left = Math.max(scrollLeft + 10, Math.min(left, viewportWidth + scrollLeft - tooltipWidth - 10));
|
|
|
top = Math.max(scrollTop + 10, Math.min(top, viewportHeight + scrollTop - tooltipHeight - 10));
|
|
|
|
|
|
|
|
|
tooltip.style.left = `${left}px`;
|
|
|
tooltip.style.top = `${top}px`;
|
|
|
tooltip.style.visibility = 'visible';
|
|
|
tooltip.classList.add('active');
|
|
|
};
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
if (tooltip.classList.contains('active')) {
|
|
|
tooltip.classList.remove('active');
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const hideTooltip = () => {
|
|
|
tooltip.classList.remove('active');
|
|
|
};
|
|
|
|
|
|
function setupEventListeners() {
|
|
|
document.querySelectorAll('area').forEach((area, index) => {
|
|
|
area.addEventListener('mouseenter', () => {
|
|
|
drawOutline(areas[index].coords);
|
|
|
});
|
|
|
area.addEventListener('mouseleave', clearOutline);
|
|
|
|
|
|
area.addEventListener('mouseenter', (event) => {
|
|
|
showTooltip(locationsData[index], event);
|
|
|
});
|
|
|
area.addEventListener('mousemove', (event) => {
|
|
|
|
|
|
const tooltipRect = tooltip.getBoundingClientRect();
|
|
|
const viewportHeight = window.innerHeight;
|
|
|
const viewportWidth = window.innerWidth;
|
|
|
|
|
|
|
|
|
if (tooltipRect.width + event.clientX < viewportWidth &&
|
|
|
tooltipRect.height + event.clientY < viewportHeight) {
|
|
|
tooltip.style.left = `${event.pageX + 10}px`;
|
|
|
tooltip.style.top = `${event.pageY + 10}px`;
|
|
|
}
|
|
|
});
|
|
|
area.addEventListener('mouseleave', hideTooltip);
|
|
|
});
|
|
|
|
|
|
document.addEventListener('mousemove', (event) => {
|
|
|
if (!event.target.closest('area') && !tooltip.contains(event.target)) {
|
|
|
tooltip.style.display = 'none';
|
|
|
tooltip.innerHTML = '';
|
|
|
}
|
|
|
});
|
|
|
} |