import { canvasState, fontFamilyState, fontSizeState } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' import { getDirectionByPoint } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' export const usePolygon = () => { const canvas = useRecoilValue(canvasState) const isFlowDisplay = useRecoilValue(flowDisplaySelector) const fontSize = useRecoilValue(fontSizeState) const fontFamily = useRecoilValue(fontFamilyState) const addPolygon = (points, options) => { const polygon = new QPolygon(points, { ...options, fill: options.fill || 'transparent', stroke: options.stroke || '#000000', fontSize: fontSize, fontFamily: fontFamily, selectable: true, }) canvas?.add(polygon) return polygon } const addPolygonByLines = (lines, options) => { //lines의 idx를 정렬한다. lines.sort((a, b) => a.idx - b.idx) const points = createPolygonPointsFromOuterLines(lines) return addPolygon(points, { ...options, }) } const addLengthText = (polygon) => { const points = polygon.get('points') points.forEach((start, i) => { const end = points[(i + 1) % points.length] const dx = end.x - start.x const dy = end.y - start.y const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1)) * 10 const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2) const degree = (Math.atan2(dy, dx) * 180) / Math.PI // Create new text object if it doesn't exist const text = new fabric.Text(length.toString(), { left: midPoint.x, top: midPoint.y, fontSize: fontSize, fontFamily: fontFamily, parentId: polygon.id, minX: Math.min(start.x, end.x), maxX: Math.max(start.x, end.x), minY: Math.min(start.y, end.y), maxY: Math.max(start.y, end.y), parentDirection: getDirectionByPoint(start, end), parentDegree: degree, dirty: true, editable: true, selectable: true, lockRotation: true, lockScalingX: true, lockScalingY: true, idx: i, name: 'lengthText', parent: this, }) // this.texts.push(text) canvas.add(text) }) canvas.renderAll() } const createPolygonPointsFromOuterLines = (outerLines) => { if (!outerLines || outerLines.length === 0) { return [] } // Extract points from outerLines return outerLines.map((line) => ({ x: line.x1, y: line.y1, })) } const removePolygon = (polygon) => { const texts = canvas.getObjects().filter((obj) => obj.parentId === polygon.id) texts.forEach((text) => { canvas.remove(text) }) canvas.remove(polygon) canvas.renderAll() } /** * poolygon의 방향에 따라 화살표를 추가한다. * @param polygon */ const drawDirectionArrow = (polygon) => { const direction = polygon.direction if (!direction) { return } polygon.canvas .getObjects() .filter((obj) => obj.name === 'directionText' && obj.parent === polygon.arrow) .forEach((obj) => polygon.canvas.remove(obj)) let arrow = null let points = [] if (polygon.arrow) { polygon.canvas.remove(polygon.arrow) } let centerPoint = { x: polygon.left, y: polygon.top } let stickeyPoint const polygonMaxX = Math.max(...polygon.getCurrentPoints().map((point) => point.x)) const polygonMinX = Math.min(...polygon.getCurrentPoints().map((point) => point.x)) const polygonMaxY = Math.max(...polygon.getCurrentPoints().map((point) => point.y)) const polygonMinY = Math.min(...polygon.getCurrentPoints().map((point) => point.y)) switch (direction) { case 'north': points = [ { x: centerPoint.x, y: polygonMinY - 50 }, { x: centerPoint.x + 20, y: polygonMinY - 50 }, { x: centerPoint.x + 20, y: polygonMinY - 80 }, { x: centerPoint.x + 50, y: polygonMinY - 80 }, { x: centerPoint.x, y: polygonMinY - 110 }, { x: centerPoint.x - 50, y: polygonMinY - 80 }, { x: centerPoint.x - 20, y: polygonMinY - 80 }, { x: centerPoint.x - 20, y: polygonMinY - 50 }, ] stickeyPoint = { x: centerPoint.x, y: polygonMinY - 110 } break case 'south': points = [ { x: centerPoint.x, y: polygonMaxY + 50 }, { x: centerPoint.x + 20, y: polygonMaxY + 50 }, { x: centerPoint.x + 20, y: polygonMaxY + 80 }, { x: centerPoint.x + 50, y: polygonMaxY + 80 }, { x: centerPoint.x, y: polygonMaxY + 110 }, { x: centerPoint.x - 50, y: polygonMaxY + 80 }, { x: centerPoint.x - 20, y: polygonMaxY + 80 }, { x: centerPoint.x - 20, y: polygonMaxY + 50 }, ] stickeyPoint = { x: centerPoint.x, y: polygonMaxY + 110 } break case 'west': points = [ { x: polygonMinX - 50, y: centerPoint.y }, { x: polygonMinX - 50, y: centerPoint.y + 20 }, { x: polygonMinX - 80, y: centerPoint.y + 20 }, { x: polygonMinX - 80, y: centerPoint.y + 50 }, { x: polygonMinX - 110, y: centerPoint.y }, { x: polygonMinX - 80, y: centerPoint.y - 50 }, { x: polygonMinX - 80, y: centerPoint.y - 20 }, { x: polygonMinX - 50, y: centerPoint.y - 20 }, ] stickeyPoint = { x: polygonMinX - 110, y: centerPoint.y } break case 'east': points = [ { x: polygonMaxX + 50, y: centerPoint.y }, { x: polygonMaxX + 50, y: centerPoint.y + 20 }, { x: polygonMaxX + 80, y: centerPoint.y + 20 }, { x: polygonMaxX + 80, y: centerPoint.y + 50 }, { x: polygonMaxX + 110, y: centerPoint.y }, { x: polygonMaxX + 80, y: centerPoint.y - 50 }, { x: polygonMaxX + 80, y: centerPoint.y - 20 }, { x: polygonMaxX + 50, y: centerPoint.y - 20 }, ] stickeyPoint = { x: polygonMaxX + 110, y: centerPoint.y } break } arrow = new fabric.Polygon(points, { selectable: false, name: 'arrow', fill: 'transparent', stroke: 'black', direction: direction, parent: polygon, stickeyPoint: stickeyPoint, visible: isFlowDisplay, }) polygon.arrow = arrow polygon.canvas.add(arrow) polygon.canvas.renderAll() drawDirectionStringToArrow(polygon.canvas, 0) } /** * 방향을 나타낸 화살표에 각도에 따라 글씨 추가 * @param canvas * @param compass */ const drawDirectionStringToArrow = (canvas, compass = 0) => { const arrows = canvas?.getObjects().filter((obj) => obj.name === 'arrow') if (arrows.length === 0) { return } const eastArrows = arrows.filter((arrow) => arrow.direction === 'east') const westArrows = arrows.filter((arrow) => arrow.direction === 'west') const northArrows = arrows.filter((arrow) => arrow.direction === 'north') const southArrows = arrows.filter((arrow) => arrow.direction === 'south') let southText = '南' let eastText = '東' let westText = '西' let northText = '北' if (compass === 0 || compass === 360) { // 남,동,서 가능 // 그대로 } else if (compass < 45) { //남(남남동),동(동북동),서(서남서) 가능 //북(북북서) southText = '南南東' eastText = '東北東' westText = '西南西' northText = '北北西' } else if (compass === 45) { // 남, 서 가능 // 남(남동) // 서(남서) // 북(북서) // 동(북동) southText = '南東' westText = '南西' northText = '北西' eastText = '北東' } else if (compass < 90) { // 북(서북서) // 동 (북북동) // 남(동남동) // 서(남남서) northText = '北西北' eastText = '北北東' southText = '東南東' westText = '南南西' } else if (compass === 90) { // 동(북) // 서(남) // 남(동) // 북(서) eastText = '北' westText = '南' southText = '東' northText = '西' } else if (compass < 135) { // 남,서,북 가능 // 동(북북서) // 서(남남동) // 남(동북동) // 북(서남서) eastText = '北北西' westText = '南南東' southText = '東北東' northText = '西南西' } else if (compass === 135) { // 서,북 가능 // 서(남동) // 북(남서) // 남(북동) // 동(북서) westText = '南東' northText = '南西' southText = '北東' eastText = '北西' } else if (compass < 180) { // 북,동,서 가능 // 북(남남서) // 동(서북서) // 남(북북동) // 서(동남동) northText = '南南西' eastText = '西北西' southText = '北北東' westText = '東南東' } else if (compass === 180) { // 북,동,서 가능 // 북(남) // 동(서) // 남(북) // 서(동) northText = '南' eastText = '西' southText = '北' westText = '東' } else if (compass < 225) { // 서,북,동 가능 // 북(남남동) // 동(서남서) // 남(북북서) // 서(동남동) northText = '南南東' eastText = '西南西' southText = '北北西' westText = '東南東' } else if (compass === 225) { // 북,동 가능 // 북(남동) // 동(남서) // 남(북서) // 서(북동) northText = '南東' eastText = '南西' southText = '北西' westText = '北東' } else if (compass < 270) { // 북동남 가능 // 북(동남동) // 동(남남서) // 남(서북서) // 서(북북동) northText = '東南東' eastText = '南南西' southText = '西北西' westText = '北北東' } else if (compass === 270) { // 북동남 가능 // 북(동) // 동(남) // 남(서) // 서(북) northText = '東' eastText = '南' southText = '西' westText = '北' } else if (compass < 315) { // 북,동,남 가능 // 북(동북동) // 동(남남동) // 남(서남서) // 서(북북서) northText = '東北東' eastText = '南南東' southText = '西南西' westText = '北北西' } else if (compass === 315) { // 동,남 가능 // 북(북동) // 동(남동) // 남(남서) // 서(북서) northText = '北東' eastText = '南東' southText = '南西' westText = '北西' } else if (compass < 360) { // 남,동,서 가능 // 북(북북동) // 동(동남동) // 남(남남서) // 서(서북서) northText = '北北東' eastText = '東南東' southText = '南南西' westText = '西北西' } clearDirectionText(canvas) addTextByArrows(eastArrows, eastText, canvas) addTextByArrows(westArrows, westText, canvas) addTextByArrows(northArrows, northText, canvas) addTextByArrows(southArrows, southText, canvas) } const clearDirectionText = (canvas) => { const texts = canvas.getObjects().filter((obj) => obj.name === 'directionText') texts.forEach((text) => { canvas.remove(text) }) } const addTextByArrows = (arrows, txt, canvas) => { arrows.forEach((arrow, index) => { const text = new fabric.Text(`${txt}${index + 1}`, { fontSize: arrow.parent.fontSize, fill: 'black', originX: 'center', originY: 'center', name: 'directionText', selectable: false, left: arrow.stickeyPoint.x, top: arrow.stickeyPoint.y, parent: arrow, visible: isFlowDisplay, }) canvas.add(text) }) } return { addPolygon, addPolygonByLines, removePolygon, drawDirectionArrow, } }