diff --git a/packages/ketcher-core/src/application/render/draw.ts b/packages/ketcher-core/src/application/render/draw.ts index 9a37a25bb7..5f584bb1bb 100644 --- a/packages/ketcher-core/src/application/render/draw.ts +++ b/packages/ketcher-core/src/application/render/draw.ts @@ -139,6 +139,16 @@ function arrow( shouldApplySnappingStyle, ); } + case RxnArrowMode.Retrosynthetic: { + return arrowRetrosynthetic( + paper, + item, + length, + angle, + options, + shouldApplySnappingStyle, + ); + } case RxnArrowMode.BothEndsFilledTriangle: { return arrowBothEndsFilledTriangle( paper, @@ -591,6 +601,61 @@ function arrowFailed( }); } +function arrowRetrosynthetic( + paper: RaphaelPaper, + { pos: [start] }: ArrowItem, + arrowLength: number, + arrowAngle: number, + options: RenderOptions, + shouldApplySnappingStyle: boolean, +) { + const width = 5; + const length = 7; + const arrowheadLength = 0.1; + const lineOffset = 4; + + const endX = start.x + arrowLength; + + const path: string[] = []; + + // First arrow and arrowhead base + path.push( + `M${tfx(start.x)},${tfx(start.y - lineOffset)}` + + `L${tfx(endX)},${tfx(start.y - lineOffset)}` + + `L${tfx(endX - length)},${tfx(start.y - width - lineOffset)}`, + ); + + // First Arrowhead line + path.push( + `M${tfx(endX - length)},${tfx(start.y - width - lineOffset)}` + + `L${tfx(endX + length)},${tfx(start.y - arrowheadLength)}`, + ); + + // Second arrow and arrowhead base + path.push( + `M${tfx(start.x)},${tfx(start.y + lineOffset)}` + + `L${tfx(endX)},${tfx(start.y + lineOffset)}` + + `L${tfx(endX - length)},${tfx(start.y + width + lineOffset)}`, + ); + + // Second Arrowhead line + path.push( + `M${tfx(endX - length)},${tfx(start.y + width + lineOffset)}` + + `L${tfx(endX + length)},${tfx(start.y + arrowheadLength)}`, + ); + + const transformedPath = svgPath(path.join('')) + .rotate(arrowAngle, start.x, start.y) + .toString(); + + return paper.path(transformedPath).attr({ + ...options.lineattr, + ...(shouldApplySnappingStyle && { + stroke: options.arrowSnappingStyle.stroke, + }), + }); +} + function arrowBothEndsFilledTriangle( paper: RaphaelPaper, { pos: [start] }: ArrowItem, diff --git a/packages/ketcher-core/src/domain/entities/rxnArrow.ts b/packages/ketcher-core/src/domain/entities/rxnArrow.ts index 0cbdd6491f..f0090bec5c 100644 --- a/packages/ketcher-core/src/domain/entities/rxnArrow.ts +++ b/packages/ketcher-core/src/domain/entities/rxnArrow.ts @@ -26,6 +26,7 @@ export enum RxnArrowMode { FilledBow = 'filled-bow', DashedOpenAngle = 'dashed-open-angle', Failed = 'failed', + Retrosynthetic = 'retrosynthetic', BothEndsFilledTriangle = 'both-ends-filled-triangle', EquilibriumFilledTriangle = 'equilibrium-filled-triangle', EquilibriumFilledHalfBow = 'equilibrium-filled-half-bow', diff --git a/packages/ketcher-react/src/script/ui/action/tools.js b/packages/ketcher-react/src/script/ui/action/tools.js index 1c380366fd..5b9be01ad1 100644 --- a/packages/ketcher-react/src/script/ui/action/tools.js +++ b/packages/ketcher-react/src/script/ui/action/tools.js @@ -137,6 +137,11 @@ const toolActions = { action: { tool: 'reactionarrow', opts: RxnArrowMode.Failed }, hidden: (options) => isHidden(options, 'reaction-arrow-failed'), }, + 'retrosynthetic-arrow': { + title: 'Retrosynthetic Arrow Tool', + action: { tool: 'reactionarrow', opts: RxnArrowMode.Retrosynthetic }, + hidden: (options) => isHidden(options, 'retrosynthetic-arrow'), + }, 'reaction-arrow-both-ends-filled-triangle': { title: 'Arrow Both Ends Filled Triangle Tool', action: {