Skip to content

Commit

Permalink
#4554 - Support variant monomers in Ketcher (flex mode)
Browse files Browse the repository at this point in the history
- added variant monomers to model/view and deserialization
  • Loading branch information
rrodionov91 committed Aug 19, 2024
1 parent 2b898c6 commit f0c324b
Show file tree
Hide file tree
Showing 20 changed files with 484 additions and 40 deletions.
49 changes: 42 additions & 7 deletions packages/ketcher-core/src/application/formatters/types/ket.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import { AttachmentPointName } from 'domain/types';

export enum KetNodeType {
MONOMER = 'monomer',
VARIANT_MONOMER = 'variantMonomer',
}

export interface IKetMonomerNode {
type: 'monomer';
type: KetNodeType.MONOMER;
id: string;
seqid?: number;
position: {
x: number;
y: number;
};
alias?: string;
alias: string;
templateId: string;
}

export interface IKetGroupNode {
type: 'group';
export interface IKetVariantMonomerNode {
type: KetNodeType.VARIANT_MONOMER;
id: string;
position: {
x: number;
y: number;
};
alias: string;
templateId: string;
}

export type KetNode = IKetMonomerNode | IKetGroupNode;
export type KetNode = IKetMonomerNode | IKetVariantMonomerNode;

export interface IKetConnectionMonomerEndPoint {
monomerId: string;
Expand Down Expand Up @@ -100,6 +112,17 @@ export enum KetTemplateType {
MONOMER_GROUP_TEMPLATE = 'monomerGroupTemplate',
}

export enum KetVariantMonomerTemplateType {
ALTERNATIVES = 'alternatives',
MIXTURE = 'mixture',
}

export interface KetVariantMonomerTemplateOption {
templateId: string;
ratio?: number;
probability?: number;
}

export interface IKetMonomerTemplate {
type: KetTemplateType.MONOMER_TEMPLATE;
class?: monomerClass;
Expand All @@ -115,7 +138,7 @@ export interface IKetMonomerTemplate {
naturalAnalogShort: string;
id: string;
fullName?: string;
alias?: string;
alias: string;
naturalAnalog?: string;
attachmentPoints?: IKetAttachmentPoint[];
root: {
Expand All @@ -129,6 +152,14 @@ export interface IKetMonomerTemplate {
bonds: [];
}

export interface IKetVariantMonomerTemplate {
type: KetTemplateType.MONOMER_TEMPLATE;
id: string;
subtype: KetVariantMonomerTemplateType;
options: KetVariantMonomerTemplateOption[];
idtAliases?: IKetIdtAliases;
}

export interface IKetMonomerTemplateRef {
$ref: string;
}
Expand Down Expand Up @@ -160,7 +191,11 @@ export interface IKetMacromoleculesContentRootProperty {
}

export interface IKetMacromoleculesContentOtherProperties {
[key: string]: KetNode | IKetMonomerTemplate | IKetMonomerGroupTemplate;
[key: string]:
| KetNode
| IKetMonomerTemplate
| IKetMonomerGroupTemplate
| IKetVariantMonomerTemplate;
}

export type IKetMacromoleculesContent = IKetMacromoleculesContentRootProperty &
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Selection } from 'd3';
import { Chem } from 'domain/entities/Chem';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { MONOMER_SYMBOLS_IDS } from 'application/render/renderers/constants';
import { KetMonomerClass } from 'application/formatters';

const CHEM_SELECTED_ELEMENT_ID = '#chem-selection';
const CHEM_SYMBOL_ELEMENT_ID = '#chem';
const CHEM_SELECTED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.CHEM].selected;
const CHEM_SYMBOL_ELEMENT_ID = MONOMER_SYMBOLS_IDS[KetMonomerClass.CHEM].body;

export class ChemRenderer extends BaseMonomerRenderer {
constructor(public monomer: Chem, scale?: number) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Selection } from 'd3';
import { Peptide } from 'domain/entities/Peptide';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { MONOMER_SYMBOLS_IDS } from 'application/render/renderers/constants';
import { KetMonomerClass } from 'application/formatters';

const PEPTIDE_SELECTED_ELEMENT_ID = '#peptide-selection';
const PEPTIDE_HOVERED_ELEMENT_ID = '#peptide-hover';
const PEPTIDE_SYMBOL_ELEMENT_ID = '#peptide';
const PEPTIDE_SELECTED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.AminoAcid].selected;
const PEPTIDE_HOVERED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.AminoAcid].hover;
const PEPTIDE_SYMBOL_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.AminoAcid].body;

export class PeptideRenderer extends BaseMonomerRenderer {
public CHAIN_BEGINNING = 'N';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Selection } from 'd3';
import { Phosphate } from 'domain/entities/Phosphate';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { MONOMER_SYMBOLS_IDS } from 'application/render/renderers/constants';
import { KetMonomerClass } from 'application/formatters';

const PHOSPHATE_SELECTED_ELEMENT_ID = '#phosphate-selection';
const PHOSPHATE_SYMBOL_ELEMENT_ID = '#phosphate';
const PHOSPHATE_SELECTED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.Phosphate].selected;
const PHOSPHATE_SYMBOL_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.Phosphate].body;

export class PhosphateRenderer extends BaseMonomerRenderer {
constructor(public monomer: Phosphate, scale?: number) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Selection } from 'd3';
import { RNABase } from 'domain/entities/RNABase';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { MONOMER_SYMBOLS_IDS } from 'application/render/renderers/constants';
import { KetMonomerClass } from 'application/formatters';

const RNABASE_SELECTED_ELEMENT_ID = '#rna-base-selection';
const RNABASE_SYMBOL_ELEMENT_ID = '#rna-base';
const RNABASE_SELECTED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.Base].selected;
const RNABASE_SYMBOL_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.Base].body;

export class RNABaseRenderer extends BaseMonomerRenderer {
constructor(public monomer: RNABase, scale?: number) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ import {
} from 'domain/helpers/monomers';
import { CoreEditor } from 'application/editor';
import { ChainsCollection } from 'domain/entities/monomer-chains/ChainsCollection';
import { VariantMonomer } from 'domain/entities/VariantMonomer';
import { VariantMonomerRenderer } from 'application/render/renderers/VariantMonomerRenderer';

export class RenderersManager {
private theme;
public monomers: Map<number, BaseMonomerRenderer> = new Map();
public monomers: Map<number, BaseMonomerRenderer | VariantMonomerRenderer> =
new Map();

public polymerBonds: Map<number, PolymerBondRenderer> = new Map();
private needRecalculateMonomersEnumeration = false;
private needRecalculateMonomersBeginning = false;
Expand Down Expand Up @@ -66,9 +70,19 @@ export class RenderersManager {
this.needRecalculateMonomersBeginning = true;
}

public addMonomer(monomer: BaseMonomer, callback?: () => void) {
const [, MonomerRenderer] = monomerFactory(monomer.monomerItem);
const monomerRenderer = new MonomerRenderer(monomer);
public addMonomer(
monomer: BaseMonomer | VariantMonomer,
callback?: () => void,
) {
let monomerRenderer;

if (monomer instanceof VariantMonomer) {
monomerRenderer = new VariantMonomerRenderer(monomer);
} else {
const MonomerRenderer = monomerFactory(monomer.monomerItem)[1];
monomerRenderer = new MonomerRenderer(monomer);
}

this.monomers.set(monomer.id, monomerRenderer);
monomerRenderer.show(this.theme);
this.markForReEnumeration();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Selection } from 'd3';
import { Sugar } from 'domain/entities/Sugar';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { MONOMER_SYMBOLS_IDS } from 'application/render/renderers/constants';
import { KetMonomerClass } from 'application/formatters';

const SUGAR_SELECTED_ELEMENT_ID = '#sugar-selection';
const SUGAR_SYMBOL_ELEMENT_ID = '#sugar';
const SUGAR_SELECTED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.Sugar].selected;
const SUGAR_SYMBOL_ELEMENT_ID = MONOMER_SYMBOLS_IDS[KetMonomerClass.Sugar].body;

export class SugarRenderer extends BaseMonomerRenderer {
public CHAIN_BEGINNING = '’5';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import { Selection } from 'd3';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { UnsplitNucleotide } from 'domain/entities';
import { D3SvgElementSelection } from 'application/render/types';
import { MONOMER_SYMBOLS_IDS } from 'application/render/renderers/constants';
import { KetMonomerClass } from 'application/formatters';

const NUCLEOTIDE_SELECTED_ELEMENT_ID = '#nucleotide-selection';
const NUCLEOTIDE_HOVERED_ELEMENT_ID = '#nucleotide-hover';
const NUCLEOTIDE_SYMBOL_ELEMENT_ID = '#nucleotide';
const NUCLEOTIDE_SELECTED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.RNA].selected;
const NUCLEOTIDE_HOVERED_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.RNA].hover;
const NUCLEOTIDE_SYMBOL_ELEMENT_ID =
MONOMER_SYMBOLS_IDS[KetMonomerClass.RNA].body;

export class UnsplitNucleotideRenderer extends BaseMonomerRenderer {
constructor(public monomer: UnsplitNucleotide, scale?: number) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Selection } from 'd3';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { VariantMonomer } from 'domain/entities/VariantMonomer';
import { MONOMER_SYMBOLS_IDS } from 'application/render/renderers/constants';

const NUMBER_OF_MONOMERS_CIRCLE_RADIUS = 3;
const NUMBER_OF_MONOMERS_CIRCLE_Y_OFFSET = 7;

export class VariantMonomerRenderer extends BaseMonomerRenderer {
private monomerSymbolElementsIds: {
selected: string;
hover: string;
body: string;
variant?: string;
};

constructor(public monomer: VariantMonomer, scale?: number) {
const monomerClass = VariantMonomer.getMonomerClass(monomer.monomers);
const monomerSymbolElementsIds = MONOMER_SYMBOLS_IDS[monomerClass];

super(
monomer,
monomerSymbolElementsIds.selected,
monomerSymbolElementsIds.hover,
monomerSymbolElementsIds.body,
scale,
);

this.monomerSymbolElementsIds = monomerSymbolElementsIds;
}

protected appendBody(
rootElement: Selection<SVGGElement, void, HTMLElement, never>,
) {
return rootElement
.append('use')
.data([this])
.attr(
'href',
this.monomerSymbolElementsIds.variant ||
this.monomerSymbolElementsIds.body,
)
.attr('fill', '#fff')
.attr('stroke', '#585858')
.attr('stroke-width', '1px')
.attr('paint-order', 'fill');
}

protected get enumerationElementPosition() {
return undefined;
}

protected get beginningElementPosition() {
return undefined;
}

private appendNumberOfMonomers() {
const numberOfMonomersElement = this.rootElement
?.append('g')
.attr(
'transform',
`translate(${this.center.x - this.scaledMonomerPosition.x}, ${
this.center.y -
this.scaledMonomerPosition.y +
NUMBER_OF_MONOMERS_CIRCLE_Y_OFFSET
})`,
)
.attr('pointer-events', 'none');

numberOfMonomersElement
?.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', NUMBER_OF_MONOMERS_CIRCLE_RADIUS)
.attr('fill', '#fff')
.attr('stroke', '#CCEAEE')
.attr('stroke-width', 0.5);

numberOfMonomersElement
?.append('text')
.attr('x', -1.6)
.attr('y', 2.1)
.attr('font-size', '6px')
.attr('font-weight', 300)
.text(this.monomer.monomers.length);
}

public show(theme) {
super.show(theme);
this.appendNumberOfMonomers();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { KetMonomerClass } from 'application/formatters';

export const MONOMER_SYMBOLS_IDS = {
[KetMonomerClass.AminoAcid]: {
hover: '#peptide-hover',
selected: '#peptide-selection',
body: '#peptide',
},
[KetMonomerClass.CHEM]: {
hover: '#chem-selection',
selected: '#chem-selection',
body: '#chem',
},
[KetMonomerClass.Sugar]: {
hover: '#sugar-selection',
selected: '#sugar-selection',
body: '#sugar',
},
[KetMonomerClass.Base]: {
hover: '#rna-base-selection',
selected: '#rna-base-selection',
body: '#rna-base',
variant: '#rna-base-variant',
},
[KetMonomerClass.Phosphate]: {
hover: '#phosphate-selection',
selected: '#phosphate-selection',
body: '#phosphate',
},
[KetMonomerClass.RNA]: {
hover: '#nucleotide-hover',
selected: '#nucleotide-selection',
body: '#nucleotide',
},
};
17 changes: 17 additions & 0 deletions packages/ketcher-core/src/domain/constants/monomers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { KetMonomerClass } from 'application/formatters';
import { Chem } from 'domain/entities/Chem';
import { Peptide } from 'domain/entities/Peptide';
import { Phosphate } from 'domain/entities/Phosphate';
import { RNABase } from 'domain/entities/RNABase';
import { UnsplitNucleotide } from 'domain/entities/UnsplitNucleotide';
import { Sugar } from 'domain/entities/Sugar';

export enum RNA_DNA_NON_MODIFIED_PART {
SUGAR_RNA = 'R',
SUGAR_DNA = 'dR',
Expand Down Expand Up @@ -29,3 +37,12 @@ export const peptideNaturalAnalogues = [
];

export const NO_NATURAL_ANALOGUE = 'X';

export const MONOMER_CLASS_TO_CONSTRUCTOR = {
[KetMonomerClass.CHEM]: Chem,
[KetMonomerClass.AminoAcid]: Peptide,
[KetMonomerClass.Phosphate]: Phosphate,
[KetMonomerClass.Sugar]: Sugar,
[KetMonomerClass.Base]: RNABase,
[KetMonomerClass.RNA]: UnsplitNucleotide,
};
Loading

0 comments on commit f0c324b

Please sign in to comment.