Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#5497 – Refactor (SnakeModePolymerBondRenderer): Create SnakeModeSideChainBondRenderer #6033

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5fb5bcd
Add files via upload
DmitriiP-EPAM Dec 1, 2024
c351e77
Update PolymerBondRendererFactory.ts
DmitriiP-EPAM Dec 1, 2024
7cbcda7
Update SnakeModePolymerBondRenderer.ts
DmitriiP-EPAM Dec 1, 2024
4a75608
Update RenderersManager.ts
DmitriiP-EPAM Dec 1, 2024
7c04b5e
Update PolymerBond.ts
DmitriiP-EPAM Dec 1, 2024
94e5c71
#5497 – Refactor (SnakeModePolymerBondRenderer): Refactor `generateBe…
DmitriiP-EPAM Dec 3, 2024
d6e6dd7
#5497 – Refactor (SnakeModeHydrogenBondRenderer): Refactor `generateB…
DmitriiP-EPAM Dec 3, 2024
1dcc4b1
#5497 – Refactor (BaseRenderer.ts): Create `selectionElement`
DmitriiP-EPAM Dec 3, 2024
2cda4d4
#5497 – Refactor (BaseRenderer.ts): Cancel the creation of `selection…
DmitriiP-EPAM Dec 3, 2024
6f64110
#5497 – Refactor (types.ts): Rename `Datum`
DmitriiP-EPAM Dec 3, 2024
db02fea
#5497 – Refactor (SnakeModePolymerBondRenderer): Create and use `Side…
DmitriiP-EPAM Dec 3, 2024
a9247fd
Merge branch 'refs/heads/master' into 5497-refactor-split-logic-of-sn…
DmitriiP-EPAM Dec 3, 2024
557aab8
#5497 – Refactor (PolymerBond): Correct by the code style
DmitriiP-EPAM Dec 4, 2024
6c4481c
#5497 – Refactor (SnakeModeHydrogenBondRenderer): Specify the types
DmitriiP-EPAM Dec 4, 2024
0dd84e6
#5497 – Refactor / Bugfix (SnakeModePolymerBondRenderer): Recover `si…
DmitriiP-EPAM Dec 5, 2024
37f0128
#5497 – Refactor (SideChainConnectionBondRenderer): Create `calculate…
DmitriiP-EPAM Dec 5, 2024
1062c3e
#5497 – Refactor (SideChainConnectionBondRenderer): Create the interf…
DmitriiP-EPAM Dec 5, 2024
a2cc652
#5497 – Refactor (SideChainConnectionBondRenderer): Specify the types
DmitriiP-EPAM Dec 6, 2024
79de1bc
#5497 – Refactor (SnakeModeSideChainBondRenderer): Rename the class
DmitriiP-EPAM Dec 6, 2024
bba5bcd
#5497 – Refactor (SnakeModePolymerBondRenderer): Specify the type
DmitriiP-EPAM Dec 6, 2024
2d6a68e
#5497 – Refactor (SnakeModeSideChainBondRenderer): Rename the class (…
DmitriiP-EPAM Dec 6, 2024
cfe6f84
#5497 – Refactor (SnakeModeSideChainBondRenderer): Create `appendPath…
DmitriiP-EPAM Dec 6, 2024
b9c20c0
#5497 – Refactor (SnakeModeSideChainBondRenderer): Delete the useless…
DmitriiP-EPAM Dec 6, 2024
75f294a
#5497 – Refactor (SnakeModePolymerBondRenderer): Create the constant …
DmitriiP-EPAM Dec 6, 2024
0154ba2
Merge branch 'refs/heads/master' into 5497-refactor-split-logic-of-sn…
DmitriiP-EPAM Dec 6, 2024
7130f51
#5497 – Refactor (SnakeModeSideChainBondRenderer): Rename `calculateS…
DmitriiP-EPAM Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export abstract class BaseRenderer implements IBaseRenderer {
| D3SvgElementSelection<SVGRectElement, void>
| D3SvgElementSelection<SVGPathElement, void>;

// An extra invisible area around `bodyElement` to make it easier for a user to hover over it.
protected hoverAreaElement?: D3SvgElementSelection<
SVGGElement | SVGLineElement,
void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { CoreEditor, SnakeMode } from 'application/editor';
import { FlexModePolymerBondRenderer } from 'application/render/renderers/PolymerBondRenderer/FlexModePolymerBondRenderer';
import { SnakeModePolymerBondRenderer } from 'application/render/renderers/PolymerBondRenderer/SnakeModePolymerBondRenderer';
import { PolymerBond } from 'domain/entities/PolymerBond';
import { HydrogenBond } from 'domain/entities/HydrogenBond';

export enum LayoutMode {
Flex = 'Flex',
Expand All @@ -25,15 +24,13 @@ const polymerBondRendererMap = new Map<

export class PolymerBondRendererFactory {
public static createInstance(
polymerBond: PolymerBond | HydrogenBond,
polymerBond: PolymerBond,
): PolymerBondRendererClass {
const mode = checkIfIsSnakeMode() ? LayoutMode.Snake : LayoutMode.Flex;
return polymerBond instanceof HydrogenBond
? new SnakeModePolymerBondRenderer(polymerBond)
: (PolymerBondRendererFactory.createInstanceByMode(
mode,
polymerBond,
) as PolymerBondRendererClass);
return PolymerBondRendererFactory.createInstanceByMode(
mode,
polymerBond,
) as PolymerBondRendererClass;
}

public static createInstanceByMode(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
import {
BaseMonomerRenderer,
BaseSequenceItemRenderer,
} from 'application/render';
import { D3SvgElementSelection } from 'application/render/types';
import { BaseMonomer, PolymerBond, Vec2 } from 'domain/entities';
import { Cell } from 'domain/entities/canvas-matrix/Cell';
import {
Connection,
ConnectionDirectionInDegrees,
ConnectionDirectionOfLastCell,
} from 'domain/entities/canvas-matrix/Connection';
import { SNAKE_LAYOUT_CELL_WIDTH } from 'domain/entities/DrawingEntitiesManager';

type AppendPathToElementFunction = (
element: D3SvgElementSelection<SVGGElement, void>,
cssClassForPath: string,
) => D3SvgElementSelection<SVGPathElement, void>;

interface AppendSideConnectionBondParameter {
readonly cells: readonly Cell[];
readonly polymerBond: PolymerBond;
readonly scaledPosition: {
readonly endPosition: Vec2;
readonly startPosition: Vec2;
};
readonly sideConnectionBondTurnPoint?: number;
}

interface AppendSideConnectionBondResult {
readonly appendPathToElement: AppendPathToElementFunction;
readonly pathDAttributeValue: string;
}

const BOND_END_LENGTH = 15;
const CELL_HEIGHT = 40;
const SMOOTH_CORNER_SIZE = 5;

// TODO: Can be:
// - a class with static methods.
// - a set of functions.
export class SideChainConnectionBondRenderer {
public appendSideConnectionBond({
cells,
polymerBond,
scaledPosition,
sideConnectionBondTurnPoint,
}: AppendSideConnectionBondParameter): AppendSideConnectionBondResult {
const firstCell = cells[0];
const firstCellConnection = firstCell.connections.find((connection) => {
return connection.polymerBond === polymerBond;
}) as Connection;
const isVerticalConnection = firstCellConnection.isVertical;
const isStraightVerticalConnection =
cells.length === 2 && isVerticalConnection;
const isFirstMonomerOfBondInFirstCell = firstCell.node?.monomers.includes(
polymerBond.firstMonomer,
);
const isTwoNeighborRowsConnection = cells.every(
(cell) => cell.y === firstCell.y || cell.y === firstCell.y + 1,
);
const startPosition = isFirstMonomerOfBondInFirstCell
? scaledPosition.startPosition
: scaledPosition.endPosition;
const endPosition = isFirstMonomerOfBondInFirstCell
? scaledPosition.endPosition
: scaledPosition.startPosition;
const xDirection =
startPosition.x >= (sideConnectionBondTurnPoint || endPosition.x)
? 180
: 0;
let pathDAttributeValue = `M ${startPosition.x},${startPosition.y} `;

const cos = Math.cos((xDirection * Math.PI) / 180);

let previousConnection: Connection;
let previousCell: Cell;

const horizontalPartIntersectionsOffset = firstCellConnection.xOffset;

const areCellsOnSameRow = cells.every((cell) => {
return cell.y === firstCell.y;
});
const isSecondCellEmpty = cells[1].node === null;

if (areCellsOnSameRow) {
pathDAttributeValue += `L ${startPosition.x},${
startPosition.y -
BOND_END_LENGTH -
horizontalPartIntersectionsOffset * 3
} `;
pathDAttributeValue += generateBend(0, -1, cos, -1);
} else {
pathDAttributeValue += `L ${startPosition.x},${
startPosition.y +
BOND_END_LENGTH +
horizontalPartIntersectionsOffset * 3
} `;
if (
!isStraightVerticalConnection &&
!isSecondCellEmpty &&
!isTwoNeighborRowsConnection
) {
pathDAttributeValue += generateBend(0, 1, cos, 1);
}
}

if (isVerticalConnection && !isStraightVerticalConnection) {
const direction =
sideConnectionBondTurnPoint &&
startPosition.x < sideConnectionBondTurnPoint
? 0
: 180;
const result = this.drawPartOfSideConnection({
cell: firstCell,
connection: firstCellConnection,
direction,
horizontal: true,
sideConnectionBondTurnPoint: sideConnectionBondTurnPoint ?? 0,
});
pathDAttributeValue += result.pathPart;
sideConnectionBondTurnPoint = result.sideConnectionBondTurnPoint;
}

let maxHorizontalOffset = 0;

cells.forEach((cell: Cell, cellIndex: number): void => {
const cellConnection = cell.connections.find(
(connection: Connection): boolean => {
return connection.polymerBond === polymerBond;
},
) as Connection;
const isLastCell = cellIndex === cells.length - 1;
const _xDirection = sideConnectionBondTurnPoint
? endPosition.x < sideConnectionBondTurnPoint
? 180
: 0
: xDirection;
const maxXOffset = cell.connections.reduce(
(max: number, connection: Connection): number => {
return connection.isVertical || max > connection.xOffset
? max
: connection.xOffset;
},
0,
);

maxHorizontalOffset =
maxHorizontalOffset > maxXOffset ? maxHorizontalOffset : maxXOffset;

if (isLastCell) {
if (isStraightVerticalConnection) {
return;
}

const directionObject =
cellConnection.direction as ConnectionDirectionOfLastCell;
const yDirection = isVerticalConnection ? 90 : directionObject.y;
const sin = Math.sin((yDirection * Math.PI) / 180);
const cos = Math.cos((_xDirection * Math.PI) / 180);

if (!areCellsOnSameRow) {
pathDAttributeValue += `V ${
endPosition.y -
CELL_HEIGHT / 2 -
SMOOTH_CORNER_SIZE -
sin * (cellConnection.yOffset || 0) * 3 -
(isTwoNeighborRowsConnection
? maxHorizontalOffset - cellConnection.xOffset
: cellConnection.xOffset) *
3
} `;
pathDAttributeValue += generateBend(0, sin, cos, 1);
}
pathDAttributeValue += `H ${endPosition.x - SMOOTH_CORNER_SIZE * cos} `;
pathDAttributeValue += generateBend(cos, 0, cos, 1);
return;
}

// Empty cells.
if (cell.node === null) {
return;
}

// Other cells.
if (
previousConnection &&
previousConnection.direction !== cellConnection.direction
) {
// TODO?: Check. I am not sure about `as DirectionInDegrees`.
const horizontal = new Set([0, 180]).has(
previousConnection.direction as ConnectionDirectionInDegrees,
);
const direction = horizontal
? xDirection
: // TODO?: Check. I am not sure about `as DirectionInDegrees`.
(previousConnection.direction as ConnectionDirectionInDegrees);
const result = this.drawPartOfSideConnection({
cell: previousCell,
connection: previousConnection,
direction,
horizontal,
sideConnectionBondTurnPoint: sideConnectionBondTurnPoint ?? 0,
});
pathDAttributeValue += result.pathPart;
sideConnectionBondTurnPoint = result.sideConnectionBondTurnPoint;
}
previousCell = cell;
previousConnection = cellConnection;
});

pathDAttributeValue += `L ${endPosition.x},${endPosition.y} `;

const appendPathToElement: AppendPathToElementFunction = (
element: D3SvgElementSelection<SVGGElement, void>,
cssClassForPath: string,
): D3SvgElementSelection<SVGPathElement, void> => {
const pathElement: D3SvgElementSelection<SVGPathElement, void> =
element.append('path');
return pathElement
.attr('class', cssClassForPath)
.attr('d', pathDAttributeValue)
.attr('pointer-events', 'stroke')
.attr('stroke', '#43b5c0')
.attr('stroke-dasharray', '0')
DmitriiP-EPAM marked this conversation as resolved.
Show resolved Hide resolved
.attr('stroke-width', 1)
.attr('fill', 'none');
};

return {
appendPathToElement,
pathDAttributeValue,
};
}

// TODO: Specify the types.
private drawPartOfSideConnection({
cell,
connection,
direction,
horizontal,
sideConnectionBondTurnPoint,
}: {
readonly cell: Cell;
readonly connection: Connection;
readonly direction: ConnectionDirectionInDegrees;
readonly horizontal: boolean;
readonly sideConnectionBondTurnPoint: number;
}): {
readonly pathPart: string;
readonly sideConnectionBondTurnPoint: number;
} {
const sin = Math.sin((direction * Math.PI) / 180);
const cos = Math.cos((direction * Math.PI) / 180);
const xOffset = (SNAKE_LAYOUT_CELL_WIDTH / 2) * cos;
const yOffset = (CELL_HEIGHT / 2) * sin;
const maxXOffset = cell.connections.reduce(
(max: number, connection: Connection): number => {
return max > connection.xOffset ? max : connection.xOffset;
},
0,
);
const maxYOffset = cell.connections.reduce(
(max: number, connection: Connection): number => {
const connectionYOffset = connection.yOffset || 0;
return max > connectionYOffset ? max : connectionYOffset;
},
0,
);

let endOfPathPart: number;
if (horizontal && sideConnectionBondTurnPoint) {
endOfPathPart = sideConnectionBondTurnPoint;
} else {
const { monomerSize, scaledMonomerPosition } = (
cell.monomer as BaseMonomer
).renderer as BaseMonomerRenderer | BaseSequenceItemRenderer;
endOfPathPart = horizontal
? scaledMonomerPosition.x + monomerSize.width / 2 + xOffset
: scaledMonomerPosition.y + monomerSize.height / 2 + yOffset;
}

const sideConnectionBondTurnPointInternal = endOfPathPart;

if (horizontal) {
endOfPathPart +=
-(connection.yOffset || 0) * 3 +
cos * -connection.xOffset * 3 +
cos * (maxXOffset + 1) * 3 +
(maxYOffset + 1) * 3;
}
let pathPart = horizontal ? 'H ' : 'V ';
pathPart += `${endOfPathPart - SMOOTH_CORNER_SIZE * cos} `;
pathPart += generateBend(cos, sin, cos, 1);

return {
pathPart,
sideConnectionBondTurnPoint: sideConnectionBondTurnPointInternal,
};
}
}

function generateBend(
dx1: number,
dy1: number,
dx: number,
dy: number,
): string {
const controlPoint = `${SMOOTH_CORNER_SIZE * dx1},${
SMOOTH_CORNER_SIZE * dy1
}`;
const endPoint = `${SMOOTH_CORNER_SIZE * dx},${SMOOTH_CORNER_SIZE * dy}`;
return `q ${controlPoint} ${endPoint} `;
}
Loading
Loading