PolygonPoint.jsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { useState } from "react";
  2. import { Circle, Rect } from "react-konva";
  3. import { observer } from "mobx-react";
  4. import { getParent, hasParent, types } from "mobx-state-tree";
  5. import { guidGenerator } from "../core/Helpers";
  6. import { useRegionStyles } from "../hooks/useRegionColor";
  7. import { AnnotationMixin } from "../mixins/AnnotationMixin";
  8. const PolygonPointModel = types
  9. .model("PolygonPoint", {
  10. id: types.optional(types.identifier, guidGenerator),
  11. x: types.number,
  12. y: types.number,
  13. index: types.number,
  14. style: "circle",
  15. size: "small",
  16. })
  17. .volatile(() => ({
  18. selected: false,
  19. }))
  20. .views((self) => ({
  21. get parent() {
  22. if (!hasParent(self, 2)) return null;
  23. return getParent(self, 2);
  24. },
  25. get stage() {
  26. return self.parent?.parent;
  27. },
  28. get canvasX() {
  29. return self.stage?.internalToCanvasX(self.x);
  30. },
  31. get canvasY() {
  32. return self.stage?.internalToCanvasY(self.y);
  33. },
  34. }))
  35. .actions((self) => ({
  36. /**
  37. * External function for Polygon Parent
  38. * @param {number} x
  39. * @param {number} y
  40. */
  41. movePoint(offsetX, offsetY) {
  42. const dx = self.stage.canvasToInternalX(offsetX);
  43. const dy = self.stage.canvasToInternalY(offsetY);
  44. self.x = self.x + dx;
  45. self.y = self.y + dy;
  46. },
  47. _setPos(x, y) {
  48. self.x = x;
  49. self.y = y;
  50. },
  51. _movePoint(canvasX, canvasY) {
  52. const point = self.parent.control?.getSnappedPoint({
  53. x: self.stage.canvasToInternalX(canvasX),
  54. y: self.stage.canvasToInternalY(canvasY),
  55. });
  56. self._setPos(point.x, point.y);
  57. },
  58. /**
  59. * Close polygon
  60. * @param {*} ev
  61. */
  62. closeStartPoint() {
  63. if (self.annotation.isReadOnly()) return;
  64. if (self.parent.closed) return;
  65. if (self.parent.mouseOverStartPoint) {
  66. self.parent.closePoly();
  67. }
  68. },
  69. handleMouseOverStartPoint(ev) {
  70. ev.cancelBubble = true;
  71. const stage = self.stage?.stageRef;
  72. if (!stage) return;
  73. stage.container().style.cursor = "crosshair";
  74. /**
  75. * Check if polygon > 2 points and closed point
  76. */
  77. if (self.parent.closed || self.parent.points.length < 3) return;
  78. const startPoint = ev.target;
  79. if (self.style === "rectangle") {
  80. startPoint.setX(startPoint.x() - startPoint.width() / 2);
  81. startPoint.setY(startPoint.y() - startPoint.height() / 2);
  82. }
  83. const scaleMap = {
  84. small: 2,
  85. medium: 3,
  86. large: 4,
  87. };
  88. const scale = scaleMap[self.size];
  89. startPoint.scale({
  90. x: scale / self.stage.zoomScale,
  91. y: scale / self.stage.zoomScale,
  92. });
  93. self.parent.setMouseOverStartPoint(true);
  94. },
  95. handleMouseOutStartPoint(ev) {
  96. const t = ev.target;
  97. const stage = self.stage?.stageRef;
  98. if (!stage) return;
  99. stage.container().style.cursor = "default";
  100. if (self.style === "rectangle") {
  101. t.setX(t.x() + t.width() / 2);
  102. t.setY(t.y() + t.height() / 2);
  103. }
  104. t.scale({
  105. x: 1 / self.stage.zoomScale,
  106. y: 1 / self.stage.zoomScale,
  107. });
  108. self.parent.setMouseOverStartPoint(false);
  109. },
  110. getSkipInteractions() {
  111. return self.parent.control.obj.getSkipInteractions();
  112. },
  113. }));
  114. const PolygonPoint = types.compose("PolygonPoint", AnnotationMixin, PolygonPointModel);
  115. const PolygonPointView = observer(({ item, name }) => {
  116. if (!item.parent) return;
  117. const [draggable, setDraggable] = useState(true);
  118. const regionStyles = useRegionStyles(item.parent);
  119. const sizes = {
  120. small: 4,
  121. medium: 8,
  122. large: 12,
  123. };
  124. const stroke = {
  125. small: 1,
  126. medium: 2,
  127. large: 3,
  128. };
  129. const w = sizes[item.size];
  130. const startPointAttr =
  131. item.index === 0
  132. ? {
  133. hitStrokeWidth: 12,
  134. fill: regionStyles.strokeColor || item.primary,
  135. onMouseOver: item.handleMouseOverStartPoint,
  136. onMouseOut: item.handleMouseOutStartPoint,
  137. }
  138. : null;
  139. const dragOpts = {
  140. onDragMove: (e) => {
  141. if (item.getSkipInteractions()) return false;
  142. if (e.target !== e.currentTarget) return;
  143. const shape = e.target;
  144. let { x, y } = shape.attrs;
  145. if (x < 0) x = 0;
  146. if (y < 0) y = 0;
  147. if (x > item.stage.stageWidth) x = item.stage.stageWidth;
  148. if (y > item.stage.stageHeight) y = item.stage.stageHeight;
  149. item._movePoint(x, y);
  150. shape.setAttr("x", item.canvasX);
  151. shape.setAttr("y", item.canvasY);
  152. },
  153. onDragStart: () => {
  154. if (item.getSkipInteractions()) {
  155. setDraggable(false);
  156. return false;
  157. }
  158. item.annotation.history.freeze();
  159. },
  160. onDragEnd: (e) => {
  161. setDraggable(true);
  162. item.annotation.history.unfreeze();
  163. e.cancelBubble = true;
  164. },
  165. onMouseOver: (e) => {
  166. e.cancelBubble = true;
  167. const stage = item.stage?.stageRef;
  168. if (!stage) return;
  169. stage.container().style.cursor = "crosshair";
  170. },
  171. onMouseOut: () => {
  172. const stage = item.stage?.stageRef;
  173. if (!stage) return;
  174. stage.container().style.cursor = "default";
  175. },
  176. onTransformEnd(e) {
  177. if (e.target !== e.currentTarget) return;
  178. const t = e.target;
  179. t.setAttr("x", 0);
  180. t.setAttr("y", 0);
  181. t.setAttr("scaleX", 1);
  182. t.setAttr("scaleY", 1);
  183. },
  184. };
  185. const fill = item.selected ? "green" : "white";
  186. if (item.style === "circle") {
  187. return (
  188. <Circle
  189. key={name}
  190. name={name}
  191. x={item.canvasX}
  192. y={item.canvasY}
  193. radius={w}
  194. fill={fill}
  195. stroke="black"
  196. strokeWidth={stroke[item.size]}
  197. dragOnTop={false}
  198. strokeScaleEnabled={false}
  199. perfectDrawEnabled={false}
  200. shadowForStrokeEnabled={false}
  201. scaleX={1 / (item.stage.zoomScale || 1)}
  202. scaleY={1 / (item.stage.zoomScale || 1)}
  203. onDblClick={() => {
  204. item.parent.deletePoint(item);
  205. }}
  206. onClick={(ev) => {
  207. if (ev.evt.altKey) return item.parent.deletePoint(item);
  208. if (item.parent.isDrawing && item.parent.points.length === 1) return;
  209. // don't unselect polygon on point click
  210. ev.evt.preventDefault();
  211. ev.cancelBubble = true;
  212. if (item.parent.mouseOverStartPoint) {
  213. item.closeStartPoint();
  214. item.parent.notifyDrawingFinished();
  215. } else {
  216. item.parent.setSelectedPoint(item);
  217. }
  218. }}
  219. {...dragOpts}
  220. {...startPointAttr}
  221. draggable={!item.parent.isReadOnly() && draggable}
  222. />
  223. );
  224. }
  225. return (
  226. <Rect
  227. name={name}
  228. key={name}
  229. x={item.x - w / 2}
  230. y={item.y - w / 2}
  231. width={w}
  232. height={w}
  233. fill={fill}
  234. stroke="black"
  235. strokeWidth={stroke[item.size]}
  236. strokeScaleEnabled={false}
  237. perfectDrawEnabled={false}
  238. shadowForStrokeEnabled={false}
  239. dragOnTop={false}
  240. {...dragOpts}
  241. {...startPointAttr}
  242. draggable={!item.parent.isReadOnly()}
  243. />
  244. );
  245. });
  246. export { PolygonPoint, PolygonPointView };