Skip to content
Snippets Groups Projects
Commit dddf6dde authored by David Sehnal's avatar David Sehnal
Browse files

Merge branch 'custom-repr' of https://github.com/molstar/molstar-proto

parents 4fde5618 e225ee24
Branches
Tags
No related merge requests found
Showing
with 1155 additions and 168 deletions
File suppressed by a .gitattributes entry, the file's encoding is unsupported, or the file size exceeds the limit.
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import Viewer from 'mol-view/viewer';
import { getCifFromUrl, getModelsFromMmcif, getCifFromFile } from './util';
import { StructureView } from './structure-view';
import { BehaviorSubject } from 'rxjs';
import { CifBlock } from 'mol-io/reader/cif';
export class App {
viewer: Viewer
container: HTMLDivElement | null = null;
canvas: HTMLCanvasElement | null = null;
structureView: StructureView | null = null;
pdbIdLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null)
initViewer(_canvas: HTMLCanvasElement, _container: HTMLDivElement) {
this.canvas = _canvas
this.container = _container
try {
this.viewer = Viewer.create(this.canvas, this.container)
this.viewer.animate()
return true
} catch (e) {
console.error(e)
return false
}
}
async loadCif(cif: CifBlock, assemblyId?: string) {
const models = await getModelsFromMmcif(cif)
this.structureView = await StructureView(this.viewer, models, { assemblyId })
this.pdbIdLoaded.next(this.structureView)
}
async loadPdbId(id: string, assemblyId?: string) {
if (this.structureView) this.structureView.destroy()
const cif = await getCifFromUrl(`https://files.rcsb.org/download/${id}.cif`)
this.loadCif(cif, assemblyId)
}
async loadCifFile(file: File) {
if (this.structureView) this.structureView.destroy()
const cif = await getCifFromFile(file)
this.loadCif(cif)
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry';
import { Table } from 'mol-data/db';
import { Color, ColorScale } from 'mol-util/color';
import { MeshBuilder } from 'mol-geo/mesh/mesh-builder';
import { Tensor } from 'mol-math/linear-algebra';
import { addSphere } from 'mol-geo/mesh/builder/sphere';
import { addCylinder } from 'mol-geo/mesh/builder/cylinder';
import { Shape } from 'mol-model/shape';
import { ColorTheme } from 'mol-view/theme/color';
import { Location } from 'mol-model/location';
import { StructureElement, Unit, StructureProperties } from 'mol-model/structure';
export function getAxesShape(featureId: number, assemblySymmetry: AssemblySymmetry) {
const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature
const feature = Table.pickRow(f, i => f.id.value(i) === featureId)
if (!feature) return
const axes = assemblySymmetry.getAxes(featureId)
if (!axes._rowCount) return
const vectorSpace = AssemblySymmetry.Schema.rcsb_assembly_symmetry_axis.start.space;
const colors: Color[] = []
const labels: string[] = []
const radius = 0.4
const cylinderProps = { radiusTop: radius, radiusBottom: radius }
const meshBuilder = MeshBuilder.create(256, 128)
for (let i = 0, il = axes._rowCount; i < il; ++i) {
const start = Tensor.toVec3(vectorSpace, axes.start.value(i))
const end = Tensor.toVec3(vectorSpace, axes.end.value(i))
meshBuilder.setGroup(i)
addSphere(meshBuilder, start, radius, 2)
addSphere(meshBuilder, end, radius, 2)
addCylinder(meshBuilder, start, end, 1, cylinderProps)
colors.push(Color(0xCCEE11))
labels.push(`Axis ${i + 1} for ${feature.symmetry_value} ${feature.type.toLowerCase()} symmetry`)
}
const mesh = meshBuilder.getMesh()
const shape = Shape.create('Axes', mesh, colors, labels)
return shape
}
function getAsymId(unit: Unit): StructureElement.Property<string> {
switch (unit.kind) {
case Unit.Kind.Atomic:
return StructureProperties.chain.auth_asym_id // TODO
case Unit.Kind.Spheres:
case Unit.Kind.Gaussians:
return StructureProperties.coarse.asym_id
}
}
function memberKey (asym_id: string, oper_list_id?: number) {
return `${asym_id}|${oper_list_id}`
}
export function getClusterColorTheme(featureId: number, assemblySymmetry: AssemblySymmetry): ColorTheme {
const DefaultColor = Color(0xCCCCCC)
const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature
const feature = Table.pickRow(f, i => f.id.value(i) === featureId)
if (!feature) return { granularity: 'uniform', color: () => DefaultColor }
const clusters = assemblySymmetry.getClusters(featureId)
if (!clusters._rowCount) return { granularity: 'uniform', color: () => DefaultColor }
const clusterByMember = new Map<string, number>()
for (let i = 0, il = clusters._rowCount; i < il; ++i) {
clusters.members.value(i).forEach(m => {
const ms = m.split('_')
const asym_id = ms[0]
const oper_list_id = ms.length === 2 ? parseInt(ms[1]) : undefined
clusterByMember.set(memberKey(asym_id, oper_list_id), i)
})
}
const scale = ColorScale.create({ domain: [ 0, clusters._rowCount - 1 ] })
return {
granularity: 'instance',
color: (location: Location): Color => {
if (StructureElement.isLocation(location)) {
const ns = location.unit.conformation.operator.name.split('-')
const asym_id = getAsymId(location.unit)
const oper_list_id = ns.length === 2 ? parseInt(ns[1]) : undefined
const cluster = clusterByMember.get(memberKey(asym_id(location), oper_list_id))
return cluster !== undefined ? scale.color(cluster) : DefaultColor
}
return DefaultColor
}
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { StructureView } from '../structure-view';
import { App } from '../app';
import { Viewport } from './viewport';
import { StructureViewComponent } from './structure-view';
// export function FileInput (props: {
// accept: string
// onChange: (v: FileList | null) => void,
// }) {
// return <input
// accept={props.accept || '*.*'}
// type='file'
// onChange={e => props.onChange.call(null, e.target.files)}
// />
// }
export interface AppProps {
app: App
}
export interface AppState {
structureView: StructureView | null
}
export class AppComponent extends React.Component<AppProps, AppState> {
state = {
structureView: this.props.app.structureView,
}
componentDidMount() {
this.props.app.pdbIdLoaded.subscribe((structureView) => {
this.setState({
structureView: this.props.app.structureView
})
})
}
render() {
const { structureView } = this.state
return <div style={{width: '100%', height: '100%'}}>
<div style={{left: '0px', right: '350px', height: '100%', position: 'absolute'}}>
<Viewport app={this.props.app} />
</div>
<div style={{width: '330px', paddingLeft: '10px', paddingRight: '10px', right: '0px', height: '100%', position: 'absolute', overflow: 'auto'}}>
<div style={{marginTop: '10px'}}>
<span>Load PDB ID </span>
<input
type='text'
onKeyDown={e => {
if (e.keyCode === 13) {
const value = e.currentTarget.value.trim()
if (value) {
this.props.app.loadPdbId(value)
}
}
}}
/>
</div>
<div>
<span>Load CIF file </span>
<input
accept='*.cif'
type='file'
onChange={e => {
if (e.target.files) this.props.app.loadCifFile(e.target.files[0])
}}
/>
</div>
<hr/>
<div style={{marginBottom: '10px'}}>
{structureView ? <StructureViewComponent structureView={structureView} /> : ''}
</div>
</div>
</div>;
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { StructureRepresentation, StructureProps } from 'mol-geo/representation/structure';
import Viewer from 'mol-view/viewer';
import { VisualQuality, VisualQualityNames } from 'mol-geo/representation/util';
import { ColorThemeProps, ColorThemeName, ColorThemeNames, ColorTheme } from 'mol-view/theme/color';
import { Color } from 'mol-util/color';
export interface StructureRepresentationComponentProps {
viewer: Viewer
representation: StructureRepresentation<StructureProps>
}
export interface StructureRepresentationComponentState {
label: string
visible: boolean
quality: VisualQuality
colorTheme: ColorThemeProps
}
export class StructureRepresentationComponent extends React.Component<StructureRepresentationComponentProps, StructureRepresentationComponentState> {
state = {
label: this.props.representation.label,
visible: this.props.representation.props.visible,
quality: this.props.representation.props.quality,
colorTheme: this.props.representation.props.colorTheme,
}
componentWillMount() {
const repr = this.props.representation
this.setState({
...this.state,
label: repr.label,
visible: repr.props.visible,
quality: repr.props.quality,
colorTheme: repr.props.colorTheme,
})
}
async update(state: Partial<StructureRepresentationComponentState>) {
const repr = this.props.representation
const props: Partial<StructureProps> = {}
if (state.visible !== undefined) props.visible = state.visible
if (state.quality !== undefined) props.quality = state.quality
if (state.colorTheme !== undefined) props.colorTheme = state.colorTheme
await repr.createOrUpdate(props).run()
this.props.viewer.add(repr)
this.props.viewer.requestDraw(true)
console.log(this.props.viewer.stats)
const newState = {
...this.state,
visible: repr.props.visible,
quality: repr.props.quality,
colorTheme: repr.props.colorTheme,
}
this.setState(newState)
}
render() {
const { label, visible, quality, colorTheme } = this.state
const ct = ColorTheme(colorTheme)
if (ct.legend && ct.legend.kind === 'scale-legend') {
// console.log(`linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})`)
}
return <div>
<div>
<h4>{label}</h4>
</div>
<div>
<div>
<span>Visible </span>
<button onClick={(e) => this.update({ visible: !visible }) }>
{visible ? 'Hide' : 'Show'}
</button>
</div>
<div>
<span>Quality </span>
<select value={quality} onChange={(e) => this.update({ quality: e.target.value as VisualQuality }) }>
{VisualQualityNames.map(name => <option key={name} value={name}>{name}</option>)}
</select>
</div>
<div>
<span>Color Theme </span>
<select value={colorTheme.name} onChange={(e) => this.update({ colorTheme: { name: e.target.value as ColorThemeName } }) }>
{ColorThemeNames.map(name => <option key={name} value={name}>{name}</option>)}
</select>
{ct.description ? <div><i>{ct.description}</i></div> : ''}
{
ct.legend && ct.legend.kind === 'scale-legend'
? <div
style={{
width: '100%',
height: '30px',
background: `linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})`
}}
>
<span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.min}</span>
<span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.max}</span>
</div>
: ct.legend && ct.legend.kind === 'table-legend'
? <div>
{ct.legend.table.map((value, i) => {
const [name, color] = value
return <div key={i} style={{minWidth: '60px', marginRight: '5px', display: 'inline-block'}}>
<div style={{width: '30px', height: '20px', backgroundColor: Color.toStyle(color), display: 'inline-block'}}></div>
{name}
</div>
})}
</div>
: ''
}
</div>
</div>
</div>;
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { StructureView } from '../structure-view';
import { StructureRepresentation } from 'mol-geo/representation/structure';
import { StructureRepresentationComponent } from './structure-representation';
// export function FileInput (props: {
// accept: string
// onChange: (v: FileList | null) => void,
// }) {
// return <input
// accept={props.accept || '*.*'}
// type='file'
// onChange={e => props.onChange.call(null, e.target.files)}
// />
// }
export interface StructureViewComponentProps {
structureView: StructureView
}
export interface StructureViewComponentState {
structureView: StructureView
label: string
modelId: number
modelIds: { id: number, label: string }[]
assemblyId: string
assemblyIds: { id: string, label: string }[]
symmetryFeatureId: number
symmetryFeatureIds: { id: number, label: string }[]
active: { [k: string]: boolean }
structureRepresentations: { [k: string]: StructureRepresentation<any> }
}
export class StructureViewComponent extends React.Component<StructureViewComponentProps, StructureViewComponentState> {
state = this.stateFromStructureView(this.props.structureView)
private stateFromStructureView(sv: StructureView) {
return {
structureView: sv,
label: sv.label,
modelId: sv.modelId,
modelIds: sv.getModelIds(),
assemblyId: sv.assemblyId,
assemblyIds: sv.getAssemblyIds(),
symmetryFeatureId: sv.symmetryFeatureId,
symmetryFeatureIds: sv.getSymmetryFeatureIds(),
active: sv.active,
structureRepresentations: sv.structureRepresentations
}
}
componentWillMount() {
this.setState(this.stateFromStructureView(this.props.structureView))
}
componentDidMount() {
const sv = this.props.structureView
this.props.structureView.updated.subscribe(() => this.setState({
symmetryFeatureIds: sv.getSymmetryFeatureIds(),
structureRepresentations: sv.structureRepresentations
}))
}
componentWillReceiveProps(nextProps: StructureViewComponentProps) {
if (nextProps.structureView !== this.props.structureView) {
this.setState(this.stateFromStructureView(nextProps.structureView))
nextProps.structureView.updated.subscribe(() => this.setState({
symmetryFeatureIds: nextProps.structureView.getSymmetryFeatureIds(),
structureRepresentations: nextProps.structureView.structureRepresentations
}))
}
}
async update(state: Partial<StructureViewComponentState>) {
const sv = this.state.structureView
if (state.modelId !== undefined) await sv.setModel(state.modelId)
if (state.assemblyId !== undefined) await sv.setAssembly(state.assemblyId)
if (state.symmetryFeatureId !== undefined) await sv.setSymmetryFeature(state.symmetryFeatureId)
this.setState(this.stateFromStructureView(sv))
}
render() {
const { structureView, label, modelIds, assemblyIds, symmetryFeatureIds, active, structureRepresentations } = this.state
const modelIdOptions = modelIds.map(m => {
return <option key={m.id} value={m.id}>{m.label}</option>
})
const assemblyIdOptions = assemblyIds.map(a => {
return <option key={a.id} value={a.id}>{a.label}</option>
})
const symmetryFeatureIdOptions = symmetryFeatureIds.map(f => {
return <option key={f.id} value={f.id}>{f.label}</option>
})
return <div>
<div>
<h2>{label}</h2>
</div>
<div>
<div>
<span>Model </span>
<select
style={{width: '100px'}}
value={this.state.modelId}
onChange={(e) => {
this.update({ modelId: parseInt(e.target.value) })
}}
>
{modelIdOptions}
</select>
<span> </span>
<input type='range'
value={this.state.modelId}
min={Math.min(...modelIds.map(m => m.id))}
max={Math.max(...modelIds.map(m => m.id))}
step='1'
onInput={(e) => {
this.update({ modelId: parseInt(e.currentTarget.value) })
}}
>
</input>
</div>
<div>
<span>Assembly </span>
<select
style={{width: '150px'}}
value={this.state.assemblyId}
onChange={(e) => {
this.update({ assemblyId: e.target.value })
}}
>
{assemblyIdOptions}
</select>
</div>
<div>
<span>Symmetry Feature </span>
<select
style={{width: '150px'}}
value={this.state.symmetryFeatureId}
onChange={(e) => {
this.update({ symmetryFeatureId: parseInt(e.target.value) })
}}
>
{symmetryFeatureIdOptions}
</select>
</div>
<div>
<h4>Active</h4>
{ Object.keys(active).map((k, i) => {
return <div key={i}>
<input
type='checkbox'
checked={active[k]}
onChange={(e) => {
const sv = structureView
if (k === 'symmetryAxes') {
sv.setSymmetryAxes(e.target.checked)
} else if (Object.keys(sv.structureRepresentations).includes(k)) {
sv.setStructureRepresentation(k, e.target.checked)
}
}}
/> {k}
</div>
} ) }
</div>
<div>
<h3>Structure Representations</h3>
{ Object.keys(structureRepresentations).map((k, i) => {
if (active[k]) {
return <div key={i}>
<StructureRepresentationComponent
representation={structureRepresentations[k]}
viewer={structureView.viewer}
/>
</div>
} else {
return ''
}
} ) }
</div>
</div>
</div>;
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { App } from '../app';
import { MarkerAction } from 'mol-geo/util/marker-data';
import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci';
import { labelFirst } from 'mol-view/label';
interface ViewportProps {
app: App
}
interface ViewportState {
noWebGl: boolean,
info: string
}
export class Viewport extends React.Component<ViewportProps, ViewportState> {
private container: HTMLDivElement | null = null;
private canvas: HTMLCanvasElement | null = null;
state: ViewportState = {
noWebGl: false,
info: ''
};
handleResize() {
this.props.app.viewer.handleResize()
}
componentDidMount() {
if (!this.canvas || !this.container || !this.props.app.initViewer(this.canvas, this.container)) {
this.setState({ noWebGl: true });
}
this.handleResize()
const viewer = this.props.app.viewer
viewer.input.resize.subscribe(() => this.handleResize())
let prevLoci: Loci = EmptyLoci
viewer.input.move.subscribe(({x, y, inside, buttons}) => {
if (!inside || buttons) return
const p = viewer.identify(x, y)
if (p) {
const loci = viewer.getLoci(p)
if (!areLociEqual(loci, prevLoci)) {
viewer.mark(prevLoci, MarkerAction.RemoveHighlight)
viewer.mark(loci, MarkerAction.Highlight)
prevLoci = loci
const label = labelFirst(loci)
const info = `${label}`
this.setState({ info })
}
}
})
}
componentWillUnmount() {
if (super.componentWillUnmount) super.componentWillUnmount();
// TODO viewer cleanup
}
renderMissing() {
return <div>
<div>
<p><b>WebGL does not seem to be available.</b></p>
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
<p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p>
</div>
</div>
}
render() {
if (this.state.noWebGl) return this.renderMissing();
return <div style={{ backgroundColor: 'rgb(0, 0, 0)', width: '100%', height: '100%'}}>
<div ref={elm => this.container = elm} style={{width: '100%', height: '100%'}}>
<canvas ref={elm => this.canvas = elm}></canvas>
</div>
<div
style={{
position: 'absolute',
top: 10,
left: 10,
padding: 10,
color: 'lightgrey',
background: 'rgba(0, 0, 0, 0.2)'
}}
>
{this.state.info}
</div>
</div>;
}
}
\ No newline at end of file
...@@ -4,12 +4,30 @@ ...@@ -4,12 +4,30 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Canvas</title> <title>Mol* Canvas</title>
<style>
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
hr {
margin: 10px;
}
h1, h2, h3, h4, h5 {
margin-top: 5px;
margin-bottom: 3px;
}
button {
padding: 2px;
}
</style>
</head> </head>
<body> <body>
<div id="container" style="width:800px; height: 600px;"> <div id="app" style="width: 100%; height: 100%"></div>
<canvas id="canvas"></canvas>
</div>
<span id="info"></span>
<script type="text/javascript" src="./index.js"></script> <script type="text/javascript" src="./index.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -4,151 +4,21 @@ ...@@ -4,151 +4,21 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import './index.html' import * as React from 'react'
import * as ReactDOM from 'react-dom'
import Viewer from 'mol-view/viewer';
import CIF, { CifBlock } from 'mol-io/reader/cif'
// import { parse as parseObj } from 'mol-io/reader/obj/parser'
import { readUrlAs } from 'mol-util/read'
import { Model, Format, Structure, StructureSymmetry } from 'mol-model/structure';
import { CartoonRepresentation } from 'mol-geo/representation/structure/representation/cartoon';
import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick';
import { EveryLoci } from 'mol-model/loci';
import { MarkerAction } from 'mol-geo/util/marker-data';
import { labelFirst } from 'mol-view/label';
import { Queries as Q, StructureProperties as SP, StructureSelection, StructureQuery } from 'mol-model/structure';
import { MeshBuilder } from 'mol-geo/mesh/mesh-builder';
import { ShapeRepresentation } from 'mol-geo/representation/shape';
import { Vec3, Mat4 } from 'mol-math/linear-algebra';
import { Shape } from 'mol-model/shape';
import { Color } from 'mol-util/color';
import { addSphere } from 'mol-geo/mesh/builder/sphere';
import { Box } from 'mol-geo/primitive/box';
const container = document.getElementById('container')
if (!container) throw new Error('Can not find element with id "container".')
const canvas = document.getElementById('canvas') as HTMLCanvasElement
if (!canvas) throw new Error('Can not find element with id "canvas".')
const info = document.getElementById('info') as HTMLCanvasElement
if (!info) throw new Error('Can not find element with id "info".')
const viewer = Viewer.create(canvas, container)
viewer.animate()
viewer.input.resize.subscribe(() => {
// do whatever appropriate
})
viewer.input.move.subscribe(({x, y, inside, buttons}) => {
if (!inside || buttons) return
const p = viewer.identify(x, y)
const loci = viewer.getLoci(p)
viewer.mark(EveryLoci, MarkerAction.RemoveHighlight)
viewer.mark(loci, MarkerAction.Highlight)
const label = labelFirst(loci)
info.innerText = `${label}`
})
// async function getObjFromUrl(url: string) { import './index.html'
// const data = await readUrlAs(url, false) as string
// const comp = parseObj(data)
// const parsed = await comp.run()
// if (parsed.isError) throw parsed
// return parsed.result
// }
async function getCifFromUrl(url: string) {
const data = await readUrlAs(url, false)
const comp = CIF.parse(data)
const parsed = await comp.run()
if (parsed.isError) throw parsed
return parsed.result.blocks[0]
}
async function getModelFromMmcif(cif: CifBlock) {
const models = await Model.create(Format.mmCIF(cif)).run()
return models[0]
}
async function getStructureFromModel(model: Model, assembly = '1') {
const assemblies = model.symmetry.assemblies
if (assemblies.length) {
return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run()
} else {
return Structure.ofModel(model)
}
}
async function init() {
const cif = await getCifFromUrl('https://files.rcsb.org/download/1crn.cif')
const model = await getModelFromMmcif(cif)
const structure = await getStructureFromModel(model)
viewer.center(structure.boundary.sphere.center)
// cartoon for whole structure
const cartoonRepr = CartoonRepresentation()
await cartoonRepr.create(structure, {
colorTheme: { name: 'chain-id' },
sizeTheme: { name: 'uniform', value: 0.2 },
useFog: false // TODO fog not working properly
}).run()
viewer.add(cartoonRepr)
// create new structure via query
const q1 = Q.generators.atoms({
residueTest: qtx => SP.residue.label_seq_id(qtx.element) < 7
});
const newStructure = StructureSelection.unionStructure(await StructureQuery.run(q1, structure));
// ball+stick for new structure
const ballStickRepr = BallAndStickRepresentation()
await ballStickRepr.create(newStructure, {
colorTheme: { name: 'element-symbol' },
sizeTheme: { name: 'uniform', value: 0.1 },
useFog: false // TODO fog not working properly
}).run()
viewer.add(ballStickRepr)
// create a mesh
const meshBuilder = MeshBuilder.create(256, 128)
const colors: Color[] = []
const labels: string[] = []
// red sphere
meshBuilder.setGroup(0)
colors[0] = Color(0xFF2233)
labels[0] = 'red sphere'
addSphere(meshBuilder, Vec3.create(0, 0, 0), 4, 2)
// green cube
meshBuilder.setGroup(1)
colors[1] = Color(0x2233FF)
labels[1] = 'blue cube'
const t = Mat4.identity()
Mat4.fromTranslation(t, Vec3.create(10, 0, 0))
Mat4.scale(t, t, Vec3.create(3, 3, 3))
meshBuilder.add(t, Box())
const mesh = meshBuilder.getMesh()
// const mesh = getObjFromUrl('mesh.obj')
// create shape from mesh import { App } from './app';
const shape = Shape.create('myShape', mesh, colors, labels) import { AppComponent } from './component/app';
import { urlQueryParameter } from 'mol-util/url-query';
// add representation from shape const elm = document.getElementById('app') as HTMLElement
const customRepr = ShapeRepresentation() if (!elm) throw new Error('Can not find element with id "app".')
await customRepr.create(shape, {
colorTheme: { name: 'shape-group' },
// colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
useFog: false // TODO fog not working properly
}).run()
viewer.add(customRepr)
// ensure the added representations get rendered, i.e. without mouse input const app = new App()
viewer.requestDraw() ReactDOM.render(React.createElement(AppComponent, { app }), elm);
}
init() const assemblyId = urlQueryParameter('assembly')
\ No newline at end of file const pdbId = urlQueryParameter('pdb')
if (pdbId) app.loadPdbId(pdbId, assemblyId)
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Model, Structure } from 'mol-model/structure';
import { CartoonRepresentation } from 'mol-geo/representation/structure/representation/cartoon';
import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick';
import { getStructureFromModel } from './util';
import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry';
import { ShapeRepresentation, ShapeProps } from 'mol-geo/representation/shape';
import { getAxesShape } from './assembly-symmetry';
import Viewer from 'mol-view/viewer';
import { CarbohydrateRepresentation } from 'mol-geo/representation/structure/representation/carbohydrate';
// import { MeshBuilder } from 'mol-geo/mesh/mesh-builder';
// import { addSphere } from 'mol-geo/mesh/builder/sphere';
// import { Shape } from 'mol-model/shape';
// import { Color } from 'mol-util/color';
// import { computeUnitBoundary } from 'mol-model/structure/structure/util/boundary';
// import { addBoundingBox } from 'mol-geo/mesh/builder/bounding-box';
import { PointRepresentation } from 'mol-geo/representation/structure/representation/point';
import { StructureRepresentation } from 'mol-geo/representation/structure';
import { BehaviorSubject } from 'rxjs';
import { SpacefillRepresentation } from 'mol-geo/representation/structure/representation/spacefill';
import { DistanceRestraintRepresentation } from 'mol-geo/representation/structure/representation/distance-restraint';
export interface StructureView {
readonly viewer: Viewer
readonly label: string
readonly models: ReadonlyArray<Model>
readonly structure: Structure | undefined
readonly assemblySymmetry: AssemblySymmetry | undefined
readonly active: { [k: string]: boolean }
readonly structureRepresentations: { [k: string]: StructureRepresentation<any> }
readonly updated: BehaviorSubject<null>
readonly symmetryAxes: ShapeRepresentation<ShapeProps>
setSymmetryAxes(value: boolean): void
setStructureRepresentation(name: string, value: boolean): void
readonly modelId: number
readonly assemblyId: string
readonly symmetryFeatureId: number
setModel(modelId: number): Promise<void>
getModelIds(): { id: number, label: string }[]
setAssembly(assemblyId: string): Promise<void>
getAssemblyIds(): { id: string, label: string }[]
setSymmetryFeature(symmetryFeatureId: number): Promise<void>
getSymmetryFeatureIds(): { id: number, label: string }[]
destroy: () => void
}
interface StructureViewProps {
assemblyId?: string
symmetryFeatureId?: number
}
export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> {
const active: { [k: string]: boolean } = {
cartoon: true,
point: false,
ballAndStick: false,
carbohydrate: false,
spacefill: false,
distanceRestraint: false,
symmetryAxes: false,
// polymerSphere: false,
}
const structureRepresentations: { [k: string]: StructureRepresentation<any> } = {
cartoon: CartoonRepresentation(),
point: PointRepresentation(),
ballAndStick: BallAndStickRepresentation(),
carbohydrate: CarbohydrateRepresentation(),
spacefill: SpacefillRepresentation(),
distanceRestraint: DistanceRestraintRepresentation(),
}
const symmetryAxes = ShapeRepresentation()
const polymerSphere = ShapeRepresentation()
const updated: BehaviorSubject<null> = new BehaviorSubject<null>(null)
let label: string
let model: Model | undefined
let assemblySymmetry: AssemblySymmetry | undefined
let structure: Structure | undefined
let modelId: number
let assemblyId: string
let symmetryFeatureId: number
async function setSymmetryAxes(value: boolean) {
if (!value) {
assemblySymmetry = undefined
} else {
await AssemblySymmetry.attachFromCifOrAPI(models[modelId])
assemblySymmetry = AssemblySymmetry.get(models[modelId])
}
active.symmetryAxes = value
await setSymmetryFeature()
}
async function setStructureRepresentation(k: string, value: boolean) {
active[k] = value
await createStructureRepr()
}
async function setModel(newModelId: number, newAssemblyId?: string, newSymmetryFeatureId?: number) {
console.log('setModel', newModelId)
modelId = newModelId
model = models[modelId]
if (active.symmetryAxes) {
await AssemblySymmetry.attachFromCifOrAPI(model)
assemblySymmetry = AssemblySymmetry.get(model)
}
await setAssembly(newAssemblyId, newSymmetryFeatureId)
}
function getModelIds() {
const modelIds: { id: number, label: string }[] = []
models.forEach((m, i) => {
modelIds.push({ id: i, label: `${i}: ${m.label} #${m.modelNum}` })
})
return modelIds
}
async function setAssembly(newAssemblyId?: string, newSymmetryFeatureId?: number) {
console.log('setAssembly', newAssemblyId)
if (newAssemblyId !== undefined) {
assemblyId = newAssemblyId
} else if (model && model.symmetry.assemblies.length) {
assemblyId = model.symmetry.assemblies[0].id
} else if (model) {
assemblyId = '0'
} else {
assemblyId = '-1'
}
await getStructure()
await setSymmetryFeature(newSymmetryFeatureId)
}
function getAssemblyIds() {
const assemblyIds: { id: string, label: string }[] = [
{ id: '0', label: '0: model' }
]
if (model) model.symmetry.assemblies.forEach(a => {
assemblyIds.push({ id: a.id, label: `${a.id}: ${a.details}` })
})
return assemblyIds
}
async function setSymmetryFeature(newSymmetryFeatureId?: number) {
console.log('setSymmetryFeature', newSymmetryFeatureId)
if (newSymmetryFeatureId !== undefined) {
symmetryFeatureId = newSymmetryFeatureId
} else if (assemblySymmetry) {
const f = assemblySymmetry.getFeatures(assemblyId)
if (f._rowCount) {
symmetryFeatureId = f.id.value(0)
} else {
symmetryFeatureId = -1
}
} else {
symmetryFeatureId = -1
}
await createSymmetryRepr()
}
function getSymmetryFeatureIds() {
const symmetryFeatureIds: { id: number, label: string }[] = []
if (assemblySymmetry) {
const symmetryFeatures = assemblySymmetry.getFeatures(assemblyId)
for (let i = 0, il = symmetryFeatures._rowCount; i < il; ++i) {
const id = symmetryFeatures.id.value(i)
const symmetry = symmetryFeatures.symmetry_value.value(i)
const type = symmetryFeatures.type.value(i)
const stoichiometry = symmetryFeatures.stoichiometry_value.value(i)
const label = `${id}: ${symmetry} ${type} ${stoichiometry}`
symmetryFeatureIds.push({ id, label })
}
}
return symmetryFeatureIds
}
async function getStructure() {
if (model) structure = await getStructureFromModel(model, assemblyId)
if (model && structure) {
label = `${model.label} - Assembly ${assemblyId}`
} else {
label = ''
}
await createStructureRepr()
}
async function createStructureRepr() {
if (structure) {
console.log('createStructureRepr')
for (const k in structureRepresentations) {
if (active[k]) {
await structureRepresentations[k].createOrUpdate({}, structure).run()
viewer.add(structureRepresentations[k])
} else {
viewer.remove(structureRepresentations[k])
}
}
viewer.center(structure.boundary.sphere.center)
// const mb = MeshBuilder.create()
// mb.setGroup(0)
// addSphere(mb, structure.boundary.sphere.center, structure.boundary.sphere.radius, 3)
// addBoundingBox(mb, structure.boundary.box, 1, 2, 8)
// for (let i = 0, il = structure.units.length; i < il; ++i) {
// mb.setGroup(1)
// const u = structure.units[i]
// const ci = u.model.atomicHierarchy.chainAtomSegments.index[u.elements[0]]
// const ek = u.model.atomicHierarchy.getEntityKey(ci)
// if (u.model.entities.data.type.value(ek) === 'water') continue
// const boundary = computeUnitBoundary(u)
// addSphere(mb, boundary.sphere.center, boundary.sphere.radius, 3)
// addBoundingBox(mb, boundary.box, 0.5, 2, 8)
// }
// const shape = Shape.create('boundary', mb.getMesh(), [Color(0xCC6633), Color(0x3366CC)], ['sphere boundary'])
// await polymerSphere.createOrUpdate({
// alpha: 0.5,
// doubleSided: false,
// depthMask: false,
// useFog: false // TODO fog not working properly
// }, shape).run()
} else {
for (const k in structureRepresentations) structureRepresentations[k].destroy()
polymerSphere.destroy()
}
viewer.add(polymerSphere)
updated.next(null)
viewer.requestDraw(true)
console.log(viewer.stats)
}
async function createSymmetryRepr() {
if (assemblySymmetry) {
const features = assemblySymmetry.getFeatures(assemblyId)
if (features._rowCount) {
const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry)
if (axesShape) {
// const colorTheme = getClusterColorTheme(symmetryFeatureId, assemblySymmetry)
// await cartoon.createOrUpdate({
// colorTheme: { name: 'custom', color: colorTheme.color, granularity: colorTheme.granularity },
// sizeTheme: { name: 'uniform', value: 0.2 },
// useFog: false // TODO fog not working properly
// }).run()
// await ballAndStick.createOrUpdate({
// colorTheme: { name: 'custom', color: colorTheme.color, granularity: colorTheme.granularity },
// sizeTheme: { name: 'uniform', value: 0.1 },
// useFog: false // TODO fog not working properly
// }).run()
await symmetryAxes.createOrUpdate({}, axesShape).run()
viewer.add(symmetryAxes)
} else {
viewer.remove(symmetryAxes)
}
} else {
viewer.remove(symmetryAxes)
}
} else {
viewer.remove(symmetryAxes)
}
updated.next(null)
viewer.requestDraw(true)
}
await setModel(0, props.assemblyId, props.symmetryFeatureId)
return {
viewer,
get label() { return label },
models,
get structure() { return structure },
get assemblySymmetry() { return assemblySymmetry },
active,
structureRepresentations,
updated,
symmetryAxes,
setSymmetryAxes,
setStructureRepresentation,
get modelId() { return modelId },
get assemblyId() { return assemblyId },
get symmetryFeatureId() { return symmetryFeatureId },
setModel,
getModelIds,
setAssembly,
getAssemblyIds,
setSymmetryFeature,
getSymmetryFeatureIds,
destroy: () => {
for (const k in structureRepresentations) {
viewer.remove(structureRepresentations[k])
structureRepresentations[k].destroy()
}
viewer.remove(polymerSphere)
viewer.remove(symmetryAxes)
viewer.requestDraw(true)
polymerSphere.destroy()
symmetryAxes.destroy()
}
}
}
// // create new structure via query
// const q1 = Q.generators.atoms({
// residueTest: qtx => SP.residue.label_seq_id(qtx.element) < 7
// });
// const newStructure = StructureSelection.unionStructure(await StructureQuery.run(q1, structure));
// // ball+stick for new structure
// const newBallStickRepr = BallAndStickRepresentation()
// await newBallStickRepr.create(newStructure, {
// colorTheme: { name: 'element-symbol' },
// sizeTheme: { name: 'uniform', value: 0.1 },
// useFog: false // TODO fog not working properly
// }).run()
// viewer.add(newBallStickRepr)
// // create a mesh
// const meshBuilder = MeshBuilder.create(256, 128)
// const colors: Color[] = []
// const labels: string[] = []
// // red sphere
// meshBuilder.setGroup(0)
// colors[0] = Color(0xFF2233)
// labels[0] = 'red sphere'
// addSphere(meshBuilder, Vec3.create(0, 0, 0), 4, 2)
// // green cube
// meshBuilder.setGroup(1)
// colors[1] = Color(0x2233FF)
// labels[1] = 'blue cube'
// const t = Mat4.identity()
// Mat4.fromTranslation(t, Vec3.create(10, 0, 0))
// Mat4.scale(t, t, Vec3.create(3, 3, 3))
// meshBuilder.add(t, Box())
// const mesh = meshBuilder.getMesh()
// const mesh = getObjFromUrl('mesh.obj')
// // create shape from mesh
// const shape = Shape.create('myShape', mesh, colors, labels)
// // add representation from shape
// const customRepr = ShapeRepresentation()
// await customRepr.create(shape, {
// colorTheme: { name: 'shape-group' },
// // colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
// useFog: false // TODO fog not working properly
// }).run()
// viewer.add(customRepr)
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import CIF, { CifBlock } from 'mol-io/reader/cif'
import { readUrlAs, readFileAs } from 'mol-util/read';
import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure';
// import { parse as parseObj } from 'mol-io/reader/obj/parser'
// export async function getObjFromUrl(url: string) {
// const data = await readUrlAs(url, false) as string
// const comp = parseObj(data)
// const parsed = await comp.run()
// if (parsed.isError) throw parsed
// return parsed.result
// }
export async function getCifFromData(data: string | Uint8Array) {
const comp = CIF.parse(data)
const parsed = await comp.run()
if (parsed.isError) throw parsed
return parsed.result.blocks[0]
}
export async function getCifFromUrl(url: string) {
return getCifFromData(await readUrlAs(url, false))
}
export async function getCifFromFile(file: File, binary = false) {
return getCifFromData(await readFileAs(file, binary))
}
export async function getModelsFromMmcif(cif: CifBlock) {
return await Model.create(Format.mmCIF(cif)).run()
}
export async function getStructureFromModel(model: Model, assembly: string) {
const assemblies = model.symmetry.assemblies
if (assembly === '0') {
return Structure.ofModel(model)
} else if (assemblies.find(a => a.id === assembly)) {
return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run()
}
}
\ No newline at end of file
...@@ -104,10 +104,4 @@ ctx.dispatcher.getStream(InteractivityEvents.HighlightLoci).subscribe(event => { ...@@ -104,10 +104,4 @@ ctx.dispatcher.getStream(InteractivityEvents.HighlightLoci).subscribe(event => {
} }
}) })
ctx.dispatcher.getStream(InteractivityEvents.SelectLoci).subscribe(event => {
if (event && event.data) {
ctx.stage.viewer.mark(event.data, MarkerAction.ToggleSelect)
}
})
ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm); ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm);
...@@ -163,6 +163,7 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl? ...@@ -163,6 +163,7 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
viewer.input.move.subscribe(({x, y, inside, buttons}) => { viewer.input.move.subscribe(({x, y, inside, buttons}) => {
if (!inside || buttons) return if (!inside || buttons) return
const p = viewer.identify(x, y) const p = viewer.identify(x, y)
if (p) {
const loci = viewer.getLoci(p) const loci = viewer.getLoci(p)
InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci); InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci);
...@@ -170,12 +171,16 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl? ...@@ -170,12 +171,16 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
const label = labelFirst(loci) const label = labelFirst(loci)
const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Group: ${p.groupId}, Label: ${label}` const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Group: ${p.groupId}, Label: ${label}`
this.setState({ info }) this.setState({ info })
}
}) })
// TODO filter only for left button/single finger touch? // TODO filter only for left button/single finger touch?
viewer.input.click.subscribe(({x, y}) => { viewer.input.click.subscribe(({x, y}) => {
const loci = viewer.getLoci(viewer.identify(x, y)) const p = viewer.identify(x, y)
if (p) {
const loci = viewer.getLoci(p)
InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci); InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci);
}
}) })
} }
......
...@@ -101,6 +101,14 @@ namespace Table { ...@@ -101,6 +101,14 @@ namespace Table {
return ret as Table<R>; return ret as Table<R>;
} }
export function pick<S extends R, R extends Schema>(table: Table<S>, schema: R, test: (i: number) => boolean) {
const _view: number[] = []
for (let i = 0, il = table._rowCount; i < il; ++i) {
if (test(i)) _view.push(i)
}
return view(table, schema, _view)
}
export function window<S extends R, R extends Schema>(table: Table<S>, schema: R, start: number, end: number) { export function window<S extends R, R extends Schema>(table: Table<S>, schema: R, start: number, end: number) {
if (start === 0 && end === table._rowCount) return table; if (start === 0 && end === table._rowCount) return table;
const ret = Object.create(null); const ret = Object.create(null);
...@@ -194,6 +202,13 @@ namespace Table { ...@@ -194,6 +202,13 @@ namespace Table {
return row; return row;
} }
/** Pick the first row for which `test` evaluates to true */
export function pickRow<S extends Schema>(table: Table<S>, test: (i: number) => boolean) {
for (let i = 0, il = table._rowCount; i < il; ++i) {
if (test(i)) return getRow(table, i)
}
}
export function getRows<S extends Schema>(table: Table<S>) { export function getRows<S extends Schema>(table: Table<S>) {
const ret: Row<S>[] = []; const ret: Row<S>[] = [];
const { _rowCount: c } = table; const { _rowCount: c } = table;
......
...@@ -19,6 +19,7 @@ export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); } ...@@ -19,6 +19,7 @@ export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
export const hashCode = Tuple.hashCode; export const hashCode = Tuple.hashCode;
export function has(int: Tuple, v: number) { return Tuple.fst(int) <= v && v < Tuple.snd(int); } export function has(int: Tuple, v: number) { return Tuple.fst(int) <= v && v < Tuple.snd(int); }
/** Returns the index of `x` in `set` or -1 if not found. */
export function indexOf(int: Tuple, x: number) { const m = start(int); return x >= m && x < end(int) ? x - m : -1; } export function indexOf(int: Tuple, x: number) { const m = start(int); return x >= m && x < end(int) ? x - m : -1; }
export function getAt(int: Tuple, i: number) { return Tuple.fst(int) + i; } export function getAt(int: Tuple, i: number) { return Tuple.fst(int) + i; }
......
...@@ -25,6 +25,7 @@ export function ofSortedArray(xs: Nums): OrderedSetImpl { ...@@ -25,6 +25,7 @@ export function ofSortedArray(xs: Nums): OrderedSetImpl {
export function size(set: OrderedSetImpl) { return I.is(set) ? I.size(set) : S.size(set); } export function size(set: OrderedSetImpl) { return I.is(set) ? I.size(set) : S.size(set); }
export function has(set: OrderedSetImpl, x: number) { return I.is(set) ? I.has(set, x) : S.has(set, x); } export function has(set: OrderedSetImpl, x: number) { return I.is(set) ? I.has(set, x) : S.has(set, x); }
/** Returns the index of `x` in `set` or -1 if not found. */
export function indexOf(set: OrderedSetImpl, x: number) { return I.is(set) ? I.indexOf(set, x) : S.indexOf(set, x); } export function indexOf(set: OrderedSetImpl, x: number) { return I.is(set) ? I.indexOf(set, x) : S.indexOf(set, x); }
export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : set[i]; } export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : set[i]; }
export function min(set: OrderedSetImpl) { return I.is(set) ? I.min(set) : S.min(set); } export function min(set: OrderedSetImpl) { return I.is(set) ? I.min(set) : S.min(set); }
......
...@@ -36,6 +36,7 @@ export function hashCode(xs: Nums) { ...@@ -36,6 +36,7 @@ export function hashCode(xs: Nums) {
return hash3(s, xs[0], xs[s - 1]); return hash3(s, xs[0], xs[s - 1]);
} }
/** Returns the index of `x` in `set` or -1 if not found. */
export function indexOf(xs: Nums, v: number) { export function indexOf(xs: Nums, v: number) {
const l = xs.length; const l = xs.length;
return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1; return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;
......
...@@ -18,6 +18,7 @@ namespace Interval { ...@@ -18,6 +18,7 @@ namespace Interval {
/** Test if a value is within the bounds of the interval */ /** Test if a value is within the bounds of the interval */
export const has: <T extends number = number>(interval: Interval<T>, x: T) => boolean = Impl.has as any; export const has: <T extends number = number>(interval: Interval<T>, x: T) => boolean = Impl.has as any;
/** Returns the index of `x` in `set` or -1 if not found. */
export const indexOf: <T extends number = number>(interval: Interval<T>, x: T) => number = Impl.indexOf as any; export const indexOf: <T extends number = number>(interval: Interval<T>, x: T) => number = Impl.indexOf as any;
export const getAt: <T extends number = number>(interval: Interval<T>, i: number) => T = Impl.getAt as any; export const getAt: <T extends number = number>(interval: Interval<T>, i: number) => T = Impl.getAt as any;
......
...@@ -18,6 +18,7 @@ namespace OrderedSet { ...@@ -18,6 +18,7 @@ namespace OrderedSet {
export const ofSortedArray: <T extends number = number>(xs: ArrayLike<T>) => OrderedSet<T> = Base.ofSortedArray as any; export const ofSortedArray: <T extends number = number>(xs: ArrayLike<T>) => OrderedSet<T> = Base.ofSortedArray as any;
export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any; export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any;
/** Returns the index of `x` in `set` or -1 if not found. */
export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any; export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any;
export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any; export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment