@plait/draw
Version:
Implementation of the core logic of the flowchart drawing tool plugin.
1,242 lines (1,225 loc) • 428 kB
JavaScript
import { ACTIVE_STROKE_WIDTH, DEFAULT_COLOR, ThemeColorMode, PlaitElement, RectangleClient, getSelectedElements, idCreator, createDebugGenerator, Point, createG, arrowPoints, createPath, distanceBetweenPointAndPoint, drawLinearPath, rotate, catmullRomFitting, PlaitBoard, setStrokeLinecap, findElements, createMask, createRect, getNearestPointBetweenPointAndArc, setPathStrokeLinecap, distanceBetweenPointAndSegments, HIT_DISTANCE_BUFFER, isPointInPolygon, isLineHitRectangle, rotatePointsByAngle, rotateAntiPointsByElement, getEllipseArcCenter, Transforms, clearSelectedElement, addSelectedElement, BoardTransforms, PlaitPointerType, depthFirstRecursion, getIsRecursionFunc, SNAPPING_STROKE_WIDTH, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, getI18nValue, SELECTION_RECTANGLE_CLASS_NAME, toActivePointFromViewBoxPoint, toActiveRectangleFromViewBoxRectangle, drawRectangle, isSelectionMoving, rgbaToHEX, getElementById, rotatePointsByElement, PlaitNode, hasValidAngle, toViewBoxPoint, toHostPoint, Direction, rotatePoints, getSelectionAngle, rotatedDataPoints, isAxisChangedByAngle, getRectangleByElements, getRectangleByAngle, getSnapRectangles, getTripleAxis, getMinPointDelta, SNAP_TOLERANCE, drawPointSnapLines, drawSolidLines, getNearestPointBetweenPointAndSegments, getEllipseTangentSlope, getVectorFromPointAndSlope, getNearestPointBetweenPointAndEllipse, isPointInEllipse, isPointInRoundRectangle, drawRoundRectangle, getCrossingPointsBetweenEllipseAndSegment, drawLine, getNearestPointBetweenPointAndDiscreteSegments, getNearestPointBetweenPointAndSegment, Path, getHitElementByPoint, WritableClipboardOperationType, WritableClipboardType, addOrCreateClipboardContext, setAngleForG, toActivePoint, temporaryDisableSelection, toScreenPointFromActivePoint, PRESS_AND_MOVE_BUFFER, CursorClass, isHorizontalDirection, isMainPointer, throttleRAF, getAngleBetweenPoints, normalizeAngle, degreesToRadians, rotateElements, MERGING, ROTATE_HANDLE_CLASS_NAME, isSelectedElement, isDragging } from '@plait/core';
import { DEFAULT_FILL, Alignment, WithTextPluginKey, TextManage, getMemorizedLatest, memorizeLatest, removeDuplicatePoints, generateElbowLineRoute, simplifyOrthogonalPoints, getExtendPoint, getUnitVectorByPointAndPoint, Generator, getElementSize, DEFAULT_FONT_FAMILY, getPointByVectorComponent, getStrokeLineDash, StrokeStyle, getPointOnPolyline, buildText, getCrossingPointsBetweenPointAndSegment, isPointOnSegment, getFirstTextEditor, sortElementsByArea, isFilled, getTextEditorsByElement, RESIZE_HANDLE_DIAMETER, drawPrimaryHandle, drawFillPrimaryHandle, PRIMARY_COLOR, measureElement, getFirstTextManage, isSourceAndTargetIntersect, getPoints, DEFAULT_ROUTE_MARGIN, normalizeShapePoints, resetPointsAfterResize, getDirectionByVector, getRectangleResizeHandleRefs, getRotatedResizeCursorClassByAngle, ROTATE_HANDLE_SIZE, ROTATE_HANDLE_DISTANCE_TO_ELEMENT, isCornerHandle, getIndexByResizeHandle, withResize, getSymmetricHandleIndex, getResizeHandlePointByIndex, drawHandle, getDirectionFactorByDirectionComponent, buildClipboardData as buildClipboardData$1, insertClipboardData as insertClipboardData$1, getDirectionByPointOfRectangle, getDirectionFactor, rotateVector, getOppositeDirection, rotateVectorAnti90, getSourceAndTargetOuterRectangle, getNextPoint, CommonElementFlavour, createActiveGenerator, hasResizeHandle, ActiveGenerator, isVirtualKey, isDelete, isSpaceHotkey, isDndMode, isDrawingMode, getElementsText, acceptImageTypes, getElementOfFocusedImage, buildImage, isResizingByCondition, getRatioByPoint, getTextManages, ImageGenerator, getDirectionByIndex, moveXOfPoint, getXDistanceBetweenPoint, ResizeHandle, addRotating, removeRotating, drawRotateHandle } from '@plait/common';
import { pointsOnBezierCurves } from 'points-on-curve';
import { DEFAULT_FONT_SIZE, AlignEditor } from '@plait/text-plugins';
import { Editor, Node } from 'slate';
import { isKeyHotkey } from 'is-hotkey';
var BasicShapes;
(function (BasicShapes) {
BasicShapes["rectangle"] = "rectangle";
BasicShapes["ellipse"] = "ellipse";
BasicShapes["diamond"] = "diamond";
BasicShapes["roundRectangle"] = "roundRectangle";
BasicShapes["parallelogram"] = "parallelogram";
BasicShapes["text"] = "text";
BasicShapes["triangle"] = "triangle";
BasicShapes["leftArrow"] = "leftArrow";
BasicShapes["trapezoid"] = "trapezoid";
BasicShapes["rightArrow"] = "rightArrow";
BasicShapes["cross"] = "cross";
BasicShapes["star"] = "star";
BasicShapes["pentagon"] = "pentagon";
BasicShapes["hexagon"] = "hexagon";
BasicShapes["octagon"] = "octagon";
BasicShapes["pentagonArrow"] = "pentagonArrow";
BasicShapes["processArrow"] = "processArrow";
BasicShapes["twoWayArrow"] = "twoWayArrow";
BasicShapes["comment"] = "comment";
BasicShapes["roundComment"] = "roundComment";
BasicShapes["cloud"] = "cloud";
})(BasicShapes || (BasicShapes = {}));
var FlowchartSymbols;
(function (FlowchartSymbols) {
FlowchartSymbols["process"] = "process";
FlowchartSymbols["decision"] = "decision";
FlowchartSymbols["data"] = "data";
FlowchartSymbols["connector"] = "connector";
FlowchartSymbols["terminal"] = "terminal";
FlowchartSymbols["manualInput"] = "manualInput";
FlowchartSymbols["preparation"] = "preparation";
FlowchartSymbols["manualLoop"] = "manualLoop";
FlowchartSymbols["merge"] = "merge";
FlowchartSymbols["delay"] = "delay";
FlowchartSymbols["storedData"] = "storedData";
FlowchartSymbols["or"] = "or";
FlowchartSymbols["summingJunction"] = "summingJunction";
FlowchartSymbols["predefinedProcess"] = "predefinedProcess";
FlowchartSymbols["offPage"] = "offPage";
FlowchartSymbols["document"] = "document";
FlowchartSymbols["multiDocument"] = "multiDocument";
FlowchartSymbols["database"] = "database";
FlowchartSymbols["hardDisk"] = "hardDisk";
FlowchartSymbols["internalStorage"] = "internalStorage";
FlowchartSymbols["noteCurlyRight"] = "noteCurlyRight";
FlowchartSymbols["noteCurlyLeft"] = "noteCurlyLeft";
FlowchartSymbols["noteSquare"] = "noteSquare";
FlowchartSymbols["display"] = "display";
})(FlowchartSymbols || (FlowchartSymbols = {}));
var UMLSymbols;
(function (UMLSymbols) {
UMLSymbols["actor"] = "actor";
UMLSymbols["useCase"] = "useCase";
UMLSymbols["container"] = "container";
UMLSymbols["note"] = "note";
UMLSymbols["simpleClass"] = "simpleClass";
UMLSymbols["activityClass"] = "activityClass";
UMLSymbols["branchMerge"] = "branchMerge";
UMLSymbols["port"] = "port";
UMLSymbols["package"] = "package";
UMLSymbols["combinedFragment"] = "combinedFragment";
UMLSymbols["class"] = "class";
UMLSymbols["interface"] = "interface";
UMLSymbols["object"] = "object";
UMLSymbols["component"] = "component";
UMLSymbols["componentBox"] = "componentBox";
UMLSymbols["template"] = "template";
UMLSymbols["activation"] = "activation";
UMLSymbols["deletion"] = "deletion";
UMLSymbols["assembly"] = "assembly";
UMLSymbols["providedInterface"] = "providedInterface";
UMLSymbols["requiredInterface"] = "requiredInterface";
})(UMLSymbols || (UMLSymbols = {}));
var GeometryCommonTextKeys;
(function (GeometryCommonTextKeys) {
GeometryCommonTextKeys["name"] = "name";
GeometryCommonTextKeys["content"] = "content";
})(GeometryCommonTextKeys || (GeometryCommonTextKeys = {}));
const PlaitGeometry = {};
var SwimlaneSymbols;
(function (SwimlaneSymbols) {
SwimlaneSymbols["swimlaneVertical"] = "swimlaneVertical";
SwimlaneSymbols["swimlaneHorizontal"] = "swimlaneHorizontal";
})(SwimlaneSymbols || (SwimlaneSymbols = {}));
var SwimlaneDrawSymbols;
(function (SwimlaneDrawSymbols) {
SwimlaneDrawSymbols["swimlaneVertical"] = "swimlaneVertical";
SwimlaneDrawSymbols["swimlaneHorizontal"] = "swimlaneHorizontal";
SwimlaneDrawSymbols["swimlaneVerticalWithHeader"] = "swimlaneVerticalWithHeader";
SwimlaneDrawSymbols["swimlaneHorizontalWithHeader"] = "swimlaneHorizontalWithHeader";
})(SwimlaneDrawSymbols || (SwimlaneDrawSymbols = {}));
var TableSymbols;
(function (TableSymbols) {
TableSymbols["table"] = "table";
})(TableSymbols || (TableSymbols = {}));
const PlaitTableElement = {
isTable: (value) => {
return value.type === 'table';
},
isVerticalText: (value) => {
return value.text?.direction === 'vertical';
}
};
const WithDrawPluginKey = 'plait-draw-plugin-key';
var DrawI18nKey;
(function (DrawI18nKey) {
DrawI18nKey["lineText"] = "line-text";
DrawI18nKey["geometryText"] = "geometry-text";
})(DrawI18nKey || (DrawI18nKey = {}));
const ShapeDefaultSpace = {
rectangleAndText: 4
};
const DefaultDrawStyle = {
strokeWidth: 2,
defaultRadius: 4,
strokeColor: '#000',
fill: DEFAULT_FILL
};
const DefaultDrawActiveStyle = {
strokeWidth: ACTIVE_STROKE_WIDTH,
selectionStrokeWidth: ACTIVE_STROKE_WIDTH
};
const DefaultBasicShapeProperty = {
width: 100,
height: 100,
strokeColor: DEFAULT_COLOR,
strokeWidth: 2
};
const DefaultPentagonArrowProperty = {
width: 120,
height: 50
};
const DefaultTwoWayArrowProperty = {
width: 138,
height: 80
};
const DefaultArrowProperty = {
width: 100,
height: 80
};
const DefaultCloudProperty = {
width: 120,
height: 100
};
const DefaultTextProperty = {
width: 36,
height: 20,
text: '文本'
};
const GeometryThreshold = {
defaultTextMaxWidth: 34 * 14
};
const DefaultConnectorProperty = {
width: 44,
height: 44
};
const DefaultFlowchartProperty = {
width: 120,
height: 60
};
const DefaultDataBaseProperty = {
width: 70,
height: 80
};
const DefaultInternalStorageProperty = {
width: 80,
height: 80
};
const DefaultDecisionProperty = {
width: 140,
height: 70
};
const DefaultDataProperty = {
width: 124,
height: 60
};
const DefaultDocumentProperty = {
width: 120,
height: 70
};
const DefaultNoteProperty = {
width: 160,
height: 100
};
const DefaultMultiDocumentProperty = {
width: 120,
height: 80
};
const DefaultManualInputProperty = {
width: 117,
height: 59
};
const DefaultMergeProperty = {
width: 47,
height: 33
};
const DefaultActorProperty = {
width: 68,
height: 100
};
const DefaultContainerProperty = {
width: 300,
height: 240
};
const DefaultPackageProperty = {
width: 210,
height: 150,
texts: [
{
id: GeometryCommonTextKeys.name,
text: '包名',
align: Alignment.left
},
{
id: GeometryCommonTextKeys.content,
text: '',
align: Alignment.left
}
]
};
const DefaultActivationProperty = {
width: 18,
height: 80
};
const DefaultObjectProperty = {
width: 120,
height: 60
};
const DefaultComponentBoxProperty = {
width: 200,
height: 150
};
const DefaultDeletionProperty = {
width: 40,
height: 40
};
const DefaultPortProperty = {
width: 20,
height: 20
};
const DefaultRequiredInterfaceProperty = {
width: 70,
height: 56
};
const DefaultAssemblyProperty = {
width: 120,
height: 56
};
const DefaultProvidedInterfaceProperty = {
width: 70,
height: 34
};
const DefaultCombinedFragmentProperty = {
width: 400,
height: 280,
texts: [
{
id: GeometryCommonTextKeys.name,
text: 'Opt | Alt | Loop',
align: Alignment.left
},
{
id: GeometryCommonTextKeys.content,
text: '[Condition]',
align: Alignment.left
}
]
};
const DefaultClassProperty = {
width: 230,
height: 180,
texts: [
{ text: 'Class', align: Alignment.center },
{
text: '+ attribute1:type defaultValue\n+ attribute2:type\n- attribute3:type',
align: Alignment.left
},
{
text: '+ operation1(params):returnType\n- operation2(params)\n- operation3()',
align: Alignment.left
}
]
};
const DefaultInterfaceProperty = {
width: 230,
height: 140,
texts: [
{ text: '<<interface>>\nInterface', align: Alignment.center },
{
text: '+ operation1(params):returnType\n- operation2(params)\n- operation3()',
align: Alignment.left
}
]
};
const DefaultBasicShapePropertyMap = {
[BasicShapes.pentagonArrow]: DefaultPentagonArrowProperty,
[BasicShapes.processArrow]: DefaultPentagonArrowProperty,
[BasicShapes.cloud]: DefaultCloudProperty,
[BasicShapes.twoWayArrow]: DefaultTwoWayArrowProperty,
[BasicShapes.leftArrow]: DefaultArrowProperty,
[BasicShapes.rightArrow]: DefaultArrowProperty
};
const DefaultFlowchartPropertyMap = {
[FlowchartSymbols.connector]: DefaultConnectorProperty,
[FlowchartSymbols.process]: DefaultFlowchartProperty,
[FlowchartSymbols.decision]: DefaultDecisionProperty,
[FlowchartSymbols.data]: DefaultDataProperty,
[FlowchartSymbols.terminal]: DefaultFlowchartProperty,
[FlowchartSymbols.manualInput]: DefaultManualInputProperty,
[FlowchartSymbols.preparation]: DefaultFlowchartProperty,
[FlowchartSymbols.manualLoop]: DefaultFlowchartProperty,
[FlowchartSymbols.merge]: DefaultMergeProperty,
[FlowchartSymbols.delay]: DefaultFlowchartProperty,
[FlowchartSymbols.storedData]: DefaultFlowchartProperty,
[FlowchartSymbols.or]: DefaultConnectorProperty,
[FlowchartSymbols.summingJunction]: DefaultConnectorProperty,
[FlowchartSymbols.predefinedProcess]: DefaultFlowchartProperty,
[FlowchartSymbols.offPage]: DefaultFlowchartProperty,
[FlowchartSymbols.document]: DefaultDocumentProperty,
[FlowchartSymbols.multiDocument]: DefaultMultiDocumentProperty,
[FlowchartSymbols.database]: DefaultDataBaseProperty,
[FlowchartSymbols.hardDisk]: DefaultFlowchartProperty,
[FlowchartSymbols.internalStorage]: DefaultInternalStorageProperty,
[FlowchartSymbols.noteCurlyLeft]: DefaultNoteProperty,
[FlowchartSymbols.noteCurlyRight]: DefaultNoteProperty,
[FlowchartSymbols.noteSquare]: DefaultNoteProperty,
[FlowchartSymbols.display]: DefaultFlowchartProperty
};
const DefaultUMLPropertyMap = {
[UMLSymbols.actor]: DefaultActorProperty,
[UMLSymbols.useCase]: DefaultDocumentProperty,
[UMLSymbols.container]: DefaultContainerProperty,
[UMLSymbols.note]: DefaultObjectProperty,
[UMLSymbols.package]: DefaultPackageProperty,
[UMLSymbols.combinedFragment]: DefaultCombinedFragmentProperty,
[UMLSymbols.class]: DefaultClassProperty,
[UMLSymbols.interface]: DefaultInterfaceProperty,
[UMLSymbols.activation]: DefaultActivationProperty,
[UMLSymbols.object]: DefaultObjectProperty,
[UMLSymbols.deletion]: DefaultDeletionProperty,
[UMLSymbols.activityClass]: DefaultObjectProperty,
[UMLSymbols.simpleClass]: DefaultObjectProperty,
[UMLSymbols.component]: DefaultMultiDocumentProperty,
[UMLSymbols.template]: DefaultMultiDocumentProperty,
[UMLSymbols.componentBox]: DefaultComponentBoxProperty,
[UMLSymbols.port]: DefaultPortProperty,
[UMLSymbols.branchMerge]: DefaultDeletionProperty,
[UMLSymbols.assembly]: DefaultAssemblyProperty,
[UMLSymbols.providedInterface]: DefaultProvidedInterfaceProperty,
[UMLSymbols.requiredInterface]: DefaultRequiredInterfaceProperty
};
const MultipleTextGeometryTextKeys = {
[UMLSymbols.package]: Object.keys(GeometryCommonTextKeys),
[UMLSymbols.combinedFragment]: Object.keys(GeometryCommonTextKeys)
};
const LINE_HIT_GEOMETRY_BUFFER = 4;
const LINE_SNAPPING_BUFFER = 4;
const LINE_SNAPPING_CONNECTOR_BUFFER = 4;
const LINE_ALIGN_TOLERANCE = 4;
const GEOMETRY_WITHOUT_TEXT = [
FlowchartSymbols.or,
FlowchartSymbols.summingJunction,
UMLSymbols.activation,
UMLSymbols.deletion,
UMLSymbols.port,
UMLSymbols.branchMerge,
UMLSymbols.assembly,
UMLSymbols.providedInterface,
UMLSymbols.requiredInterface
];
const GEOMETRY_WITH_MULTIPLE_TEXT = [UMLSymbols.package, UMLSymbols.combinedFragment];
const GEOMETRY_NOT_CLOSED = [
FlowchartSymbols.noteCurlyLeft,
FlowchartSymbols.noteCurlyRight,
FlowchartSymbols.noteSquare,
UMLSymbols.requiredInterface,
UMLSymbols.deletion
];
const getGeometryPointers = () => {
return [...Object.keys(BasicShapes), ...Object.keys(FlowchartSymbols), ...Object.keys(UMLSymbols)];
};
const getSwimlanePointers = () => {
return Object.keys(SwimlaneDrawSymbols);
};
const getSwimlaneShapes = () => {
return Object.keys(SwimlaneSymbols);
};
const getBasicPointers = () => {
return Object.keys(BasicShapes);
};
const getFlowchartPointers = () => {
return Object.keys(FlowchartSymbols);
};
const getUMLPointers = () => {
return Object.keys(UMLSymbols);
};
const getArrowLinePointers = () => {
return Object.keys(ArrowLineShape);
};
const getVectorLinePointers = () => {
return Object.keys(VectorLinePointerType);
};
const DEFAULT_IMAGE_WIDTH = 1000;
const DrawThemeColors = {
[ThemeColorMode.default]: {
strokeColor: DEFAULT_COLOR,
fill: '#FFFFFF'
},
[ThemeColorMode.colorful]: {
strokeColor: '#06ADBF',
fill: '#CDEFF2'
},
[ThemeColorMode.soft]: {
strokeColor: '#6D89C1',
fill: '#DADFEB'
},
[ThemeColorMode.retro]: {
strokeColor: '#E9C358',
fill: '#F6EDCF'
},
[ThemeColorMode.dark]: {
strokeColor: '#FFFFFF',
fill: '#434343'
},
[ThemeColorMode.starry]: {
strokeColor: '#42ABE5',
fill: '#163F5A'
}
};
const SWIMLANE_HEADER_SIZE = 42;
const DefaultSwimlaneVerticalWithHeaderProperty = {
width: 580,
height: 524
};
const DefaultSwimlaneHorizontalWithHeaderProperty = {
width: 524,
height: 580
};
const DefaultSwimlaneVerticalProperty = {
width: 580,
height: 524
};
const DefaultSwimlaneHorizontalProperty = {
width: 524,
height: 580
};
const DefaultSwimlanePropertyMap = {
[SwimlaneDrawSymbols.swimlaneHorizontal]: DefaultSwimlaneHorizontalProperty,
[SwimlaneDrawSymbols.swimlaneVertical]: DefaultSwimlaneVerticalProperty,
[SwimlaneDrawSymbols.swimlaneHorizontalWithHeader]: DefaultSwimlaneHorizontalWithHeaderProperty,
[SwimlaneDrawSymbols.swimlaneVerticalWithHeader]: DefaultSwimlaneVerticalWithHeaderProperty
};
const MIN_TEXT_WIDTH = 5;
const DefaultLineStyle = {
strokeWidth: 2,
strokeColor: '#000'
};
const LINE_TEXT_SPACE = 4;
const LINE_AUTO_COMPLETE_DIAMETER = 6;
const LINE_AUTO_COMPLETE_OPACITY = 1;
const LINE_AUTO_COMPLETE_HOVERED_OPACITY = 1;
const LINE_AUTO_COMPLETE_HOVERED_DIAMETER = 12;
const LINE_TEXT = '文本';
// TODO: 是否可以完全基于位置定位 TextManager,实现 line 和 多文本 geometry 统一
// 一个元素有多个文本时,单纯通过位置无法获取 TextManage,因此这里单独通过 Map 保存关键字 key 和 TextManage 的对应关系
// 1. 单文本元素 key 就是元素的 id
// 2. 表格元素 key 是单元格的 id
// 3. 符合 isMultipleTextGeometry 的元素,key 是元素 id + text.id (通常不是 id 而是文本位置的常量)
// 4. arrow-line 和 vector-line 文本不依赖于 text.generator,基于 text 可以直接找到 TextManage
const KEY_TO_TEXT_MANAGE = new WeakMap();
const setTextManage = (board, element, text, textManage) => {
const textManages = KEY_TO_TEXT_MANAGE.get(board);
return KEY_TO_TEXT_MANAGE.set(board, { ...textManages, [getTextKey(element, text)]: textManage });
};
const getTextManage = (board, element, text) => {
const textManages = KEY_TO_TEXT_MANAGE.get(board);
return textManages[getTextKey(element, text)];
};
const deleteTextManage = (board, key) => {
const textManages = KEY_TO_TEXT_MANAGE.get(board);
delete textManages[key];
KEY_TO_TEXT_MANAGE.set(board, textManages);
};
class TextGenerator {
get shape() {
return this.element.shape || this.element.type;
}
constructor(board, element, texts, options) {
this.board = board;
this.texts = texts;
this.element = element;
this.options = options;
}
initialize() {
const textPlugins = (this.board.getPluginOptions(WithTextPluginKey) || {})
.textPlugins;
this.textManages = this.texts.map(text => {
const textManage = this.createTextManage(text, textPlugins);
setTextManage(this.board, this.element, text, textManage);
return textManage;
});
const ref = PlaitElement.getElementRef(this.element);
ref.initializeTextManage(this.textManages);
}
draw(elementG) {
const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element));
this.texts.forEach(drawShapeText => {
const textManage = getTextManage(this.board, this.element, drawShapeText);
if (drawShapeText.text && textManage) {
textManage.draw(drawShapeText.text);
elementG.append(textManage.g);
(this.element.angle || this.element.angle === 0) && textManage.updateAngle(centerPoint, this.element.angle);
}
});
}
update(element, previousDrawShapeTexts, currentDrawShapeTexts, elementG) {
this.element = element;
const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element));
const textPlugins = (this.board.getPluginOptions(WithTextPluginKey) || {})
.textPlugins;
const removedTexts = previousDrawShapeTexts.filter(value => {
return !currentDrawShapeTexts.find(item => item.id === value.id);
});
if (removedTexts.length) {
removedTexts.forEach(item => {
const textManage = getTextManage(this.board, element, item);
const index = this.textManages.findIndex(value => value === textManage);
if (index > -1 && item.text) {
this.textManages.splice(index, 1);
}
textManage?.destroy();
deleteTextManage(this.board, item.id);
});
}
currentDrawShapeTexts.forEach(drawShapeText => {
if (drawShapeText.text) {
let textManage = getTextManage(this.board, this.element, drawShapeText);
if (!textManage) {
textManage = this.createTextManage(drawShapeText, textPlugins);
setTextManage(this.board, element, drawShapeText, textManage);
textManage.draw(drawShapeText.text);
elementG.append(textManage.g);
this.textManages.push(textManage);
}
else {
textManage.updateText(drawShapeText.text);
textManage.updateRectangle();
}
(this.element.angle || this.element.angle === 0) && textManage.updateAngle(centerPoint, this.element.angle);
}
});
}
createTextManage(text, textPlugins) {
const textManage = new TextManage(this.board, {
getRectangle: () => {
return this.getRectangle(text);
},
onChange: (data) => {
return this.options.onChange(this.element, data, text);
},
getMaxWidth: () => {
return this.getMaxWidth(text);
},
getRenderRectangle: () => {
return this.options.getRenderRectangle ? this.options.getRenderRectangle(this.element, text) : this.getRectangle(text);
},
textPlugins
});
return textManage;
}
getRectangle(text) {
const getRectangle = getEngine(this.shape).getTextRectangle;
if (getRectangle) {
return getRectangle(this.board, this.element, text);
}
return getTextRectangle$1(this.board, this.element);
}
getMaxWidth(text) {
return this.options.getMaxWidth ? this.options.getMaxWidth() : this.getRectangle(text).width;
}
destroy() {
const ref = PlaitElement.getElementRef(this.element);
ref.destroyTextManage();
this.textManages = [];
this.texts.forEach(item => {
deleteTextManage(this.board, item.id);
});
}
}
const isSingleSelectTable = (board) => {
const selectedElements = getSelectedElements(board);
return selectedElements && selectedElements.length === 1 && PlaitDrawElement.isElementByTable(selectedElements[0]);
};
const getSelectedTableElements = (board, elements) => {
const selectedElements = elements?.length ? elements : getSelectedElements(board);
return selectedElements.filter(value => PlaitDrawElement.isElementByTable(value));
};
const SELECTED_CELLS = new WeakMap();
function getSelectedCells(element) {
return SELECTED_CELLS.get(element);
}
function setSelectedCells(element, cells) {
return SELECTED_CELLS.set(element, cells);
}
function clearSelectedCells(element) {
return SELECTED_CELLS.delete(element);
}
function getCellsWithPoints(board, element) {
const table = board?.buildTable(element);
if (!table || !table.points || !table.columns || !table.rows) {
throw new Error('can not get table cells points');
}
const rectangle = RectangleClient.getRectangleByPoints(table.points);
const columnsCount = table.columns.length;
const rowsCount = table.rows.length;
const cellWidths = calculateCellsSize(table.columns, rectangle.width, columnsCount, true);
const cellHeights = calculateCellsSize(table.rows, rectangle.height, rowsCount, false);
const cells = table.cells.map(cell => {
const rowIdx = table.rows.findIndex(row => row.id === cell.rowId);
const columnIdx = table.columns.findIndex(column => column.id === cell.columnId);
let cellTopLeftX = rectangle.x;
for (let i = 0; i < columnIdx; i++) {
cellTopLeftX += cellWidths[i];
}
let cellTopLeftY = rectangle.y;
for (let i = 0; i < rowIdx; i++) {
cellTopLeftY += cellHeights[i];
}
const cellWidth = calculateCellSize(cell, cellWidths, columnIdx, true);
const cellBottomRightX = cellTopLeftX + cellWidth;
const cellHeight = calculateCellSize(cell, cellHeights, rowIdx, false);
const cellBottomRightY = cellTopLeftY + cellHeight;
return {
...cell,
points: [
[cellTopLeftX, cellTopLeftY],
[cellBottomRightX, cellBottomRightY]
]
};
});
return cells;
}
function getCellWithPoints(board, table, cellId) {
try {
const cells = getCellsWithPoints(board, table);
const cellIndex = cells && table.cells.findIndex(item => item.id === cellId);
return cells[cellIndex];
}
catch (error) {
throw new Error('can not get table cell points');
}
}
function calculateCellsSize(items, tableSize, count, isWidth) {
const cellSizes = [];
const sizeType = isWidth ? 'width' : 'height';
// The remaining size of the table excluding cells with already set sizes.
let totalSizeRemaining = tableSize;
items.forEach((item, index) => {
if (item[sizeType]) {
cellSizes[index] = item[sizeType];
totalSizeRemaining -= item[sizeType];
}
});
// Divide the remaining size equally.
const remainingItemCount = count - cellSizes.filter(item => !!item).length;
const remainingCellSize = remainingItemCount > 0 ? totalSizeRemaining / remainingItemCount : 0;
for (let i = 0; i < count; i++) {
if (!cellSizes[i]) {
cellSizes[i] = remainingCellSize;
}
}
return cellSizes;
}
function calculateCellSize(cell, sizes, index, isWidth) {
const span = isWidth ? cell.colspan || 1 : cell.rowspan || 1;
let size = 0;
for (let i = 0; i < span; i++) {
const cellIndex = index + i;
size += sizes[cellIndex];
}
return size;
}
function getHitCell(board, element, point) {
const table = board.buildTable(element);
const cells = getCellsWithPoints(board, table);
const rectangle = RectangleClient.getRectangleByPoints([point, point]);
const cell = cells.find(item => {
const cellRectangle = RectangleClient.getRectangleByPoints(item.points);
return RectangleClient.isHit(rectangle, cellRectangle);
});
if (cell) {
return table.cells.find(item => item.id === cell.id);
}
return null;
}
function editCell(board, cell) {
const textManage = getTextManageByCell(board, cell);
textManage && textManage.edit();
}
function getTextManageByCell(board, cell) {
return getTextManage(board, undefined, cell);
}
const updateColumns = (table, columnId, width, offset) => {
const columns = table.columns.map(item => (item.id === columnId ? { ...item, width } : item));
const points = [table.points[0], [table.points[1][0] + offset, table.points[1][1]]];
return { columns, points };
};
const updateRows = (table, rowId, height, offset) => {
const rows = table.rows.map(item => (item.id === rowId ? { ...item, height } : item));
const points = [table.points[0], [table.points[1][0], table.points[1][1] + offset]];
return { rows, points };
};
function updateCellIdsByRowOrColumn(cells, oldId, newId, type) {
const id = `${type}Id`;
cells.forEach(item => {
if (item[id] === oldId) {
item[id] = newId;
}
});
}
function updateRowOrColumnIds(element, type) {
element[`${type}s`].forEach(item => {
const newId = idCreator();
updateCellIdsByRowOrColumn(element.cells, item.id, newId, type);
item.id = newId;
});
}
function updateCellIds(cells) {
cells.forEach(item => {
const newId = idCreator();
item.id = newId;
});
}
function isCellIncludeText(cell) {
return cell.text;
}
function getCellsRectangle(board, element, cells) {
const cellsWithPoints = getCellsWithPoints(board, element);
const points = cells.map(cell => {
const cellWithPoints = cellsWithPoints.find(item => item.id === cell.id);
return cellWithPoints.points;
});
return RectangleClient.getRectangleByPoints(points);
}
const createCell = (rowId, columnId, text = null) => {
const cell = {
id: idCreator(),
rowId,
columnId
};
if (text !== null) {
cell['text'] = {
children: [{ text }],
align: Alignment.center
};
}
return cell;
};
const getSelectedTableCellsEditor = (board) => {
if (isSingleSelectTable(board)) {
const elements = getSelectedTableElements(board);
const selectedCells = getSelectedCells(elements[0]);
const selectedCellsEditor = selectedCells?.map(cell => {
const textManage = getTextManageByCell(board, cell);
return textManage?.editor;
});
if (selectedCellsEditor?.length) {
return selectedCellsEditor;
}
}
return undefined;
};
const SHAPE_MAX_LENGTH = 6;
const memorizedShape = new WeakMap();
const getMemorizeKey = (element) => {
let key = '';
switch (true) {
case PlaitDrawElement.isText(element): {
key = MemorizeKey.text;
break;
}
case PlaitDrawElement.isBasicShape(element): {
key = MemorizeKey.basicShape;
break;
}
case PlaitDrawElement.isFlowchart(element): {
key = MemorizeKey.flowchart;
break;
}
case PlaitDrawElement.isArrowLine(element): {
key = MemorizeKey.arrowLine;
break;
}
case PlaitDrawElement.isUML(element): {
key = MemorizeKey.UML;
}
}
return key;
};
const getLineMemorizedLatest = () => {
const properties = getMemorizedLatest(MemorizeKey.arrowLine);
return { ...properties };
};
const getMemorizedLatestByPointer = (pointer) => {
let memorizeKey = '';
if (PlaitDrawElement.isBasicShape({ shape: pointer })) {
memorizeKey = pointer === BasicShapes.text ? MemorizeKey.text : MemorizeKey.basicShape;
}
else if (PlaitDrawElement.isUML({ shape: pointer })) {
memorizeKey = MemorizeKey.UML;
}
else {
memorizeKey = MemorizeKey.flowchart;
}
const properties = { ...getMemorizedLatest(memorizeKey) };
const textProperties = { ...properties.text };
delete properties.text;
return { textProperties, geometryProperties: properties };
};
const memorizeLatestText = (element, operations) => {
const memorizeKey = getMemorizeKey(element);
let textMemory = getMemorizedLatest(memorizeKey)?.text || {};
const setNodeOperation = operations.find((operation) => operation.type === 'set_node');
if (setNodeOperation) {
const { properties, newProperties } = setNodeOperation;
for (const key in newProperties) {
const value = newProperties[key];
if (value == null) {
delete textMemory[key];
}
else {
textMemory[key] = value;
}
}
for (const key in properties) {
if (!newProperties.hasOwnProperty(key)) {
delete textMemory[key];
}
}
memorizeLatest(memorizeKey, 'text', textMemory);
}
};
const memorizeLatestShape = (board, shape) => {
const shapes = memorizedShape.has(board) ? memorizedShape.get(board) : [];
const shapeIndex = shapes.indexOf(shape);
if (shape === BasicShapes.text || shapeIndex === 0) {
return;
}
if (shapeIndex !== -1) {
shapes.splice(shapeIndex, 1);
}
else {
if (shapes.length === SHAPE_MAX_LENGTH) {
shapes.pop();
}
}
shapes.unshift(shape);
memorizedShape.set(board, shapes);
};
const getMemorizedLatestShape = (board) => {
return memorizedShape.get(board);
};
const debugKey$4 = 'debug:plait:line-mirror';
const debugGenerator$6 = createDebugGenerator(debugKey$4);
const alignPoint = (basePoint, movingPoint) => {
const newPoint = [...movingPoint];
if (Point.isVertical(newPoint, basePoint, LINE_ALIGN_TOLERANCE)) {
newPoint[0] = basePoint[0];
}
if (Point.isHorizontal(newPoint, basePoint, LINE_ALIGN_TOLERANCE)) {
newPoint[1] = basePoint[1];
}
return newPoint;
};
const alignPoints = (basePoints, movingPoint, targetIndex) => {
let newMovingPoint = [...movingPoint];
basePoints.forEach((basePoint, index) => {
if (index === targetIndex) {
return;
}
newMovingPoint = alignPoint(basePoint, newMovingPoint);
});
return newMovingPoint;
};
function getResizedPreviousAndNextPoint(nextRenderPoints, sourcePoint, targetPoint, handleIndex) {
const referencePoint = {
previous: null,
next: null
};
const startPoint = nextRenderPoints[handleIndex];
const endPoint = nextRenderPoints[handleIndex + 1];
const isHorizontal = Point.isHorizontal(startPoint, endPoint);
const isVertical = Point.isVertical(startPoint, endPoint);
const previousPoint = nextRenderPoints[handleIndex - 1] ?? nextRenderPoints[0];
const beforePreviousPoint = nextRenderPoints[handleIndex - 2] ?? sourcePoint;
if ((isHorizontal && Point.isHorizontal(beforePreviousPoint, previousPoint)) ||
(isVertical && Point.isVertical(beforePreviousPoint, previousPoint))) {
referencePoint.previous = previousPoint;
}
const nextPoint = nextRenderPoints[handleIndex + 2] ?? nextRenderPoints[nextRenderPoints.length - 1];
const afterNextPoint = nextRenderPoints[handleIndex + 3] ?? targetPoint;
if ((isHorizontal && Point.isHorizontal(nextPoint, afterNextPoint)) || (isVertical && Point.isVertical(nextPoint, afterNextPoint))) {
referencePoint.next = nextPoint;
}
return referencePoint;
}
function alignElbowSegment(startKeyPoint, endKeyPoint, resizeState, resizedPreviousAndNextPoint) {
let newStartPoint = startKeyPoint;
let newEndPoint = endKeyPoint;
if (Point.isHorizontal(startKeyPoint, endKeyPoint)) {
const offsetY = Point.getOffsetY(resizeState.startPoint, resizeState.endPoint);
let pointY = startKeyPoint[1] + offsetY;
if (resizedPreviousAndNextPoint.previous && Math.abs(resizedPreviousAndNextPoint.previous[1] - pointY) < LINE_ALIGN_TOLERANCE) {
pointY = resizedPreviousAndNextPoint.previous[1];
}
else if (resizedPreviousAndNextPoint.next && Math.abs(resizedPreviousAndNextPoint.next[1] - pointY) < LINE_ALIGN_TOLERANCE) {
pointY = resizedPreviousAndNextPoint.next[1];
}
newStartPoint = [startKeyPoint[0], pointY];
newEndPoint = [endKeyPoint[0], pointY];
}
if (Point.isVertical(startKeyPoint, endKeyPoint)) {
const offsetX = Point.getOffsetX(resizeState.startPoint, resizeState.endPoint);
let pointX = startKeyPoint[0] + offsetX;
if (resizedPreviousAndNextPoint.previous && Math.abs(resizedPreviousAndNextPoint.previous[0] - pointX) < LINE_ALIGN_TOLERANCE) {
pointX = resizedPreviousAndNextPoint.previous[0];
}
else if (resizedPreviousAndNextPoint.next && Math.abs(resizedPreviousAndNextPoint.next[0] - pointX) < LINE_ALIGN_TOLERANCE) {
pointX = resizedPreviousAndNextPoint.next[0];
}
newStartPoint = [pointX, startKeyPoint[1]];
newEndPoint = [pointX, endKeyPoint[1]];
}
return [newStartPoint, newEndPoint];
}
function getIndexAndDeleteCountByKeyPoint(board, element, dataPoints, nextRenderPoints, handleIndex) {
let index = null;
let deleteCount = null;
const startKeyPoint = nextRenderPoints[handleIndex];
const endKeyPoint = nextRenderPoints[handleIndex + 1];
if (!startKeyPoint || !endKeyPoint) {
return {
index,
deleteCount
};
}
const midDataPoints = dataPoints.slice(1, -1);
const startIndex = midDataPoints.findIndex((item) => Point.isEquals(item, startKeyPoint));
const endIndex = midDataPoints.findIndex((item) => Point.isEquals(item, endKeyPoint));
if (Math.max(startIndex, endIndex) > -1) {
if (startIndex > -1 && endIndex > -1) {
return {
index: startIndex,
deleteCount: 2
};
}
if (startIndex > -1 && endIndex === -1) {
const isReplace = startIndex < midDataPoints.length - 1 &&
Point.isAlign([midDataPoints[startIndex], midDataPoints[startIndex + 1], startKeyPoint, endKeyPoint]);
if (isReplace) {
return {
index: startIndex,
deleteCount: 2
};
}
return {
index: startIndex,
deleteCount: 1
};
}
if (startIndex === -1 && endIndex > -1) {
const isReplace = endIndex > 0 && Point.isAlign([midDataPoints[endIndex], midDataPoints[endIndex - 1], startKeyPoint, endKeyPoint]);
if (isReplace) {
return {
index: endIndex - 1,
deleteCount: 2
};
}
return {
index: endIndex,
deleteCount: 1
};
}
}
else {
for (let i = 0; i < midDataPoints.length - 1; i++) {
const currentPoint = midDataPoints[i];
const nextPoint = midDataPoints[i + 1];
if (Point.isAlign([currentPoint, nextPoint, startKeyPoint, endKeyPoint])) {
index = i;
deleteCount = 2;
break;
}
if (Point.isAlign([currentPoint, nextPoint, startKeyPoint])) {
index = Math.min(i + 1, midDataPoints.length - 1);
deleteCount = 1;
break;
}
if (Point.isAlign([currentPoint, nextPoint, endKeyPoint])) {
index = Math.max(i - 1, 0);
deleteCount = 1;
break;
}
}
}
if (index === null) {
deleteCount = 0;
if (midDataPoints.length > 0) {
const handleRefPair = getArrowLineHandleRefPair(board, element);
const params = getElbowLineRouteOptions(board, element, handleRefPair);
const keyPoints = removeDuplicatePoints(generateElbowLineRoute(params, board));
const nextKeyPoints = simplifyOrthogonalPoints(keyPoints.slice(1, keyPoints.length - 1));
const nextDataPoints = [nextRenderPoints[0], ...midDataPoints, nextRenderPoints[nextRenderPoints.length - 1]];
const mirrorDataPoints = getMirrorDataPoints(board, nextDataPoints, nextKeyPoints, params);
for (let i = handleIndex - 1; i >= 0; i--) {
const previousIndex = mirrorDataPoints.slice(1, -1).findIndex((item) => Point.isEquals(item, nextRenderPoints[i]));
if (previousIndex > -1) {
index = previousIndex + 1;
break;
}
}
if (index === null) {
index = 0;
// When renderPoints is a straight line and dataPoints are not on the line,
// the default 'deleteCount' is set to midDataPoints.length.
if (Point.isAlign(nextRenderPoints)) {
deleteCount = midDataPoints.length;
}
}
}
else {
index = 0;
}
}
return {
index,
deleteCount
};
}
function getMirrorDataPoints(board, nextDataPoints, nextKeyPoints, params) {
for (let index = 1; index < nextDataPoints.length - 2; index++) {
adjustByCustomPointStartIndex(board, index, nextDataPoints, nextKeyPoints, params);
}
return nextDataPoints;
}
/**
* adjust based parallel segment
*/
const adjustByCustomPointStartIndex = (board, customPointStartIndex, nextDataPoints, nextKeyPoints, params) => {
const beforePoint = nextDataPoints[customPointStartIndex - 1];
const startPoint = nextDataPoints[customPointStartIndex];
const endPoint = nextDataPoints[customPointStartIndex + 1];
const afterPoint = nextDataPoints[customPointStartIndex + 2];
const beforeSegment = [beforePoint, startPoint];
const afterSegment = [endPoint, afterPoint];
const isStraightWithBefore = Point.isAlign(beforeSegment);
const isStraightWithAfter = Point.isAlign(afterSegment);
let isAdjustStart = false;
let isAdjustEnd = false;
if (!isStraightWithBefore || !isStraightWithAfter) {
const midKeyPointsWithBefore = getMidKeyPoints(nextKeyPoints, beforeSegment[0], beforeSegment[1]);
const midKeyPointsWithAfter = getMidKeyPoints(nextKeyPoints, afterSegment[0], afterSegment[1]);
const hasMidKeyPoints = midKeyPointsWithBefore.length > 0 && midKeyPointsWithAfter.length > 0;
isAdjustStart = !isStraightWithBefore && !hasMidKeyPoints;
isAdjustEnd = !isStraightWithAfter && !hasMidKeyPoints;
}
if (isAdjustStart || isAdjustEnd) {
const parallelSegment = [startPoint, endPoint];
const parallelSegments = findOrthogonalParallelSegments(parallelSegment, nextKeyPoints);
const mirrorSegments = findMirrorSegments(board, parallelSegment, parallelSegments, params.sourceRectangle, params.targetRectangle);
if (mirrorSegments.length === 1) {
const mirrorSegment = mirrorSegments[0];
if (isAdjustStart) {
nextDataPoints.splice(customPointStartIndex, 1, mirrorSegment[0]);
}
if (isAdjustEnd) {
nextDataPoints.splice(customPointStartIndex + 1, 1, mirrorSegment[1]);
}
}
else {
const isHorizontal = Point.isHorizontal(startPoint, endPoint);
const adjustIndex = isHorizontal ? 0 : 1;
if (isAdjustStart) {
const newStartPoint = [startPoint[0], startPoint[1]];
newStartPoint[adjustIndex] = beforePoint[adjustIndex];
nextDataPoints.splice(customPointStartIndex, 1, newStartPoint);
}
if (isAdjustEnd) {
const newEndPoint = [endPoint[0], endPoint[1]];
newEndPoint[adjustIndex] = afterPoint[adjustIndex];
nextDataPoints.splice(customPointStartIndex + 1, 1, newEndPoint);
}
}
}
};
function isUpdatedHandleIndex(board, element, dataPoints, nextRenderPoints, handleIndex) {
const { deleteCount } = getIndexAndDeleteCountByKeyPoint(board, element, dataPoints, nextRenderPoints, handleIndex);
if (deleteCount !== null && deleteCount > 1) {
return true;
}
return false;
}
function getMidKeyPoints(simplifiedNextKeyPoints, startPoint, endPoint) {
let midElbowPoints = [];
let startPointIndex = -1;
let endPointIndex = -1;
for (let i = 0; i < simplifiedNextKeyPoints.length; i++) {
if (Point.isAlign([simplifiedNextKeyPoints[i], startPoint])) {
startPointIndex = i;
}
if (startPointIndex > -1 && Point.isAlign([simplifiedNextKeyPoints[i], endPoint])) {
endPointIndex = i;
break;
}
}
if (startPointIndex > -1 && endPointIndex > -1) {
midElbowPoints = simplifiedNextKeyPoints.slice(startPointIndex, endPointIndex + 1);
}
return midElbowPoints;
}
function findOrthogonalParallelSegments(segment, keyPoints) {
const isHorizontalSegment = Point.isHorizontal(segment[0], segment[1]);
const parallelSegments = [];
for (let i = 0; i < keyPoints.length - 1; i++) {
const current = keyPoints[i];
const next = keyPoints[i + 1];
const isHorizontal = Point.isHorizontal(current, next, 0.1);
if (isHorizontalSegment && isHorizontal) {
parallelSegments.push([current, next]);
}
if (!isHorizontalSegment && !isHorizontal) {
parallelSegments.push([current, next]);
}
}
return parallelSegments;
}
function findMirrorSegments(board, segment, parallelSegments, sourceRectangle, targetRectangle) {
debugGenerator$6.isDebug() && debugGenerator$6.clear();
const mirrorSegments = [];
for (let index = 0; index < parallelSegments.length; index++) {
const parallelPath = parallelSegments[index];
const startPoint = [segment[0][0], segment[0][1]];
const endPoint = [segment[1][0], segment[1][1]];
const isHorizontal = Point.isHorizontal(startPoint, endPoint);
const adjustDataIndex = isHorizontal ? 0 : 1;
startPoint[adjustDataIndex] = parallelPath[0][adjustDataIndex];
endPoint[adjustDataIndex] = parallelPath[1][adjustDataIndex];
const fakeRectangle = RectangleClient.getRectangleByPoints([startPoint, endPoint, ...parallelPath]);
const isValid = !RectangleClient.isHit(fakeRectangle, sourceRectangle) && !RectangleClient.isHit(fakeRectangle, targetRectangle);
if (isValid) {
mirrorSegments.push([startPoint, endPoint]);
debugGenerator$6.isDebug() && debugGenerator$6.drawPolygon(board, RectangleClient.getCornerPoints(fakeRectangle));
}
}
return mirrorSegments;
}
const hasIllegalElbowPoint = (midDataPoints) => {
if (midDataPoints.length === 1) {
return true;
}
return midDataPoints.some((item, index) => {
const beforePoint = midDataPoints[index - 1];
const afterPoint = midDataPoints[index + 1];
const beforeSegment = beforePoint && [beforePoint, item];
const afterSegment = afterPoint && [item, afterPoint];
const isStraightWithBefore = beforeSegment && Point.isAlign(beforeSegment);
const isStraightWithAfter = afterSegment && Point.isAlign(afterSegment);
if (index === 0) {
return !isStraightWithAfter;
}
if (index === midDataPoints.length - 1) {
return !isStraightWithBefore;
}
return !isStraightWithBefore && !isStraightWithAfter;
});
};
const ARROW_LENGTH = 20;
const drawArrowLineArrow = (element, points, options) => {
const arrowG = createG();
if (PlaitArrowLine.isSourceMark(element, ArrowLineMarkerType.none) && PlaitArrowLine.isTargetMark(element, ArrowLineMarkerType.none)) {
return null;
}
const strokeWidth = getStrokeWidthByElement(element);
const offset = (strokeWidth * strokeWidth) / 3;
if (points.length === 1) {
points = [points[0], [points[0][0] + 0.1, points[0][1]]];
}
if (!PlaitArrowLine.isSourceMark(element, ArrowLineMarkerType.none)) {
const source = getExtendPoint(points[0], points[1], ARROW_LENGTH + offset);
const sourceArrow = getArrow(element, { marker: element.source.marker, source, target: points[0], isSource: true }, options);
sourceArrow && arrowG.appendChild(sourceArrow);
}
if (!PlaitArrowLine.isTargetMark(element, ArrowLineMarkerType.none)) {
const source = getExtendPoint(points[points.length - 1], points[points.length - 2], ARROW_LENGTH + offset);
const arrow = getArrow(element, { marker: element.target.marker, source, target: points[points.length - 1], isSource: false }, options);
arrow && arrowG.appendChild(arrow);
}
return arrowG;
};
const getArrow = (element, arrowOptions, options) => {
const { marker, target, source, isSource } = arrowOptions;
let targetArrow;
switch (marker) {
case ArrowLineMarkerType.openTriangle: {
targetArrow = drawOpenTriangle(element, source, target, options);
break;
}
case ArrowLineMarkerType.solidTriangle: {
targetArrow = drawSolidTriangle(source, target, options);
break;
}
case ArrowLineMarkerType.arrow: {
targetArrow = drawArrow(element, source, target, options);
break;
}
case ArrowLineMarkerType.sharpArrow: {
targetArrow = d