新年収データを使用して東京都23区の年収ヒストグラムを表示してみました

# はじめに
前の記事では、東京都23区の年収データをGeoJSON形式で取得し、平均年収に応じて色分け表示しました。

https://qiita.com/yan_x/items/7a18f6f632dd94f4e2c7

今回は、新たな年収データを用いて各区の平均年収表示を行い、加えて各区の年収ヒストグラムを作成してみました。

# 完成画面
画面上半分には地図が表示され、各区の平均年収で色分けしています。
画面下半分には、クリックした区の年収ヒストグラムが表示されます。グラフのx軸は年収範囲、y軸は世帯数となっており、各区の年収分布状況を視覚的に把握できます。
![年収23区ヒストグラム.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916958/77ef5285-5203-4470-b8a9-12878f5a35d1.gif)

# 新年収データについて
新年収データ（年収別世帯数推計データ）住宅・土地統計および賃金関係データを最新年度に更新しています。

https://www.mapmarketing.co.jp/data_nenshu.html?grid=qiita&utm_source=qiita.com&utm_medium=referral&utm_campaign=qiita_gmap_nenshu_8b481b34971e64fb3905_01

# コードについて
### JavaScript全体のサンプルコード

ヒストグラムの作成はChart.jsを使用しています。
```javascript
let map;
let histogramChart = null;
let globalInfoWindow;

const labelMapping = {
    24532: "年収200万円未満",
    24533: "年収200～300万円",
    24534: "年収300～400万円",
    24535: "年収400～500万円",
    24536: "年収500～600万円",
    24537: "年収600～700万円",
    24538: "年収700～800万円",
    24539: "年収800～900万円",
    24540: "年収900～1000万円",
    24541: "年収1000～1250万円",
    24542: "年収1250～1500万円",
    24543: "年収1500～2000万円",
    24544: "年収2000万円以上"
};

async function initMap() {
  const { Map, InfoWindow } = await window.google.maps.importLibrary("maps");
  map = new Map(document.getElementById("map"), {
    center: { lat: 35.68437, lng: 139.75247 },
    zoom: 10,
  });
  
  globalInfoWindow = new InfoWindow();

  // TerraMap APIからデータを取得する部分はJSONファイルを読み込んで疑似的に再現
  fetch('area.json')
    .then(response => response.json())
    .then(geoJsonData => {
      drawPolygons(geoJsonData);
    })
    .catch(error => console.error('Error fetching GeoJSON data:', error));
}

// 平均年収に基づいて色を決定する関数
function getColor(value) {
  if (value <= 450) {
    return '#5b97ee';
  } else if (value <= 550) {
    return '#92c7ff';
  } else if (value <= 650) {
    return '#ffd976';
  } else if (value <= 750) {
    return '#ffa746';
  } else {
    return '#d7352b';
  }
}

function drawPolygons(geoJsonData) {
  geoJsonData.features.forEach(feature => {
    const polygons = feature.geometry.coordinates;
    
    // 年収総額データと世帯合計データ
    const nenshuSum = feature.properties.data.find(item => item.stat_item_id === 24545)?.value;
    const householdNumber = feature.properties.data.find(item => item.stat_item_id === 24531)?.value;
    const calculatedValue = (nenshuSum && householdNumber) ? (nenshuSum / householdNumber).toFixed(2) : "N/A";
    
    const wardName = feature.properties.points[0].join("");
    
    polygons.forEach(polygon => {
      const paths = polygon[0].map(coord => ({ lat: coord[1], lng: coord[0] }));
      
      const poly = new google.maps.Polygon({
        paths: paths,
        strokeColor: '#666',
        strokeOpacity: 0.8,
        strokeWeight: 2,
        fillColor: getColor(calculatedValue),
        fillOpacity: 0.6,
        map: map
      });
      
      // クリック時にInfoWindow下部のヒストグラムを更新
      poly.addListener('click', (event) => {
        globalInfoWindow.close();
        
        // ヒストグラム用のstat_item_id一覧
        const statIds = [24532, 24533, 24534, 24535, 24536, 24537, 24538, 24539, 24540, 24541, 24542, 24543, 24544];
        const histogramData = statIds.map(id => {
          const item = feature.properties.data.find(item => item.stat_item_id === id);
          return item ? item.value : 0;
        });
        
        // InfoWindowに平均年収を表示
        const infoContent = `
          <div class="custom-info-window">
            <h3>${wardName}</h3>
            <p>平均年収（万円）: ${calculatedValue}</p>
          </div>
        `;
        globalInfoWindow.setContent(infoContent);
        globalInfoWindow.setPosition(event.latLng);
        globalInfoWindow.open(map);
        
        // ヒストグラムキャンバスを更新
        const canvas = document.getElementById('histogramChart');
        if (histogramChart) {
          histogramChart.destroy();
        }
        histogramChart = new Chart(canvas.getContext('2d'), {
          type: 'bar',
          data: {
            labels: statIds.map(id => labelMapping[id]),
            datasets: [{
              label: wardName,
              data: histogramData,
              backgroundColor: 'rgba(75, 192, 192, 0.2)',
              borderColor: 'rgba(75, 192, 192, 1)',
              borderWidth: 1
            }]
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                tooltip: {
                  enabled: false
                }
            },            
            scales: {
              y: {
                beginAtZero: true
              }
            }
          }
        });
      });
    });
  });
}

initMap();

```

index.jsコードで使用したJSONファイルの抜粋は以下になります。

<details><summary>area.json</summary>

```json
{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {
                "area": {
                    "area": 11.39768875876782,
                    "ratio_area": [
                        11.39768875876782
                    ],
                    "ratio": [
                        1.0
                    ]
                },
                "data": [
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "33993"
                        ],
                        "stat_item_id": 24531,
                        "stat_id": "007002300",
                        "value": "33993"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2148"
                        ],
                        "stat_item_id": 24532,
                        "stat_id": "007002300",
                        "value": "2148"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2483"
                        ],
                        "stat_item_id": 24533,
                        "stat_id": "007002300",
                        "value": "2483"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2667"
                        ],
                        "stat_item_id": 24534,
                        "stat_id": "007002300",
                        "value": "2667"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2280"
                        ],
                        "stat_item_id": 24535,
                        "stat_id": "007002300",
                        "value": "2280"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2536"
                        ],
                        "stat_item_id": 24536,
                        "stat_id": "007002300",
                        "value": "2536"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "1978"
                        ],
                        "stat_item_id": 24537,
                        "stat_id": "007002300",
                        "value": "1978"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2483"
                        ],
                        "stat_item_id": 24538,
                        "stat_id": "007002300",
                        "value": "2483"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2416"
                        ],
                        "stat_item_id": 24539,
                        "stat_id": "007002300",
                        "value": "2416"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "1945"
                        ],
                        "stat_item_id": 24540,
                        "stat_id": "007002300",
                        "value": "1945"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "4735"
                        ],
                        "stat_item_id": 24541,
                        "stat_id": "007002300",
                        "value": "4735"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2353"
                        ],
                        "stat_item_id": 24542,
                        "stat_id": "007002300",
                        "value": "2353"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "3430"
                        ],
                        "stat_item_id": 24543,
                        "stat_id": "007002300",
                        "value": "3430"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "2539"
                        ],
                        "stat_item_id": 24544,
                        "stat_id": "007002300",
                        "value": "2539"
                    },
                    {
                        "is_authorized": true,
                        "ratio_value": [
                            "31624000"
                        ],
                        "stat_item_id": 24545,
                        "stat_id": "007002300",
                        "value": "31624000"
                    }
                ],
                "point_coordinates": [
                    139.75019,
                    35.68687
                ],
                "geocode": "13101",
                "points": [
                    [
                        "東京都",
                        "千代田区"
                    ]
                ]
            },
            "geometry": {
                "type": "MultiPolygon",
                "coordinates": [
                    [
                        [
                            [
                                139.748974398,
                                35.670568286
                            ],
                            [
                                139.748956569,
                                35.670558464
                            ],
```

</details>