molstar
Version:
A comprehensive macromolecular library.
394 lines (393 loc) • 26 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAPairwiseScorePlotBase = exports.MAPairwiseScorePlotPanel = void 0;
exports.MAPairwiseScorePlot = MAPairwiseScorePlot;
const jsx_runtime_1 = require("react/jsx-runtime");
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
const react_1 = require("react");
const rxjs_1 = require("rxjs");
const interpolate_1 = require("../../../../mol-math/interpolate");
const structure_1 = require("../../../../mol-model/structure");
const atomic_1 = require("../../../../mol-model/structure/model/properties/atomic");
const generators_1 = require("../../../../mol-model/structure/query/queries/generators");
const objects_1 = require("../../../../mol-plugin-state/objects");
const representation_1 = require("../../../../mol-plugin-state/transforms/representation");
const base_1 = require("../../../../mol-plugin-ui/base");
const icons_1 = require("../../../../mol-plugin-ui/controls/icons");
const parameters_1 = require("../../../../mol-plugin-ui/controls/parameters");
const use_behavior_1 = require("../../../../mol-plugin-ui/hooks/use-behavior");
const mol_util_1 = require("../../../../mol-util");
const color_1 = require("../../../../mol-util/color");
const param_definition_1 = require("../../../../mol-util/param-definition");
const single_async_queue_1 = require("../../../../mol-util/single-async-queue");
const prop_1 = require("../prop");
const plot_1 = require("./plot");
class MAPairwiseScorePlotPanel extends base_1.CollapsableControls {
constructor() {
super(...arguments);
this.interactivity = new rxjs_1.BehaviorSubject({});
this.queue = new single_async_queue_1.SingleAsyncQueue();
}
defaultState() {
return {
header: 'Predicted Aligned Error',
isCollapsed: false,
isHidden: true,
brand: { accent: 'purple', svg: icons_1.ScatterPlotSvg },
params: {},
values: undefined,
dataSources: [],
};
}
toggleCollapsed() {
if (!this.state.isCollapsed) {
this.setState({ isCollapsed: true });
}
else {
const state = getPropsAndValues(this.plugin, this.state.values);
this.setState({
...state,
isCollapsed: false,
isHidden: state.params.data.options.length === 0 || state.params.model.options.length === 0
});
}
}
;
componentDidMount() {
this.subscribe((0, rxjs_1.combineLatest)([
this.plugin.state.data.events.changed,
this.plugin.behaviors.state.isAnimating
]), ([_, anim]) => {
if (anim || this.state.isCollapsed)
return;
const state = getPropsAndValues(this.plugin, this.state.values);
this.setState({
...state,
isHidden: state.params.data.options.length === 0 || state.params.model.options.length === 0
});
});
this.subscribe(filterHighlightState(this.interactivity), state => {
highlightState(this.plugin, state);
});
this.subscribe(filterOverpaintState(this.interactivity), state => {
this.queue.enqueue(() => overpaintState(this.plugin, state));
});
}
renderControls() {
const { params, values, dataSources } = this.state;
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: params, values: values, onChangeValues: values => this.setState({ values }) }), (0, jsx_runtime_1.jsx)(PlotWrapper, { plugin: this.plugin, values: values, dataSources: dataSources, interactivity: this.interactivity })] });
}
}
exports.MAPairwiseScorePlotPanel = MAPairwiseScorePlotPanel;
function MAPairwiseScorePlot({ plugin, model, pairwiseMetric }) {
var _a;
const _interactivity = (0, react_1.useRef)();
const interactivity = (_a = _interactivity.current) !== null && _a !== void 0 ? _a : (_interactivity.current = new rxjs_1.BehaviorSubject({}));
(0, react_1.useEffect)(() => {
const queue = new single_async_queue_1.SingleAsyncQueue();
const highlight = filterHighlightState(interactivity).subscribe(state => highlightState(plugin, state));
const paint = filterOverpaintState(interactivity).subscribe(state => queue.enqueue(() => overpaintState(plugin, state)));
return () => {
highlight.unsubscribe();
paint.unsubscribe();
queue.enqueue(() => overpaintState(plugin, interactivity.value));
};
}, [model, pairwiseMetric]);
return (0, jsx_runtime_1.jsx)(exports.MAPairwiseScorePlotBase, { model: model, pairwiseMetric: pairwiseMetric, interactivity: interactivity });
}
function filterHighlightState(state) {
return state.pipe((0, rxjs_1.throttleTime)(16, undefined, { leading: true, trailing: true }), (0, rxjs_1.distinctUntilChanged)((a, b) => a.crosshairOffset === b.crosshairOffset));
}
function filterOverpaintState(state) {
return state.pipe((0, rxjs_1.throttleTime)(66, undefined, { leading: true, trailing: true }), (0, rxjs_1.distinctUntilChanged)((a, b) => a.boxStart === b.boxStart && (a.mouseDown ? a.crosshairOffset : a.boxEnd) === (b.mouseDown ? b.crosshairOffset : b.boxEnd)));
}
const PlotWrapper = (0, react_1.memo)(({ plugin, values, dataSources, interactivity }) => {
var _a, _b, _c;
const model = (_b = (_a = plugin.managers.structure.hierarchy.current.models.find(m => m.cell.transform.ref === values.model)) === null || _a === void 0 ? void 0 : _a.cell.obj) === null || _b === void 0 ? void 0 : _b.data;
const src = dataSources.find(src => src.id === values.data);
const cif = (_c = plugin.state.data.cells.get(src === null || src === void 0 ? void 0 : src.dataRef)) === null || _c === void 0 ? void 0 : _c.obj;
const block = cif === null || cif === void 0 ? void 0 : cif.data.blocks[src === null || src === void 0 ? void 0 : src.blockIndex];
if (!model || !block || !src)
return (0, jsx_runtime_1.jsx)("div", { className: 'msp-description', children: "Data not available" });
const metric = prop_1.QualityAssessment.pairwiseMetricFromModelArchiveCIF(model, block, src.metridId);
if (!metric)
return (0, jsx_runtime_1.jsx)("div", { className: 'msp-description', children: "Data not available" });
return (0, jsx_runtime_1.jsx)(exports.MAPairwiseScorePlotBase, { interactivity: interactivity, model: model, pairwiseMetric: metric });
}, (prev, next) => prev.values.data === next.values.data && prev.values.model === next.values.model);
function getPropsAndValues(plugin, current) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const models = plugin.managers.structure.hierarchy.current.models;
const cifs = plugin.state.data.selectQ(q => q.root.subtree().ofType(objects_1.PluginStateObject.Format.Cif));
const dataSources = [];
for (const cif of cifs) {
if (!((_a = cif.obj) === null || _a === void 0 ? void 0 : _a.data.blocks))
continue;
let blockIndex = 0;
for (const block of cif.obj.data.blocks) {
for (const pae of prop_1.QualityAssessment.findModelArchiveCIFPAEMetrics(block)) {
dataSources.push({
id: `${cif.transform.ref}:${blockIndex}:${pae.id}`,
metridId: pae.id,
label: `${block.header}: ${pae.name}`,
dataRef: cif.transform.ref,
blockIndex,
});
}
blockIndex++;
}
}
const params = {
model: param_definition_1.ParamDefinition.Select((_b = models[0]) === null || _b === void 0 ? void 0 : _b.cell.transform.ref, models.map(m => { var _a; return [m.cell.transform.ref, (_a = m.cell.obj) === null || _a === void 0 ? void 0 : _a.data.label]; }), { isHidden: models.length <= 1 }),
data: param_definition_1.ParamDefinition.Select((_c = dataSources[0]) === null || _c === void 0 ? void 0 : _c.id, dataSources.map(o => [o.id, o.label]), { isHidden: dataSources.length <= 1 })
};
const values = {
model: (_e = (_d = params.model.options.find(o => o[0] === (current === null || current === void 0 ? void 0 : current.model))) === null || _d === void 0 ? void 0 : _d[0]) !== null && _e !== void 0 ? _e : (_f = params.model.options[0]) === null || _f === void 0 ? void 0 : _f[0],
data: (_h = (_g = params.data.options.find(o => o[0] === (current === null || current === void 0 ? void 0 : current.data))) === null || _g === void 0 ? void 0 : _g[0]) !== null && _h !== void 0 ? _h : (_j = params.data.options[0]) === null || _j === void 0 ? void 0 : _j[0],
};
return { params, values, dataSources };
}
const PlotSize = 1000;
const PlotOffset = 120;
const PlotColors = {
ScoredOverpaint: (0, color_1.Color)(0xFFA500),
ScoredLabel: (0, color_1.Color)(0xBC7100),
AlignedOverpaint: (0, color_1.Color)(0x1AFFBB),
AlignedLabel: (0, color_1.Color)(0x0F8E68),
};
exports.MAPairwiseScorePlotBase = (0, react_1.memo)(({ model, pairwiseMetric, interactivity }) => {
const interactivityRect = (0, react_1.useRef)();
const drawing = (0, plot_1.maDrawPairwiseMetricPNG)(model, pairwiseMetric);
(0, react_1.useEffect)(() => {
if (!drawing) {
interactivity.next({});
return;
}
interactivity.next({ model, drawing });
const moveEvent = (ev) => {
const current = interactivity.value;
if (!current.inside && !current.mouseDown)
return;
const offset = getPlotMouseOffsetBase(interactivityRect.current, ev.clientX, ev.clientY);
interactivity.next({ ...current, crosshairOffset: offset });
};
const mouseUpEvent = (ev) => {
if (!interactivity.value.mouseDown)
return;
const offset = getPlotMouseOffsetBase(interactivityRect.current, ev.clientX, ev.clientY);
interactivity.next({ ...interactivity.value, mouseDown: false, boxEnd: offset });
};
window.addEventListener('mousemove', moveEvent);
window.addEventListener('mouseup', mouseUpEvent);
return () => {
window.removeEventListener('mousemove', moveEvent);
window.removeEventListener('mouseup', mouseUpEvent);
};
}, [model, interactivity, drawing]);
if (!drawing)
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: "Not available" });
const { metric, colorRange, chains, png } = drawing;
const nResidues = metric.residueRange[1] - metric.residueRange[0];
const border = '#333';
const line = '#000';
const legendHeight = 80;
const legendOffsetY = PlotOffset + PlotSize + 50;
const viewBox = '0 0 1140 1270';
return (0, jsx_runtime_1.jsxs)("div", { style: { margin: '8px 8px 0 8px', position: 'relative' }, children: [(0, jsx_runtime_1.jsxs)("svg", { viewBox: viewBox, width: '100%', children: [(0, jsx_runtime_1.jsx)("image", { x: PlotOffset + 1, y: PlotOffset + 1, width: PlotSize - 1, height: PlotSize - 1, href: png }), (0, jsx_runtime_1.jsx)("line", { x1: PlotOffset, x2: PlotOffset + PlotSize, y1: PlotOffset, y2: PlotOffset + PlotSize, style: { stroke: line, strokeDasharray: '15,15' } }), (0, jsx_runtime_1.jsxs)("linearGradient", { id: 'legend-gradient', x1: 0, x2: 1, y1: 0, y2: 0, children: [(0, jsx_runtime_1.jsx)("stop", { offset: '0%', stopColor: colorRange[0] }), (0, jsx_runtime_1.jsx)("stop", { offset: '100%', stopColor: colorRange[1] })] }), (0, jsx_runtime_1.jsx)("rect", { x: PlotOffset, y: legendOffsetY, width: PlotSize, height: legendHeight, style: { fill: 'url(#legend-gradient)', strokeWidth: 1, stroke: border } }), (0, jsx_runtime_1.jsxs)("text", { x: PlotOffset + 20, y: legendOffsetY + legendHeight - 22, style: { fontSize: '45px', fill: 'white', fontWeight: 'bold' }, children: [(0, mol_util_1.round)(metric.valueRange[0], 2), " \u00C5"] }), (0, jsx_runtime_1.jsxs)("text", { x: PlotOffset + PlotSize - 20, y: legendOffsetY + legendHeight - 22, style: { fontSize: '45px', fill: 'black', fontWeight: 'bold' }, textAnchor: 'end', children: [(0, mol_util_1.round)(metric.valueRange[1], 2), " \u00C5"] }), (0, jsx_runtime_1.jsx)("text", { x: PlotOffset + PlotSize / 2, y: legendOffsetY + legendHeight - 22, style: { fontSize: '45px', fill: 'black' }, textAnchor: 'middle', children: "Predicted Aligned Error" }), (0, jsx_runtime_1.jsx)("text", { x: PlotOffset + PlotSize / 2, y: 50, style: { fontSize: '45px', fontWeight: 'bold', fill: color_1.Color.toStyle(PlotColors.ScoredLabel) }, textAnchor: 'middle', children: "Scored Residue" }), (0, jsx_runtime_1.jsx)("text", { className: 'msp-svg-text', style: { fontSize: '50px', fontWeight: 'bold', fill: color_1.Color.toStyle(PlotColors.AlignedLabel) }, transform: `translate(50, ${PlotOffset + PlotSize / 2}) rotate(270)`, textAnchor: 'middle', children: "Aligned Residue" }), chains.map(({ startOffset, endOffset, label }) => {
const textOffset = PlotOffset + PlotSize * (startOffset + (endOffset - startOffset) / 2) / nResidues;
const endLineOffset = PlotOffset + PlotSize * endOffset / nResidues;
const startLineOffset = PlotOffset + PlotSize * startOffset / nResidues;
const seq_id = model.atomicHierarchy.residues.label_seq_id;
const startIndex = seq_id.value(metric.residueRange[0] + startOffset);
const endIndex = seq_id.value(metric.residueRange[0] + endOffset - 1);
return (0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("text", { x: textOffset, y: PlotOffset - 15, className: 'msp-svg-text', style: { fontSize: '40px' }, textAnchor: 'middle', children: [label, " ", startIndex, "-", endIndex] }), (0, jsx_runtime_1.jsxs)("text", { className: 'msp-svg-text', style: { fontSize: '40px' }, transform: `translate(${PlotOffset - 15}, ${textOffset}) rotate(270)`, textAnchor: 'middle', children: [label, " ", startIndex, "-", endIndex] }), (0, jsx_runtime_1.jsx)("line", { x1: startLineOffset, x2: startLineOffset, y1: PlotOffset - 20, y2: PlotOffset + PlotSize + 20, style: { stroke: line, strokeDasharray: '15,15' } }), (0, jsx_runtime_1.jsx)("line", { x1: endLineOffset, x2: endLineOffset, y1: PlotOffset - 20, y2: PlotOffset + PlotSize + 20, style: { stroke: line, strokeDasharray: '15,15' } }), (0, jsx_runtime_1.jsx)("line", { x1: PlotOffset - 20, x2: PlotOffset + PlotSize + 20, y1: startLineOffset, y2: startLineOffset, style: { stroke: line, strokeDasharray: '15,15' } }), (0, jsx_runtime_1.jsx)("line", { x1: PlotOffset - 20, x2: PlotOffset + PlotSize + 20, y1: endLineOffset, y2: endLineOffset, style: { stroke: line, strokeDasharray: '15,15' } })] }, startOffset);
})] }), (0, jsx_runtime_1.jsxs)("svg", { viewBox: viewBox, style: { position: 'absolute', inset: 0 }, children: [(0, jsx_runtime_1.jsx)("rect", { x: PlotOffset, y: PlotOffset, width: PlotSize, height: PlotSize, style: { fill: 'transparent', cursor: 'crosshair' }, ref: interactivityRect, onMouseMove: (ev) => {
interactivity.next({ ...interactivity.value, inside: true });
ev.currentTarget.style.stroke = 'black';
ev.currentTarget.style.strokeWidth = '4px';
}, onMouseDown: (ev) => {
interactivity.next({ ...interactivity.value, mouseDown: true, boxStart: getPlotMouseOffset(ev) });
}, onMouseLeave: (ev) => {
interactivity.next({ ...interactivity.value, inside: false, crosshairOffset: undefined });
ev.currentTarget.style.stroke = '#333';
ev.currentTarget.style.strokeWidth = '1px';
} }), (0, jsx_runtime_1.jsx)(PlotInteractivity, { drawing: drawing, interactity: interactivity })] })] });
}, (prev, next) => prev.model === next.model && prev.pairwiseMetric === next.pairwiseMetric);
function PlotInteractivity({ drawing, interactity }) {
const state = (0, use_behavior_1.useBehavior)(interactity);
const { crosshairOffset, inside } = state;
const box = getBox(state);
const label = getCrosshairLabel(state);
let labelNode;
if (label) {
const labelStyle = label ? { fontSize: '45px', fill: 'black', fontWeight: 'bold', pointerEvents: 'none', userSelect: 'none' } : undefined;
let x, y, anchor;
if (crosshairOffset[0] < PlotSize / 2) {
x = PlotOffset + crosshairOffset[0] + 20;
anchor = 'start';
}
else {
x = PlotOffset + crosshairOffset[0] - 20;
anchor = 'end';
}
if (crosshairOffset[1] < PlotSize / 2) {
y = PlotOffset + crosshairOffset[1] + 65;
}
else {
y = PlotOffset + crosshairOffset[1] - (label[2] ? 3 * 45 : 2 * 45) + 20;
}
labelNode = (0, jsx_runtime_1.jsxs)("text", { y: y, style: labelStyle, textAnchor: anchor, children: [(0, jsx_runtime_1.jsxs)("tspan", { x: x, children: ["S: ", label[0]] }), (0, jsx_runtime_1.jsxs)("tspan", { x: x, dy: 45, children: ["A: ", label[1]] }), label[2] && (0, jsx_runtime_1.jsx)("tspan", { x: x, dy: 45, children: label[2] })] });
}
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [inside && crosshairOffset && (0, jsx_runtime_1.jsx)("line", { x1: crosshairOffset[0] + PlotOffset, x2: crosshairOffset[0] + PlotOffset, y1: PlotOffset, y2: PlotOffset + PlotSize, style: { pointerEvents: 'none', stroke: 'black', strokeDasharray: '5,5' } }), inside && crosshairOffset && (0, jsx_runtime_1.jsx)("line", { x1: PlotOffset, x2: PlotOffset + PlotSize, y1: crosshairOffset[1] + PlotOffset, y2: crosshairOffset[1] + PlotOffset, style: { pointerEvents: 'none', stroke: 'black', strokeDasharray: '5,5' } }), box && (0, jsx_runtime_1.jsx)("rect", { x: PlotOffset + box[0], y: PlotOffset + box[1], width: box[2], height: box[3], style: { stroke: '#eee', strokeWidth: 4, fill: 'rgba(0, 0, 0, 0.15)', pointerEvents: 'none' } }), labelNode] });
}
function getCrosshairLabel(state) {
var _a, _b, _c;
if (!state.drawing || !state.crosshairOffset || !state.inside)
return;
const { drawing } = state;
const rA = getResidueIndex(drawing, (0, interpolate_1.clamp)(state.crosshairOffset[0], 0, PlotSize));
const rB = getResidueIndex(drawing, (0, interpolate_1.clamp)(state.crosshairOffset[1], 0, PlotSize));
const value = (_b = (_a = drawing.metric.values[rA]) === null || _a === void 0 ? void 0 : _a[rB]) !== null && _b !== void 0 ? _b : (_c = drawing.metric.values[rB]) === null || _c === void 0 ? void 0 : _c[rA];
const valueLabel = typeof value === 'number' ? `${(0, mol_util_1.round)(value, 2)} Å` : '';
return [getResidueLabel(drawing, rA), getResidueLabel(drawing, rB), valueLabel];
}
function getResidueIndex(drawing, offset) {
const rI = drawing.metric.residueRange[0] + Math.round(offset / PlotSize * (drawing.metric.residueRange[1] - drawing.metric.residueRange[0] + 1));
return (0, interpolate_1.clamp)(rI, drawing.metric.residueRange[0], drawing.metric.residueRange[1]);
}
function getResidueLabel(drawing, rI) {
const hierarchy = drawing.model.atomicHierarchy;
const asym_id = hierarchy.chains.label_asym_id;
const seq_id = hierarchy.residues.label_seq_id;
const comp_id = hierarchy.atoms.label_comp_id;
return `${asym_id.value(atomic_1.AtomicHierarchy.residueChainIndex(hierarchy, rI))} ${seq_id.value(rI)} ${comp_id.value(atomic_1.AtomicHierarchy.residueFirstAtomIndex(hierarchy, rI))}`;
}
function getBox(state) {
const start = state.boxStart;
const end = state.mouseDown ? state.crosshairOffset : state.boxEnd;
if (!start || !end)
return undefined;
const x = (0, interpolate_1.clamp)(Math.min(start[0], end[0]), 0, PlotSize);
const width = (0, interpolate_1.clamp)(Math.max(start[0], end[0]), 0, PlotSize) - x;
const y = (0, interpolate_1.clamp)(Math.min(start[1], end[1]), 0, PlotSize);
const height = (0, interpolate_1.clamp)(Math.max(start[1], end[1]), 0, PlotSize) - y;
if (width < 1 && height < 1)
return undefined;
return [x, y, width, height];
}
function getPlotMouseOffset(ev) {
return getPlotMouseOffsetBase(ev.currentTarget, ev.clientX, ev.clientY);
}
function getPlotMouseOffsetBase(target, clientX, clientY) {
const rect = target.getBoundingClientRect();
const offsetX = PlotSize * (clientX - rect.left) / rect.width;
const offsetY = PlotSize * (clientY - rect.top) / rect.height;
return [offsetX, offsetY];
}
function findModelRef(plugin, model) {
var _a;
if (!model)
return undefined;
for (const m of plugin.managers.structure.hierarchy.current.models) {
if (((_a = m.cell.obj) === null || _a === void 0 ? void 0 : _a.data) === model)
return m;
}
return undefined;
}
function highlightState(plugin, state) {
var _a, _b, _c;
const structure = (_c = (_b = (_a = findModelRef(plugin, state.model)) === null || _a === void 0 ? void 0 : _a.structures[0]) === null || _b === void 0 ? void 0 : _b.cell.obj) === null || _c === void 0 ? void 0 : _c.data;
if (!state.drawing || !state.crosshairOffset || !state.inside || !structure) {
plugin.managers.interactivity.lociHighlights.clearHighlights();
return;
}
const { drawing } = state;
const rA = getResidueIndex(drawing, (0, interpolate_1.clamp)(state.crosshairOffset[0], 0, PlotSize));
const rB = getResidueIndex(drawing, (0, interpolate_1.clamp)(state.crosshairOffset[1], 0, PlotSize));
const resIdx = structure_1.StructureProperties.residue.key;
const loci = structure_1.StructureQuery.loci((0, generators_1.atoms)({
residueTest: ctx => {
const rI = resIdx(ctx.element);
return rI === rA || rI === rB;
},
}), structure);
plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
}
async function overpaintState(plugin, state) {
var _a, _b;
const tag = 'modelarchive-pae-overpaint';
const overpaints = plugin.state.data.selectQ(q => q.root.subtree().withTag(tag));
const update = plugin.build();
for (const overpaint of overpaints)
update.delete(overpaint);
const model = findModelRef(plugin, state.model);
const structure = (_b = (_a = model === null || model === void 0 ? void 0 : model.structures[0]) === null || _a === void 0 ? void 0 : _a.cell.obj) === null || _b === void 0 ? void 0 : _b.data;
if (!state.drawing || !state.boxStart || !(state.boxEnd || state.crosshairOffset) || !structure) {
if (!overpaints)
return;
return reApplyRepresentationStates(plugin, update);
}
const start = state.boxStart;
const end = state.mouseDown ? state.crosshairOffset : state.boxEnd;
const x0 = (0, interpolate_1.clamp)(Math.min(start[0], end[0]), 0, PlotSize);
const x1 = (0, interpolate_1.clamp)(Math.max(start[0], end[0]), 0, PlotSize);
const y0 = (0, interpolate_1.clamp)(Math.min(start[1], end[1]), 0, PlotSize);
const y1 = (0, interpolate_1.clamp)(Math.max(start[1], end[1]), 0, PlotSize);
if (x1 - x0 <= 1 || y1 - y0 <= 1) {
if (!overpaints)
return;
return reApplyRepresentationStates(plugin, update);
}
const representations = plugin.state.data.selectQ(q => q.byRef(model.cell.transform.ref)
.subtree()
.ofType(objects_1.PluginStateObject.Molecule.Structure.Representation3D));
const resIdx = structure_1.StructureProperties.residue.key;
const startScored = getResidueIndex(state.drawing, x0);
const endScored = getResidueIndex(state.drawing, x1);
const lociScored = structure_1.StructureQuery.loci((0, generators_1.atoms)({
residueTest: ctx => {
const rI = resIdx(ctx.element);
return rI >= startScored && rI <= endScored;
},
}), structure);
const startAligned = getResidueIndex(state.drawing, y0);
const endAligned = getResidueIndex(state.drawing, y1);
const lociAligned = structure_1.StructureQuery.loci((0, generators_1.atoms)({
residueTest: ctx => {
const rI = resIdx(ctx.element);
return rI >= startAligned && rI <= endAligned;
},
}), structure);
const layers = [{
bundle: structure_1.StructureElement.Bundle.fromSubStructure(structure, structure),
color: (0, color_1.Color)(0x777777),
clear: false,
}, {
bundle: structure_1.StructureElement.Bundle.fromLoci(lociScored),
color: PlotColors.ScoredOverpaint,
clear: false,
}, {
bundle: structure_1.StructureElement.Bundle.fromLoci(lociAligned),
color: PlotColors.AlignedOverpaint,
clear: false,
}];
for (const repr of representations) {
update.to(repr).apply(representation_1.OverpaintStructureRepresentation3DFromBundle, { layers }, { tags: [tag], state: { isGhost: true } });
}
return update.commit();
}
async function reApplyRepresentationStates(plugin, update) {
var _a, _b;
await update.commit();
const states = plugin.state.data.selectQ(q => q.root.subtree().ofType(objects_1.PluginStateObject.Molecule.Structure.Representation3DState));
for (const state of states) {
const data = (_a = state.obj) === null || _a === void 0 ? void 0 : _a.data;
if (!data)
continue;
data.repr.setState(data.state);
(_b = plugin.canvas3d) === null || _b === void 0 ? void 0 : _b.update(data.repr);
}
}