
日本の人口をiPhone・iPadのホーム画面からいつでも確認できる「日本の人口」ウィジェットです。
ニュースなどで「日本の人口減少」という話はよく耳にしますが、実際に今何人いて、どのくらいのペースで増減しているのかを数字で見る機会は意外とありません。
それなら、最新の人口と前月からの増減をホーム画面に表示して、数字の変化を気軽に見られたら面白いんじゃないかと思って作りました。
総務省統計局の「人口推計」をもとに、日本の総人口や前月からの増減、男性・女性それぞれの人口を表示します。
なお、表示される人口はリアルタイムの人数ではありません。総務省統計局から公表される月次データを使用しているため、基本的に月1回の更新となります。
データは新しい統計が公開されると自動で取得するため、ウィジェット側で毎月数字を書き換える必要はありません。
小・中・大の3サイズに対応しており、ウィジェットサイズに合わせて見やすい情報量に自動で切り替わります。
ウィジェットの表示内容
e-Statの人口データを取得します。
サイズごとの表示
- 小:総人口と前月からの増減、データの対象月を表示します。
- 中:男性・女性の人口も確認できます。
- 大:中サイズと共通の情報を、広いレイアウトで読みやすく表示します。
設定手順
上から順に進めてください。コードの保存と設定のコピーに必要な操作欄は、それぞれの手順の中にあります。
- Scriptableを用意する
App StoreでScriptableを開き、iPhoneにインストールします。すでに入っている場合は次へ進んでください。
- コードをコピーして保存する
下のコード欄の「コピー」を押します。Scriptableへ移動し、「+」から新しいスクリプトを作ってコード全文を貼り付け、日本の人口ウィジェットなどの名前で保存してください。
保存したらこのページに戻り、次の手順へ進みます。
// Version: 1.0.0 // ===================================================== // 日本の人口 - Scriptable Widget // 小・中・大サイズ対応 // // ・総務省 統計ダッシュボードAPIから自動取得 // ・総人口 // ・前月比 // ・男性人口 // ・女性人口 // ・取得失敗時はキャッシュを使用 // ===================================================== // ===================================================== // 基本設定 // ===================================================== const TITLE = "日本の人口"; const API_BASE = "https://dashboard.e-stat.go.jp/api/1.0/Json"; const CACHE_NAME = "japan_population_widget.json"; // 総人口(総数) const TOTAL_CODE = "0201010000000010000"; // ===================================================== // カラー // ===================================================== const BG_TOP = new Color("#101827"); const BG_BOTTOM = new Color("#1d2939"); const WHITE = Color.white(); const SUB = new Color("#98a2b3"); const RED = new Color("#f97066"); const GREEN = new Color("#32d583"); const MALE_COLOR = new Color("#53b1fd"); const FEMALE_COLOR = new Color("#f670c7"); // ===================================================== // API // ===================================================== async function loadJSON(url) { const req = new Request(url); req.timeoutInterval = 15; return await req.loadJSON(); } // ===================================================== // 人口系列を検索 // ===================================================== async function findPopulationIndicators() { const url = API_BASE + "/getIndicatorInfo" + "?Lang=JP" + "&Category=0201"; const json = await loadJSON(url); const objects = []; collectObjects(json, objects); let male = null; let female = null; for (const obj of objects) { const text = Object.values(obj) .filter(v => typeof v === "string" ) .join(" "); // ----------------------------- // 男性 // ----------------------------- if ( !male && ( text.includes("総人口(男)") || text.includes("総人口(男)") || text.includes("総人口 男") || text.includes("人口(男)") ) ) { const code = findIndicatorCode(obj); if (code) { male = code; } } // ----------------------------- // 女性 // ----------------------------- if ( !female && ( text.includes("総人口(女)") || text.includes("総人口(女)") || text.includes("総人口 女") || text.includes("人口(女)") ) ) { const code = findIndicatorCode(obj); if (code) { female = code; } } } return { total: TOTAL_CODE, male, female }; } // ===================================================== // JSON内のオブジェクトを収集 // ===================================================== function collectObjects(value, result) { if (!value) return; if (Array.isArray(value)) { for (const item of value) { collectObjects(item, result); } return; } if (typeof value !== "object") { return; } result.push(value); for (const key in value) { collectObjects( value[key], result ); } } // ===================================================== // 19桁の系列コードを探す // ===================================================== function findIndicatorCode(obj) { for (const key in obj) { const value = obj[key]; if ( typeof value === "string" && /^\d{19}$/.test(value) ) { return value; } } return null; } // ===================================================== // 系列データ取得 // ===================================================== async function getSeries(code) { if (!code) return []; const url = API_BASE + "/getData" + "?Lang=JP" + "&IndicatorCode=" + encodeURIComponent(code) + "&RegionCode=00000"; const json = await loadJSON(url); const values = []; collectValues( json, code, values ); values.sort((a, b) => String(b.time) .localeCompare( String(a.time) ) ); return values; } // ===================================================== // APIレスポンスから値を抽出 // ===================================================== function collectValues( value, code, result ) { if (!value) return; if (Array.isArray(value)) { for (const item of value) { collectValues( item, code, result ); } return; } if (typeof value !== "object") { return; } // 統計ダッシュボード VALUE形式 if ( value["@time"] !== undefined ) { const indicator = value["@indicator"] || value.indicator; if ( !indicator || indicator === code ) { let raw = null; if (value["$"] !== undefined) { raw = value["$"]; } else if ( value.value !== undefined ) { raw = value.value; } if (raw !== null) { const num = Number(raw); if (!isNaN(num)) { result.push({ time: value["@time"], value: normalizePopulation( num ) }); } } } } for (const key in value) { collectValues( value[key], code, result ); } } // ===================================================== // 単位補正 // ===================================================== function normalizePopulation(value) { /* * 人口系列は単位によって * 「千人」「万人」等の場合があるため補正 */ if (value < 200000) { return Math.round( value * 1000 ); } return Math.round(value); } // ===================================================== // 全データ取得 // ===================================================== async function getPopulationData() { const fm = FileManager.local(); const cachePath = fm.joinPath( fm.documentsDirectory(), CACHE_NAME ); try { // ----------------------------- // 系列検索 // ----------------------------- const indicators = await findPopulationIndicators(); // ----------------------------- // データ取得 // ----------------------------- const total = await getSeries( indicators.total ); if (!total.length) { throw new Error( "総人口を取得できません" ); } const male = indicators.male ? await getSeries( indicators.male ) : []; const female = indicators.female ? await getSeries( indicators.female ) : []; // ----------------------------- // 最新月 // ----------------------------- const latest = total[0]; const previous = total[1] || null; // 男女は総人口と同じ月を優先 const maleLatest = findSamePeriod( male, latest.time ); const femaleLatest = findSamePeriod( female, latest.time ); const data = { total: latest.value, previous: previous ? previous.value : null, male: maleLatest ? maleLatest.value : null, female: femaleLatest ? femaleLatest.value : null, time: latest.time, indicators, updated: new Date() .toISOString() }; // キャッシュ fm.writeString( cachePath, JSON.stringify(data) ); return data; } catch (error) { console.log( "API ERROR: " + error ); // ----------------------------- // キャッシュ使用 // ----------------------------- if ( fm.fileExists( cachePath ) ) { try { return JSON.parse( fm.readString( cachePath ) ); } catch (e) { console.log( "CACHE ERROR: " + e ); } } return null; } } // ===================================================== // 同じ年月の値を探す // ===================================================== function findSamePeriod( list, time ) { if (!list.length) { return null; } const exact = list.find( item => item.time === time ); return exact || list[0]; } // ===================================================== // 通常人数 // ===================================================== function formatNumber(num) { return Math.round(num) .toLocaleString( "ja-JP" ); } // ===================================================== // 小サイズ用 // // 122,680,000 // ↓ // 1億2268万人 // ===================================================== function formatJapanesePopulation( num ) { const oku = Math.floor( num / 100000000 ); const man = Math.floor( ( num % 100000000 ) / 10000 ); if (oku > 0) { return ( oku + "億" + man + "万人" ); } return ( Math.floor( num / 10000 ) + "万人" ); } // ===================================================== // 男女用 // // 59,700,000 // ↓ // 5970万人 // ===================================================== function formatMan(num) { if (num === null) { return "―"; } return ( Math.round( num / 10000 ).toLocaleString( "ja-JP" ) + "万人" ); } // ===================================================== // 前月比 // ===================================================== function formatDiff(diff) { const sign = diff > 0 ? "+" : ""; return ( sign + formatNumber(diff) + "人" ); } // ===================================================== // 小サイズ用 前月比 // ===================================================== function formatSmallDiff(diff) { const man = Math.round( Math.abs(diff) / 10000 ); if (diff < 0) { return ( "↓ " + man + "万人" ); } if (diff > 0) { return ( "↑ " + man + "万人" ); } return "±0人"; } // ===================================================== // 年月 // ===================================================== function formatPeriod(time) { if (!time) return ""; const str = String(time); const match = str.match( /(\d{4}).*?(\d{2})/ ); if (match) { return ( Number(match[1]) + "年" + Number(match[2]) + "月" ); } return str; } // ===================================================== // 背景 // ===================================================== function setBackground(widget) { const gradient = new LinearGradient(); gradient.colors = [ BG_TOP, BG_BOTTOM ]; gradient.locations = [ 0, 1 ]; widget.backgroundGradient = gradient; } // ===================================================== // 小サイズ // ===================================================== function createSmall( widget, data ) { widget.setPadding( 16, 15, 14, 15 ); // タイトル const title = widget.addText( "🇯🇵 日本の人口" ); title.font = Font.boldSystemFont(14); title.textColor = WHITE; title.lineLimit = 1; widget.addSpacer(); // 総人口 const population = widget.addText( formatJapanesePopulation( data.total ) ); population.font = Font.boldSystemFont(24); population.textColor = WHITE; population.lineLimit = 1; population.minimumScaleFactor = 0.7; widget.addSpacer(7); // 前月比 if ( data.previous !== null ) { const diff = data.total - data.previous; const row = widget.addStack(); row.centerAlignContent(); const label = row.addText( "前月比 " ); label.font = Font.systemFont(10); label.textColor = SUB; const value = row.addText( formatSmallDiff( diff ) ); value.font = Font.semiboldSystemFont(11); value.textColor = diff < 0 ? RED : GREEN; } widget.addSpacer(); // 年月 const date = widget.addText( formatPeriod( data.time ) + " 現在" ); date.font = Font.systemFont(9); date.textColor = SUB; } // ===================================================== // 中サイズ // ===================================================== function createMedium( widget, data ) { widget.setPadding( 16, 19, 14, 19 ); // ----------------------------- // ヘッダー // ----------------------------- const top = widget.addStack(); top.centerAlignContent(); const title = top.addText( "🇯🇵 日本の人口" ); title.font = Font.boldSystemFont(15); title.textColor = WHITE; top.addSpacer(); const date = top.addText( formatPeriod( data.time ) ); date.font = Font.systemFont(10); date.textColor = SUB; widget.addSpacer(9); // ----------------------------- // 総人口 // ----------------------------- const population = widget.addText( formatNumber( data.total ) + " 人" ); population.font = Font.boldSystemFont(27); population.textColor = WHITE; population.lineLimit = 1; population.minimumScaleFactor = 0.75; widget.addSpacer(7); // ----------------------------- // 前月比 // ----------------------------- if ( data.previous !== null ) { const diff = data.total - data.previous; const row = widget.addStack(); row.centerAlignContent(); const label = row.addText( "前月比 " ); label.font = Font.systemFont(10); label.textColor = SUB; const value = row.addText( formatDiff(diff) ); value.font = Font.semiboldSystemFont(11); value.textColor = diff < 0 ? RED : GREEN; } widget.addSpacer(9); // ----------------------------- // 男女 // ----------------------------- const genders = widget.addStack(); genders.centerAlignContent(); const male = genders.addText( "👨 " + formatMan( data.male ) ); male.font = Font.semiboldSystemFont(11); male.textColor = MALE_COLOR; genders.addSpacer(18); const female = genders.addText( "👩 " + formatMan( data.female ) ); female.font = Font.semiboldSystemFont(11); female.textColor = FEMALE_COLOR; widget.addSpacer(); const source = widget.addText( "総務省統計局「人口推計」" ); source.font = Font.systemFont(8); source.textColor = SUB; } // ===================================================== // 大サイズ // ===================================================== function createLarge( widget, data ) { widget.setPadding( 22, 22, 20, 22 ); // タイトル const title = widget.addText( "🇯🇵 日本の人口" ); title.font = Font.boldSystemFont(20); title.textColor = WHITE; widget.addSpacer(5); const date = widget.addText( formatPeriod( data.time ) + " 現在" ); date.font = Font.systemFont(11); date.textColor = SUB; widget.addSpacer(22); // 総人口 const label = widget.addText( "総人口" ); label.font = Font.mediumSystemFont(11); label.textColor = SUB; widget.addSpacer(5); const population = widget.addText( formatNumber( data.total ) + " 人" ); population.font = Font.boldSystemFont(36); population.textColor = WHITE; population.lineLimit = 1; population.minimumScaleFactor = 0.7; widget.addSpacer(18); // 前月比 if ( data.previous !== null ) { const diff = data.total - data.previous; const box = widget.addStack(); box.layoutVertically(); box.setPadding( 11, 13, 11, 13 ); box.backgroundColor = new Color( "#ffffff", 0.06 ); box.cornerRadius = 11; const diffLabel = box.addText( "前月比" ); diffLabel.font = Font.systemFont(10); diffLabel.textColor = SUB; box.addSpacer(4); const diffText = box.addText( formatDiff(diff) ); diffText.font = Font.boldSystemFont(20); diffText.textColor = diff < 0 ? RED : GREEN; } widget.addSpacer(18); // 男女 const genderTitle = widget.addText( "男女別人口" ); genderTitle.font = Font.mediumSystemFont(11); genderTitle.textColor = SUB; widget.addSpacer(9); const male = widget.addText( "👨 男性 " + formatMan( data.male ) ); male.font = Font.semiboldSystemFont(15); male.textColor = MALE_COLOR; widget.addSpacer(7); const female = widget.addText( "👩 女性 " + formatMan( data.female ) ); female.font = Font.semiboldSystemFont(15); female.textColor = FEMALE_COLOR; widget.addSpacer(); const source = widget.addText( "出典:総務省統計局「人口推計」" ); source.font = Font.systemFont(9); source.textColor = SUB; } // ===================================================== // Widget生成 // ===================================================== async function createWidget() { const widget = new ListWidget(); setBackground(widget); const data = await getPopulationData(); // ----------------------------- // エラー // ----------------------------- if (!data) { widget.setPadding( 16, 16, 16, 16 ); const title = widget.addText( "🇯🇵 日本の人口" ); title.font = Font.boldSystemFont(14); title.textColor = WHITE; widget.addSpacer(); const error = widget.addText( "人口データを\n取得できませんでした" ); error.font = Font.mediumSystemFont(12); error.textColor = SUB; return widget; } // ----------------------------- // サイズ // ----------------------------- const family = config.widgetFamily || "medium"; if (family === "small") { createSmall( widget, data ); } else if ( family === "large" ) { createLarge( widget, data ); } else { createMedium( widget, data ); } return widget; } // ===================================================== // 実行 // ===================================================== const widget = await createWidget(); Script.setWidget(widget); // Scriptableアプリから実行した場合 if (!config.runsInWidget) { await widget.presentMedium(); } Script.complete(); - ホーム画面に追加して設定する
iPhoneのホーム画面を長押しし、ウィジェット追加画面からScriptableを選びます。この記事の対応サイズの説明に合わせてサイズを選んで追加してください。
追加したウィジェットを長押しして「ウィジェットを編集」を開き、「Script」に保存したスクリプトを選びます。
「Parameter」は空欄で構いません。
編集画面を閉じて表示を確認します。ホーム画面への反映には少し時間がかかる場合があります。
動作確認・使用結果
| 確認した端末 | iPhone 17 ProMAX |
|---|---|
| iOS | iOS27 |
| Scriptable | 1.7.19 |
| 確認したサイズ | 小・中・大 |
| バージョン | 1.0.0 |
| 動作確認日 | 2026-09-18 |
表示・使用時のポイント
e-Statの人口データを取得します。通信に失敗したときは、保存済みのデータがあれば前回取得分を使います。表示の対象月を合わせて確認してください。
更新履歴
1.0.0
- 初回リリース
よくあるエラーと対処
- 確認すること
- 表示されない場合は、ウィジェット編集画面の「Script」に保存したスクリプトが選ばれているかも確認してください。コードを貼り直すときは全文を置き換えます。
- 人口が毎日変わらない
- 月次の統計なので、更新されるまでは同じ数値です。
- 初回に取得できない
- まだ保存データがないため、通信を確認して再実行してください。
- 古い対象月が表示される
- 取得先の公開状況と通信状態を確認してください。保存データを表示している場合があります。
注意事項・制限
- 瞬間ごとの人口を測るものではなく、公表された統計値の表示です。
- 統計の公表や改定のタイミングにより、対象月と現在の月は一致しない場合があります。
- ホーム画面の更新タイミングはiOSが管理するため、操作やデータの変更がすぐに反映されない場合があります。
データ提供・参考資料
e-Stat 統計ダッシュボード:人口統計データを取得しています。
SHARE

