quadraticCurveTo(cp1x, cp1y, x, y)
- cp1x 및 cp1y로 지정된 제어점을 사용하여 현재 펜의 위치에서 x와 y로 지정된 끝점까지 이차 베지어 곡선을 그립니다.
quadraticCurveTo(cp1x, cp1y, x, y)에서
cp1x, cp1y 로 위 그림의 Control Point를 지정하고,
x,y 로 Ending Point를 지정할 수 있다.
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
- (cp1x, cp1y) 및 (cp2x, cp2y)로 지정된 제어점을 사용하여 현재 펜 위치에서 x 및 y로 지정된 끝점까지 삼차 베지어 곡선을 그립니다.
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)에서
cp1x, cp1y, cp2x, cp2y 를 그림의 각 Control Point와 같이 곡선 지점을 설정 해 줄 수 있다.
먼저, 하고자 하는 목표는 데이터를 사용해 형태에 맞게 곡선그래프를 Canvas에 그려보는 것이다.
📝 문제
bezierCurveTo()
를 사용.🔍 이유
💡 해결방안
ctx.beginPath(); // 경로 생성
Ctx.moveTo(0,0); // 경로 시작점 정의
ctx.bezierCurveTo(10, 100, 30, 100, 40, 0);
ctx.stroke(); // 실제 그리기
(이미지1)
그림에서의 상단 두개의 점이 각각 cp1x,cp1y / cp2x,cp2y 를 지정한다.
(실제로는 보다 상단에 위치)
초록색 점은 끝지점인 40,0(x,y)이다.
여기서 실제 y-Point를 100
으로 지정했지만, 실제 곡선의 최정상 지점은 100
이 아니다.
(이미지2)
위 이미지를 보면 곡선은 bezierCurveTo(10, 100, 30, 100, 40, 0);
으로 지정했지만 옆에 직선 lineTo(40, 100);
과의 높이차이가 보인다.
즉, 실제 데이터가 표시한 지점까지 정확하게 표시하기 위해서는 베지어곡선의 cp1y
/ cp2y
를 실제 위치보다 높게 표시해야 될 것으로 판단된다.
두개의 베지어 곡선을 이어 그려본다.
ctx.beginPath();
ctx.moveTo(0, 100);
ctx.bezierCurveTo(10, 200, 30, 200, 40, 100);
ctx.bezierCurveTo(50, 0, 70, 0, 80, 100);
ctx.stroke();
(이미지3)
여기서 알 수 있는 점은 최상단지점과 최하단 지점에 대해, 두개의 곡선을 단순 이어 붙이기를 하면 의도와는 다르게 표현 될 수 있다.
(이미지4)
위 이미지와 같이 곡선 그릴시 두번째 지점까지 설정을 해야하는데 여기서 두번째 지점에 대해 다시 곡선을 그리면 이미지3 처럼 될 수도 있기에 이미지4 에서 1 과 2 사이의 중간지점을 곡선 bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
의 x,y로 설정하면 해결 될수 있다고 본다.
앞서 말했듯이 데이터의 모든 포인트를 표시하기보다 전체적인 상승 하강의 표현을 목적으로 하기에 데이터를 가공할 필요가 있다.
📝 문제
- 여러 데이터에서 상승 및 하강 포인트 도출
🔍 이유
- 전체적인 흐름을 표시하기 위함
- 예를 들어, y값이 10단위로 상승된다면 각각의 곡선을 그리기에는 불필요하다고 생각되기때문이다.
💡 해결방안
- 배열을 통해 데이터를 입력받고 상승/하강의 흐름이 바뀌기 전까지 곡선포인트를 받지 않는다.
- 예를 들어,
[10,20,40,20,60,100]
의 배열이 있다면 40까지 지속적으로 상승하고 이후에 하락함으로, 40을 새로운 배열에 넣고, 20 또한 하강의 시작점이기도 하지만 끝점이기도 하기 때문에 20을 넣는다. 결과적으로[40,20,100]
의 배열을 생성하여 이를 토대로 그래프를 그린다.
const seperatePoint = (): number[] => {
// 여러개의 점을 유연하게 표시하기 위해서 상승점과 하강점만을 추출
const point: number[] = []; // 결과점 배열
let temp = arr[0]; // 비교 값
let dir = 'up'; // 이전까지의 흐름이 상승인지, 하강인지 표시 (시작 상승)
for (let i = 1; i < arr.length; i += 1) {
if (temp > arr[i]) {
// 이전값이 arr[i]보다 클 때
if (dir === 'up') {
// 흐름은 상승이지만 하강으로의 변곡점이 나왔기에
point.push(temp); // 최고상승점을 point배열에 저장
dir = 'down'; // 하강 흐름으로 변경
}
temp = arr[i]; // 비굣값 temp에 할당
}
if (temp < arr[i]) {
// 아전값이 arr[i]보다 작을 때
if (dir === 'down') {
// 흐름이 하강이면 (이전까지는 상승)
point.push(temp); // 최저 하강점을 point배열에 저장
dir = 'up'; // 상승 흐름으로 변경
}
temp = arr[i]; // 비굣값 temp에 할당
}
if (temp === arr[i]) {
// 값이 같다면 비굣값만 변경
temp = arr[i];
}
if (i + 1 === arr.length) {
// 다음 인덱스가 없다면 마지막값 point배열에 저장
point.push(temp);
}
}
return point;
};
(point[n]+point[n+1]) / 2
//x 값은 시작지점 = 0, cp1x = 10, cp2x = 30, x = 40 설정(곡선점은 = 30)
//cpy 값은 +20 (이미지2에 의한 대체 수)
// y = (point[n]+point[n+1]) / 2
ctx.bezierCurveTo(x, y + 20, x + 20, y + 20, x + 30, (point[n]+point[n+1]) / 2);
const drawCurve = (
ctx: CanvasRenderingContext2D,
x: number,
y: number,
z: number,
bool: boolean
) => {
if (bool) {
ctx.bezierCurveTo(x, y + 20, x + 20, y + 20, x + 30, z);
// 상승곡선
} else {
ctx.bezierCurveTo(x, y - 20, x + 20, y - 20, x + 30, z);
// 하강곡선
}
};
const curve = () => {
const ctx = ref.current?.getContext('2d');
if (ctx) {
ctx.beginPath();
ctx.moveTo(0, 0);
if (ctx !== undefined && ctx !== null) {
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
}
const point = seperatePoint(); // 여러점들에서 고점, 저점 point만을 모은 배열
let xPoint = 10; // 초기 x값
for (let i = 0; i < point.length; i += 1) {
const yPoint = point[i]; // 현재 포인트
const nextYPoint = point[i + 1]; // 다음 포인트 (중간점 찾기위해)
if (i % 2 === 0) {
// 상승곡선 그리기
if (i + 1 === point.length) {
// 마지막 요소이라면
drawCurve(ctx, xPoint, yPoint, yPoint, true);
} else {
drawCurve(ctx, xPoint, yPoint, (yPoint + nextYPoint) / 2, true);
}
} else if (i % 2 === 1) {
// 하강곡선 그리기
if (i + 1 === point.length) {
// 마지막 요소이라면
drawCurve(ctx, xPoint, yPoint, yPoint, false);
} else {
drawCurve(ctx, xPoint, yPoint, (yPoint + nextYPoint) / 2, false);
}
}
xPoint += 40;
// 다음 곡선의 시작포인트 설정
}
ctx.stroke();
}
};
[10, 20, 30, 60, 40, 20, 60, 80, 60, 100]
데이터에 대한 그래프를 그려본다.⭕️ 정상적으로 그래프가 그려진것을 확인 할 수 있다.
🛠️ 다만, 몇가지 수정점이 필요하다.
- 예시로 사용한 y점의 +20이 아닌 데이터에 따라 정확한 수치로의 표시가 가능하도록 해야한다.
- 상승흐름이 길어짐에 따라 x(곡선의 넓이)도 넓게 표시하도록 해야한다.
- 곡선 그래프가 그려지는 것에 애니메이션을 적용한다.
다음 Canvas 포스팅에는 애니메이션 적용에 대해 작성하도록 하겠다.