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 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>
|
||||
);
|
||||
}
|
||||
|
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 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>
|
||||
|
Loading…
Reference in New Issue
Block a user