完成画面
初期画面では、右下に全国の都道府県の人口指数を示すバーチャートが表示されます。ここでは、2020年の人口総数を基準に、2025年から2050年までの人口増減が表現されています。
また、左側のマップ上の都道府県ポリゴンをクリックすると、該当する都道府県の2025年~2050年の人口総数を示す折れ線グラフが表示されます。
将来推計人口データについて
今回使用したデータは、国立社会保障・人口問題研究所が発表している「将来推計人口」をもとに作成した「未来人口データ」です。2020年を基準年とし、2025年から2050年までを5年ごとに、5歳階級別の推計人口データとなっています。
今回使用した全国都道府県ポリゴンおよび未来人口推計データは、TerraMap APIからレスポンスされたものです。
コードについて
地図表示
地図はOSMを使用し、地図ライブラリはLeafletを用いて、TerraMap APIから取得したGeoJSONファイルを読み込み、マップ上にポリゴンを表示しています。TerraMap APIにリクエストする部分は、area.jsonにリクエストすることで疑似的に表現しています。
let map;
let geojsonData;
async function initMap() {
map = L.map('map').setView([35.68437, 139.75247], 7);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 13,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
try {
const response = await fetch("area.json");
geojsonData = await response.json();
L.geoJSON(geojsonData, {
style: {
fillColor: "#2196F3",
fillOpacity: 0.4,
color: "#1565C0",
weight: 2
},
onEachFeature: (feature, layer) => {
layer.on('click', () => {
const districtName = feature.properties.points[0][0];
createLineChart(districtName, feature.properties.data);
});
}
}).addTo(map);
} catch (error) {
console.error("GeoJSONの読み込みに失敗しました:", error);
}
}
initMap();
JSONファイルの抜粋は以下になります。
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"area":{"area":78124.56292457819,"ratio_area":[78124.56292457819],"ratio":[1.0]},"data":[{"is_authorized":true,"ratio_value":["5224614"],"stat_item_id":21142,"stat_id":"029002300","value":"5224614"},{"is_authorized":true,"ratio_value":["5007066"],"stat_item_id":21143,"stat_id":"029002300","value":"5007066"},{"is_authorized":true,"ratio_value":["4791556"],"stat_item_id":21144,"stat_id":"029002300","value":"4791556"},{"is_authorized":true,"ratio_value":["4562362"],"stat_item_id":21145,"stat_id":"029002300","value":"4562362"},{"is_authorized":true,"ratio_value":["4319217"],"stat_item_id":21146,"stat_id":"029002300","value":"4319217"},{"is_authorized":true,"ratio_value":["4067642"],"stat_item_id":21147,"stat_id":"029002300","value":"4067642"},{"is_authorized":true,"ratio_value":["3820016"],"stat_item_id":21148,"stat_id":"029002300","value":"3820016"}],"point_coordinates":[143.33075,43.46036],"geocode":"01","points":[["北海道"]]},"geometry":{"type":"MultiPolygon","coordinates":[[[[140.218914647,41.419258644],
チャートの表示にはChart.jsを使用します
バーチャートの生成と表示設定
未来人口の人口指数は、2020年国勢調査の人口を基準(100)として、未来人口の増減率を示す値です。
例:2050年総人口指数 = (2050年人口総数 / 2020年人口総数) * 100
また、クリックしたボタンに応じて、その年の人口指数が表示されます。
/**
* 年度に応じた stat_item_id を返す関数
*/
function getStatId(year) {
switch(year) {
case 2025: return 21143;
case 2030: return 21144;
case 2035: return 21145;
case 2040: return 21146;
case 2045: return 21147;
case 2050: return 21148;
default: return 21143;
}
}
/**
* 各地区の人口指数のバーチャートを作成する関数
* @param {number} year 対象の年(2025, 2030, ...)
*/
function createBarChart(year) {
// 対象の stat_item_id を取得
const statId = getStatId(year);
const districtNames = [];
const populationIndices = [];
// 全地区のデータをループして、x軸のラベルと対象年の人口指数を計算
geojsonData.features.forEach(feature => {
const districtName = feature.properties.points[0][0];
const data = feature.properties.data;
const pop2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0;
const popYear = data.find(entry => entry.stat_item_id === statId)?.value || 0;
let index = 0;
if (pop2020 > 0) {
index = (popYear / pop2020) * 100;
}
districtNames.push(districtName);
populationIndices.push(index);
});
const ctx = document.getElementById('barChart').getContext('2d');
// 既にチャートがある場合は破棄
if (window.barChart instanceof Chart) {
window.barChart.destroy();
}
window.barChart = new Chart(ctx, {
type: 'bar',
data: {
labels: districtNames,
datasets: [
{
label: year + "年の人口指数(増加)",
data: populationIndices.map(value => value >= 100 ? value - 100 : 0),
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
},
{
label: year + "年の人口指数(減少)",
data: populationIndices.map(value => value < 100 ? value - 100 : 0),
backgroundColor: 'rgba(255, 99, 132, 0.5)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
}
]
},
options: {
responsive: true,
scales: {
x: {
stacked: true,
title: {
display: true,
text: '地区'
},
ticks: {
autoSkip: false,
maxRotation: 90, // 最大回転角度
minRotation: 90, // 最小回転角度(90なら縦書きになる)
font: {
size: 10
}
}
},
y: {
stacked: true,
beginAtZero: false,
suggestedMin: 0, // 適宜調整
suggestedMax: 10, // 適宜調整
title: {
display: true,
text: '人口指数'
}
}
}
}
});
}
/**
* ボタンから呼ばれる、チャート更新用の関数
* @param {number} year
*/
function updateChart(year) {
createBarChart(year);
}
折り線グラフの生成と表示設定
グラフのY軸は、全データ中の最大の人口総数に合わせて設定しています。
function createLineChart(districtName, data) {
const ctx = document.getElementById('lineChart').getContext('2d');
const population2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0; // (基準)2020年人口総数
const population2025 = data.find(entry => entry.stat_item_id === 21143)?.value || 0; // 2025年人口総数
const population2030 = data.find(entry => entry.stat_item_id === 21144)?.value || 0; // 2030年人口総数
const population2035 = data.find(entry => entry.stat_item_id === 21145)?.value || 0; // 2035年人口総数
const population2040 = data.find(entry => entry.stat_item_id === 21146)?.value || 0; // 2040年人口総数
const population2045 = data.find(entry => entry.stat_item_id === 21147)?.value || 0; // 2045年人口総数
const population2050 = data.find(entry => entry.stat_item_id === 21148)?.value || 0; // 2050年人口総数
const chartData = {
labels: ['2020年', '2025年', '2030年', '2035年', '2040年', '2045年', '2050年'],
datasets: [{
label: districtName,
data: [population2020, population2025, population2030, population2035, population2040, population2045, population2050],
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1,
fill: false
}]
};
if (window.lineChart instanceof Chart) {
window.lineChart.destroy();
}
window.lineChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
scales: {
x: {
display: true,
title: {
display: true,
text: '年次'
}
},
y: {
beginAtZero: true,
max: unifiedMax,
display: true,
title: {
display: true,
text: '人口総数'
}
}
}
}
});
// チャートを表示
document.getElementById('lineChart').style.display = 'block';
}
全体のコードは以下のファイルに記載しています。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Leaflet Map</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<div id="map"></div>
<div id="charts">
<canvas id="lineChart"></canvas>
<hr style="border: 1px solid #ccc; margin: 0;">
<canvas id="barChart"></canvas>
<div id="buttons">
<button onclick="updateChart(2025)">2025年</button>
<button onclick="updateChart(2030)">2030年</button>
<button onclick="updateChart(2035)">2035年</button>
<button onclick="updateChart(2040)">2040年</button>
<button onclick="updateChart(2045)">2045年</button>
<button onclick="updateChart(2050)">2050年</button>
</div>
</div>
</div>
<script src="index.js"></script>
</body>
</html>
style.css
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
.container {
display: flex;
height: 100vh;
}
#map {
width: 50%;
height: 100%;
}
#charts {
width: 50%;
height: 45%;
display: flex;
flex-direction: column;
}
#lineChart {
flex: 1;
display: block;
}
#buttons {
text-align: center;
padding: 10px;
background-color: #f5f5f5;
}
#buttons button {
margin: 0 5px;
padding: 5px 10px;
}
index.js
let map;
let geojsonData;
let unifiedMax = 0;
async function initMap() {
map = L.map('map').setView([35.68437, 139.75247], 7);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 13,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
try {
const response = await fetch("area.json");
geojsonData = await response.json();
geojsonData.features.forEach(feature => {
const data = feature.properties.data;
const population2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0;
const population2025 = data.find(entry => entry.stat_item_id === 21143)?.value || 0;
const population2030 = data.find(entry => entry.stat_item_id === 21144)?.value || 0;
const population2035 = data.find(entry => entry.stat_item_id === 21145)?.value || 0;
const population2040 = data.find(entry => entry.stat_item_id === 21146)?.value || 0;
const population2045 = data.find(entry => entry.stat_item_id === 21147)?.value || 0;
const population2050 = data.find(entry => entry.stat_item_id === 21148)?.value || 0;
const maxForFeature = Math.max(population2020, population2025, population2030, population2035, population2040, population2045, population2050);
if (maxForFeature > unifiedMax) {
unifiedMax = maxForFeature;
}
});
L.geoJSON(geojsonData, {
style: {
fillColor: "#2196F3",
fillOpacity: 0.4,
color: "#1565C0",
weight: 2
},
onEachFeature: (feature, layer) => {
layer.on('click', () => {
const districtName = feature.properties.points[0][0];
createLineChart(districtName, feature.properties.data);
});
}
}).addTo(map);
// ページ読み込み時にデフォルト(2025年)のバーチャートを描画
createBarChart(2025);
} catch (error) {
console.error("GeoJSONの読み込みに失敗しました:", error);
}
}
/**
* 年度に応じた stat_item_id を返す関数
*/
function getStatId(year) {
switch(year) {
case 2025: return 21143;
case 2030: return 21144;
case 2035: return 21145;
case 2040: return 21146;
case 2045: return 21147;
case 2050: return 21148;
default: return 21143;
}
}
/**
* 各地区の人口指数のバーチャートを作成する関数
* @param {number} year 対象の年(2025, 2030, ...)
*/
function createBarChart(year) {
// 対象の stat_item_id を取得
const statId = getStatId(year);
const districtNames = [];
const populationIndices = [];
// 全地区のデータをループして、x軸のラベルと対象年の人口指数を計算
geojsonData.features.forEach(feature => {
const districtName = feature.properties.points[0][0];
const data = feature.properties.data;
const pop2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0;
const popYear = data.find(entry => entry.stat_item_id === statId)?.value || 0;
let index = 0;
if (pop2020 > 0) {
index = (popYear / pop2020) * 100;
}
districtNames.push(districtName);
populationIndices.push(index);
});
const ctx = document.getElementById('barChart').getContext('2d');
// 既にチャートがある場合は破棄
if (window.barChart instanceof Chart) {
window.barChart.destroy();
}
window.barChart = new Chart(ctx, {
type: 'bar',
data: {
labels: districtNames,
datasets: [
{
label: year + "年の人口指数(増加)",
data: populationIndices.map(value => value >= 100 ? value - 100 : 0),
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
},
{
label: year + "年の人口指数(減少)",
data: populationIndices.map(value => value < 100 ? value - 100 : 0),
backgroundColor: 'rgba(255, 99, 132, 0.5)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
}
]
},
options: {
responsive: true,
scales: {
x: {
stacked: true,
title: {
display: true,
text: '地区'
},
ticks: {
autoSkip: false,
maxRotation: 90, // 最大回転角度
minRotation: 90, // 最小回転角度(90なら縦書きになる)
font: {
size: 10
}
}
},
y: {
stacked: true,
beginAtZero: false,
suggestedMin: 0, // 適宜調整
suggestedMax: 10, // 適宜調整
title: {
display: true,
text: '人口指数'
}
}
}
}
});
}
/**
* ボタンから呼ばれる、チャート更新用の関数
* @param {number} year
*/
function updateChart(year) {
createBarChart(year);
}
function createLineChart(districtName, data) {
const ctx = document.getElementById('lineChart').getContext('2d');
const population2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0; // (基準)2020年人口総数
const population2025 = data.find(entry => entry.stat_item_id === 21143)?.value || 0; // 2025年人口総数
const population2030 = data.find(entry => entry.stat_item_id === 21144)?.value || 0; // 2030年人口総数
const population2035 = data.find(entry => entry.stat_item_id === 21145)?.value || 0; // 2035年人口総数
const population2040 = data.find(entry => entry.stat_item_id === 21146)?.value || 0; // 2040年人口総数
const population2045 = data.find(entry => entry.stat_item_id === 21147)?.value || 0; // 2045年人口総数
const population2050 = data.find(entry => entry.stat_item_id === 21148)?.value || 0; // 2050年人口総数
const chartData = {
labels: ['2020年', '2025年', '2030年', '2035年', '2040年', '2045年', '2050年'],
datasets: [{
label: districtName,
data: [population2020, population2025, population2030, population2035, population2040, population2045, population2050],
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1,
fill: false
}]
};
if (window.lineChart instanceof Chart) {
window.lineChart.destroy();
}
window.lineChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
scales: {
x: {
display: true,
title: {
display: true,
text: '年次'
}
},
y: {
beginAtZero: true,
max: unifiedMax,
display: true,
title: {
display: true,
text: '人口総数'
}
}
}
}
});
// チャートを表示
document.getElementById('lineChart').style.display = 'block';
}
initMap();