1.先来看下目录结构
- .github
- bin
- browser
- build
- css
- docs
- img
- plugins
- sample_data
- src
- JBrowse
- perl5
- jdataview-1.1-patch.X.js
- lazyload.js
- tests
- utils
- website
- .editorconfig
- .eslintrc
- .gitattributes
- .gitignore // git配置文件
- .htaccess
- .npmignore
- travis.yml
- browserconfig.xml
- index.html // 项目入口页面
- INSTALL
- jbrowse_conf.json
- jbrowse.conf
- LICENSE
- Makefile.PL
- package.json
- README.md
- release.notes.md
- setup.sh
- site.webmanifest
- webpack.config.js
- yarn.lock
2.先看README.md
1. 初始化的时候要执行./setup.sh
2. 作为npm模块安装的时候要执行 .bin/jb_setup.js .bin/jb_run.js
3. tests是项目的测试模块
4. 执行build目录下的Makefile可以生成最小的代码库
5. 安装electron-packager包并执行Makeile release-electron-all可以生成electron app
6. 安装electron包,执行browser/main.js可以进electron的开发模式
3. 看package.json
"scripts": {
"test": "npm run test-puppeteer",
"test-all": "npm run build && npm run test-puppeteer && npm run test-perl && npm run test-electron && npm run test-selenium",
"test-puppeteer": "node tests/js_tests/run-puppeteer.js http://localhost:8082/tests/js_tests/index.html",
"test-electron": "ava --timeout=60s tests/electron_tests",
"test-perl": "prove -Isrc/perl5 -j8 -lr tests/perl_tests/",
"test-selenium": "SELENIUM_BROWSER=firefox JBROWSE_URL='http://localhost:8082/index.html' nosetests --verbose tests/selenium_tests/",
"start": "utils/jb_run.js -p 8082",
"watch": "webpack -w",
"build": "webpack",
"build-electron": "electron-packager . JBrowse --overwrite --icon=browser/icons/jbrowse",
"hint": "npm run lint",
"lint": "eslint src/JBrowse tests/js_tests/spec"
},
"bin": {
"jb_run.js": "utils/jb_run.js",
"jb_setup.js": "utils/jb_setup.js"
},
官网上说,你可以执行npm run start在本地开启一个express的服务器,但是每次修改了内容之后需要重新执行一次。也可以使用npm run watch来实现自动重启。
所以之后要留意一下jb_run.js和webpack配置文件。
4. 看webpack.config.js
摘一下比较重要的配置
entry: {
main: "src/JBrowse/main",
browser: "src/JBrowse/standalone"
},
plugins: [
new CleanWebpackPlugin(['dist']),
new DojoWebpackPlugin({
loaderConfig: require("./build/dojo-loader-config"),
environment: {
dojoRoot: process.env.JBROWSE_PUBLIC_PATH || "./dist/"
},
buildEnvironment: {
dojoRoot: "node_modules/"
},
locales: ["en"],
loader: path.resolve('./build/dojo-webpack-plugin-loader/dojo/dojo.js')
}),
new CopyWebpackPlugin([{
context: "node_modules",
from: "dojo/resources/blank.gif",
to: "dojo/resources"
}]),
],
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: process.env.JBROWSE_PUBLIC_PATH || 'dist/'
},
入口文件有两个src/JBrowse/main和src/JBrowse/standalone,输入为dist目录下的[name].bundle.js。
5.看下setup.sh
值得注意的代码:
# if we are running in a development build, then run npm install and run the webpack build.
if [ -f "src/JBrowse/Browser.js" ]; then
log_echo -n "Installing node.js dependencies and building with webpack ..."
(
set -e
check_node
[[ -f node_modules/.bin/yarn ]] || npm install yarn
node_modules/.bin/yarn install
JBROWSE_BUILD_MIN=$JBROWSE_BUILD_MIN node_modules/.bin/yarn build
) >>setup.log 2>&1;
done_message "" "" "FAILURE NOT ALLOWED"
else
log_echo "Minimal release, skipping node and Webpack build (note: this version will not allow using plugins. Use a github clone or a dev version of JBrowse to use plugins"
fi
根据src/JBrowse/Browser.js是否存在判断是否是开发版代码,如果是develop build,要安装node yarn然后执行yarn install yarn build。
然后会安装一些perl的依赖。
生成Volvox的实例数据:
# ...
# format volvox
rm -rf sample_data/json/volvox;
bin/prepare-refseqs.pl --fasta docs/tutorial/data_files/volvox.fa --out sample_data/json/volvox;
if [ $SUPPRESS_BIODB_TO_JSON -eq 1 ]; then
echo "Not running biodb-to-json.pl for Volvox";
else
bin/biodb-to-json.pl -v --conf docs/tutorial/conf_files/volvox.json --out sample_data/json/volvox;
fi
cat \
docs/tutorial/data_files/volvox_microarray.bw.conf \
docs/tutorial/data_files/volvox_sine.bw.conf \
docs/tutorial/data_files/volvox-sorted.bam.conf \
docs/tutorial/data_files/volvox-sorted.bam.coverage.conf \
docs/tutorial/data_files/volvox-paired.bam.conf \
docs/tutorial/data_files/volvox.vcf.conf \
docs/tutorial/data_files/volvox_fromconfig.conf \
docs/tutorial/data_files/volvox.gff3.conf \
docs/tutorial/data_files/volvox.gtf.conf \
docs/tutorial/data_files/volvox.sort.gff3.gz.conf \
docs/tutorial/data_files/volvox.sort.gff3.gz.htmlfeatures.conf \
docs/tutorial/data_files/volvox.sort.bed.gz.conf \
docs/tutorial/data_files/gvcf.vcf.gz.conf \
docs/tutorial/data_files/bookmarks.conf \
docs/tutorial/data_files/volvox.subsubparts.gff3.conf \
docs/tutorial/data_files/volvox-long-reads.fastq.sorted.bam.conf \
docs/tutorial/data_files/volvox-long-reads.fastq.sorted.cram.conf \
docs/tutorial/data_files/volvox.bb.conf \
docs/tutorial/data_files/volvox-sorted.cram.conf \
docs/tutorial/data_files/volvox-sv.bam.conf \
docs/tutorial/data_files/volvox-sv.cram.conf \
>> sample_data/json/volvox/tracks.conf
bin/add-json.pl '{ "dataset_id": "volvox", "include": [ "../../raw/volvox/functions.conf" ] }' sample_data/json/volvox/trackList.json
bin/add-json.pl '{ "dataset_id": "volvox", "plugins": [ "HideTrackLabels", "NeatCanvasFeatures", "NeatHTMLFeatures" ] }' sample_data/json/volvox/trackList.json
bin/flatfile-to-json.pl --bed docs/tutorial/data_files/volvox_segment.bed --out sample_data/json/volvox --trackLabel ChromHMM --trackType CanvasFeatures --clientConfig '{"color": "{chromHMM}", "strandArrow": false}' --config '{"displayMode": "collapsed", "enableCollapsedMouseover": true, "category": "Miscellaneous" }';
bin/generate-names.pl --safeMode -v --out sample_data/json/volvox;
mkdir -p sample_data/raw;
if [ ! -e sample_data/raw/volvox ]; then
ln -s ../../docs/tutorial/data_files sample_data/raw/volvox;
fi;
ln -sf ../../docs/tutorial/conf_files/volvox.json sample_data/raw/;
touch sample_data/json/volvox/successfully_run;
上面的代码总结下来有这么几步:
1.使用perl脚本(bin/prepare-refseqs.pl, biodb-to-json.pl)处理.fa .json文件,生成初始化实例数据
2.生成配置数据tracks.conf
3.使用perl脚本(add-json.pl)生成trackList.json,还有个bin/flatfile-to-json.pl不清楚是什么作用
生成yeast示例数据的过程类似。
6. 看下webpack中的两个入口文件src/JBrowse/main和src/JBrowse/standalone
main.js:
require([
'JBrowse/Browser',
'JBrowse/Util',
'JBrowse/QueryParamConfigMapper',
'dojo/io-query',
'dojo/json',
'css!../../css/genome.scss',
],
function (
Browser,
Util,
QueryParamConfigMapper,
ioQuery,
JSON,
) {
// 根据url的参数来生成一个queryParams
var queryParams = ioQuery.queryToObject( window.location.search.slice(1) );
// 声明一个全局配置变量参数config,这个参数用来生成全局jbrowse
var config = {
containerID: "GenomeBrowser",
...
};
// 这里使用的是dojo,一个DHTML框架?
dojo.addOnLoad(() => {
// 这里依然是对config进行一下参数配置
if( queryParams.addFeatures ) {
config.stores.url.features = JSON.parse( queryParams.addFeatures );
}
...
QueryParamConfigMapper().handleQueryParams(config,queryParams);
// 创建一个包含JBrowse实例的JBrowse全局变量
window.JBrowse = new Browser( config );
window.JBrowse.afterMilestone('loadRefSeqs', function() { dojo.destroy(dojo.byId('LoadingScreen')); });
})
});
这个项目应该是使用dojo作为基础框架,去查了一下是个比较早的框架,项目也使用了dojo配套的叫做dijit的widget组件库。
而且看到这里创建了一个Browser的实例,命名为JBrowse挂在windows下。这个实例生成了在浏览器中显示的数据图形页面。所以之后我们要去看下Browser的构造函数是如何实现的。
standalone.js:
import 'babel-polyfill'
require([
'JBrowse/Browser',
'css!../../css/genome.scss',
], function (Browser) {
window.Browser = Browser;
});
7. 看下src/JBrowse/Browse.js
我们先将src/JBrowse/目录下的文件夹和重要文件展示出来:
- src
- JBrowse
- ConfigAdaptor
- Digest
- Model
- Store
- Util
- View
- .eslintrc
- BehaviorManager.js
- Browser.js // jbrowse前端页面的主文件
- CodonTable.js
- Component.js
- ConfigManager.js
- Errors.js
- FeatureFiltererMixin.js
- Finisher.js
- GenomeView.js
- has.js
- main.js
- package.json
- Plugin.js
- QueryParamConfigMapper.js
- standalone.js
- Store.js
- TouchScreenSupport.js
- Track.js
- Util.js
再看Browse.js文件,Browse.js有3471行,是jbrowser项目前端页面的构造函数,使用了dojo框架搭配widget组件以AMD模块加载方式开发。
重要代码:
define([
'dojo/_base/declare',
'dojo/_base/lang',
'dojo/on',
'dojo/html',
'dojo/query',
'dojo/dom-construct',
'dojo/keys',
'dojo/Deferred',
'dojo/DeferredList',
'dojo/topic',
'dojo/aspect',
'dojo/request',
'dojo/io-query',
'JBrowse/has',
'dojo/_base/array',
'dijit/layout/ContentPane',
'dijit/layout/BorderContainer',
'dijit/Dialog',
'dijit/form/ComboBox',
'dojo/store/Memory',
'dijit/form/Button',
'dijit/form/Select',
'dijit/form/ToggleButton',
'dijit/form/DropDownButton',
'dijit/DropDownMenu',
'dijit/CheckedMenuItem',
'dijit/MenuItem',
'dijit/MenuSeparator',
'dojox/form/TriStateCheckBox',
'dojox/html/entities',
'JBrowse/Util',
'JBrowse/Store/LazyTrie',
'JBrowse/Store/Names/LazyTrieDojoData',
'dojo/store/DataStore',
'JBrowse/FeatureFiltererMixin',
'JBrowse/GenomeView',
'JBrowse/TouchScreenSupport',
'JBrowse/ConfigManager',
'JBrowse/View/InfoDialog',
'JBrowse/View/FileDialog',
'JBrowse/View/FastaFileDialog',
'JBrowse/Model/Location',
'JBrowse/View/LocationChoiceDialog',
'JBrowse/View/Dialog/SetHighlight',
'JBrowse/View/Dialog/Preferences',
'JBrowse/View/Dialog/OpenDirectory',
'JBrowse/View/Dialog/SetTrackHeight',
'JBrowse/View/Dialog/QuickHelp',
'JBrowse/View/StandaloneDatasetList',
'JBrowse/Store/SeqFeature/ChromSizes',
'JBrowse/Store/SeqFeature/UnindexedFasta',
'JBrowse/Store/SeqFeature/IndexedFasta',
'JBrowse/Store/SeqFeature/BgzipIndexedFasta',
'JBrowse/Store/SeqFeature/TwoBit',
'dijit/focus',
'../lazyload.js', // for dynamic CSS loading
// extras for webpack
'dojox/data/CsvStore',
'dojox/data/JsonRestStore'
], function(
declare,
lang,
on,
html,
query,
domConstruct,
keys,
Deferred,
DeferredList,
topic,
aspect,
request,
ioQuery,
has,
array,
dijitContentPane,
dijitBorderContainer,
dijitDialog,
dijitComboBox,
dojoMemoryStore,
dijitButton,
dijitSelectBox,
dijitToggleButton,
dijitDropDownButton,
dijitDropDownMenu,
dijitCheckedMenuItem,
dijitMenuItem,
dijitMenuSeparator,
dojoxTriStateCheckBox,
dojoxHtmlEntities,
Util,
LazyTrie,
NamesLazyTrieDojoDataStore,
DojoDataStore,
FeatureFiltererMixin,
GenomeView,
Touch,
ConfigManager,
InfoDialog,
FileDialog,
FastaFileDialog,
Location,
LocationChoiceDialog,
SetHighlightDialog,
PreferencesDialog,
OpenDirectoryDialog,
SetTrackHeightDialog,
HelpDialog,
StandaloneDatasetList,
ChromSizes,
UnindexedFasta,
IndexedFasta,
BgzipIndexedFasta,
TwoBit,
dijitFocus,
LazyLoad
){
return declare(FeatureFiltererMixin, {
constructor: function(){},
_initialLocation: function(){},
...
initPlugins: function(){},
initView: function(){},
...
})
})
然后这里的实现已经非常接近页面的具体细节了,现在我们希望的是如何将页面每个部分的具体模块与代码对应上,方便理解开发模式已经将来对页面自己需要的地方进行修改。不妨单独把constructor函数单独拿出来分析,不重要的代码直接省略了:
constructor: function(params) {
// 新建Browser实例时,传入的参数作为配置数据
this.config = params || {};
// 如果是在执行单元测试,代码会在这里停止
if( this.config.unitTestMode ) return;
// 外部应用初始化时的钩子,可以在index.html中设置
if (typeof this.config.initExtra === 'function')
this.config.initExtra(this,params);
this.startTime = new Date();
// 开始初始化
var thisB = this;
// dojo 初始化
dojo.addOnLoad( function() {
// util工具函数的集合,判断如果是electron应用
if(Util.isElectron() && !thisB.config.dataRoot) {
dojo.addClass(document.body, "jbrowse");
dojo.addClass(document.body, thisB.config.theme || "tundra");
thisB.welcomeScreen(document.body);
return;
}
// loadConfig函数,根据配置数据加载所有的文件
thisB.loadConfig().then( function() {
// 初始化container
thisB.container = dojo.byId( thisB.config.containerID );
thisB.container.onselectstart = function() { return false; };
// 如果在配置中设置了突出显示,则初始化突出显示
if( thisB.config.initialHighlight && thisB.config.initialHighlight != "/" ) {
thisB.setHighlight( new Location( thisB.config.initialHighlight ) );
}
// 初始化所有在config中定义的插件
thisB.initPlugins().then( function() {
thisB.loadNames();
thisB.loadUserCSS().then( function() {
// 初始化异步获取的track元数据
thisB.initTrackMetadata();
thisB.loadRefSeqs().then( function() {
// 计算出初始化的坐标
var initialLocString = thisB._initialLocation();
// util工具中提供的parseLocString()函数
var initialLoc = Util.parseLocString( initialLocString );
if (initialLoc && initialLoc.ref && thisB.allRefs[initialLoc.ref]) {
thisB.refSeq = thisB.allRefs[initialLoc.ref];
}
// 确认container的宽和高非零
thisB.ensureNonzeroContainerDimensions()
// 初始化试图
thisB.initView().then( function() {
Touch.loadTouch(); // 初始化对触屏设备的支持
if( initialLocString ) {
thisB.navigateTo( initialLocString, true );
}
// 计算出我们使用的初始化track list
// tracksToShow就是我们将要绘制的图形的参数信息
var tracksToShow = [];
// 总是添加始终轨道,不管任何其他轨道参数
if (thisB.config.alwaysOnTracks) {
tracksToShow = tracksToShow.concat(thisB.config.alwaysOnTracks.split(","));
}
// 在URL track参数中添加tracks指定的规则
if (thisB.config.forceTracks) {
tracksToShow = tracksToShow.concat(thisB.config.forceTracks.split(","));
} else if (thisB.cookie("tracks")) {
// 如果没有URL track参数,通过tracks cookie添加上次查看的tracks
tracksToShow = tracksToShow.concat(thisB.cookie("tracks").split(","));
} else if (thisB.config.defaultTracks) {
// 如果没有URL参数和也没有tracks cookie,那么使用defaultTracks
// 在少数情况下defaultTracks已经包括了一个数组,在先前的函数中已经被分割调用了。
// 所以加入他没有分割的话 我们只能分割尝试
if (!(thisB.config.defaultTracks instanceof Array)) {
tracksToShow = tracksToShow.concat(thisB.config.defaultTracks.split(","));
}
}
// 目前只能在没有其他要显示的指南下,才会显示DNA
// 这里也可以改为强制迫使DNA显示
if (tracksToShow.length == 0) { tracksToShow.push("DNA"); }
tracksToShow = Util.uniq(tracksToShow);
//消除轨道重复(可能同时在alwaysOnTracks和defaultTracks中指定)
thisB.showTracks( tracksToShow );
thisB.passMilestone( 'completely initialized', { success: true } );
});
thisB.reportUsageStats();
});
});
});
});
});
},
上面的代码可以看到,constructor构造函数使用了很多定义在this上的方法。先根据配置文件加载所有需要的文件,然后初始化视图框,计算出初始化坐标的位置,判断视图框的宽和高不为零。初始化视图view,定义一个空数组tracksToShow作为要展示的数据,根据url,cookie等把不同的值赋给tracksToShow,最后将数据绘制出来。
但是很明显的可以看到,现在只能对图形初始化做一个大概的了解,生成的数据来源,如何初始化视图框之类的细节还需要找到对应的函数中了解。
8. jbrowse加载数组的方式
在src/JBrowse/Model文件夹中有有个异步获取文件的js文件XHRBlob.js。这个文件应该是jbrowse异步获取数据的关键文件。在src下搜索XHRBlob,发现src/JBrowse/Store/SeqFeature里几乎每个以文件格式命名的文件都引用了XHRBlob.js。那么SeqFeature里面是什么文件呢,来看下该文件的文件目录(只摘去部分):
- src
- JBrowse
- Store
- SeqFeature
- BED
- BigWig
- GTF
- UnindexedFasta
- BAM.js
- BED.js
- BEDTabix.js
- BigWig.js
- CRAM.js
- GFF3.js
- GTF.js
...
我们从上面的目录可以得出一个结论,这个目录下是对应着不同文件格式数据的处理代码,不同的js处理不同格式的数据,其中处理步骤包括获取相应格式的数据。所以jbrose是确定本次要展示的是哪一种数据然后调用对应的js文件再进行处理的。