diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index 76c0982a1f65792f5597106ad90b011c01420d79..8b5e1b947e970cc7bf8fcb6533801849423e972e 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -391,6 +391,31 @@ export namespace SecondaryStructureType { } } +/** Maximum accessible surface area observed for amino acids. Taken from: http://dx.doi.org/10.1371/journal.pone.0080635 */ +export const MaxAsa = { + 'ALA': 121.0, + 'ARG': 265.0, + 'ASN': 187.0, + 'ASP': 187.0, + 'CYS': 148.0, + 'GLU': 214.0, + 'GLN': 214.0, + 'GLY': 97.0, + 'HIS': 216.0, + 'ILE': 195.0, + 'LEU': 191.0, + 'LYS': 230.0, + 'MET': 203.0, + 'PHE': 228.0, + 'PRO': 154.0, + 'SER': 143.0, + 'THR': 163.0, + 'TRP': 264.0, + 'TYR': 255.0, + 'VAL': 165.0 +} +export const DefaultMaxAsa = 121.0 + export const VdwRadii = { 'H': 1.1, 'HE': 1.4, diff --git a/src/mol-model/structure/structure/accessible-surface-area.ts b/src/mol-model/structure/structure/accessible-surface-area.ts new file mode 100644 index 0000000000000000000000000000000000000000..754aaf7d6aef86c7cc1443ce7426e2fe1eb9f5ce --- /dev/null +++ b/src/mol-model/structure/structure/accessible-surface-area.ts @@ -0,0 +1,336 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org> + */ + +import Structure from './structure'; +import { Task, RuntimeContext } from 'mol-task'; +import { BitFlags } from 'mol-util'; +import { ParamDefinition as PD } from 'mol-util/param-definition' +import { Vec3 } from 'mol-math/linear-algebra'; +import { isPolymer, ElementSymbol, isNucleic, MoleculeType } from '../model/types'; +import { VdwRadius } from '../model/properties/atomic'; +import { isHydrogen, getElementIdx } from './unit/links/common'; + +namespace AccessibleSurfaceArea { + // Chothia's amino acid and nucleotide atom vdw radii + export const VdWLookup = [ + -1.0, // 0: missing + 1.76, // 1: trigonal C + 1.87, // 2: tetrahedral C + 1.65, // 3: trigonal N + 1.50, // 4: tetrahedral N + 1.40, // 5: O + 1.85, // 6: S + 1.80, // 7: C (nucleic) + 1.60, // 8: N (nucleic) + 1.40 // 9: P (nucleic) + ] // can still be appended on-the-fly for rare elements like selenium + + /** + * Adapts the BioJava implementation by Jose Duarte. That implementation is based on the publication by Shrake, A., and + * J. A. Rupley. "Environment and Exposure to Solvent of Protein Atoms. Lysozyme and Insulin." JMB (1973). + */ + export function compute(structure: Structure, + params: Partial<PD.Values<AccessibleSurfaceAreaComputationParams>> = {}) { + params = { ...PD.getDefaultValues(AccessibleSurfaceAreaComputationParams), ...params }; + return Task.create('Compute Accessible Surface Area', async rtctx => { + return await _compute(rtctx, structure, params); + }).run(); + } + + async function _compute(rtctx: RuntimeContext, structure: Structure, params: Partial<PD.Values<AccessibleSurfaceAreaComputationParams>> = {}): Promise<AccessibleSurfaceArea> { + const ctx = initialize(rtctx, structure, params); + + assignRadiusForHeavyAtoms(ctx); + computePerResidue(ctx); + normalizeAccessibleSurfaceArea(ctx); + + return { + accessibleSurfaceArea: ctx.accessibleSurfaceArea!, + relativeAccessibleSurfaceArea: ctx.relativeAccessibleSurfaceArea!, + buried: (index: number) => ctx.relativeAccessibleSurfaceArea![index] < 0.16 // TODO this doesnt seem super elegant + }; + } + + interface AccessibleSurfaceAreaContext { + rtctx: RuntimeContext, + structure: Structure, + params: Partial<PD.Values<AccessibleSurfaceAreaComputationParams>>, + spherePoints: Vec3[], + cons: number, + maxLookupRadius: number, + atomRadius?: Int8Array, + accessibleSurfaceArea?: Float32Array, + relativeAccessibleSurfaceArea?: Float32Array + } + + function normalizeAccessibleSurfaceArea(ctx: AccessibleSurfaceAreaContext) { + const { accessibleSurfaceArea, relativeAccessibleSurfaceArea, structure } = ctx; + const { residues, derived } = structure.model.atomicHierarchy; + + for (let i = 0; i < residues.label_comp_id.rowCount; ++i) { + // skip entities not part of a polymer chain + if (!ctx.params.nonPolymer) { + if (!isPolymer(derived.residue.moleculeType[i])) continue; + } + + const maxAsa = (MaxAsa as any)[residues.label_comp_id.value(i)]; + const rasa = accessibleSurfaceArea![i] / (maxAsa === undefined ? DefaultMaxAsa : maxAsa); + relativeAccessibleSurfaceArea![i] = rasa; + } + } + + async function computePerResidue(ctx: AccessibleSurfaceAreaContext) { + const { structure, atomRadius, accessibleSurfaceArea, spherePoints, cons, params, maxLookupRadius } = ctx; + const { probeSize } = params; + const { model, elementCount: atomCount } = structure; + const { x, y, z } = model.atomicConformation; + const { residueAtomSegments } = model.atomicHierarchy; + const { lookup3d } = structure; + + const position = (i: number, v: Vec3) => Vec3.set(v, x[i], y[i], z[i]); + const aPos = Vec3.zero(); + const bPos = Vec3.zero(); + let testPoint = Vec3.zero(); + + for (let aI = 0; aI < atomCount; ++aI) { + if (aI % 10000 === 0) { + if (ctx.rtctx.shouldUpdate) { + await ctx.rtctx.update({ message: 'calculating accessible surface area', current: aI, max: atomCount }); + } + } + + const radius1 = VdWLookup[atomRadius![aI]]; + if (radius1 === VdWLookup[0]) continue; + + // pre-filter by lookup3d + // 36275 ms - lookup ~3000 ms + const { count, units, indices, squaredDistances } = lookup3d.find(x[aI], y[aI], z[aI], maxLookupRadius); + + // collect neighbors for each atom + const cutoff1 = probeSize! + probeSize! + radius1; + const neighbors = []; + for (let iI = 0; iI < count; ++iI) { + const bI = units[iI].elements[indices[iI]]; + const radius2 = VdWLookup[atomRadius![bI]]; + if (aI === bI || radius2 === VdWLookup[0]) continue; + + const cutoff2 = (cutoff1 + radius2) * (cutoff1 + radius2); + if (squaredDistances[iI] < cutoff2) { + neighbors[neighbors.length] = bI; + } + } + + // for all neighbors: test all sphere points + position(aI, aPos); + const scalar = probeSize! + radius1; + let accessiblePointCount = 0; + for (let sI = 0; sI < spherePoints.length; ++sI) { + const spherePoint = spherePoints[sI]; + testPoint = [spherePoint[0] * scalar + aPos[0], spherePoint[1] * scalar + aPos[1], spherePoint[2] * scalar + aPos[2]] as Vec3; + let accessible = true; + + for (let _nI = 0; _nI < neighbors.length; ++_nI) { + const nI = neighbors[_nI]; + position(nI, bPos); + const radius3 = VdWLookup[atomRadius![nI]]; + const cutoff3 = (radius3 + probeSize!) * (radius3 + probeSize!); + if (Vec3.squaredDistance(testPoint, bPos) < cutoff3) { + accessible = false; + break; + } + } + + if (accessible) ++accessiblePointCount; + } + + accessibleSurfaceArea![residueAtomSegments.index[aI]] += cons * accessiblePointCount * scalar * scalar; + } + } + + function assignRadiusForHeavyAtoms(ctx: AccessibleSurfaceAreaContext) { + const { structure } = ctx; + const { model, elementCount: atomCount } = structure; + const { atoms: atomInfo, derived, residues, residueAtomSegments } = model.atomicHierarchy; + const { label_comp_id } = residues; + const { moleculeType } = derived.residue; + const { type_symbol, label_atom_id } = atomInfo; + const residueCount = moleculeType.length; + + // with atom and residue count at hand: initialize arrays + ctx.atomRadius = new Int8Array(atomCount - 1); + ctx.accessibleSurfaceArea = new Float32Array(residueCount - 1); + ctx.relativeAccessibleSurfaceArea = new Float32Array(residueCount - 1); + + for (let aI = 0; aI < atomCount; ++aI) { + const rI = residueAtomSegments.index[aI]; + const element = type_symbol.value(aI); + const elementIdx = getElementIdx(element); + // skip hydrogen atoms + if (isHydrogen(elementIdx)) { + ctx.atomRadius[aI] = VdWLookup[0]; + continue; + } + + const residueType = moleculeType[rI]; + // skip non-polymer groups + if (!ctx.params.nonPolymer) { + if (!isPolymer(residueType)) { + ctx.atomRadius[aI] = VdWLookup[0]; + continue; + } + } + + const atomId = label_atom_id.value(aI); + let compId = label_comp_id.value(rI); + + // handle modified residues + const parentId = model.properties.modifiedResidues.parentId.get(compId); + if (parentId !== void 0) compId = parentId; + + if (isNucleic(residueType)) { + ctx.atomRadius[aI] = determineRadiusNucl(atomId, element, compId); + } else if (residueType === MoleculeType.protein) { + ctx.atomRadius[aI] = determineRadiusAmino(atomId, element, compId); + } else { + ctx.atomRadius[aI] = handleNonStandardCase(element); + } + } + } + + /** + * Gets the van der Waals radius of the given atom following the values defined by Chothia (1976) + * J.Mol.Biol.105,1-14. NOTE: the vdw values defined by the paper assume no Hydrogens and thus "inflates" slightly + * the heavy atoms to account for Hydrogens. + */ + function determineRadiusAmino(atomId: string, element: ElementSymbol, compId: string): number { + switch (element) { + case 'O': + return 5; + case 'S': + return 6; + case 'N': + return atomId === 'NZ' ? 4 : 3; + case 'C': + switch (atomId) { + case 'C': case 'CE1': case'CE2': case 'CE3': case 'CH2': case 'CZ': case 'CZ2': case 'CZ3': + return 1; + case 'CA': case 'CB': case 'CE': case 'CG1': case 'CG2': + return 2; + default: + switch (compId) { + case 'PHE': case 'TRP': case 'TYR': case 'HIS': case 'ASP': case 'ASN': + return 1; + case 'PRO': case 'LYS': case 'ARG': case 'MET': case 'ILE': case 'LEU': + return 2; + case 'GLU': case 'GLN': + return atomId === 'CD' ? 1 : 2; + } + } + } + return handleNonStandardCase(element); + } + + function determineRadiusNucl(atomId: string, element: ElementSymbol, compId: string): number { + switch (element) { + case 'C': + return 7; + case 'N': + return 8; + case 'P': + return 9; + case 'O': + return 5; + } + return handleNonStandardCase(element); + } + + function handleNonStandardCase(element: ElementSymbol): number { + const radius = VdwRadius(element); + let index = VdWLookup.indexOf(radius); + if (index === -1) { + // add novel value to lookup array + index = VdWLookup.length; + VdWLookup[index] = radius; + } + return index; + } + + function initialize(rtctx: RuntimeContext, structure: Structure, params: Partial<PD.Values<AccessibleSurfaceAreaComputationParams>>): AccessibleSurfaceAreaContext { + return { + rtctx: rtctx, + structure: structure, + params: params, + spherePoints: generateSpherePoints(params.numberOfSpherePoints!), + cons: 4.0 * Math.PI / params.numberOfSpherePoints!, + maxLookupRadius: 2 * params.probeSize! + 2 * VdWLookup[2] // 2x probe size + 2x largest VdW + } + } + + /** Creates a collection of points on a sphere by the Golden Section Spiral algorithm. */ + function generateSpherePoints(numberOfSpherePoints: number): Vec3[] { + const points: Vec3[] = []; + const inc = Math.PI * (3.0 - Math.sqrt(5.0)); + const offset = 2.0 / numberOfSpherePoints; + for (let k = 0; k < numberOfSpherePoints; ++k) { + const y = k * offset - 1.0 + (offset / 2.0); + const r = Math.sqrt(1.0 - y * y); + const phi = k * inc; + points[points.length] = [Math.cos(phi) * r, y, Math.sin(phi) * r] as Vec3; + } + return points; + } + + /** Maximum accessible surface area observed for amino acids. Taken from: http://dx.doi.org/10.1371/journal.pone.0080635 */ + export const MaxAsa = { + 'ALA': 121.0, + 'ARG': 265.0, + 'ASN': 187.0, + 'ASP': 187.0, + 'CYS': 148.0, + 'GLU': 214.0, + 'GLN': 214.0, + 'GLY': 97.0, + 'HIS': 216.0, + 'ILE': 195.0, + 'LEU': 191.0, + 'LYS': 230.0, + 'MET': 203.0, + 'PHE': 228.0, + 'PRO': 154.0, + 'SER': 143.0, + 'THR': 163.0, + 'TRP': 264.0, + 'TYR': 255.0, + 'VAL': 165.0 + } + export const DefaultMaxAsa = 121.0 + + export const AccessibleSurfaceAreaComputationParams = { + numberOfSpherePoints: PD.Numeric(92, {}, { description: 'number of sphere points to sample per atom: 92 (original paper), 960 (BioJava), 3000 (EPPIC) - see Shrake A, Rupley JA: Environment and exposure to solvent of protein atoms. Lysozyme and insulin. J Mol Biol 1973.' }), + probeSize: PD.Numeric(1.4, {}, { description: 'corresponds to the size of a water molecule: 1.4 (original paper), 1.5 (occassionally used)' }), + buriedRasaThreshold: PD.Numeric(0.16, { min: 0.0, max: 1.0 }, { description: 'below this cutoff of relative accessible surface area a residue will be considered buried - see: Rost B, Sander C: Conservation and prediction of solvent accessibility in protein families. Proteins 1994.' }), + nonPolymer: PD.Boolean(false, { description: 'Include non-polymer atoms in computation.' }) + } + export type AccessibleSurfaceAreaComputationParams = typeof AccessibleSurfaceAreaComputationParams + + export namespace SolventAccessibility { + export const is: (t: number, f: Flag) => boolean = BitFlags.has + export const create: (f: Flag) => number = BitFlags.create + export const enum Flag { + _ = 0x0, + BURIED = 0x1, + ACCESSIBLE = 0x2 + } + } +} + +interface AccessibleSurfaceArea { + readonly accessibleSurfaceArea?: ArrayLike<number> + readonly relativeAccessibleSurfaceArea?: ArrayLike<number> + buried(index: number): boolean +} + +export { AccessibleSurfaceArea } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 258141272e6fe52857ea23f20a2619b94e37fdf9..28869515a8ab573b618a463aaaf92f5b3b84927c 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -224,7 +224,7 @@ namespace Unit { rings: ValueRef.create(void 0), polymerElements: ValueRef.create(void 0), gapElements: ValueRef.create(void 0), - nucleotideElements: ValueRef.create(void 0), + nucleotideElements: ValueRef.create(void 0) }; } diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts index 2427ba51e2d0bb44a4200fe4b0384a63f8669664..d63fb8721dcb5c57df5cb326139d0a8a9b12bc04 100644 --- a/src/mol-model/structure/structure/util/lookup3d.ts +++ b/src/mol-model/structure/structure/util/lookup3d.ts @@ -5,12 +5,30 @@ */ import Structure from '../structure' -import { Lookup3D, GridLookup3D, Result, Box3D, Sphere3D } from 'mol-math/geometry'; +import { Lookup3D, GridLookup3D, Box3D, Sphere3D, Result } from 'mol-math/geometry'; import { Vec3 } from 'mol-math/linear-algebra'; import { computeStructureBoundary } from './boundary'; import { OrderedSet } from 'mol-data/int'; import { StructureUniqueSubsetBuilder } from './unique-subset-builder'; import StructureElement from '../element'; +import Unit from '../unit'; + +export interface StructureResult extends Result<number> { + units: Unit[] +} + +export namespace StructureResult { + export function add(result: StructureResult, unit: Unit, index: number, distSq: number) { + result.indices[result.count] = index; + result.units[result.count] = unit; + result.squaredDistances[result.count] = distSq; + result.count++; + } + + export function create(): StructureResult { + return { count: 0, indices: [], units: [], squaredDistances: [] }; + } +} export class StructureLookup3D { private unitLookup: Lookup3D; @@ -20,28 +38,27 @@ export class StructureLookup3D { return this.unitLookup.find(x, y, z, radius); } - // TODO: find another efficient way how to implement this instead of using "tuple". - // find(x: number, y: number, z: number, radius: number): Result<Element.Packed> { - // Result.reset(this.result); - // const { units } = this.structure; - // const closeUnits = this.unitLookup.find(x, y, z, radius); - // if (closeUnits.count === 0) return this.result; - - // for (let t = 0, _t = closeUnits.count; t < _t; t++) { - // const unit = units[closeUnits.indices[t]]; - // Vec3.set(this.pivot, x, y, z); - // if (!unit.conformation.operator.isIdentity) { - // Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse); - // } - // const unitLookup = unit.lookup3d; - // const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius); - // for (let j = 0, _j = groupResult.count; j < _j; j++) { - // Result.add(this.result, Element.Packed.create(unit.id, groupResult.indices[j]), groupResult.squaredDistances[j]); - // } - // } - - // return this.result; - // } + private result: StructureResult = StructureResult.create(); + find(x: number, y: number, z: number, radius: number): StructureResult { + Result.reset(this.result); + const { units } = this.structure; + const closeUnits = this.unitLookup.find(x, y, z, radius); + if (closeUnits.count === 0) return this.result; + + for (let t = 0, _t = closeUnits.count; t < _t; t++) { + const unit = units[closeUnits.indices[t]]; + Vec3.set(this.pivot, x, y, z); + if (!unit.conformation.operator.isIdentity) { + Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse); + } + const unitLookup = unit.lookup3d; + const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius); + for (let j = 0, _j = groupResult.count; j < _j; j++) { + StructureResult.add(this.result, unit, groupResult.indices[j], groupResult.squaredDistances[j]); + } + } + return this.result; + } findIntoBuilder(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder) { const { units } = this.structure; @@ -98,8 +115,6 @@ export class StructureLookup3D { } } - - check(x: number, y: number, z: number, radius: number): boolean { const { units } = this.structure; const closeUnits = this.unitLookup.find(x, y, z, radius); diff --git a/src/tests/browser/render-asa.ts b/src/tests/browser/render-asa.ts new file mode 100644 index 0000000000000000000000000000000000000000..a77239225bb01a525e33481bcd6a0403c341b757 --- /dev/null +++ b/src/tests/browser/render-asa.ts @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import './index.html' +import { Canvas3D } from 'mol-canvas3d/canvas3d'; +import CIF, { CifFrame } from 'mol-io/reader/cif' +import { Model, Structure, StructureElement, Unit } from 'mol-model/structure'; +import { ColorTheme, LocationColor } from 'mol-theme/color'; +import { SizeTheme } from 'mol-theme/size'; +import { CartoonRepresentationProvider } from 'mol-repr/structure/representation/cartoon'; +import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; +import { AccessibleSurfaceArea } from 'mol-model/structure/structure/accessible-surface-area'; +import { Color, ColorScale } from 'mol-util/color'; +import { Location } from 'mol-model/location'; +import { ThemeDataContext } from 'mol-theme/theme'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { ColorListName, ColorListOptions } from 'mol-util/color/scale'; + +const parent = document.getElementById('app')! +parent.style.width = '100%' +parent.style.height = '100%' + +const canvas = document.createElement('canvas') +canvas.style.width = '100%' +canvas.style.height = '100%' +parent.appendChild(canvas) + +const canvas3d = Canvas3D.create(canvas, parent) +canvas3d.animate() + + +async function parseCif(data: string|Uint8Array) { + const comp = CIF.parse(data); + const parsed = await comp.run(); + if (parsed.isError) throw parsed; + return parsed.result; +} + +async function downloadCif(url: string, isBinary: boolean) { + const data = await fetch(url); + return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text()); +} + +async function downloadFromPdb(pdb: string) { + // const parsed = await downloadCif(`https://files.rcsb.org/download/${pdb}.cif`, false); + const parsed = await downloadCif(`https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${pdb}`, true); + return parsed.blocks[0]; +} + +async function getModels(frame: CifFrame) { + return await trajectoryFromMmCIF(frame).run(); +} + +async function getStructure(model: Model) { + return Structure.ofModel(model); +} + +const reprCtx = { + colorThemeRegistry: ColorTheme.createRegistry(), + sizeThemeRegistry: SizeTheme.createRegistry() +} +function getCartoonRepr() { + return CartoonRepresentationProvider.factory(reprCtx, CartoonRepresentationProvider.getParams) +} + +let accessibleSurfaceArea: AccessibleSurfaceArea; +async function init(props = {}) { + const cif = await downloadFromPdb( + // '3j3q' + // '1aon' + // '1acj' + // '1pga' + '1brr' + // '1hrc' + ) + const models = await getModels(cif) + const structure = await getStructure(models[0]) + + // async compute ASA + accessibleSurfaceArea = await AccessibleSurfaceArea.compute(structure) + + const cartoonRepr = getCartoonRepr() + + // create color theme + cartoonRepr.setTheme({ + color: AccessibleSurfaceAreaColorTheme(reprCtx, { ...PD.getDefaultValues(AccessibleSurfaceAreaColorThemeParams), ...props }), + size: reprCtx.sizeThemeRegistry.create('uniform', { structure }) + }) + await cartoonRepr.createOrUpdate({ ...CartoonRepresentationProvider.defaultValues, quality: 'auto' }, structure).run() + + canvas3d.add(cartoonRepr) + canvas3d.resetCamera() +} + +init() + +const DefaultColor = Color(0xFFFFFF) +const Description = 'Assigns a color based on the relative accessible surface area of a residue.' + +export const AccessibleSurfaceAreaColorThemeParams = { + list: PD.ColorScale<ColorListName>('Rainbow', ColorListOptions) +} +export type AccessibleSurfaceAreaColorThemeParams = typeof AccessibleSurfaceAreaColorThemeParams +export function getAccessibleSurfaceAreaColorThemeParams(ctx: ThemeDataContext) { + return AccessibleSurfaceAreaColorThemeParams // TODO return copy +} + +export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD.Values<AccessibleSurfaceAreaColorThemeParams>): ColorTheme<AccessibleSurfaceAreaColorThemeParams> { + let color: LocationColor = () => DefaultColor + const scale = ColorScale.create({ + listOrName: props.list, + minLabel: '0.0 (buried)', + maxLabel: '1.0 (exposed)', + domain: [0.0, 1.0] + }) + color = (location: Location): Color => { + if (StructureElement.isLocation(location)) { + if (Unit.isAtomic(location.unit)) { + const value = accessibleSurfaceArea.relativeAccessibleSurfaceArea![location.unit.residueIndex[location.element]]; + return value !== AccessibleSurfaceArea.VdWLookup[0] /* signals missing value */ ? scale.color(value) : DefaultColor; + } + } + + return DefaultColor + } + + return { + factory: AccessibleSurfaceAreaColorTheme, + granularity: 'group', + color, + props, + description: Description, + legend: scale ? scale.legend : undefined + } +} + +export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<AccessibleSurfaceAreaColorThemeParams> = { + label: 'Accessible Surface Area', + factory: AccessibleSurfaceAreaColorTheme, + getParams: getAccessibleSurfaceAreaColorThemeParams, + defaultValues: PD.getDefaultValues(AccessibleSurfaceAreaColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 90c91d820e6e4c329f4e8c77eed90565f13694ff..28c3be13aab7da7eb0e18b7b6dfeacd9edfa8b97 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -104,6 +104,7 @@ module.exports = [ createApp('model-server-query'), createBrowserTest('font-atlas'), + createBrowserTest('render-asa'), createBrowserTest('marching-cubes'), createBrowserTest('render-lines'), createBrowserTest('render-mesh'),