molstar
Version:
A comprehensive macromolecular library.
781 lines (780 loc) • 38.6 kB
JavaScript
"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);
}
}