svg로 points 를 이어서 그래프를 그려주려고 했는데 입력한 데이터와 그래프의 간극이 너무 심하다 ㅎㅎㅎ
클릭한 순서대로 배열에 추가되니까, 그래프 그릴때는 x 좌표 기준으로 정렬해주었다.
그래프를 어떻게하면 데이터 입력한대로 그려줄수 있을까?
import { useEffect, useState } from "react";
import { useGraphStore } from "../../../store/useGraphStore";
import Point from "../../Main/component/Point/Point";
const ResultGraph = () => {
const { points } = useGraphStore();
const [data, setData] = useState(points);
//x 좌표 기준으로 정렬
useEffect(() => {
const sorted = [...points].sort((a, b) => a.x - b.x);
setData(sorted);
}, []);
const generatePath = (points: { x: number; y: number }[]) => {
if (points.length < 2) return ""; // 두 점 이상이 필요
// Catmull-Rom Spline을 사용하여 점들 사이를 부드럽게 연결
let path = `M ${points[0].x} ${points[0].y}`; // 첫 번째 점으로 시작
// 점들 사이의 부드러운 경로 생성
for (let i = 1; i < points.length - 2; i++) {
const p0 = points[i - 1];
const p1 = points[i];
const p2 = points[i + 1];
const p3 = points[i + 2];
// Catmull-Rom Spline 공식
const cx1 = (p0.x + p1.x) / 2;
const cy1 = (p0.y + p1.y) / 2;
const cx2 = (p1.x + p2.x) / 2;
const cy2 = (p1.y + p2.y) / 2;
const cx3 = (p2.x + p3.x) / 2; // 마지막 제어점
const cy3 = (p2.y + p3.y) / 2; // 마지막 제어점
path += ` C ${cx1} ${cy1}, ${cx2} ${cy2}, ${cx3} ${cy3}, ${p3.x} ${p3.y}`; // Catmull-Rom 곡선 추가
}
return path;
};
const pathData = generatePath(data);
return (
<div className="result-graph-container">
<svg className="result-graph" width="100%" height="100%">
<path d={pathData} fill="transparent" stroke="black" strokeWidth="2" />
</svg>
{points.map((point) => (
<Point key={point.id} x={point.x} y={point.y} title={point.title} />
))}
</div>
);
};
export default ResultGraph;
path를 반환하는 함수를 좀더 보정했지만 내가 원하는 그래프의 형태는 아니다.
Catmull-Rom Spline 이라니. 난 들어본적도 없는 개념이다.
컴퓨터 그래픽스에서 캐트물 롬 스플라인은 주로 키 프레임들 사이에서 부드러운 보간 이동을 얻는데 사용된다고 한다.
Catmull-Rom Spline
두 개의 점이 주어지는 경우 이들을 잇는 가장 단순한 곡선은 직선이다. 또 직선은 두 점을 잇는 가장 짧은 거리를 갖는 곡선이기도 하다. 그러면 $N$개의 점이 주어지는 경우는 이들 모두 지나는
kipl.tistory.com
const generatePath = (points: { x: number; y: number }[]) => {
if (points.length < 2) return ""; // 최소 2개의 점이 필요
let path = `M ${points[0].x} ${points[0].y}`; // 시작점
if (points.length === 2) {
// 점이 2개뿐이면 직선으로 연결
path += ` L ${points[1].x} ${points[1].y}`;
return path;
}
// 첫 번째 보간 (첫 번째와 두 번째 점을 자연스럽게 연결)
let p0 = points[0];
let p1 = points[1];
let p2 = points[2];
let cp1x = (p0.x + p1.x) / 2;
let cp1y = (p0.y + p1.y) / 2;
let cp2x = (p1.x + p2.x) / 2;
let cp2y = (p1.y + p2.y) / 2;
path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
// 중간 구간 (점이 4개 이상일 때)
for (let i = 2; i < points.length - 1; i++) {
p0 = points[i - 1];
p1 = points[i];
p2 = points[i + 1];
cp1x = (p0.x + p1.x) / 2;
cp1y = (p0.y + p1.y) / 2;
cp2x = (p1.x + p2.x) / 2;
cp2y = (p1.y + p2.y) / 2;
path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
}
return path;
};
지피티에게 계속 더 부드러운 곡선을 요청하자 조오금 더 부드러워진 모습이긴 하다
흠... .. 정말 지피티에 의존하는 방법밖에 없는걸까?
아니면 이걸 의존이 아니라, 수학을 엄청 잘하는 나의 조수로서 바라보며 그저 기특해하면 되는걸까?
const generatePath = (points: { x: number; y: number }[]) => {
if (points.length < 2) return ""; // 최소 두 점 필요
let path = `M ${points[0].x} ${points[0].y}`; // 시작점
if (points.length === 2) {
// 점이 2개뿐이면 직선 연결
path += ` L ${points[1].x} ${points[1].y}`;
return path;
}
// Catmull-Rom Spline 보간
for (let i = 0; i < points.length - 1; i++) {
const p0 = points[i === 0 ? i : i - 1]; // 이전 점 (처음이면 자기 자신)
const p1 = points[i]; // 현재 점
const p2 = points[i + 1]; // 다음 점
const p3 = points[i + 2 < points.length ? i + 2 : i + 1]; // 다음다음 점 (마지막이면 자기 자신)
// Catmull-Rom 공식으로 제어점 계산
const tension = 0.5; // 0.5가 적당한 값, 0에 가까우면 직선
const cp1x = p1.x + (p2.x - p0.x) * tension;
const cp1y = p1.y + (p2.y - p0.y) * tension;
const cp2x = p2.x - (p3.x - p1.x) * tension;
const cp2y = p2.y - (p3.y - p1.y) * tension;
// Cubic Bezier 곡선 추가
path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
}
return path;
};
generatePath 함수 내의 tension = 0.5 으로 선언되어있는 상수값을 조정해보니 내가 원하는 모습이 나왔다.
실제로 클릭 이벤트가 일어난 좌표와 Point 컴포넌트 위치의 간극이 약간 존재한다.
이번 개인 프로젝트를 하면서, 기능 구현을 빠르게 완성하기 위해 지피티를 적극 사용하고 있다.
그런데 어려움을 마주했을때 스스로 문제를 해결하기 위해 고민하는 시간이 너무 짧다.
답을 너무 쉽게 찾고 있다. 라는 위기의식이 새삼스럽게 느껴진다.
설령 이게 정답이라고 할지라도, 정말 이것뿐일까? 다른 방법은 없을까? 이것보다 구리더라도 내 수준에서 구현할 수 있는 방법이 있지 않을까? 그것을 찾아보는 과정이 또한 학습이기 때문에 필요한 과정이라고 생각한다.
'개발개발 > LifeGraph_인생그래프' 카테고리의 다른 글
[인생 그래프] 이미지 저장하기 (html2canvas + file-saver) (0) | 2025.02.11 |
---|---|
[인생 그래프] 클릭 이벤트 핸들러 뜯어보기 (0) | 2025.02.11 |
[인생 그래프] 클릭 데이터 전역상태로 관리하기 (0) | 2025.02.10 |
[인생 그래프] 클릭한 위치에 컴포넌트 추가하기 (0) | 2025.02.10 |
[인생 그래프] <canvas>로 그래프 그리기 (1) | 2025.02.10 |