Merge branch 'fe/feat/102-canvas-polygon' into 'fe/develop'

Feat: 캔버스 폴리곤 이동 기능 구현 - S11P21S002-102

See merge request s11-s-project/S11P21S002!34
This commit is contained in:
홍창기 2024-09-02 16:54:41 +09:00
commit 9a67b5ace1
4 changed files with 159 additions and 63 deletions

View File

@ -0,0 +1,41 @@
import { Label } from '@/types';
import Konva from 'konva';
import { useRef, useState } from 'react';
import { Group, Line } from 'react-konva';
import PolygonTransformer from './PolygonTransformer';
export default function LabelPolygon({
isSelected,
onSelect,
info,
}: {
isSelected: boolean;
onSelect: () => void;
info: Label;
}) {
const polyRef = useRef<Konva.Line>(null);
const [coordinates, setCoordinates] = useState<Array<[number, number]>>(info.coordinates);
return (
<Group zIndex={isSelected ? 9999 : 1}>
<Line
points={coordinates.flat()}
stroke={info.color}
strokeWidth={1}
ref={polyRef}
onMouseDown={onSelect}
onTouchStart={onSelect}
strokeScaleEnabled={false}
fillAfterStrokeEnabled={false}
fill={`${info.color}33`}
closed
/>
{isSelected && (
<PolygonTransformer
coordinates={coordinates}
setCoordinates={setCoordinates}
/>
)}
</Group>
);
}

View File

@ -1,7 +1,7 @@
import { Label } from '@/types';
import Konva from 'konva';
import { useEffect, useRef } from 'react';
import { Line, Transformer } from 'react-konva';
import { Group, Line, Transformer } from 'react-konva';
export default function LabelRect({
isSelected,
@ -14,12 +14,16 @@ export default function LabelRect({
}) {
const rectRef = useRef<Konva.Line>(null);
const trRef = useRef<Konva.Transformer>(null);
const coordinates = [
info.coordinates[0],
[info.coordinates[0][0], info.coordinates[1][1]],
info.coordinates[1],
[info.coordinates[1][0], info.coordinates[0][1]],
].flat();
const handleTransformEnd = () => {
const points = rectRef.current?.points();
if (points) {
console.log(points);
console.log(trRef.current?.getAbsoluteScale());
}
console.log(points);
};
useEffect(() => {
@ -30,9 +34,9 @@ export default function LabelRect({
}, [isSelected]);
return (
<>
<Group zIndex={isSelected ? 9999 : 1}>
<Line
points={info.coordinates.flat()}
points={coordinates}
stroke={info.color}
strokeWidth={1}
ref={rectRef}
@ -57,6 +61,6 @@ export default function LabelRect({
onTransformEnd={handleTransformEnd}
/>
)}
</>
</Group>
);
}

View File

@ -0,0 +1,64 @@
import Konva from 'konva';
import { Circle, Line } from 'react-konva';
interface PolygonTransformerProps {
coordinates: Array<[number, number]>;
setCoordinates: (coordinates: Array<[number, number]>) => void;
}
// TODO: scale 상관 없이 고정된 크기로 표시되도록 수정
export default function PolygonTransformer({ coordinates, setCoordinates }: PolygonTransformerProps) {
const handleDragMove = (index: number) => (e: Konva.KonvaEventObject<DragEvent>) => {
const circle = e.target as Konva.Circle;
const stage = circle.getStage();
const pos = circle.position();
const newCoordinates = [...coordinates];
newCoordinates[index] = [pos.x, pos.y];
setCoordinates(newCoordinates);
stage?.batchDraw();
};
const handleMouseOver = (e: Konva.KonvaEventObject<MouseEvent>) => {
const circle = e.target as Konva.Circle;
const stage = circle.getStage();
circle.strokeWidth(2);
circle.radius(15);
stage?.batchDraw();
};
const handleMouseOut = (e: Konva.KonvaEventObject<MouseEvent>) => {
const circle = e.target as Konva.Circle;
const stage = circle.getStage();
circle.strokeWidth(1);
circle.radius(10);
stage?.batchDraw();
};
return (
<>
<Line
points={coordinates.flat()}
stroke="#00a1ff"
strokeWidth={2}
closed
zIndex={1}
/>
{coordinates.map((point, index) => {
return (
<Circle
key={index}
x={point[0]}
y={point[1]}
radius={10}
stroke="#00a1ff"
strokeWidth={1}
fill="white"
draggable
onDragMove={handleDragMove(index)}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
/>
);
})}
</>
);
}

View File

@ -6,51 +6,34 @@ import useImage from 'use-image';
import { Label } from '@/types';
import LabelRect from './LabelRect';
import { Vector2d } from 'konva/lib/types';
import LabelPolygon from './LabelPolygon';
const mockLabels: Label[] = [
{
id: 1,
name: 'Label 1',
color: '#FFaa33',
type: 'rect',
coordinates: [
[100, 100],
[200, 100],
[200, 200],
[100, 200],
],
},
{
id: 2,
name: 'Label 3',
color: '#aa33ff',
type: 'rect',
coordinates: [
[1200, 200],
[1200, 400],
[1400, 400],
[1400, 200],
],
},
{
id: 3,
name: 'Label 3',
color: '#aaff33',
type: 'polygon',
coordinates: [
[500, 375],
[523, 232],
[600, 232],
[535, 175],
[560, 100],
[500, 150],
[440, 100],
[465, 175],
[400, 232],
[477, 232],
],
},
];
const mockLabels: Label[] = Array.from({ length: 10 }, (_, i) => {
const startX = Math.random() * 1200 + 300;
const startY = Math.random() * 2000 + 300;
const color = Math.floor(Math.random() * 65535)
.toString(16)
.padStart(4, '0');
return {
id: i,
name: `label-${i}`,
type: i % 2 === 0 ? 'polygon' : 'rect',
color: i % 2 === 0 ? `#ff${color}` : `#${color}ff`,
coordinates:
i % 2 === 0
? [
[startX, startY],
[startX + 200, startY + 50],
[startX + 300, startY + 300],
[startX + 100, startY + 250],
]
: [
[startX, startY],
[startX + 300, startY + 300],
],
};
});
export default function ImageCanvas() {
const stageWidth = window.innerWidth;
@ -106,8 +89,6 @@ export default function ImageCanvas() {
const heightRatio = stageHeight / image!.height;
scale.current = Math.min(widthRatio, heightRatio);
console.log(scale);
return { x: scale.current, y: scale.current };
};
@ -118,11 +99,11 @@ export default function ImageCanvas() {
return imageStatus === 'loaded' ? (
<Stage
className="overflow-hidden bg-gray-200"
ref={stageRef}
width={stageWidth}
height={stageHeight}
className="overflow-hidden bg-gray-200"
draggable
ref={stageRef}
onWheel={handleWheel}
onMouseDown={handleClick}
onTouchStart={handleClick}
@ -131,19 +112,25 @@ export default function ImageCanvas() {
<Layer>
<Image image={image} />
</Layer>
{labels.map((label) => (
<Layer key={label.id}>
{label.type === 'rect' ? (
<Layer>
{labels.map((label) =>
label.type === 'rect' ? (
<LabelRect
key={label.id}
isSelected={label.id === selectedId}
onSelect={() => setSelectedId(label.id)}
info={label}
/>
) : (
<></>
)}
</Layer>
))}
<LabelPolygon
key={label.id}
isSelected={label.id === selectedId}
onSelect={() => setSelectedId(label.id)}
info={label}
/>
)
)}
</Layer>
</Stage>
) : (
<div></div>