Skip to content

Commit

Permalink
Merge pull request #7 from crab85193/develop
Browse files Browse the repository at this point in the history
マルチカテゴリ対応の人口推移グラフ機能追加および関連修正
  • Loading branch information
crab85193 authored Dec 18, 2024
2 parents 79608ba + 806af23 commit 137b795
Show file tree
Hide file tree
Showing 17 changed files with 740 additions and 86 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "5.0.1",
"recharts": "^2.15.0",
"typescript": "^4.4.2",
"web-vitals": "^2.1.0"
"web-vitals": "^2.1.0",
"zustand": "^5.0.2"
},
"scripts": {
"start": "react-scripts start",
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from "react";
import Header from "./components/Header";
import PopulationGraphPage from "./pages/PopulationGraphPage";
import "./assets/styles/reset.css";

function App() {
return (
<div className="layout">
<Header />
<PopulationGraphPage />
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/api/population.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const fetchPopulation = async (
): Promise<PopulationCategory[]> => {
try {
const response = await axios.get<PopulationResponse>(
`${process.env.REACT_APP_YUMEMI_API_URL}/population/${prefCode}`,
`${process.env.REACT_APP_YUMEMI_API_URL}/api/v1/population/composition/perYear?prefCode=${prefCode}`,
{
headers: {
"X-API-KEY": process.env.REACT_APP_YUMEMI_API_KEY!,
Expand Down
12 changes: 3 additions & 9 deletions src/api/prefectures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,13 @@ import { Prefecture } from "../types/prefecture";
export const fetchPrefectures = async (): Promise<Prefecture[]> => {
try {
const response = await axios.get(
`${process.env.REACT_APP_YUMEMI_API_URL}/prefectures`,
`${process.env.REACT_APP_YUMEMI_API_URL}/api/v1/prefectures`,
{
headers: {
"X-API-KEY": process.env.REACT_APP_YUMEMI_API_KEY!,
},
headers: { "X-API-KEY": process.env.REACT_APP_YUMEMI_API_KEY },
}
);

if (response.status !== 200) {
throw new Error("Failed to fetch prefectures");
}

return response.data as Prefecture[];
return response.data.result as Prefecture[];
} catch (error) {
throw new Error("Error fetching prefectures: " + error);
}
Expand Down
129 changes: 129 additions & 0 deletions src/components/PopulationChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useState } from "react";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts";

interface PopulationData {
year: number;
value: number;
rate: number;
}

interface PrefectureCategoryData {
prefName: string;
data: PopulationData[];
}

interface AllCategoriesData {
[categoryName: string]: PrefectureCategoryData[];
}

interface PopulationChartProps {
allCategoriesData: AllCategoriesData;
}

const categories = ["総人口", "年少人口", "生産年齢人口", "老年人口"];

const colorMap: { [prefName: string]: string } = {};

const generateRandomColor = (): string => {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};

const getColorForPrefName = (pName: string) => {
if (!colorMap[pName]) {
colorMap[pName] = generateRandomColor();
}
return colorMap[pName];
};

const PopulationChart: React.FC<PopulationChartProps> = ({
allCategoriesData,
}) => {
const [selectedCategory, setSelectedCategory] = useState<string>(
categories[0]
);

const prefectureDataArray = allCategoriesData[selectedCategory] || [];

if (prefectureDataArray.length === 0) {
return <p>選択中のカテゴリのデータがありません</p>;
}

const allYears = prefectureDataArray.flatMap((p) =>
p.data.map((d) => d.year)
);
const uniqueYears = Array.from(new Set(allYears)).sort((a, b) => a - b);

const combinedData = uniqueYears.map((year) => {
const entry: { [key: string]: number | string } = { year };
for (const pData of prefectureDataArray) {
const yearData = pData.data.find((d) => d.year === year);
entry[pData.prefName] = yearData ? yearData.value : 0;
}
return entry;
});

const prefNames = prefectureDataArray.map((p) => p.prefName);

return (
<div style={{ width: "100%", height: "500px" }}>
{/* カテゴリ切り替えUI */}
<div style={{ marginBottom: "10px" }}>
{categories.map((cat) => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
style={{
marginRight: "5px",
padding: "5px 10px",
backgroundColor: cat === selectedCategory ? "#007bff" : "#e0e0e0",
color: cat === selectedCategory ? "#fff" : "#000",
border: "none",
borderRadius: "3px",
cursor: "pointer",
}}
>
{cat}
</button>
))}
</div>

<ResponsiveContainer width="100%" height="100%">
<LineChart
data={combinedData}
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="year" />
<YAxis />
<Tooltip />
<Legend />
{prefNames.map((pName) => (
<Line
key={pName}
type="monotone"
dataKey={pName}
name={pName}
stroke={getColorForPrefName(pName)}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
);
};

export default PopulationChart;
6 changes: 3 additions & 3 deletions src/components/PopulationSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { fetchPopulation } from "../api/population"; // 人口データを取得するAPI
import { Prefecture } from "../types/prefecture"; // 都道府県の型
import { PopulationCategory } from "../types/population"; // 人口データのカテゴリ型
import { fetchPopulation } from "../api/population";
import { Prefecture } from "../types/prefecture";
import { PopulationCategory } from "../types/population";

interface PopulationSelectorProps {
prefectures: Prefecture[];
Expand Down
43 changes: 20 additions & 23 deletions src/components/PrefectureCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
import React from "react";
import { Prefecture } from "../types/prefecture";
import usePrefecturesStore from "../store/usePrefecturesStore";

interface PrefectureCheckboxProps {
prefectures: Prefecture[];
selectedPrefectures: string[];
onSelect: (selected: string[]) => void;
onSelect: (prefCode: number) => void;
onSelectAll: (prefCodes: number[]) => void;
onClearSelection: () => void;
}

const PrefectureCheckbox: React.FC<PrefectureCheckboxProps> = ({
prefectures,
selectedPrefectures,
onSelect,
onSelectAll,
onClearSelection,
}) => {
const handleCheckboxChange = (prefCode: string) => {
if (selectedPrefectures.includes(prefCode)) {
onSelect(selectedPrefectures.filter((code) => code !== prefCode));
} else {
onSelect([...selectedPrefectures, prefCode]);
}
};
const { selectedPrefectures } = usePrefecturesStore();

return (
<div>
<h3>都道府県選択</h3>
{prefectures.map((prefecture) => (
<div key={prefecture.prefCode}>
<h3>都道府県を選択</h3>
<button
onClick={() => onSelectAll(prefectures.map((pref) => pref.prefCode))}
>
すべて選択
</button>
<button onClick={onClearSelection}>選択をクリア</button>
{prefectures.map((pref) => (
<div key={pref.prefCode}>
<input
id={`pref-${pref.prefCode}`}
type="checkbox"
id={prefecture.prefCode.toString()}
checked={selectedPrefectures.includes(
prefecture.prefCode.toString()
)}
onChange={() =>
handleCheckboxChange(prefecture.prefCode.toString())
}
checked={selectedPrefectures.includes(pref.prefCode)}
onChange={() => onSelect(pref.prefCode)}
/>
<label htmlFor={prefecture.prefCode.toString()}>
{prefecture.prefName}
</label>
<label htmlFor={`pref-${pref.prefCode}`}>{pref.prefName}</label>
</div>
))}
</div>
Expand Down
32 changes: 32 additions & 0 deletions src/hooks/usePopulation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState, useEffect } from "react";
import { fetchPopulation } from "../api/population";
import { PopulationCategory } from "../types/population";

const usePopulation = (prefCode: number | null) => {
const [data, setData] = useState<PopulationCategory[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!prefCode) return;

const fetchData = async () => {
setLoading(true);
setError(null);
try {
const result = await fetchPopulation(prefCode.toString());
setData(result);
} catch (err) {
setError("人口データの取得に失敗しました. " + err);
} finally {
setLoading(false);
}
};

fetchData();
}, [prefCode]);

return { data, loading, error };
};

export default usePopulation;
Loading

0 comments on commit 137b795

Please sign in to comment.