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:
commit
9a67b5ace1
41
frontend/src/components/ImageCanvas/LabelPolygon.tsx
Normal file
41
frontend/src/components/ImageCanvas/LabelPolygon.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { Label } from '@/types';
|
import { Label } from '@/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { Line, Transformer } from 'react-konva';
|
import { Group, Line, Transformer } from 'react-konva';
|
||||||
|
|
||||||
export default function LabelRect({
|
export default function LabelRect({
|
||||||
isSelected,
|
isSelected,
|
||||||
@ -14,12 +14,16 @@ export default function LabelRect({
|
|||||||
}) {
|
}) {
|
||||||
const rectRef = useRef<Konva.Line>(null);
|
const rectRef = useRef<Konva.Line>(null);
|
||||||
const trRef = useRef<Konva.Transformer>(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 handleTransformEnd = () => {
|
||||||
const points = rectRef.current?.points();
|
const points = rectRef.current?.points();
|
||||||
if (points) {
|
|
||||||
console.log(points);
|
console.log(points);
|
||||||
console.log(trRef.current?.getAbsoluteScale());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -30,9 +34,9 @@ export default function LabelRect({
|
|||||||
}, [isSelected]);
|
}, [isSelected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Group zIndex={isSelected ? 9999 : 1}>
|
||||||
<Line
|
<Line
|
||||||
points={info.coordinates.flat()}
|
points={coordinates}
|
||||||
stroke={info.color}
|
stroke={info.color}
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
ref={rectRef}
|
ref={rectRef}
|
||||||
@ -57,6 +61,6 @@ export default function LabelRect({
|
|||||||
onTransformEnd={handleTransformEnd}
|
onTransformEnd={handleTransformEnd}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
64
frontend/src/components/ImageCanvas/PolygonTransformer.tsx
Normal file
64
frontend/src/components/ImageCanvas/PolygonTransformer.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -6,51 +6,34 @@ import useImage from 'use-image';
|
|||||||
import { Label } from '@/types';
|
import { Label } from '@/types';
|
||||||
import LabelRect from './LabelRect';
|
import LabelRect from './LabelRect';
|
||||||
import { Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
|
import LabelPolygon from './LabelPolygon';
|
||||||
|
|
||||||
const mockLabels: Label[] = [
|
const mockLabels: Label[] = Array.from({ length: 10 }, (_, i) => {
|
||||||
{
|
const startX = Math.random() * 1200 + 300;
|
||||||
id: 1,
|
const startY = Math.random() * 2000 + 300;
|
||||||
name: 'Label 1',
|
const color = Math.floor(Math.random() * 65535)
|
||||||
color: '#FFaa33',
|
.toString(16)
|
||||||
type: 'rect',
|
.padStart(4, '0');
|
||||||
coordinates: [
|
|
||||||
[100, 100],
|
return {
|
||||||
[200, 100],
|
id: i,
|
||||||
[200, 200],
|
name: `label-${i}`,
|
||||||
[100, 200],
|
type: i % 2 === 0 ? 'polygon' : 'rect',
|
||||||
],
|
color: i % 2 === 0 ? `#ff${color}` : `#${color}ff`,
|
||||||
},
|
coordinates:
|
||||||
{
|
i % 2 === 0
|
||||||
id: 2,
|
? [
|
||||||
name: 'Label 3',
|
[startX, startY],
|
||||||
color: '#aa33ff',
|
[startX + 200, startY + 50],
|
||||||
type: 'rect',
|
[startX + 300, startY + 300],
|
||||||
coordinates: [
|
[startX + 100, startY + 250],
|
||||||
[1200, 200],
|
]
|
||||||
[1200, 400],
|
: [
|
||||||
[1400, 400],
|
[startX, startY],
|
||||||
[1400, 200],
|
[startX + 300, startY + 300],
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
{
|
});
|
||||||
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],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function ImageCanvas() {
|
export default function ImageCanvas() {
|
||||||
const stageWidth = window.innerWidth;
|
const stageWidth = window.innerWidth;
|
||||||
@ -106,8 +89,6 @@ export default function ImageCanvas() {
|
|||||||
const heightRatio = stageHeight / image!.height;
|
const heightRatio = stageHeight / image!.height;
|
||||||
scale.current = Math.min(widthRatio, heightRatio);
|
scale.current = Math.min(widthRatio, heightRatio);
|
||||||
|
|
||||||
console.log(scale);
|
|
||||||
|
|
||||||
return { x: scale.current, y: scale.current };
|
return { x: scale.current, y: scale.current };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,11 +99,11 @@ export default function ImageCanvas() {
|
|||||||
|
|
||||||
return imageStatus === 'loaded' ? (
|
return imageStatus === 'loaded' ? (
|
||||||
<Stage
|
<Stage
|
||||||
className="overflow-hidden bg-gray-200"
|
ref={stageRef}
|
||||||
width={stageWidth}
|
width={stageWidth}
|
||||||
height={stageHeight}
|
height={stageHeight}
|
||||||
|
className="overflow-hidden bg-gray-200"
|
||||||
draggable
|
draggable
|
||||||
ref={stageRef}
|
|
||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
onMouseDown={handleClick}
|
onMouseDown={handleClick}
|
||||||
onTouchStart={handleClick}
|
onTouchStart={handleClick}
|
||||||
@ -131,19 +112,25 @@ export default function ImageCanvas() {
|
|||||||
<Layer>
|
<Layer>
|
||||||
<Image image={image} />
|
<Image image={image} />
|
||||||
</Layer>
|
</Layer>
|
||||||
{labels.map((label) => (
|
<Layer>
|
||||||
<Layer key={label.id}>
|
{labels.map((label) =>
|
||||||
{label.type === 'rect' ? (
|
label.type === 'rect' ? (
|
||||||
<LabelRect
|
<LabelRect
|
||||||
|
key={label.id}
|
||||||
isSelected={label.id === selectedId}
|
isSelected={label.id === selectedId}
|
||||||
onSelect={() => setSelectedId(label.id)}
|
onSelect={() => setSelectedId(label.id)}
|
||||||
info={label}
|
info={label}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<LabelPolygon
|
||||||
)}
|
key={label.id}
|
||||||
</Layer>
|
isSelected={label.id === selectedId}
|
||||||
))}
|
onSelect={() => setSelectedId(label.id)}
|
||||||
|
info={label}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Layer>
|
||||||
</Stage>
|
</Stage>
|
||||||
) : (
|
) : (
|
||||||
<div></div>
|
<div></div>
|
||||||
|
Loading…
Reference in New Issue
Block a user