lost-sia
Version:
Single Image Annotation Tool
389 lines (359 loc) • 9.55 kB
JSX
import React, { Component } from "react";
import Edge from "./Edge";
import Node from "./Node";
import InfSelectionArea from "./InfSelectionArea";
import * as transform from "../utils/transform";
import * as canvasActions from "../types/canvasActions";
import "./Annotation.scss";
import * as modes from "../types/modes";
import * as mouse from "../utils/mouse";
class Polygon extends Component {
/*************
* LIFECYCLE *
**************/
constructor(props) {
super(props);
this.state = {
anno: undefined,
};
}
componentDidMount() {
if (this.props.anno.mode === modes.CREATE) {
const data = this.props.anno.data[0];
const newAnno = {
...this.props.anno,
data: [
{ x: data.x, y: data.y },
{ x: data.x + 1, y: data.y },
],
selectedNode: 1,
};
this.setState({
anno: newAnno,
});
} else {
this.setState({ anno: { ...this.props.anno } });
}
}
componentDidUpdate(prevProps) {
if (prevProps.anno !== this.props.anno) {
this.setState({ anno: { ...this.props.anno } });
}
}
/**************
* ANNO EVENTS *
***************/
onMouseMove(e) {
switch (this.state.anno.mode) {
case modes.MOVE:
this.move(
e.movementX / this.props.svg.scale,
e.movementY / this.props.svg.scale,
);
break;
default:
break;
}
}
onMouseUp(e) {
switch (this.state.anno.mode) {
case modes.MOVE:
if (e.button === 0) {
this.performedAction(this.state.anno, canvasActions.ANNO_MOVED);
this.requestModeChange(this.state.anno, modes.VIEW);
}
break;
default:
break;
}
}
onMouseDown(e) {
switch (this.state.anno.mode) {
case modes.VIEW:
if (e.button === 0) {
if (this.props.isSelected) {
this.requestModeChange(this.state.anno, modes.MOVE);
}
}
break;
default:
break;
}
}
/**************
* NODE EVENTS *
***************/
onNodeMouseUp(e, idx) {
switch (this.state.anno.mode) {
case modes.EDIT:
this.performedAction(this.state.anno, canvasActions.ANNO_EDITED);
this.requestModeChange(this.state.anno, modes.VIEW);
break;
default:
break;
}
}
onNodeMouseDown(e, idx) {
switch (this.state.anno.mode) {
case modes.CREATE:
if (e.button === 2) {
let newAnnoData = [...this.state.anno.data];
newAnnoData.push({
x: newAnnoData[idx].x,
y: newAnnoData[idx].y,
});
const newAnno = {
...this.state.anno,
data: newAnnoData,
selectedNode: this.state.anno.selectedNode + 1,
};
this.setState({
anno: newAnno,
});
this.performedAction(newAnno, canvasActions.ANNO_CREATED_NODE);
}
break;
case modes.VIEW:
if (e.button === 0) {
this.requestModeChange(
{
...this.state.anno,
selectedNode: idx,
},
modes.EDIT,
);
}
break;
default:
break;
}
}
onNodeMouseMove(e, idx) {
switch (this.state.anno.mode) {
case modes.CREATE:
this.updateAnnoByMousePos(e, idx);
break;
case modes.EDIT:
e.stopPropagation();
this.updateAnnoByMousePos(e, idx);
break;
default:
break;
}
}
onNodeDoubleClick(e, idx) {
switch (this.state.anno.mode) {
case modes.CREATE:
this.performedAction(
this.state.anno,
canvasActions.ANNO_CREATED_FINAL_NODE,
);
break;
default:
break;
}
}
/**************
* EDGE EVENTS *
***************/
onEdgeMouseDown(e, idx) {
switch (this.state.anno.mode) {
case modes.ADD:
this.addNode(e, idx);
break;
case modes.VIEW:
if (e.button === 0) {
this.requestModeChange(this.state.anno, modes.MOVE);
}
break;
default:
break;
}
}
/*************
* LOGIC *
*************/
performedAction(anno, pAction) {
if (this.props.onAction) {
this.props.onAction(anno, pAction);
}
}
toPolygonStr(data) {
return data
.map((e) => {
return `${e.x},${e.y}`;
})
.join(" ");
}
requestModeChange(anno, mode) {
this.props.onModeChangeRequest(anno, mode);
}
move(movementX, movementY) {
this.setState({
anno: {
...this.state.anno,
data: transform.move(this.state.anno.data, movementX, movementY),
},
});
}
addNode(e, idx) {
const mPos = mouse.getMousePosition(e, this.props.svg);
let newAnnoData = this.state.anno.data.slice(0, idx);
newAnnoData.push(mPos);
const oldRest = this.state.anno.data.slice(idx);
const newAnno = {
...this.state.anno,
data: newAnnoData.concat(oldRest),
};
this.setState({ anno: newAnno });
this.performedAction(newAnno, canvasActions.ANNO_ADDED_NODE);
}
removeLastNode() {
const newAnno = {
...this.state.anno,
data: [...this.state.anno.data.slice(0, this.state.anno.data.length - 1)],
selectedNode: this.state.anno.selectedNode - 1,
};
this.setState({ anno: newAnno });
this.performedAction(newAnno, canvasActions.ANNO_REMOVED_NODE);
}
updateAnnoByMousePos(e, idx) {
const mousePos = mouse.getMousePosition(e, this.props.svg);
let newAnnoData = [...this.state.anno.data];
newAnnoData[idx].x = mousePos.x;
newAnnoData[idx].y = mousePos.y;
this.setState({
anno: {
...this.state.anno,
data: newAnnoData,
},
});
}
getResult() {
return this.state.anno;
}
/*************
* RENDERING *
**************/
renderNodes() {
if (!this.props.isSelected) return null;
switch (this.state.anno.mode) {
case modes.MOVE:
return null;
case modes.EDIT:
case modes.CREATE:
return (
<Node
anno={this.state.anno.data}
idx={this.state.anno.selectedNode}
key={this.state.anno.selectedNode}
style={this.props.style}
className={this.props.className}
isSelected={this.props.isSelected}
mode={this.state.anno.mode}
svg={this.props.svg}
onMouseDown={(e, idx) => this.onNodeMouseDown(e, idx)}
onMouseUp={(e, idx) => this.onNodeMouseUp(e, idx)}
onDoubleClick={(e, idx) => this.onNodeDoubleClick(e, idx)}
onMouseMove={(e, idx) => this.onNodeMouseMove(e, idx)}
/>
);
default:
return this.state.anno.data.map((e, idx) => {
return (
<Node
anno={this.state.anno.data}
idx={idx}
key={idx}
style={this.props.style}
className={this.props.className}
isSelected={this.props.isSelected}
mode={this.state.anno.mode}
svg={this.props.svg}
onMouseDown={(e, idx) => this.onNodeMouseDown(e, idx)}
onMouseUp={(e, idx) => this.onNodeMouseUp(e, idx)}
onDoubleClick={(e, idx) => this.onNodeDoubleClick(e, idx)}
onMouseMove={(e, idx) => this.onNodeMouseMove(e, idx)}
/>
);
});
}
}
renderEdges() {
if (!this.props.isSelected) return null;
switch (this.state.anno.mode) {
case modes.VIEW:
case modes.ADD:
let edges = this.state.anno.data.map((e, idx) => {
return (
<Edge
anno={this.state.anno.data}
idx={idx}
key={idx}
style={this.props.style}
className={this.props.className}
isSelected={this.props.isSelected}
onMouseDown={(e, idx) => {
this.onEdgeMouseDown(e, idx);
}}
/>
);
});
edges.push(
<Edge
anno={this.state.anno.data}
closingEdge={true}
key={edges.length}
idx={0}
style={this.props.style}
className={this.props.className}
isSelected={this.props.isSelected}
onMouseDown={(e, idx) => {
this.onEdgeMouseDown(e, idx);
}}
/>,
);
return edges;
default:
return null;
}
}
renderPolygon() {
return (
<polygon
points={this.toPolygonStr(this.state.anno.data)}
fill="none"
stroke="purple"
style={this.props.style}
className={this.props.className}
/>
);
}
renderInfSelectionArea() {
switch (this.state.anno.mode) {
case modes.MOVE:
return <InfSelectionArea enable={true} svg={this.props.svg} />;
default:
return null;
}
}
render() {
if (this.state.anno) {
return (
<g
onMouseMove={(e) => this.onMouseMove(e)}
onMouseUp={(e) => this.onMouseUp(e)}
onMouseDown={(e) => this.onMouseDown(e)}
>
{this.renderPolygon()}
{this.renderEdges()}
{this.renderNodes()}
{this.renderInfSelectionArea()}
</g>
);
} else {
return <g></g>;
}
}
}
export default Polygon;