mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-04 07:28:12 +08:00
initial commit
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# coverage
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# production
|
||||||
|
build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
npm-debug.log
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
data
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
language: node_js
|
||||||
|
|
||||||
|
node_js:
|
||||||
|
- 4
|
||||||
|
- 5
|
||||||
|
- 6
|
||||||
|
|
||||||
|
# Use container-based Travis infrastructure.
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- /^greenkeeper-.*$/
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
on_success: change
|
||||||
|
on_failure: always
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- npm install -g npm@3
|
||||||
|
- export DISPLAY=:99.0
|
||||||
|
- sh -e /etc/init.d/xvfb start
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm run lint
|
||||||
|
- npm run test
|
||||||
|
- cat coverage/lcov.info | node_modules/.bin/coveralls || echo "Coveralls upload failed"
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#cellxgene
|
||||||
|
|
||||||
|
##Quickstart:
|
||||||
|
|
||||||
|
* `npm install`
|
||||||
|
* `npm run dev`
|
||||||
|
* `localhost:3000`
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
babelrc: false,
|
||||||
|
cacheDirectory: true,
|
||||||
|
presets: [
|
||||||
|
[ 'es2015', { loose: true, modules: false } ],
|
||||||
|
'stage-0',
|
||||||
|
'react'
|
||||||
|
],
|
||||||
|
plugins: [ 'react-hot-loader/babel' ]
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
module.exports = {
|
||||||
|
babelrc: false,
|
||||||
|
presets: [
|
||||||
|
[ 'es2015', { loose: true, modules: false } ],
|
||||||
|
'stage-0',
|
||||||
|
'react'
|
||||||
|
],
|
||||||
|
plugins: [ 'babel-plugin-transform-react-constant-elements' ]
|
||||||
|
.map(require.resolve)
|
||||||
|
.concat([ [ require.resolve('babel-plugin-transform-runtime') ] ])
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
babelrc: false,
|
||||||
|
presets: [
|
||||||
|
'babel-preset-es2015',
|
||||||
|
'babel-preset-stage-0',
|
||||||
|
'babel-preset-react'
|
||||||
|
],
|
||||||
|
plugins: [ 'istanbul' ]
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
extends: 'formidable/configurations/es6-react',
|
||||||
|
env: { browser: true, commonjs: true, es6: true, node: true, mocha: true },
|
||||||
|
globals: { expect: true },
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 6,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
generators: true,
|
||||||
|
experimentalObjectRestSpread: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
quotes: [ 2, 'single', { allowTemplateLiterals: true } ],
|
||||||
|
'no-magic-numbers': 'off',
|
||||||
|
'func-style': 'off',
|
||||||
|
'arrow-parens': 'off',
|
||||||
|
'no-use-before-define': 'off',
|
||||||
|
'react/jsx-filename-extension': 'off',
|
||||||
|
'react/require-extension': 'off',
|
||||||
|
'react/no-multi-comp': 'warn',
|
||||||
|
'react/prop-types': 'warn',
|
||||||
|
'react/sort-comp': 'warn',
|
||||||
|
'react/sort-prop-types': 'warn'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
if (typeof Promise === 'undefined') {
|
||||||
|
require('promise/lib/rejection-tracking').enable();
|
||||||
|
window.Promise = require('promise/lib/es6-extensions.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
require('whatwg-fetch');
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const autoprefixer = require('autoprefixer');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
|
const src = path.resolve('src');
|
||||||
|
const nodeModules = path.resolve('node_modules');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
devtool: 'eval',
|
||||||
|
entry: [
|
||||||
|
'webpack-hot-middleware/client?quiet=true&noInfo=true',
|
||||||
|
require.resolve('react-hot-loader/patch'),
|
||||||
|
require.resolve('../polyfills/polyfills'),
|
||||||
|
path.join(src, 'index')
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
path: path.resolve('build'),
|
||||||
|
pathinfo: true,
|
||||||
|
filename: 'static/js/bundle.js',
|
||||||
|
publicPath: '/'
|
||||||
|
},
|
||||||
|
resolve: { extensions: [ '.js', '.json' ] },
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
include: src,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
query: require('../babel/babel.dev')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
include: [ src, nodeModules ],
|
||||||
|
loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader'
|
||||||
|
},
|
||||||
|
{ test: /\.json$/, include: [ src, nodeModules ], loader: 'json-loader' },
|
||||||
|
{
|
||||||
|
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?$/,
|
||||||
|
include: [ src, nodeModules ],
|
||||||
|
loader: 'file-loader',
|
||||||
|
query: { name: 'static/media/[name].[ext]' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(mp4|webm)(\?.*)?$/,
|
||||||
|
include: [ src, nodeModules ],
|
||||||
|
loader: 'url-loader',
|
||||||
|
query: { limit: 10000, name: 'static/media/[name].[ext]' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
inject: true,
|
||||||
|
template: path.resolve('index.html'),
|
||||||
|
favicon: path.resolve('favicon.png')
|
||||||
|
}),
|
||||||
|
new webpack.LoaderOptionsPlugin({
|
||||||
|
options: {
|
||||||
|
eslint: {
|
||||||
|
configFile: path.resolve('./configuration/eslint/eslint.js'),
|
||||||
|
useEslintrc: false
|
||||||
|
},
|
||||||
|
postcss() {
|
||||||
|
return [ autoprefixer ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }),
|
||||||
|
// Note: only CSS is currently hot reloaded
|
||||||
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
new webpack.NoEmitOnErrorsPlugin()
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const autoprefixer = require('autoprefixer');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const HtmlWebpackInlineSourcePlugin = require(
|
||||||
|
'html-webpack-inline-source-plugin'
|
||||||
|
);
|
||||||
|
|
||||||
|
const src = path.resolve('src');
|
||||||
|
const nodeModules = path.resolve('node_modules');
|
||||||
|
|
||||||
|
const publicPath = '/';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bail: true,
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: [ require.resolve('../polyfills/polyfills'), path.join(src, 'index') ],
|
||||||
|
output: {
|
||||||
|
path: path.resolve('build'),
|
||||||
|
filename: 'static/js/[name].[chunkhash:8].js',
|
||||||
|
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
|
||||||
|
publicPath
|
||||||
|
},
|
||||||
|
resolve: { extensions: [ '.js', '.json' ] },
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
include: src,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
query: require('../babel/babel.prod')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
include: [ src, nodeModules ],
|
||||||
|
loader: ExtractTextPlugin.extract({
|
||||||
|
fallbackLoader: 'style-loader',
|
||||||
|
loader: 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]-autoprefixer!postcss-loader'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.json$/,
|
||||||
|
include: [ src, nodeModules ],
|
||||||
|
loader: 'json-loader',
|
||||||
|
exclude: /manifest.json$/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?$/,
|
||||||
|
include: [ src, nodeModules ],
|
||||||
|
loader: 'file-loader',
|
||||||
|
query: { name: 'static/media/[name].[hash:8].[ext]' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(mp4|webm)(\?.*)?$/,
|
||||||
|
include: [ src, nodeModules ],
|
||||||
|
loader: 'url-loader',
|
||||||
|
query: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
inject: 'body',
|
||||||
|
template: path.resolve('index.html'),
|
||||||
|
favicon: path.resolve('favicon.png'),
|
||||||
|
inlineSource: '.(js|css)$',
|
||||||
|
minify: {
|
||||||
|
removeComments: true,
|
||||||
|
collapseWhitespace: true,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
useShortDoctype: true,
|
||||||
|
removeEmptyAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
keepClosingSlash: true,
|
||||||
|
minifyJS: true,
|
||||||
|
minifyCSS: true,
|
||||||
|
minifyURLs: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new HtmlWebpackInlineSourcePlugin(),
|
||||||
|
new webpack.LoaderOptionsPlugin({
|
||||||
|
options: {
|
||||||
|
eslint: {
|
||||||
|
configFile: path.resolve('./configuration/eslint/eslint.js'),
|
||||||
|
useEslintrc: false
|
||||||
|
},
|
||||||
|
postcss() {
|
||||||
|
return [ autoprefixer ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }),
|
||||||
|
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||||
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
|
compress: { screw_ie8: true, warnings: false },
|
||||||
|
mangle: { screw_ie8: true },
|
||||||
|
output: { comments: false, screw_ie8: true }
|
||||||
|
}),
|
||||||
|
new ExtractTextPlugin('static/css/[name].[contenthash:8].css'),
|
||||||
|
new CopyWebpackPlugin([
|
||||||
|
{ from: 'public' },
|
||||||
|
{ from: 'manifest.webmanifest' }
|
||||||
|
]),
|
||||||
|
new SWPrecacheWebpackPlugin({
|
||||||
|
cacheId: 'formidable-react-starter',
|
||||||
|
filename: 'service-worker.js'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
+15
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="theme-color" content="#db5945">
|
||||||
|
<title>Cellx</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript>
|
||||||
|
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"name": "cellxgene",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "https://github.com/FormidableLabs/formidable-react-starter",
|
||||||
|
"scripts": {
|
||||||
|
"build": "npm run clean && webpack --config configuration/webpack/webpack.config.prod.js",
|
||||||
|
"clean": "rimraf build",
|
||||||
|
"dev": "node server/development.js",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"prod": "cross-env NODE_ENV=production PORT=3000 node server/production.js",
|
||||||
|
"test": "nyc --reporter=lcov --reporter=text mocha test/.setup.js test/**/*.spec.js"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=3.0.0"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "./configuration/eslint/eslint.js"
|
||||||
|
},
|
||||||
|
"nyc": {
|
||||||
|
"sourceMap": false,
|
||||||
|
"instrument": false
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"d3": "^4.10.0",
|
||||||
|
"express": "^4.14.0",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"react": "^15.3.0",
|
||||||
|
"react-dom": "^15.3.0",
|
||||||
|
"react-helmet": "^4.0.0",
|
||||||
|
"react-hot-loader": "^3.0.0-beta.2",
|
||||||
|
"react-router-dom": "4.1.2",
|
||||||
|
"serve-favicon": "^2.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^6.4.0",
|
||||||
|
"babel-core": "^6.13.2",
|
||||||
|
"babel-eslint": "^7.1.1",
|
||||||
|
"babel-loader": "^6.2.4",
|
||||||
|
"babel-plugin-istanbul": "^3.1.2",
|
||||||
|
"babel-plugin-transform-react-constant-elements": "^6.9.1",
|
||||||
|
"babel-plugin-transform-runtime": "^6.12.0",
|
||||||
|
"babel-preset-es2015": "^6.13.2",
|
||||||
|
"babel-preset-react": "^6.11.1",
|
||||||
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
|
"babel-register": "^6.11.6",
|
||||||
|
"babel-runtime": "^6.11.6",
|
||||||
|
"chai": "^3.5.0",
|
||||||
|
"chalk": "^1.1.3",
|
||||||
|
"connect-history-api-fallback": "^1.3.0",
|
||||||
|
"copy-webpack-plugin": "^4.0.1",
|
||||||
|
"coveralls": "^2.11.12",
|
||||||
|
"cross-env": "^3.1.4",
|
||||||
|
"css-loader": "^0.26.1",
|
||||||
|
"css-modules-require-hook": "^4.0.1",
|
||||||
|
"enzyme": "^2.4.1",
|
||||||
|
"eslint": "^2.10.2",
|
||||||
|
"eslint-config-formidable": "^2.0.1",
|
||||||
|
"eslint-loader": "^1.5.0",
|
||||||
|
"eslint-plugin-filenames": "^1.1.0",
|
||||||
|
"eslint-plugin-import": "^2.2.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^3.0.2",
|
||||||
|
"eslint-plugin-react": "^6.0.0",
|
||||||
|
"extract-text-webpack-plugin": "^2.0.0-beta.3",
|
||||||
|
"file-loader": "^0.9.0",
|
||||||
|
"filesize": "^3.3.0",
|
||||||
|
"gzip-size": "^3.0.0",
|
||||||
|
"html-webpack-inline-source-plugin": "0.0.6",
|
||||||
|
"html-webpack-plugin": "^2.22.0",
|
||||||
|
"jsdom": "^9.4.1",
|
||||||
|
"json-loader": "^0.5.4",
|
||||||
|
"mocha": "^3.0.2",
|
||||||
|
"nyc": "^10.0.0",
|
||||||
|
"postcss-loader": "^1.2.2",
|
||||||
|
"promise": "^7.1.1",
|
||||||
|
"react-addons-test-utils": "^15.3.0",
|
||||||
|
"redbox-react": "^1.3.0",
|
||||||
|
"rimraf": "^2.5.4",
|
||||||
|
"sinon": "^1.17.5",
|
||||||
|
"sinon-chai": "^2.8.0",
|
||||||
|
"style-loader": "^0.13.1",
|
||||||
|
"sw-precache-webpack-plugin": "^0.7.1",
|
||||||
|
"url-loader": "^0.5.7",
|
||||||
|
"web-app-manifest-loader": "^0.1.1",
|
||||||
|
"webpack": "^2.2.0-rc.4",
|
||||||
|
"webpack-dev-middleware": "^1.6.1",
|
||||||
|
"webpack-hot-middleware": "^2.12.2",
|
||||||
|
"whatwg-fetch": "^2.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
var path = require('path');
|
||||||
|
var historyApiFallback = require('connect-history-api-fallback');
|
||||||
|
var chalk = require('chalk');
|
||||||
|
var express = require('express');
|
||||||
|
var favicon = require('serve-favicon');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
var config = require('../configuration/webpack/webpack.config.dev');
|
||||||
|
var utils = require('./utils');
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
|
||||||
|
var PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Set up compiler
|
||||||
|
var compiler = webpack(config);
|
||||||
|
|
||||||
|
compiler.plugin('invalid', () => {
|
||||||
|
utils.clearConsole();
|
||||||
|
console.log('Compiling...');
|
||||||
|
});
|
||||||
|
|
||||||
|
compiler.plugin('done', stats => {
|
||||||
|
utils.formatStats(stats, PORT);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Launch server
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
app.use(historyApiFallback({ verbose: false }));
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
require('webpack-dev-middleware')(compiler, {
|
||||||
|
noInfo: true,
|
||||||
|
publicPath: config.output.publicPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(require('webpack-hot-middleware')(compiler));
|
||||||
|
|
||||||
|
app.use(favicon('./favicon.png'));
|
||||||
|
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.sendFile(path.resolve('index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, err => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.clearConsole();
|
||||||
|
console.log(chalk.cyan('Starting the development server...'));
|
||||||
|
console.log();
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
var chalk = require('chalk');
|
||||||
|
var express = require('express');
|
||||||
|
var favicon = require('serve-favicon');
|
||||||
|
var utils = require('./utils');
|
||||||
|
|
||||||
|
var PORT = process.env.PORT || 80;
|
||||||
|
|
||||||
|
// Launch server
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
app.use(express.static('./build'));
|
||||||
|
|
||||||
|
app.use(favicon('./build/favicon.png'));
|
||||||
|
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.sendFile(path.resolve('./build/index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, err => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.clearConsole();
|
||||||
|
console.log(chalk.cyan('Production server started on port ' + PORT));
|
||||||
|
console.log();
|
||||||
|
});
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
var chalk = require('chalk');
|
||||||
|
|
||||||
|
var friendlySyntaxErrorLabel = 'Syntax error:';
|
||||||
|
|
||||||
|
function isLikelyASyntaxError(message) {
|
||||||
|
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMessage(message) {
|
||||||
|
return message
|
||||||
|
.replace('Module build failed: SyntaxError:', friendlySyntaxErrorLabel)
|
||||||
|
.replace(
|
||||||
|
/Module not found: Error: Cannot resolve 'file' or 'directory'/,
|
||||||
|
'Module not found:'
|
||||||
|
)
|
||||||
|
.replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, '')
|
||||||
|
.replace('./~/css-loader!./~/postcss-loader!', '');
|
||||||
|
}
|
||||||
|
var clearConsole = () => {
|
||||||
|
process.stdout.write('\x1bc');
|
||||||
|
};
|
||||||
|
|
||||||
|
var formatStats = (stats, port) => {
|
||||||
|
clearConsole();
|
||||||
|
var hasErrors = stats.hasErrors();
|
||||||
|
var hasWarnings = stats.hasWarnings();
|
||||||
|
if (!hasErrors && !hasWarnings) {
|
||||||
|
console.log(chalk.green('Compiled successfully!'));
|
||||||
|
console.log();
|
||||||
|
console.log('The app is running at http://localhost:' + port + '/');
|
||||||
|
console.log();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = stats.toJson();
|
||||||
|
var formattedErrors = json.errors.map(
|
||||||
|
message => 'Error in ' + formatMessage(message)
|
||||||
|
);
|
||||||
|
var formattedWarnings = json.warnings.map(
|
||||||
|
message => 'Warning in ' + formatMessage(message)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasErrors) {
|
||||||
|
console.log(chalk.red('Failed to compile.'));
|
||||||
|
console.log();
|
||||||
|
if (formattedErrors.some(isLikelyASyntaxError)) {
|
||||||
|
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
|
||||||
|
}
|
||||||
|
formattedErrors.forEach(message => {
|
||||||
|
console.log(message);
|
||||||
|
console.log();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasWarnings) {
|
||||||
|
console.log(chalk.yellow('Compiled with warnings.'));
|
||||||
|
console.log();
|
||||||
|
formattedWarnings.forEach(message => {
|
||||||
|
console.log(message);
|
||||||
|
console.log();
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('You may use special comments to disable some warnings.');
|
||||||
|
console.log(
|
||||||
|
'Use ' +
|
||||||
|
chalk.yellow('// eslint-disable-next-line') +
|
||||||
|
' to ignore the next line.'
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'Use ' +
|
||||||
|
chalk.yellow('/* eslint-disable */') +
|
||||||
|
' to ignore all warnings in a file.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { formatStats: formatStats, clearConsole: clearConsole };
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
|
import cells from "../../../data/GBM_metadata.js";
|
||||||
|
import createCategoryCounts from "./createCategoryCounts";
|
||||||
|
|
||||||
|
const Button = ({category, c, i}) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
border: i === 0 ? "none" : `1px solid ${globals.lightgrey}`,
|
||||||
|
background: i === 0 ? globals.hcaBlue : "none",
|
||||||
|
color: i === 0 ? "white" : "black",
|
||||||
|
padding: "7px 10px",
|
||||||
|
marginRight: 20,
|
||||||
|
borderRadius: 3,
|
||||||
|
cursor: "pointer"
|
||||||
|
}}>
|
||||||
|
{c}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontWeight: i === 0 ? globals.bolder : "auto",
|
||||||
|
background: i === 0 ? "white" : "none",
|
||||||
|
color: "black",
|
||||||
|
marginLeft: 10,
|
||||||
|
padding: "2px 4px",
|
||||||
|
borderRadius: 3
|
||||||
|
}}>
|
||||||
|
{category[c]}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Category = ({category, title}) => (
|
||||||
|
<div>
|
||||||
|
<p>{title}</p>
|
||||||
|
{ _.map(Object.keys(category), (c, i) => <Button key={i} category={category} c={c} i={i} />) }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Categorical = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Categorical Metadata</h3>
|
||||||
|
{
|
||||||
|
_.map(createCategoryCounts(cells),
|
||||||
|
(value, key) => {
|
||||||
|
return <Category key={key} title={key} category={value} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Categorical;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
[on off] toggle hide deselected filters (shows a menu vs shows what you ordered in compact/narrative form. fold out animation.)
|
||||||
|
|
||||||
|
<p> <button> Field name [initial state] [create 2d graph] [explain part of 2d graph] </button> </p>
|
||||||
|
<p> <button> Field name [initial state] [create hypothesis] [validate hypothesis] </button> </p>
|
||||||
|
<p> <button> Field name [total] [selected in current filters] [selected in graph selection] </button> </p>
|
||||||
|
<p> <button> Tumor [400] [40] [4] </button> </p>
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import * as globals from "../../globals.js"
|
||||||
|
|
||||||
|
/*
|
||||||
|
end result:
|
||||||
|
|
||||||
|
counts: {
|
||||||
|
Sample.type: {
|
||||||
|
glioblastoma: 3452
|
||||||
|
},
|
||||||
|
Selection: {
|
||||||
|
foo: 234,
|
||||||
|
bar: 21
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const categories = globals.categories;
|
||||||
|
|
||||||
|
const createCategoryCounts = (cells) => {
|
||||||
|
|
||||||
|
/* instantiate counts obj */
|
||||||
|
const counts = {};
|
||||||
|
|
||||||
|
categories.forEach((category) => {
|
||||||
|
counts[category] = {};
|
||||||
|
})
|
||||||
|
|
||||||
|
cells.forEach((cell) => {
|
||||||
|
categories.forEach((category) => {
|
||||||
|
|
||||||
|
/* if, for the given category (ie., categories.Location), we do not have ie., glioblastoma already, create it. Otherwise increment it. */
|
||||||
|
if (!counts[category][cell[category]]) {
|
||||||
|
counts[category][cell[category]] = 1;
|
||||||
|
} else {
|
||||||
|
counts[category][cell[category]]++
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createCategoryCounts;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: auto;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styles from './container.css';
|
||||||
|
|
||||||
|
const Container = props => (
|
||||||
|
<div className={styles.container}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Container;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from "lodash";
|
||||||
|
import cells from "../../../data/GBM_metadata.js";
|
||||||
|
import drawParallelCoordinates from "./drawParallelCoordinates";
|
||||||
|
import createContinuousRanges from "./createContinuousRanges";
|
||||||
|
|
||||||
|
const Continuous = () => {
|
||||||
|
|
||||||
|
drawParallelCoordinates(cells)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3> Continuous Metadata </h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Continuous;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as globals from "../../globals.js"
|
||||||
|
|
||||||
|
/*
|
||||||
|
end result:
|
||||||
|
|
||||||
|
ranges: {
|
||||||
|
Total_reads: {
|
||||||
|
min: 11111,
|
||||||
|
max: 33333
|
||||||
|
},
|
||||||
|
Genes_detected: {
|
||||||
|
min: 444,
|
||||||
|
max: 555
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const createContinuousRanges = (cells) => {
|
||||||
|
const ranges = {};
|
||||||
|
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createContinuousRanges;
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
import styles from './parallelCoordinates.css';
|
||||||
|
import {
|
||||||
|
margin,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
innerHeight,
|
||||||
|
color,
|
||||||
|
dimensions,
|
||||||
|
types,
|
||||||
|
xscale,
|
||||||
|
yAxis,
|
||||||
|
} from "./util";
|
||||||
|
|
||||||
|
/*****************************************
|
||||||
|
******************************************
|
||||||
|
Canvas Parallel coordinates with svg brushing via /* via https://bl.ocks.org/syntagmatic/05a5b0897a48890133beb59c815bd953
|
||||||
|
******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
|
/*****************************************
|
||||||
|
******************************************
|
||||||
|
Render Queue via http://bl.ocks.org/syntagmatic/raw/3341641/render-queue.js
|
||||||
|
******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
|
const renderQueue = (function(callback1234) {
|
||||||
|
var _queue = [], // data to be rendered
|
||||||
|
_rate = 1000, // number of calls per frame
|
||||||
|
_invalidate = function() {}, // invalidate last render queue
|
||||||
|
_clear = function() {}; // clearing function
|
||||||
|
|
||||||
|
var rq = function(data) {
|
||||||
|
if (data) rq.data(data);
|
||||||
|
_invalidate();
|
||||||
|
_clear();
|
||||||
|
rq.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.render = function() {
|
||||||
|
var valid = true;
|
||||||
|
_invalidate = rq.invalidate = function() {
|
||||||
|
valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function doFrame() {
|
||||||
|
if (!valid) return true;
|
||||||
|
var chunk = _queue.splice(0,_rate);
|
||||||
|
chunk.map(callback1234);
|
||||||
|
timer_frame(doFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
doFrame();
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.data = function(data) {
|
||||||
|
_invalidate();
|
||||||
|
_queue = data.slice(0); // creates a copy of the data
|
||||||
|
return rq;
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.add = function(data) {
|
||||||
|
_queue = _queue.concat(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.rate = function(value) {
|
||||||
|
if (!arguments.length) return _rate;
|
||||||
|
_rate = value;
|
||||||
|
return rq;
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.remaining = function() {
|
||||||
|
return _queue.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// clear the canvas
|
||||||
|
rq.clear = function(func) {
|
||||||
|
if (!arguments.length) {
|
||||||
|
_clear();
|
||||||
|
return rq;
|
||||||
|
}
|
||||||
|
_clear = func;
|
||||||
|
return rq;
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.invalidate = _invalidate;
|
||||||
|
|
||||||
|
var timer_frame = window.requestAnimationFrame
|
||||||
|
|| window.webkitRequestAnimationFrame
|
||||||
|
|| window.mozRequestAnimationFrame
|
||||||
|
|| window.oRequestAnimationFrame
|
||||||
|
|| window.msRequestAnimationFrame
|
||||||
|
|| function(callback) { setTimeout(callback, 17); };
|
||||||
|
|
||||||
|
return rq;
|
||||||
|
});
|
||||||
|
|
||||||
|
/*****************************************
|
||||||
|
******************************************
|
||||||
|
MAIN: drawParallelCoordinates
|
||||||
|
******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
|
const drawParallelCoordinates = (data) => {
|
||||||
|
|
||||||
|
const d3_functor = (v) => {
|
||||||
|
return typeof v === "function" ? v : function() { return v; };
|
||||||
|
};
|
||||||
|
|
||||||
|
/*****************************************
|
||||||
|
******************************************
|
||||||
|
Setup SVG & Canvas elements
|
||||||
|
******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
|
var container = d3.select("body").append("div")
|
||||||
|
.attr("class", styles.parcoords)
|
||||||
|
.style("width", width + margin.left + margin.right + "px")
|
||||||
|
.style("height", height + margin.top + margin.bottom + "px");
|
||||||
|
|
||||||
|
var svg = container.append("svg")
|
||||||
|
.attr("width", width + margin.left + margin.right)
|
||||||
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
|
||||||
|
var canvas = container.append("canvas")
|
||||||
|
.attr("width", width * devicePixelRatio)
|
||||||
|
.attr("height", height * devicePixelRatio)
|
||||||
|
.style("width", width + "px")
|
||||||
|
.style("height", height + "px")
|
||||||
|
.style("margin-top", margin.top + "px")
|
||||||
|
.style("margin-left", margin.left + "px");
|
||||||
|
|
||||||
|
var ctx = canvas.node().getContext("2d");
|
||||||
|
ctx.globalCompositeOperation = 'darken';
|
||||||
|
ctx.globalAlpha = 0.15;
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
|
||||||
|
var output = d3.select("body").append("pre");
|
||||||
|
|
||||||
|
var axes = svg.selectAll(".axis")
|
||||||
|
.data(dimensions)
|
||||||
|
.enter().append("g")
|
||||||
|
.attr("class", styles.axis)
|
||||||
|
.attr("transform", function(d,i) { return "translate(" + xscale(i) + ")"; });
|
||||||
|
|
||||||
|
/*****************************************
|
||||||
|
******************************************
|
||||||
|
Setup SVG & Canvas elements
|
||||||
|
******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
|
d3.csv("https://gist.githubusercontent.com/syntagmatic/05a5b0897a48890133beb59c815bd953/raw/310a68d713eb4d351f809b6cfbcffe3d9d96a205/nutrient.csv", (error, data) => {
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
// shuffle the data!
|
||||||
|
data = d3.shuffle(data);
|
||||||
|
|
||||||
|
data.forEach(function(d) {
|
||||||
|
dimensions.forEach(function(p) {
|
||||||
|
d[p.key] = !d[p.key] ? null : p.type.coerce(d[p.key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// truncate long text strings to fit in data table
|
||||||
|
for (var key in d) {
|
||||||
|
if (d[key] && d[key].length > 35) d[key] = d[key].slice(0,36);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// type/dimension default setting happens here
|
||||||
|
dimensions.forEach(function(dim) {
|
||||||
|
if (!("domain" in dim)) {
|
||||||
|
// detect domain using dimension type's extent function
|
||||||
|
dim.domain = d3_functor(dim.type.extent)(data.map(function(d) { return d[dim.key]; }));
|
||||||
|
}
|
||||||
|
if (!("scale" in dim)) {
|
||||||
|
// use type's default scale for dimension
|
||||||
|
dim.scale = dim.type.defaultScale.copy();
|
||||||
|
}
|
||||||
|
dim.scale.domain(dim.domain);
|
||||||
|
});
|
||||||
|
|
||||||
|
const draw = (d) => {
|
||||||
|
ctx.strokeStyle = "rgba(0,0,0,.4)" /* color(d.food_group); */
|
||||||
|
ctx.beginPath();
|
||||||
|
var coords = project(d);
|
||||||
|
coords.forEach((p,i) => {
|
||||||
|
// this tricky bit avoids rendering null values as 0
|
||||||
|
if (p === null) {
|
||||||
|
// this bit renders horizontal lines on the previous/next
|
||||||
|
// dimensions, so that sandwiched null values are visible
|
||||||
|
if (i > 0) {
|
||||||
|
var prev = coords[i-1];
|
||||||
|
if (prev !== null) {
|
||||||
|
ctx.moveTo(prev[0],prev[1]);
|
||||||
|
ctx.lineTo(prev[0]+6,prev[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i < coords.length-1) {
|
||||||
|
var next = coords[i+1];
|
||||||
|
if (next !== null) {
|
||||||
|
ctx.moveTo(next[0]-6,next[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
ctx.moveTo(p[0],p[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.lineTo(p[0],p[1]);
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************
|
||||||
|
******************************************
|
||||||
|
Handles a brush event, toggling the display of foreground lines.
|
||||||
|
******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
|
const brush = () => {
|
||||||
|
render.invalidate();
|
||||||
|
|
||||||
|
var actives = [];
|
||||||
|
svg.selectAll(".axis .brush")
|
||||||
|
.filter((d) => {
|
||||||
|
return d3.brushSelection(this);
|
||||||
|
})
|
||||||
|
.each(function(d) {
|
||||||
|
actives.push({
|
||||||
|
dimension: d,
|
||||||
|
extent: d3.brushSelection(this)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var selected = data.filter(function(d) {
|
||||||
|
if (actives.every(function(active) {
|
||||||
|
var dim = active.dimension;
|
||||||
|
// test if point is within extents for each active brush
|
||||||
|
return dim.type.within(d[dim.key], active.extent, dim);
|
||||||
|
})) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.clearRect(0,0,width,height);
|
||||||
|
ctx.globalAlpha = d3.min([0.85/Math.pow(selected.length,0.3),1]);
|
||||||
|
render(selected);
|
||||||
|
|
||||||
|
output.text(d3.tsvFormat(selected.slice(0,24)));
|
||||||
|
console.log('fires brush', actives)
|
||||||
|
}
|
||||||
|
|
||||||
|
function brushstart() {
|
||||||
|
d3.event.sourceEvent.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
var render = renderQueue(draw).rate(50);
|
||||||
|
|
||||||
|
ctx.clearRect(0,0,width,height);
|
||||||
|
ctx.globalAlpha = d3.min([0.85/Math.pow(data.length,0.3),1]);
|
||||||
|
|
||||||
|
render(data);
|
||||||
|
|
||||||
|
axes.append("g")
|
||||||
|
.each(function(d) {
|
||||||
|
var renderAxis = "axis" in d
|
||||||
|
? d.axis.scale(d.scale) // custom axis
|
||||||
|
: yAxis.scale(d.scale); // default axis
|
||||||
|
d3.select(this).call(renderAxis);
|
||||||
|
})
|
||||||
|
.append("text")
|
||||||
|
.attr("class", styles.title)
|
||||||
|
.attr("text-anchor", "start")
|
||||||
|
.text(function(d) { return "description" in d ? d.description : d.key; });
|
||||||
|
|
||||||
|
// Add and store a brush for each axis.
|
||||||
|
axes.append("g")
|
||||||
|
.attr("class", styles.brush)
|
||||||
|
.each(function(d) {
|
||||||
|
d3.select(this).call(d.brush = d3.brushY()
|
||||||
|
.extent([[-10,0], [10,height]])
|
||||||
|
.on("start", brushstart)
|
||||||
|
.on("brush", brush)
|
||||||
|
.on("end", brush)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.selectAll("rect")
|
||||||
|
.attr("x", -8)
|
||||||
|
.attr("width", 16);
|
||||||
|
|
||||||
|
output.text(d3.tsvFormat(data.slice(0,24)));
|
||||||
|
|
||||||
|
function project(d) {
|
||||||
|
return dimensions.map(function(p,i) {
|
||||||
|
// check if data element has property and contains a value
|
||||||
|
if (
|
||||||
|
!(p.key in d) ||
|
||||||
|
d[p.key] === null
|
||||||
|
) return null;
|
||||||
|
|
||||||
|
return [xscale(i),p.scale(d[p.key])];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default drawParallelCoordinates;
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
This code can be found: https://bl.ocks.org/syntagmatic/05a5b0897a48890133beb59c815bd953
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-width: 760px;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.parcoords {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parcoords svg,
|
||||||
|
.parcoords canvas {
|
||||||
|
font: 10px sans-serif;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parcoords canvas {
|
||||||
|
opacity: 0.9;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis .title {
|
||||||
|
font-size: 10px;
|
||||||
|
transform: rotate(-21deg) translate(-5px,-6px);
|
||||||
|
fill: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis line,
|
||||||
|
.axis path {
|
||||||
|
fill: none;
|
||||||
|
stroke: #ccc;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis .tick text {
|
||||||
|
fill: #222;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
old, from reference bl.ocks
|
||||||
|
.axis.manufac_name .tick text,
|
||||||
|
.axis.food_group .tick text {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.axis:hover line,
|
||||||
|
.axis:hover path,
|
||||||
|
.axis.active line,
|
||||||
|
.axis.active path {
|
||||||
|
fill: none;
|
||||||
|
stroke: #222;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis:hover .title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis:hover .tick text {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis.active .title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis.active .tick text {
|
||||||
|
opacity: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brush .extent {
|
||||||
|
fill-opacity: .3;
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
margin: 6px 12px;
|
||||||
|
tab-size: 40;
|
||||||
|
font-size: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
export const margin = {top: 66, right: 110, bottom: 20, left: 188};
|
||||||
|
export const width = document.body.clientWidth - margin.left - margin.right;
|
||||||
|
export const height = 340 - margin.top - margin.bottom;
|
||||||
|
export const innerHeight = height - 2;
|
||||||
|
|
||||||
|
export const devicePixelRatio = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
|
export const color = d3.scaleOrdinal()
|
||||||
|
.range(["#5DA5B3","#D58323","#DD6CA7","#54AF52","#8C92E8","#E15E5A","#725D82","#776327","#50AB84","#954D56","#AB9C27","#517C3F","#9D5130","#357468","#5E9ACF","#C47DCB","#7D9E33","#DB7F85","#BA89AD","#4C6C86","#B59248","#D8597D","#944F7E","#D67D4B","#8F86C2"]);
|
||||||
|
|
||||||
|
export const types = {
|
||||||
|
"Number": {
|
||||||
|
key: "Number",
|
||||||
|
coerce: function(d) { return +d; },
|
||||||
|
extent: d3.extent,
|
||||||
|
within: function(d, extent, dim) { return extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1]; },
|
||||||
|
defaultScale: d3.scaleLinear().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
"String": {
|
||||||
|
key: "String",
|
||||||
|
coerce: String,
|
||||||
|
extent: function (data) { return data.sort(); },
|
||||||
|
within: function(d, extent, dim) { return extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1]; },
|
||||||
|
defaultScale: d3.scalePoint().range([0, innerHeight])
|
||||||
|
},
|
||||||
|
"Date": {
|
||||||
|
key: "Date",
|
||||||
|
coerce: function(d) { return new Date(d); },
|
||||||
|
extent: d3.extent,
|
||||||
|
within: function(d, extent, dim) { return extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1]; },
|
||||||
|
defaultScale: d3.scaleTime().range([0, innerHeight])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dimensions = [
|
||||||
|
{
|
||||||
|
key: "Total lipid (fat) (g)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Sugars, total (g)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Calcium, Ca (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Sodium, Na (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Phosphorus, P (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Potassium, K (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Thiamin (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Riboflavin (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Niacin (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Iron, Fe (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Magnesium, Mg (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Protein (g)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Zinc, Zn (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Vitamin B-6 (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Vitamin B-12 (mcg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Folic acid (mcg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Selenium, Se (mcg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Vitamin A, IU (IU)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Vitamin K (phylloquinone) (mcg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Vitamin C, total ascorbic acid (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Vitamin D (IU)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Cholesterol (mg)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Fiber, total dietary (g)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Carbohydrate, by difference (g)",
|
||||||
|
type: types["Number"],
|
||||||
|
scale: d3.scaleSqrt().range([innerHeight, 0])
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const xscale = d3.scalePoint()
|
||||||
|
.domain(d3.range(dimensions.length))
|
||||||
|
.range([0, width]);
|
||||||
|
|
||||||
|
export const yAxis = d3.axisLeft();
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.header {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Container from './container';
|
||||||
|
import styles from './header.css';
|
||||||
|
|
||||||
|
|
||||||
|
const Header = () => (
|
||||||
|
<header className={styles.header}>
|
||||||
|
<Container>
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Header;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import _ from "lodash";
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
import Container from './container';
|
||||||
|
|
||||||
|
import Categorical from "./categorical/categorical";
|
||||||
|
import Continuous from "./continuous/continuous";
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Helmet title="cellxgene" />
|
||||||
|
<h1><Link to="/page2">cellxgene</Link></h1>
|
||||||
|
<Categorical/>
|
||||||
|
<Continuous/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|
||||||
|
// <Categorical title={"Sample type"} category={types}/>
|
||||||
|
// <Categorical title={"Selection"} category={selection}/>
|
||||||
|
// <Categorical title={"Location"} category={location}/>
|
||||||
|
// <Categorical title={"Sample name"} category={names}/>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
.heroContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 95vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
font-size: 48;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subHero {
|
||||||
|
font-size: 18;
|
||||||
|
max-width: 600;
|
||||||
|
text-align: "center";
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryButton {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or {
|
||||||
|
margin: 20px;
|
||||||
|
font-family: Georgia;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
import styles from './page2.css';
|
||||||
|
|
||||||
|
import Container from './container';
|
||||||
|
|
||||||
|
const Page2 = () => (
|
||||||
|
<Container>
|
||||||
|
<Helmet title="cellxgene" />
|
||||||
|
<div className={styles.heroContainer}>
|
||||||
|
<h1 className={styles.hero}>cellxgene</h1>
|
||||||
|
<p className={styles.subHero}>
|
||||||
|
A data exploration interface for single cell genetic expression matrices. Subset, filter, cluster & validate, all in one place.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<Link to="/"><button className={styles.primaryButton}>explore a sample dataset</button></Link>
|
||||||
|
<span className={styles.or}>or</span>
|
||||||
|
<button className={styles.primaryButton}>get started with your own data</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Page2;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.root {
|
||||||
|
background: #fff;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: Georgia,serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
a {
|
||||||
|
color: #ad1b11;
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styles from './root.css';
|
||||||
|
|
||||||
|
const Root = props => (
|
||||||
|
<div className={styles.root}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Root;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* these will be either (preferably) specified or inferred */
|
||||||
|
export const categories = ["Sample.type", "Selection", "Location", "Sample.name", "Class", "Neoplastic"];
|
||||||
|
export const continuous = [
|
||||||
|
"ERCC_reads",
|
||||||
|
"ERCC_to_non_ERCC",
|
||||||
|
"Genes_detected",
|
||||||
|
"Multimapping_reads_percent",
|
||||||
|
"Non_ERCC_reads",
|
||||||
|
"Splice_sites_AT.AC",
|
||||||
|
"Splice_sites_Annotated",
|
||||||
|
"Splice_sites_GC.AG",
|
||||||
|
"Splice_sites_GT.AG",
|
||||||
|
"Splice_sites_non_canonical",
|
||||||
|
"Splice_sites_total",
|
||||||
|
"Total_reads",
|
||||||
|
"Unique_reads",
|
||||||
|
"Unique_reads_percent",
|
||||||
|
"Unmapped_mismatch",
|
||||||
|
"Unmapped_other",
|
||||||
|
"Unmapped_short"
|
||||||
|
]
|
||||||
|
|
||||||
|
/* colors */
|
||||||
|
export const hcaBlue = "#1c7cc7"
|
||||||
|
export const lightgrey = "rgb(211,211,211)";
|
||||||
|
|
||||||
|
export const bolder = 700;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { AppContainer } from 'react-hot-loader';
|
||||||
|
import Redbox from 'redbox-react';
|
||||||
|
|
||||||
|
import Routes from './routes';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register('/service-worker.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(<Routes />, document.getElementById('root'));
|
||||||
|
} else {
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppContainer errorReporter={Redbox}>
|
||||||
|
<Routes />
|
||||||
|
</AppContainer>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./routes', () => {
|
||||||
|
// TODO: Remove console override when
|
||||||
|
// https://github.com/reactjs/react-router/issues/2704 is fixed
|
||||||
|
const orgError = console.error;
|
||||||
|
console.error = message => {
|
||||||
|
if (
|
||||||
|
message &&
|
||||||
|
message.indexOf('You cannot change <Router routes>;') === -1
|
||||||
|
) {
|
||||||
|
orgError.apply(console, [ message ]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const RoutesUpdate = require('./routes').default;
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppContainer errorReporter={Redbox}>
|
||||||
|
<RoutesUpdate />
|
||||||
|
</AppContainer>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default getComponent => {
|
||||||
|
return class AsyncComponent extends React.Component {
|
||||||
|
static Component = null;
|
||||||
|
mounted = false;
|
||||||
|
|
||||||
|
state = { Component: AsyncComponent.Component };
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (this.state.Component === null) {
|
||||||
|
getComponent()
|
||||||
|
.then(m => m.default)
|
||||||
|
.then(Component => {
|
||||||
|
AsyncComponent.Component = Component;
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ Component });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.mounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { Component } = this.state;
|
||||||
|
|
||||||
|
if (Component !== null) {
|
||||||
|
return <Component {...this.props} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { BrowserRouter as Router, Route } from 'react-router-dom';
|
||||||
|
|
||||||
|
import asyncRoute from './async-route';
|
||||||
|
import Root from '../containers/root';
|
||||||
|
import Header from '../components/header';
|
||||||
|
|
||||||
|
const Routes = () => (
|
||||||
|
<Router>
|
||||||
|
<Root>
|
||||||
|
<Header />
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/"
|
||||||
|
component={asyncRoute(() => System.import('../components/home'))}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/page2"
|
||||||
|
component={asyncRoute(() => System.import('../components/page2'))}
|
||||||
|
/>
|
||||||
|
</Root>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Routes;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
var babelConfig = require('../configuration/babel/babel.test.js');
|
||||||
|
require('babel-register')(babelConfig);
|
||||||
|
|
||||||
|
// Deobfuscate CSS modules classes for testing
|
||||||
|
require('css-modules-require-hook')({ generateScopedName: '[local]' });
|
||||||
|
|
||||||
|
require.extensions['.svg'] = () => null;
|
||||||
|
|
||||||
|
var jsdom = require('jsdom').jsdom;
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var sinonChai = require('sinon-chai');
|
||||||
|
|
||||||
|
var exposedProperties = [ 'window', 'navigator', 'document' ];
|
||||||
|
|
||||||
|
global.document = jsdom('');
|
||||||
|
global.window = document.defaultView;
|
||||||
|
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
chai.use(sinonChai);
|
||||||
|
|
||||||
|
Object.keys(document.defaultView).forEach(property => {
|
||||||
|
if (typeof global[property] === 'undefined') {
|
||||||
|
exposedProperties.push(property);
|
||||||
|
global[property] = document.defaultView[property];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
global.navigator = { userAgent: 'node.js' };
|
||||||
|
|
||||||
|
documentRef = document;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Routes from '../../src/routes';
|
||||||
|
|
||||||
|
describe('Routes', () => {
|
||||||
|
it('contains spec with an expectation', () => {
|
||||||
|
expect(shallow(<Routes />).find(Router)).to.have.length(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user