mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 06:34:23 +08:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87028c0a0b | ||
|
|
8ea23e6965 | ||
|
|
f9d8942814 | ||
|
|
b40df2f1e3 | ||
|
|
884cb0d9a4 | ||
|
|
ef1ccd4286 | ||
|
|
898abda373 | ||
|
|
e42c664a8c | ||
|
|
987bf47827 | ||
|
|
6201dd1d74 | ||
|
|
5ed17ce4e5 | ||
|
|
e301eca9c2 | ||
|
|
8a4ef015a2 | ||
|
|
67f3f3fdbb | ||
|
|
adc5b559cd | ||
|
|
bbaa637118 | ||
|
|
a3094b4d19 | ||
|
|
c3f937e113 | ||
|
|
04df327939 | ||
|
|
b1a0f46ade | ||
|
|
389e249862 | ||
|
|
cfcf9f6818 | ||
|
|
bcb8419f37 | ||
|
|
7d24bcf1dc | ||
|
|
8d0f7a2dc7 | ||
|
|
a7cb7beaa8 | ||
|
|
3c9b82dc04 | ||
|
|
9dba6d5371 | ||
|
|
a65bba0969 | ||
|
|
175e009152 | ||
|
|
897d17c8ed | ||
|
|
ca866cfa3a | ||
|
|
f6b2c0b2ba | ||
|
|
ea419c68ae | ||
|
|
40cf348d40 | ||
|
|
93ea759a71 | ||
|
|
3e50377eb8 | ||
|
|
3fbd1f8dc4 | ||
|
|
f03ce68513 | ||
|
|
50e2d542df | ||
|
|
e53e739d18 | ||
|
|
dfb7f7811f | ||
|
|
115824bbcf | ||
|
|
438de5760d | ||
|
|
3f765aedec | ||
|
|
96144fb10f | ||
|
|
cc34425712 | ||
|
|
9d68838893 | ||
|
|
6c68cebca0 | ||
|
|
b3e784262d | ||
|
|
2fdc22de71 | ||
|
|
77f0b0033f | ||
|
|
69da5abb88 | ||
|
|
a827e9a449 | ||
|
|
e74a29ae6a | ||
|
|
9b3d2f396e | ||
|
|
14cf7cc101 | ||
|
|
e5293c4d36 | ||
|
|
3afe21a4c3 | ||
|
|
bb50b69bb4 | ||
|
|
af7c030338 | ||
|
|
34b1eee876 | ||
|
|
c9cd1075d3 | ||
|
|
4fa04f0ff8 | ||
|
|
75f6466fd7 | ||
|
|
503ffd80fd | ||
|
|
c8ac64a571 | ||
|
|
bf81b902bd | ||
|
|
b636cdf9cc | ||
|
|
2d3b85825a | ||
|
|
a5a34f39e0 | ||
|
|
fe7e04f61b | ||
|
|
1a14720e35 | ||
|
|
18bf743ed2 | ||
|
|
f8d085a034 | ||
|
|
94bf3a136c | ||
|
|
9e8a8f3e71 | ||
|
|
45f9d93f3a | ||
|
|
44a566fdf3 | ||
|
|
d7f7770b7c | ||
|
|
df6b163505 | ||
|
|
51f88fff71 | ||
|
|
6b8db5abc6 | ||
|
|
37a0b07d56 |
16
.vscode/extensions.json
vendored
Normal file
16
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"slevesque.shader",
|
||||
"stpn.vscode-graphql",
|
||||
"wayou.vscode-todo-highlight"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
}
|
||||
11
README.md
11
README.md
@@ -85,7 +85,7 @@ and navigate to `build/viewer`
|
||||
Install CIFTools `npm install ciftools -g`
|
||||
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
|
||||
cifschema -mip ../../../../mol-data-o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
|
||||
|
||||
**GraphQL schemas**
|
||||
@@ -95,7 +95,7 @@ Install CIFTools `npm install ciftools -g`
|
||||
### Other scripts
|
||||
**Create chem comp bond table**
|
||||
|
||||
export NODE_PATH="lib"; node --max-old-space-size=8192 build/src/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
export NODE_PATH="lib"; node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
|
||||
**Test model server**
|
||||
|
||||
@@ -133,14 +133,17 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
|
||||
## Publish
|
||||
|
||||
## Prerelease
|
||||
### Prerelease
|
||||
npm version prerelease # asumes the current version ends with '-dev.X'
|
||||
npm publish --tag next
|
||||
|
||||
## Release
|
||||
### Release
|
||||
npm version 0.X.0 # provide valid semver string
|
||||
npm publish
|
||||
|
||||
## Deploy
|
||||
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
|
||||
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const path = require('path')
|
||||
const basePath = path.join(__dirname, '..', '..', 'src', 'mol-model-props', 'rcsb', 'graphql')
|
||||
|
||||
generate({
|
||||
schema: 'http://rest-dev.rcsb.org/graphql',
|
||||
schema: 'http://rest-staging.rcsb.org/graphql',
|
||||
documents: {
|
||||
[path.join(basePath, 'symmetry.gql.ts')]: {
|
||||
loader: path.join(__dirname, 'loader.js')
|
||||
|
||||
1077
package-lock.json
generated
1077
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.4",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -65,9 +65,10 @@
|
||||
"circular-dependency-plugin": "^5.0.2",
|
||||
"concurrently": "^4.1.0",
|
||||
"cpx": "^1.5.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"css-loader": "^3.0.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^3.0.1",
|
||||
"file-loader": "^4.0.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"graphql-code-generator": "^0.18.2",
|
||||
"graphql-codegen-time": "^0.18.2",
|
||||
"graphql-codegen-typescript-template": "^0.18.2",
|
||||
@@ -75,26 +76,25 @@
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.7.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"raw-loader": "^2.0.0",
|
||||
"raw-loader": "^3.0.0",
|
||||
"resolve-url-loader": "^3.1.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"simple-git": "^1.113.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"typescript": "^3.5.1",
|
||||
"uglify-js": "^3.6.0",
|
||||
"util.promisify": "^1.0.0",
|
||||
"webpack": "^4.32.2",
|
||||
"webpack-cli": "^3.3.2"
|
||||
"webpack": "^4.33.0",
|
||||
"webpack-cli": "^3.3.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.36",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "0.0.36",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/node": "^12.0.4",
|
||||
"@types/node-fetch": "^2.3.4",
|
||||
"@types/node": "^12.0.8",
|
||||
"@types/node-fetch": "^2.3.5",
|
||||
"@types/react": "^16.8.19",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/swagger-ui-dist": "3.0.0",
|
||||
@@ -108,7 +108,8 @@
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"rxjs": "^6.5.2",
|
||||
"swagger-ui-dist": "^3.22.2",
|
||||
"swagger-ui-dist": "^3.22.3",
|
||||
"util.promisify": "^1.0.0",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
59
scripts/deploy.js
Normal file
59
scripts/deploy.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
const git = require('simple-git')
|
||||
const path = require('path')
|
||||
const fs = require("fs")
|
||||
const fse = require("fs-extra")
|
||||
|
||||
const remoteUrl = "https://github.com/molstar/molstar.github.io.git"
|
||||
const buildDir = path.resolve(__dirname, '../build/')
|
||||
const deployDir = path.resolve(buildDir, 'deploy/')
|
||||
const localPath = path.resolve(deployDir, 'molstar.github.io/')
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
console.log('\n###', command)
|
||||
stdout.pipe(process.stdout)
|
||||
stderr.pipe(process.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
function copyViewer() {
|
||||
console.log('\n###', 'copy viewer files')
|
||||
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/')
|
||||
const viewerDeployPath = path.resolve(localPath, 'viewer/')
|
||||
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localPath)) {
|
||||
console.log('\n###', 'create localPath')
|
||||
fs.mkdirSync(localPath, { recursive: true })
|
||||
}
|
||||
|
||||
process.chdir(localPath);
|
||||
|
||||
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
|
||||
console.log('\n###', 'clone repository')
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.clone(remoteUrl, localPath)
|
||||
.fetch(['--all'])
|
||||
.exec(copyViewer)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
} else {
|
||||
console.log('\n###', 'update repository')
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.fetch(['--all'])
|
||||
.reset(['--hard', 'origin/master'])
|
||||
.exec(copyViewer)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,12 +15,13 @@ const readFile = util.promisify(fs.readFile)
|
||||
const writeFile = util.promisify(fs.writeFile)
|
||||
|
||||
import { Progress } from '../../mol-task'
|
||||
import { Database, Table, DatabaseCollection, Column } from '../../mol-data/db'
|
||||
import { Database, Table, DatabaseCollection } from '../../mol-data/db'
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { CifWriter } from '../../mol-io/writer/cif'
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'
|
||||
import { SetUtils } from '../../mol-util/set'
|
||||
import { DefaultMap } from '../../mol-util/map'
|
||||
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
|
||||
export async function ensureAvailable(path: string, url: string) {
|
||||
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
|
||||
@@ -74,16 +75,6 @@ export function getEncodedCif(name: string, database: Database<Database.Schema>,
|
||||
type CCB = Table<CCD_Schema['chem_comp_bond']>
|
||||
type CCA = Table<CCD_Schema['chem_comp_atom']>
|
||||
|
||||
const ChemCompBond_Schema = {
|
||||
comp_id: CCD_Schema['chem_comp_bond'].comp_id,
|
||||
atom_id_1: CCD_Schema['chem_comp_bond'].atom_id_1,
|
||||
atom_id_2: CCD_Schema['chem_comp_bond'].atom_id_2,
|
||||
value_order: CCD_Schema['chem_comp_bond'].value_order,
|
||||
pdbx_aromatic_flag: CCD_Schema['chem_comp_bond'].pdbx_aromatic_flag,
|
||||
pdbx_stereo_config: CCD_Schema['chem_comp_bond'].pdbx_stereo_config,
|
||||
molstar_protonation_variant: Column.Schema.Str()
|
||||
}
|
||||
|
||||
function ccbKey(compId: string, atomId1: string, atomId2: string) {
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`
|
||||
}
|
||||
@@ -202,14 +193,14 @@ async function createBonds() {
|
||||
}
|
||||
}
|
||||
|
||||
const bondTable = Table.ofArrays(ChemCompBond_Schema, {
|
||||
const bondTable = Table.ofArrays(mmCIF_chemCompBond_schema, {
|
||||
comp_id, atom_id_1, atom_id_2, value_order,
|
||||
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
|
||||
})
|
||||
|
||||
const bondDatabase = Database.ofTables(
|
||||
TABLE_NAME,
|
||||
{ chem_comp_bond: ChemCompBond_Schema },
|
||||
{ chem_comp_bond: mmCIF_chemCompBond_schema },
|
||||
{ chem_comp_bond: bondTable }
|
||||
)
|
||||
|
||||
@@ -220,12 +211,15 @@ async function run(out: string, binary = false) {
|
||||
const bonds = await createBonds()
|
||||
|
||||
const cif = getEncodedCif(TABLE_NAME, bonds, binary)
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeFile(out, cif)
|
||||
}
|
||||
|
||||
const TABLE_NAME = 'CHEM_COMP_BONDS'
|
||||
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'data')
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data')
|
||||
const CCD_PATH = path.join(DATA_DIR, 'components.cif')
|
||||
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif')
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif'
|
||||
|
||||
@@ -29,6 +29,9 @@ function init() {
|
||||
initial: {
|
||||
isExpanded: true,
|
||||
showControls: !hideControls
|
||||
},
|
||||
controls: {
|
||||
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
== v3.2 ==
|
||||
|
||||
* Fixed assembly loading.
|
||||
* Better HET group focus.
|
||||
|
||||
== v3.0 ==
|
||||
|
||||
* Fixed initial camera zoom.
|
||||
|
||||
@@ -105,6 +105,8 @@ export enum StateElements {
|
||||
ModelProps = 'model-props',
|
||||
Assembly = 'assembly',
|
||||
|
||||
VolumeStreaming = 'volume-streaming',
|
||||
|
||||
Sequence = 'sequence',
|
||||
SequenceVisual = 'sequence-visual',
|
||||
Het = 'het',
|
||||
@@ -113,5 +115,6 @@ export enum StateElements {
|
||||
Water = 'water',
|
||||
WaterVisual = 'water-visual',
|
||||
|
||||
HetGroupFocus = 'het-group-focus'
|
||||
HetGroupFocus = 'het-group-focus',
|
||||
HetGroupFocusGroup = 'het-group-focus-group'
|
||||
}
|
||||
@@ -40,6 +40,13 @@
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#volume-streaming-wrapper {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 780px;
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
@@ -55,6 +62,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<div id="volume-streaming-wrapper"></div>
|
||||
<script>
|
||||
// it might be a good idea to define these colors in a separate script file
|
||||
var CustomColors = [0x00ff00, 0x0000ff];
|
||||
@@ -66,7 +74,7 @@
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
var pdbId = '1eve', assemblyId= 'preferred';
|
||||
var pdbId = '1cbs', assemblyId= 'preferred';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var format = 'cif';
|
||||
|
||||
@@ -141,7 +149,8 @@
|
||||
addSeparator();
|
||||
addHeader('Misc');
|
||||
|
||||
addControl('Apply Evo Cons', () => PluginWrapper.coloring.evolutionaryConservation());
|
||||
addControl('Apply Evo Cons Style', () => PluginWrapper.coloring.evolutionaryConservation());
|
||||
addControl('Apply Evo Cons Colors', () => PluginWrapper.coloring.evolutionaryConservation({ sequence: true, het: false, keepStyle: true }));
|
||||
addControl('Default Visuals', () => PluginWrapper.updateStyle());
|
||||
|
||||
addSeparator();
|
||||
@@ -151,6 +160,11 @@
|
||||
addHetGroupsContainer();
|
||||
|
||||
addSeparator();
|
||||
addHeader('Exp. Data');
|
||||
addControl('Init', () => PluginWrapper.experimentalData.init($('volume-streaming-wrapper')));
|
||||
addControl('Remove', () => PluginWrapper.experimentalData.remove());
|
||||
|
||||
addSeparator();
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
@@ -13,11 +14,11 @@ import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transfo
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { StateBuilder, StateObject } from '../../mol-state';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { ControlsWrapper } from './ui/controls';
|
||||
import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { Scheduler } from '../../mol-task';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
@@ -26,6 +27,8 @@ import { BuiltInStructureRepresentations } from '../../mol-repr/structure/regist
|
||||
import { BuiltInColorThemes } from '../../mol-theme/color';
|
||||
import { BuiltInSizeThemes } from '../../mol-theme/size';
|
||||
import { ColorNames } from '../../mol-util/color/tables';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
// import { Vec3 } from 'mol-math/linear-algebra';
|
||||
// import { ParamDefinition } from 'mol-util/param-definition';
|
||||
// import { Text } from 'mol-geo/geometry/text/text';
|
||||
@@ -33,7 +36,7 @@ require('../../mol-plugin/skin/light.scss')
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 3;
|
||||
static VERSION_MINOR = 1;
|
||||
static VERSION_MINOR = 2;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
|
||||
@@ -78,7 +81,7 @@ class MolStarProteopediaWrapper {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
}
|
||||
|
||||
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
|
||||
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
@@ -187,7 +190,7 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = '', representationStyle }: LoadParams) {
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
@@ -200,14 +203,17 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format, assemblyId);
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
|
||||
await this.applyState(modelTree);
|
||||
const info = await this.doInfo(true);
|
||||
const structureTree = this.structure((assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
const structureTree = this.structure(asmId);
|
||||
await this.applyState(structureTree);
|
||||
} else {
|
||||
const tree = state.build();
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
const info = await this.doInfo(true);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
|
||||
await this.applyState(tree);
|
||||
}
|
||||
|
||||
@@ -247,53 +253,83 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
coloring = {
|
||||
evolutionaryConservation: async () => {
|
||||
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
|
||||
evolutionaryConservation: async (params?: { sequence?: boolean, het?: boolean, keepStyle?: boolean }) => {
|
||||
if (!params || !params.keepStyle) {
|
||||
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
|
||||
}
|
||||
|
||||
const state = this.state;
|
||||
|
||||
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
|
||||
|
||||
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
// for (const v of visuals) {
|
||||
// }
|
||||
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
|
||||
|
||||
if (!params || !!params.sequence) {
|
||||
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
}
|
||||
if (params && !!params.het) {
|
||||
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
private experimentalDataElement?: Element = void 0;
|
||||
experimentalData = {
|
||||
init: async (parent: Element) => {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj!;
|
||||
const params = ParamDefinition.getDefaultValues(InitVolumeStreaming.definition.params!(asm, this.plugin));
|
||||
params.behaviorRef = StateElements.VolumeStreaming;
|
||||
params.defaultView = 'box';
|
||||
await this.plugin.runTask(this.state.applyAction(InitVolumeStreaming, params, StateElements.Assembly));
|
||||
this.experimentalDataElement = parent;
|
||||
volumeStreamingControls(this.plugin, parent);
|
||||
},
|
||||
remove: () => {
|
||||
const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
|
||||
if (!r) return;
|
||||
PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
if (this.experimentalDataElement) {
|
||||
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
|
||||
this.experimentalDataElement = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hetGroups = {
|
||||
reset: () => {
|
||||
const update = this.state.build().delete(StateElements.HetGroupFocus);
|
||||
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
},
|
||||
focusFirst: async (resn: string) => {
|
||||
focusFirst: async (compId: string) => {
|
||||
if (!this.state.transforms.has(StateElements.Assembly)) return;
|
||||
await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
|
||||
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
|
||||
const update = this.state.build();
|
||||
|
||||
update.delete(StateElements.HetGroupFocus);
|
||||
update.delete(StateElements.HetGroupFocusGroup);
|
||||
|
||||
const surroundings = MS.struct.modifier.includeSurroundings({
|
||||
0: MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), resn]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]),
|
||||
radius: 5,
|
||||
'as-whole-residues': true
|
||||
});
|
||||
const core = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), compId]),
|
||||
'group-by': MS.core.str.concat([MS.struct.atomProperty.core.operatorName(), MS.struct.atomProperty.macromolecular.residueKey()])
|
||||
})
|
||||
]);
|
||||
const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
|
||||
|
||||
const sel = update.to(StateElements.Assembly)
|
||||
.apply(StateTransforms.Model.StructureSelection, { label: resn, query: surroundings }, { ref: StateElements.HetGroupFocus });
|
||||
const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
|
||||
|
||||
sel.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
|
||||
group.apply(StateTransforms.Model.StructureSelection, { label: 'Core', query: core }, { ref: StateElements.HetGroupFocus })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createCoreVisualParams());
|
||||
group.apply(StateTransforms.Model.StructureSelection, { label: 'Surroundings', query: surroundings })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
|
||||
// sel.apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// target: { name: 'residues', params: { } },
|
||||
// options: {
|
||||
@@ -313,7 +349,7 @@ class MolStarProteopediaWrapper {
|
||||
// const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
|
||||
// Vec3.normalize(position, position);
|
||||
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
|
||||
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, 0.75 * sphere.radius);
|
||||
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, Math.max(sphere.radius, 5));
|
||||
PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
|
||||
}
|
||||
}
|
||||
@@ -327,6 +363,15 @@ class MolStarProteopediaWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
private createCoreVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
// color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
// size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
});
|
||||
}
|
||||
|
||||
snapshot = {
|
||||
get: () => {
|
||||
return this.plugin.state.getSnapshot();
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIComponent } from '../../../mol-plugin/ui/base';
|
||||
import { CurrentObject } from '../../../mol-plugin/ui/plugin';
|
||||
import { CurrentObject, PluginContextContainer } from '../../../mol-plugin/ui/plugin';
|
||||
import { AnimationControls } from '../../../mol-plugin/ui/state/animation';
|
||||
import { CameraSnapshots } from '../../../mol-plugin/ui/camera';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { TransformUpdaterControl } from '../../../mol-plugin/ui/state/update-transform';
|
||||
import { StateElements } from '../helpers';
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
@@ -18,4 +22,11 @@ export class ControlsWrapper extends PluginUIComponent {
|
||||
<CameraSnapshots />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>,
|
||||
parent);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* 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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface BooleanParamComponentProps {
|
||||
label: string
|
||||
param: PD.Boolean
|
||||
value: boolean
|
||||
onChange(v: boolean): void
|
||||
}
|
||||
|
||||
export interface BooleanParamComponentState {
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export class BooleanParamComponent extends React.Component<BooleanParamComponentProps, BooleanParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: boolean) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<button onClick={e => this.onChange(!this.state.value) }>
|
||||
{this.state.value ? 'Off' : 'On'}
|
||||
</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* 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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../../mol-util/color/tables';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
|
||||
export interface ColorParamComponentProps {
|
||||
label: string
|
||||
param: PD.Color
|
||||
value: Color
|
||||
onChange(v: Color): void
|
||||
}
|
||||
|
||||
export interface ColorParamComponentState {
|
||||
value: Color
|
||||
}
|
||||
|
||||
export class ColorParamComponent extends React.Component<ColorParamComponentProps, ColorParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: Color) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<select value={this.state.value} onChange={e => this.onChange(Color(parseInt(e.target.value))) }>
|
||||
{Object.keys(ColorNames).map(name => {
|
||||
return <option key={name} value={(ColorNames as { [k: string]: Color})[name]}>{name}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* 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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface MultiSelectParamComponentProps<T extends string> {
|
||||
label: string
|
||||
param: PD.MultiSelect<T>
|
||||
value: T[]
|
||||
onChange(v: T[]): void
|
||||
}
|
||||
|
||||
export interface MultiSelectParamComponentState<T extends string> {
|
||||
value: T[]
|
||||
}
|
||||
|
||||
export class MultiSelectParamComponent<T extends string> extends React.Component<MultiSelectParamComponentProps<T>, MultiSelectParamComponentState<T>> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: T[]) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<select multiple value={this.state.value} onChange={e => {
|
||||
const value = Array.from(e.target.options).filter(option => option.selected).map(option => option.value)
|
||||
this.onChange(value as T[])
|
||||
}}>
|
||||
{this.props.param.options.map(v => {
|
||||
const [value, label] = v
|
||||
return <option key={label} value={value}>{label}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* 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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface NumberParamComponentProps {
|
||||
label: string
|
||||
param: PD.Numeric
|
||||
value: number
|
||||
onChange(v: number): void
|
||||
}
|
||||
|
||||
export interface NumberParamComponentState {
|
||||
value: number
|
||||
}
|
||||
|
||||
export class NumberParamComponent extends React.Component<NumberParamComponentProps, NumberParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(valueStr: string) {
|
||||
const value = this.props.param.step && Number.isInteger(this.props.param.step) ? parseInt(valueStr) : parseFloat(valueStr)
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<input type='range'
|
||||
value={this.state.value}
|
||||
min={this.props.param.min}
|
||||
max={this.props.param.max}
|
||||
step={this.props.param.step}
|
||||
onChange={e => this.onChange(e.currentTarget.value)}
|
||||
>
|
||||
</input>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface SelectParamComponentProps<T extends string> {
|
||||
label: string
|
||||
param: PD.Select<T>
|
||||
value: T
|
||||
onChange(v: T): void
|
||||
}
|
||||
|
||||
export interface SelectParamComponentState<T extends string> {
|
||||
value: T
|
||||
}
|
||||
|
||||
export class SelectParamComponent<T extends string> extends React.Component<SelectParamComponentProps<T>, SelectParamComponentState<T>> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: T) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<select value={this.state.value} onChange={e => this.onChange(e.target.value as T) }>
|
||||
{this.props.param.options.map(v => {
|
||||
const [value, label] = v
|
||||
return <option key={label} value={value}>{label}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* 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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface TextParamComponentProps {
|
||||
label: string
|
||||
param: PD.Text
|
||||
value: string
|
||||
onChange(v: string): void
|
||||
}
|
||||
|
||||
export interface TextParamComponentState {
|
||||
value: string
|
||||
}
|
||||
|
||||
export class TextParamComponent extends React.Component<TextParamComponentProps, TextParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: string) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<input type='text'
|
||||
value={this.state.value}
|
||||
onChange={e => this.onChange(e.currentTarget.value)}
|
||||
>
|
||||
</input>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 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 { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { BooleanParamComponent } from './parameter/boolean';
|
||||
import { NumberParamComponent } from './parameter/number';
|
||||
import { SelectParamComponent } from './parameter/select';
|
||||
import { MultiSelectParamComponent } from './parameter/multi-select';
|
||||
import { TextParamComponent } from './parameter/text';
|
||||
import { ColorParamComponent } from './parameter/color';
|
||||
import { camelCaseToWords } from '../../mol-util/string';
|
||||
|
||||
interface ParametersProps<P extends PD.Params> {
|
||||
params: P
|
||||
values: { [k in keyof P]: P[k]['defaultValue'] }
|
||||
onChange<K extends keyof P>(k: K, v: P[K]['defaultValue']): void
|
||||
}
|
||||
|
||||
type ParametersState = {}
|
||||
|
||||
function getParamComponent<P extends PD.Any>(label: string, param: PD.Any, value: P['defaultValue'], onChange: (v: P['defaultValue']) => void) {
|
||||
switch (param.type) {
|
||||
case 'boolean':
|
||||
return <BooleanParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'number':
|
||||
return <NumberParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'select':
|
||||
return <SelectParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'multi-select':
|
||||
return <MultiSelectParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'text':
|
||||
return <TextParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'color':
|
||||
return <ColorParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function getLabel(name: string, param: PD.Base<any>) {
|
||||
return param.label === undefined ? camelCaseToWords(name) : param.label
|
||||
}
|
||||
|
||||
export class ParametersComponent<P extends PD.Params> extends React.Component<ParametersProps<P>, ParametersState> {
|
||||
onChange(k: string, value: any) {
|
||||
this.props.onChange(k, value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ width: '100%' }}>
|
||||
{ Object.keys(this.props.params).map(k => {
|
||||
const param = this.props.params[k]
|
||||
const value = this.props.values[k]
|
||||
const label = getLabel(k, param)
|
||||
return <div key={k}>
|
||||
{getParamComponent(label, param, value, v => this.onChange(k, v))}
|
||||
</div>
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// /**
|
||||
// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
// *
|
||||
// * @author David Sehnal <david.sehnal@gmail.com>
|
||||
// */
|
||||
|
||||
// import * as React from 'react'
|
||||
// import { Structure, StructureSequence, Queries, StructureSelection, StructureProperties, StructureQuery } from 'mol-model/structure';
|
||||
// import { EmptyLoci } from 'mol-model/loci';
|
||||
|
||||
// export class SequenceView extends View<SequenceViewController, {}, {}> {
|
||||
// render() {
|
||||
// const s = this.controller.latestState.structure;
|
||||
// if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
|
||||
|
||||
// const seqs = s.models[0].sequence.sequences;
|
||||
// return <div className='molstar-sequence-view-wrap'>
|
||||
// {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
|
||||
// </div>;
|
||||
// }
|
||||
// }
|
||||
|
||||
// function createQuery(entityId: string, label_seq_id: number) {
|
||||
// return Queries.generators.atoms({
|
||||
// entityTest: ctx => StructureProperties.entity.id(ctx.element) === entityId,
|
||||
// residueTest: ctx => StructureProperties.residue.label_seq_id(ctx.element) === label_seq_id
|
||||
// });
|
||||
// }
|
||||
|
||||
// // TODO: this is really ineffective and should be done using a canvas.
|
||||
// class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
|
||||
|
||||
// raiseInteractityEvent(seqId?: number) {
|
||||
// if (typeof seqId === 'undefined') {
|
||||
// InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const query = createQuery(this.props.seq.entityId, seqId);
|
||||
// const loci = StructureSelection.toLoci(StructureQuery.run(query, this.props.structure));
|
||||
// if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
|
||||
// else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci);
|
||||
// }
|
||||
|
||||
|
||||
// render() {
|
||||
// const { ctx, seq } = this.props;
|
||||
// const { offset, sequence } = seq.sequence;
|
||||
|
||||
// const elems: JSX.Element[] = [];
|
||||
// for (let i = 0, _i = sequence.length; i < _i; i++) {
|
||||
// elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
|
||||
// }
|
||||
|
||||
// return <div style={{ wordWrap: 'break-word' }}>
|
||||
// <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset} </span>
|
||||
// {elems}
|
||||
// </div>;
|
||||
// }
|
||||
// }
|
||||
|
||||
// class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
|
||||
// state = { isHighlighted: false }
|
||||
|
||||
// mouseEnter = () => {
|
||||
// this.setState({ isHighlighted: true });
|
||||
// this.props.parent.raiseInteractityEvent(this.props.seqId);
|
||||
// }
|
||||
|
||||
// mouseLeave = () => {
|
||||
// this.setState({ isHighlighted: false });
|
||||
// this.props.parent.raiseInteractityEvent();
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
|
||||
// style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
|
||||
// {this.props.letter}
|
||||
// </span>;
|
||||
// }
|
||||
// }
|
||||
@@ -250,7 +250,7 @@ function updateOrtho(camera: Camera) {
|
||||
let top = cy + dy
|
||||
let bottom = cy - dy
|
||||
|
||||
if (viewOffset && viewOffset.enabled) {
|
||||
if (viewOffset.enabled) {
|
||||
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth)
|
||||
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight)
|
||||
const scaleW = (fullRight - fullLeft) / viewOffset.width
|
||||
@@ -279,7 +279,7 @@ function updatePers(camera: Camera) {
|
||||
let width = aspect * height
|
||||
let left = -0.5 * width
|
||||
|
||||
if (viewOffset && viewOffset.enabled) {
|
||||
if (viewOffset.enabled) {
|
||||
left += viewOffset.offsetX * width / viewOffset.fullWidth
|
||||
top -= viewOffset.offsetY * height / viewOffset.fullHeight
|
||||
width *= viewOffset.width / viewOffset.fullWidth
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Representation } from '../mol-repr/representation';
|
||||
import Scene from '../mol-gl/scene';
|
||||
import { GraphicsRenderVariant } from '../mol-gl/webgl/render-item';
|
||||
import { PickingId } from '../mol-geo/geometry/picking';
|
||||
import { MarkerAction } from '../mol-geo/geometry/marker-data';
|
||||
import { MarkerAction } from '../mol-util/marker-action';
|
||||
import { Loci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
|
||||
import { Camera } from './camera';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
@@ -87,7 +87,7 @@ interface Canvas3D {
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
|
||||
|
||||
namespace Canvas3D {
|
||||
export interface HighlightEvent { current: Representation.Loci, prev: Representation.Loci, modifiers?: ModifiersKeys }
|
||||
export interface HighlightEvent { current: Representation.Loci, modifiers?: ModifiersKeys }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
|
||||
|
||||
@@ -59,7 +59,7 @@ export class Canvas3dInteractionHelper {
|
||||
|
||||
const loci = this.getLoci(this.id);
|
||||
if (!Representation.Loci.areEqual(this.prevLoci, loci)) {
|
||||
this.events.highlight.next({ current: loci, prev: this.prevLoci, modifiers: this.modifiers });
|
||||
this.events.highlight.next({ current: loci, modifiers: this.modifiers });
|
||||
this.prevLoci = loci;
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,8 @@ export class Canvas3dInteractionHelper {
|
||||
leave() {
|
||||
this.inside = false;
|
||||
if (this.prevLoci.loci !== EmptyLoci) {
|
||||
const prev = this.prevLoci;
|
||||
this.prevLoci = Representation.Loci.Empty;
|
||||
this.events.highlight.next({ current: this.prevLoci, prev });
|
||||
this.events.highlight.next({ current: this.prevLoci });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@ export class Canvas3dInteractionHelper {
|
||||
modify(modifiers: ModifiersKeys) {
|
||||
if (this.prevLoci.loci === EmptyLoci || ModifiersKeys.areEqual(modifiers, this.modifiers)) return;
|
||||
this.modifiers = modifiers;
|
||||
this.events.highlight.next({ current: this.prevLoci, prev: this.prevLoci, modifiers: this.modifiers });
|
||||
this.events.highlight.next({ current: this.prevLoci, modifiers: this.modifiers });
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -17,6 +17,7 @@ export const min = Tuple.fst;
|
||||
export function max(i: Tuple) { return Tuple.snd(i) - 1; }
|
||||
export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
|
||||
export const hashCode = Tuple.hashCode;
|
||||
export const toString = Tuple.toString;
|
||||
|
||||
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. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -36,6 +36,8 @@ export function end(set: OrderedSetImpl) { return I.is(set) ? I.end(set) : S.end
|
||||
export function hashCode(set: OrderedSetImpl) { return I.is(set) ? I.hashCode(set) : S.hashCode(set); }
|
||||
// TODO: possibly add more hash functions to allow for multilevel hashing.
|
||||
|
||||
export function toString(set: OrderedSetImpl) { return I.is(set) ? I.toString(set) : S.toString(set); }
|
||||
|
||||
export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) {
|
||||
if (I.is(a)) {
|
||||
if (I.is(b)) return I.areEqual(a, b);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -35,6 +35,11 @@ export function hashCode(xs: Nums) {
|
||||
if (s > 2) return hash4(s, xs[0], xs[s - 1], xs[s >> 1]);
|
||||
return hash3(s, xs[0], xs[s - 1]);
|
||||
}
|
||||
export function toString(xs: Nums) {
|
||||
const s = xs.length;
|
||||
if (s > 5) return `[${xs[0]}, ${xs[1]}, ..., ${xs[s - 1]}], length ${s}`;
|
||||
return `[${(xs as number[]).join(', ')}]`;
|
||||
}
|
||||
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
export function indexOf(xs: Nums, v: number) {
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Interval {
|
||||
export const size: <T extends number = number>(interval: Interval<T>) => number = Impl.size as any;
|
||||
/** Hash code describing the interval */
|
||||
export const hashCode: <T extends number = number>(interval: Interval<T>) => number = Impl.hashCode as any;
|
||||
/** String representation of the interval */
|
||||
export const toString: <T extends number = number>(interval: Interval<T>) => string = Impl.toString as any;
|
||||
|
||||
/** Test if two intervals are identical */
|
||||
export const areEqual: <T extends number = number>(a: Interval<T>, b: Interval<T>) => boolean = Impl.areEqual as any;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -8,7 +8,6 @@ import * as Base from './impl/ordered-set'
|
||||
import Interval from './interval'
|
||||
import SortedArray from './sorted-array';
|
||||
|
||||
/** test */
|
||||
namespace OrderedSet {
|
||||
export const Empty: OrderedSet = Base.Empty as any;
|
||||
export const ofSingleton: <T extends number = number>(value: T) => OrderedSet<T> = Base.ofSingleton as any;
|
||||
@@ -62,10 +61,12 @@ namespace OrderedSet {
|
||||
OrderedSet.forEach(set, v => array.push(v))
|
||||
return array
|
||||
}
|
||||
|
||||
export function toString<T extends number = number>(set: OrderedSet<T>): string {
|
||||
return Base.toString(set)
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents bla */
|
||||
type OrderedSet<T extends number = number> = SortedArray<T> | Interval<T>
|
||||
|
||||
|
||||
export default OrderedSet
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -31,6 +31,7 @@ namespace SortedArray {
|
||||
export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any;
|
||||
export const size: <T extends number = number>(array: SortedArray<T>) => number = Impl.size as any;
|
||||
export const hashCode: <T extends number = number>(array: SortedArray<T>) => number = Impl.hashCode as any;
|
||||
export const toString: <T extends number = number>(array: SortedArray<T>) => string = Impl.toString as any;
|
||||
|
||||
export const areEqual: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areEqual as any;
|
||||
export const areIntersecting: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areIntersecting as any;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -73,6 +73,11 @@ namespace IntTuple {
|
||||
_float64[0] = t as any;
|
||||
return hash2(_int32[0], _int32[1]);
|
||||
}
|
||||
|
||||
export function toString(t: IntTuple) {
|
||||
_float64[0] = t as any;
|
||||
return `(${_int32[0]}, ${_int32[1]})`;
|
||||
}
|
||||
}
|
||||
|
||||
export default IntTuple
|
||||
@@ -13,57 +13,6 @@ export type MarkerData = {
|
||||
uMarkerTexDim: ValueCell<Vec2>
|
||||
}
|
||||
|
||||
export enum MarkerAction {
|
||||
Highlight,
|
||||
RemoveHighlight,
|
||||
Select,
|
||||
Deselect,
|
||||
Toggle,
|
||||
Clear
|
||||
}
|
||||
|
||||
export function applyMarkerAction(array: Uint8Array, start: number, end: number, action: MarkerAction) {
|
||||
let changed = false
|
||||
for (let i = start; i < end; ++i) {
|
||||
let v = array[i]
|
||||
switch (action) {
|
||||
case MarkerAction.Highlight:
|
||||
if (v % 2 === 0) {
|
||||
v += 1
|
||||
}
|
||||
break
|
||||
case MarkerAction.RemoveHighlight:
|
||||
if (v % 2 !== 0) {
|
||||
v -= 1
|
||||
}
|
||||
break
|
||||
case MarkerAction.Select:
|
||||
if (v < 2) v += 2
|
||||
// v += 2
|
||||
break
|
||||
case MarkerAction.Deselect:
|
||||
// if (v >= 2) {
|
||||
// v -= 2
|
||||
// }
|
||||
v = v % 2
|
||||
break
|
||||
case MarkerAction.Toggle:
|
||||
if (v >= 2) {
|
||||
v -= 2
|
||||
} else {
|
||||
v += 2
|
||||
}
|
||||
break
|
||||
case MarkerAction.Clear:
|
||||
v = 0
|
||||
break
|
||||
}
|
||||
changed = array[i] !== v || changed
|
||||
array[i] = v
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
|
||||
const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array)
|
||||
if (markerData) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export function applyOverpaintColor(array: Uint8Array, start: number, end: numbe
|
||||
}
|
||||
|
||||
export function clearOverpaint(array: Uint8Array, start: number, end: number) {
|
||||
array.fill(0, start, end)
|
||||
array.fill(0, start * 4, end * 4)
|
||||
}
|
||||
|
||||
export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.312, IHM 1.0, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.312, IHM 1.0, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { mmCIF_Schema } from './mmcif';
|
||||
import { mmCIF_Schema } from './mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
|
||||
export const mmCIF_residueId_schema = {
|
||||
label_comp_id: mmCIF_Schema.atom_site.label_comp_id,
|
||||
@@ -15,4 +17,10 @@ export const mmCIF_residueId_schema = {
|
||||
auth_comp_id: mmCIF_Schema.atom_site.auth_atom_id,
|
||||
auth_seq_id: mmCIF_Schema.atom_site.auth_seq_id,
|
||||
auth_asym_id: mmCIF_Schema.atom_site.auth_asym_id
|
||||
}
|
||||
|
||||
export const mmCIF_chemCompBond_schema = {
|
||||
...mmCIF_Schema.chem_comp_bond,
|
||||
/** Indicates if the bond entry was taken from the protonation variant dictionary */
|
||||
molstar_protonation_variant: Column.Schema.Str()
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.312, IHM 1.0, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -1549,6 +1549,85 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
program_version: str,
|
||||
},
|
||||
/**
|
||||
* Data items in the PDBX_UNOBS_OR_ZERO_OCC_RESIDUES category list the
|
||||
* residues within the entry that are not observed or have zero occupancy.
|
||||
*/
|
||||
pdbx_unobs_or_zero_occ_residues: {
|
||||
/**
|
||||
* The value of _pdbx_unobs_or_zero_occ_residues.id must uniquely identify
|
||||
* each item in the PDBX_UNOBS_OR_ZERO_OCC_RESIDUES list.
|
||||
*
|
||||
* This is an integer serial number.
|
||||
*/
|
||||
id: int,
|
||||
/**
|
||||
* The value of polymer flag indicates whether the unobserved or
|
||||
* zero occupancy residue is part of a polymer chain or not
|
||||
*/
|
||||
polymer_flag: Aliased<'Y' | 'N'>(str),
|
||||
/**
|
||||
* The value of occupancy flag indicates whether the residue
|
||||
* is unobserved (= 1) or the coordinates have an occupancy of zero (=0)
|
||||
*/
|
||||
occupancy_flag: Aliased<'1' | '0'>(int),
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.pdbx_PDB_model_num in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
PDB_model_num: int,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
auth_asym_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.auth_comp_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
auth_comp_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.auth_seq_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
auth_seq_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.pdbx_PDB_ins_code in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
PDB_ins_code: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.label_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
label_asym_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.label_comp_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
label_comp_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.label_seq_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
label_seq_id: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the PDBX_STRUCT_MOD_RESIDUE category list the
|
||||
* modified polymer components in the entry and provide some
|
||||
@@ -2079,10 +2158,10 @@ export const mmCIF_Schema = {
|
||||
pdbx_end_seq_num: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the PDBX_ENTITY_DESCRIPTOR category provide
|
||||
* Data items in the PDBX_ENTITY_BRANCH_DESCRIPTOR category provide
|
||||
* string descriptors of entity chemical structure.
|
||||
*/
|
||||
pdbx_entity_descriptor: {
|
||||
pdbx_entity_branch_descriptor: {
|
||||
/**
|
||||
* This data item is a pointer to _entity_poly.entity_id in the ENTITY
|
||||
* category.
|
||||
|
||||
@@ -84,7 +84,7 @@ function handleMolecule(state: State) {
|
||||
molecule.mol_comment = getTokenString(tokenizer)
|
||||
}
|
||||
|
||||
function isStatus_bit(aString: String): Boolean {
|
||||
function isStatus_bit(aString: string): boolean {
|
||||
if (aString.includes('DSPMOD') || aString.includes('TYPECOL') || aString.includes('CAP')
|
||||
|| aString.includes('BACKBONE') || aString.includes('DICT') || aString.includes('ESSENTIAL')
|
||||
|| aString.includes('WATER') || aString.includes('DIRECT')) {
|
||||
|
||||
183
src/mol-io/writer/_spec/cif.spec.ts
Normal file
183
src/mol-io/writer/_spec/cif.spec.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import * as Data from '../../reader/cif/data-model'
|
||||
import { CifWriter } from '../cif';
|
||||
import decodeMsgPack from '../../common/msgpack/decode'
|
||||
import { EncodedFile, EncodedCategory } from '../../common/binary-cif';
|
||||
import Field from '../../reader/cif/binary/field';
|
||||
import * as C from '../cif/encoder';
|
||||
|
||||
const cartn_x = Data.CifField.ofNumbers([1.001, 1.002, 1.003, 1.004, 1.005, 1.006, 1.007, 1.008, 1.009]);
|
||||
const cartn_y = Data.CifField.ofNumbers([-3.0, -2.666, -2.3333, -2.0, -1.666, -1.333, -1.0, -0.666, -0.333]);
|
||||
const cartn_z = Data.CifField.ofNumbers([1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => Math.sqrt(i)));
|
||||
const label_seq_id = Data.CifField.ofNumbers([1, 2, 3, 6, 11, 23, 47, 106, 235]);
|
||||
const atom_site = Data.CifCategory.ofFields('atom_site', { 'Cartn_x': cartn_x, 'Cartn_y': cartn_y, 'Cartn_z': cartn_z, 'label_seq_id': label_seq_id });
|
||||
const field1 = Data.CifField.ofNumbers([1, 2, 3, 6, 11, 23, 47, 106, 235]);
|
||||
const field2 = Data.CifField.ofNumbers([-1, -2, -3, -6, -11, -23, -47, -106, -235]);
|
||||
const other_fields = Data.CifCategory.ofFields('other_fields', { 'field1': field1, 'field2': field2 });
|
||||
|
||||
const encoding_aware_encoder = CifWriter.createEncoder({
|
||||
binary: true,
|
||||
binaryAutoClassifyEncoding: true,
|
||||
binaryEncodingPovider: CifWriter.createEncodingProviderFromJsonConfig([
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'Cartn_y',
|
||||
'encoding': 'rle',
|
||||
'precision': 0
|
||||
},
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'Cartn_z',
|
||||
'encoding': 'delta',
|
||||
'precision': 1
|
||||
},
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'label_seq_id',
|
||||
'encoding': 'delta-rle'
|
||||
}
|
||||
])
|
||||
});
|
||||
|
||||
describe('encoding-config', () => {
|
||||
const decoded = process(encoding_aware_encoder);
|
||||
|
||||
const decoded_atom_site = decoded.blocks[0].categories['atom_site'];
|
||||
const decoded_cartn_x = decoded_atom_site.getField('Cartn_x')!;
|
||||
const decoded_cartn_y = decoded_atom_site.getField('Cartn_y')!;
|
||||
const decoded_cartn_z = decoded_atom_site.getField('Cartn_z')!;
|
||||
const decoded_label_seq_id = decoded_atom_site.getField('label_seq_id')!;
|
||||
|
||||
const delta = 0.001;
|
||||
function assert(e: ArrayLike<number>, a: ArrayLike<number>) {
|
||||
expect(e.length).toBe(a.length);
|
||||
for (let i = 0; i < e.length; i++) {
|
||||
expect(Math.abs(e[i] - a[i])).toBeLessThan(delta);
|
||||
}
|
||||
}
|
||||
|
||||
function join(field: Data.CifField) {
|
||||
return field.binaryEncoding!.map(e => e.kind).join();
|
||||
}
|
||||
|
||||
it('strategy', () => {
|
||||
expect(join(decoded_cartn_x)).toBe('FixedPoint,Delta,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_cartn_y)).toBe('FixedPoint,RunLength,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_cartn_z)).toBe('FixedPoint,Delta,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_label_seq_id)).toBe('Delta,RunLength,IntegerPacking,ByteArray');
|
||||
});
|
||||
|
||||
it('precision', () => {
|
||||
assert(decoded_cartn_x.toFloatArray(), cartn_x.toFloatArray());
|
||||
assert(decoded_cartn_y.toFloatArray(), cartn_y.toFloatArray().map(d => Math.round(d)));
|
||||
assert(decoded_cartn_z.toFloatArray(), cartn_z.toFloatArray().map(d => Math.round(d * 10) / 10));
|
||||
assert(decoded_label_seq_id.toIntArray(), label_seq_id.toIntArray());
|
||||
});
|
||||
});
|
||||
|
||||
const filter_aware_encoder1 = CifWriter.createEncoder({
|
||||
binary: true,
|
||||
binaryAutoClassifyEncoding: true
|
||||
});
|
||||
filter_aware_encoder1.setFilter(C.Category.filterOf('atom_site\n' +
|
||||
'\n' +
|
||||
'atom_site.Cartn_x\n' +
|
||||
'atom_site.Cartn_y\n'));
|
||||
|
||||
const filter_aware_encoder2 = CifWriter.createEncoder({
|
||||
binary: true
|
||||
});
|
||||
filter_aware_encoder2.setFilter(C.Category.filterOf('!atom_site\n' +
|
||||
'\n' +
|
||||
'!other_fields.field2\n'));
|
||||
|
||||
describe('filtering-config', () => {
|
||||
const decoded1 = process(filter_aware_encoder1);
|
||||
|
||||
const atom_site1 = decoded1.blocks[0].categories['atom_site'];
|
||||
const cartn_x1 = atom_site1.getField('Cartn_x');
|
||||
const cartn_y1 = atom_site1.getField('Cartn_y');
|
||||
const cartn_z1 = atom_site1.getField('Cartn_z');
|
||||
const label_seq_id1 = atom_site1.getField('label_seq_id');
|
||||
const fields1 = decoded1.blocks[0].categories['other_fields'];
|
||||
|
||||
it('whitelist-filtering', () => {
|
||||
expect(atom_site1).toBeDefined();
|
||||
expect(cartn_x1).toBeDefined();
|
||||
expect(cartn_y1).toBeDefined();
|
||||
expect(cartn_z1).toBeUndefined();
|
||||
expect(label_seq_id1).toBeUndefined();
|
||||
expect(fields1).toBeUndefined();
|
||||
});
|
||||
|
||||
const decoded2 = process(filter_aware_encoder2);
|
||||
|
||||
const atom_site2 = decoded2.blocks[0].categories['atom_site'];
|
||||
const fields2 = decoded2.blocks[0].categories['other_fields'];
|
||||
const field12 = fields2.getField('field1');
|
||||
const field22 = fields2.getField('field2');
|
||||
|
||||
it('blacklist-filtering', () => {
|
||||
expect(atom_site2).toBeUndefined();
|
||||
expect(fields2).toBeDefined();
|
||||
expect(field12).toBeDefined();
|
||||
expect(field22).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
function process(encoder: C.Encoder) {
|
||||
encoder.startDataBlock('test');
|
||||
|
||||
for (const cat of [atom_site, other_fields]) {
|
||||
const fields: CifWriter.Field[] = [];
|
||||
for (const f of cat.fieldNames) {
|
||||
fields.push(wrap(f, cat.getField(f)!))
|
||||
}
|
||||
encoder.writeCategory(getCategoryInstanceProvider(cat, fields));
|
||||
}
|
||||
|
||||
const encoded = encoder.getData() as Uint8Array;
|
||||
|
||||
const unpacked = decodeMsgPack(encoded) as EncodedFile;
|
||||
return Data.CifFile(unpacked.dataBlocks.map(block => {
|
||||
const cats = Object.create(null);
|
||||
for (const cat of block.categories) cats[cat.name.substr(1)] = Category(cat);
|
||||
return Data.CifBlock(block.categories.map(c => c.name.substr(1)), cats, block.header);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function getCategoryInstanceProvider(cat: Data.CifCategory, fields: CifWriter.Field[]): CifWriter.Category {
|
||||
return {
|
||||
name: cat.name,
|
||||
instance: () => CifWriter.categoryInstance(fields, { data: cat, rowCount: cat.rowCount })
|
||||
};
|
||||
}
|
||||
|
||||
function wrap(name: string, field: Data.CifField): CifWriter.Field {
|
||||
const type = Data.getCifFieldType(field);
|
||||
if (type['@type'] === 'str') {
|
||||
return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
|
||||
} else if (type['@type'] === 'float') {
|
||||
return { name, type: CifWriter.Field.Type.Float, value: field.float, valueKind: field.valueKind };
|
||||
} else {
|
||||
return { name, type: CifWriter.Field.Type.Int, value: field.int, valueKind: field.valueKind };
|
||||
}
|
||||
}
|
||||
|
||||
function Category(data: EncodedCategory): Data.CifCategory {
|
||||
const map = Object.create(null);
|
||||
const cache = Object.create(null);
|
||||
for (const col of data.columns) map[col.name] = col;
|
||||
return {
|
||||
rowCount: data.rowCount,
|
||||
name: data.name.substr(1),
|
||||
fieldNames: data.columns.map(c => c.name),
|
||||
getField(name) {
|
||||
const col = map[name];
|
||||
if (!col) return void 0;
|
||||
if (!!cache[name]) return cache[name];
|
||||
cache[name] = Field(col);
|
||||
return cache[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,5 +53,64 @@ export namespace CifWriter {
|
||||
return ff && ff.binaryEncoding ? ArrayEncoder.fromEncoding(ff.binaryEncoding) : void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
const hint = hints[i];
|
||||
if (hint.categoryName === c && hint.columnName === f) {
|
||||
return resolveEncoding(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveEncoding(hint: EncodingStrategyHint): ArrayEncoder {
|
||||
const precision: number | undefined = hint.precision;
|
||||
if (precision !== void 0) {
|
||||
const multiplier = Math.pow(10, precision);
|
||||
const fixedPoint = E.by(E.fixedPoint(multiplier));
|
||||
switch (hint.encoding) {
|
||||
case 'pack':
|
||||
return fixedPoint.and(E.integerPacking);
|
||||
case 'rle':
|
||||
return fixedPoint.and(E.runLength).and(E.integerPacking);
|
||||
case 'delta':
|
||||
return fixedPoint.and(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle':
|
||||
return fixedPoint.and(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
};
|
||||
} else {
|
||||
switch (hint.encoding) {
|
||||
case 'pack':
|
||||
return E.by(E.integerPacking);
|
||||
case 'rle':
|
||||
return E.by(E.runLength).and(E.integerPacking);
|
||||
case 'delta':
|
||||
return E.by(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle':
|
||||
return E.by(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
}
|
||||
}
|
||||
throw new Error('cannot be reached');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the information needed to encode certain fields: category and column name as well as encoding tag, precision is optional and identifies float columns.
|
||||
*/
|
||||
export interface EncodingStrategyHint {
|
||||
categoryName: string,
|
||||
columnName: string,
|
||||
// TODO would be nice to infer strategy and precision if needed
|
||||
encoding: EncodingType,
|
||||
/**
|
||||
* number of decimal places to keep - must be specified to float columns
|
||||
*/
|
||||
precision?: number
|
||||
}
|
||||
|
||||
type EncodingType = 'pack' | 'rle' | 'delta' | 'delta-rle'
|
||||
@@ -132,6 +132,60 @@ export namespace Category {
|
||||
includeField(categoryName: string, fieldName: string): boolean,
|
||||
}
|
||||
|
||||
export function filterOf(directives: string): Filter {
|
||||
const cat_whitelist: string[] = [];
|
||||
const cat_blacklist: string[] = [];
|
||||
const field_whitelist: string[] = [];
|
||||
const field_blacklist: string[] = [];
|
||||
|
||||
for (let d of directives.split(/[\r\n]+/)) {
|
||||
d = d.trim();
|
||||
// allow for empty lines in config
|
||||
if (d.length === 0) continue;
|
||||
// let ! denote blacklisted entries
|
||||
const blacklist = /^!/.test(d);
|
||||
if (blacklist) d = d.substr(1);
|
||||
const split = d.split(/\./);
|
||||
const field = split[1];
|
||||
const list = blacklist ? (field ? field_blacklist : cat_blacklist) : (field ? field_whitelist : cat_whitelist);
|
||||
|
||||
list[list.length] = d;
|
||||
|
||||
// ensure categories are aware about whitelisted columns
|
||||
if (field && !cat_whitelist.includes(split[0])) {
|
||||
cat_whitelist[cat_whitelist.length] = split[0];
|
||||
}
|
||||
}
|
||||
|
||||
const wlcatcol = field_whitelist.map(it => it.split('.')[0]);
|
||||
// blacklist has higher priority
|
||||
return {
|
||||
includeCategory(cat) {
|
||||
// block if category in black
|
||||
if (cat_blacklist.includes(cat)) {
|
||||
return false;
|
||||
} else {
|
||||
// if there is a whitelist, the category has to be explicitly allowed
|
||||
return cat_whitelist.length <= 0 ||
|
||||
// otherwise include if whitelist contains category
|
||||
cat_whitelist.indexOf(cat) !== -1;
|
||||
}
|
||||
},
|
||||
includeField(cat, field) {
|
||||
// column names are assumed to follow the pattern 'category_name.column_name'
|
||||
const full = cat + '.' + field;
|
||||
if (field_blacklist.includes(full)) {
|
||||
return false;
|
||||
} else {
|
||||
// if for this category no whitelist entries exist
|
||||
return !wlcatcol.includes(cat) ||
|
||||
// otherwise must be specifically allowed
|
||||
field_whitelist.includes(full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultFilter: Filter = {
|
||||
includeCategory(cat) { return true; },
|
||||
includeField(cat, field) { return true; }
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Spacegroup {
|
||||
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = updateOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k));
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { Vec3, Mat4, Mat3, Quat } from '../linear-algebra/3d'
|
||||
import { lerp as scalar_lerp } from '../../mol-math/interpolate';
|
||||
import { defaults } from '../../mol-util';
|
||||
|
||||
interface SymmetryOperator {
|
||||
readonly name: string,
|
||||
@@ -21,6 +22,8 @@ interface SymmetryOperator {
|
||||
readonly ncsId: string,
|
||||
|
||||
readonly hkl: Vec3,
|
||||
/** spacegroup symmetry operator index, -1 if not applicable */
|
||||
readonly spgrOp: number,
|
||||
|
||||
readonly matrix: Mat4,
|
||||
// cache the inverse of the transform
|
||||
@@ -35,12 +38,13 @@ namespace SymmetryOperator {
|
||||
|
||||
export const RotationTranslationEpsilon = 0.005;
|
||||
|
||||
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3): SymmetryOperator {
|
||||
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
|
||||
const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
|
||||
spgrOp = defaults(spgrOp, -1)
|
||||
ncsId = ncsId || ''
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, ncsId };
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId };
|
||||
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, ncsId };
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId };
|
||||
}
|
||||
|
||||
export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
|
||||
@@ -106,11 +110,11 @@ namespace SymmetryOperator {
|
||||
|
||||
/**
|
||||
* Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix).
|
||||
* Keep `name`, `assembly`, `ncsId` and `hkl` properties from second.
|
||||
* Keep `name`, `assembly`, `ncsId`, `hkl` and `spgrOpId` properties from second.
|
||||
*/
|
||||
export function compose(first: SymmetryOperator, second: SymmetryOperator) {
|
||||
const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
|
||||
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl);
|
||||
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl, second.spgrOp);
|
||||
}
|
||||
|
||||
export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
|
||||
|
||||
@@ -435,6 +435,10 @@ namespace Quat {
|
||||
|
||||
return normalize(out, Quat.fromMat3(out, axesTmpMat));
|
||||
}
|
||||
|
||||
export function toString(a: Quat, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)} ${a[3].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Quat
|
||||
@@ -166,6 +166,10 @@ namespace Vec2 {
|
||||
export function areEqual(a: Vec2, b: Vec2) {
|
||||
return a[0] === b[0] && a[1] === b[1];
|
||||
}
|
||||
|
||||
export function toString(a: Vec2, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)}}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Vec2
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import Mat4 from './mat4';
|
||||
import { Quat, Mat3, EPSILON } from '../3d';
|
||||
import { spline as _spline, clamp } from '../../interpolate'
|
||||
import { spline as _spline, quadraticBezier as _quadraticBezier, clamp } from '../../interpolate'
|
||||
import { NumberArray } from '../../../mol-util/type-helpers';
|
||||
|
||||
interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 }
|
||||
@@ -344,6 +344,14 @@ namespace Vec3 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function quadraticBezier(out: Vec3, a: Vec3, b: Vec3, c: Vec3, t: number) {
|
||||
out[0] = _quadraticBezier(a[0], b[0], c[0], t);
|
||||
out[1] = _quadraticBezier(a[1], b[1], c[1], t);
|
||||
out[2] = _quadraticBezier(a[2], b[2], c[2], t);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a spline interpolation with two control points and a tension parameter
|
||||
*/
|
||||
@@ -525,8 +533,8 @@ namespace Vec3 {
|
||||
return normalize(out, cross(out, triangleNormalTmpAB, triangleNormalTmpAC));
|
||||
}
|
||||
|
||||
export function toString(a: Vec3) {
|
||||
return `[${a[0]} ${a[1]} ${a[2]}]`;
|
||||
export function toString(a: Vec3, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,6 +225,10 @@ namespace Vec4 {
|
||||
Math.abs(a2 - b2) <= EPSILON.Value * Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
|
||||
Math.abs(a3 - b3) <= EPSILON.Value * Math.max(1.0, Math.abs(a3), Math.abs(b3)));
|
||||
}
|
||||
|
||||
export function toString(a: Vec4, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)} ${a[3].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Vec4
|
||||
@@ -12,7 +12,7 @@ import { Tensor, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import UUID from '../../../mol-util/uuid';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { Entities, ChemicalComponent, MissingResidue } from '../../../mol-model/structure/model/properties/common';
|
||||
import { CustomProperties } from '../../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
|
||||
import { createAssemblies } from './assembly';
|
||||
@@ -23,7 +23,6 @@ import { getSecondaryStructure } from './secondary-structure';
|
||||
import { getSequence } from './sequence';
|
||||
import { sortAtomSite } from './sort';
|
||||
import { StructConn } from './bonds/struct_conn';
|
||||
import { ChemicalComponent } from '../../../mol-model/structure/model/properties/chemical-component';
|
||||
import { getMoleculeType, MoleculeType, getEntityType } from '../../../mol-model/structure/model/types';
|
||||
import { ModelFormat } from '../format';
|
||||
import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
|
||||
@@ -98,14 +97,36 @@ function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['m
|
||||
return { parentId, details };
|
||||
}
|
||||
|
||||
function getMissingResidues(format: mmCIF_Format): Model['properties']['missingResidues'] {
|
||||
const map = new Map<string, MissingResidue>();
|
||||
const c = format.data.pdbx_unobs_or_zero_occ_residues
|
||||
|
||||
const getKey = (model_num: number, asym_id: string, seq_id: number) => {
|
||||
return `${model_num}|${asym_id}|${seq_id}`
|
||||
}
|
||||
|
||||
for (let i = 0, il = c._rowCount; i < il; ++i) {
|
||||
const key = getKey(c.PDB_model_num.value(i), c.label_asym_id.value(i), c.label_seq_id.value(i))
|
||||
map.set(key, { polymer_flag: c.polymer_flag.value(i), occupancy_flag: c.occupancy_flag.value(i) })
|
||||
}
|
||||
|
||||
return {
|
||||
has: (model_num: number, asym_id: string, seq_id: number) => {
|
||||
return map.has(getKey(model_num, asym_id, seq_id))
|
||||
},
|
||||
get: (model_num: number, asym_id: string, seq_id: number) => {
|
||||
return map.get(getKey(model_num, asym_id, seq_id))
|
||||
},
|
||||
size: map.size
|
||||
}
|
||||
}
|
||||
|
||||
function getChemicalComponentMap(format: mmCIF_Format): Model['properties']['chemicalComponentMap'] {
|
||||
const map = new Map<string, ChemicalComponent>();
|
||||
const { chem_comp } = format.data
|
||||
if (chem_comp._rowCount > 0) {
|
||||
const { id } = format.data.chem_comp
|
||||
for (let i = 0, il = id.rowCount; i < il; ++i) {
|
||||
map.set(id.value(i), Table.getRow(format.data.chem_comp, i))
|
||||
}
|
||||
const { id } = chem_comp
|
||||
for (let i = 0, il = id.rowCount; i < il; ++i) {
|
||||
map.set(id.value(i), Table.getRow(chem_comp, i))
|
||||
}
|
||||
return map
|
||||
}
|
||||
@@ -158,6 +179,7 @@ const getUniqueComponentNames = memoize1((format: mmCIF_Format) => {
|
||||
|
||||
export interface FormatData {
|
||||
modifiedResidues: Model['properties']['modifiedResidues']
|
||||
missingResidues: Model['properties']['missingResidues']
|
||||
chemicalComponentMap: Model['properties']['chemicalComponentMap']
|
||||
saccharideComponentMap: Model['properties']['saccharideComponentMap']
|
||||
}
|
||||
@@ -165,6 +187,7 @@ export interface FormatData {
|
||||
function getFormatData(format: mmCIF_Format): FormatData {
|
||||
return {
|
||||
modifiedResidues: getModifiedResidueNameMap(format),
|
||||
missingResidues: getMissingResidues(format),
|
||||
chemicalComponentMap: getChemicalComponentMap(format),
|
||||
saccharideComponentMap: getSaccharideComponentMap(format)
|
||||
}
|
||||
@@ -172,11 +195,12 @@ function getFormatData(format: mmCIF_Format): FormatData {
|
||||
|
||||
function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model): Model {
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, formatData, previous);
|
||||
const modelNum = atom_site.pdbx_PDB_model_num.value(0)
|
||||
if (previous && atomic.sameAsPrevious) {
|
||||
return {
|
||||
...previous,
|
||||
id: UUID.create22(),
|
||||
modelNum: atom_site.pdbx_PDB_model_num.value(0),
|
||||
modelNum,
|
||||
atomicConformation: atomic.conformation,
|
||||
_dynamicPropertyData: Object.create(null)
|
||||
};
|
||||
@@ -192,7 +216,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIn
|
||||
label,
|
||||
entry: label,
|
||||
sourceData: format,
|
||||
modelNum: atom_site.pdbx_PDB_model_num.value(0),
|
||||
modelNum,
|
||||
entities,
|
||||
symmetry: getSymmetry(format),
|
||||
sequence: getSequence(format.data, entities, atomic.hierarchy, formatData.modifiedResidues.parentId),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -18,7 +18,6 @@ import { CifCategory } from '../../mol-io/reader/cif';
|
||||
import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { GraphQLClient } from '../../mol-util/graphql-client';
|
||||
import { ajaxGet } from '../../mol-util/data-source';
|
||||
|
||||
const { str, int, float, Aliased, Vector, List } = Column.Schema;
|
||||
|
||||
@@ -183,13 +182,12 @@ export function AssemblySymmetry(db: AssemblySymmetry.Database): AssemblySymmetr
|
||||
type SymmetryKind = 'GLOBAL' | 'LOCAL' | 'PSEUDO'
|
||||
type SymmetryType = 'ASYMMETRIC' | 'CYCLIC' | 'DIHEDRAL' | 'HELICAL' | 'ICOSAHEDRAL' | 'OCTAHEDRAL' | 'TETRAHEDRAL'
|
||||
|
||||
const Client = new GraphQLClient(AssemblySymmetry.GraphQLEndpointURL, ajaxGet)
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
export function is(x: any): x is AssemblySymmetry {
|
||||
return x['@type'] === 'rcsb_assembly_symmetry'
|
||||
}
|
||||
export const GraphQLEndpointURL = '//rest-dev.rcsb.org/graphql'
|
||||
export const GraphQLEndpointURL = '//rest-staging.rcsb.org/graphql'
|
||||
|
||||
export const Schema = {
|
||||
rcsb_assembly_symmetry_info: {
|
||||
updated_datetime_utc: Column.Schema.str
|
||||
@@ -257,7 +255,7 @@ export namespace AssemblySymmetry {
|
||||
|
||||
export const Descriptor = _Descriptor;
|
||||
|
||||
export async function attachFromCifOrAPI(model: Model, client: GraphQLClient = Client, ctx?: RuntimeContext) {
|
||||
export async function attachFromCifOrAPI(model: Model, client: GraphQLClient, ctx?: RuntimeContext) {
|
||||
if (model.customProperties.has(Descriptor)) return true;
|
||||
|
||||
let db: Database
|
||||
@@ -266,8 +264,10 @@ export namespace AssemblySymmetry {
|
||||
db = createDatabaseFromCif(model)
|
||||
} else {
|
||||
let result: AssemblySymmetryGraphQL.Query
|
||||
console.log('model.label.toLowerCase()', model.label.toLowerCase())
|
||||
const variables: AssemblySymmetryGraphQL.Variables = { pdbId: model.label.toLowerCase() };
|
||||
try {
|
||||
console.log('foo', client)
|
||||
result = await client.request<AssemblySymmetryGraphQL.Query>(ctx || RuntimeContext.Synchronous, query, variables);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
84
src/mol-model-props/wwpdb/chem-comp-bond.ts
Normal file
84
src/mol-model-props/wwpdb/chem-comp-bond.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
|
||||
export namespace ChemCompBond {
|
||||
export type Property = Table<Schema['chem_comp_bond']>
|
||||
|
||||
export function getFromModel(model: Model): Property {
|
||||
if (model.sourceData.kind !== 'mmCIF') return Table.ofUndefinedColumns(Schema.chem_comp_bond, 0);
|
||||
const { chem_comp_bond } = model.sourceData.data
|
||||
return Table.ofColumns(Schema.chem_comp_bond, {
|
||||
...chem_comp_bond,
|
||||
molstar_protonation_variant: Column.Undefined(chem_comp_bond._rowCount, Column.Schema.Str())
|
||||
});
|
||||
}
|
||||
|
||||
export function get(model: Model): Property {
|
||||
return model._staticPropertyData.__ChemCompBond__ || getFromModel(model);
|
||||
}
|
||||
function set(model: Model, prop: Property) {
|
||||
(model._staticPropertyData.__ChemCompBond__ as Property) = prop;
|
||||
}
|
||||
|
||||
export const Schema = { chem_comp_bond: mmCIF_chemCompBond_schema };
|
||||
export type Schema = typeof Schema
|
||||
|
||||
export const Descriptor = CustomPropertyDescriptor({
|
||||
isStatic: true,
|
||||
name: 'chem_comp_bond',
|
||||
cifExport: {
|
||||
prefix: '',
|
||||
context(ctx): Property { return get(ctx.firstModel); },
|
||||
categories: [{
|
||||
name: 'chem_comp_bond',
|
||||
instance(ctx: Property) {
|
||||
return CifWriter.Category.ofTable(ctx);
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
function fromCifData(model: Model): Table<Schema['chem_comp_bond']> | undefined {
|
||||
if (model.sourceData.kind !== 'mmCIF') return void 0;
|
||||
const cat = model.sourceData.frame.categories.chem_comp_bond;
|
||||
if (!cat) return void 0;
|
||||
return toTable(Schema.chem_comp_bond, cat);
|
||||
}
|
||||
|
||||
export async function attachFromCifOrTable(model: Model, params: {
|
||||
// optional Table source
|
||||
wwPDB_apiSourceTable?: (model: Model) => Promise<Table<Schema['chem_comp_bond']>>
|
||||
}) {
|
||||
if (model.customProperties.has(Descriptor)) return true;
|
||||
|
||||
let chemCompBond: Table<Schema['chem_comp_bond']> | undefined = fromCifData(model);
|
||||
if (chemCompBond === void 0 && params.wwPDB_apiSourceTable) {
|
||||
const data = await params.wwPDB_apiSourceTable(model);
|
||||
if (!data) return false;
|
||||
chemCompBond = chemCompBondFromTable(model, data);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chemCompBond) return false;
|
||||
|
||||
model.customProperties.add(Descriptor);
|
||||
set(model, chemCompBond);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function chemCompBondFromTable(model: Model, table: Table<ChemCompBond.Schema['chem_comp_bond']>): Table<ChemCompBond.Schema['chem_comp_bond']> {
|
||||
return Table.pick(table, ChemCompBond.Schema.chem_comp_bond, (i: number) => {
|
||||
return model.properties.chemicalComponentMap.has(table.comp_id.value(i))
|
||||
})
|
||||
}
|
||||
@@ -10,12 +10,11 @@ import StructureSequence from './properties/sequence';
|
||||
import { AtomicHierarchy, AtomicConformation } from './properties/atomic';
|
||||
import { ModelSymmetry } from './properties/symmetry';
|
||||
import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
|
||||
import { Entities } from './properties/common';
|
||||
import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common';
|
||||
import { CustomProperties } from '../common/custom-property';
|
||||
import { SecondaryStructure } from './properties/seconday-structure';
|
||||
import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
|
||||
import { ModelFormat } from '../../../mol-model-formats/structure/format';
|
||||
import { ChemicalComponentMap } from './properties/chemical-component';
|
||||
|
||||
/**
|
||||
* Interface to the "source data" of the molecule.
|
||||
@@ -49,6 +48,8 @@ export interface Model extends Readonly<{
|
||||
parentId: ReadonlyMap<string, string>,
|
||||
details: ReadonlyMap<string, string>
|
||||
}>,
|
||||
/** map that holds details about unobserved or zero occurrence residues */
|
||||
readonly missingResidues: MissingResidues,
|
||||
/** maps residue name to `ChemicalComponent` data */
|
||||
readonly chemicalComponentMap: ChemicalComponentMap
|
||||
/** maps residue name to `SaccharideComponent` data */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -113,7 +113,8 @@ export interface AtomicData {
|
||||
export interface AtomicDerivedData {
|
||||
readonly residue: {
|
||||
readonly traceElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly directionElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly directionFromElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly directionToElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly moleculeType: ArrayLike<MoleculeType>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Table } from '../../../../mol-data/db';
|
||||
|
||||
export type ChemicalComponent = Table.Row<mmCIF_Schema['chem_comp']>
|
||||
export type ChemicalComponentMap = ReadonlyMap<string, ChemicalComponent>
|
||||
|
||||
// TODO add data for common chemical components
|
||||
@@ -1,13 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { mmCIF_Database as mmCIF } from '../../../../mol-io/reader/cif/schema/mmcif'
|
||||
import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'
|
||||
import { Table } from '../../../../mol-data/db';
|
||||
import { EntityIndex } from '../indexing';
|
||||
|
||||
export interface Entities {
|
||||
data: mmCIF['entity'],
|
||||
data: mmCIF_Database['entity'],
|
||||
getEntityIndex(id: string): EntityIndex
|
||||
}
|
||||
|
||||
export type ChemicalComponent = Table.Row<mmCIF_Schema['chem_comp']>
|
||||
export type ChemicalComponentMap = ReadonlyMap<string, ChemicalComponent>
|
||||
|
||||
export type MissingResidue = Table.Row<Pick<
|
||||
mmCIF_Schema['pdbx_unobs_or_zero_occ_residues'],
|
||||
'polymer_flag' | 'occupancy_flag'>
|
||||
>
|
||||
export interface MissingResidues {
|
||||
has(model_num: number, asym_id: string, seq_id: number): boolean
|
||||
get(model_num: number, asym_id: string, seq_id: number): MissingResidue | undefined
|
||||
readonly size: number
|
||||
}
|
||||
@@ -5,17 +5,18 @@
|
||||
*/
|
||||
|
||||
import { AtomicData } from '../atomic';
|
||||
import { ChemicalComponentMap } from '../chemical-component';
|
||||
import { AtomicIndex, AtomicDerivedData } from '../atomic/hierarchy';
|
||||
import { ElementIndex, ResidueIndex } from '../../indexing';
|
||||
import { MoleculeType, getMoleculeType, getComponentType } from '../../types';
|
||||
import { getAtomIdForAtomRole } from '../../../../../mol-model/structure/util';
|
||||
import { ChemicalComponentMap } from '../common';
|
||||
|
||||
export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemicalComponentMap: ChemicalComponentMap): AtomicDerivedData {
|
||||
const { label_comp_id, _rowCount: n } = data.residues
|
||||
|
||||
const traceElementIndex = new Int32Array(n)
|
||||
const directionElementIndex = new Int32Array(n)
|
||||
const directionFromElementIndex = new Int32Array(n)
|
||||
const directionToElementIndex = new Int32Array(n)
|
||||
const moleculeType = new Uint8Array(n)
|
||||
|
||||
const moleculeTypeMap = new Map<string, MoleculeType>()
|
||||
@@ -44,14 +45,18 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
|
||||
}
|
||||
traceElementIndex[i] = traceIndex
|
||||
|
||||
const directionAtomId = getAtomIdForAtomRole(molType, 'direction')
|
||||
directionElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionAtomId)
|
||||
const directionFromAtomId = getAtomIdForAtomRole(molType, 'directionFrom')
|
||||
directionFromElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionFromAtomId)
|
||||
|
||||
const directionToAtomId = getAtomIdForAtomRole(molType, 'directionTo')
|
||||
directionToElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionToAtomId)
|
||||
}
|
||||
|
||||
return {
|
||||
residue: {
|
||||
traceElementIndex: traceElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
directionElementIndex: directionElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
directionFromElementIndex: directionFromElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
directionToElementIndex: directionToElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
moleculeType: moleculeType as unknown as ArrayLike<MoleculeType>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
import { CoarseRanges, CoarseElementData } from '../coarse/hierarchy';
|
||||
import { Segmentation, Interval } from '../../../../../mol-data/int';
|
||||
import SortedRanges from '../../../../../mol-data/int/sorted-ranges';
|
||||
import { ChemicalComponent } from '../chemical-component';
|
||||
import { ElementIndex } from '../../indexing';
|
||||
import { ChemicalComponent } from '../common';
|
||||
|
||||
// TODO assumes all coarse elements are part of a polymer
|
||||
// TODO add gaps at the ends of the chains by comparing to the polymer sequence data
|
||||
|
||||
@@ -59,33 +59,39 @@ export const enum MoleculeType {
|
||||
saccharide
|
||||
}
|
||||
|
||||
export type AtomRole = 'trace' | 'direction' | 'backboneStart' | 'backboneEnd' | 'coarseBackbone'
|
||||
export type AtomRole = 'trace' | 'directionFrom' | 'directionTo' | 'backboneStart' | 'backboneEnd' | 'coarseBackbone'
|
||||
|
||||
export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: Set<string> } } = {
|
||||
[MoleculeType.protein]: {
|
||||
trace: new Set(['CA']),
|
||||
direction: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']),
|
||||
directionFrom: new Set(['C']),
|
||||
directionTo: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']),
|
||||
backboneStart: new Set(['N']),
|
||||
backboneEnd: new Set(['C']),
|
||||
coarseBackbone: new Set(['CA', 'BB'])
|
||||
// CA1 is used e.g. in GFP chromophores
|
||||
// BB is often used for coarse grained models
|
||||
coarseBackbone: new Set(['CA', 'BB', 'CA1'])
|
||||
},
|
||||
[MoleculeType.RNA]: {
|
||||
trace: new Set(['C4\'', 'C4*']),
|
||||
direction: new Set(['C3\'', 'C3*']),
|
||||
directionFrom: new Set(['C4\'', 'C4*']),
|
||||
directionTo: new Set(['C3\'', 'C3*']),
|
||||
backboneStart: new Set(['P']),
|
||||
backboneEnd: new Set(['O3\'', 'O3*']),
|
||||
coarseBackbone: new Set(['P'])
|
||||
},
|
||||
[MoleculeType.DNA]: {
|
||||
trace: new Set(['C3\'', 'C3*']),
|
||||
direction: new Set(['C1\'', 'C1*']),
|
||||
directionFrom: new Set(['C3\'', 'C3*']),
|
||||
directionTo: new Set(['C1\'', 'C1*']),
|
||||
backboneStart: new Set(['P']),
|
||||
backboneEnd: new Set(['O3\'', 'O3*']),
|
||||
coarseBackbone: new Set(['P'])
|
||||
},
|
||||
[MoleculeType.PNA]: {
|
||||
trace: new Set(['N4\'', 'N4*']),
|
||||
direction: new Set(['C7\'', 'C7*']),
|
||||
directionFrom: new Set(['N4\'', 'N4*']),
|
||||
directionTo: new Set(['C7\'', 'C7*']),
|
||||
backboneStart: new Set(['N1\'', 'N1*']),
|
||||
backboneEnd: new Set(['C\'', 'C*']),
|
||||
coarseBackbone: new Set(['P'])
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StructureQuery } from '../query'
|
||||
@@ -20,11 +21,13 @@ export const none: StructureQuery = ctx => StructureSelection.Sequence(ctx.input
|
||||
export const all: StructureQuery = ctx => StructureSelection.Singletons(ctx.inputStructure, ctx.inputStructure);
|
||||
|
||||
export interface AtomsQueryParams {
|
||||
/** Query to be executed for each unit once */
|
||||
unitTest: QueryPredicate,
|
||||
/** Query to be executed for each entity once */
|
||||
entityTest: QueryPredicate,
|
||||
/** Query to be executed for each chain once */
|
||||
chainTest: QueryPredicate,
|
||||
/** Query to be executed for each residue once */
|
||||
/** Query to be executed for each residue (or coarse element) once */
|
||||
residueTest: QueryPredicate,
|
||||
/** Query to be executed for each atom */
|
||||
atomTest: QueryPredicate,
|
||||
@@ -38,10 +41,11 @@ function _true(ctx: QueryContextView) { return true; }
|
||||
function _zero(ctx: QueryContextView) { return 0; }
|
||||
|
||||
export function atoms(params?: Partial<AtomsQueryParams>): StructureQuery {
|
||||
if (!params || (!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.groupBy)) return all;
|
||||
if (!!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.groupBy) return atomGroupsLinear(params.atomTest);
|
||||
if (!params || (!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.unitTest && !params.groupBy)) return all;
|
||||
if (!!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.unitTest && !params.groupBy) return atomGroupsLinear(params.atomTest);
|
||||
|
||||
const normalized: AtomsQueryParams = {
|
||||
unitTest: params.unitTest || _true,
|
||||
entityTest: params.entityTest || _true,
|
||||
chainTest: params.chainTest || _true,
|
||||
residueTest: params.residueTest || _true,
|
||||
@@ -78,7 +82,7 @@ function atomGroupsLinear(atomTest: QueryPredicate): StructureQuery {
|
||||
};
|
||||
}
|
||||
|
||||
function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: AtomsQueryParams): StructureQuery {
|
||||
function atomGroupsSegmented({ unitTest, entityTest, chainTest, residueTest, atomTest }: AtomsQueryParams): StructureQuery {
|
||||
return ctx => {
|
||||
const { inputStructure } = ctx;
|
||||
const { units } = inputStructure;
|
||||
@@ -86,31 +90,53 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
|
||||
const builder = inputStructure.subsetBuilder(true);
|
||||
|
||||
for (const unit of units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
const elements = unit.elements;
|
||||
if (!unitTest(ctx)) continue;
|
||||
|
||||
const { elements, model } = unit;
|
||||
builder.beginUnit(unit.id);
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
// test entity and chain
|
||||
if (!entityTest(ctx) || !chainTest(ctx)) continue;
|
||||
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
if (unit.kind === Unit.Kind.Atomic) {
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
|
||||
|
||||
// test residue
|
||||
if (!residueTest(ctx)) continue;
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
// test entity and chain
|
||||
if (!entityTest(ctx) || !chainTest(ctx)) continue;
|
||||
|
||||
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
|
||||
// test residue
|
||||
if (!residueTest(ctx)) continue;
|
||||
|
||||
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
|
||||
l.element = elements[j];
|
||||
// test atom
|
||||
if (atomTest(ctx)) {
|
||||
builder.addElement(l.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const { chainElementSegments } = Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians
|
||||
const chainsIt = Segmentation.transientSegments(chainElementSegments, elements);
|
||||
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
// test entity and chain
|
||||
if (!entityTest(ctx) || !chainTest(ctx)) continue;
|
||||
|
||||
for (let j = chainSegment.start, _j = chainSegment.end; j < _j; j++) {
|
||||
l.element = elements[j];
|
||||
if (atomTest(ctx)) {
|
||||
// test residue/coarse element
|
||||
if (residueTest(ctx)) {
|
||||
builder.addElement(l.element);
|
||||
}
|
||||
}
|
||||
@@ -125,7 +151,7 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
|
||||
};
|
||||
}
|
||||
|
||||
function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomsQueryParams): StructureQuery {
|
||||
function atomGroupsGrouped({ unitTest, entityTest, chainTest, residueTest, atomTest, groupBy }: AtomsQueryParams): StructureQuery {
|
||||
return ctx => {
|
||||
const { inputStructure } = ctx;
|
||||
const { units } = inputStructure;
|
||||
@@ -133,30 +159,53 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group
|
||||
const builder = new LinearGroupingBuilder(inputStructure);
|
||||
|
||||
for (const unit of units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
const elements = unit.elements;
|
||||
if (!unitTest(ctx)) continue;
|
||||
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
// test entity and chain
|
||||
if (!entityTest(ctx) || !chainTest(ctx)) continue;
|
||||
const { elements, model } = unit;
|
||||
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
if (unit.kind === Unit.Kind.Atomic) {
|
||||
const chainsIt = Segmentation.transientSegments(model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(model.atomicHierarchy.residueAtomSegments, elements);
|
||||
|
||||
// test residue
|
||||
if (!residueTest(ctx)) continue;
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
// test entity and chain
|
||||
if (!entityTest(ctx) || !chainTest(ctx)) continue;
|
||||
|
||||
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
|
||||
// test residue
|
||||
if (!residueTest(ctx)) continue;
|
||||
|
||||
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
|
||||
l.element = elements[j];
|
||||
// test atom
|
||||
if (atomTest(ctx)) {
|
||||
builder.add(groupBy(ctx), unit.id, l.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const { chainElementSegments } = Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians
|
||||
const chainsIt = Segmentation.transientSegments(chainElementSegments, elements);
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
// test entity and chain
|
||||
if (!entityTest(ctx) || !chainTest(ctx)) continue;
|
||||
|
||||
for (let j = chainSegment.start, _j = chainSegment.end; j < _j; j++) {
|
||||
l.element = elements[j];
|
||||
if (atomTest(ctx)) builder.add(groupBy(ctx), unit.id, l.element);
|
||||
// test residue/coarse element
|
||||
if (residueTest(ctx)) {
|
||||
builder.add(groupBy(ctx), unit.id, l.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { UniqueArray } from '../../../mol-data/generic';
|
||||
@@ -30,6 +31,12 @@ namespace StructureElement {
|
||||
return { kind: 'element-location', unit: unit!, element: element || (0 as ElementIndex) };
|
||||
}
|
||||
|
||||
export function set(a: StructureElement, unit?: Unit, element?: ElementIndex): StructureElement {
|
||||
if (unit) a.unit = unit
|
||||
if (element !== undefined) a.element = element
|
||||
return a;
|
||||
}
|
||||
|
||||
// TODO: when nominal types are available, make this indexed by UnitIndex
|
||||
export type Set = SortedArray<ElementIndex>
|
||||
|
||||
@@ -150,11 +157,16 @@ namespace StructureElement {
|
||||
for (const e of ys.elements) {
|
||||
if (map.has(e.unit.id)) {
|
||||
elements[elements.length] = { unit: e.unit, indices: OrderedSet.union(map.get(e.unit.id)!, e.indices) };
|
||||
map.delete(e.unit.id)
|
||||
} else {
|
||||
elements[elements.length] = e;
|
||||
}
|
||||
}
|
||||
|
||||
map.forEach((indices, id) => {
|
||||
elements[elements.length] = { unit: xs.structure.unitMap.get(id)!, indices };
|
||||
});
|
||||
|
||||
return Loci(xs.structure, elements);
|
||||
}
|
||||
|
||||
@@ -195,24 +207,65 @@ namespace StructureElement {
|
||||
const elements: Loci['elements'][0][] = [];
|
||||
|
||||
for (const lociElement of loci.elements) {
|
||||
if (lociElement.unit.kind !== Unit.Kind.Atomic) elements[elements.length] = lociElement;
|
||||
if (lociElement.unit.kind === Unit.Kind.Atomic) {
|
||||
const unitElements = lociElement.unit.elements;
|
||||
const h = lociElement.unit.model.atomicHierarchy;
|
||||
|
||||
const unitElements = lociElement.unit.elements;
|
||||
const h = lociElement.unit.model.atomicHierarchy;
|
||||
const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments;
|
||||
|
||||
const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments;
|
||||
const newIndices: UnitIndex[] = [];
|
||||
const indices = lociElement.indices, len = OrderedSet.size(indices);
|
||||
let i = 0;
|
||||
while (i < len) {
|
||||
const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]];
|
||||
i++;
|
||||
while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) {
|
||||
i++;
|
||||
}
|
||||
|
||||
for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) {
|
||||
const idx = OrderedSet.indexOf(unitElements, j);
|
||||
if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex;
|
||||
}
|
||||
}
|
||||
|
||||
elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) };
|
||||
} else {
|
||||
// coarse elements are already by-residue
|
||||
elements[elements.length] = lociElement;
|
||||
}
|
||||
}
|
||||
|
||||
return Loci(loci.structure, elements);
|
||||
}
|
||||
|
||||
function getChainSegments(unit: Unit) {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic: return unit.model.atomicHierarchy.chainAtomSegments
|
||||
case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.chainElementSegments
|
||||
case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.chainElementSegments
|
||||
}
|
||||
}
|
||||
|
||||
export function extendToWholeChains(loci: Loci): Loci {
|
||||
const elements: Loci['elements'][0][] = [];
|
||||
|
||||
for (const lociElement of loci.elements) {
|
||||
const newIndices: UnitIndex[] = [];
|
||||
const unitElements = lociElement.unit.elements;
|
||||
|
||||
const { index: chainIndex, offsets: chainOffsets } = getChainSegments(lociElement.unit)
|
||||
|
||||
const indices = lociElement.indices, len = OrderedSet.size(indices);
|
||||
let i = 0;
|
||||
while (i < len) {
|
||||
const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]];
|
||||
const cI = chainIndex[unitElements[OrderedSet.getAt(indices, i)]];
|
||||
i++;
|
||||
while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) {
|
||||
while (i < len && chainIndex[unitElements[OrderedSet.getAt(indices, i)]] === cI) {
|
||||
i++;
|
||||
}
|
||||
|
||||
for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) {
|
||||
for (let j = chainOffsets[cI], _j = chainOffsets[cI + 1]; j < _j; j++) {
|
||||
const idx = OrderedSet.indexOf(unitElements, j);
|
||||
if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex;
|
||||
}
|
||||
|
||||
@@ -111,8 +111,15 @@ const entity = {
|
||||
}
|
||||
|
||||
const unit = {
|
||||
id: StructureElement.property(l => l.unit.id),
|
||||
operator_name: StructureElement.property(l => l.unit.conformation.operator.name),
|
||||
model_num: StructureElement.property(l => l.unit.model.modelNum)
|
||||
hkl: StructureElement.property(l => l.unit.conformation.operator.hkl),
|
||||
spgrOp: StructureElement.property(l => l.unit.conformation.operator.spgrOp),
|
||||
|
||||
model_num: StructureElement.property(l => l.unit.model.modelNum),
|
||||
pdbx_struct_assembly_id: StructureElement.property(l => l.unit.conformation.operator.assembly.id),
|
||||
pdbx_struct_oper_list_ids: StructureElement.property(l => l.unit.conformation.operator.assembly.operList),
|
||||
struct_ncs_oper_id: StructureElement.property(l => l.unit.conformation.operator.ncsId),
|
||||
}
|
||||
|
||||
const StructureProperties = {
|
||||
|
||||
@@ -141,6 +141,7 @@ class Structure {
|
||||
return new Structure.ElementLocationIterator(this);
|
||||
}
|
||||
|
||||
/** the root/top-most parent or `undefined` in case this is the root */
|
||||
get parent() {
|
||||
return this._props.parent;
|
||||
}
|
||||
@@ -240,7 +241,7 @@ class Structure {
|
||||
constructor(units: ArrayLike<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator) {
|
||||
this.unitMap = this.initUnits(units);
|
||||
this.units = units as ReadonlyArray<Unit>;
|
||||
if (parent) this._props.parent = parent;
|
||||
if (parent) this._props.parent = parent.parent || parent;
|
||||
if (coordinateSystem) this._props.coordinateSystem = coordinateSystem;
|
||||
else if (parent) this._props.coordinateSystem = parent.coordinateSystem;
|
||||
}
|
||||
@@ -507,6 +508,16 @@ namespace Structure {
|
||||
)
|
||||
}
|
||||
|
||||
/** Check if the structures or their parents are equivalent */
|
||||
export function areParentsEquivalent(a: Structure, b: Structure) {
|
||||
return areEquivalent(a.parent || a, b.parent || b)
|
||||
}
|
||||
|
||||
/** Check if the structures or their parents are equal */
|
||||
export function areParentsEqual(a: Structure, b: Structure) {
|
||||
return (a.parent || a) === (b.parent || b)
|
||||
}
|
||||
|
||||
export class ElementLocationIterator implements Iterator<StructureElement> {
|
||||
private current = StructureElement.create();
|
||||
private unitIndex = 0;
|
||||
|
||||
@@ -117,7 +117,7 @@ function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
|
||||
for (let u = 0; u < ncsCount; ++u) {
|
||||
const ncsOp = ncsOperators![u]
|
||||
const matrix = Mat4.mul(Mat4.zero(), symOp.matrix, ncsOp.matrix)
|
||||
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl);
|
||||
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
|
||||
operators[operators.length] = operator;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Unit, StructureElement } from '../../structure'
|
||||
import Structure from '../structure';
|
||||
import { LinkType } from '../../model/types';
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
|
||||
export * from './links/data'
|
||||
export * from './links/intra-compute'
|
||||
@@ -61,6 +62,33 @@ namespace Link {
|
||||
return true
|
||||
}
|
||||
|
||||
export function toStructureElementLoci(loci: Loci): StructureElement.Loci {
|
||||
const elements: StructureElement.Loci['elements'][0][] = []
|
||||
const map = new Map<number, number[]>()
|
||||
|
||||
for (const lociLink of loci.links) {
|
||||
const { aIndex, aUnit, bIndex, bUnit } = lociLink
|
||||
if (aUnit === bUnit) {
|
||||
if (map.has(aUnit.id)) map.get(aUnit.id)!.push(aIndex, bIndex)
|
||||
else map.set(aUnit.id, [aIndex, bIndex])
|
||||
} else {
|
||||
if (map.has(aUnit.id)) map.get(aUnit.id)!.push(aIndex)
|
||||
else map.set(aUnit.id, [aIndex])
|
||||
if (map.has(bUnit.id)) map.get(bUnit.id)!.push(bIndex)
|
||||
else map.set(bUnit.id, [bIndex])
|
||||
}
|
||||
}
|
||||
|
||||
map.forEach((indices: number[], id: number) => {
|
||||
elements.push({
|
||||
unit: loci.structure.unitMap.get(id)!,
|
||||
indices: SortedArray.deduplicate(SortedArray.ofUnsortedArray(indices))
|
||||
})
|
||||
})
|
||||
|
||||
return StructureElement.Loci(loci.structure, elements);
|
||||
}
|
||||
|
||||
export function getType(structure: Structure, link: Location<Unit.Atomic>): LinkType {
|
||||
if (link.aUnit === link.bUnit) {
|
||||
const links = link.aUnit.links;
|
||||
|
||||
@@ -14,7 +14,7 @@ export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extr
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number }> {
|
||||
register(): void {
|
||||
this.subscribeObservable(this.ctx.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || buttons !== ButtonsType.Flag.Primary || !ModifiersKeys.areEqual(modifiers, ModifiersKeys.None)) return;
|
||||
|
||||
const sphere = Loci.getBoundingSphere(current.loci);
|
||||
@@ -25,7 +25,7 @@ export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extr
|
||||
},
|
||||
params: () => ({
|
||||
minRadius: ParamDefinition.Numeric(8, { min: 1, max: 50, step: 1 }),
|
||||
extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the boundning sphere radius of the Loci.' })
|
||||
extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci.' })
|
||||
}),
|
||||
display: { name: 'Focus Loci on Select' }
|
||||
});
|
||||
@@ -5,45 +5,27 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { MarkerAction } from '../../../mol-geo/geometry/marker-data';
|
||||
import { EmptyLoci } from '../../../mol-model/loci';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Representation } from '../../../mol-repr/representation';
|
||||
import { PluginStateObject as SO } from '../../state/objects';
|
||||
import { labelFirst } from '../../../mol-theme/label';
|
||||
import { ButtonsType } from '../../../mol-util/input/input-observer';
|
||||
import { PluginBehavior } from '../behavior';
|
||||
import { Interactivity } from '../../util/interactivity';
|
||||
import { StateTreeSpine } from '../../../mol-state/tree/spine';
|
||||
|
||||
export const HighlightLoci = PluginBehavior.create({
|
||||
name: 'representation-highlight-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler {
|
||||
register(): void {
|
||||
let prev: Representation.Loci = { loci: EmptyLoci, repr: void 0 };
|
||||
const sel = this.ctx.helpers.structureSelection;
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.canvas3d.highlight, ({ current, modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
|
||||
if (StructureElement.isLoci(current.loci)) {
|
||||
let loci: StructureElement.Loci = current.loci;
|
||||
if (modifiers && modifiers.shift) {
|
||||
loci = sel.tryGetRange(loci) || loci;
|
||||
}
|
||||
|
||||
this.ctx.canvas3d.mark(prev, MarkerAction.RemoveHighlight);
|
||||
const toHighlight = { loci, repr: current.repr };
|
||||
this.ctx.canvas3d.mark(toHighlight, MarkerAction.Highlight);
|
||||
prev = toHighlight;
|
||||
} else {
|
||||
if (!Representation.Loci.areEqual(prev, current)) {
|
||||
this.ctx.canvas3d.mark(prev, MarkerAction.RemoveHighlight);
|
||||
this.ctx.canvas3d.mark(current, MarkerAction.Highlight);
|
||||
prev = current;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
private lociMarkProvider = (interactionLoci: Interactivity.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action)
|
||||
}
|
||||
register() {
|
||||
this.ctx.interactivity.lociHighlights.addProvider(this.lociMarkProvider)
|
||||
}
|
||||
unregister() {
|
||||
this.ctx.interactivity.lociHighlights.removeProvider(this.lociMarkProvider)
|
||||
}
|
||||
},
|
||||
display: { name: 'Highlight Loci on Canvas' }
|
||||
@@ -53,52 +35,33 @@ export const SelectLoci = PluginBehavior.create({
|
||||
name: 'representation-select-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler {
|
||||
register(): void {
|
||||
const sel = this.ctx.helpers.structureSelection;
|
||||
private spine: StateTreeSpine.Impl
|
||||
private lociMarkProvider = (interactionLoci: Interactivity.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action)
|
||||
}
|
||||
register() {
|
||||
this.ctx.interactivity.lociSelections.addProvider(this.lociMarkProvider)
|
||||
|
||||
const toggleSel = (current: Representation.Loci<StructureElement.Loci>) => {
|
||||
if (sel.has(current.loci)) {
|
||||
sel.remove(current.loci);
|
||||
this.ctx.canvas3d.mark(current, MarkerAction.Deselect);
|
||||
} else {
|
||||
sel.add(current.loci);
|
||||
this.ctx.canvas3d.mark(current, MarkerAction.Select);
|
||||
}
|
||||
}
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
|
||||
if (current.loci.kind === 'empty-loci') {
|
||||
if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
|
||||
// clear the selection on Ctrl + Right-Click on empty
|
||||
const sels = sel.clear();
|
||||
for (const s of sels) this.ctx.canvas3d.mark({ loci: s }, MarkerAction.Deselect);
|
||||
this.subscribeObservable(this.ctx.events.state.object.created, ({ ref }) => {
|
||||
const cell = this.ctx.state.dataState.cells.get(ref)
|
||||
if (cell && SO.isRepresentation3D(cell.obj)) {
|
||||
this.spine.current = cell
|
||||
const so = this.spine.getRootOfType(SO.Molecule.Structure)
|
||||
if (so) {
|
||||
const loci = this.ctx.helpers.structureSelection.get(so.data)
|
||||
this.lociMarkProvider({ loci }, MarkerAction.Select)
|
||||
}
|
||||
} else if (StructureElement.isLoci(current.loci)) {
|
||||
if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
|
||||
// select only the current element on Ctrl + Right-Click
|
||||
const old = sel.get(current.loci.structure);
|
||||
this.ctx.canvas3d.mark({ loci: old }, MarkerAction.Deselect);
|
||||
sel.set(current.loci);
|
||||
this.ctx.canvas3d.mark(current, MarkerAction.Select);
|
||||
} else if (modifiers.control && buttons === ButtonsType.Flag.Primary) {
|
||||
// toggle current element on Ctrl + Left-Click
|
||||
toggleSel(current as Representation.Loci<StructureElement.Loci>);
|
||||
} else if (modifiers.shift && buttons === ButtonsType.Flag.Primary) {
|
||||
// try to extend sequence on Shift + Left-Click
|
||||
let loci: StructureElement.Loci = current.loci;
|
||||
if (modifiers && modifiers.shift) {
|
||||
loci = sel.tryGetRange(loci) || loci;
|
||||
}
|
||||
toggleSel({ loci, repr: current.repr });
|
||||
}
|
||||
} else {
|
||||
if (!ButtonsType.has(buttons, ButtonsType.Flag.Secondary)) return;
|
||||
this.ctx.canvas3d.mark(current, MarkerAction.Toggle);
|
||||
}
|
||||
});
|
||||
}
|
||||
unregister() {
|
||||
this.ctx.interactivity.lociSelections.removeProvider(this.lociMarkProvider)
|
||||
}
|
||||
constructor(ctx: PluginContext, params: {}) {
|
||||
super(ctx, params)
|
||||
this.spine = new StateTreeSpine.Impl(ctx.state.dataState.cells)
|
||||
}
|
||||
},
|
||||
display: { name: 'Select Loci on Canvas' }
|
||||
});
|
||||
@@ -108,7 +71,7 @@ export const DefaultLociLabelProvider = PluginBehavior.create({
|
||||
category: 'interaction',
|
||||
ctor: class implements PluginBehavior<undefined> {
|
||||
private f = labelFirst;
|
||||
register(): void { this.ctx.lociLabels.addProvider(this.f); }
|
||||
register() { this.ctx.lociLabels.addProvider(this.f); }
|
||||
unregister() { this.ctx.lociLabels.removeProvider(this.f); }
|
||||
constructor(protected ctx: PluginContext) { }
|
||||
},
|
||||
|
||||
@@ -121,7 +121,7 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.plugin.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
|
||||
this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
|
||||
if (buttons !== ButtonsType.Flag.Secondary) return;
|
||||
|
||||
if (current.loci.kind === 'empty-loci') {
|
||||
|
||||
@@ -43,13 +43,13 @@ export namespace VolumeStreaming {
|
||||
valuesInfo: [{ mean: 0, min: -1, max: 1, sigma: 0.1 }, { mean: 0, min: -1, max: 1, sigma: 0.1 }]
|
||||
};
|
||||
|
||||
export function createParams(data?: VolumeServerInfo.Data) {
|
||||
export function createParams(data?: VolumeServerInfo.Data, defaultView?: ViewTypes) {
|
||||
// fake the info
|
||||
const info = data || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
|
||||
const box = (data && data.structure.boundary.box) || Box3D.empty();
|
||||
|
||||
return {
|
||||
view: PD.MappedStatic(info.kind === 'em' ? 'cell' : 'selection-box', {
|
||||
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
|
||||
'box': PD.Group({
|
||||
bottomLeft: PD.Vec3(box.min),
|
||||
topRight: PD.Vec3(box.max),
|
||||
@@ -76,6 +76,8 @@ export namespace VolumeStreaming {
|
||||
};
|
||||
}
|
||||
|
||||
export type ViewTypes = 'box' | 'selection-box' | 'cell'
|
||||
|
||||
export type ParamDefinition = typeof createParams extends (...args: any[]) => (infer T) ? T : never
|
||||
export type Params = ParamDefinition extends PD.Params ? PD.Values<ParamDefinition> : {}
|
||||
|
||||
@@ -193,7 +195,7 @@ export namespace VolumeStreaming {
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.plugin.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
|
||||
this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
|
||||
if (buttons !== ButtonsType.Flag.Secondary || this.params.view.name !== 'selection-box') return;
|
||||
|
||||
if (current.loci.kind === 'empty-loci') {
|
||||
|
||||
@@ -26,10 +26,13 @@ export const InitVolumeStreaming = StateAction.build({
|
||||
display: { name: 'Volume Streaming' },
|
||||
from: SO.Molecule.Structure,
|
||||
params(a) {
|
||||
const method = getStreamingMethod(a && a.data);
|
||||
return {
|
||||
method: PD.Select<VolumeServerInfo.Kind>(getStreamingMethod(a && a.data), [['em', 'EM'], ['x-ray', 'X-Ray']]),
|
||||
method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
|
||||
id: PD.Text((a && a.data.models.length > 0 && a.data.models[0].label) || ''),
|
||||
serverUrl: PD.Text('https://ds.litemol.org')
|
||||
serverUrl: PD.Text('https://ds.litemol.org'),
|
||||
defaultView: PD.Text<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box'),
|
||||
behaviorRef: PD.Text('', { isHidden: true })
|
||||
};
|
||||
},
|
||||
isApplicable: (a) => a.data.models.length === 1
|
||||
@@ -56,7 +59,8 @@ export const InitVolumeStreaming = StateAction.build({
|
||||
const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
|
||||
|
||||
const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior,
|
||||
PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data)));
|
||||
PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data, params.defaultView)),
|
||||
{ ref: params.behaviorRef ? params.behaviorRef : void 0 });
|
||||
|
||||
if (params.method === 'em') {
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true } });
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
@@ -9,6 +10,7 @@ import { PluginCommands } from '../../../mol-plugin/command';
|
||||
|
||||
export function registerDefault(ctx: PluginContext) {
|
||||
Canvas3DSetSettings(ctx);
|
||||
InteractivitySetProps(ctx);
|
||||
}
|
||||
|
||||
export function Canvas3DSetSettings(ctx: PluginContext) {
|
||||
@@ -17,3 +19,10 @@ export function Canvas3DSetSettings(ctx: PluginContext) {
|
||||
ctx.events.canvas3d.settingsUpdated.next();
|
||||
})
|
||||
}
|
||||
|
||||
export function InteractivitySetProps(ctx: PluginContext) {
|
||||
PluginCommands.Interactivity.SetProps.subscribe(ctx, e => {
|
||||
ctx.interactivity.setProps(e.props);
|
||||
ctx.events.interactivity.propsUpdated.next();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Canvas3DProps } from '../mol-canvas3d/canvas3d';
|
||||
import { PluginLayoutStateProps } from './layout';
|
||||
import { StructureElement } from '../mol-model/structure';
|
||||
import { PluginState } from './state';
|
||||
import { Interactivity } from './util/interactivity';
|
||||
|
||||
export * from './command/base';
|
||||
|
||||
@@ -43,6 +44,7 @@ export const PluginCommands = {
|
||||
}
|
||||
},
|
||||
Interactivity: {
|
||||
SetProps: PluginCommand<{ props: Partial<Interactivity.Props> }>(),
|
||||
Structure: {
|
||||
Highlight: PluginCommand<{ loci: StructureElement.Loci, isOff?: boolean }>(),
|
||||
Select: PluginCommand<{ loci: StructureElement.Loci, isOff?: boolean }>()
|
||||
|
||||
@@ -8,7 +8,7 @@ import { shallowMergeArray } from '../mol-util/object';
|
||||
import { RxEventHelper } from '../mol-util/rx-event-helper';
|
||||
|
||||
export class PluginComponent<State> {
|
||||
private _ev: RxEventHelper;
|
||||
private _ev: RxEventHelper | undefined;
|
||||
|
||||
protected get ev() {
|
||||
return this._ev || (this._ev = RxEventHelper.create());
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { List } from 'immutable';
|
||||
@@ -32,10 +33,19 @@ import { TaskManager } from './util/task-manager';
|
||||
import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
|
||||
import { StructureElementSelectionManager } from './util/structure-element-selection';
|
||||
import { SubstructureParentHelper } from './util/substructure-parent-helper';
|
||||
import { Representation } from '../mol-repr/representation';
|
||||
import { ModifiersKeys } from '../mol-util/input/input-observer';
|
||||
import { isProductionMode, isDebugMode } from '../mol-util/debug';
|
||||
import { Model, Structure } from '../mol-model/structure';
|
||||
import { Interactivity } from './util/interactivity';
|
||||
|
||||
interface Log {
|
||||
entries: List<LogEntry>
|
||||
readonly entry: (e: LogEntry) => void
|
||||
readonly error: (msg: string) => void
|
||||
readonly message: (msg: string) => void
|
||||
readonly info: (msg: string) => void
|
||||
readonly warn: (msg: string) => void
|
||||
}
|
||||
|
||||
export class PluginContext {
|
||||
private disposed = false;
|
||||
@@ -64,41 +74,45 @@ export class PluginContext {
|
||||
task: this.tasks.events,
|
||||
canvas3d: {
|
||||
settingsUpdated: this.ev()
|
||||
},
|
||||
interactivity: {
|
||||
propsUpdated: this.ev()
|
||||
}
|
||||
};
|
||||
} as const
|
||||
|
||||
readonly behaviors = {
|
||||
state: {
|
||||
isAnimating: this.ev.behavior<boolean>(false),
|
||||
isUpdating: this.ev.behavior<boolean>(false)
|
||||
},
|
||||
canvas3d: {
|
||||
highlight: this.ev.behavior<Canvas3D.HighlightEvent>({ current: Representation.Loci.Empty, prev: Representation.Loci.Empty }),
|
||||
click: this.ev.behavior<Canvas3D.ClickEvent>({ current: Representation.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0 })
|
||||
interaction: {
|
||||
highlight: this.ev.behavior<Interactivity.HighlightEvent>({ current: Interactivity.Loci.Empty }),
|
||||
click: this.ev.behavior<Interactivity.ClickEvent>({ current: Interactivity.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0 })
|
||||
},
|
||||
labels: {
|
||||
highlight: this.ev.behavior<{ entries: ReadonlyArray<LociLabelEntry> }>({ entries: [] })
|
||||
}
|
||||
};
|
||||
} as const
|
||||
|
||||
readonly canvas3d: Canvas3D;
|
||||
readonly layout: PluginLayout = new PluginLayout(this);
|
||||
readonly interactivity: Interactivity;
|
||||
|
||||
readonly lociLabels: LociLabelManager;
|
||||
|
||||
readonly structureRepresentation = {
|
||||
registry: new StructureRepresentationRegistry(),
|
||||
themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
|
||||
}
|
||||
} as const
|
||||
|
||||
readonly volumeRepresentation = {
|
||||
registry: new VolumeRepresentationRegistry(),
|
||||
themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
|
||||
}
|
||||
} as const
|
||||
|
||||
readonly dataFormat = {
|
||||
registry: new DataFormatRegistry()
|
||||
}
|
||||
} as const
|
||||
|
||||
readonly customModelProperties = new CustomPropertyRegistry<Model>();
|
||||
readonly customStructureProperties = new CustomPropertyRegistry<Structure>();
|
||||
@@ -107,7 +121,7 @@ export class PluginContext {
|
||||
readonly helpers = {
|
||||
structureSelection: new StructureElementSelectionManager(this),
|
||||
substructureParent: new SubstructureParentHelper(this)
|
||||
};
|
||||
} as const;
|
||||
|
||||
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
|
||||
try {
|
||||
@@ -125,7 +139,7 @@ export class PluginContext {
|
||||
}
|
||||
}
|
||||
|
||||
readonly log = {
|
||||
readonly log: Log = {
|
||||
entries: List<LogEntry>(),
|
||||
entry: (e: LogEntry) => this.events.log.next(e),
|
||||
error: (msg: string) => this.events.log.next(LogEntry.error(msg)),
|
||||
@@ -226,6 +240,7 @@ export class PluginContext {
|
||||
this.initAnimations();
|
||||
this.initCustomParamEditors();
|
||||
|
||||
this.interactivity = new Interactivity(this);
|
||||
this.lociLabels = new LociLabelManager(this);
|
||||
|
||||
this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`);
|
||||
|
||||
@@ -54,7 +54,7 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
|
||||
this.events.updated.next();
|
||||
}
|
||||
|
||||
private root: HTMLElement;
|
||||
private root: HTMLElement | undefined;
|
||||
private rootState: RootState | undefined = void 0;
|
||||
private expandedViewport: HTMLMetaElement;
|
||||
|
||||
@@ -78,7 +78,7 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
|
||||
let body = document.getElementsByTagName('body')[0];
|
||||
let head = document.getElementsByTagName('head')[0];
|
||||
|
||||
if (!body || !head) return;
|
||||
if (!body || !head || !this.root) return;
|
||||
|
||||
if (this.state.isExpanded) {
|
||||
let children = head.children;
|
||||
|
||||
@@ -8,42 +8,42 @@
|
||||
select, button, input[type=text] {
|
||||
@extend .msp-form-control;
|
||||
}
|
||||
|
||||
|
||||
button {
|
||||
@extend .msp-btn;
|
||||
@extend .msp-btn-block;
|
||||
}
|
||||
}
|
||||
|
||||
.msp-control-row {
|
||||
.msp-control-row {
|
||||
position: relative;
|
||||
height: $row-height;
|
||||
background: $default-background;
|
||||
margin-top: 1px;
|
||||
|
||||
|
||||
> span {
|
||||
line-height: $row-height;
|
||||
display: block;
|
||||
width: $control-label-width + $control-spacing;
|
||||
text-align: right;
|
||||
padding: 0 $control-spacing;
|
||||
padding: 0 $control-spacing;
|
||||
color: color-lower-contrast($font-color, 15%);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@include non-selectable;
|
||||
|
||||
@include non-selectable;
|
||||
}
|
||||
|
||||
|
||||
select, button, input[type=text] {
|
||||
@extend .msp-form-control;
|
||||
}
|
||||
|
||||
|
||||
button {
|
||||
@extend .msp-btn;
|
||||
@extend .msp-btn-block;
|
||||
}
|
||||
|
||||
|
||||
> div:nth-child(2) {
|
||||
background: $msp-form-control-background;
|
||||
position: absolute;
|
||||
@@ -58,21 +58,21 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.msp-toggle-button {
|
||||
.msp-toggle-button {
|
||||
.msp-icon {
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
|
||||
> div > button:hover {
|
||||
border-color: color-increase-contrast($msp-form-control-background, 5%) !important;
|
||||
border: none;
|
||||
outline-offset: -1px !important;
|
||||
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
|
||||
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.msp-slider {
|
||||
.msp-slider {
|
||||
> div:first-child {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -91,14 +91,14 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
input[type=text] {
|
||||
padding-right: 6px;
|
||||
padding-left: 4px;
|
||||
font-size: 80%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
// input[type=range] {
|
||||
// width: 100%;
|
||||
// }
|
||||
@@ -135,14 +135,14 @@
|
||||
bottom: 0;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
||||
input[type=text] {
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
// input[type=range] {
|
||||
// width: 100%;
|
||||
// }
|
||||
@@ -154,24 +154,24 @@
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
padding-right: $control-spacing;
|
||||
padding-left: $control-spacing;
|
||||
|
||||
padding-left: $control-spacing;
|
||||
|
||||
&:hover {
|
||||
border-color: color-increase-contrast($msp-form-control-background, 5%) !important;
|
||||
border: none;
|
||||
outline-offset: -1px !important;
|
||||
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
|
||||
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.msp-color-picker {
|
||||
position: absolute;
|
||||
z-index: 100000;
|
||||
background: $default-background;
|
||||
border-top: 1px solid $default-background;
|
||||
padding-bottom: $control-spacing / 2;
|
||||
width: 100%;
|
||||
|
||||
width: 100%;
|
||||
|
||||
// input[type=text] {
|
||||
// background: $msp-form-control-background !important;
|
||||
// }
|
||||
@@ -217,7 +217,7 @@
|
||||
height: 2 * $row-height / 3 !important;
|
||||
line-height: 2 * $row-height / 3 !important;
|
||||
font-size: 70% !important;
|
||||
background: $default-background !important;
|
||||
background: $default-background !important;
|
||||
color: color-lower-contrast($font-color, 15%) !important;
|
||||
}
|
||||
}
|
||||
@@ -231,13 +231,13 @@
|
||||
|
||||
.msp-control-subgroup {
|
||||
margin-top: 1px;
|
||||
|
||||
|
||||
.msp-control-row {
|
||||
margin-left: $control-spacing !important;
|
||||
> span {
|
||||
width: $control-label-width !important;
|
||||
}
|
||||
|
||||
|
||||
> div:nth-child(2) {
|
||||
left: $control-label-width !important;
|
||||
}
|
||||
@@ -254,7 +254,7 @@
|
||||
width: $control-label-width + $control-spacing;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
|
||||
|
||||
.msp-icon {
|
||||
line-height: $row-height - 3;
|
||||
width: $row-height - 1;
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
background: $default-background;
|
||||
text-align: center;
|
||||
//font-style: italic;
|
||||
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE10+ */
|
||||
|
||||
/* Rules below not implemented in browsers yet */
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
|
||||
font-weight: light;
|
||||
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
top: 0;
|
||||
display: table;
|
||||
text-align: center;
|
||||
|
||||
|
||||
> div {
|
||||
b {
|
||||
font-size: 120%;
|
||||
font-size: 120%;
|
||||
}
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
@@ -68,6 +68,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.msp-contols-section {
|
||||
.msp-controls-section {
|
||||
margin-bottom: $control-spacing;
|
||||
}
|
||||
29
src/mol-plugin/skin/base/components/sequence.scss
Normal file
29
src/mol-plugin/skin/base/components/sequence.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.msp-sequence {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: $sequence-background;
|
||||
}
|
||||
|
||||
.msp-sequence-select {
|
||||
float: left;
|
||||
width: $sequence-select-width;
|
||||
}
|
||||
|
||||
.msp-sequence-wrapper {
|
||||
word-break: break-word;
|
||||
padding: $info-vertical-padding $control-spacing $info-vertical-padding $control-spacing;
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.msp-sequence-wrapper {
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -33,34 +33,34 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msp-layout-main, .msp-layout-bottom {
|
||||
.msp-layout-static {
|
||||
.msp-layout-top, .msp-layout-main, .msp-layout-bottom {
|
||||
.msp-layout-static {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msp-layout-right {
|
||||
.msp-layout-static {
|
||||
.msp-layout-right {
|
||||
.msp-layout-static {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0; // height: $row-height + $control-spacing;
|
||||
}
|
||||
|
||||
|
||||
.msp-layout-scrollable {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: $row-height + $control-spacing + 1;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.msp-layout-left {
|
||||
.msp-layout-static {
|
||||
.msp-layout-left {
|
||||
.msp-layout-static {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
@mixin non-selectable {
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE10+ */
|
||||
/* Rules below not implemented in browsers yet */
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
//-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.8);
|
||||
//-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.8);
|
||||
border-radius: 0;
|
||||
background-color: color-lower-contrast($control-background, 4%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 0;
|
||||
//-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.9);
|
||||
//-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.9);
|
||||
background-color: color-lower-contrast($control-background, 8%);
|
||||
}
|
||||
|
||||
@import 'components/controls-base';
|
||||
@import 'components/controls-base';
|
||||
@import 'components/controls';
|
||||
@import 'components/slider';
|
||||
@import 'components/panel';
|
||||
@@ -37,6 +37,7 @@
|
||||
@import 'components/tasks';
|
||||
@import 'components/viewport';
|
||||
@import 'components/log';
|
||||
@import 'components/sequence';
|
||||
@import 'components/transformer';
|
||||
@import 'components/toast';
|
||||
@import 'components/help';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
// measures
|
||||
// measures
|
||||
|
||||
$control-label-width: 110px;
|
||||
$row-height: 32px;
|
||||
@@ -28,14 +28,14 @@ $standard-top-height: 2 * $row-height + 1;
|
||||
// TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Visual' | 'Selection' | 'Action' | 'Behaviour'
|
||||
|
||||
// DO NOT CHANGE THESE!!
|
||||
$entity-color-Root: $default-background;
|
||||
$entity-color-Data: color-lower-contrast(#95a5a6, 15%);
|
||||
$entity-color-Selection: color-lower-contrast(#e74c3c, 15%);
|
||||
$entity-color-Action: color-lower-contrast(#34495e, 10%);
|
||||
$entity-color-Object: color-lower-contrast(#2ecc71, 10%);
|
||||
$entity-color-Behaviour: color-lower-contrast(#9b59b6, 10%);
|
||||
$entity-color-Visual: color-lower-contrast(#3498db, 5%);
|
||||
$entity-color-Group: color-lower-contrast(#e67e22, 5%);
|
||||
$entity-color-Root: $default-background;
|
||||
$entity-color-Data: color-lower-contrast(#95a5a6, 15%);
|
||||
$entity-color-Selection: color-lower-contrast(#e74c3c, 15%);
|
||||
$entity-color-Action: color-lower-contrast(#34495e, 10%);
|
||||
$entity-color-Object: color-lower-contrast(#2ecc71, 10%);
|
||||
$entity-color-Behaviour: color-lower-contrast(#9b59b6, 10%);
|
||||
$entity-color-Visual: color-lower-contrast(#3498db, 5%);
|
||||
$entity-color-Group: color-lower-contrast(#e67e22, 5%);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// COLORS and COMPUTED COLORS
|
||||
@@ -43,8 +43,8 @@ $entity-color-Group: color-lower-contrast(#e67e22, 5%);
|
||||
$slider-disabledColor: #ccc;
|
||||
|
||||
$control-background: color-increase-contrast($default-background, 6.5%);
|
||||
$border-color: color-increase-contrast($default-background, 15%);
|
||||
$msp-form-control-background: color-lower-contrast($default-background, 2.5%);
|
||||
$border-color: color-increase-contrast($default-background, 15%);
|
||||
$msp-form-control-background: color-lower-contrast($default-background, 2.5%);
|
||||
|
||||
// buttons
|
||||
$msp-btn-link-font-color: $font-color;
|
||||
@@ -72,7 +72,11 @@ $highlight-info-font-color: $hover-font-color;
|
||||
$highlight-info-additional-font-color: color-lower-contrast($hover-font-color, 20%);
|
||||
|
||||
// entity state
|
||||
$entity-color-fully-visible: $font-color;
|
||||
$entity-color-fully-visible: $font-color;
|
||||
$entity-color-not-visible: color-lower-contrast($font-color, 66%);
|
||||
$entity-color-partialy-visible: color-lower-contrast($font-color, 33%);
|
||||
$entity-tag-color: color-lower-contrast($font-color, 20%);
|
||||
|
||||
// sequence
|
||||
$sequence-background: $default-background;
|
||||
$sequence-select-width: 300px;
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateTransformer, StateAction } from '../mol-state';
|
||||
@@ -18,11 +19,7 @@ interface PluginSpec {
|
||||
customParamEditors?: [StateAction | StateTransformer, StateTransformParameters.Class][],
|
||||
layout?: {
|
||||
initial?: Partial<PluginLayoutStateProps>,
|
||||
controls?: {
|
||||
left?: React.ComponentClass | 'none',
|
||||
right?: React.ComponentClass | 'none',
|
||||
bottom?: React.ComponentClass | 'none'
|
||||
}
|
||||
controls?: PluginSpec.LayoutControls
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +44,7 @@ namespace PluginSpec {
|
||||
}
|
||||
|
||||
export interface LayoutControls {
|
||||
top?: React.ComponentClass | 'none',
|
||||
left?: React.ComponentClass | 'none',
|
||||
right?: React.ComponentClass | 'none',
|
||||
bottom?: React.ComponentClass | 'none'
|
||||
|
||||
@@ -16,6 +16,7 @@ import { PluginCommands } from './command';
|
||||
import { PluginAnimationManager } from './state/animation/manager';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { UUID } from '../mol-util';
|
||||
import { Interactivity } from './util/interactivity';
|
||||
export { PluginState }
|
||||
|
||||
class PluginState {
|
||||
@@ -53,12 +54,11 @@ class PluginState {
|
||||
camera: p.camera ? {
|
||||
current: this.plugin.canvas3d.camera.getSnapshot(),
|
||||
transitionStyle: p.cameraTranstion.name,
|
||||
transitionDurationInMs: (params && params.cameraTranstion && params.cameraTranstion.name === 'animate' && params.cameraTranstion.params.durationInMs) || void 0
|
||||
transitionDurationInMs: (params && params.cameraTranstion && params.cameraTranstion.name === 'animate') ? params.cameraTranstion.params.durationInMs : undefined
|
||||
} : void 0,
|
||||
cameraSnapshots: p.cameraSnapshots ? this.cameraSnapshots.getStateSnapshot() : void 0,
|
||||
canvas3d: p.canvas3d ? {
|
||||
props: this.plugin.canvas3d.props
|
||||
} : void 0,
|
||||
canvas3d: p.canvas3d ? { props: this.plugin.canvas3d.props } : void 0,
|
||||
interactivity: p.interactivity ? { props: this.plugin.interactivity.props } : void 0,
|
||||
durationInMs: params && params.durationInMs
|
||||
};
|
||||
}
|
||||
@@ -69,7 +69,10 @@ class PluginState {
|
||||
if (snapshot.behaviour) await this.plugin.runTask(this.behaviorState.setSnapshot(snapshot.behaviour));
|
||||
if (snapshot.data) await this.plugin.runTask(this.dataState.setSnapshot(snapshot.data));
|
||||
if (snapshot.canvas3d) {
|
||||
if (snapshot.canvas3d.props) await PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: snapshot.canvas3d.props || { } });
|
||||
if (snapshot.canvas3d.props) await PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: snapshot.canvas3d.props });
|
||||
}
|
||||
if (snapshot.interactivity) {
|
||||
if (snapshot.interactivity.props) await PluginCommands.Interactivity.SetProps.dispatch(this.plugin, { props: snapshot.interactivity.props });
|
||||
}
|
||||
if (snapshot.cameraSnapshots) this.cameraSnapshots.setStateSnapshot(snapshot.cameraSnapshots);
|
||||
if (snapshot.animation) {
|
||||
@@ -125,6 +128,7 @@ namespace PluginState {
|
||||
animation: PD.Boolean(true),
|
||||
startAnimation: PD.Boolean(false),
|
||||
canvas3d: PD.Boolean(true),
|
||||
interactivity: PD.Boolean(true),
|
||||
camera: PD.Boolean(true),
|
||||
// TODO: make camera snapshots same as the StateSnapshots with "child states?"
|
||||
cameraSnapshots: PD.Boolean(false),
|
||||
@@ -153,6 +157,9 @@ namespace PluginState {
|
||||
canvas3d?: {
|
||||
props?: Canvas3DProps
|
||||
},
|
||||
interactivity?: {
|
||||
props?: Interactivity.Props
|
||||
},
|
||||
durationInMs?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { camelCaseToWords } from '../../../mol-util/string';
|
||||
import * as React from 'react';
|
||||
import LineGraphComponent from './line-graph/line-graph-component';
|
||||
import { Slider, Slider2 } from './slider';
|
||||
import { NumericInput, IconButton } from './common';
|
||||
import { NumericInput, IconButton, ControlGroup } from './common';
|
||||
import { _Props, _State } from '../base';
|
||||
|
||||
export interface ParameterControlsProps<P extends PD.Params = PD.Params> {
|
||||
@@ -97,7 +97,7 @@ export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent<
|
||||
}
|
||||
}
|
||||
|
||||
export class BoolControl extends SimpleParam<PD.Boolean> {
|
||||
export class BoolControl extends SimpleParam<PD.BooleanParam> {
|
||||
onClick = (e: React.MouseEvent<HTMLButtonElement>) => { this.update(!this.props.value); e.currentTarget.blur(); }
|
||||
renderControl() {
|
||||
return <button onClick={this.onClick} disabled={this.props.isDisabled}>
|
||||
@@ -461,8 +461,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>,
|
||||
</div>
|
||||
{this.state.isExpanded && <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
|
||||
{controls}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -655,9 +654,12 @@ export class ObjectListControl extends React.PureComponent<ParamProps<PD.ObjectL
|
||||
<button onClick={this.toggleExpanded}>{value}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.isExpanded && <div className='msp-control-offset'>
|
||||
{this.props.value.map((v, i) => <ObjectListItem key={i} param={this.props.param} value={v} index={i} actions={this.actions} />)}
|
||||
<ObjectListEditor params={this.props.param.element} apply={this.add} value={this.props.param.ctor()} isDisabled={this.props.isDisabled} />
|
||||
<ControlGroup header='New Item'>
|
||||
<ObjectListEditor params={this.props.param.element} apply={this.add} value={this.props.param.ctor()} isDisabled={this.props.isDisabled} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { List } from 'immutable';
|
||||
@@ -18,7 +19,8 @@ import { StateTree } from './state/tree';
|
||||
import { BackgroundTaskProgress } from './task';
|
||||
import { Viewport, ViewportControls } from './viewport';
|
||||
import { StateTransform } from '../../mol-state';
|
||||
import { UpdateTransformContol } from './state/update-transform';
|
||||
import { UpdateTransformControl } from './state/update-transform';
|
||||
import { SequenceView } from './sequence';
|
||||
|
||||
export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
|
||||
|
||||
@@ -37,27 +39,61 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export class PluginContextContainer extends React.Component<{ plugin: PluginContext }> {
|
||||
render() {
|
||||
return <PluginReactContext.Provider value={this.props.plugin}>
|
||||
<div className='msp-plugin'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</PluginReactContext.Provider>;
|
||||
}
|
||||
}
|
||||
|
||||
type RegionKind = 'top' | 'left' | 'right' | 'bottom' | 'main'
|
||||
|
||||
class Layout extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
region(kind: 'left' | 'right' | 'bottom' | 'main', Element: React.ComponentClass) {
|
||||
region(kind: RegionKind, Element?: React.ComponentClass) {
|
||||
return <div className={`msp-layout-region msp-layout-${kind}`}>
|
||||
<div className='msp-layout-static'>
|
||||
<Element />
|
||||
{Element ? <Element /> : null}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
get layoutVisibilityClassName() {
|
||||
const layout = this.plugin.layout.state;
|
||||
const controls = (this.plugin.spec.layout && this.plugin.spec.layout.controls) || { };
|
||||
|
||||
const classList: string[] = []
|
||||
if (controls.top === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-top')
|
||||
}
|
||||
if (controls.left === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-left')
|
||||
}
|
||||
if (controls.right === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-right')
|
||||
}
|
||||
if (controls.bottom === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-bottom')
|
||||
}
|
||||
|
||||
return classList.join(' ')
|
||||
}
|
||||
|
||||
render() {
|
||||
const layout = this.plugin.layout.state;
|
||||
const controls = (this.plugin.spec.layout && this.plugin.spec.layout.controls) || { };
|
||||
|
||||
return <div className='msp-plugin'>
|
||||
<div className={`msp-plugin-content ${layout.isExpanded ? 'msp-layout-expanded' : 'msp-layout-standard msp-layout-standard-outside'}`}>
|
||||
<div className={layout.showControls ? 'msp-layout-hide-top' : 'msp-layout-hide-top msp-layout-hide-right msp-layout-hide-bottom msp-layout-hide-left'}>
|
||||
<div className={this.layoutVisibilityClassName}>
|
||||
{this.region('main', ViewportWrapper)}
|
||||
{layout.showControls && controls.top !== 'none' && this.region('top', controls.top || SequenceView)}
|
||||
{layout.showControls && controls.left !== 'none' && this.region('left', controls.left || State)}
|
||||
{layout.showControls && controls.right !== 'none' && this.region('right', controls.right || ControlsWrapper)}
|
||||
{layout.showControls && controls.bottom !== 'none' && this.region('bottom', controls.bottom || Log)}
|
||||
@@ -67,7 +103,6 @@ class Layout extends PluginUIComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
return <div className='msp-scrollable-container msp-right-controls'>
|
||||
@@ -193,7 +228,7 @@ export class CurrentObject extends PluginUIComponent {
|
||||
if (!showActions) return null;
|
||||
|
||||
return <>
|
||||
{(cell.status === 'ok' || cell.status === 'error') && <UpdateTransformContol state={current.state} transform={transform} /> }
|
||||
{(cell.status === 'ok' || cell.status === 'error') && <UpdateTransformControl state={current.state} transform={transform} /> }
|
||||
{cell.status === 'ok' && <StateObjectActions state={current.state} nodeRef={ref} initiallyColapsed />}
|
||||
</>;
|
||||
}
|
||||
|
||||
206
src/mol-plugin/ui/sequence.tsx
Normal file
206
src/mol-plugin/ui/sequence.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { PluginUIComponent } from './base';
|
||||
import { StateTreeSpine } from '../../mol-state/tree/spine';
|
||||
import { PluginStateObject as SO } from '../state/objects';
|
||||
import { Sequence } from './sequence/sequence';
|
||||
import { Structure, StructureElement, StructureProperties as SP } from '../../mol-model/structure';
|
||||
import { SequenceWrapper } from './sequence/util';
|
||||
import { PolymerSequenceWrapper } from './sequence/polymer';
|
||||
import { StructureElementSelectionManager } from '../util/structure-element-selection';
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
import { ParameterControls } from './controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
function opKey(l: StructureElement) {
|
||||
const ids = SP.unit.pdbx_struct_oper_list_ids(l)
|
||||
const ncs = SP.unit.struct_ncs_oper_id(l)
|
||||
const hkl = SP.unit.hkl(l)
|
||||
const spgrOp = SP.unit.spgrOp(l)
|
||||
return `${ids.sort().join(',')}|${ncs}|${hkl}|${spgrOp}`
|
||||
}
|
||||
|
||||
function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | undefined {
|
||||
const { structure, entity, chain, operator } = state
|
||||
const l = StructureElement.create()
|
||||
for (let i = 0, il = structure.units.length; i < il; ++i) {
|
||||
const unit = structure.units[i]
|
||||
if (unit.polymerElements.length === 0) continue
|
||||
|
||||
StructureElement.set(l, unit, unit.elements[0])
|
||||
if (SP.entity.id(l) !== entity) continue
|
||||
if (SP.chain.label_asym_id(l) !== chain) continue
|
||||
if (opKey(l) !== operator) continue
|
||||
|
||||
// console.log('new PolymerSequenceWrapper', structureSelection.get(structure))
|
||||
const sw = new PolymerSequenceWrapper({ structure, unit })
|
||||
sw.markResidue(structureSelection.get(structure), MarkerAction.Select)
|
||||
return sw
|
||||
}
|
||||
}
|
||||
|
||||
function getEntityOptions(structure: Structure) {
|
||||
const options: [string, string][] = []
|
||||
const l = StructureElement.create()
|
||||
const seen = new Set<string>()
|
||||
|
||||
structure.units.forEach(unit => {
|
||||
if (unit.polymerElements.length === 0) return
|
||||
|
||||
StructureElement.set(l, unit, unit.elements[0])
|
||||
const id = SP.entity.id(l)
|
||||
if (seen.has(id)) return
|
||||
|
||||
const label = `${id}: ${SP.entity.pdbx_description(l).join(', ')}`
|
||||
options.push([ id, label ])
|
||||
seen.add(id)
|
||||
})
|
||||
|
||||
if (options.length === 0) options.push(['', 'No entities'])
|
||||
return options
|
||||
}
|
||||
|
||||
function getChainOptions(structure: Structure, entityId: string) {
|
||||
const options: [string, string][] = []
|
||||
const l = StructureElement.create()
|
||||
const seen = new Set<string>()
|
||||
|
||||
structure.units.forEach(unit => {
|
||||
if (unit.polymerElements.length === 0) return
|
||||
|
||||
StructureElement.set(l, unit, unit.elements[0])
|
||||
if (SP.entity.id(l) !== entityId) return
|
||||
|
||||
const id = SP.chain.label_asym_id(l)
|
||||
if (seen.has(id)) return
|
||||
|
||||
const label = `${id}: ${SP.chain.auth_asym_id(l)}`
|
||||
options.push([ id, label ])
|
||||
seen.add(id)
|
||||
})
|
||||
|
||||
if (options.length === 0) options.push(['', 'No chains'])
|
||||
return options
|
||||
}
|
||||
|
||||
function getOperatorOptions(structure: Structure, entityId: string, label_asym_id: string) {
|
||||
const options: [string, string][] = []
|
||||
const l = StructureElement.create()
|
||||
const seen = new Set<string>()
|
||||
|
||||
structure.units.forEach(unit => {
|
||||
if (unit.polymerElements.length === 0) return
|
||||
StructureElement.set(l, unit, unit.elements[0])
|
||||
if (SP.entity.id(l) !== entityId) return
|
||||
if (SP.chain.label_asym_id(l) !== label_asym_id) return
|
||||
|
||||
const id = opKey(l)
|
||||
if (seen.has(id)) return
|
||||
|
||||
const label = unit.conformation.operator.name
|
||||
options.push([ id, label ])
|
||||
seen.add(id)
|
||||
})
|
||||
|
||||
if (options.length === 0) options.push(['', 'No operators'])
|
||||
return options
|
||||
}
|
||||
|
||||
type SequenceViewState = { structure: Structure, entity: string, chain: string, operator: string }
|
||||
|
||||
export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
|
||||
private spine: StateTreeSpine.Impl
|
||||
|
||||
state = { structure: Structure.Empty, entity: '', chain: '', operator: '' }
|
||||
|
||||
constructor(props: {}, context?: any) {
|
||||
super(props, context);
|
||||
this.spine = new StateTreeSpine.Impl(this.plugin.state.dataState.cells);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.behavior.currentObject, o => {
|
||||
const current = this.plugin.state.dataState.cells.get(o.ref)!;
|
||||
this.spine.current = current
|
||||
if (!Structure.areParentsEqual(this.state.structure, this.getStructure())) {
|
||||
this.setState(this.getInitialState())
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
|
||||
const current = this.spine.current;
|
||||
if (!current || current.sourceRef !== ref || current.state !== state) return;
|
||||
this.setState(this.getInitialState())
|
||||
});
|
||||
}
|
||||
|
||||
private getStructure() {
|
||||
const so = this.spine.getRootOfType(SO.Molecule.Structure)
|
||||
return (so && so.data) || Structure.Empty
|
||||
}
|
||||
|
||||
private getSequenceWrapper() {
|
||||
return getSequenceWrapper(this.state, this.plugin.helpers.structureSelection)
|
||||
}
|
||||
|
||||
private getInitialState(): SequenceViewState {
|
||||
const structure = this.getStructure()
|
||||
const entity = getEntityOptions(structure)[0][0]
|
||||
const chain = getChainOptions(structure, entity)[0][0]
|
||||
const operator = getOperatorOptions(structure, entity, chain)[0][0]
|
||||
return { structure, entity, chain, operator }
|
||||
}
|
||||
|
||||
private get params() {
|
||||
const { structure, entity, chain } = this.state
|
||||
const entityOptions = getEntityOptions(structure)
|
||||
const chainOptions = getChainOptions(structure, entity)
|
||||
const operatorOptions = getOperatorOptions(structure, entity, chain)
|
||||
return {
|
||||
entity: PD.Select(entityOptions[0][0], entityOptions),
|
||||
chain: PD.Select(chainOptions[0][0], chainOptions),
|
||||
operator: PD.Select(operatorOptions[0][0], operatorOptions)
|
||||
}
|
||||
}
|
||||
|
||||
private setParamProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
|
||||
const state = { ...this.state }
|
||||
switch (p.name) {
|
||||
case 'entity':
|
||||
state.entity = p.value
|
||||
state.chain = getChainOptions(state.structure, state.entity)[0][0]
|
||||
state.operator = getOperatorOptions(state.structure, state.entity, state.chain)[0][0]
|
||||
break
|
||||
case 'chain':
|
||||
state.chain = p.value
|
||||
state.operator = getOperatorOptions(state.structure, state.entity, state.chain)[0][0]
|
||||
break
|
||||
case 'operator':
|
||||
state.operator = p.value
|
||||
break
|
||||
}
|
||||
this.setState(state)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.structure === Structure.Empty) return <div className='msp-sequence'>
|
||||
<div className='msp-sequence-wrapper'>No structure available</div>
|
||||
</div>;
|
||||
|
||||
const sequenceWrapper = this.getSequenceWrapper()
|
||||
return <div className='msp-sequence'>
|
||||
<div className='msp-sequence-select'>
|
||||
<ParameterControls params={this.params} values={this.state} onChange={this.setParamProps} />
|
||||
</div>
|
||||
{sequenceWrapper !== undefined
|
||||
? <Sequence sequenceWrapper={sequenceWrapper} />
|
||||
: <div className='msp-sequence-wrapper'>No sequence available</div>}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
124
src/mol-plugin/ui/sequence/polymer.ts
Normal file
124
src/mol-plugin/ui/sequence/polymer.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StructureSelection, StructureQuery, Structure, Queries, StructureProperties as SP, StructureElement, Unit } from '../../../mol-model/structure';
|
||||
import { SequenceWrapper } from './util';
|
||||
import { OrderedSet, Interval } from '../../../mol-data/int';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { Sequence } from '../../../mol-model/sequence';
|
||||
import { MissingResidues } from '../../../mol-model/structure/model/properties/common';
|
||||
import { ColorNames } from '../../../mol-util/color/tables';
|
||||
|
||||
export type StructureUnit = { structure: Structure, unit: Unit }
|
||||
|
||||
export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
|
||||
private readonly location: StructureElement
|
||||
private readonly sequence: Sequence
|
||||
private readonly missing: MissingResidues
|
||||
|
||||
private readonly modelNum: number
|
||||
private readonly asymId: string
|
||||
|
||||
seqId(i: number) {
|
||||
return this.sequence.offset + i + 1
|
||||
}
|
||||
residueLabel(i: number) {
|
||||
return this.sequence.sequence[i]
|
||||
}
|
||||
residueColor(i: number) {
|
||||
return this.missing.has(this.modelNum, this.asymId, this.seqId(i))
|
||||
? ColorNames.grey
|
||||
: ColorNames.black
|
||||
}
|
||||
|
||||
eachResidue(loci: Loci, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
const { structure, unit } = this.data
|
||||
if (!StructureElement.isLoci(loci)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
|
||||
const { location } = this
|
||||
for (const e of loci.elements) {
|
||||
let rIprev = -1
|
||||
location.unit = e.unit
|
||||
|
||||
const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments
|
||||
|
||||
OrderedSet.forEach(e.indices, v => {
|
||||
location.element = e.unit.elements[v]
|
||||
const rI = residueIndex[location.element]
|
||||
// avoid checking for the same residue multiple times
|
||||
if (rI !== rIprev) {
|
||||
if (SP.unit.id(location) !== unit.id) return
|
||||
|
||||
if (apply(getSeqIdInterval(location))) changed = true
|
||||
rIprev = rI
|
||||
}
|
||||
})
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
getLoci(seqId: number) {
|
||||
const query = createResidueQuery(this.data.unit.id, seqId);
|
||||
return StructureSelection.toLoci2(StructureQuery.run(query, this.data.structure));
|
||||
}
|
||||
|
||||
constructor(data: StructureUnit) {
|
||||
const l = StructureElement.create(data.unit, data.unit.elements[0])
|
||||
const sequence = data.unit.model.sequence.byEntityKey[SP.entity.key(l)].sequence
|
||||
const markerArray = new Uint8Array(sequence.sequence.length)
|
||||
|
||||
super(data, markerArray, sequence.sequence.length)
|
||||
|
||||
this.sequence = sequence
|
||||
this.location = StructureElement.create()
|
||||
this.missing = data.unit.model.properties.missingResidues
|
||||
|
||||
this.modelNum = data.unit.model.modelNum
|
||||
this.asymId = SP.chain.label_asym_id(l)
|
||||
}
|
||||
}
|
||||
|
||||
function createResidueQuery(unitId: number, label_seq_id: number) {
|
||||
return Queries.generators.atoms({
|
||||
unitTest: ctx => {
|
||||
return SP.unit.id(ctx.element) === unitId
|
||||
},
|
||||
residueTest: ctx => {
|
||||
if (ctx.element.unit.kind === Unit.Kind.Atomic) {
|
||||
return SP.residue.label_seq_id(ctx.element) === label_seq_id
|
||||
} else {
|
||||
return (
|
||||
SP.coarse.seq_id_begin(ctx.element) <= label_seq_id &&
|
||||
SP.coarse.seq_id_end(ctx.element) >= label_seq_id
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Zero-indexed */
|
||||
function getSeqIdInterval(location: StructureElement): Interval {
|
||||
const { unit, element } = location
|
||||
const { model } = unit
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic:
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index[element]
|
||||
const seqId = model.atomicHierarchy.residues.label_seq_id.value(residueIndex)
|
||||
return Interval.ofSingleton(seqId - 1)
|
||||
case Unit.Kind.Spheres:
|
||||
return Interval.ofRange(
|
||||
model.coarseHierarchy.spheres.seq_id_begin.value(element) - 1,
|
||||
model.coarseHierarchy.spheres.seq_id_end.value(element) - 1
|
||||
)
|
||||
case Unit.Kind.Gaussians:
|
||||
return Interval.ofRange(
|
||||
model.coarseHierarchy.gaussians.seq_id_begin.value(element) - 1,
|
||||
model.coarseHierarchy.gaussians.seq_id_end.value(element) - 1
|
||||
)
|
||||
}
|
||||
}
|
||||
52
src/mol-plugin/ui/sequence/residue.tsx
Normal file
52
src/mol-plugin/ui/sequence/residue.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { PurePluginUIComponent } from '../base';
|
||||
import { getButtons, getModifiers } from '../../../mol-util/input/input-observer';
|
||||
import { Sequence } from './sequence';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
|
||||
export class Residue extends PurePluginUIComponent<{ seqId: number, label: string, parent: Sequence<any>, marker: number, color: Color }> {
|
||||
|
||||
mouseEnter = (e: React.MouseEvent) => {
|
||||
const modifiers = getModifiers(e.nativeEvent)
|
||||
this.props.parent.highlight(this.props.seqId, modifiers);
|
||||
}
|
||||
|
||||
mouseLeave = () => {
|
||||
this.props.parent.highlight();
|
||||
}
|
||||
|
||||
mouseDown = (e: React.MouseEvent) => {
|
||||
const buttons = getButtons(e.nativeEvent)
|
||||
const modifiers = getModifiers(e.nativeEvent)
|
||||
this.props.parent.click(this.props.seqId, buttons, modifiers);
|
||||
e.stopPropagation() // so that `parent.mouseDown` is not called
|
||||
}
|
||||
|
||||
getBackgroundColor() {
|
||||
// TODO make marker color configurable
|
||||
if (this.props.marker === 0) return ''
|
||||
if (this.props.marker % 2 === 0) return 'rgb(51, 255, 25)' // selected
|
||||
if (this.props.marker === undefined) console.error('unexpected marker value')
|
||||
return 'rgb(255, 102, 153)' // highlighted
|
||||
}
|
||||
|
||||
render() {
|
||||
return <span
|
||||
onMouseEnter={this.mouseEnter}
|
||||
onMouseLeave={this.mouseLeave}
|
||||
onMouseDown={this.mouseDown}
|
||||
style={{
|
||||
color: Color.toStyle(this.props.color),
|
||||
backgroundColor: this.getBackgroundColor()
|
||||
}}>
|
||||
{this.props.label}
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
113
src/mol-plugin/ui/sequence/sequence.tsx
Normal file
113
src/mol-plugin/ui/sequence/sequence.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { Interactivity } from '../../util/interactivity';
|
||||
import { MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { ButtonsType, ModifiersKeys, getButtons, getModifiers } from '../../../mol-util/input/input-observer';
|
||||
import { ValueBox } from '../../../mol-util';
|
||||
import { Residue } from './residue';
|
||||
import { SequenceWrapper } from './util';
|
||||
|
||||
type SequenceProps = { sequenceWrapper: SequenceWrapper.Any }
|
||||
type SequenceState = { markerData: ValueBox<Uint8Array> }
|
||||
|
||||
function getState(markerData: ValueBox<Uint8Array>) {
|
||||
return { markerData: ValueBox.withValue(markerData, markerData.value) }
|
||||
}
|
||||
|
||||
// TODO: this is really inefficient and should be done using a canvas.
|
||||
export class Sequence<P extends SequenceProps> extends PluginUIComponent<P, SequenceState> {
|
||||
state = {
|
||||
markerData: ValueBox.create(this.props.sequenceWrapper.markerArray)
|
||||
}
|
||||
|
||||
private setMarkerData(markerData: ValueBox<Uint8Array>) {
|
||||
this.setState(getState(markerData))
|
||||
}
|
||||
|
||||
private lociHighlightProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
|
||||
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
|
||||
if (changed) this.setMarkerData(this.state.markerData)
|
||||
}
|
||||
|
||||
private lociSelectionProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
|
||||
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
|
||||
if (changed) this.setMarkerData(this.state.markerData)
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps: SequenceProps, prevState: SequenceState): SequenceState | null {
|
||||
if (prevState.markerData.value !== nextProps.sequenceWrapper.markerArray) {
|
||||
return getState(ValueBox.create(nextProps.sequenceWrapper.markerArray))
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.plugin.interactivity.lociHighlights.addProvider(this.lociHighlightProvider)
|
||||
this.plugin.interactivity.lociSelections.addProvider(this.lociSelectionProvider)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.plugin.interactivity.lociHighlights.removeProvider(this.lociHighlightProvider)
|
||||
this.plugin.interactivity.lociSelections.removeProvider(this.lociSelectionProvider)
|
||||
}
|
||||
|
||||
highlight(seqId?: number, modifiers?: ModifiersKeys) {
|
||||
const ev = { current: Interactivity.Loci.Empty, modifiers }
|
||||
if (seqId !== undefined) {
|
||||
const loci = this.props.sequenceWrapper.getLoci(seqId);
|
||||
if (loci.elements.length > 0) ev.current = { loci };
|
||||
}
|
||||
this.plugin.behaviors.interaction.highlight.next(ev)
|
||||
}
|
||||
|
||||
click(seqId: number | undefined, buttons: ButtonsType, modifiers: ModifiersKeys) {
|
||||
const ev = { current: Interactivity.Loci.Empty, buttons, modifiers }
|
||||
if (seqId !== undefined) {
|
||||
const loci = this.props.sequenceWrapper.getLoci(seqId);
|
||||
if (loci.elements.length > 0) ev.current = { loci };
|
||||
}
|
||||
this.plugin.behaviors.interaction.click.next(ev)
|
||||
}
|
||||
|
||||
contextMenu = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
mouseDown = (e: React.MouseEvent) => {
|
||||
const buttons = getButtons(e.nativeEvent)
|
||||
const modifiers = getModifiers(e.nativeEvent)
|
||||
this.click(undefined, buttons, modifiers);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { markerData } = this.state;
|
||||
const sw = this.props.sequenceWrapper
|
||||
|
||||
const elems: JSX.Element[] = [];
|
||||
for (let i = 0, il = sw.length; i < il; ++i) {
|
||||
elems[elems.length] = <Residue
|
||||
seqId={sw.seqId(i)}
|
||||
label={sw.residueLabel(i)}
|
||||
parent={this}
|
||||
marker={markerData.value[i]}
|
||||
color={sw.residueColor(i)}
|
||||
key={i}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <div
|
||||
className='msp-sequence-wrapper'
|
||||
onContextMenu={this.contextMenu}
|
||||
onMouseDown={this.mouseDown}
|
||||
>
|
||||
{elems}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
36
src/mol-plugin/ui/sequence/util.ts
Normal file
36
src/mol-plugin/ui/sequence/util.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Interval } from '../../../mol-data/int';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { MarkerAction, applyMarkerAction } from '../../../mol-util/marker-action';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
|
||||
export { SequenceWrapper }
|
||||
|
||||
abstract class SequenceWrapper<D> {
|
||||
abstract seqId(i: number): number
|
||||
abstract residueLabel(i: number): string
|
||||
abstract residueColor(i: number): Color
|
||||
|
||||
abstract eachResidue(loci: Loci, apply: (interval: Interval) => boolean): boolean
|
||||
abstract getLoci(seqId: number): StructureElement.Loci
|
||||
|
||||
markResidue(loci: Loci, action: MarkerAction) {
|
||||
return this.eachResidue(loci, (i: Interval) => {
|
||||
return applyMarkerAction(this.markerArray, i, action)
|
||||
})
|
||||
}
|
||||
|
||||
constructor(readonly data: D, readonly markerArray: Uint8Array, readonly length: number) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
namespace SequenceWrapper {
|
||||
export type Any = SequenceWrapper<any>
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { ApplyActionContol } from './apply-action';
|
||||
import { ApplyActionControl } from './apply-action';
|
||||
import { State } from '../../../mol-state';
|
||||
import { Icon } from '../controls/common';
|
||||
|
||||
@@ -38,7 +38,7 @@ export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRe
|
||||
|
||||
return <div className='msp-state-actions'>
|
||||
{!this.props.hideHeader && <div className='msp-section-header'><Icon name='code' /> {`Actions (${display})`}</div> }
|
||||
{actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} initiallyCollapsed={this.props.initiallyColapsed} />)}
|
||||
{actions.map((act, i) => <ApplyActionControl plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} initiallyCollapsed={this.props.initiallyColapsed} />)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export class AnimationControlsWrapper extends PluginUIComponent<{ }> {
|
||||
render() {
|
||||
const anim = this.plugin.state.animation;
|
||||
if (anim.isEmpty) return null;
|
||||
return <div className='msp-contols-section'>
|
||||
return <div className='msp-controls-section'>
|
||||
<div className='msp-section-header'><Icon name='code' /> Animations</div>
|
||||
<AnimationControls />
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,12 @@ import { PluginCommands } from '../../../mol-plugin/command';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { State, StateTransform, StateAction } from '../../../mol-state';
|
||||
import { memoizeLatest } from '../../../mol-util/memoize';
|
||||
import { StateTransformParameters, TransformContolBase } from './common';
|
||||
import { StateTransformParameters, TransformControlBase } from './common';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export { ApplyActionContol };
|
||||
export { ApplyActionControl };
|
||||
|
||||
namespace ApplyActionContol {
|
||||
namespace ApplyActionControl {
|
||||
export interface Props {
|
||||
plugin: PluginContext,
|
||||
nodeRef: StateTransform.Ref,
|
||||
@@ -32,7 +32,7 @@ namespace ApplyActionContol {
|
||||
}
|
||||
}
|
||||
|
||||
class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, ApplyActionContol.ComponentState> {
|
||||
class ApplyActionControl extends TransformControlBase<ApplyActionControl.Props, ApplyActionControl.ComponentState> {
|
||||
applyAction() {
|
||||
return PluginCommands.State.ApplyAction.dispatch(this.plugin, {
|
||||
state: this.props.state,
|
||||
@@ -53,7 +53,7 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App
|
||||
|
||||
state = { ref: this.props.nodeRef, version: this.props.state.transforms.get(this.props.nodeRef).version, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
|
||||
|
||||
static getDerivedStateFromProps(props: ApplyActionContol.Props, state: ApplyActionContol.ComponentState) {
|
||||
static getDerivedStateFromProps(props: ApplyActionControl.Props, state: ApplyActionControl.ComponentState) {
|
||||
if (props.nodeRef === state.ref) return null;
|
||||
const version = props.state.transforms.get(props.nodeRef).version;
|
||||
if (version === state.version) return null;
|
||||
@@ -63,7 +63,7 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App
|
||||
? PD.getDefaultValues(props.action.definition.params(source, props.plugin))
|
||||
: { };
|
||||
|
||||
const newState: Partial<ApplyActionContol.ComponentState> = {
|
||||
const newState: Partial<ApplyActionControl.ComponentState> = {
|
||||
ref: props.nodeRef,
|
||||
version,
|
||||
params,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Icon } from '../controls/common';
|
||||
|
||||
export { StateTransformParameters, TransformContolBase };
|
||||
export { StateTransformParameters, TransformControlBase };
|
||||
|
||||
class StateTransformParameters extends PurePluginUIComponent<StateTransformParameters.Props> {
|
||||
validate(params: any) {
|
||||
@@ -89,7 +89,7 @@ namespace StateTransformParameters {
|
||||
}
|
||||
}
|
||||
|
||||
namespace TransformContolBase {
|
||||
namespace TransformControlBase {
|
||||
export interface ComponentState {
|
||||
params: any,
|
||||
error?: string,
|
||||
@@ -99,7 +99,7 @@ namespace TransformContolBase {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TransformContolBase<P, S extends TransformContolBase.ComponentState> extends PurePluginUIComponent<P, S> {
|
||||
abstract class TransformControlBase<P, S extends TransformControlBase.ComponentState> extends PurePluginUIComponent<P, S> {
|
||||
abstract applyAction(): Promise<void>;
|
||||
abstract getInfo(): StateTransformParameters.Props['info'];
|
||||
abstract getHeader(): StateTransformer.Definition['display'];
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
import { State, StateTransform, StateTransformer } from '../../../mol-state';
|
||||
import { memoizeLatest } from '../../../mol-util/memoize';
|
||||
import { StateTransformParameters, TransformContolBase } from './common';
|
||||
import { StateTransformParameters, TransformControlBase } from './common';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
|
||||
export { UpdateTransformContol, TransformUpdaterControl };
|
||||
export { UpdateTransformControl, TransformUpdaterControl };
|
||||
|
||||
namespace UpdateTransformContol {
|
||||
namespace UpdateTransformControl {
|
||||
export interface Props {
|
||||
transform: StateTransform,
|
||||
state: State,
|
||||
@@ -22,12 +22,12 @@ namespace UpdateTransformContol {
|
||||
customHeader?: StateTransformer.Definition['display']
|
||||
}
|
||||
|
||||
export interface ComponentState extends TransformContolBase.ComponentState {
|
||||
export interface ComponentState extends TransformControlBase.ComponentState {
|
||||
transform: StateTransform
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Props, UpdateTransformContol.ComponentState> {
|
||||
class UpdateTransformControl extends TransformControlBase<UpdateTransformControl.Props, UpdateTransformControl.ComponentState> {
|
||||
applyAction() { return this.plugin.updateTransform(this.props.state, this.props.transform.ref, this.state.params); }
|
||||
getInfo() { return this._getInfo(this.props.transform); }
|
||||
getTransformerId() { return this.props.transform.transformer.id; }
|
||||
@@ -70,12 +70,12 @@ class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Pr
|
||||
|
||||
private _getInfo = memoizeLatest((t: StateTransform) => StateTransformParameters.infoFromTransform(this.plugin, this.props.state, t));
|
||||
|
||||
state: UpdateTransformContol.ComponentState = { transform: this.props.transform, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
|
||||
state: UpdateTransformControl.ComponentState = { transform: this.props.transform, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
|
||||
|
||||
static getDerivedStateFromProps(props: UpdateTransformContol.Props, state: UpdateTransformContol.ComponentState) {
|
||||
static getDerivedStateFromProps(props: UpdateTransformControl.Props, state: UpdateTransformControl.ComponentState) {
|
||||
if (props.transform === state.transform) return null;
|
||||
const cell = props.state.cells.get(props.transform.ref)!;
|
||||
const newState: Partial<UpdateTransformContol.ComponentState> = {
|
||||
const newState: Partial<UpdateTransformControl.ComponentState> = {
|
||||
transform: props.transform,
|
||||
params: (cell.params && cell.params.values) || { },
|
||||
isInitial: true,
|
||||
@@ -101,7 +101,6 @@ class TransformUpdaterControl extends PluginUIComponent<{ nodeRef: string, initi
|
||||
if (!cell || (cell.status !== 'ok' && cell.status !== 'error')) return null;
|
||||
|
||||
const transform = cell.transform;
|
||||
|
||||
return <UpdateTransformContol state={state} transform={transform} initiallyCollapsed={this.props.initiallyCollapsed} customHeader={this.props.header} />;
|
||||
return <UpdateTransformControl state={state} transform={transform} initiallyCollapsed={this.props.initiallyCollapsed} customHeader={this.props.header} />;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { PluginLayoutStateParams } from '../../mol-plugin/layout';
|
||||
import { ControlGroup, IconButton } from './controls/common';
|
||||
import { resizeCanvas } from '../../mol-canvas3d/util';
|
||||
import { Interactivity } from '../util/interactivity';
|
||||
|
||||
interface ViewportState {
|
||||
noWebGl: boolean
|
||||
@@ -49,14 +50,14 @@ export class ViewportControls extends PluginUIComponent<{}, { isSettingsExpanded
|
||||
PluginCommands.Layout.Update.dispatch(this.plugin, { state: { [p.name]: p.value } });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, e => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
setInteractivityProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
|
||||
PluginCommands.Interactivity.SetProps.dispatch(this.plugin, { props: { [p.name]: p.value } });
|
||||
}
|
||||
|
||||
this.subscribe(this.plugin.layout.events.updated, () => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.events.interactivity.propsUpdated, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
icon(name: string, onClick: (e: React.MouseEvent<HTMLButtonElement>) => void, title: string, isOn = true) {
|
||||
@@ -75,6 +76,9 @@ export class ViewportControls extends PluginUIComponent<{}, { isSettingsExpanded
|
||||
<ControlGroup header='Layout' initialExpanded={true}>
|
||||
<ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.state} onChange={this.setLayout} />
|
||||
</ControlGroup>
|
||||
<ControlGroup header='Interactivity' initialExpanded={true}>
|
||||
<ParameterControls params={Interactivity.Params} values={this.plugin.interactivity.props} onChange={this.setInteractivityProps} />
|
||||
</ControlGroup>
|
||||
<ControlGroup header='Viewport' initialExpanded={true}>
|
||||
<ParameterControls params={Canvas3DParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
|
||||
</ControlGroup>
|
||||
@@ -109,8 +113,8 @@ export class Viewport extends PluginUIComponent<{ }, ViewportState> {
|
||||
const canvas3d = this.plugin.canvas3d;
|
||||
this.subscribe(canvas3d.input.resize, this.handleResize);
|
||||
|
||||
this.subscribe(canvas3d.interaction.click, e => this.plugin.behaviors.canvas3d.click.next(e));
|
||||
this.subscribe(canvas3d.interaction.highlight, e => this.plugin.behaviors.canvas3d.highlight.next(e));
|
||||
this.subscribe(canvas3d.interaction.click, e => this.plugin.behaviors.interaction.click.next(e));
|
||||
this.subscribe(canvas3d.interaction.highlight, e => this.plugin.behaviors.interaction.highlight.next(e));
|
||||
this.subscribe(this.plugin.layout.events.updated, () => {
|
||||
setTimeout(this.handleResize, 50);
|
||||
});
|
||||
|
||||
199
src/mol-plugin/util/interactivity.ts
Normal file
199
src/mol-plugin/util/interactivity.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Loci as ModelLoci, EmptyLoci } from '../../mol-model/loci';
|
||||
import { ModifiersKeys, ButtonsType } from '../../mol-util/input/input-observer';
|
||||
import { Representation } from '../../mol-repr/representation';
|
||||
import { StructureElement, Link } from '../../mol-model/structure';
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
import { StructureElementSelectionManager } from './structure-element-selection';
|
||||
import { PluginContext } from '../context';
|
||||
import { StructureElement as SE, Structure } from '../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginCommands } from '../command';
|
||||
import { capitalize } from '../../mol-util/string';
|
||||
|
||||
export { Interactivity }
|
||||
|
||||
class Interactivity {
|
||||
readonly lociSelections: Interactivity.LociSelectionManager;
|
||||
readonly lociHighlights: Interactivity.LociHighlightManager;
|
||||
|
||||
private _props = PD.getDefaultValues(Interactivity.Params)
|
||||
|
||||
get props() { return { ...this._props } }
|
||||
setProps(props: Partial<Interactivity.Props>) {
|
||||
Object.assign(this._props, props)
|
||||
this.lociSelections.setProps(this._props)
|
||||
this.lociHighlights.setProps(this._props)
|
||||
}
|
||||
|
||||
constructor(readonly ctx: PluginContext, props: Partial<Interactivity.Props> = {}) {
|
||||
Object.assign(this._props, props)
|
||||
|
||||
this.lociSelections = new Interactivity.LociSelectionManager(ctx, this._props);
|
||||
this.lociHighlights = new Interactivity.LociHighlightManager(ctx, this._props);
|
||||
|
||||
PluginCommands.Interactivity.SetProps.subscribe(ctx, e => this.setProps(e.props));
|
||||
}
|
||||
}
|
||||
|
||||
namespace Interactivity {
|
||||
export interface Loci<T extends ModelLoci = ModelLoci> { loci: T, repr?: Representation.Any }
|
||||
|
||||
export namespace Loci {
|
||||
export function areEqual(a: Loci, b: Loci) {
|
||||
return a.repr === b.repr && ModelLoci.areEqual(a.loci, b.loci);
|
||||
}
|
||||
export const Empty: Loci = { loci: EmptyLoci };
|
||||
}
|
||||
|
||||
const Granularity = {
|
||||
'element': (loci: ModelLoci) => loci,
|
||||
'residue': (loci: ModelLoci) => SE.isLoci(loci) ? SE.Loci.extendToWholeResidues(loci) : loci,
|
||||
'chain': (loci: ModelLoci) => SE.isLoci(loci) ? SE.Loci.extendToWholeChains(loci) : loci,
|
||||
'structure': (loci: ModelLoci) => SE.isLoci(loci) ? Structure.Loci(loci.structure) : loci
|
||||
}
|
||||
type Granularity = keyof typeof Granularity
|
||||
const GranularityOptions = Object.keys(Granularity).map(n => [n, capitalize(n)]) as [Granularity, string][]
|
||||
|
||||
export const Params = {
|
||||
granularity: PD.Select('residue', GranularityOptions),
|
||||
}
|
||||
export type Props = PD.Values<typeof Params>
|
||||
|
||||
export interface HighlightEvent { current: Loci, modifiers?: ModifiersKeys }
|
||||
export interface ClickEvent { current: Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
|
||||
|
||||
export type LociMarkProvider = (loci: Loci, action: MarkerAction) => void
|
||||
|
||||
export abstract class LociMarkManager<MarkEvent extends any> {
|
||||
protected providers: LociMarkProvider[] = [];
|
||||
protected sel: StructureElementSelectionManager
|
||||
|
||||
readonly props: Readonly<Props> = PD.getDefaultValues(Params)
|
||||
|
||||
setProps(props: Partial<Props>) {
|
||||
Object.assign(this.props, props)
|
||||
}
|
||||
|
||||
addProvider(provider: LociMarkProvider) {
|
||||
this.providers.push(provider);
|
||||
}
|
||||
|
||||
removeProvider(provider: LociMarkProvider) {
|
||||
this.providers = this.providers.filter(p => p !== provider);
|
||||
// TODO clear, then re-apply remaining providers
|
||||
}
|
||||
|
||||
normalizedLoci(interactivityLoci: Loci) {
|
||||
let { loci, repr } = interactivityLoci
|
||||
if (this.props.granularity !== 'element' && Link.isLoci(loci)) {
|
||||
// convert Link.Loci to a StructureElement.Loci so granularity can be applied
|
||||
loci = Link.toStructureElementLoci(loci)
|
||||
}
|
||||
loci = Granularity[this.props.granularity](loci)
|
||||
if (StructureElement.isLoci(loci) && loci.structure.parent) {
|
||||
loci = StructureElement.Loci.remap(loci, loci.structure.parent)
|
||||
}
|
||||
return { loci, repr }
|
||||
}
|
||||
|
||||
protected mark(current: Loci<ModelLoci>, action: MarkerAction) {
|
||||
for (let p of this.providers) p(current, action);
|
||||
}
|
||||
|
||||
abstract apply(e: MarkEvent): void
|
||||
|
||||
constructor(public readonly ctx: PluginContext, props: Partial<Props> = {}) {
|
||||
this.sel = ctx.helpers.structureSelection
|
||||
this.setProps(props)
|
||||
}
|
||||
}
|
||||
|
||||
export class LociHighlightManager extends LociMarkManager<HighlightEvent> {
|
||||
private prev: Loci = { loci: EmptyLoci, repr: void 0 };
|
||||
|
||||
apply(e: HighlightEvent) {
|
||||
const { current, modifiers } = e
|
||||
|
||||
const normalized: Loci<ModelLoci> = this.normalizedLoci(current)
|
||||
if (StructureElement.isLoci(normalized.loci)) {
|
||||
let loci: StructureElement.Loci = normalized.loci;
|
||||
if (modifiers && modifiers.shift) {
|
||||
loci = this.sel.tryGetRange(loci) || loci;
|
||||
}
|
||||
|
||||
this.mark(this.prev, MarkerAction.RemoveHighlight);
|
||||
const toHighlight = { loci, repr: normalized.repr };
|
||||
this.mark(toHighlight, MarkerAction.Highlight);
|
||||
this.prev = toHighlight;
|
||||
} else {
|
||||
if (!Loci.areEqual(this.prev, normalized)) {
|
||||
this.mark(this.prev, MarkerAction.RemoveHighlight);
|
||||
this.mark(normalized, MarkerAction.Highlight);
|
||||
this.prev = normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(ctx: PluginContext, props: Partial<Props> = {}) {
|
||||
super(ctx, props)
|
||||
ctx.behaviors.interaction.highlight.subscribe(e => this.apply(e));
|
||||
}
|
||||
}
|
||||
|
||||
export class LociSelectionManager extends LociMarkManager<ClickEvent> {
|
||||
toggleSel(current: Loci<ModelLoci>) {
|
||||
if (this.sel.has(current.loci)) {
|
||||
this.sel.remove(current.loci);
|
||||
this.mark(current, MarkerAction.Deselect);
|
||||
} else {
|
||||
this.sel.add(current.loci);
|
||||
this.mark(current, MarkerAction.Select);
|
||||
}
|
||||
}
|
||||
|
||||
apply(e: ClickEvent) {
|
||||
const { current, buttons, modifiers } = e
|
||||
const normalized: Loci<ModelLoci> = this.normalizedLoci(current)
|
||||
if (normalized.loci.kind === 'empty-loci') {
|
||||
if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
|
||||
// clear the selection on Ctrl + Right-Click on empty
|
||||
const sels = this.sel.clear();
|
||||
for (const s of sels) this.mark({ loci: s }, MarkerAction.Deselect);
|
||||
}
|
||||
} else if (StructureElement.isLoci(normalized.loci)) {
|
||||
if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
|
||||
// select only the current element on Ctrl + Right-Click
|
||||
const old = this.sel.get(normalized.loci.structure);
|
||||
this.mark({ loci: old }, MarkerAction.Deselect);
|
||||
this.sel.set(normalized.loci);
|
||||
this.mark(normalized, MarkerAction.Select);
|
||||
} else if (modifiers.control && buttons === ButtonsType.Flag.Primary) {
|
||||
// toggle current element on Ctrl + Left-Click
|
||||
this.toggleSel(normalized as Representation.Loci<StructureElement.Loci>);
|
||||
} else if (modifiers.shift && buttons === ButtonsType.Flag.Primary) {
|
||||
// try to extend sequence on Shift + Left-Click
|
||||
let loci: StructureElement.Loci = normalized.loci;
|
||||
if (modifiers.shift) {
|
||||
loci = this.sel.tryGetRange(loci) || loci;
|
||||
}
|
||||
this.toggleSel({ loci, repr: normalized.repr });
|
||||
}
|
||||
} else {
|
||||
if (!ButtonsType.has(buttons, ButtonsType.Flag.Secondary)) return;
|
||||
for (let p of this.providers) p(normalized, MarkerAction.Toggle);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(ctx: PluginContext, props: Partial<Props> = {}) {
|
||||
super(ctx, props)
|
||||
ctx.behaviors.interaction.click.subscribe(e => this.apply(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,6 @@ export class LociLabelManager {
|
||||
}
|
||||
|
||||
constructor(public ctx: PluginContext) {
|
||||
ctx.behaviors.canvas3d.highlight.subscribe(ev => ctx.behaviors.labels.highlight.next({ entries: this.getInfo(ev.current) }));
|
||||
ctx.behaviors.interaction.highlight.subscribe(ev => ctx.behaviors.labels.highlight.next({ entries: this.getInfo(ev.current) }));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
@@ -29,25 +30,37 @@ class StructureElementSelectionManager {
|
||||
return this.entries.get(ref)!;
|
||||
}
|
||||
|
||||
add(loci: StructureElement.Loci): Loci {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (!entry) return EmptyLoci;
|
||||
entry.selection = StructureElement.Loci.union(entry.selection, loci);
|
||||
return entry.selection;
|
||||
add(loci: Loci): Loci {
|
||||
if (StructureElement.isLoci(loci)) {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (entry) {
|
||||
entry.selection = StructureElement.Loci.union(entry.selection, loci);
|
||||
return entry.selection;
|
||||
}
|
||||
}
|
||||
return EmptyLoci
|
||||
}
|
||||
|
||||
remove(loci: StructureElement.Loci): Loci {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (!entry) return EmptyLoci;
|
||||
entry.selection = StructureElement.Loci.subtract(entry.selection, loci);
|
||||
return entry.selection.elements.length === 0 ? EmptyLoci : entry.selection;
|
||||
remove(loci: Loci): Loci {
|
||||
if (StructureElement.isLoci(loci)) {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (entry) {
|
||||
entry.selection = StructureElement.Loci.subtract(entry.selection, loci);
|
||||
return entry.selection.elements.length === 0 ? EmptyLoci : entry.selection;
|
||||
}
|
||||
}
|
||||
return EmptyLoci
|
||||
}
|
||||
|
||||
set(loci: StructureElement.Loci): Loci {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (!entry) return EmptyLoci;
|
||||
entry.selection = loci;
|
||||
return entry.selection.elements.length === 0 ? EmptyLoci : entry.selection;
|
||||
set(loci: Loci): Loci {
|
||||
if (StructureElement.isLoci(loci)) {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (entry) {
|
||||
entry.selection = loci;
|
||||
return entry.selection.elements.length === 0 ? EmptyLoci : entry.selection;
|
||||
}
|
||||
}
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
clear() {
|
||||
@@ -69,13 +82,18 @@ class StructureElementSelectionManager {
|
||||
return entry.selection;
|
||||
}
|
||||
|
||||
has(loci: StructureElement.Loci) {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (!entry) return false;
|
||||
return StructureElement.Loci.areIntersecting(loci, entry.selection);
|
||||
has(loci: Loci) {
|
||||
if (StructureElement.isLoci(loci)) {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (entry) {
|
||||
return StructureElement.Loci.areIntersecting(loci, entry.selection);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
tryGetRange(loci: StructureElement.Loci): StructureElement.Loci | undefined {
|
||||
tryGetRange(loci: Loci): StructureElement.Loci | undefined {
|
||||
if (!StructureElement.isLoci(loci)) return;
|
||||
if (loci.elements.length !== 1) return;
|
||||
const entry = this.getEntry(loci.structure);
|
||||
if (!entry) return;
|
||||
@@ -90,31 +108,18 @@ class StructureElementSelectionManager {
|
||||
}
|
||||
if (!e) return;
|
||||
|
||||
let predIdx = OrderedSet.findPredecessorIndex(e.indices, OrderedSet.min(xs.indices));
|
||||
if (predIdx === 0) return;
|
||||
|
||||
let fst;
|
||||
|
||||
if (predIdx < OrderedSet.size(e.indices)) {
|
||||
fst = OrderedSet.getAt(e.indices, predIdx)
|
||||
if (fst > OrderedSet.min(xs.indices)) fst = OrderedSet.getAt(e.indices, predIdx - 1) + 1 as StructureElement.UnitIndex;
|
||||
} else {
|
||||
fst = OrderedSet.getAt(e.indices, predIdx - 1) + 1 as StructureElement.UnitIndex;
|
||||
}
|
||||
|
||||
return StructureElement.Loci(entry.selection.structure, [{
|
||||
unit: e.unit,
|
||||
indices: OrderedSet.ofRange(fst, OrderedSet.max(xs.indices))
|
||||
}]);
|
||||
return tryGetElementRange(entry.selection.structure, e, xs)
|
||||
}
|
||||
|
||||
private prevHighlight: StructureElement.Loci | undefined = void 0;
|
||||
|
||||
accumulateInteractiveHighlight(loci: StructureElement.Loci) {
|
||||
if (this.prevHighlight) {
|
||||
this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci);
|
||||
} else {
|
||||
this.prevHighlight = loci;
|
||||
accumulateInteractiveHighlight(loci: Loci) {
|
||||
if (StructureElement.isLoci(loci)) {
|
||||
if (this.prevHighlight) {
|
||||
this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci);
|
||||
} else {
|
||||
this.prevHighlight = loci;
|
||||
}
|
||||
}
|
||||
return this.prevHighlight;
|
||||
}
|
||||
@@ -162,8 +167,39 @@ function SelectionEntry(s: Structure): SelectionEntry {
|
||||
};
|
||||
}
|
||||
|
||||
/** remap `selection-entry` to be related to `structure` if possible */
|
||||
function remapSelectionEntry(e: SelectionEntry, s: Structure): SelectionEntry {
|
||||
return {
|
||||
selection: StructureElement.Loci.remap(e.selection, s)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes `ref` and `ext` belong to the same unit in the same structure
|
||||
*/
|
||||
function tryGetElementRange(structure: Structure, ref: StructureElement.Loci['elements'][0], ext: StructureElement.Loci['elements'][0]) {
|
||||
|
||||
const refMin = OrderedSet.min(ref.indices)
|
||||
const refMax = OrderedSet.max(ref.indices)
|
||||
const extMin = OrderedSet.min(ext.indices)
|
||||
const extMax = OrderedSet.max(ext.indices)
|
||||
|
||||
let min: number
|
||||
let max: number
|
||||
|
||||
if (refMax < extMin) {
|
||||
min = refMax + 1
|
||||
max = extMax
|
||||
} else if (extMax < refMin) {
|
||||
min = extMin
|
||||
max = refMin - 1
|
||||
} else {
|
||||
// TODO handle range overlap cases
|
||||
return
|
||||
}
|
||||
|
||||
return StructureElement.Loci(structure, [{
|
||||
unit: ref.unit,
|
||||
indices: OrderedSet.ofRange(min as StructureElement.UnitIndex, max as StructureElement.UnitIndex)
|
||||
}]);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { Subject } from 'rxjs';
|
||||
import { GraphicsRenderObject } from '../mol-gl/render-object';
|
||||
import { Task } from '../mol-task';
|
||||
import { PickingId } from '../mol-geo/geometry/picking';
|
||||
import { MarkerAction } from '../mol-geo/geometry/marker-data';
|
||||
import { MarkerAction } from '../mol-util/marker-action';
|
||||
import { Loci as ModelLoci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
|
||||
import { Overpaint } from '../mol-theme/overpaint';
|
||||
import { Transparency } from '../mol-theme/transparency';
|
||||
|
||||
@@ -14,7 +14,8 @@ import { LocationIterator } from '../../mol-geo/util/location-iterator';
|
||||
import { VisualUpdateState } from '../util';
|
||||
import { ShapeGroupColorTheme } from '../../mol-theme/color/shape-group';
|
||||
import { ShapeGroupSizeTheme } from '../../mol-theme/size/shape-group';
|
||||
import { createMarkers, MarkerAction } from '../../mol-geo/geometry/marker-data';
|
||||
import { createMarkers } from '../../mol-geo/geometry/marker-data';
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { createColors } from '../../mol-geo/geometry/color-data';
|
||||
import { createSizes } from '../../mol-geo/geometry/size-data';
|
||||
|
||||
@@ -15,7 +15,7 @@ import { createEmptyTheme, Theme } from '../../mol-theme/theme';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { MarkerAction } from '../../mol-geo/geometry/marker-data';
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
|
||||
export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number) => ComplexVisual<P>): StructureRepresentation<P> {
|
||||
let version = 0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user