UNPKG

molstar

Version:

A comprehensive macromolecular library.

781 lines (780 loc) 38.6 kB
"use strict"; /** * Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Adam Midlik <midlik@gmail.com> * @author David Sehnal <david.sehnal@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AnnotationFromSourceKinds = exports.AnnotationFromUriKinds = void 0; exports.transformFromRotationTranslation = transformFromRotationTranslation; exports.decomposeRotationMatrix = decomposeRotationMatrix; exports.transformAndInstantiateStructure = transformAndInstantiateStructure; exports.transformAndInstantiateVolume = transformAndInstantiateVolume; exports.collectAnnotationReferences = collectAnnotationReferences; exports.collectAnnotationTooltips = collectAnnotationTooltips; exports.collectInlineTooltips = collectInlineTooltips; exports.collectInlineLabels = collectInlineLabels; exports.isPhantomComponent = isPhantomComponent; exports.structureProps = structureProps; exports.componentPropsFromSelector = componentPropsFromSelector; exports.prettyNameFromSelector = prettyNameFromSelector; exports.labelFromXProps = labelFromXProps; exports.componentFromXProps = componentFromXProps; exports.representationProps = representationProps; exports.alphaForNode = alphaForNode; exports.clippingForNode = clippingForNode; exports.colorThemeForNode = colorThemeForNode; exports.palettePropsFromMVSPalette = palettePropsFromMVSPalette; exports.makeNearestReprMap = makeNearestReprMap; exports.volumeRepresentationProps = volumeRepresentationProps; exports.volumeColorThemeForNode = volumeColorThemeForNode; const linear_algebra_1 = require("../../mol-math/linear-algebra.js"); const volume_1 = require("../../mol-model/volume.js"); const model_1 = require("../../mol-plugin-state/transforms/model.js"); const volume_2 = require("../../mol-plugin-state/transforms/volume.js"); const array_1 = require("../../mol-util/array.js"); const color_1 = require("../../mol-util/color/index.js"); const json_1 = require("../../mol-util/json.js"); const string_1 = require("../../mol-util/string.js"); const annotation_color_theme_1 = require("./components/annotation-color-theme.js"); const representation_1 = require("./components/annotation-label/representation.js"); const multilayer_color_theme_1 = require("./components/multilayer-color-theme.js"); const selector_1 = require("./components/selector.js"); const colors_1 = require("./helpers/colors.js"); const selections_1 = require("./helpers/selections.js"); const utils_1 = require("./helpers/utils.js"); const load_generic_1 = require("./load-generic.js"); const tree_schema_1 = require("./tree/generic/tree-schema.js"); const tree_utils_1 = require("./tree/generic/tree-utils.js"); const mvs_tree_1 = require("./tree/mvs/mvs-tree.js"); const param_types_1 = require("./tree/mvs/param-types.js"); exports.AnnotationFromUriKinds = new Set(['color_from_uri', 'component_from_uri', 'label_from_uri', 'tooltip_from_uri']); exports.AnnotationFromSourceKinds = new Set(['color_from_source', 'component_from_source', 'label_from_source', 'tooltip_from_source']); /** Return a 4x4 matrix representing a rotation followed by a translation */ function transformFromRotationTranslation(rotation, translation) { if (rotation && rotation.length !== 9) throw new Error(`'rotation' param for 'transform' node must be array of 9 elements, found ${rotation}`); if (translation && translation.length !== 3) throw new Error(`'translation' param for 'transform' node must be array of 3 elements, found ${translation}`); const T = linear_algebra_1.Mat4.identity(); if (rotation) { const rotMatrix = linear_algebra_1.Mat3.fromArray((0, linear_algebra_1.Mat3)(), rotation, 0); ensureRotationMatrix(rotMatrix, rotMatrix); linear_algebra_1.Mat4.fromMat3(T, rotMatrix); } if (translation) { linear_algebra_1.Mat4.setTranslation(T, linear_algebra_1.Vec3.fromArray((0, linear_algebra_1.Vec3)(), translation, 0)); } if (!linear_algebra_1.Mat4.isRotationAndTranslation(T)) throw new Error(`'rotation' param for 'transform' is not a valid rotation matrix: ${rotation}`); return T; } function decomposeRotationMatrix(rotation) { if (rotation && rotation.length !== 9) throw new Error(`'rotation' param for 'transform' node must be array of 9 elements, found ${rotation}`); if (rotation) { const rotMatrix = linear_algebra_1.Mat3.fromArray((0, linear_algebra_1.Mat3)(), rotation, 0); ensureRotationMatrix(rotMatrix, rotMatrix); const quat = linear_algebra_1.Quat.fromMat3((0, linear_algebra_1.Quat)(), rotMatrix); const axis = (0, linear_algebra_1.Vec3)(); const angle = linear_algebra_1.Quat.getAxisAngle(axis, quat) * 180 / Math.PI; return { axis, angle }; } return { axis: linear_algebra_1.Vec3.create(1, 0, 0), angle: 0 }; } /** Adjust values in a close-to-rotation matrix `a` to ensure it is a proper rotation matrix * (i.e. its columns and rows are orthonormal and determinant equal to 1, within available precission). */ function ensureRotationMatrix(out, a) { const x = linear_algebra_1.Vec3.fromArray(_tmpVecX, a, 0); const y = linear_algebra_1.Vec3.fromArray(_tmpVecY, a, 3); const z = linear_algebra_1.Vec3.fromArray(_tmpVecZ, a, 6); linear_algebra_1.Vec3.normalize(x, x); linear_algebra_1.Vec3.orthogonalize(y, x, y); linear_algebra_1.Vec3.normalize(z, linear_algebra_1.Vec3.cross(z, x, y)); linear_algebra_1.Mat3.fromColumns(out, x, y, z); return out; } const _tmpVecX = (0, linear_algebra_1.Vec3)(); const _tmpVecY = (0, linear_algebra_1.Vec3)(); const _tmpVecZ = (0, linear_algebra_1.Vec3)(); function transformAndInstantiateStructure(target, node) { return applyTransformAndInstances(target, node, model_1.TransformStructureConformation, model_1.StructureInstances); } function transformAndInstantiateVolume(target, node) { return applyTransformAndInstances(target, node, volume_2.VolumeTransform, volume_2.VolumeInstances); } function applyTransformAndInstances(target, node, transform, instantiate) { let modified = target; for (const { params, ref } of transformProps(node, 'transform')) { modified = load_generic_1.UpdateTarget.apply(modified, transform, params); load_generic_1.UpdateTarget.tag(modified, (0, load_generic_1.mvsRefTags)(ref)); } const instances = transformProps(node, 'instance'); if (instances.length > 0) { modified = load_generic_1.UpdateTarget.apply(modified, instantiate, { transforms: instances.map(i => i.params) }); } return modified; } /** Create an array of props for `TransformStructureConformation` transformers from all 'transform' nodes applied to a 'structure' node. */ function transformProps(node, kind) { const result = []; const transforms = (0, tree_schema_1.getChildren)(node).filter(c => c.kind === kind); for (const transform of transforms) { let matrix = transform.params.matrix; if (!matrix) { const { rotation, translation, rotation_center } = transform.params; if (rotation_center) { const axisAngle = decomposeRotationMatrix(rotation); result.push({ params: { transform: { name: 'components', params: { translation: translation ? linear_algebra_1.Vec3.fromArray((0, linear_algebra_1.Vec3)(), translation, 0) : linear_algebra_1.Vec3.create(0, 0, 0), angle: axisAngle.angle, axis: axisAngle.axis, rotationCenter: rotation_center === 'centroid' ? { name: 'centroid', params: {} } : { name: 'point', params: { point: linear_algebra_1.Vec3.fromArray((0, linear_algebra_1.Vec3)(), rotation_center, 0) } } } } }, ref: transform.ref }); continue; } matrix = transformFromRotationTranslation(rotation, translation); } result.push({ params: { transform: { name: 'matrix', params: { data: matrix, transpose: false } } }, ref: transform.ref }); } return result; } /** Collect distinct annotation specs from all nodes in `tree` and set `context.annotationMap[node]` to respective annotationIds */ function collectAnnotationReferences(tree, context) { const distinctSpecs = {}; (0, tree_utils_1.dfs)(tree, node => { var _a; let spec = undefined; if (exports.AnnotationFromUriKinds.has(node.kind)) { const p = node.params; spec = { source: { name: 'url', params: { url: p.uri, format: p.format } }, schema: p.schema, cifBlock: blockSpec(p.block_header, p.block_index), cifCategory: p.category_name, fieldRemapping: Object.entries(p.field_remapping).map(([key, value]) => ({ standardName: key, actualName: value })), }; } else if (exports.AnnotationFromSourceKinds.has(node.kind)) { const p = node.params; spec = { source: { name: 'source-cif', params: {} }, schema: p.schema, cifBlock: blockSpec(p.block_header, p.block_index), cifCategory: p.category_name, fieldRemapping: Object.entries(p.field_remapping).map(([key, value]) => ({ standardName: key, actualName: value })), }; } if (spec) { const key = (0, json_1.canonicalJsonString)(spec); (_a = distinctSpecs[key]) !== null && _a !== void 0 ? _a : (distinctSpecs[key] = { ...spec, id: (0, utils_1.stringHash)(key) }); context.annotationMap.set(node, distinctSpecs[key].id); } }); return Object.values(distinctSpecs); } function blockSpec(header, index) { if ((0, utils_1.isDefined)(header)) { return { name: 'header', params: { header: header } }; } else { return { name: 'index', params: { index: index !== null && index !== void 0 ? index : 0 } }; } } /** Collect annotation tooltips from all nodes in `tree` and map them to annotationIds. */ function collectAnnotationTooltips(tree, context) { const annotationTooltips = []; (0, tree_utils_1.dfs)(tree, node => { if (node.kind === 'tooltip_from_uri' || node.kind === 'tooltip_from_source') { const annotationId = context.annotationMap.get(node); if (annotationId) { annotationTooltips.push({ annotationId, fieldName: node.params.field_name }); } ; } }); return (0, array_1.arrayDistinct)(annotationTooltips); } /** Collect inline tooltips from all nodes in `tree`. */ function collectInlineTooltips(tree, context) { const inlineTooltips = []; (0, tree_utils_1.dfs)(tree, (node, parent) => { if (node.kind === 'tooltip') { if ((parent === null || parent === void 0 ? void 0 : parent.kind) === 'component') { inlineTooltips.push({ text: node.params.text, selector: componentPropsFromSelector(parent.params.selector), }); } else if ((parent === null || parent === void 0 ? void 0 : parent.kind) === 'component_from_uri' || (parent === null || parent === void 0 ? void 0 : parent.kind) === 'component_from_source') { const p = componentFromXProps(parent, context); if ((0, utils_1.isDefined)(p.annotationId) && (0, utils_1.isDefined)(p.fieldName) && (0, utils_1.isDefined)(p.fieldValues)) { inlineTooltips.push({ text: node.params.text, selector: { name: 'annotation', params: { annotationId: p.annotationId, fieldName: p.fieldName, fieldValues: p.fieldValues, label: p.label || 'Annotation' }, }, }); } } } }); return inlineTooltips; } /** Collect inline labels from all nodes in `tree`. */ function collectInlineLabels(tree, context) { const inlineLabels = []; (0, tree_utils_1.dfs)(tree, (node, parent) => { if (node.kind === 'label') { if ((parent === null || parent === void 0 ? void 0 : parent.kind) === 'component') { inlineLabels.push({ text: node.params.text, position: { name: 'selection', params: { selector: componentPropsFromSelector(parent.params.selector), }, }, }); } else if ((parent === null || parent === void 0 ? void 0 : parent.kind) === 'component_from_uri' || (parent === null || parent === void 0 ? void 0 : parent.kind) === 'component_from_source') { const p = componentFromXProps(parent, context); if ((0, utils_1.isDefined)(p.annotationId) && (0, utils_1.isDefined)(p.fieldName) && (0, utils_1.isDefined)(p.fieldValues)) { inlineLabels.push({ text: node.params.text, position: { name: 'selection', params: { selector: { name: 'annotation', params: { annotationId: p.annotationId, fieldName: p.fieldName, fieldValues: p.fieldValues, label: p.label || 'Annotation' }, }, }, }, }); } } } }); return inlineLabels; } /** Return `true` for components nodes which only serve for tooltip placement (not to be created in the MolStar object hierarchy) */ function isPhantomComponent(node) { if (node.ref !== undefined) return false; if (node.custom !== undefined && Object.keys(node.custom).length > 0) return false; return node.children && node.children.every(child => child.kind === 'tooltip' || child.kind === 'label'); // These nodes could theoretically be removed when converting MVS to Molstar tree, but would get very tricky if we allow nested components } /** Create props for `StructureFromModel` transformer from a structure node. */ function structureProps(node) { var _a; const params = node.params; switch (params.type) { case 'model': return { type: { name: 'model', params: {} }, }; case 'assembly': return { type: { name: 'assembly', params: { id: (_a = params.assembly_id) !== null && _a !== void 0 ? _a : undefined } }, }; case 'symmetry': return { type: { name: 'symmetry', params: { ijkMin: linear_algebra_1.Vec3.ofArray(params.ijk_min), ijkMax: linear_algebra_1.Vec3.ofArray(params.ijk_max) } }, }; case 'symmetry_mates': return { type: { name: 'symmetry-mates', params: { radius: params.radius } } }; default: throw new Error(`NotImplementedError: Loading action for "structure" node, type "${params.type}"`); } } /** Create value for `type` prop for `StructureComponent` transformer based on a MVS selector. */ function componentPropsFromSelector(selector) { if (selector === undefined) { return selector_1.SelectorAll; } else if (typeof selector === 'string') { return { name: 'static', params: selector }; } else if (Array.isArray(selector)) { return { name: 'expression', params: (0, selections_1.rowsToExpression)(selector) }; } else { return { name: 'expression', params: (0, selections_1.rowToExpression)(selector) }; } } /** Return a pretty name for a value of selector param, e.g. "protein" -> 'Protein', {label_asym_id: "A"} -> 'Custom Selection: {label_asym_id: "A"}' */ function prettyNameFromSelector(selector) { if (selector === undefined) { return 'All'; } else if (typeof selector === 'string') { return (0, string_1.stringToWords)(selector); } else if (Array.isArray(selector)) { return `Custom Selection: [${selector.map(tree_utils_1.formatObject).join(', ')}]`; } else { return `Custom Selection: ${(0, tree_utils_1.formatObject)(selector)}`; } } /** Create props for `StructureRepresentation3D` transformer from a label_from_* node. */ function labelFromXProps(node, context) { var _a; const annotationId = context.annotationMap.get(node); const fieldName = node.params.field_name; const nearestReprNode = (_a = context.nearestReprMap) === null || _a === void 0 ? void 0 : _a.get(node); return { type: { name: representation_1.MVSAnnotationLabelRepresentationProvider.name, params: { annotationId, fieldName } }, colorTheme: colorThemeForNode(nearestReprNode, context), }; } /** Create props for `AnnotationStructureComponent` transformer from a component_from_* node. */ function componentFromXProps(node, context) { const annotationId = context.annotationMap.get(node); const { field_name, field_values } = node.params; return { annotationId, fieldName: field_name, fieldValues: field_values ? { name: 'selected', params: field_values.map(v => ({ value: v })) } : { name: 'all', params: {} }, nullIfEmpty: false, }; } /** Create props for `StructureRepresentation3D` transformer from a representation node. */ function representationPropsBase(node) { var _a, _b; const alpha = alphaForNode(node); const params = node.params; switch (params.type) { case 'cartoon': return { type: { name: 'cartoon', params: { alpha, tubularHelices: params.tubular_helices } }, sizeTheme: { name: 'uniform', params: { value: params.size_factor } }, }; case 'backbone': return { type: { name: 'backbone', params: { alpha } }, sizeTheme: { name: 'uniform', params: { value: params.size_factor } }, }; case 'ball_and_stick': return { type: { name: 'ball-and-stick', params: { sizeFactor: ((_a = params.size_factor) !== null && _a !== void 0 ? _a : 1) * 0.5, sizeAspectRatio: 0.5, alpha, ignoreHydrogens: params.ignore_hydrogens } }, }; case 'line': return { type: { name: 'line', params: { alpha, ignoreHydrogens: params.ignore_hydrogens } }, sizeTheme: { name: 'uniform', params: { value: params.size_factor } }, }; case 'spacefill': return { type: { name: 'spacefill', params: { alpha, ignoreHydrogens: params.ignore_hydrogens } }, sizeTheme: { name: 'physical', params: { scale: params.size_factor } }, }; case 'carbohydrate': return { type: { name: 'carbohydrate', params: { alpha, sizeFactor: (_b = params.size_factor) !== null && _b !== void 0 ? _b : 1 } }, }; case 'surface': { return { type: { name: params.surface_type === 'gaussian' ? 'gaussian-surface' : 'molecular-surface', params: { alpha, ignoreHydrogens: params.ignore_hydrogens } }, sizeTheme: { name: 'physical', params: { scale: params.size_factor } }, }; } default: throw new Error('NotImplementedError'); } } function representationProps(node) { var _a, _b; const base = representationPropsBase(node); const clip = clippingForNode(node); if (clip) { base.type.params = { ...(_a = base.type) === null || _a === void 0 ? void 0 : _a.params, clip }; } if ((_b = node.custom) === null || _b === void 0 ? void 0 : _b.molstar_representation_params) { base.type.params = { ...base.type.params, ...node.custom.molstar_representation_params }; } return base; } /** Create value for `type.params.alpha` prop for `StructureRepresentation3D` transformer from a representation node based on 'opacity' nodes in its subtree. */ function alphaForNode(node) { const children = (0, tree_schema_1.getChildren)(node).filter(c => c.kind === 'opacity'); if (children.length > 0) { return children[children.length - 1].params.opacity; } else { return 1; } } function getCommonClipParams(node) { return { invert: !!node.params.invert, transform: node.params.check_transform ? linear_algebra_1.Mat4.fromArray((0, linear_algebra_1.Mat4)(), node.params.check_transform, 0) : linear_algebra_1.Mat4.identity(), }; } function getClipObject(node) { switch (node.params.type) { case 'sphere': return { type: 'sphere', position: linear_algebra_1.Vec3.ofArray(node.params.center), scale: typeof node.params.radius === 'number' ? linear_algebra_1.Vec3.create(2 * node.params.radius, 2 * node.params.radius, 2 * node.params.radius) : linear_algebra_1.Vec3.create(2, 2, 2), rotation: { axis: linear_algebra_1.Vec3.create(1, 0, 0), angle: 0 }, ...getCommonClipParams(node), }; case 'plane': { const up = linear_algebra_1.Vec3.create(0, 1, 0); const n = linear_algebra_1.Vec3.normalize((0, linear_algebra_1.Vec3)(), linear_algebra_1.Vec3.ofArray(node.params.normal)); const axis = linear_algebra_1.Vec3.cross((0, linear_algebra_1.Vec3)(), up, n); const isSingular = linear_algebra_1.Vec3.magnitude(axis) < 1e-6; return { type: 'plane', position: linear_algebra_1.Vec3.ofArray(node.params.point), scale: linear_algebra_1.Vec3.create(1, 1, 1), rotation: { axis: isSingular ? linear_algebra_1.Vec3.unitX : axis, angle: isSingular ? 0 : linear_algebra_1.Vec3.angle(up, n) * 180 / Math.PI, }, ...getCommonClipParams(node), }; } case 'box': const q = linear_algebra_1.Quat.fromMat3((0, linear_algebra_1.Quat)(), linear_algebra_1.Mat3.fromArray((0, linear_algebra_1.Mat3)(), node.params.rotation, 0)); const axis = (0, linear_algebra_1.Vec3)(); const angle = linear_algebra_1.Quat.getAxisAngle(axis, q) * 180 / Math.PI; return { type: 'cube', position: linear_algebra_1.Vec3.ofArray(node.params.center), scale: linear_algebra_1.Vec3.ofArray(node.params.size), rotation: { axis, angle }, ...getCommonClipParams(node), }; default: console.warn(`Mol* MVS: Unsupported clip type "${node.params.type}" in node ${node.ref}.`); } } function clippingForNode(node) { const children = (0, tree_schema_1.getChildren)(node).filter(c => c.kind === 'clip'); if (!children.length) return; const variant = children[0].params.variant === 'object' ? 'instance' : 'pixel'; const objects = children.map(getClipObject).filter(o => !!o); return { variant, objects }; } function hasMolStarUseDefaultColoring(node) { if (!node.custom) return false; return 'molstar_use_default_coloring' in node.custom || 'molstar_color_theme_name' in node.custom; } function customColoring(custom) { var _a, _b; if (custom === null || custom === void 0 ? void 0 : custom.molstar_use_default_coloring) return undefined; return { name: (_a = custom === null || custom === void 0 ? void 0 : custom.molstar_color_theme_name) !== null && _a !== void 0 ? _a : undefined, params: (_b = custom === null || custom === void 0 ? void 0 : custom.molstar_color_theme_params) !== null && _b !== void 0 ? _b : {}, }; } /** Create value for `colorTheme` prop for `StructureRepresentation3D` transformer from a representation node based on color* nodes in its subtree. */ function colorThemeForNode(node, context) { if ((node === null || node === void 0 ? void 0 : node.kind) === 'representation') { const children = (0, tree_schema_1.getChildren)(node).filter(c => c.kind === 'color' || c.kind === 'color_from_uri' || c.kind === 'color_from_source'); if (children.length === 0) { return { name: 'uniform', params: { value: (0, utils_1.decodeColor)(mvs_tree_1.DefaultColor) }, }; } else if (children.length === 1 && hasMolStarUseDefaultColoring(children[0])) { return customColoring(children[0].custom); } else if (children.length === 1 && appliesColorToWholeRepr(children[0])) { return colorThemeForNode(children[0], context); } else { const layers = children.map(c => { const theme = colorThemeForNode(c, context); if (!theme) return undefined; return { theme, selection: componentPropsFromSelector(c.kind === 'color' ? c.params.selector : undefined) }; }).filter(t => !!t); return { name: multilayer_color_theme_1.MultilayerColorThemeName, params: { layers }, }; } } if ((node === null || node === void 0 ? void 0 : node.kind) === 'color') { if (hasMolStarUseDefaultColoring(node)) { return customColoring(node.custom); } return { name: 'uniform', params: { value: (0, utils_1.decodeColor)(node.params.color) }, }; } if ((node === null || node === void 0 ? void 0 : node.kind) === 'color_from_uri' || (node === null || node === void 0 ? void 0 : node.kind) === 'color_from_source') { const annotationId = context.annotationMap.get(node); if (annotationId === undefined) return { name: 'uniform', params: {}, }; const fieldName = node.params.field_name; return { name: annotation_color_theme_1.MVSAnnotationColorThemeProvider.name, params: { annotationId, fieldName, background: multilayer_color_theme_1.NoColor, palette: palettePropsFromMVSPalette(node.params.palette) }, }; } } function appliesColorToWholeRepr(node) { if (node.kind === 'color') { return !(0, utils_1.isDefined)(node.params.selector) || node.params.selector === 'all'; } else { return true; } } const FALLBACK_COLOR = (0, utils_1.decodeColor)(mvs_tree_1.DefaultColor); function palettePropsFromMVSPalette(palette) { var _a, _b, _c; if (!palette) { return { name: 'direct', params: {} }; } if (palette.kind === 'categorical') { const fullParams = objMerge(param_types_1.CategoricalPaletteDefaults, palette); return { name: 'categorical', params: { colors: categoricalPalettePropsFromMVSColors(fullParams.colors), repeatColorList: fullParams.repeat_color_list, sort: fullParams.sort, sortDirection: fullParams.sort_direction, caseInsensitive: fullParams.case_insensitive, setMissingColor: !!fullParams.missing_color, missingColor: (_a = (0, utils_1.decodeColor)(fullParams.missing_color)) !== null && _a !== void 0 ? _a : FALLBACK_COLOR, }, }; } if (palette.kind === 'discrete') { const fullParams = objMerge(param_types_1.DiscretePaletteDefaults, palette); return { name: 'discrete', params: { colors: discretePalettePropsFromMVSColors(fullParams.colors, fullParams.reverse), mode: fullParams.mode, xMin: fullParams.value_domain[0], xMax: fullParams.value_domain[1], }, }; } if (palette.kind === 'continuous') { const fullParams = objMerge(param_types_1.ContinuousPaletteDefaults, palette); const colors = continuousPalettePropsFromMVSColors(fullParams.colors, fullParams.reverse); return { name: 'continuous', params: { colors: colors, mode: fullParams.mode, xMin: fullParams.value_domain[0], xMax: fullParams.value_domain[1], setUnderflowColor: !!fullParams.underflow_color, underflowColor: (_b = (fullParams.underflow_color === 'auto' ? minColor(colors.colors) : (0, utils_1.decodeColor)(fullParams.underflow_color))) !== null && _b !== void 0 ? _b : FALLBACK_COLOR, setOverflowColor: !!fullParams.overflow_color, overflowColor: (_c = (fullParams.overflow_color === 'auto' ? maxColor(colors.colors) : (0, utils_1.decodeColor)(fullParams.overflow_color))) !== null && _c !== void 0 ? _c : FALLBACK_COLOR, }, }; } throw new Error(`NotImplementedError: palettePropsFromMVSPalette is not implemented for palette kind "${palette.kind}"`); } /** Merge properties of two object into a new object. Property values from `second` override those from `first`, but `undefined` is treated as if property missing while `null` as a regular value. */ function objMerge(first, second) { const out = { ...first }; for (const key in second) { const value = second[key]; if (value !== undefined) out[key] = value; } return out; } function categoricalPalettePropsFromMVSColors(colors) { if (typeof colors === 'string') { if (colors in colors_1.MvsNamedColorLists) { const colorList = colors_1.MvsNamedColorLists[colors]; return { name: 'list', params: { kind: 'set', colors: colorList.list } }; } if (colors in colors_1.MvsNamedColorDicts) { const colorDict = colors_1.MvsNamedColorDicts[colors]; return { name: 'dictionary', params: Object.entries(colorDict).map(([value, color]) => ({ value, color })) }; } console.warn(`Could not find named color palette "${colors}"`); } if (Array.isArray(colors)) { return { name: 'list', params: { kind: 'set', colors: colors.map(c => { var _a; return (_a = (0, utils_1.decodeColor)(c)) !== null && _a !== void 0 ? _a : FALLBACK_COLOR; }) } }; } if (typeof colors === 'object') { return { name: 'dictionary', params: Object.entries(colors).map(([value, color]) => { var _a; return ({ value, color: (_a = (0, utils_1.decodeColor)(color)) !== null && _a !== void 0 ? _a : FALLBACK_COLOR }); }) }; } return { name: 'list', params: { kind: 'set', colors: [] } }; } function discretePalettePropsFromMVSColors(colors, reverse) { if (typeof colors === 'string') { if (colors in colors_1.MvsNamedColorLists) { const colorList = colors_1.MvsNamedColorLists[colors]; const list = reverse ? colorList.list.slice().reverse() : colorList.list; const sectionLength = 1 / list.length; return list.map((e, i) => ({ color: color_1.Color.fromColorListEntry(e), fromValue: i * sectionLength, toValue: (i + 1) * sectionLength })); } console.warn(`Could not find named color palette "${colors}"`); } if (Array.isArray(colors) && colors.every(t => typeof t === 'string')) { const list = reverse ? colors.slice().reverse() : colors; const sectionLength = 1 / colors.length; return list.map((c, i) => { var _a; return ({ color: (_a = (0, utils_1.decodeColor)(c)) !== null && _a !== void 0 ? _a : multilayer_color_theme_1.NoColor, fromValue: i * sectionLength, toValue: (i + 1) * sectionLength }); }); } if (Array.isArray(colors) && colors.every(t => Array.isArray(t) && t.length === 2)) { return colors.map((t, i) => { var _a, _b, _c; return ({ color: (_a = (0, utils_1.decodeColor)(t[0])) !== null && _a !== void 0 ? _a : multilayer_color_theme_1.NoColor, fromValue: t[1], toValue: (_c = (_b = colors[i + 1]) === null || _b === void 0 ? void 0 : _b[1]) !== null && _c !== void 0 ? _c : Infinity }); }); } if (Array.isArray(colors) && colors.every(t => Array.isArray(t) && t.length === 3)) { return colors.map(t => { var _a, _b, _c; return ({ color: (_a = (0, utils_1.decodeColor)(t[0])) !== null && _a !== void 0 ? _a : multilayer_color_theme_1.NoColor, fromValue: (_b = t[1]) !== null && _b !== void 0 ? _b : -Infinity, toValue: (_c = t[2]) !== null && _c !== void 0 ? _c : Infinity }); }); } return []; } function continuousPalettePropsFromMVSColors(colors, reverse) { if (typeof colors === 'string') { // Named color list if (colors in colors_1.MvsNamedColorLists) { const colorList = colors_1.MvsNamedColorLists[colors]; const list = reverse ? colorList.list.slice().reverse() : colorList.list; const n = list.length - 1; return { kind: 'interpolate', colors: list.map((col, i) => [color_1.Color.fromColorListEntry(col), i / n]) }; } console.warn(`Could not find named color palette "${colors}"`); } if (Array.isArray(colors)) { if (colors.every(t => Array.isArray(t))) { // Color list with checkpoints // Not applying `reverse` here, as it would have no effect return { kind: 'interpolate', colors: colors.map(t => { var _a; return [(_a = (0, utils_1.decodeColor)(t[0])) !== null && _a !== void 0 ? _a : FALLBACK_COLOR, t[1]]; }) }; } else { // Color list without checkpoints const list = reverse ? colors.slice().reverse() : colors; const n = list.length - 1; return { kind: 'interpolate', colors: list.map((col, i) => { var _a; return [(_a = (0, utils_1.decodeColor)(col)) !== null && _a !== void 0 ? _a : FALLBACK_COLOR, i / n]; }) }; } } return { kind: 'interpolate', colors: [] }; } /** Return the color with the lowest checkpoint, or the first color if checkpoints not available. */ function minColor(colors) { if (colors.length === 0) return undefined; if (colors.every(t => Array.isArray(t))) return color_1.Color.fromColorListEntry(colors.reduce((a, b) => a[1] < b[1] ? a : b)); return color_1.Color.fromColorListEntry(colors[0]); } /** Return the color with the highest checkpoint, or the last color if checkpoints not available. */ function maxColor(colors) { if (colors.length === 0) return undefined; if (colors.every(t => Array.isArray(t))) return color_1.Color.fromColorListEntry(colors.reduce((a, b) => a[1] > b[1] ? a : b)); return color_1.Color.fromColorListEntry(colors[colors.length - 1]); } /** Create a mapping of nearest representation nodes for each node in the tree * (to transfer coloring to label nodes smartly). * Only considers nodes within the same 'structure' subtree. */ function makeNearestReprMap(root) { const map = new Map(); // Propagate up: (0, tree_utils_1.dfs)(root, undefined, (node, parent) => { if (node.kind === 'representation') { map.set(node, node); } if (node.kind !== 'structure' && map.has(node) && parent && !map.has(parent)) { // do not propagate above the lowest structure node map.set(parent, map.get(node)); } }); // Propagate down: (0, tree_utils_1.dfs)(root, (node, parent) => { if (!map.has(node) && parent && map.has(parent)) { map.set(node, map.get(parent)); } }); return map; } /** Create props for `VolumeRepresentation3D` transformer from a representation node. */ function volumeRepresentationProps(node) { var _a, _b; const alpha = alphaForNode(node); const clip = clippingForNode(node); const params = node.params; const isoValue = typeof params.absolute_isovalue === 'number' ? volume_1.Volume.IsoValue.absolute(params.absolute_isovalue) : volume_1.Volume.IsoValue.relative((_a = params.relative_isovalue) !== null && _a !== void 0 ? _a : 0); switch (params.type) { case 'isosurface': const visuals = []; if (params.show_wireframe) visuals.push('wireframe'); if (params.show_faces) visuals.push('solid'); return { type: { name: 'isosurface', params: { alpha, isoValue, visuals, clip } }, }; case 'grid_slice': const isRelative = params.relative_index !== undefined; const dimension = { name: isRelative ? `relative${params.dimension.toUpperCase()}` : params.dimension, params: (_b = params.relative_index) !== null && _b !== void 0 ? _b : params.relative_index }; return { type: { name: 'slice', params: { alpha, dimension, isoValue, clip } }, }; default: throw new Error('NotImplementedError'); } } /** Create value for `colorTheme` prop for `StructureRepresentation3D` transformer from a representation node based on color* nodes in its subtree. */ function volumeColorThemeForNode(node, context) { if ((node === null || node === void 0 ? void 0 : node.kind) !== 'volume_representation') return undefined; const children = (0, tree_schema_1.getChildren)(node).filter(c => c.kind === 'color'); if (children.length === 0) { return { name: 'uniform', params: { value: (0, utils_1.decodeColor)(mvs_tree_1.DefaultColor) }, }; } if (children.length === 1) { return colorThemeForNode(children[0], context); } }