mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-06 06:08:12 +08:00
Merge pull request #17 from chanzuckerberg/bkmartinjr-prettier
Prettier
This commit is contained in:
@@ -1,11 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
cacheDirectory: true,
|
cacheDirectory: true,
|
||||||
presets: [
|
presets: [["env", { loose: true, modules: false }], "stage-0", "react"],
|
||||||
[ "env", { loose: true, modules: false } ],
|
|
||||||
"stage-0",
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
"react-hot-loader/babel",
|
"react-hot-loader/babel",
|
||||||
"babel-plugin-transform-decorators-legacy"
|
"babel-plugin-transform-decorators-legacy"
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
presets: [
|
presets: [["env", { loose: true, modules: false }], "stage-0", "react"],
|
||||||
[ "env", { loose: true, modules: false } ],
|
|
||||||
"stage-0",
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
"babel-plugin-transform-react-constant-elements",
|
"babel-plugin-transform-react-constant-elements",
|
||||||
"babel-plugin-transform-decorators-legacy"
|
"babel-plugin-transform-decorators-legacy"
|
||||||
].map(require.resolve).concat([ [ require.resolve('babel-plugin-transform-runtime') ] ])
|
]
|
||||||
|
.map(require.resolve)
|
||||||
|
.concat([[require.resolve("babel-plugin-transform-runtime")]])
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
presets: [
|
presets: [
|
||||||
'babel-preset-es2015',
|
"babel-preset-es2015",
|
||||||
'babel-preset-stage-0',
|
"babel-preset-stage-0",
|
||||||
'babel-preset-react'
|
"babel-preset-react"
|
||||||
],
|
],
|
||||||
plugins: [ 'istanbul' ]
|
plugins: ["istanbul"]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: 'babel-eslint',
|
parser: "babel-eslint",
|
||||||
extends: 'airbnb',
|
extends: "airbnb",
|
||||||
env: { browser: true, commonjs: true, es6: true },
|
env: { browser: true, commonjs: true, es6: true },
|
||||||
globals: { expect: true },
|
globals: { expect: true },
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 6,
|
ecmaVersion: 6,
|
||||||
sourceType: 'module',
|
sourceType: "module",
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true,
|
jsx: true,
|
||||||
generators: true,
|
generators: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-magic-numbers': 'off',
|
"no-magic-numbers": "off",
|
||||||
'func-style': 'off',
|
"func-style": "off",
|
||||||
'arrow-parens': 'off',
|
"arrow-parens": "off",
|
||||||
'no-use-before-define': 'off',
|
"no-use-before-define": "off",
|
||||||
'react/jsx-filename-extension': 'off',
|
"react/jsx-filename-extension": "off"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
if (typeof Promise === 'undefined') {
|
if (typeof Promise === "undefined") {
|
||||||
require('promise/lib/rejection-tracking').enable();
|
require("promise/lib/rejection-tracking").enable();
|
||||||
window.Promise = require('promise/lib/es6-extensions.js');
|
window.Promise = require("promise/lib/es6-extensions.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
require('whatwg-fetch');
|
require("whatwg-fetch");
|
||||||
|
|||||||
@@ -1,72 +1,74 @@
|
|||||||
const path = require('path');
|
// jshint esversion: 6
|
||||||
const autoprefixer = require('autoprefixer');
|
const path = require("path");
|
||||||
const webpack = require('webpack');
|
const autoprefixer = require("autoprefixer");
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const webpack = require("webpack");
|
||||||
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
|
|
||||||
const src = path.resolve('src');
|
const src = path.resolve("src");
|
||||||
const nodeModules = path.resolve('node_modules');
|
const nodeModules = path.resolve("node_modules");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devtool: 'eval',
|
devtool: "eval",
|
||||||
entry: [
|
entry: [
|
||||||
'webpack-hot-middleware/client?quiet=true&noInfo=true',
|
"webpack-hot-middleware/client?quiet=true&noInfo=true",
|
||||||
require.resolve('react-hot-loader/patch'),
|
require.resolve("react-hot-loader/patch"),
|
||||||
require.resolve('../polyfills/polyfills'),
|
require.resolve("../polyfills/polyfills"),
|
||||||
path.join(src, 'index')
|
path.join(src, "index")
|
||||||
],
|
],
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve('build'),
|
path: path.resolve("build"),
|
||||||
pathinfo: true,
|
pathinfo: true,
|
||||||
filename: 'static/js/bundle.js',
|
filename: "static/js/bundle.js",
|
||||||
publicPath: '/'
|
publicPath: "/"
|
||||||
},
|
},
|
||||||
resolve: { extensions: [ '.js', '.json' ] },
|
resolve: { extensions: [".js", ".json"] },
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
loaders: [
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: src,
|
include: src,
|
||||||
loader: 'babel-loader',
|
loader: "babel-loader",
|
||||||
query: require('../babel/babel.dev')
|
query: require("../babel/babel.dev")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: [ src, nodeModules ],
|
include: [src, nodeModules],
|
||||||
loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader'
|
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: /\.json$/, include: [src, nodeModules], loader: "json-loader" },
|
||||||
{
|
{
|
||||||
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?$/,
|
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?$/,
|
||||||
include: [ src, nodeModules ],
|
include: [src, nodeModules],
|
||||||
loader: 'file-loader',
|
loader: "file-loader",
|
||||||
query: { name: 'static/media/[name].[ext]' }
|
query: { name: "static/media/[name].[ext]" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(mp4|webm)(\?.*)?$/,
|
test: /\.(mp4|webm)(\?.*)?$/,
|
||||||
include: [ src, nodeModules ],
|
include: [src, nodeModules],
|
||||||
loader: 'url-loader',
|
loader: "url-loader",
|
||||||
query: { limit: 10000, name: 'static/media/[name].[ext]' }
|
query: { limit: 10000, name: "static/media/[name].[ext]" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
inject: true,
|
inject: true,
|
||||||
template: path.resolve('index.html'),
|
template: path.resolve("index.html"),
|
||||||
favicon: path.resolve('favicon.png')
|
favicon: path.resolve("favicon.png")
|
||||||
}),
|
}),
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
options: {
|
options: {
|
||||||
eslint: {
|
eslint: {
|
||||||
configFile: path.resolve('./configuration/eslint/eslint.js'),
|
configFile: path.resolve("./configuration/eslint/eslint.js"),
|
||||||
useEslintrc: false
|
useEslintrc: false
|
||||||
},
|
},
|
||||||
postcss() {
|
postcss() {
|
||||||
return [ autoprefixer ];
|
return [autoprefixer];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }),
|
new webpack.DefinePlugin({ "process.env.NODE_ENV": '"development"' }),
|
||||||
// Note: only CSS is currently hot reloaded
|
// Note: only CSS is currently hot reloaded
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
new webpack.NoEmitOnErrorsPlugin()
|
new webpack.NoEmitOnErrorsPlugin()
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
const path = require('path');
|
// jshint esversion: 6
|
||||||
const autoprefixer = require('autoprefixer');
|
const path = require("path");
|
||||||
const webpack = require('webpack');
|
const autoprefixer = require("autoprefixer");
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const webpack = require("webpack");
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
|
const ExtractTextPlugin = require("extract-text-webpack-plugin");
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const SWPrecacheWebpackPlugin = require("sw-precache-webpack-plugin");
|
||||||
const HtmlWebpackInlineSourcePlugin = require(
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
'html-webpack-inline-source-plugin'
|
const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin");
|
||||||
);
|
|
||||||
const MinifyPlugin = require("babel-minify-webpack-plugin");
|
const MinifyPlugin = require("babel-minify-webpack-plugin");
|
||||||
|
|
||||||
const src = path.resolve('src');
|
const src = path.resolve("src");
|
||||||
const nodeModules = path.resolve('node_modules');
|
const nodeModules = path.resolve("node_modules");
|
||||||
|
|
||||||
const publicPath = '/';
|
const publicPath = "/";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
bail: true,
|
bail: true,
|
||||||
devtool: 'cheap-source-map',
|
devtool: "cheap-source-map",
|
||||||
entry: [ require.resolve('../polyfills/polyfills'), path.join(src, 'index') ],
|
entry: [require.resolve("../polyfills/polyfills"), path.join(src, "index")],
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve('build'),
|
path: path.resolve("build"),
|
||||||
filename: 'static/js/[name].[chunkhash:8].js',
|
filename: "static/js/[name].[chunkhash:8].js",
|
||||||
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
|
chunkFilename: "static/js/[name].[chunkhash:8].chunk.js",
|
||||||
publicPath
|
publicPath
|
||||||
},
|
},
|
||||||
resolve: { extensions: [ '.js', '.json' ] },
|
resolve: { extensions: [".js", ".json"] },
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
loaders: [
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: src,
|
include: src,
|
||||||
loader: 'babel-loader',
|
loader: "babel-loader",
|
||||||
query: require('../babel/babel.prod')
|
query: require("../babel/babel.prod")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: [ src, nodeModules ],
|
include: [src, nodeModules],
|
||||||
loader: ExtractTextPlugin.extract({
|
loader: ExtractTextPlugin.extract({
|
||||||
fallbackLoader: 'style-loader',
|
fallbackLoader: "style-loader",
|
||||||
loader: 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]-autoprefixer!postcss-loader'
|
loader:
|
||||||
|
"css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]-autoprefixer!postcss-loader"
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.json$/,
|
test: /\.json$/,
|
||||||
include: [ src, nodeModules ],
|
include: [src, nodeModules],
|
||||||
loader: 'json-loader',
|
loader: "json-loader",
|
||||||
exclude: /manifest.json$/
|
exclude: /manifest.json$/
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?$/,
|
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?$/,
|
||||||
include: [ src, nodeModules ],
|
include: [src, nodeModules],
|
||||||
loader: 'file-loader',
|
loader: "file-loader",
|
||||||
query: { name: 'static/media/[name].[hash:8].[ext]' }
|
query: { name: "static/media/[name].[hash:8].[ext]" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(mp4|webm)(\?.*)?$/,
|
test: /\.(mp4|webm)(\?.*)?$/,
|
||||||
include: [ src, nodeModules ],
|
include: [src, nodeModules],
|
||||||
loader: 'url-loader',
|
loader: "url-loader",
|
||||||
query: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]' }
|
query: { limit: 10000, name: "static/media/[name].[hash:8].[ext]" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
inject: 'body',
|
inject: "body",
|
||||||
filename: 'index.html',
|
filename: "index.html",
|
||||||
template: path.resolve('index_template.html'),
|
template: path.resolve("index_template.html"),
|
||||||
favicon: path.resolve('favicon.png'),
|
favicon: path.resolve("favicon.png"),
|
||||||
inlineSource: '.(js|css)$',
|
inlineSource: ".(js|css)$",
|
||||||
minify: {
|
minify: {
|
||||||
removeComments: true,
|
removeComments: true,
|
||||||
collapseWhitespace: true,
|
collapseWhitespace: true,
|
||||||
@@ -86,21 +86,21 @@ module.exports = {
|
|||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
options: {
|
options: {
|
||||||
eslint: {
|
eslint: {
|
||||||
configFile: path.resolve('./configuration/eslint/eslint.js'),
|
configFile: path.resolve("./configuration/eslint/eslint.js"),
|
||||||
useEslintrc: false
|
useEslintrc: false
|
||||||
},
|
},
|
||||||
postcss() {
|
postcss() {
|
||||||
return [ autoprefixer ];
|
return [autoprefixer];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }),
|
new webpack.DefinePlugin({ "process.env.NODE_ENV": '"production"' }),
|
||||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||||
new MinifyPlugin(),
|
new MinifyPlugin(),
|
||||||
new ExtractTextPlugin('static/css/[name].[contenthash:8].css'),
|
new ExtractTextPlugin("static/css/[name].[contenthash:8].css"),
|
||||||
new SWPrecacheWebpackPlugin({
|
new SWPrecacheWebpackPlugin({
|
||||||
cacheId: 'cellxgene',
|
cacheId: "cellxgene",
|
||||||
filename: 'service-worker.js'
|
filename: "service-worker.js"
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-6
@@ -13,12 +13,12 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.CELLXGENE = {}
|
window.CELLXGENE = {};
|
||||||
window.CELLXGENE.API= {
|
window.CELLXGENE.API = {
|
||||||
prefix: "{{ prefix | safe }}",
|
prefix: "{{ prefix | safe }}",
|
||||||
version: "v0.1/",
|
version: "v0.1/"
|
||||||
}
|
};
|
||||||
window.CELLXGENE.datasetTitle = "{{ datasetTitle }}"
|
window.CELLXGENE.datasetTitle = "{{ datasetTitle }}";
|
||||||
</script>
|
</script>
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
|
|||||||
+19
-18
@@ -1,26 +1,27 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
var path = require('path');
|
// jshint esversion: 6
|
||||||
var historyApiFallback = require('connect-history-api-fallback');
|
var path = require("path");
|
||||||
var chalk = require('chalk');
|
var historyApiFallback = require("connect-history-api-fallback");
|
||||||
var express = require('express');
|
var chalk = require("chalk");
|
||||||
var favicon = require('serve-favicon');
|
var express = require("express");
|
||||||
var webpack = require('webpack');
|
var favicon = require("serve-favicon");
|
||||||
var config = require('../configuration/webpack/webpack.config.dev');
|
var webpack = require("webpack");
|
||||||
var utils = require('./utils');
|
var config = require("../configuration/webpack/webpack.config.dev");
|
||||||
|
var utils = require("./utils");
|
||||||
|
|
||||||
process.env.NODE_ENV = 'development';
|
process.env.NODE_ENV = "development";
|
||||||
|
|
||||||
var PORT = process.env.PORT || 3000;
|
var PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
// Set up compiler
|
// Set up compiler
|
||||||
var compiler = webpack(config);
|
var compiler = webpack(config);
|
||||||
|
|
||||||
compiler.plugin('invalid', () => {
|
compiler.plugin("invalid", () => {
|
||||||
utils.clearConsole();
|
utils.clearConsole();
|
||||||
console.log('Compiling...');
|
console.log("Compiling...");
|
||||||
});
|
});
|
||||||
|
|
||||||
compiler.plugin('done', stats => {
|
compiler.plugin("done", stats => {
|
||||||
utils.formatStats(stats, PORT);
|
utils.formatStats(stats, PORT);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,18 +31,18 @@ var app = express();
|
|||||||
app.use(historyApiFallback({ verbose: false }));
|
app.use(historyApiFallback({ verbose: false }));
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
require('webpack-dev-middleware')(compiler, {
|
require("webpack-dev-middleware")(compiler, {
|
||||||
noInfo: true,
|
noInfo: true,
|
||||||
publicPath: config.output.publicPath
|
publicPath: config.output.publicPath
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use(require('webpack-hot-middleware')(compiler));
|
app.use(require("webpack-hot-middleware")(compiler));
|
||||||
|
|
||||||
app.use(favicon('./favicon.png'));
|
app.use(favicon("./favicon.png"));
|
||||||
|
|
||||||
app.get('*', (req, res) => {
|
app.get("*", (req, res) => {
|
||||||
res.sendFile(path.resolve('index.html'));
|
res.sendFile(path.resolve("index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(PORT, err => {
|
app.listen(PORT, err => {
|
||||||
@@ -51,6 +52,6 @@ app.listen(PORT, err => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
utils.clearConsole();
|
utils.clearConsole();
|
||||||
console.log(chalk.cyan('Starting the development server...'));
|
console.log(chalk.cyan("Starting the development server..."));
|
||||||
console.log();
|
console.log();
|
||||||
});
|
});
|
||||||
|
|||||||
+12
-11
@@ -1,22 +1,23 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
var path = require('path');
|
// jshint esversion: 6
|
||||||
var fs = require('fs');
|
var path = require("path");
|
||||||
var chalk = require('chalk');
|
var fs = require("fs");
|
||||||
var express = require('express');
|
var chalk = require("chalk");
|
||||||
var favicon = require('serve-favicon');
|
var express = require("express");
|
||||||
var utils = require('./utils');
|
var favicon = require("serve-favicon");
|
||||||
|
var utils = require("./utils");
|
||||||
|
|
||||||
var PORT = process.env.PORT || 80;
|
var PORT = process.env.PORT || 80;
|
||||||
|
|
||||||
// Launch server
|
// Launch server
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
app.use(express.static('./build'));
|
app.use(express.static("./build"));
|
||||||
|
|
||||||
app.use(favicon('./build/favicon.png'));
|
app.use(favicon("./build/favicon.png"));
|
||||||
|
|
||||||
app.get('*', (req, res) => {
|
app.get("*", (req, res) => {
|
||||||
res.sendFile(path.resolve('./build/index.html'));
|
res.sendFile(path.resolve("./build/index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(PORT, err => {
|
app.listen(PORT, err => {
|
||||||
@@ -26,6 +27,6 @@ app.listen(PORT, err => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
utils.clearConsole();
|
utils.clearConsole();
|
||||||
console.log(chalk.cyan('Production server started on port ' + PORT));
|
console.log(chalk.cyan("Production server started on port " + PORT));
|
||||||
console.log();
|
console.log();
|
||||||
});
|
});
|
||||||
|
|||||||
+21
-20
@@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
var chalk = require('chalk');
|
// jshint esversion: 6
|
||||||
|
var chalk = require("chalk");
|
||||||
|
|
||||||
var friendlySyntaxErrorLabel = 'Syntax error:';
|
var friendlySyntaxErrorLabel = "Syntax error:";
|
||||||
|
|
||||||
function isLikelyASyntaxError(message) {
|
function isLikelyASyntaxError(message) {
|
||||||
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
|
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
|
||||||
@@ -9,16 +10,16 @@ function isLikelyASyntaxError(message) {
|
|||||||
|
|
||||||
function formatMessage(message) {
|
function formatMessage(message) {
|
||||||
return message
|
return message
|
||||||
.replace('Module build failed: SyntaxError:', friendlySyntaxErrorLabel)
|
.replace("Module build failed: SyntaxError:", friendlySyntaxErrorLabel)
|
||||||
.replace(
|
.replace(
|
||||||
/Module not found: Error: Cannot resolve 'file' or 'directory'/,
|
/Module not found: Error: Cannot resolve 'file' or 'directory'/,
|
||||||
'Module not found:'
|
"Module not found:"
|
||||||
)
|
)
|
||||||
.replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, '')
|
.replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, "")
|
||||||
.replace('./~/css-loader!./~/postcss-loader!', '');
|
.replace("./~/css-loader!./~/postcss-loader!", "");
|
||||||
}
|
}
|
||||||
var clearConsole = () => {
|
var clearConsole = () => {
|
||||||
process.stdout.write('\x1bc');
|
process.stdout.write("\x1bc");
|
||||||
};
|
};
|
||||||
|
|
||||||
var formatStats = (stats, port) => {
|
var formatStats = (stats, port) => {
|
||||||
@@ -26,23 +27,23 @@ var formatStats = (stats, port) => {
|
|||||||
var hasErrors = stats.hasErrors();
|
var hasErrors = stats.hasErrors();
|
||||||
var hasWarnings = stats.hasWarnings();
|
var hasWarnings = stats.hasWarnings();
|
||||||
if (!hasErrors && !hasWarnings) {
|
if (!hasErrors && !hasWarnings) {
|
||||||
console.log(chalk.green('Compiled successfully!'));
|
console.log(chalk.green("Compiled successfully!"));
|
||||||
console.log();
|
console.log();
|
||||||
console.log('The app is running at http://localhost:' + port + '/');
|
console.log("The app is running at http://localhost:" + port + "/");
|
||||||
console.log();
|
console.log();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var json = stats.toJson();
|
var json = stats.toJson();
|
||||||
var formattedErrors = json.errors.map(
|
var formattedErrors = json.errors.map(
|
||||||
message => 'Error in ' + formatMessage(message)
|
message => "Error in " + formatMessage(message)
|
||||||
);
|
);
|
||||||
var formattedWarnings = json.warnings.map(
|
var formattedWarnings = json.warnings.map(
|
||||||
message => 'Warning in ' + formatMessage(message)
|
message => "Warning in " + formatMessage(message)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
console.log(chalk.red('Failed to compile.'));
|
console.log(chalk.red("Failed to compile."));
|
||||||
console.log();
|
console.log();
|
||||||
if (formattedErrors.some(isLikelyASyntaxError)) {
|
if (formattedErrors.some(isLikelyASyntaxError)) {
|
||||||
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
|
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
|
||||||
@@ -55,23 +56,23 @@ var formatStats = (stats, port) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasWarnings) {
|
if (hasWarnings) {
|
||||||
console.log(chalk.yellow('Compiled with warnings.'));
|
console.log(chalk.yellow("Compiled with warnings."));
|
||||||
console.log();
|
console.log();
|
||||||
formattedWarnings.forEach(message => {
|
formattedWarnings.forEach(message => {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
console.log();
|
console.log();
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('You may use special comments to disable some warnings.');
|
console.log("You may use special comments to disable some warnings.");
|
||||||
console.log(
|
console.log(
|
||||||
'Use ' +
|
"Use " +
|
||||||
chalk.yellow('// eslint-disable-next-line') +
|
chalk.yellow("// eslint-disable-next-line") +
|
||||||
' to ignore the next line.'
|
" to ignore the next line."
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
'Use ' +
|
"Use " +
|
||||||
chalk.yellow('/* eslint-disable */') +
|
chalk.yellow("/* eslint-disable */") +
|
||||||
' to ignore all warnings in a file.'
|
" to ignore all warnings in a file."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+113
-103
@@ -1,30 +1,31 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import * as globals from "../globals";
|
import * as globals from "../globals";
|
||||||
import URI from "urijs";
|
import URI from "urijs";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
const requestCells = (query = "") => {
|
const requestCells = (query = "") => {
|
||||||
return (dispatch) => {
|
return dispatch => {
|
||||||
dispatch({type: "request cells started"})
|
dispatch({ type: "request cells started" });
|
||||||
return fetch(`${globals.API.prefix}${globals.API.version}cells${query}`, {
|
return fetch(`${globals.API.prefix}${globals.API.version}cells${query}`, {
|
||||||
method: "get",
|
method: "get",
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json"
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(
|
.then(
|
||||||
data => dispatch({type: "request cells success", data}),
|
data => dispatch({ type: "request cells success", data }),
|
||||||
error => dispatch({type: "request cells error", error})
|
error => dispatch({ type: "request cells error", error })
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
/* SELECT */
|
/* SELECT */
|
||||||
const regraph = () => {
|
const regraph = () => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({type: "regraph started"})
|
dispatch({ type: "regraph started" });
|
||||||
|
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const selectedMetadata = {};
|
const selectedMetadata = {};
|
||||||
|
|
||||||
_.each(state.controls.categoricalAsBooleansMap, (options, field) => {
|
_.each(state.controls.categoricalAsBooleansMap, (options, field) => {
|
||||||
@@ -40,143 +41,152 @@ const regraph = () => {
|
|||||||
_.each(options, (isActive, option) => {
|
_.each(options, (isActive, option) => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
if (selectedMetadata[field]) {
|
if (selectedMetadata[field]) {
|
||||||
selectedMetadata[field].push(option)
|
selectedMetadata[field].push(option);
|
||||||
} else if (!selectedMetadata[field]) {
|
} else if (!selectedMetadata[field]) {
|
||||||
selectedMetadata[field] = [option]
|
selectedMetadata[field] = [option];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
let uri = new URI()
|
let uri = new URI();
|
||||||
uri.setSearch(selectedMetadata)
|
uri.setSearch(selectedMetadata);
|
||||||
console.log(uri.search(), selectedMetadata)
|
console.log(uri.search(), selectedMetadata);
|
||||||
|
|
||||||
dispatch(
|
dispatch(requestCells(uri.search())).then(res => {
|
||||||
requestCells(uri.search())
|
|
||||||
).then((res) => {
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
dispatch({type: "regraph success"})
|
dispatch({ type: "regraph success" });
|
||||||
} else {
|
} else {
|
||||||
dispatch({type: "regraph error"})
|
dispatch({ type: "regraph error" });
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const initialize = () => {
|
const initialize = () => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({type: "initialize started"})
|
dispatch({ type: "initialize started" });
|
||||||
fetch(`${globals.API.prefix}${globals.API.version}initialize`, {
|
fetch(`${globals.API.prefix}${globals.API.version}initialize`, {
|
||||||
method: "get",
|
method: "get",
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json"
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(
|
.then(
|
||||||
data => dispatch({type: "initialize success", data}),
|
data => dispatch({ type: "initialize success", data }),
|
||||||
error => dispatch({type: "initialize error", error})
|
error => dispatch({ type: "initialize error", error })
|
||||||
)
|
);
|
||||||
|
};
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const requestGeneExpressionCounts = () => {
|
const requestGeneExpressionCounts = () => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({type: "get expression started"})
|
dispatch({ type: "get expression started" });
|
||||||
fetch(`${globals.API.prefix}${globals.API.version}expression`, {
|
fetch(`${globals.API.prefix}${globals.API.version}expression`, {
|
||||||
method: "get",
|
method: "get",
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'accept': 'application/json'
|
accept: "application/json"
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(
|
.then(
|
||||||
data => dispatch({type: "get expression success", data}),
|
data => dispatch({ type: "get expression success", data }),
|
||||||
error => dispatch({type: "get expression error", error})
|
error => dispatch({ type: "get expression error", error })
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const requestSingleGeneExpressionCountsForColoringPOST = (gene) => {
|
const requestSingleGeneExpressionCountsForColoringPOST = gene => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({type: "get single gene expression for coloring started"})
|
dispatch({ type: "get single gene expression for coloring started" });
|
||||||
fetch(`${globals.API.prefix}${globals.API.version}expression`, {
|
fetch(`${globals.API.prefix}${globals.API.version}expression`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"genelist": [gene]
|
genelist: [gene]
|
||||||
}),
|
}),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
"accept": "application/json",
|
accept: "application/json",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(
|
.then(
|
||||||
data => dispatch({
|
data =>
|
||||||
type: "color by expression",
|
dispatch({
|
||||||
gene: gene,
|
type: "color by expression",
|
||||||
data
|
gene: gene,
|
||||||
}),
|
data
|
||||||
error => dispatch({type: "get single gene expression for coloring error", error})
|
}),
|
||||||
)
|
error =>
|
||||||
}
|
dispatch({
|
||||||
}
|
type: "get single gene expression for coloring error",
|
||||||
|
error
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const requestGeneExpressionCountsPOST = (genes) => {
|
const requestGeneExpressionCountsPOST = genes => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({type: "get expression started"})
|
dispatch({ type: "get expression started" });
|
||||||
fetch(`${globals.API.prefix}${globals.API.version}expression`, {
|
fetch(`${globals.API.prefix}${globals.API.version}expression`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"genelist": genes
|
genelist: genes
|
||||||
}),
|
}),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
"accept": "application/json",
|
accept: "application/json",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(
|
.then(
|
||||||
data => dispatch({type: "get expression success", data}),
|
data => dispatch({ type: "get expression success", data }),
|
||||||
error => dispatch({type: "get expression error", error})
|
error => dispatch({ type: "get expression error", error })
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const requestDifferentialExpression = (celllist1, celllist2, num_genes = 7) => {
|
const requestDifferentialExpression = (celllist1, celllist2, num_genes = 7) => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({type: "request differential expression started"})
|
dispatch({ type: "request differential expression started" });
|
||||||
fetch(`${globals.API.prefix}${globals.API.version}diffexpression`, {
|
fetch(`${globals.API.prefix}${globals.API.version}diffexpression`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
celllist1,
|
celllist1,
|
||||||
celllist2,
|
celllist2,
|
||||||
num_genes,
|
num_genes
|
||||||
}),
|
}),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
"accept": "application/json",
|
accept: "application/json",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(
|
.then(
|
||||||
(data) => {
|
data => {
|
||||||
/* kick off a secondary action to get all expression counts for all cells now that we know what the top expressed are */
|
/* kick off a secondary action to get all expression counts for all cells now that we know what the top expressed are */
|
||||||
dispatch(
|
dispatch(
|
||||||
requestGeneExpressionCountsPOST(
|
requestGeneExpressionCountsPOST(
|
||||||
_.union(data.data.celllist1.topgenes, data.data.celllist2.topgenes) // ["GPM6B", "FEZ1", "TSPAN31", "PCSK1N", "TUBA1A", "GPM6A", "CLU", "FCER1G", "TYROBP", "C1QB", "CD74", "CYBA", "GPX1", "TMSB4X"]
|
_.union(
|
||||||
|
data.data.celllist1.topgenes,
|
||||||
|
data.data.celllist2.topgenes
|
||||||
|
) // ["GPM6B", "FEZ1", "TSPAN31", "PCSK1N", "TUBA1A", "GPM6A", "CLU", "FCER1G", "TYROBP", "C1QB", "CD74", "CYBA", "GPX1", "TMSB4X"]
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
/* then send the success case action through */
|
/* then send the success case action through */
|
||||||
return dispatch({type: "request differential expression success", data})
|
return dispatch({
|
||||||
|
type: "request differential expression success",
|
||||||
|
data
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error => dispatch({type: "request differential expression error", error})
|
error =>
|
||||||
)
|
dispatch({ type: "request differential expression error", error })
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
initialize,
|
initialize,
|
||||||
@@ -186,4 +196,4 @@ export default {
|
|||||||
requestGeneExpressionCountsPOST,
|
requestGeneExpressionCountsPOST,
|
||||||
requestSingleGeneExpressionCountsForColoringPOST,
|
requestSingleGeneExpressionCountsForColoringPOST,
|
||||||
requestDifferentialExpression
|
requestDifferentialExpression
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import React from 'react';
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import DeckGL, {PointCloudLayer, ScreenGridLayer, COORDINATE_SYSTEM} from 'deck.gl';
|
import DeckGL, {
|
||||||
import OrbitController from './orbit-control';
|
PointCloudLayer,
|
||||||
import { Popup } from './popup';
|
ScreenGridLayer,
|
||||||
|
COORDINATE_SYSTEM
|
||||||
|
} from "deck.gl";
|
||||||
|
import OrbitController from "./orbit-control";
|
||||||
|
import { Popup } from "./popup";
|
||||||
|
|
||||||
class Heatmap extends React.Component {
|
class Heatmap extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -20,16 +24,16 @@ class Heatmap extends React.Component {
|
|||||||
height: 0,
|
height: 0,
|
||||||
points: [],
|
points: [],
|
||||||
sampleExpressionMatrix: [
|
sampleExpressionMatrix: [
|
||||||
{color:[0, 255, 0], position: [100, 100]},
|
{ color: [0, 255, 0], position: [100, 100] },
|
||||||
{color:[0, 255, 0], position: [100, 100]},
|
{ color: [0, 255, 0], position: [100, 100] },
|
||||||
{color:[0, 255, 0], position: [100, 100]},
|
{ color: [0, 255, 0], position: [100, 100] }
|
||||||
],
|
],
|
||||||
progress: 0,
|
progress: 0,
|
||||||
popup: {
|
popup: {
|
||||||
displayed: false,
|
displayed: false,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
title: '',
|
title: ""
|
||||||
},
|
},
|
||||||
viewport: {
|
viewport: {
|
||||||
lookAt: [0, 0, 0],
|
lookAt: [0, 0, 0],
|
||||||
@@ -46,7 +50,7 @@ class Heatmap extends React.Component {
|
|||||||
getColor(cluster) {
|
getColor(cluster) {
|
||||||
let color = [0, 0, 0];
|
let color = [0, 0, 0];
|
||||||
|
|
||||||
switch(cluster){
|
switch (cluster) {
|
||||||
case 0:
|
case 0:
|
||||||
color = [166, 206, 227];
|
color = [166, 206, 227];
|
||||||
break;
|
break;
|
||||||
@@ -82,9 +86,8 @@ class Heatmap extends React.Component {
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener("resize", this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,30 +100,31 @@ class Heatmap extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.onResize);
|
window.removeEventListener("resize", this.onResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData() {
|
fetchData() {
|
||||||
fetch('https://raw.githubusercontent.com/zdenekhynek/data-science-capstone-visualisation/master/public/clusters.json')
|
fetch(
|
||||||
.then((res) => {
|
"https://raw.githubusercontent.com/zdenekhynek/data-science-capstone-visualisation/master/public/clusters.json"
|
||||||
res.json().then((obj) => {
|
).then(res => {
|
||||||
const clusters = Object.keys(obj).map((k) => obj[k])
|
res.json().then(obj => {
|
||||||
const points = clusters.map((cluster) => {
|
const clusters = Object.keys(obj).map(k => obj[k]);
|
||||||
const position = [cluster.x, cluster.y, cluster.z];
|
const points = clusters.map(cluster => {
|
||||||
const color = [255,0,0];
|
const position = [cluster.x, cluster.y, cluster.z];
|
||||||
const id = cluster.id;
|
const color = [255, 0, 0];
|
||||||
const title = cluster.webTitle;
|
const id = cluster.id;
|
||||||
return { id, title, position, color };
|
const title = cluster.webTitle;
|
||||||
});
|
return { id, title, position, color };
|
||||||
|
|
||||||
this.setState({ points, progress: 1 })
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setState({ points, progress: 1 });
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onHover(d) {
|
onHover(d) {
|
||||||
let popup = { displayed: false };
|
let popup = { displayed: false };
|
||||||
console.log('d', d)
|
console.log("d", d);
|
||||||
|
|
||||||
if (d.object) {
|
if (d.object) {
|
||||||
const object = d.object;
|
const object = d.object;
|
||||||
@@ -129,7 +133,7 @@ class Heatmap extends React.Component {
|
|||||||
title: object.title,
|
title: object.title,
|
||||||
displayed: true,
|
displayed: true,
|
||||||
x: d.x,
|
x: d.x,
|
||||||
y: d.y,
|
y: d.y
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +141,8 @@ class Heatmap extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onResize() {
|
onResize() {
|
||||||
const {innerWidth: width, innerHeight: height} = window;
|
const { innerWidth: width, innerHeight: height } = window;
|
||||||
this.setState({width: width / 1.5, height: height / 1.5});
|
this.setState({ width: width / 1.5, height: height / 1.5 });
|
||||||
}
|
}
|
||||||
|
|
||||||
onInitialized(gl) {
|
onInitialized(gl) {
|
||||||
@@ -150,27 +154,30 @@ class Heatmap extends React.Component {
|
|||||||
onChangeViewport(viewport) {
|
onChangeViewport(viewport) {
|
||||||
this.setState({
|
this.setState({
|
||||||
rotating: !viewport.isDragging,
|
rotating: !viewport.isDragging,
|
||||||
viewport: {...this.state.viewport, ...viewport}
|
viewport: { ...this.state.viewport, ...viewport }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate() {
|
onUpdate() {
|
||||||
const {viewport} = this.state;
|
const { viewport } = this.state;
|
||||||
window.requestAnimationFrame(this.onUpdate);
|
window.requestAnimationFrame(this.onUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPointCloudLayer() {
|
renderPointCloudLayer() {
|
||||||
return this.state.points.length && new PointCloudLayer({
|
return (
|
||||||
id: 'point-cloud-layer',
|
this.state.points.length &&
|
||||||
data: this.state.points,
|
new PointCloudLayer({
|
||||||
projectionMode: COORDINATE_SYSTEM.IDENTITY,
|
id: "point-cloud-layer",
|
||||||
pickable: true,
|
data: this.state.points,
|
||||||
onHover: this.onHover,
|
projectionMode: COORDINATE_SYSTEM.IDENTITY,
|
||||||
getPosition: d => d.position,
|
pickable: true,
|
||||||
getNormal: d => [0, 0.5, 0.2],
|
onHover: this.onHover,
|
||||||
getColor: d => d.color,
|
getPosition: d => d.position,
|
||||||
radiusPixels: 2
|
getNormal: d => [0, 0.5, 0.2],
|
||||||
});
|
getColor: d => d.color,
|
||||||
|
radiusPixels: 2
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGridLayer() {
|
renderGridLayer() {
|
||||||
@@ -182,7 +189,7 @@ class Heatmap extends React.Component {
|
|||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
const screenGridLayer = new ScreenGridLayer({
|
const screenGridLayer = new ScreenGridLayer({
|
||||||
id: 'screen-grid-layer',
|
id: "screen-grid-layer",
|
||||||
data: this.state.sampleExpressionMatrix,
|
data: this.state.sampleExpressionMatrix,
|
||||||
projectionMode: COORDINATE_SYSTEM.IDENTITY,
|
projectionMode: COORDINATE_SYSTEM.IDENTITY,
|
||||||
pickable: true,
|
pickable: true,
|
||||||
@@ -195,34 +202,42 @@ class Heatmap extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDeckGLCanvas() {
|
renderDeckGLCanvas() {
|
||||||
const {width, height, viewport} = this.state;
|
const { width, height, viewport } = this.state;
|
||||||
const canvasProps = {width, height, ...viewport};
|
const canvasProps = { width, height, ...viewport };
|
||||||
const glViewport = OrbitController.getViewport(canvasProps);
|
const glViewport = OrbitController.getViewport(canvasProps);
|
||||||
|
|
||||||
return width && height && (
|
return (
|
||||||
<OrbitController {...canvasProps} ref={canvas => {
|
width &&
|
||||||
this.canvas = canvas;
|
height && (
|
||||||
}} onChangeViewport={this.onChangeViewport}>
|
<OrbitController
|
||||||
<DeckGL
|
{...canvasProps}
|
||||||
width={width}
|
ref={canvas => {
|
||||||
height={height}
|
this.canvas = canvas;
|
||||||
viewport={glViewport}
|
}}
|
||||||
layers={[
|
onChangeViewport={this.onChangeViewport}
|
||||||
// this.renderPointCloudLayer(),
|
>
|
||||||
this.renderGridLayer()
|
<DeckGL
|
||||||
].filter(Boolean)}
|
width={width}
|
||||||
onWebGLInitialized={this.onInitialized}/>
|
height={height}
|
||||||
</OrbitController>
|
viewport={glViewport}
|
||||||
|
layers={[
|
||||||
|
// this.renderPointCloudLayer(),
|
||||||
|
this.renderGridLayer()
|
||||||
|
].filter(Boolean)}
|
||||||
|
onWebGLInitialized={this.onInitialized}
|
||||||
|
/>
|
||||||
|
</OrbitController>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {width, height, popup} = this.state;
|
const { width, height, popup } = this.state;
|
||||||
if (!width || !height) {
|
if (!width || !height) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderedPopup = (popup.displayed)? <Popup {...popup} /> : null;
|
const renderedPopup = popup.displayed ? <Popup {...popup} /> : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="heatmap">
|
<div id="heatmap">
|
||||||
@@ -231,6 +246,6 @@ class Heatmap extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Heatmap;
|
export default Heatmap;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
/* global window */
|
/* global window */
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import {PerspectiveViewport} from 'deck.gl';
|
import { PerspectiveViewport } from "deck.gl";
|
||||||
import {vec3} from 'gl-matrix';
|
import { vec3 } from "gl-matrix";
|
||||||
|
|
||||||
/* Utils */
|
/* Utils */
|
||||||
// constrain number between bounds
|
// constrain number between bounds
|
||||||
@@ -15,15 +16,24 @@ function clamp(x, min, max) {
|
|||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ua = typeof window.navigator !== 'undefined' ?
|
const ua =
|
||||||
window.navigator.userAgent.toLowerCase() : '';
|
typeof window.navigator !== "undefined"
|
||||||
const firefox = ua.indexOf('firefox') !== -1;
|
? window.navigator.userAgent.toLowerCase()
|
||||||
|
: "";
|
||||||
|
const firefox = ua.indexOf("firefox") !== -1;
|
||||||
|
|
||||||
/* Interaction */
|
/* Interaction */
|
||||||
|
|
||||||
export default class OrbitController extends Component {
|
export default class OrbitController extends Component {
|
||||||
|
static getViewport({
|
||||||
static getViewport({width, height, lookAt, distance, rotationX, rotationY, fov}) {
|
width,
|
||||||
|
height,
|
||||||
|
lookAt,
|
||||||
|
distance,
|
||||||
|
rotationX,
|
||||||
|
rotationY,
|
||||||
|
fov
|
||||||
|
}) {
|
||||||
const cameraPos = vec3.add([], lookAt, [0, 0, distance]);
|
const cameraPos = vec3.add([], lookAt, [0, 0, distance]);
|
||||||
vec3.rotateX(cameraPos, cameraPos, lookAt, rotationX / 180 * Math.PI);
|
vec3.rotateX(cameraPos, cameraPos, lookAt, rotationX / 180 * Math.PI);
|
||||||
vec3.rotateY(cameraPos, cameraPos, lookAt, rotationY / 180 * Math.PI);
|
vec3.rotateY(cameraPos, cameraPos, lookAt, rotationY / 180 * Math.PI);
|
||||||
@@ -45,25 +55,29 @@ export default class OrbitController extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onDragStart(evt) {
|
_onDragStart(evt) {
|
||||||
const {pageX, pageY} = evt;
|
const { pageX, pageY } = evt;
|
||||||
this._dragStartPos = [pageX, pageY];
|
this._dragStartPos = [pageX, pageY];
|
||||||
this.props.onChangeViewport({isDragging: true});
|
this.props.onChangeViewport({ isDragging: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDrag(evt) {
|
_onDrag(evt) {
|
||||||
if (this._dragStartPos) {
|
if (this._dragStartPos) {
|
||||||
const {pageX, pageY} = evt;
|
const { pageX, pageY } = evt;
|
||||||
const {width, height} = this.props;
|
const { width, height } = this.props;
|
||||||
const dx = (pageX - this._dragStartPos[0]) / width;
|
const dx = (pageX - this._dragStartPos[0]) / width;
|
||||||
const dy = (pageY - this._dragStartPos[1]) / height;
|
const dy = (pageY - this._dragStartPos[1]) / height;
|
||||||
|
|
||||||
if (evt.shiftKey || evt.ctrlKey || evt.altKey || evt.metaKey) {
|
if (evt.shiftKey || evt.ctrlKey || evt.altKey || evt.metaKey) {
|
||||||
// pan
|
// pan
|
||||||
const {lookAt, distance, rotationX, rotationY, fov} = this.props;
|
const { lookAt, distance, rotationX, rotationY, fov } = this.props;
|
||||||
|
|
||||||
const unitsPerPixel = distance / Math.tan(fov / 180 * Math.PI / 2) / 2;
|
const unitsPerPixel = distance / Math.tan(fov / 180 * Math.PI / 2) / 2;
|
||||||
|
|
||||||
const newLookAt = vec3.add([], lookAt, [-unitsPerPixel * dx, unitsPerPixel * dy, 0]);
|
const newLookAt = vec3.add([], lookAt, [
|
||||||
|
-unitsPerPixel * dx,
|
||||||
|
unitsPerPixel * dy,
|
||||||
|
0
|
||||||
|
]);
|
||||||
vec3.rotateX(newLookAt, newLookAt, lookAt, rotationX / 180 * Math.PI);
|
vec3.rotateX(newLookAt, newLookAt, lookAt, rotationX / 180 * Math.PI);
|
||||||
vec3.rotateY(newLookAt, newLookAt, lookAt, rotationY / 180 * Math.PI);
|
vec3.rotateY(newLookAt, newLookAt, lookAt, rotationY / 180 * Math.PI);
|
||||||
|
|
||||||
@@ -72,7 +86,7 @@ export default class OrbitController extends Component {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// rotate
|
// rotate
|
||||||
const {rotationX, rotationY} = this.props;
|
const { rotationX, rotationY } = this.props;
|
||||||
const newRotationX = clamp(rotationX - dy * 180, -90, 90);
|
const newRotationX = clamp(rotationX - dy * 180, -90, 90);
|
||||||
const newRotationY = (rotationY - dx * 180) % 360;
|
const newRotationY = (rotationY - dx * 180) % 360;
|
||||||
|
|
||||||
@@ -88,7 +102,7 @@ export default class OrbitController extends Component {
|
|||||||
|
|
||||||
_onDragEnd() {
|
_onDragEnd() {
|
||||||
this._dragStartPos = null;
|
this._dragStartPos = null;
|
||||||
this.props.onChangeViewport({isDragging: false});
|
this.props.onChangeViewport({ isDragging: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWheel(evt) {
|
_onWheel(evt) {
|
||||||
@@ -107,8 +121,12 @@ export default class OrbitController extends Component {
|
|||||||
value = Math.floor(value / 4);
|
value = Math.floor(value / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {distance, minDistance, maxDistance} = this.props;
|
const { distance, minDistance, maxDistance } = this.props;
|
||||||
const newDistance = clamp(distance * Math.pow(1.01, value), minDistance, maxDistance);
|
const newDistance = clamp(
|
||||||
|
distance * Math.pow(1.01, value),
|
||||||
|
minDistance,
|
||||||
|
maxDistance
|
||||||
|
);
|
||||||
|
|
||||||
this.props.onChangeViewport({
|
this.props.onChangeViewport({
|
||||||
distance: newDistance
|
distance: newDistance
|
||||||
@@ -117,7 +135,7 @@ export default class OrbitController extends Component {
|
|||||||
|
|
||||||
// public API
|
// public API
|
||||||
fitBounds(min, max) {
|
fitBounds(min, max) {
|
||||||
const {fov} = this.props;
|
const { fov } = this.props;
|
||||||
const size = Math.max(max[0] - min[0], max[1] - min[1], max[2] - min[2]);
|
const size = Math.max(max[0] - min[0], max[1] - min[1], max[2] - min[2]);
|
||||||
const newDistance = size / Math.tan(fov / 180 * Math.PI / 2) / 2;
|
const newDistance = size / Math.tan(fov / 180 * Math.PI / 2) / 2;
|
||||||
|
|
||||||
@@ -128,16 +146,17 @@ export default class OrbitController extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div style={{position: 'relative', userSelect: 'none'}}
|
<div
|
||||||
|
style={{ position: "relative", userSelect: "none" }}
|
||||||
onMouseDown={this._onDragStart.bind(this)}
|
onMouseDown={this._onDragStart.bind(this)}
|
||||||
onMouseMove={this._onDrag.bind(this)}
|
onMouseMove={this._onDrag.bind(this)}
|
||||||
onMouseLeave={this._onDragEnd.bind(this)}
|
onMouseLeave={this._onDragEnd.bind(this)}
|
||||||
onMouseUp={this._onDragEnd.bind(this)}
|
onMouseUp={this._onDragEnd.bind(this)}
|
||||||
onWheel={this._onWheel.bind(this)} >
|
onWheel={this._onWheel.bind(this)}
|
||||||
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
</div>
|
||||||
</div>);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
import React, {PureComponent} from 'react';
|
// jshint esversion: 6
|
||||||
|
import React, { PureComponent } from "react";
|
||||||
|
|
||||||
export class Popup extends PureComponent {
|
export class Popup extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { title, x, y } = this.props;
|
const { title, x, y } = this.props;
|
||||||
const style = {
|
const style = {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: y,
|
top: y,
|
||||||
left: x,
|
left: x,
|
||||||
maxWidth: '200px',
|
maxWidth: "200px",
|
||||||
padding: '10px',
|
padding: "10px",
|
||||||
color: 'white',
|
color: "white",
|
||||||
backgroundColor: 'black',
|
backgroundColor: "black",
|
||||||
pointerEvents: 'none',
|
pointerEvents: "none",
|
||||||
transform: 'translate(10px, -50%)',
|
transform: "translate(10px, -50%)"
|
||||||
};
|
};
|
||||||
|
|
||||||
const arrowStyle = {
|
const arrowStyle = {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: '50%',
|
top: "50%",
|
||||||
left: '-14px',
|
left: "-14px",
|
||||||
width: '7px',
|
width: "7px",
|
||||||
height: '5px',
|
height: "5px",
|
||||||
boxSizing: 'border-box',
|
boxSizing: "border-box",
|
||||||
transform: 'translateY(-50%)',
|
transform: "translateY(-50%)",
|
||||||
border: '7px solid transparent',
|
border: "7px solid transparent",
|
||||||
borderRight: '7px solid black',
|
borderRight: "7px solid black"
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,13 +33,13 @@ export class Popup extends PureComponent {
|
|||||||
<div style={arrowStyle} />
|
<div style={arrowStyle} />
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup.defaultProps = {
|
Popup.defaultProps = {
|
||||||
id: 'id',
|
id: "id",
|
||||||
title: '',
|
title: "",
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0
|
||||||
};
|
};
|
||||||
|
|||||||
+40
-32
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import Helmet from "react-helmet";
|
import Helmet from "react-helmet";
|
||||||
@@ -15,33 +16,30 @@ import actions from "../actions";
|
|||||||
|
|
||||||
import SectionHeader from "./framework/sectionHeader";
|
import SectionHeader from "./framework/sectionHeader";
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
return {
|
return {
|
||||||
cells: state.cells
|
cells: state.cells
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {};
|
||||||
|
}
|
||||||
};
|
_onURLChanged() {
|
||||||
|
this.props.dispatch({ type: "url changed", url: document.location.href });
|
||||||
}
|
}
|
||||||
_onURLChanged () {
|
|
||||||
this.props.dispatch({type: 'url changed', url: document.location.href});
|
|
||||||
};
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|
||||||
/* listen for url changes, fire one when we start the app up */
|
/* listen for url changes, fire one when we start the app up */
|
||||||
window.addEventListener('popstate', this._onURLChanged);
|
window.addEventListener("popstate", this._onURLChanged);
|
||||||
this._onURLChanged();
|
this._onURLChanged();
|
||||||
|
|
||||||
this.props.dispatch(actions.initialize())
|
this.props.dispatch(actions.initialize());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
first request includes query straight off the url bar for now
|
first request includes query straight off the url bar for now
|
||||||
*/
|
*/
|
||||||
this.props.dispatch(actions.requestCells(window.location.search))
|
this.props.dispatch(actions.requestCells(window.location.search));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -50,32 +48,42 @@ class App extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Helmet title="cellxgene" />
|
<Helmet title="cellxgene" />
|
||||||
{
|
{this.props.cells.loading ? (
|
||||||
this.props.cells.loading ?
|
<div
|
||||||
<div style={{position: "fixed", left: window.innerWidth / 2, top: 150}}>
|
style={{ position: "fixed", left: window.innerWidth / 2, top: 150 }}
|
||||||
<PulseLoader color="rgb(0,0,0)" size="10px" margin="4px"/>
|
>
|
||||||
<span style={{fontFamily: globals.accentFont, fontStyle: "italic"}}>loading cells</span>
|
<PulseLoader color="rgb(0,0,0)" size="10px" margin="4px" />
|
||||||
</div> :
|
<span
|
||||||
null
|
style={{ fontFamily: globals.accentFont, fontStyle: "italic" }}
|
||||||
}
|
>
|
||||||
|
loading cells
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{this.props.cells.error ? "Error loading cells" : null}
|
{this.props.cells.error ? "Error loading cells" : null}
|
||||||
{false ? <Joy data={this.state.expressions && this.state.expressions.data}/> : ""}
|
{false ? (
|
||||||
|
<Joy data={this.state.expressions && this.state.expressions.data} />
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<LeftSideBar/>
|
<LeftSideBar />
|
||||||
<div style={{
|
<div
|
||||||
padding: 15,
|
style={{
|
||||||
backgroundColor: "#F7F7F7",
|
padding: 15,
|
||||||
width: 1440 - 410 /* but responsive */,
|
backgroundColor: "#F7F7F7",
|
||||||
marginLeft: 350 /* but responsive */
|
width: 1440 - 410 /* but responsive */,
|
||||||
}}>
|
marginLeft: 350 /* but responsive */
|
||||||
<Graph/>
|
}}
|
||||||
<DynamicScatterplot/>
|
>
|
||||||
|
<Graph />
|
||||||
|
<DynamicScatterplot />
|
||||||
{/*<Parallel/>*/}
|
{/*<Parallel/>*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import styles from "./categorical.css";
|
import styles from "./categorical.css";
|
||||||
import SectionHeader from "../framework/sectionHeader"
|
import SectionHeader from "../framework/sectionHeader";
|
||||||
import Value from "./value";
|
import Value from "./value";
|
||||||
import { alphabeticallySortedValues } from "./util";
|
import { alphabeticallySortedValues } from "./util";
|
||||||
|
|
||||||
import FaArrowRight from 'react-icons/lib/fa/angle-right';
|
import FaArrowRight from "react-icons/lib/fa/angle-right";
|
||||||
import FaArrowDown from 'react-icons/lib/fa/angle-down';
|
import FaArrowDown from "react-icons/lib/fa/angle-down";
|
||||||
import FaPaintBrush from 'react-icons/lib/fa/paint-brush';
|
import FaPaintBrush from "react-icons/lib/fa/paint-brush";
|
||||||
|
|
||||||
|
@connect(state => {
|
||||||
@connect((state) => {
|
|
||||||
return {
|
return {
|
||||||
colorAccessor: state.controls.colorAccessor
|
colorAccessor: state.controls.colorAccessor
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class Category extends React.Component {
|
class Category extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -31,22 +31,22 @@ class Category extends React.Component {
|
|||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "color by categorical metadata",
|
type: "color by categorical metadata",
|
||||||
colorAccessor: this.props.metadataField
|
colorAccessor: this.props.metadataField
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
toggleAll() {
|
toggleAll() {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "categorical metadata filter all of these",
|
type: "categorical metadata filter all of these",
|
||||||
metadataField: this.props.metadataField
|
metadataField: this.props.metadataField
|
||||||
})
|
});
|
||||||
this.setState({isChecked: true})
|
this.setState({ isChecked: true });
|
||||||
}
|
}
|
||||||
toggleNone() {
|
toggleNone() {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "categorical metadata filter none of these",
|
type: "categorical metadata filter none of these",
|
||||||
metadataField: this.props.metadataField,
|
metadataField: this.props.metadataField,
|
||||||
value: this.props.value
|
value: this.props.value
|
||||||
})
|
});
|
||||||
this.setState({isChecked: false})
|
this.setState({ isChecked: false });
|
||||||
}
|
}
|
||||||
renderCategoryItems() {
|
renderCategoryItems() {
|
||||||
return _.map(alphabeticallySortedValues(this.props.values), (v, i) => {
|
return _.map(alphabeticallySortedValues(this.props.values), (v, i) => {
|
||||||
@@ -56,43 +56,60 @@ class Category extends React.Component {
|
|||||||
metadataField={this.props.metadataField}
|
metadataField={this.props.metadataField}
|
||||||
count={this.props.values[v]}
|
count={this.props.values[v]}
|
||||||
value={v}
|
value={v}
|
||||||
i={i} />
|
i={i}
|
||||||
)
|
/>
|
||||||
})
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div
|
||||||
|
style={{
|
||||||
// display: "flex",
|
// display: "flex",
|
||||||
// alignItems: "baseline",
|
// alignItems: "baseline",
|
||||||
maxWidth: globals.maxControlsWidth,
|
maxWidth: globals.maxControlsWidth
|
||||||
}}>
|
}}
|
||||||
<div style={{
|
>
|
||||||
display: "flex",
|
<div
|
||||||
justifyContent: "space-between",
|
style={{
|
||||||
alignItems: "baseline"
|
display: "flex",
|
||||||
}}>
|
justifyContent: "space-between",
|
||||||
<p style={{
|
alignItems: "baseline"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
// flexShrink: 0,
|
// flexShrink: 0,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
// textAlign: "right",
|
// textAlign: "right",
|
||||||
// fontFamily: globals.accentFont,
|
// fontFamily: globals.accentFont,
|
||||||
// fontStyle: "italic",
|
// fontStyle: "italic",
|
||||||
margin: "3px 10px 3px 0px",
|
margin: "3px 10px 3px 0px"
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
style={{cursor: "pointer", display: "inline-block", position: "relative", top: 2}}
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
display: "inline-block",
|
||||||
|
position: "relative",
|
||||||
|
top: 2
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.setState({isExpanded: !this.state.isExpanded})
|
this.setState({ isExpanded: !this.state.isExpanded });
|
||||||
}}>
|
}}
|
||||||
{this.state.isExpanded ? <FaArrowDown /> : <FaArrowRight/> }
|
>
|
||||||
|
{this.state.isExpanded ? <FaArrowDown /> : <FaArrowRight />}
|
||||||
</span>
|
</span>
|
||||||
{this.props.metadataField}
|
{this.props.metadataField}
|
||||||
<input
|
<input
|
||||||
onChange={this.state.isChecked ? this.toggleNone.bind(this) : this.toggleAll.bind(this)}
|
onChange={
|
||||||
checked={this.state.isChecked}
|
this.state.isChecked
|
||||||
type="checkbox"/>
|
? this.toggleNone.bind(this)
|
||||||
|
: this.toggleAll.bind(this)
|
||||||
|
}
|
||||||
|
checked={this.state.isChecked}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
onClick={this.handleColorChange.bind(this)}
|
onClick={this.handleColorChange.bind(this)}
|
||||||
style={{
|
style={{
|
||||||
@@ -100,77 +117,69 @@ class Category extends React.Component {
|
|||||||
marginLeft: 4,
|
marginLeft: 4,
|
||||||
// padding: this.props.colorAccessor === this.props.metadataField ? 3 : "auto",
|
// padding: this.props.colorAccessor === this.props.metadataField ? 3 : "auto",
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
color: this.props.colorAccessor === this.props.metadataField ? globals.brightBlue : "black",
|
color:
|
||||||
|
this.props.colorAccessor === this.props.metadataField
|
||||||
|
? globals.brightBlue
|
||||||
|
: "black",
|
||||||
// backgroundColor: this.props.colorAccessor === this.props.metadataField ? globals.brightBlue : "inherit",
|
// backgroundColor: this.props.colorAccessor === this.props.metadataField ? globals.brightBlue : "inherit",
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
top: 2,
|
top: 2,
|
||||||
cursor: "pointer",
|
cursor: "pointer"
|
||||||
}}>
|
}}
|
||||||
<FaPaintBrush/>
|
>
|
||||||
|
<FaPaintBrush />
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>{this.state.isExpanded ? this.renderCategoryItems() : null}</div>
|
||||||
{
|
|
||||||
this.state.isExpanded ? this.renderCategoryItems() : null
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
|
const ranges =
|
||||||
const ranges = state.cells.cells && state.cells.cells.data.ranges ? state.cells.cells.data.ranges : null;
|
state.cells.cells && state.cells.cells.data.ranges
|
||||||
|
? state.cells.cells.data.ranges
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ranges
|
ranges
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class Categories extends React.Component {
|
class Categories extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {};
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
if (!this.props.ranges) return null
|
if (!this.props.ranges) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div
|
||||||
width: 310,
|
style={{
|
||||||
marginRight: 40,
|
width: 310,
|
||||||
paddingRight: 20,
|
marginRight: 40,
|
||||||
flexShrink: 0,
|
paddingRight: 20,
|
||||||
// height: 700,
|
flexShrink: 0
|
||||||
// overflow: "auto",
|
// height: 700,
|
||||||
}}>
|
// overflow: "auto",
|
||||||
|
}}
|
||||||
{
|
>
|
||||||
_.map(this.props.ranges, (value, key) => {
|
{_.map(this.props.ranges, (value, key) => {
|
||||||
const isColorField = key.includes("color") || key.includes("Color");
|
const isColorField = key.includes("color") || key.includes("Color");
|
||||||
if (
|
if (value.options && key !== "CellName" && !isColorField) {
|
||||||
value.options &&
|
return (
|
||||||
key !== "CellName" &&
|
<Category key={key} metadataField={key} values={value.options} />
|
||||||
!isColorField
|
);
|
||||||
) {
|
}
|
||||||
return (
|
})}
|
||||||
<Category
|
|
||||||
key={key}
|
|
||||||
metadataField={key}
|
|
||||||
values={value.options}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Categories;
|
export default Categories;
|
||||||
|
|
||||||
@@ -190,7 +199,6 @@ export default Categories;
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Each category has a color associated with it - ie., color by location should show up on these buttons,
|
Each category has a color associated with it - ie., color by location should show up on these buttons,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
export const alphabeticallySortedValues = (values) => {
|
// jshint esversion: 6
|
||||||
|
export const alphabeticallySortedValues = values => {
|
||||||
return Object.keys(values).sort((a, b) => {
|
return Object.keys(values).sort((a, b) => {
|
||||||
var textA = a.toUpperCase();
|
var textA = a.toUpperCase();
|
||||||
var textB = b.toUpperCase();
|
var textB = b.toUpperCase();
|
||||||
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
|
return textA < textB ? -1 : textA > textB ? 1 : 0;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
return {
|
return {
|
||||||
categoricalAsBooleansMap: state.controls.categoricalAsBooleansMap,
|
categoricalAsBooleansMap: state.controls.categoricalAsBooleansMap,
|
||||||
colorScale: state.controls.colorScale,
|
colorScale: state.controls.colorScale,
|
||||||
colorAccessor: state.controls.colorAccessor,
|
colorAccessor: state.controls.colorAccessor
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class CategoryValue extends React.Component {
|
class CategoryValue extends React.Component {
|
||||||
|
|
||||||
toggleOff() {
|
toggleOff() {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "categorical metadata filter deselect",
|
type: "categorical metadata filter deselect",
|
||||||
@@ -28,11 +28,16 @@ class CategoryValue extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
if (!this.props.categoricalAsBooleansMap) return null
|
if (!this.props.categoricalAsBooleansMap) return null;
|
||||||
|
|
||||||
const selected = this.props.categoricalAsBooleansMap[this.props.metadataField][this.props.value]
|
const selected = this.props.categoricalAsBooleansMap[
|
||||||
const c = this.props.metadataField === this.props.colorAccessor /* this is the color scale, so add swatches below */
|
this.props.metadataField
|
||||||
|
][this.props.value];
|
||||||
|
const c =
|
||||||
|
this.props.metadataField ===
|
||||||
|
this.props
|
||||||
|
.colorAccessor; /* this is the color scale, so add swatches below */
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -41,32 +46,42 @@ class CategoryValue extends React.Component {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "baseline",
|
alignItems: "baseline",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
fontWeight: selected ? 700 : 400,
|
fontWeight: selected ? 700 : 400
|
||||||
}}>
|
}}
|
||||||
<p style={{
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
paddingLeft: 15,
|
paddingLeft: 15,
|
||||||
width: 200,
|
width: 200,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
margin: 0,
|
margin: 0,
|
||||||
lineHeight: "1em"
|
lineHeight: "1em"
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
onChange={selected ? this.toggleOff.bind(this) : this.toggleOn.bind(this)}
|
onChange={
|
||||||
|
selected ? this.toggleOff.bind(this) : this.toggleOn.bind(this)
|
||||||
|
}
|
||||||
checked={selected}
|
checked={selected}
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
{this.props.value}
|
/>
|
||||||
|
{this.props.value}
|
||||||
</p>
|
</p>
|
||||||
<p style={{
|
<p
|
||||||
|
style={{
|
||||||
padding: "1px 10px",
|
padding: "1px 10px",
|
||||||
backgroundColor: c ? this.props.colorScale(this.props.value) : "inherit",
|
backgroundColor: c
|
||||||
|
? this.props.colorScale(this.props.value)
|
||||||
|
: "inherit",
|
||||||
color: c ? "white" : "black",
|
color: c ? "white" : "black",
|
||||||
margin: 0,
|
margin: 0,
|
||||||
lineHeight: "1em"
|
lineHeight: "1em"
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{this.props.count}
|
{this.props.count}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
/* rc slider https://www.npmjs.com/package/rc-slider */
|
/* rc slider https://www.npmjs.com/package/rc-slider */
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import styles from './parallelCoordinates.css';
|
// jshint esversion: 6
|
||||||
import {
|
import styles from "./parallelCoordinates.css";
|
||||||
yAxis,
|
import { yAxis, brushstart } from "./util";
|
||||||
brushstart,
|
|
||||||
} from "./util";
|
|
||||||
|
|
||||||
|
|
||||||
const drawAxes = (
|
const drawAxes = (
|
||||||
svg,
|
svg,
|
||||||
@@ -13,19 +10,19 @@ const drawAxes = (
|
|||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
handleBrushAction,
|
handleBrushAction,
|
||||||
handleColorAction,
|
handleColorAction
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
/*****************************************
|
/*****************************************
|
||||||
******************************************
|
******************************************
|
||||||
Handles a brush event, toggling the display of foreground lines.
|
Handles a brush event, toggling the display of foreground lines.
|
||||||
******************************************
|
******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
|
|
||||||
function brush () {
|
function brush() {
|
||||||
var actives = [];
|
var actives = [];
|
||||||
svg.selectAll(".parcoords_axis .parcoords_brush")
|
svg
|
||||||
.filter(function (d) {
|
.selectAll(".parcoords_axis .parcoords_brush")
|
||||||
|
.filter(function(d) {
|
||||||
return d3.brushSelection(this);
|
return d3.brushSelection(this);
|
||||||
})
|
})
|
||||||
.each(function(d) {
|
.each(function(d) {
|
||||||
@@ -35,46 +32,57 @@ const drawAxes = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
/* fire action, with selected dimensions & their values */
|
/* fire action, with selected dimensions & their values */
|
||||||
handleBrushAction(actives)
|
handleBrushAction(actives);
|
||||||
}
|
}
|
||||||
|
|
||||||
var axes = svg.selectAll(".parcoords_axis")
|
var axes = svg
|
||||||
.data(dimensions)
|
.selectAll(".parcoords_axis")
|
||||||
.enter().append("g")
|
.data(dimensions)
|
||||||
.attr("class", `${styles.axis} parcoords_axis`)
|
.enter()
|
||||||
.attr("transform", (d,i) => { return "translate(" + xscale(i) + ")"; });
|
.append("g")
|
||||||
|
.attr("class", `${styles.axis} parcoords_axis`)
|
||||||
|
.attr("transform", (d, i) => {
|
||||||
|
return "translate(" + xscale(i) + ")";
|
||||||
|
});
|
||||||
|
|
||||||
axes.append("g")
|
axes
|
||||||
.each(function(d) {
|
.append("g")
|
||||||
var renderAxis = "axis" in d
|
.each(function(d) {
|
||||||
? d.axis.scale(d.scale) // custom axis
|
var renderAxis =
|
||||||
: yAxis.scale(d.scale); // default axis
|
"axis" in d
|
||||||
d3.select(this).call(renderAxis);
|
? d.axis.scale(d.scale) // custom axis
|
||||||
})
|
: yAxis.scale(d.scale); // default axis
|
||||||
|
d3.select(this).call(renderAxis);
|
||||||
|
})
|
||||||
.append("text")
|
.append("text")
|
||||||
.on("click", (d) => { handleColorAction(d.key) })
|
.on("click", d => {
|
||||||
.attr("class", styles.title)
|
handleColorAction(d.key);
|
||||||
.attr("text-anchor", "start")
|
})
|
||||||
.text(function(d) { return "description" in d ? d.description + " 🖌️" : d.key + " 🖌️"; });
|
.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.
|
// Add and store a brush for each axis.
|
||||||
axes.append("g")
|
axes
|
||||||
.attr("class", `${styles.brush} parcoords_brush`)
|
.append("g")
|
||||||
.each(function(d) {
|
.attr("class", `${styles.brush} parcoords_brush`)
|
||||||
d3.select(this).call(
|
.each(function(d) {
|
||||||
d.brush = d3.brushY()
|
d3.select(this).call(
|
||||||
.extent([[-10,0], [10, height]])
|
(d.brush = d3
|
||||||
.on("start", brushstart)
|
.brushY()
|
||||||
.on("brush", brush)
|
.extent([[-10, 0], [10, height]])
|
||||||
.on("end", brush)
|
.on("start", brushstart)
|
||||||
)
|
.on("brush", brush)
|
||||||
})
|
.on("end", brush))
|
||||||
|
);
|
||||||
|
})
|
||||||
.selectAll("rect")
|
.selectAll("rect")
|
||||||
.attr("x", -8)
|
.attr("x", -8)
|
||||||
.attr("width", 16);
|
.attr("width", 16);
|
||||||
|
|
||||||
return axes;
|
return axes;
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default drawAxes;
|
export default drawAxes;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import { project } from "./util";
|
||||||
project
|
|
||||||
} from "./util";
|
|
||||||
|
|
||||||
import renderQueue from "../../util/renderQueue";
|
import renderQueue from "../../util/renderQueue";
|
||||||
|
|
||||||
@@ -16,16 +15,15 @@ const drawLinesCanvas = (
|
|||||||
dimensions,
|
dimensions,
|
||||||
xscale,
|
xscale,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
colorScale,
|
colorScale
|
||||||
) => {
|
) => {
|
||||||
return (d) => {
|
return d => {
|
||||||
|
ctx.globalAlpha = 0.1;
|
||||||
ctx.globalAlpha = .1;
|
|
||||||
|
|
||||||
if (d["__selected__"]) {
|
if (d["__selected__"]) {
|
||||||
ctx.strokeStyle = d["__color__"];
|
ctx.strokeStyle = d["__color__"];
|
||||||
} else {
|
} else {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@@ -36,31 +34,31 @@ const drawLinesCanvas = (
|
|||||||
// this bit renders horizontal lines on the previous/next
|
// this bit renders horizontal lines on the previous/next
|
||||||
// dimensions, so that sandwiched null values are visible
|
// dimensions, so that sandwiched null values are visible
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
var prev = coords[i-1];
|
var prev = coords[i - 1];
|
||||||
if (prev !== null) {
|
if (prev !== null) {
|
||||||
ctx.moveTo(prev[0],prev[1]);
|
ctx.moveTo(prev[0], prev[1]);
|
||||||
ctx.lineTo(prev[0]+6,prev[1]);
|
ctx.lineTo(prev[0] + 6, prev[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i < coords.length-1) {
|
if (i < coords.length - 1) {
|
||||||
var next = coords[i+1];
|
var next = coords[i + 1];
|
||||||
if (next !== null) {
|
if (next !== null) {
|
||||||
ctx.moveTo(next[0]-6,next[1]);
|
ctx.moveTo(next[0] - 6, next[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
ctx.moveTo(p[0],p[1]);
|
ctx.moveTo(p[0], p[1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.lineTo(p[0],p[1]);
|
ctx.lineTo(p[0], p[1]);
|
||||||
});
|
});
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const drawCellLinesUsingRenderQueue = (
|
const drawCellLinesUsingRenderQueue = (
|
||||||
metadata,
|
metadata,
|
||||||
@@ -68,21 +66,14 @@ const drawCellLinesUsingRenderQueue = (
|
|||||||
xscale,
|
xscale,
|
||||||
ctx,
|
ctx,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
colorScale,
|
colorScale
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
const _renderLinesWithQueue = renderQueue(
|
const _renderLinesWithQueue = renderQueue(
|
||||||
drawLinesCanvas(
|
drawLinesCanvas(ctx, dimensions, xscale, colorAccessor, colorScale)
|
||||||
ctx,
|
|
||||||
dimensions,
|
|
||||||
xscale,
|
|
||||||
colorAccessor,
|
|
||||||
colorScale,
|
|
||||||
)
|
|
||||||
).rate(50);
|
).rate(50);
|
||||||
_renderLinesWithQueue(metadata);
|
_renderLinesWithQueue(metadata);
|
||||||
return _renderLinesWithQueue;
|
return _renderLinesWithQueue;
|
||||||
}
|
};
|
||||||
|
|
||||||
const drawCellLinesSync = (
|
const drawCellLinesSync = (
|
||||||
metadata,
|
metadata,
|
||||||
@@ -90,17 +81,17 @@ const drawCellLinesSync = (
|
|||||||
xscale,
|
xscale,
|
||||||
ctx,
|
ctx,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
colorScale,
|
colorScale
|
||||||
) => {
|
) => {
|
||||||
const _draw = drawLinesCanvas(
|
const _draw = drawLinesCanvas(
|
||||||
ctx,
|
ctx,
|
||||||
dimensions,
|
dimensions,
|
||||||
xscale,
|
xscale,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
colorScale,
|
colorScale
|
||||||
)
|
);
|
||||||
_.each(metadata, _draw)
|
_.each(metadata, _draw);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default drawCellLinesUsingRenderQueue;
|
export default drawCellLinesUsingRenderQueue;
|
||||||
// export default drawCellLinesUsingRenderQueue;
|
// export default drawCellLinesUsingRenderQueue;
|
||||||
|
|||||||
@@ -3,22 +3,31 @@ https://bl.ocks.org/mbostock/4341954
|
|||||||
https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
|
https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
|
||||||
https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771
|
https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
|
const ranges =
|
||||||
|
state.cells.cells && state.cells.cells.data.ranges
|
||||||
|
? state.cells.cells.data.ranges
|
||||||
|
: null;
|
||||||
|
const metadata =
|
||||||
|
state.cells.cells && state.cells.cells.data.metadata
|
||||||
|
? state.cells.cells.data.metadata
|
||||||
|
: null;
|
||||||
|
|
||||||
const ranges = state.cells.cells && state.cells.cells.data.ranges ? state.cells.cells.data.ranges : null;
|
const initializeRanges =
|
||||||
const metadata = state.cells.cells && state.cells.cells.data.metadata ? state.cells.cells.data.metadata : null;
|
state.initialize.data && state.initialize.data.data.ranges
|
||||||
|
? state.initialize.data.data.ranges
|
||||||
const initializeRanges = state.initialize.data && state.initialize.data.data.ranges ? state.initialize.data.data.ranges : null;
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
colorAccessor: state.controls.colorAccessor,
|
colorAccessor: state.controls.colorAccessor,
|
||||||
colorScale: state.controls.colorScale,
|
colorScale: state.controls.colorScale,
|
||||||
currentCellSelection: state.controls.currentCellSelection,
|
currentCellSelection: state.controls.currentCellSelection
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class HistogramBrush extends React.Component {
|
class HistogramBrush extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -33,15 +42,11 @@ class HistogramBrush extends React.Component {
|
|||||||
ctx: null,
|
ctx: null,
|
||||||
axes: null,
|
axes: null,
|
||||||
dimensions: null,
|
dimensions: null,
|
||||||
brush: null,
|
brush: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {}
|
||||||
|
componentDidUpdate() {}
|
||||||
}
|
|
||||||
componentDidUpdate() {
|
|
||||||
|
|
||||||
}
|
|
||||||
onBrush(selection, x) {
|
onBrush(selection, x) {
|
||||||
return () => {
|
return () => {
|
||||||
if (d3.event.selection) {
|
if (d3.event.selection) {
|
||||||
@@ -57,81 +62,104 @@ class HistogramBrush extends React.Component {
|
|||||||
range: null
|
range: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
drawHistogram(svgRef) {
|
drawHistogram(svgRef) {
|
||||||
|
|
||||||
const allValuesForContinuousFieldAsArray = _.map(
|
const allValuesForContinuousFieldAsArray = _.map(
|
||||||
this.props.currentCellSelection,
|
this.props.currentCellSelection,
|
||||||
this.props.metadataField
|
this.props.metadataField
|
||||||
)
|
);
|
||||||
|
|
||||||
var x = d3.scaleLinear()
|
var x = d3
|
||||||
.domain(d3.extent(allValuesForContinuousFieldAsArray, (d) => +d))
|
.scaleLinear()
|
||||||
.range([0, this.width])
|
.domain(d3.extent(allValuesForContinuousFieldAsArray, d => +d))
|
||||||
// .range([margin.left, width - margin.right]);
|
.range([0, this.width]);
|
||||||
|
// .range([margin.left, width - margin.right]);
|
||||||
|
|
||||||
var y = d3.scaleLinear()
|
var y = d3.scaleLinear().range([this.height - this.marginBottom, 0]);
|
||||||
.range([this.height - this.marginBottom, 0])
|
// .range([height - margin.bottom, margin.top]);
|
||||||
// .range([height - margin.bottom, margin.top]);
|
|
||||||
|
|
||||||
const bins = d3.histogram()
|
const bins = d3
|
||||||
.domain(x.domain())
|
.histogram()
|
||||||
.thresholds(40)(allValuesForContinuousFieldAsArray)
|
.domain(x.domain())
|
||||||
|
.thresholds(40)(allValuesForContinuousFieldAsArray);
|
||||||
|
|
||||||
d3.select(svgRef)
|
d3
|
||||||
|
.select(svgRef)
|
||||||
.insert("g", "*")
|
.insert("g", "*")
|
||||||
.attr("fill", "#bbb")
|
.attr("fill", "#bbb")
|
||||||
.selectAll("rect")
|
.selectAll("rect")
|
||||||
.data(bins)
|
.data(bins)
|
||||||
.enter().append("rect")
|
.enter()
|
||||||
.attr("x", function(d) { return x(d.x0) + 1; })
|
.append("rect")
|
||||||
.attr("y", function(d) { return y(d.length / allValuesForContinuousFieldAsArray.length); })
|
.attr("x", function(d) {
|
||||||
.attr("width", function(d) { return Math.abs(x(d.x1) - x(d.x0) - 1); })
|
return x(d.x0) + 1;
|
||||||
.attr("height", function(d) { return y(0) - y(d.length / allValuesForContinuousFieldAsArray.length); });
|
})
|
||||||
|
.attr("y", function(d) {
|
||||||
|
return y(d.length / allValuesForContinuousFieldAsArray.length);
|
||||||
|
})
|
||||||
|
.attr("width", function(d) {
|
||||||
|
return Math.abs(x(d.x1) - x(d.x0) - 1);
|
||||||
|
})
|
||||||
|
.attr("height", function(d) {
|
||||||
|
return y(0) - y(d.length / allValuesForContinuousFieldAsArray.length);
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.state.brush && !this.state.axis) {
|
if (!this.state.brush && !this.state.axis) {
|
||||||
const brush = d3.select(svgRef)
|
const brush = d3
|
||||||
.append('g')
|
.select(svgRef)
|
||||||
.attr('class', 'brush')
|
.append("g")
|
||||||
|
.attr("class", "brush")
|
||||||
.call(
|
.call(
|
||||||
d3.brushX()
|
d3
|
||||||
.on('end', this.onBrush(this.props.metadataField, x.invert).bind(this))
|
.brushX()
|
||||||
)
|
.on(
|
||||||
|
"end",
|
||||||
|
this.onBrush(this.props.metadataField, x.invert).bind(this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const xAxis = d3.select(svgRef)
|
const xAxis = d3
|
||||||
|
.select(svgRef)
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("class", "axis axis--x")
|
.attr("class", "axis axis--x")
|
||||||
.attr("transform", "translate(0," + (this.height - this.marginBottom) + ")")
|
.attr(
|
||||||
.call(
|
"transform",
|
||||||
d3.axisBottom(x)
|
"translate(0," + (this.height - this.marginBottom) + ")"
|
||||||
.ticks(5)
|
|
||||||
)
|
)
|
||||||
|
.call(d3.axisBottom(x).ticks(5))
|
||||||
.append("text")
|
.append("text")
|
||||||
.attr("x", 300)
|
.attr("x", 300)
|
||||||
.attr("y", -6)
|
.attr("y", -6)
|
||||||
.attr("fill", "#000")
|
.attr("fill", "#000")
|
||||||
.attr("text-anchor", "end")
|
.attr("text-anchor", "end")
|
||||||
.attr("font-weight", "bold")
|
.attr("font-weight", "bold")
|
||||||
.text(this.props.metadataField)
|
.text(this.props.metadataField);
|
||||||
|
|
||||||
this.setState({brush, xAxis})
|
this.setState({ brush, xAxis });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{marginTop: 10}} id={"histogram_" + this.props.metadataField}>
|
<div
|
||||||
<svg width={this.width} height={this.height} ref={(svgRef) => { this.drawHistogram(svgRef)}}>
|
style={{ marginTop: 10 }}
|
||||||
|
id={"histogram_" + this.props.metadataField}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width={this.width}
|
||||||
|
height={this.height}
|
||||||
|
ref={svgRef => {
|
||||||
|
this.drawHistogram(svgRef);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{this.props.ranges.min}
|
{this.props.ranges.min}
|
||||||
{" to "}
|
{" to "}
|
||||||
{this.props.ranges.max}
|
{this.props.ranges.max}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default HistogramBrush;
|
export default HistogramBrush;
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
/* rc slider https://www.npmjs.com/package/rc-slider */
|
/* rc slider https://www.npmjs.com/package/rc-slider */
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import styles from './parallelCoordinates.css';
|
import styles from "./parallelCoordinates.css";
|
||||||
import SectionHeader from "../framework/sectionHeader";
|
import SectionHeader from "../framework/sectionHeader";
|
||||||
|
|
||||||
import setupParallelCoordinates from "./setupParallelCoordinates";
|
import setupParallelCoordinates from "./setupParallelCoordinates";
|
||||||
import drawAxes from "./drawAxes";
|
import drawAxes from "./drawAxes";
|
||||||
import drawLinesCanvas from "./drawLinesCanvas";
|
import drawLinesCanvas from "./drawLinesCanvas";
|
||||||
|
|
||||||
import {
|
import { margin, width, height, createDimensions } from "./util";
|
||||||
margin,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
createDimensions,
|
|
||||||
} from "./util";
|
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
|
const ranges =
|
||||||
|
state.cells.cells && state.cells.cells.data.ranges
|
||||||
|
? state.cells.cells.data.ranges
|
||||||
|
: null;
|
||||||
|
const metadata =
|
||||||
|
state.cells.cells && state.cells.cells.data.metadata
|
||||||
|
? state.cells.cells.data.metadata
|
||||||
|
: null;
|
||||||
|
|
||||||
const ranges = state.cells.cells && state.cells.cells.data.ranges ? state.cells.cells.data.ranges : null;
|
const initializeRanges =
|
||||||
const metadata = state.cells.cells && state.cells.cells.data.metadata ? state.cells.cells.data.metadata : null;
|
state.initialize.data && state.initialize.data.data.ranges
|
||||||
|
? state.initialize.data.data.ranges
|
||||||
const initializeRanges = state.initialize.data && state.initialize.data.data.ranges ? state.initialize.data.data.ranges : null;
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ranges,
|
ranges,
|
||||||
@@ -33,8 +37,8 @@ import {
|
|||||||
colorScale: state.controls.colorScale,
|
colorScale: state.controls.colorScale,
|
||||||
graphBrushSelection: state.controls.graphBrushSelection,
|
graphBrushSelection: state.controls.graphBrushSelection,
|
||||||
currentCellSelection: state.controls.currentCellSelection,
|
currentCellSelection: state.controls.currentCellSelection,
|
||||||
axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn,
|
axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class Parallel extends React.Component {
|
class Parallel extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -43,16 +47,12 @@ class Parallel extends React.Component {
|
|||||||
svg: null,
|
svg: null,
|
||||||
ctx: null,
|
ctx: null,
|
||||||
axes: null,
|
axes: null,
|
||||||
dimensions: null,
|
dimensions: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {svg, ctx} = setupParallelCoordinates(
|
const { svg, ctx } = setupParallelCoordinates(width, height, margin);
|
||||||
width,
|
this.setState({ svg, ctx });
|
||||||
height,
|
|
||||||
margin
|
|
||||||
);
|
|
||||||
this.setState({svg, ctx})
|
|
||||||
}
|
}
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.maybeDrawAxes(nextProps);
|
this.maybeDrawAxes(nextProps);
|
||||||
@@ -63,10 +63,10 @@ class Parallel extends React.Component {
|
|||||||
!this.state.axes &&
|
!this.state.axes &&
|
||||||
nextProps.initializeRanges /* axes are created on full range of data */
|
nextProps.initializeRanges /* axes are created on full range of data */
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const dimensions = createDimensions(nextProps.initializeRanges);
|
const dimensions = createDimensions(nextProps.initializeRanges);
|
||||||
|
|
||||||
const xscale = d3.scalePoint()
|
const xscale = d3
|
||||||
|
.scalePoint()
|
||||||
.domain(d3.range(dimensions.length))
|
.domain(d3.range(dimensions.length))
|
||||||
.range([0, width]);
|
.range([0, width]);
|
||||||
|
|
||||||
@@ -78,28 +78,27 @@ class Parallel extends React.Component {
|
|||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
this.handleBrushAction.bind(this),
|
this.handleBrushAction.bind(this),
|
||||||
this.handleColorAction.bind(this),
|
this.handleColorAction.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
axes,
|
axes,
|
||||||
xscale,
|
xscale,
|
||||||
dimensions,
|
dimensions
|
||||||
})
|
});
|
||||||
|
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "parallel coordinates axes have been drawn"
|
type: "parallel coordinates axes have been drawn"
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maybeDrawLines = _.debounce((nextProps) => { /* https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js */
|
maybeDrawLines = _.debounce(nextProps => {
|
||||||
|
/* https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js */
|
||||||
if (
|
if (
|
||||||
nextProps.ranges &&
|
nextProps.ranges &&
|
||||||
nextProps.currentCellSelection &&
|
nextProps.currentCellSelection &&
|
||||||
nextProps.axesHaveBeenDrawn
|
nextProps.axesHaveBeenDrawn
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (this.state._drawLinesCanvas) {
|
if (this.state._drawLinesCanvas) {
|
||||||
this.state._drawLinesCanvas.invalidate(); /* this is only necessary if the internals of drawLinesCanvas are using the render queue */
|
this.state._drawLinesCanvas.invalidate(); /* this is only necessary if the internals of drawLinesCanvas are using the render queue */
|
||||||
}
|
}
|
||||||
@@ -112,22 +111,22 @@ class Parallel extends React.Component {
|
|||||||
this.state.xscale,
|
this.state.xscale,
|
||||||
this.state.ctx,
|
this.state.ctx,
|
||||||
nextProps.colorAccessor,
|
nextProps.colorAccessor,
|
||||||
nextProps.colorScale,
|
nextProps.colorScale
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
_drawLinesCanvas, /* this will only exist if the internals of drawLinesCanvas are using the render queue */
|
_drawLinesCanvas /* this will only exist if the internals of drawLinesCanvas are using the render queue */
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200);
|
||||||
|
|
||||||
handleBrushAction (selection) {
|
handleBrushAction(selection) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "continuous selection using parallel coords brushing",
|
type: "continuous selection using parallel coords brushing",
|
||||||
data: selection
|
data: selection
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
handleColorAction (key) {
|
handleColorAction(key) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "color by continuous metadata",
|
type: "color by continuous metadata",
|
||||||
colorAccessor: key,
|
colorAccessor: key,
|
||||||
@@ -136,22 +135,21 @@ class Parallel extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="parcoords_wrapper">
|
<div id="parcoords_wrapper">
|
||||||
<div
|
<div
|
||||||
className={styles.parcoords}
|
className={styles.parcoords}
|
||||||
id="parcoords"
|
id="parcoords"
|
||||||
style={{
|
style={{
|
||||||
width: width + margin.left + margin.right + "px",
|
width: width + margin.left + margin.right + "px",
|
||||||
height: height + margin.top + margin.bottom + "px"
|
height: height + margin.top + margin.bottom + "px"
|
||||||
}}></div>
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Parallel;
|
export default Parallel;
|
||||||
|
|
||||||
|
|
||||||
// <SectionHeader text="Continuous Metadata"/>
|
// <SectionHeader text="Continuous Metadata"/>
|
||||||
|
|||||||
@@ -3,22 +3,19 @@
|
|||||||
Setup SVG & Canvas elements
|
Setup SVG & Canvas elements
|
||||||
******************************************
|
******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
|
// jshint esversion: 6
|
||||||
|
const setupParallelCoordinates = (width, height, margin) => {
|
||||||
|
var container = d3.select("#parcoords");
|
||||||
|
|
||||||
const setupParallelCoordinates = (
|
var svg = container
|
||||||
width,
|
.append("svg")
|
||||||
height,
|
.attr("width", width + margin.left + margin.right)
|
||||||
margin
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
) => {
|
|
||||||
|
|
||||||
var container = d3.select("#parcoords")
|
|
||||||
|
|
||||||
var svg = container.append("svg")
|
|
||||||
.attr("width", width + margin.left + margin.right)
|
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
|
||||||
var canvas = container.append("canvas")
|
var canvas = container
|
||||||
|
.append("canvas")
|
||||||
.attr("width", width * devicePixelRatio)
|
.attr("width", width * devicePixelRatio)
|
||||||
.attr("height", height * devicePixelRatio)
|
.attr("height", height * devicePixelRatio)
|
||||||
.style("width", width + "px")
|
.style("width", width + "px")
|
||||||
@@ -27,16 +24,15 @@ const setupParallelCoordinates = (
|
|||||||
.style("margin-left", margin.left + "px");
|
.style("margin-left", margin.left + "px");
|
||||||
|
|
||||||
var ctx = canvas.node().getContext("2d");
|
var ctx = canvas.node().getContext("2d");
|
||||||
ctx.globalCompositeOperation = 'darken';
|
ctx.globalCompositeOperation = "darken";
|
||||||
ctx.globalAlpha = 0.15;
|
ctx.globalAlpha = 0.15;
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.scale(devicePixelRatio, devicePixelRatio);
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
svg,
|
svg,
|
||||||
ctx,
|
ctx
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
||||||
}
|
export default setupParallelCoordinates;
|
||||||
|
|
||||||
export default setupParallelCoordinates
|
|
||||||
|
|||||||
@@ -1,51 +1,57 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
const paddingRight = 120;
|
const paddingRight = 120;
|
||||||
const continuousChartWidth = 1200;
|
const continuousChartWidth = 1200;
|
||||||
|
|
||||||
export const margin = {top: 66, right: 110, bottom: 20, left: 60};
|
export const margin = { top: 66, right: 110, bottom: 20, left: 60 };
|
||||||
export const width = continuousChartWidth - margin.left - margin.right - paddingRight;
|
export const width =
|
||||||
|
continuousChartWidth - margin.left - margin.right - paddingRight;
|
||||||
export const height = 340 - margin.top - margin.bottom;
|
export const height = 340 - margin.top - margin.bottom;
|
||||||
export const innerHeight = height - 2;
|
export const innerHeight = height - 2;
|
||||||
|
|
||||||
export const devicePixelRatio = window.devicePixelRatio || 1;
|
export const devicePixelRatio = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
export const createDimensions = (data) => {
|
export const createDimensions = data => {
|
||||||
const newArr = []
|
const newArr = [];
|
||||||
_.each(data, (value, key) => {
|
_.each(data, (value, key) => {
|
||||||
if (value.range) {
|
if (value.range) {
|
||||||
newArr.push({
|
newArr.push({
|
||||||
key: key, /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */
|
key: key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */,
|
||||||
type: {
|
type: {
|
||||||
within: (d, extent, dim) => {
|
within: (d, extent, dim) => {
|
||||||
return extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1];
|
return extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1];
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
scale: d3.scaleSqrt().range([innerHeight, 0]).domain([0, value.range.max])
|
scale: d3
|
||||||
})
|
.scaleSqrt()
|
||||||
|
.range([innerHeight, 0])
|
||||||
|
.domain([0, value.range.max])
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return newArr;
|
return newArr;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const yAxis = d3.axisLeft();
|
export const yAxis = d3.axisLeft();
|
||||||
|
|
||||||
export const brushstart = () => {
|
export const brushstart = () => {
|
||||||
d3.event.sourceEvent.stopPropagation();
|
d3.event.sourceEvent.stopPropagation();
|
||||||
}
|
};
|
||||||
|
|
||||||
export const d3_functor = (v) => {
|
export const d3_functor = v => {
|
||||||
return typeof v === "function" ? v : () => { return v; };
|
return typeof v === "function"
|
||||||
|
? v
|
||||||
|
: () => {
|
||||||
|
return v;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const project = (d, dimensions, xscale) => {
|
export const project = (d, dimensions, xscale) => {
|
||||||
return dimensions.map((p,i) => {
|
return dimensions.map((p, i) => {
|
||||||
// check if data element has property and contains a value
|
// check if data element has property and contains a value
|
||||||
if (
|
if (!(p.key in d) || d[p.key] === null) return null;
|
||||||
!(p.key in d) ||
|
|
||||||
d[p.key] === null
|
|
||||||
) return null;
|
|
||||||
|
|
||||||
return [xscale(i),p.scale(d[p.key])];
|
return [xscale(i), p.scale(d[p.key])];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -10,20 +11,22 @@ class CellSetButton extends React.Component {
|
|||||||
set() {
|
set() {
|
||||||
const set = [];
|
const set = [];
|
||||||
|
|
||||||
_.each(this.props.currentCellSelection, (cell) => {
|
_.each(this.props.currentCellSelection, cell => {
|
||||||
if (cell["__selected__"]) {
|
if (cell["__selected__"]) {
|
||||||
set.push(cell.CellName)
|
set.push(cell.CellName);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "store current cell selection as differential set " + this.props.eitherCellSetOneOrTwo,
|
type:
|
||||||
|
"store current cell selection as differential set " +
|
||||||
|
this.props.eitherCellSetOneOrTwo,
|
||||||
data: set
|
data: set
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<span style={{marginRight: 10}}>
|
<span style={{ marginRight: 10 }}>
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
color: "#FFF",
|
color: "#FFF",
|
||||||
@@ -31,20 +34,34 @@ class CellSetButton extends React.Component {
|
|||||||
height: 30,
|
height: 30,
|
||||||
backgroundColor: globals.brightBlue,
|
backgroundColor: globals.brightBlue,
|
||||||
border: "none",
|
border: "none",
|
||||||
cursor: "pointer",
|
cursor: "pointer"
|
||||||
}}
|
}}
|
||||||
onClick={this.set.bind(this)}>
|
onClick={this.set.bind(this)}
|
||||||
<span style={{fontSize: 24, fontWeight: 700}}> {this.props.eitherCellSetOneOrTwo} </span>
|
>
|
||||||
<span style={{fontFamily: "Georgia", fontStyle: "italic", marginLeft: 8, position: "relative", top: -3}}>
|
<span style={{ fontSize: 24, fontWeight: 700 }}>
|
||||||
{
|
{" "}
|
||||||
this.props.differential["celllist" + this.props.eitherCellSetOneOrTwo] ?
|
{this.props.eitherCellSetOneOrTwo}{" "}
|
||||||
this.props.differential["celllist" + this.props.eitherCellSetOneOrTwo].length + " cells" :
|
</span>
|
||||||
0 + " cells"
|
<span
|
||||||
}
|
style={{
|
||||||
|
fontFamily: "Georgia",
|
||||||
|
fontStyle: "italic",
|
||||||
|
marginLeft: 8,
|
||||||
|
position: "relative",
|
||||||
|
top: -3
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.differential[
|
||||||
|
"celllist" + this.props.eitherCellSetOneOrTwo
|
||||||
|
]
|
||||||
|
? this.props.differential[
|
||||||
|
"celllist" + this.props.eitherCellSetOneOrTwo
|
||||||
|
].length + " cells"
|
||||||
|
: 0 + " cells"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,45 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import styles from "./expression.css";
|
import styles from "./expression.css";
|
||||||
import SectionHeader from "../framework/sectionHeader"
|
import SectionHeader from "../framework/sectionHeader";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import ReactAutocomplete from "react-autocomplete"; /* http://emilebres.github.io/react-virtualized-checkbox/ */
|
import ReactAutocomplete from "react-autocomplete"; /* http://emilebres.github.io/react-virtualized-checkbox/ */
|
||||||
import getContrast from "font-color-contrast"; // https://www.npmjs.com/package/font-color-contrast
|
import getContrast from "font-color-contrast"; // https://www.npmjs.com/package/font-color-contrast
|
||||||
import FaPaintBrush from 'react-icons/lib/fa/paint-brush';
|
import FaPaintBrush from "react-icons/lib/fa/paint-brush";
|
||||||
|
|
||||||
class HeatmapSquare extends React.Component {
|
class HeatmapSquare extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
value: '',
|
value: ""
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const contrastColor = getContrast(
|
const contrastColor = getContrast(
|
||||||
this.props.backgroundColor
|
this.props.backgroundColor
|
||||||
.substring(4, this.props.backgroundColor.length-1)
|
.substring(4, this.props.backgroundColor.length - 1)
|
||||||
.replace(/ /g, "")
|
.replace(/ /g, "")
|
||||||
.split(",")
|
.split(",")
|
||||||
)
|
);
|
||||||
return (
|
return (
|
||||||
<p style={{
|
<p
|
||||||
padding: "12px 6px",
|
style={{
|
||||||
textAlign: "center",
|
padding: "12px 6px",
|
||||||
color: contrastColor,
|
textAlign: "center",
|
||||||
width: 40,
|
color: contrastColor,
|
||||||
flexShrink: 0,
|
width: 40,
|
||||||
fontSize: 12,
|
flexShrink: 0,
|
||||||
margin: 0,
|
fontSize: 12,
|
||||||
backgroundColor: this.props.backgroundColor,
|
margin: 0,
|
||||||
}}>
|
backgroundColor: this.props.backgroundColor
|
||||||
|
}}
|
||||||
|
>
|
||||||
{this.props.text}
|
{this.props.text}
|
||||||
</p>
|
</p>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,72 +50,66 @@ class HeatmapSquare extends React.Component {
|
|||||||
***********************************
|
***********************************
|
||||||
***********************************
|
***********************************
|
||||||
**********************************/
|
**********************************/
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||||
colorAccessor: state.controls.colorAccessor,
|
colorAccessor: state.controls.colorAccessor
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class HeatmapRow extends React.Component {
|
class HeatmapRow extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
value: '',
|
value: ""
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
handleGeneColorScaleClick(gene) {
|
handleGeneColorScaleClick(gene) {
|
||||||
return () => {
|
return () => {
|
||||||
this.props.dispatch(
|
this.props.dispatch(
|
||||||
actions.requestSingleGeneExpressionCountsForColoringPOST(this.props.gene)
|
actions.requestSingleGeneExpressionCountsForColoringPOST(
|
||||||
)
|
this.props.gene
|
||||||
}
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
handleSetGeneAsScatterplotX(gene) {
|
handleSetGeneAsScatterplotX(gene) {
|
||||||
return () => {
|
return () => {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "set scatterplot x",
|
type: "set scatterplot x",
|
||||||
data: this.props.gene
|
data: this.props.gene
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
handleSetGeneAsScatterplotY(gene) {
|
handleSetGeneAsScatterplotY(gene) {
|
||||||
return () => {
|
return () => {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "set scatterplot y",
|
type: "set scatterplot y",
|
||||||
data: this.props.gene
|
data: this.props.gene
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div
|
||||||
width: 220,
|
style={{
|
||||||
display: "flex",
|
width: 220,
|
||||||
justifyContent: "flex-start",
|
display: "flex",
|
||||||
alignItems: "baseline",
|
justifyContent: "flex-start",
|
||||||
}}>
|
alignItems: "baseline"
|
||||||
<div style={{width: 150, flexShrink: 0}}>
|
}}
|
||||||
<span
|
>
|
||||||
onClick={this.handleSetGeneAsScatterplotX(this.props.gene).bind(this)}
|
<div style={{ width: 150, flexShrink: 0 }}>
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
color: this.props.scatterplotXXaccessor === this.props.gene ? "white" : globals.brightBlue,
|
|
||||||
cursor: "pointer",
|
|
||||||
position: "relative",
|
|
||||||
top: 1,
|
|
||||||
fontWeight: 700,
|
|
||||||
marginRight: 4,
|
|
||||||
borderRadius: 3,
|
|
||||||
padding: "2px 3px",
|
|
||||||
backgroundColor: this.props.scatterplotXXaccessor === this.props.gene ? globals.brightBlue : "inherit",
|
|
||||||
}}>X</span>
|
|
||||||
<span
|
<span
|
||||||
onClick={this.handleSetGeneAsScatterplotY(this.props.gene).bind(this)}
|
onClick={this.handleSetGeneAsScatterplotX(this.props.gene).bind(
|
||||||
|
this
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: this.props.scatterplotYYaccessor === this.props.gene ? "white" : globals.brightBlue,
|
color:
|
||||||
|
this.props.scatterplotXXaccessor === this.props.gene
|
||||||
|
? "white"
|
||||||
|
: globals.brightBlue,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
top: 1,
|
top: 1,
|
||||||
@@ -120,8 +117,39 @@ class HeatmapRow extends React.Component {
|
|||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
padding: "2px 3px",
|
padding: "2px 3px",
|
||||||
backgroundColor: this.props.scatterplotYYaccessor === this.props.gene ? globals.brightBlue : "inherit",
|
backgroundColor:
|
||||||
}}>Y</span>
|
this.props.scatterplotXXaccessor === this.props.gene
|
||||||
|
? globals.brightBlue
|
||||||
|
: "inherit"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
onClick={this.handleSetGeneAsScatterplotY(this.props.gene).bind(
|
||||||
|
this
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color:
|
||||||
|
this.props.scatterplotYYaccessor === this.props.gene
|
||||||
|
? "white"
|
||||||
|
: globals.brightBlue,
|
||||||
|
cursor: "pointer",
|
||||||
|
position: "relative",
|
||||||
|
top: 1,
|
||||||
|
fontWeight: 700,
|
||||||
|
marginRight: 4,
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: "2px 3px",
|
||||||
|
backgroundColor:
|
||||||
|
this.props.scatterplotYYaccessor === this.props.gene
|
||||||
|
? globals.brightBlue
|
||||||
|
: "inherit"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Y
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
onClick={this.handleGeneColorScaleClick(this.props.gene).bind(this)}
|
onClick={this.handleGeneColorScaleClick(this.props.gene).bind(this)}
|
||||||
style={{
|
style={{
|
||||||
@@ -131,32 +159,45 @@ class HeatmapRow extends React.Component {
|
|||||||
marginRight: 6,
|
marginRight: 6,
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
padding: "0px 2px 2px 2px",
|
padding: "0px 2px 2px 2px",
|
||||||
color: this.props.colorAccessor === this.props.gene ? "white" : "inherit",
|
color:
|
||||||
backgroundColor: this.props.colorAccessor === this.props.gene ? globals.brightBlue : "inherit",
|
this.props.colorAccessor === this.props.gene
|
||||||
}}><FaPaintBrush style={{display: "inline-block"}}/></span>
|
? "white"
|
||||||
|
: "inherit",
|
||||||
|
backgroundColor:
|
||||||
|
this.props.colorAccessor === this.props.gene
|
||||||
|
? globals.brightBlue
|
||||||
|
: "inherit"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaPaintBrush style={{ display: "inline-block" }} />
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 14
|
fontSize: 14
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{this.props.gene}
|
{this.props.gene}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<HeatmapSquare
|
<HeatmapSquare
|
||||||
backgroundColor={this.props.greyColorScale(this.props.set1exp)}
|
backgroundColor={this.props.greyColorScale(this.props.set1exp)}
|
||||||
text={this.props.set1exp}/>
|
text={this.props.set1exp}
|
||||||
|
/>
|
||||||
<HeatmapSquare
|
<HeatmapSquare
|
||||||
backgroundColor={this.props.greyColorScale(this.props.set2exp)}
|
backgroundColor={this.props.greyColorScale(this.props.set2exp)}
|
||||||
text={this.props.set2exp}/>
|
text={this.props.set2exp}
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
title={this.props.aveDiff}
|
title={this.props.aveDiff}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
paddingLeft: 10
|
paddingLeft: 10
|
||||||
}}>
|
}}
|
||||||
{this.props.aveDiff.toFixed(2)}
|
>
|
||||||
|
{this.props.aveDiff.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,21 +209,22 @@ class HeatmapRow extends React.Component {
|
|||||||
***********************************
|
***********************************
|
||||||
**********************************/
|
**********************************/
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
return {
|
return {
|
||||||
differential: state.differential,
|
differential: state.differential,
|
||||||
allGeneNames: state.controls.allGeneNames
|
allGeneNames: state.controls.allGeneNames
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class Heatmap extends React.Component {
|
class Heatmap extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
value: '',
|
value: ""
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
if (!this.props.differential.diffExp) return <p>Select cells & compute differential to see heatmap</p>
|
if (!this.props.differential.diffExp)
|
||||||
|
return <p>Select cells & compute differential to see heatmap</p>;
|
||||||
|
|
||||||
const topGenesForCellSet1 = this.props.differential.diffExp.data.celllist1;
|
const topGenesForCellSet1 = this.props.differential.diffExp.data.celllist1;
|
||||||
const topGenesForCellSet2 = this.props.differential.diffExp.data.celllist2;
|
const topGenesForCellSet2 = this.props.differential.diffExp.data.celllist2;
|
||||||
@@ -194,71 +236,86 @@ class Heatmap extends React.Component {
|
|||||||
topGenesForCellSet2.mean_expression_cellset1,
|
topGenesForCellSet2.mean_expression_cellset1,
|
||||||
topGenesForCellSet2.mean_expression_cellset2
|
topGenesForCellSet2.mean_expression_cellset2
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
|
|
||||||
const greyColorScale = d3.scaleSequential()
|
const greyColorScale = d3
|
||||||
.domain(extent)
|
.scaleSequential()
|
||||||
.interpolator(d3.interpolateGreys);
|
.domain(extent)
|
||||||
|
.interpolator(d3.interpolateGreys);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
Color by any gene:
|
Color by any gene:
|
||||||
<ReactAutocomplete
|
<ReactAutocomplete
|
||||||
items={this.props.allGeneNames}
|
items={this.props.allGeneNames}
|
||||||
shouldItemRender={(item, value) => item.toLowerCase().indexOf(value.toLowerCase()) > -1}
|
shouldItemRender={(item, value) =>
|
||||||
|
item.toLowerCase().indexOf(value.toLowerCase()) > -1
|
||||||
|
}
|
||||||
getItemValue={item => item}
|
getItemValue={item => item}
|
||||||
renderItem={(item, highlighted) =>
|
renderItem={(item, highlighted) => (
|
||||||
<div
|
<div
|
||||||
key={item}
|
key={item}
|
||||||
style={{ backgroundColor: highlighted ? '#eee' : 'transparent'}}
|
style={{ backgroundColor: highlighted ? "#eee" : "transparent" }}
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
onChange={e => this.setState({ value: e.target.value })}
|
onChange={e => this.setState({ value: e.target.value })}
|
||||||
onSelect={(value) => {
|
onSelect={value => {
|
||||||
this.setState({ value });
|
this.setState({ value });
|
||||||
this.props.dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(value))
|
this.props.dispatch(
|
||||||
|
actions.requestSingleGeneExpressionCountsForColoringPOST(value)
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div style={{
|
<div
|
||||||
display: "flex",
|
style={{
|
||||||
justifyContent: "flex-start",
|
display: "flex",
|
||||||
width: 400,
|
justifyContent: "flex-start",
|
||||||
fontWeight: 700,
|
width: 400,
|
||||||
}}>
|
fontWeight: 700
|
||||||
<p style={{marginRight: 110}}>Gene</p>
|
}}
|
||||||
<p style={{marginRight: 25}}>1</p>
|
>
|
||||||
<p style={{marginRight: 20}}>2</p>
|
<p style={{ marginRight: 110 }}>Gene</p>
|
||||||
|
<p style={{ marginRight: 25 }}>1</p>
|
||||||
|
<p style={{ marginRight: 20 }}>2</p>
|
||||||
<p>ave diff</p>
|
<p>ave diff</p>
|
||||||
</div>
|
</div>
|
||||||
{
|
{topGenesForCellSet1.topgenes.map((gene, i) => {
|
||||||
topGenesForCellSet1.topgenes.map((gene, i) => {
|
return (
|
||||||
return <HeatmapRow
|
<HeatmapRow
|
||||||
key={gene}
|
key={gene}
|
||||||
gene={gene}
|
gene={gene}
|
||||||
greyColorScale={greyColorScale}
|
greyColorScale={greyColorScale}
|
||||||
aveDiff={topGenesForCellSet1.ave_diff[i]}
|
aveDiff={topGenesForCellSet1.ave_diff[i]}
|
||||||
set1exp={Math.floor(topGenesForCellSet1.mean_expression_cellset1[i])}
|
set1exp={Math.floor(
|
||||||
set2exp={Math.floor(topGenesForCellSet1.mean_expression_cellset2[i])}
|
topGenesForCellSet1.mean_expression_cellset1[i]
|
||||||
/>
|
)}
|
||||||
})
|
set2exp={Math.floor(
|
||||||
}
|
topGenesForCellSet1.mean_expression_cellset2[i]
|
||||||
{
|
)}
|
||||||
topGenesForCellSet2.topgenes.map((gene, i) => {
|
/>
|
||||||
return <HeatmapRow
|
);
|
||||||
|
})}
|
||||||
|
{topGenesForCellSet2.topgenes.map((gene, i) => {
|
||||||
|
return (
|
||||||
|
<HeatmapRow
|
||||||
key={gene}
|
key={gene}
|
||||||
gene={gene}
|
gene={gene}
|
||||||
greyColorScale={greyColorScale}
|
greyColorScale={greyColorScale}
|
||||||
aveDiff={topGenesForCellSet2.ave_diff[i]}
|
aveDiff={topGenesForCellSet2.ave_diff[i]}
|
||||||
set1exp={Math.floor(topGenesForCellSet2.mean_expression_cellset1[i])}
|
set1exp={Math.floor(
|
||||||
set2exp={Math.floor(topGenesForCellSet2.mean_expression_cellset2[i])}
|
topGenesForCellSet2.mean_expression_cellset1[i]
|
||||||
/>
|
)}
|
||||||
})
|
set2exp={Math.floor(
|
||||||
}
|
topGenesForCellSet2.mean_expression_cellset2[i]
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -5,26 +6,24 @@ import * as globals from "../../globals";
|
|||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import CellSetButton from "./cellSetButtons";
|
import CellSetButton from "./cellSetButtons";
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
return {
|
return {
|
||||||
currentCellSelection: state.controls.currentCellSelection,
|
currentCellSelection: state.controls.currentCellSelection,
|
||||||
differential: state.differential
|
differential: state.differential
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class Expression extends React.Component {
|
class Expression extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {};
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
handleClick(gene) {
|
handleClick(gene) {
|
||||||
return () => {
|
return () => {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "color by expression",
|
type: "color by expression",
|
||||||
gene: gene,
|
gene: gene
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
computeDiffExp() {
|
computeDiffExp() {
|
||||||
this.props.dispatch(
|
this.props.dispatch(
|
||||||
@@ -32,36 +31,28 @@ class Expression extends React.Component {
|
|||||||
this.props.differential.celllist1,
|
this.props.differential.celllist1,
|
||||||
this.props.differential.celllist2
|
this.props.differential.celllist2
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
render () {
|
render() {
|
||||||
if (
|
if (!this.props.differential) {
|
||||||
!this.props.differential
|
return null;
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{margin: 10}}>
|
<div style={{ margin: 10 }}>
|
||||||
<div style={{marginBottom: 10, width: 300}}>
|
<div style={{ marginBottom: 10, width: 300 }}>
|
||||||
There are currently
|
There are currently
|
||||||
{
|
{" " +
|
||||||
" " +
|
_.filter(this.props.currentCellSelection, "__selected__").length +
|
||||||
_.filter(this.props.currentCellSelection, "__selected__").length +
|
" "}
|
||||||
" "
|
cells selected, click a cell set button to store them.
|
||||||
}
|
</div>
|
||||||
cells selected, click a cell set button to store them.
|
<CellSetButton {...this.props} eitherCellSetOneOrTwo={1} />
|
||||||
</div>
|
<CellSetButton {...this.props} eitherCellSetOneOrTwo={2} />
|
||||||
<CellSetButton
|
|
||||||
{...this.props}
|
|
||||||
eitherCellSetOneOrTwo={1}/>
|
|
||||||
<CellSetButton
|
|
||||||
{...this.props}
|
|
||||||
eitherCellSetOneOrTwo={2}/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{
|
{this.props.differential.celllist1 &&
|
||||||
this.props.differential.celllist1 && this.props.differential.celllist2 ?
|
this.props.differential.celllist2 ? (
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -71,11 +62,13 @@ class Expression extends React.Component {
|
|||||||
padding: "12px 20px",
|
padding: "12px 20px",
|
||||||
backgroundColor: globals.brightBlue,
|
backgroundColor: globals.brightBlue,
|
||||||
border: "none",
|
border: "none",
|
||||||
cursor: "pointer",
|
cursor: "pointer"
|
||||||
}}
|
}}
|
||||||
onClick={this.computeDiffExp.bind(this)}>
|
onClick={this.computeDiffExp.bind(this)}
|
||||||
|
>
|
||||||
Compute differential expression
|
Compute differential expression
|
||||||
</button> :
|
</button>
|
||||||
|
) : (
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -84,17 +77,16 @@ class Expression extends React.Component {
|
|||||||
color: "#FFF",
|
color: "#FFF",
|
||||||
padding: "12px 20px",
|
padding: "12px 20px",
|
||||||
backgroundColor: globals.mediumGrey,
|
backgroundColor: globals.mediumGrey,
|
||||||
border: "none",
|
border: "none"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Compute differential expression
|
Compute differential expression
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Expression;
|
export default Expression;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
import styles from './container.css';
|
import styles from "./container.css";
|
||||||
|
|
||||||
const Container = props => (
|
const Container = props => (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>{props.children}</div>
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Container;
|
export default Container;
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import React from 'react';
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
import Container from './container';
|
|
||||||
import styles from './header.css';
|
|
||||||
|
|
||||||
|
import Container from "./container";
|
||||||
|
import styles from "./header.css";
|
||||||
|
|
||||||
const Header = () => (
|
const Header = () => (
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Container>
|
<Container />
|
||||||
|
|
||||||
</Container>
|
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import React from 'react';
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
const SectionHeader = ({text}) => (
|
const SectionHeader = ({ text }) => (
|
||||||
<p style={{
|
<p
|
||||||
fontSize: 32,
|
style={{
|
||||||
fontWeight: 700
|
fontSize: 32,
|
||||||
}}>
|
fontWeight: 700
|
||||||
|
}}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import styles from "./graph.css";
|
import styles from "./graph.css";
|
||||||
import renderQueue from "../../util/renderQueue";
|
import renderQueue from "../../util/renderQueue";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -13,7 +14,6 @@ export const setupGraphElements = (
|
|||||||
handleBrushSelectAction,
|
handleBrushSelectAction,
|
||||||
handleBrushDeselectAction
|
handleBrushDeselectAction
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
// var canvas = d3.select("#graphAttachPoint")
|
// var canvas = d3.select("#graphAttachPoint")
|
||||||
// .append("canvas")
|
// .append("canvas")
|
||||||
// .attr("width", globals.graphWidth)
|
// .attr("width", globals.graphWidth)
|
||||||
@@ -22,25 +22,22 @@ export const setupGraphElements = (
|
|||||||
|
|
||||||
// var ctx = canvas.node().getContext("2d");
|
// var ctx = canvas.node().getContext("2d");
|
||||||
|
|
||||||
var svg = d3.select("#graphAttachPoint").append("svg")
|
var svg = d3
|
||||||
|
.select("#graphAttachPoint")
|
||||||
|
.append("svg")
|
||||||
.attr("width", globals.graphWidth)
|
.attr("width", globals.graphWidth)
|
||||||
.attr("height", globals.graphHeight)
|
.attr("height", globals.graphHeight)
|
||||||
.attr("class", `${styles.graphSVG}`)
|
.attr("class", `${styles.graphSVG}`);
|
||||||
// .append("g")
|
// .append("g")
|
||||||
// .attr("transform", "translate(" + margin.left + " " + margin.top + ")");
|
// .attr("transform", "translate(" + margin.left + " " + margin.top + ")");
|
||||||
|
|
||||||
setupGraphBrush(
|
setupGraphBrush(svg, handleBrushSelectAction, handleBrushDeselectAction);
|
||||||
svg,
|
|
||||||
handleBrushSelectAction,
|
|
||||||
handleBrushDeselectAction
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
svg,
|
svg
|
||||||
// ctx
|
// ctx
|
||||||
}
|
};
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************
|
/******************************************
|
||||||
*******************************************
|
*******************************************
|
||||||
@@ -57,36 +54,34 @@ const drawGraph = (
|
|||||||
currentCellSelection,
|
currentCellSelection,
|
||||||
graphBrushSelection,
|
graphBrushSelection,
|
||||||
colorScale,
|
colorScale,
|
||||||
graphMap, /* tmp remove when structure exists on server */
|
graphMap /* tmp remove when structure exists on server */,
|
||||||
opacityForDeselectedCells,
|
opacityForDeselectedCells,
|
||||||
_currentCellSelectionMap,
|
_currentCellSelectionMap
|
||||||
) => {
|
) => {
|
||||||
return (p) => {
|
return p => {
|
||||||
|
|
||||||
/* shuffle the data to overcome render order hiding cells, & filter first */
|
/* shuffle the data to overcome render order hiding cells, & filter first */
|
||||||
// data = d3.shuffle(data); /* make me a control */
|
// data = d3.shuffle(data); /* make me a control */
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
/* context.arc(x,y,r,sAngle,eAngle,counterclockwise); */
|
/* context.arc(x,y,r,sAngle,eAngle,counterclockwise); */
|
||||||
context.arc(
|
context.arc(
|
||||||
globals.graphXScale(p[1]), /* x */
|
globals.graphXScale(p[1]) /* x */,
|
||||||
globals.graphYScale(p[2]), /* y */
|
globals.graphYScale(p[2]) /* y */,
|
||||||
_currentCellSelectionMap[p[0]]["__selected__"] ? 3 : 1.5, /* r */
|
_currentCellSelectionMap[p[0]]["__selected__"] ? 3 : 1.5 /* r */,
|
||||||
0, /* sAngle */
|
0 /* sAngle */,
|
||||||
2 * Math.PI /* eAngle */
|
2 * Math.PI /* eAngle */
|
||||||
);
|
);
|
||||||
|
|
||||||
context.fillStyle = _currentCellSelectionMap[p[0]]["__color__"]
|
context.fillStyle = _currentCellSelectionMap[p[0]]["__color__"];
|
||||||
|
|
||||||
if (_currentCellSelectionMap[p[0]]["__selected__"]) {
|
if (_currentCellSelectionMap[p[0]]["__selected__"]) {
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
} else {
|
} else {
|
||||||
context.globalAlpha = opacityForDeselectedCells;
|
context.globalAlpha = opacityForDeselectedCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.fill();
|
context.fill();
|
||||||
|
};
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const _drawGraphUsingRenderQueue = (
|
const _drawGraphUsingRenderQueue = (
|
||||||
context,
|
context,
|
||||||
@@ -97,22 +92,26 @@ const _drawGraphUsingRenderQueue = (
|
|||||||
currentCellSelection,
|
currentCellSelection,
|
||||||
graphBrushSelection,
|
graphBrushSelection,
|
||||||
colorScale,
|
colorScale,
|
||||||
graphMap, /* tmp remove when structure exists on server */
|
graphMap /* tmp remove when structure exists on server */,
|
||||||
opacityForDeselectedCells,
|
opacityForDeselectedCells
|
||||||
) => {
|
) => {
|
||||||
const _currentCellSelectionMap = _.keyBy(currentCellSelection, "CellName"); /* move me to the reducer */
|
const _currentCellSelectionMap = _.keyBy(
|
||||||
|
currentCellSelection,
|
||||||
|
"CellName"
|
||||||
|
); /* move me to the reducer */
|
||||||
|
|
||||||
const dataForGraph = [];
|
const dataForGraph = [];
|
||||||
|
|
||||||
_.each(currentCellSelection, (cell, i) => {
|
_.each(currentCellSelection, (cell, i) => {
|
||||||
if (graphMap[cell["CellName"]]) { /* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
|
if (graphMap[cell["CellName"]]) {
|
||||||
|
/* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
|
||||||
dataForGraph.push([
|
dataForGraph.push([
|
||||||
cell["CellName"],
|
cell["CellName"],
|
||||||
graphMap[cell["CellName"]][0],
|
graphMap[cell["CellName"]][0],
|
||||||
graphMap[cell["CellName"]][1]
|
graphMap[cell["CellName"]][1]
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
/* clear canvas */
|
/* clear canvas */
|
||||||
context.clearRect(0, 0, globals.graphWidth, globals.graphHeight);
|
context.clearRect(0, 0, globals.graphWidth, globals.graphHeight);
|
||||||
@@ -127,30 +126,30 @@ const _drawGraphUsingRenderQueue = (
|
|||||||
currentCellSelection,
|
currentCellSelection,
|
||||||
graphBrushSelection,
|
graphBrushSelection,
|
||||||
colorScale,
|
colorScale,
|
||||||
graphMap, /* tmp remove when structure exists on server */
|
graphMap /* tmp remove when structure exists on server */,
|
||||||
opacityForDeselectedCells,
|
opacityForDeselectedCells,
|
||||||
_currentCellSelectionMap,
|
_currentCellSelectionMap
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
_renderGraphWithFunctionReturnedByQueue(dataForGraph);
|
_renderGraphWithFunctionReturnedByQueue(dataForGraph);
|
||||||
return _renderGraphWithFunctionReturnedByQueue;
|
return _renderGraphWithFunctionReturnedByQueue;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const drawGraphUsingRenderQueue = _.debounce(_drawGraphUsingRenderQueue, 100);
|
export const drawGraphUsingRenderQueue = _.debounce(
|
||||||
|
_drawGraphUsingRenderQueue,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
const setupGraphBrush = (
|
const setupGraphBrush = (
|
||||||
svg,
|
svg,
|
||||||
handleBrushSelectAction,
|
handleBrushSelectAction,
|
||||||
handleBrushDeselectAction
|
handleBrushDeselectAction
|
||||||
) => {
|
) => {
|
||||||
svg.append("g")
|
svg.append("g").call(
|
||||||
.call(
|
d3
|
||||||
d3.brush()
|
.brush()
|
||||||
.extent([
|
.extent([[0, 0], [globals.graphWidth, globals.graphHeight]])
|
||||||
[0, 0],
|
.on("brush", handleBrushSelectAction)
|
||||||
[globals.graphWidth, globals.graphHeight]
|
.on("end", handleBrushDeselectAction)
|
||||||
])
|
);
|
||||||
.on("brush", handleBrushSelectAction)
|
};
|
||||||
.on("end", handleBrushDeselectAction)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
const mat4 = require('gl-mat4')
|
// jshint esversion: 6
|
||||||
|
const mat4 = require("gl-mat4");
|
||||||
|
|
||||||
// opacity: https://github.com/spacetx/starfish/blob/master/viz/draw/regions.js
|
// opacity: https://github.com/spacetx/starfish/blob/master/viz/draw/regions.js
|
||||||
|
|
||||||
export default function (regl) {
|
export default function(regl) {
|
||||||
return regl({
|
return regl({
|
||||||
vert: `
|
vert: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
@@ -29,14 +30,14 @@ export default function (regl) {
|
|||||||
}`,
|
}`,
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
position: regl.prop('position'),
|
position: regl.prop("position"),
|
||||||
color: regl.prop('color'),
|
color: regl.prop("color"),
|
||||||
size: regl.prop('size')
|
size: regl.prop("size")
|
||||||
},
|
},
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
distance: regl.prop('distance'),
|
distance: regl.prop("distance"),
|
||||||
view: regl.prop('view'),
|
view: regl.prop("view"),
|
||||||
projection: (context, props) => {
|
projection: (context, props) => {
|
||||||
return mat4.perspective(
|
return mat4.perspective(
|
||||||
[],
|
[],
|
||||||
@@ -44,12 +45,12 @@ export default function (regl) {
|
|||||||
context.viewportWidth * props.scale / context.viewportHeight,
|
context.viewportWidth * props.scale / context.viewportHeight,
|
||||||
0.01,
|
0.01,
|
||||||
1000
|
1000
|
||||||
)
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
count: regl.prop('count'),
|
count: regl.prop("count"),
|
||||||
|
|
||||||
primitive: 'points'
|
primitive: "points"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+145
-104
@@ -1,29 +1,38 @@
|
|||||||
import React from 'react';
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import styles from "./graph.css";
|
import styles from "./graph.css";
|
||||||
import {setupGraphElements, drawGraphUsingRenderQueue} from "./drawGraph";
|
import { setupGraphElements, drawGraphUsingRenderQueue } from "./drawGraph";
|
||||||
import SectionHeader from "../framework/sectionHeader";
|
import SectionHeader from "../framework/sectionHeader";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
|
|
||||||
import mat4 from 'gl-mat4';
|
import mat4 from "gl-mat4";
|
||||||
import fit from 'canvas-fit';
|
import fit from "canvas-fit";
|
||||||
import _camera from '../../util/camera.js'
|
import _camera from "../../util/camera.js";
|
||||||
import _regl from 'regl'
|
import _regl from "regl";
|
||||||
import _drawPoints from './drawPointsRegl'
|
import _drawPoints from "./drawPointsRegl";
|
||||||
|
|
||||||
import FaCrosshair from 'react-icons/lib/fa/crosshairs';
|
import FaCrosshair from "react-icons/lib/fa/crosshairs";
|
||||||
import FaZoom from 'react-icons/lib/fa/search-plus';
|
import FaZoom from "react-icons/lib/fa/search-plus";
|
||||||
import FaSave from 'react-icons/lib/fa/download';
|
import FaSave from "react-icons/lib/fa/download";
|
||||||
|
|
||||||
/* https://bl.ocks.org/mbostock/9078690 - quadtree for onClick / hover selections */
|
/* https://bl.ocks.org/mbostock/9078690 - quadtree for onClick / hover selections */
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
|
const vertices =
|
||||||
const vertices = state.cells.cells && state.cells.cells.data.graph ? state.cells.cells.data.graph : null;
|
state.cells.cells && state.cells.cells.data.graph
|
||||||
const ranges = state.cells.cells && state.cells.cells.data.ranges ? state.cells.cells.data.ranges : null;
|
? state.cells.cells.data.graph
|
||||||
const metadata = state.cells.cells && state.cells.cells.data.metadata ? state.cells.cells.data.metadata : null;
|
: null;
|
||||||
|
const ranges =
|
||||||
|
state.cells.cells && state.cells.cells.data.ranges
|
||||||
|
? state.cells.cells.data.ranges
|
||||||
|
: null;
|
||||||
|
const metadata =
|
||||||
|
state.cells.cells && state.cells.cells.data.metadata
|
||||||
|
? state.cells.cells.data.metadata
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ranges,
|
ranges,
|
||||||
@@ -35,11 +44,10 @@ import FaSave from 'react-icons/lib/fa/download';
|
|||||||
graphMap: state.controls.graphMap,
|
graphMap: state.controls.graphMap,
|
||||||
currentCellSelection: state.controls.currentCellSelection,
|
currentCellSelection: state.controls.currentCellSelection,
|
||||||
graphBrushSelection: state.controls.graphBrushSelection,
|
graphBrushSelection: state.controls.graphBrushSelection,
|
||||||
opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
|
opacityForDeselectedCells: state.controls.opacityForDeselectedCells
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class Graph extends React.Component {
|
class Graph extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
@@ -48,35 +56,32 @@ class Graph extends React.Component {
|
|||||||
svg: null,
|
svg: null,
|
||||||
ctx: null,
|
ctx: null,
|
||||||
brush: null,
|
brush: null,
|
||||||
mode: "brush",
|
mode: "brush"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const { svg } = setupGraphElements(
|
||||||
svg
|
|
||||||
} = setupGraphElements(
|
|
||||||
this.handleBrushSelectAction.bind(this),
|
this.handleBrushSelectAction.bind(this),
|
||||||
this.handleBrushDeselectAction.bind(this)
|
this.handleBrushDeselectAction.bind(this)
|
||||||
);
|
);
|
||||||
this.setState({svg});
|
this.setState({ svg });
|
||||||
|
|
||||||
// setup canvas and camera
|
// setup canvas and camera
|
||||||
const camera = _camera(this.reglCanvas, {scale: true, rotate: false});
|
const camera = _camera(this.reglCanvas, { scale: true, rotate: false });
|
||||||
const regl = _regl(this.reglCanvas)
|
const regl = _regl(this.reglCanvas);
|
||||||
|
|
||||||
const drawPoints = _drawPoints(regl)
|
const drawPoints = _drawPoints(regl);
|
||||||
|
|
||||||
// preallocate buffers
|
// preallocate buffers
|
||||||
const pointBuffer = regl.buffer();
|
const pointBuffer = regl.buffer();
|
||||||
const colorBuffer = regl.buffer();
|
const colorBuffer = regl.buffer();
|
||||||
const sizeBuffer = regl.buffer();
|
const sizeBuffer = regl.buffer();
|
||||||
|
|
||||||
regl.frame(({viewportWidth, viewportHeight}) => {
|
regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||||
|
|
||||||
regl.clear({
|
regl.clear({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
color: [1, 1, 1, 1]
|
color: [1, 1, 1, 1]
|
||||||
})
|
});
|
||||||
|
|
||||||
drawPoints({
|
drawPoints({
|
||||||
size: sizeBuffer,
|
size: sizeBuffer,
|
||||||
@@ -86,18 +91,17 @@ class Graph extends React.Component {
|
|||||||
count: this.count,
|
count: this.count,
|
||||||
view: camera.view(),
|
view: camera.view(),
|
||||||
scale: viewportHeight / viewportWidth
|
scale: viewportHeight / viewportWidth
|
||||||
})
|
});
|
||||||
|
|
||||||
camera.tick()
|
camera.tick();
|
||||||
})
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
regl,
|
regl,
|
||||||
pointBuffer,
|
pointBuffer,
|
||||||
colorBuffer,
|
colorBuffer,
|
||||||
sizeBuffer
|
sizeBuffer
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
/* maybe should do a check here to confirm ref exists and pass it? */
|
/* maybe should do a check here to confirm ref exists and pass it? */
|
||||||
@@ -120,12 +124,11 @@ class Graph extends React.Component {
|
|||||||
// nextProps.opacityForDeselectedCells,
|
// nextProps.opacityForDeselectedCells,
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
if (
|
if (this.state.regl && nextProps.vertices) {
|
||||||
this.state.regl &&
|
const _currentCellSelectionMap = _.keyBy(
|
||||||
nextProps.vertices
|
nextProps.currentCellSelection,
|
||||||
) {
|
"CellName"
|
||||||
|
); /* move me to the reducer */
|
||||||
const _currentCellSelectionMap = _.keyBy(nextProps.currentCellSelection, "CellName"); /* move me to the reducer */
|
|
||||||
|
|
||||||
const positions = [];
|
const positions = [];
|
||||||
positions.length = nextProps.currentCellSelection.length;
|
positions.length = nextProps.currentCellSelection.length;
|
||||||
@@ -134,33 +137,38 @@ class Graph extends React.Component {
|
|||||||
const sizes = [];
|
const sizes = [];
|
||||||
sizes.length = nextProps.currentCellSelection.length;
|
sizes.length = nextProps.currentCellSelection.length;
|
||||||
|
|
||||||
const glScaleX = d3.scaleLinear()
|
const glScaleX = d3
|
||||||
.domain([0,1])
|
.scaleLinear()
|
||||||
.range([-1, 1]) /* padding */
|
|
||||||
|
|
||||||
const glScaleY = d3.scaleLinear()
|
|
||||||
.domain([0, 1])
|
.domain([0, 1])
|
||||||
.range([1, -1]) /* padding */
|
.range([-1, 1]); /* padding */
|
||||||
|
|
||||||
|
const glScaleY = d3
|
||||||
|
.scaleLinear()
|
||||||
|
.domain([0, 1])
|
||||||
|
.range([1, -1]); /* padding */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Construct Vectors
|
Construct Vectors
|
||||||
*/
|
*/
|
||||||
_.each(nextProps.currentCellSelection, (cell, i) => {
|
_.each(nextProps.currentCellSelection, (cell, i) => {
|
||||||
if (nextProps.graphMap[cell["CellName"]]) { /* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
|
if (nextProps.graphMap[cell["CellName"]]) {
|
||||||
|
/* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
|
||||||
positions[i] = [
|
positions[i] = [
|
||||||
glScaleX(nextProps.graphMap[cell["CellName"]][0]),
|
glScaleX(nextProps.graphMap[cell["CellName"]][0]),
|
||||||
glScaleY(nextProps.graphMap[cell["CellName"]][1])
|
glScaleY(nextProps.graphMap[cell["CellName"]][1])
|
||||||
]
|
];
|
||||||
|
|
||||||
colors[i] = cell.__colorRGB__;
|
colors[i] = cell.__colorRGB__;
|
||||||
sizes[i] = cell["__selected__"] ? 4 : .2 /* make this a function of the number of total cells, including regraph */
|
sizes[i] = cell["__selected__"]
|
||||||
|
? 4
|
||||||
|
: 0.2; /* make this a function of the number of total cells, including regraph */
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
this.state.pointBuffer(positions)
|
this.state.pointBuffer(positions);
|
||||||
this.state.colorBuffer(colors)
|
this.state.colorBuffer(colors);
|
||||||
this.state.sizeBuffer(sizes)
|
this.state.sizeBuffer(sizes);
|
||||||
this.count = positions.length
|
this.count = positions.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleBrushSelectAction() {
|
handleBrushSelectAction() {
|
||||||
@@ -183,7 +191,7 @@ class Graph extends React.Component {
|
|||||||
northwestY: s[0][1],
|
northwestY: s[0][1],
|
||||||
southeastX: s[1][0],
|
southeastX: s[1][0],
|
||||||
southeastY: s[1][1]
|
southeastY: s[1][1]
|
||||||
}
|
};
|
||||||
|
|
||||||
brushCoords.dx = brushCoords.southeastX - brushCoords.northwestX;
|
brushCoords.dx = brushCoords.southeastX - brushCoords.northwestX;
|
||||||
brushCoords.dy = brushCoords.southeastY - brushCoords.northwestY;
|
brushCoords.dy = brushCoords.southeastY - brushCoords.northwestY;
|
||||||
@@ -191,20 +199,20 @@ class Graph extends React.Component {
|
|||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "graph brush selection change",
|
type: "graph brush selection change",
|
||||||
brushCoords
|
brushCoords
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
handleBrushDeselectAction() {
|
handleBrushDeselectAction() {
|
||||||
if (!d3.event.selection) {
|
if (!d3.event.selection) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "graph brush deselect"
|
type: "graph brush deselect"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleOpacityRangeChange(e) {
|
handleOpacityRangeChange(e) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: "change opacity deselected cells in 2d graph background",
|
type: "change opacity deselected cells in 2d graph background",
|
||||||
data: e.target.value
|
data: e.target.value
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -212,20 +220,24 @@ class Graph extends React.Component {
|
|||||||
<div
|
<div
|
||||||
id="graphWrapper"
|
id="graphWrapper"
|
||||||
style={{
|
style={{
|
||||||
height: 1050, /* move this to globals */
|
height: 1050 /* move this to globals */,
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
boxShadow: "3px 4px 13px 0px rgba(201,201,201,1)",
|
boxShadow: "3px 4px 13px 0px rgba(201,201,201,1)"
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: 10,
|
padding: 10,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "baseline"
|
alignItems: "baseline"
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => { this.props.dispatch(actions.regraph()) }}
|
onClick={() => {
|
||||||
|
this.props.dispatch(actions.regraph());
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
@@ -233,67 +245,96 @@ class Graph extends React.Component {
|
|||||||
padding: "10px 20px",
|
padding: "10px 20px",
|
||||||
backgroundColor: globals.brightBlue,
|
backgroundColor: globals.brightBlue,
|
||||||
border: "none",
|
border: "none",
|
||||||
cursor: "pointer",
|
cursor: "pointer"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Regraph present selection
|
Regraph present selection
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ marginRight: 10, fontSize: 12}}>
|
<span style={{ marginRight: 10, fontSize: 12 }}>
|
||||||
deselected opacity
|
deselected opacity
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
style={{position: "relative", top: 6, marginRight: 20}}
|
style={{ position: "relative", top: 6, marginRight: 20 }}
|
||||||
type="range"
|
type="range"
|
||||||
onChange={this.handleOpacityRangeChange.bind(this)}
|
onChange={this.handleOpacityRangeChange.bind(this)}
|
||||||
min={0}
|
min={0}
|
||||||
max={1}
|
max={1}
|
||||||
step="0.01"
|
step="0.01"
|
||||||
/>
|
/>
|
||||||
<span style={{position: "relative", top: 3}}>
|
<span style={{ position: "relative", top: 3 }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => { this.setState({mode: "brush"}) }}
|
onClick={() => {
|
||||||
style={{
|
this.setState({ mode: "brush" });
|
||||||
cursor: "pointer",
|
}}
|
||||||
border: this.state.mode === "brush" ? "1px solid black" : "1px solid white",
|
style={{
|
||||||
backgroundColor: "white",
|
cursor: "pointer",
|
||||||
padding: 5,
|
border:
|
||||||
borderRadius: 3
|
this.state.mode === "brush"
|
||||||
}}> <FaCrosshair/> </button>
|
? "1px solid black"
|
||||||
<button
|
: "1px solid white",
|
||||||
onClick={() => { this.setState({mode: "zoom"}) }}
|
backgroundColor: "white",
|
||||||
style={{
|
padding: 5,
|
||||||
cursor: "pointer",
|
borderRadius: 3
|
||||||
border: this.state.mode === "zoom" ? "1px solid black" : "1px solid white",
|
}}
|
||||||
backgroundColor: "white",
|
>
|
||||||
padding: 5,
|
{" "}
|
||||||
borderRadius: 3
|
<FaCrosshair />{" "}
|
||||||
}}> <FaZoom/> </button>
|
</button>
|
||||||
</span>
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ mode: "zoom" });
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
border:
|
||||||
|
this.state.mode === "zoom"
|
||||||
|
? "1px solid black"
|
||||||
|
: "1px solid white",
|
||||||
|
backgroundColor: "white",
|
||||||
|
padding: 5,
|
||||||
|
borderRadius: 3
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
<FaZoom />{" "}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button style={{
|
<button
|
||||||
fontSize: 12,
|
style={{
|
||||||
fontWeight: 400,
|
fontSize: 12,
|
||||||
color: "white",
|
fontWeight: 400,
|
||||||
padding: "10px 20px",
|
color: "white",
|
||||||
backgroundColor: globals.brightBlue,
|
padding: "10px 20px",
|
||||||
border: "none",
|
backgroundColor: globals.brightBlue,
|
||||||
cursor: "pointer",
|
border: "none",
|
||||||
}}> <FaSave style={{display: "inline-block"}}/> csv url for present selection </button>
|
cursor: "pointer"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
<FaSave style={{ display: "inline-block" }} /> csv url for present
|
||||||
|
selection{" "}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{display: this.state.mode === "brush" ? "inherit" : "none"}}
|
style={{ display: this.state.mode === "brush" ? "inherit" : "none" }}
|
||||||
id="graphAttachPoint"
|
id="graphAttachPoint"
|
||||||
>
|
/>
|
||||||
</div>
|
<div style={{ padding: 0, margin: 0 }}>
|
||||||
<div style={{padding: 0, margin: 0}}>
|
<canvas
|
||||||
<canvas width={globals.graphWidth} height={globals.graphHeight} ref={(canvas) => { this.reglCanvas = canvas}}/>
|
width={globals.graphWidth}
|
||||||
|
height={globals.graphHeight}
|
||||||
|
ref={canvas => {
|
||||||
|
this.reglCanvas = canvas;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Graph;
|
export default Graph;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
// createExpressionsCountsMap () {
|
// createExpressionsCountsMap () {
|
||||||
//
|
//
|
||||||
// const CHANGE_ME_MAGIC_GENE_INDEX = 5;
|
// const CHANGE_ME_MAGIC_GENE_INDEX = 5;
|
||||||
|
|||||||
+118
-71
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import styles from "./joy.css";
|
import styles from "./joy.css";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -5,112 +6,158 @@ import styles from "./joy.css";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var margin = { top: 30, right: 10, bottom: 30, left: 100 },
|
var margin = { top: 30, right: 10, bottom: 30, left: 100 },
|
||||||
width = 400 - margin.left - margin.right,
|
width = 400 - margin.left - margin.right,
|
||||||
height = 600 - margin.top - margin.bottom;
|
height = 600 - margin.top - margin.bottom;
|
||||||
|
|
||||||
// Percent two area charts can overlap
|
// Percent two area charts can overlap
|
||||||
var overlap = 0.4;
|
var overlap = 0.4;
|
||||||
|
|
||||||
var formatTime = d3.timeFormat('%I %p');
|
var formatTime = d3.timeFormat("%I %p");
|
||||||
|
|
||||||
var x = function(d) { return d.time; },
|
var x = function(d) {
|
||||||
xScale = d3.scaleTime().range([0, width]),
|
return d.time;
|
||||||
xValue = function(d) { return xScale(x(d)); },
|
},
|
||||||
xAxis = d3.axisBottom(xScale).tickFormat(formatTime);
|
xScale = d3.scaleTime().range([0, width]),
|
||||||
|
xValue = function(d) {
|
||||||
|
return xScale(x(d));
|
||||||
|
},
|
||||||
|
xAxis = d3.axisBottom(xScale).tickFormat(formatTime);
|
||||||
|
|
||||||
var y = function(d) { return d.value; },
|
var y = function(d) {
|
||||||
yScale = d3.scaleLinear(),
|
return d.value;
|
||||||
yValue = function(d) { return yScale(y(d)); };
|
},
|
||||||
|
yScale = d3.scaleLinear(),
|
||||||
|
yValue = function(d) {
|
||||||
|
return yScale(y(d));
|
||||||
|
};
|
||||||
|
|
||||||
var activity = function(d) { return d.key; },
|
var activity = function(d) {
|
||||||
activityScale = d3.scaleBand().range([0, height]),
|
return d.key;
|
||||||
activityValue = function(d) { return activityScale(activity(d)); },
|
},
|
||||||
activityAxis = d3.axisLeft(activityScale);
|
activityScale = d3.scaleBand().range([0, height]),
|
||||||
|
activityValue = function(d) {
|
||||||
|
return activityScale(activity(d));
|
||||||
|
},
|
||||||
|
activityAxis = d3.axisLeft(activityScale);
|
||||||
|
|
||||||
var area = d3.area()
|
var area = d3
|
||||||
.x(xValue)
|
.area()
|
||||||
.y1(yValue);
|
.x(xValue)
|
||||||
|
.y1(yValue);
|
||||||
|
|
||||||
var line = area.lineY1();
|
var line = area.lineY1();
|
||||||
|
|
||||||
function parseTime(offset) {
|
function parseTime(offset) {
|
||||||
var date = new Date(2017, 0, 1); // chose an arbitrary day
|
var date = new Date(2017, 0, 1); // chose an arbitrary day
|
||||||
return d3.timeMinute.offset(date, offset);
|
return d3.timeMinute.offset(date, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
function row(d) {
|
function row(d) {
|
||||||
return {
|
return {
|
||||||
activity: d.activity,
|
activity: d.activity,
|
||||||
time: parseTime(d.time),
|
time: parseTime(d.time),
|
||||||
value: +d.p_smooth
|
value: +d.p_smooth
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const drawJoy = (data) => {
|
const drawJoy = data => {
|
||||||
|
console.log("drawJoy: ", data);
|
||||||
|
|
||||||
console.log('drawJoy: ', data)
|
var svg = d3
|
||||||
|
.select("#joyplot")
|
||||||
|
.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 svg = d3.select('#joyplot').append('svg')
|
d3.tsv(
|
||||||
.attr('width', width + margin.left + margin.right)
|
"https://gist.githubusercontent.com/armollica/3b5f83836c1de5cca7b1d35409a013e3/raw/783d1bbc2dd3aabbfcba83ece4a670de6f1ec371/data.tsv",
|
||||||
.attr('height', height + margin.top + margin.bottom)
|
row,
|
||||||
.append('g')
|
function(error, dataFlat) {
|
||||||
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
// Sort by time
|
||||||
|
dataFlat.sort(function(a, b) {
|
||||||
|
return a.time - b.time;
|
||||||
|
});
|
||||||
|
|
||||||
d3.tsv("https://gist.githubusercontent.com/armollica/3b5f83836c1de5cca7b1d35409a013e3/raw/783d1bbc2dd3aabbfcba83ece4a670de6f1ec371/data.tsv", row, function(error, dataFlat) {
|
var data = d3
|
||||||
|
.nest()
|
||||||
// Sort by time
|
.key(function(d) {
|
||||||
dataFlat.sort(function(a, b) { return a.time - b.time; });
|
return d.activity;
|
||||||
|
})
|
||||||
var data = d3.nest()
|
|
||||||
.key(function(d) { return d.activity; })
|
|
||||||
.entries(dataFlat);
|
.entries(dataFlat);
|
||||||
|
|
||||||
// Sort activities by peak activity time
|
// Sort activities by peak activity time
|
||||||
function peakTime(d) {
|
function peakTime(d) {
|
||||||
var i = d3.scan(d.values, function(a, b) { return y(b) - y(a); });
|
var i = d3.scan(d.values, function(a, b) {
|
||||||
|
return y(b) - y(a);
|
||||||
|
});
|
||||||
return d.values[i].time;
|
return d.values[i].time;
|
||||||
};
|
}
|
||||||
data.sort(function(a, b) { return peakTime(b) - peakTime(a); });
|
data.sort(function(a, b) {
|
||||||
|
return peakTime(b) - peakTime(a);
|
||||||
|
});
|
||||||
|
|
||||||
console.log('sorted', data)
|
console.log("sorted", data);
|
||||||
|
|
||||||
xScale.domain(d3.extent(dataFlat, x));
|
xScale.domain(d3.extent(dataFlat, x));
|
||||||
|
|
||||||
activityScale.domain(data.map(function(d) { return d.key; }));
|
activityScale.domain(
|
||||||
|
data.map(function(d) {
|
||||||
|
return d.key;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
var areaChartHeight = (1 + overlap) * (height / activityScale.domain().length);
|
var areaChartHeight =
|
||||||
|
(1 + overlap) * (height / activityScale.domain().length);
|
||||||
|
|
||||||
yScale
|
yScale.domain(d3.extent(dataFlat, y)).range([areaChartHeight, 0]);
|
||||||
.domain(d3.extent(dataFlat, y))
|
|
||||||
.range([areaChartHeight, 0]);
|
|
||||||
|
|
||||||
area.y0(yScale(0));
|
area.y0(yScale(0));
|
||||||
|
|
||||||
var gActivity = svg.append('g').attr('class', "activities")
|
var gActivity = svg
|
||||||
.selectAll('.activity').data(data)
|
.append("g")
|
||||||
.enter().append('g')
|
.attr("class", "activities")
|
||||||
.attr('class', function(d) { return `${styles.activity} ${styles.activity["--"+d.key]}`; })
|
.selectAll(".activity")
|
||||||
.attr('transform', function(d) {
|
.data(data)
|
||||||
var ty = activityValue(d) - activityScale.bandwidth() + 5;
|
.enter()
|
||||||
return 'translate(0,' + ty + ')';
|
.append("g")
|
||||||
});
|
.attr("class", function(d) {
|
||||||
|
return `${styles.activity} ${styles.activity["--" + d.key]}`;
|
||||||
|
})
|
||||||
|
.attr("transform", function(d) {
|
||||||
|
var ty = activityValue(d) - activityScale.bandwidth() + 5;
|
||||||
|
return "translate(0," + ty + ")";
|
||||||
|
});
|
||||||
|
|
||||||
gActivity.append('path').attr('class', styles.area)
|
gActivity
|
||||||
.datum(function(d) { return d.values; })
|
.append("path")
|
||||||
.attr('d', area);
|
.attr("class", styles.area)
|
||||||
|
.datum(function(d) {
|
||||||
|
return d.values;
|
||||||
|
})
|
||||||
|
.attr("d", area);
|
||||||
|
|
||||||
gActivity.append('path').attr('class', styles.line)
|
gActivity
|
||||||
.datum(function(d) { return d.values; })
|
.append("path")
|
||||||
.attr('d', line);
|
.attr("class", styles.line)
|
||||||
|
.datum(function(d) {
|
||||||
|
return d.values;
|
||||||
|
})
|
||||||
|
.attr("d", line);
|
||||||
|
|
||||||
svg.append('g').attr('class', `${styles.axis} ${styles["axis--x"]}`)
|
svg
|
||||||
.attr('transform', 'translate(0,' + height + ')')
|
.append("g")
|
||||||
|
.attr("class", `${styles.axis} ${styles["axis--x"]}`)
|
||||||
|
.attr("transform", "translate(0," + height + ")")
|
||||||
.call(xAxis);
|
.call(xAxis);
|
||||||
|
|
||||||
svg.append('g').attr('class', `${styles.axis} ${styles["axis--activity"]}`)
|
svg
|
||||||
|
.append("g")
|
||||||
|
.attr("class", `${styles.axis} ${styles["axis--activity"]}`)
|
||||||
.call(activityAxis);
|
.call(activityAxis);
|
||||||
|
}
|
||||||
})
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default drawJoy;
|
export default drawJoy;
|
||||||
|
|||||||
+13
-13
@@ -1,38 +1,38 @@
|
|||||||
import React from 'react';
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import styles from "./joy.css";
|
import styles from "./joy.css";
|
||||||
import drawJoy from "./drawJoy";
|
import drawJoy from "./drawJoy";
|
||||||
import joyParser from "./joyParser";
|
import joyParser from "./joyParser";
|
||||||
|
|
||||||
class Joy extends React.Component {
|
class Joy extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {};
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.data) {
|
if (nextProps.data) {
|
||||||
console.log('joyplot data 44', nextProps.data)
|
console.log("joyplot data 44", nextProps.data);
|
||||||
drawJoy(joyParser(nextProps.data));
|
drawJoy(joyParser(nextProps.data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div id="joyplot_wrapper" style={{marginTop: 50}}>
|
<div id="joyplot_wrapper" style={{ marginTop: 50 }}>
|
||||||
<h3> Joy </h3>
|
<h3> Joy </h3>
|
||||||
<p> Cell expression distribution per gene & if differential expression, Ie., cells for cluster 5, top genes expressed by cluster 8</p>
|
<p>
|
||||||
|
{" "}
|
||||||
|
Cell expression distribution per gene & if differential expression,
|
||||||
|
Ie., cells for cluster 5, top genes expressed by cluster 8
|
||||||
|
</p>
|
||||||
<div id="joyplot"> </div>
|
<div id="joyplot"> </div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Joy;
|
export default Joy;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const joyParser = (data, count = 20) => {
|
const joyParser = (data, count = 20) => {
|
||||||
const genes = [];
|
const genes = [];
|
||||||
@@ -9,22 +7,23 @@ const joyParser = (data, count = 20) => {
|
|||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const gene = {
|
const gene = {
|
||||||
key: data.genes[i], /* key values naming: https://bl.ocks.org/armollica/3b5f83836c1de5cca7b1d35409a013e3 */
|
key:
|
||||||
|
data.genes[
|
||||||
|
i
|
||||||
|
] /* key values naming: https://bl.ocks.org/armollica/3b5f83836c1de5cca7b1d35409a013e3 */,
|
||||||
values: []
|
values: []
|
||||||
}
|
};
|
||||||
|
|
||||||
data.cells.forEach((cell) => {
|
data.cells.forEach(cell => {
|
||||||
gene.values.push({
|
gene.values.push({
|
||||||
value: cell["e"][i]
|
value: cell["e"][i]
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
genes.push(gene)
|
genes.push(gene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return genes;
|
return genes;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default joyParser;
|
export default joyParser;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import Categorical from "./categorical/categorical";
|
import Categorical from "./categorical/categorical";
|
||||||
@@ -7,10 +8,8 @@ import { connect } from "react-redux";
|
|||||||
import Heatmap from "./expression/diffExpHeatmap";
|
import Heatmap from "./expression/diffExpHeatmap";
|
||||||
import * as globals from "../globals";
|
import * as globals from "../globals";
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
return {
|
return {};
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
class LeftSideBar extends React.Component {
|
class LeftSideBar extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -21,24 +20,42 @@ class LeftSideBar extends React.Component {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div style={{position: "fixed"}}>
|
<div style={{ position: "fixed" }}>
|
||||||
<p style={{margin: 10, fontSize: 16, fontWeight: 700, width: "100%"}}>CELLxGENE {globals.datasetTitle} </p>
|
<p style={{ margin: 10, fontSize: 16, fontWeight: 700, width: "100%" }}>
|
||||||
<div style={{padding: 10}}>
|
CELLxGENE {globals.datasetTitle}{" "}
|
||||||
|
</p>
|
||||||
|
<div style={{ padding: 10 }}>
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
padding: "10px 30px",
|
padding: "10px 30px",
|
||||||
outline: 0,
|
outline: 0,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontStyle: this.state.currentTab === "metadata" ? "inherit" : "italic",
|
fontStyle:
|
||||||
|
this.state.currentTab === "metadata" ? "inherit" : "italic",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
border: "none",
|
border: "none",
|
||||||
backgroundColor: "#FFF",
|
backgroundColor: "#FFF",
|
||||||
borderTop: this.state.currentTab === "metadata" ? "4px solid " + globals.brightBlue : "none",
|
borderTop:
|
||||||
borderBottom: this.state.currentTab === "metadata" ? "none" : "1px solid " + globals.lightGrey,
|
this.state.currentTab === "metadata"
|
||||||
borderRight: this.state.currentTab === "metadata" ? "1px solid " + globals.lightGrey : "none",
|
? "4px solid " + globals.brightBlue
|
||||||
borderLeft: this.state.currentTab === "metadata" ? "1px solid " + globals.lightGrey : "none",
|
: "none",
|
||||||
|
borderBottom:
|
||||||
|
this.state.currentTab === "metadata"
|
||||||
|
? "none"
|
||||||
|
: "1px solid " + globals.lightGrey,
|
||||||
|
borderRight:
|
||||||
|
this.state.currentTab === "metadata"
|
||||||
|
? "1px solid " + globals.lightGrey
|
||||||
|
: "none",
|
||||||
|
borderLeft:
|
||||||
|
this.state.currentTab === "metadata"
|
||||||
|
? "1px solid " + globals.lightGrey
|
||||||
|
: "none"
|
||||||
}}
|
}}
|
||||||
onClick={() => {this.setState({currentTab: "metadata"})}}>
|
onClick={() => {
|
||||||
|
this.setState({ currentTab: "metadata" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
Metadata
|
Metadata
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -46,39 +63,59 @@ class LeftSideBar extends React.Component {
|
|||||||
padding: "10px 30px",
|
padding: "10px 30px",
|
||||||
outline: 0,
|
outline: 0,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontStyle: this.state.currentTab === "expression" ? "inherit" : "italic",
|
fontStyle:
|
||||||
|
this.state.currentTab === "expression" ? "inherit" : "italic",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
border: "none",
|
border: "none",
|
||||||
backgroundColor: "#FFF",
|
backgroundColor: "#FFF",
|
||||||
borderTop: this.state.currentTab === "expression" ? "4px solid " + globals.brightBlue : "none",
|
borderTop:
|
||||||
borderBottom: this.state.currentTab === "expression" ? "none" : "1px solid " + globals.lightGrey,
|
this.state.currentTab === "expression"
|
||||||
borderRight: this.state.currentTab === "expression" ? "1px solid " + globals.lightGrey : "none",
|
? "4px solid " + globals.brightBlue
|
||||||
borderLeft: this.state.currentTab === "expression" ? "1px solid " + globals.lightGrey : "none",
|
: "none",
|
||||||
|
borderBottom:
|
||||||
|
this.state.currentTab === "expression"
|
||||||
|
? "none"
|
||||||
|
: "1px solid " + globals.lightGrey,
|
||||||
|
borderRight:
|
||||||
|
this.state.currentTab === "expression"
|
||||||
|
? "1px solid " + globals.lightGrey
|
||||||
|
: "none",
|
||||||
|
borderLeft:
|
||||||
|
this.state.currentTab === "expression"
|
||||||
|
? "1px solid " + globals.lightGrey
|
||||||
|
: "none"
|
||||||
}}
|
}}
|
||||||
onClick={() => {this.setState({currentTab: "expression"})}}>
|
onClick={() => {
|
||||||
|
this.setState({ currentTab: "expression" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
Expression
|
Expression
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div
|
||||||
height: 500,
|
style={{
|
||||||
width: 350,
|
height: 500,
|
||||||
padding: 10,
|
width: 350,
|
||||||
overflowY: "scroll",
|
padding: 10,
|
||||||
overflowX: "hidden",
|
overflowY: "scroll",
|
||||||
}}>
|
overflowX: "hidden"
|
||||||
{this.state.currentTab === "metadata" ? <Categorical/> : null}
|
}}
|
||||||
{this.state.currentTab === "metadata" ? <Continuous/> : null}
|
>
|
||||||
{this.state.currentTab === "expression" ? <Heatmap/> : null}
|
{this.state.currentTab === "metadata" ? <Categorical /> : null}
|
||||||
|
{this.state.currentTab === "metadata" ? <Continuous /> : null}
|
||||||
|
{this.state.currentTab === "expression" ? <Heatmap /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div
|
||||||
boxShadow: "-3px -4px 13px 0px rgba(201,201,201,1)",
|
style={{
|
||||||
paddingTop: 10
|
boxShadow: "-3px -4px 13px 0px rgba(201,201,201,1)",
|
||||||
}}>
|
paddingTop: 10
|
||||||
<ExpressionButtons/>
|
}}
|
||||||
|
>
|
||||||
|
<ExpressionButtons />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default LeftSideBar;
|
export default LeftSideBar;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
const mat4 = require('gl-mat4')
|
// jshint esversion: 6
|
||||||
|
const mat4 = require("gl-mat4");
|
||||||
|
|
||||||
// opacity: https://github.com/spacetx/starfish/blob/master/viz/draw/regions.js
|
// opacity: https://github.com/spacetx/starfish/blob/master/viz/draw/regions.js
|
||||||
|
|
||||||
export default function (regl) {
|
export default function(regl) {
|
||||||
return regl({
|
return regl({
|
||||||
vert: `
|
vert: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
@@ -29,14 +30,14 @@ export default function (regl) {
|
|||||||
}`,
|
}`,
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
position: regl.prop('position'),
|
position: regl.prop("position"),
|
||||||
color: regl.prop('color'),
|
color: regl.prop("color"),
|
||||||
size: regl.prop('size')
|
size: regl.prop("size")
|
||||||
},
|
},
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
distance: regl.prop('distance'),
|
distance: regl.prop("distance"),
|
||||||
view: regl.prop('view'),
|
view: regl.prop("view"),
|
||||||
projection: (context, props) => {
|
projection: (context, props) => {
|
||||||
return mat4.perspective(
|
return mat4.perspective(
|
||||||
[],
|
[],
|
||||||
@@ -44,12 +45,12 @@ export default function (regl) {
|
|||||||
context.viewportWidth * props.scale / context.viewportHeight,
|
context.viewportWidth * props.scale / context.viewportHeight,
|
||||||
0.01,
|
0.01,
|
||||||
1000
|
1000
|
||||||
)
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
count: regl.prop('count'),
|
count: regl.prop("count"),
|
||||||
|
|
||||||
primitive: 'points'
|
primitive: "points"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import renderQueue from "../../util/renderQueue";
|
import renderQueue from "../../util/renderQueue";
|
||||||
|
|
||||||
import {
|
import { margin, width, height, createDimensions } from "./util";
|
||||||
margin,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
createDimensions,
|
|
||||||
} from "./util";
|
|
||||||
|
|
||||||
const drawScatterplotCanvas = (
|
const drawScatterplotCanvas = (
|
||||||
context,
|
context,
|
||||||
@@ -17,38 +13,42 @@ const drawScatterplotCanvas = (
|
|||||||
expression,
|
expression,
|
||||||
scatterplotXXaccessor,
|
scatterplotXXaccessor,
|
||||||
scatterplotYYaccessor,
|
scatterplotYYaccessor,
|
||||||
_currentCellSelectionMap,
|
_currentCellSelectionMap
|
||||||
) => {
|
) => {
|
||||||
|
return cell => {
|
||||||
return (cell) => {
|
/*
|
||||||
|
|
||||||
/*
|
|
||||||
this is necessary until we are no longer getting expression for all cells, but only for 'world'
|
this is necessary until we are no longer getting expression for all cells, but only for 'world'
|
||||||
...which will mean refetching when we regraph, or 'go back up to all cells'
|
...which will mean refetching when we regraph, or 'go back up to all cells'
|
||||||
*/
|
*/
|
||||||
if (!_currentCellSelectionMap[cell.cellname]) { return }
|
if (!_currentCellSelectionMap[cell.cellname]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
/* context.arc(x,y,r,sAngle,eAngle,counterclockwise); */
|
/* context.arc(x,y,r,sAngle,eAngle,counterclockwise); */
|
||||||
context.arc(
|
context.arc(
|
||||||
xScale(cell.e[expression.data.genes.indexOf(scatterplotXXaccessor)]), /* x */
|
xScale(
|
||||||
yScale(cell.e[expression.data.genes.indexOf(scatterplotYYaccessor)]), /* y */
|
cell.e[expression.data.genes.indexOf(scatterplotXXaccessor)]
|
||||||
_currentCellSelectionMap[cell.cellname]["__selected__"] ? 3 : 1.5, /* r */
|
) /* x */,
|
||||||
0, /* sAngle */
|
yScale(
|
||||||
2 * Math.PI /* eAngle */
|
cell.e[expression.data.genes.indexOf(scatterplotYYaccessor)]
|
||||||
);
|
) /* y */,
|
||||||
|
_currentCellSelectionMap[cell.cellname]["__selected__"] ? 3 : 1.5 /* r */,
|
||||||
|
0 /* sAngle */,
|
||||||
|
2 * Math.PI /* eAngle */
|
||||||
|
);
|
||||||
|
|
||||||
context.fillStyle = _currentCellSelectionMap[cell.cellname]["__color__"]
|
context.fillStyle = _currentCellSelectionMap[cell.cellname]["__color__"];
|
||||||
|
|
||||||
if (_currentCellSelectionMap[cell.cellname]["__selected__"]) {
|
if (_currentCellSelectionMap[cell.cellname]["__selected__"]) {
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
} else {
|
} else {
|
||||||
context.globalAlpha = opacityForDeselectedCells;
|
context.globalAlpha = opacityForDeselectedCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.fill();
|
context.fill();
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const drawScatterplotCanvasUsingRenderQueue = (
|
export const drawScatterplotCanvasUsingRenderQueue = (
|
||||||
context,
|
context,
|
||||||
@@ -63,7 +63,10 @@ export const drawScatterplotCanvasUsingRenderQueue = (
|
|||||||
/* clear canvas */
|
/* clear canvas */
|
||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
const _currentCellSelectionMap = _.keyBy(currentCellSelection, "CellName"); /* move me to the reducer */
|
const _currentCellSelectionMap = _.keyBy(
|
||||||
|
currentCellSelection,
|
||||||
|
"CellName"
|
||||||
|
); /* move me to the reducer */
|
||||||
|
|
||||||
const _renderScatterplotWithFunctionReturnedByQueue = renderQueue(
|
const _renderScatterplotWithFunctionReturnedByQueue = renderQueue(
|
||||||
drawScatterplotCanvas(
|
drawScatterplotCanvas(
|
||||||
@@ -75,13 +78,12 @@ export const drawScatterplotCanvasUsingRenderQueue = (
|
|||||||
expression,
|
expression,
|
||||||
scatterplotXXaccessor,
|
scatterplotXXaccessor,
|
||||||
scatterplotYYaccessor,
|
scatterplotYYaccessor,
|
||||||
_currentCellSelectionMap,
|
_currentCellSelectionMap
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
|
|
||||||
_renderScatterplotWithFunctionReturnedByQueue(expression.data.cells)
|
_renderScatterplotWithFunctionReturnedByQueue(expression.data.cells);
|
||||||
return _renderScatterplotWithFunctionReturnedByQueue;
|
return _renderScatterplotWithFunctionReturnedByQueue;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
export default _.debounce(drawScatterplotCanvasUsingRenderQueue, 100);
|
||||||
|
|
||||||
export default _.debounce(drawScatterplotCanvasUsingRenderQueue, 100)
|
|
||||||
|
|||||||
@@ -1,33 +1,37 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
// https://bl.ocks.org/Jverma/076377dd0125b1a508621441752735fc
|
// https://bl.ocks.org/Jverma/076377dd0125b1a508621441752735fc
|
||||||
// https://peterbeshai.com/scatterplot-in-d3-with-voronoi-interaction.html
|
// https://peterbeshai.com/scatterplot-in-d3-with-voronoi-interaction.html
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import scatterplot from "./scatterplot";
|
import scatterplot from "./scatterplot";
|
||||||
import setupScatterplot from "./setupScatterplot";
|
import setupScatterplot from "./setupScatterplot";
|
||||||
import styles from './scatterplot.css';
|
import styles from "./scatterplot.css";
|
||||||
import drawScatterplotCanvas from "./drawScatterplotCanvas";
|
import drawScatterplotCanvas from "./drawScatterplotCanvas";
|
||||||
|
|
||||||
import mat4 from 'gl-mat4';
|
import mat4 from "gl-mat4";
|
||||||
import fit from 'canvas-fit';
|
import fit from "canvas-fit";
|
||||||
import _camera from '../../util/camera.js'
|
import _camera from "../../util/camera.js";
|
||||||
import _regl from 'regl'
|
import _regl from "regl";
|
||||||
import _drawPoints from './drawPointsRegl'
|
import _drawPoints from "./drawPointsRegl";
|
||||||
|
|
||||||
import {
|
import { margin, width, height, createDimensions } from "./util";
|
||||||
margin,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
createDimensions,
|
|
||||||
} from "./util";
|
|
||||||
|
|
||||||
@connect((state) => {
|
@connect(state => {
|
||||||
|
const ranges =
|
||||||
const ranges = state.cells.cells && state.cells.cells.data.ranges ? state.cells.cells.data.ranges : null;
|
state.cells.cells && state.cells.cells.data.ranges
|
||||||
const metadata = state.cells.cells && state.cells.cells.data.metadata ? state.cells.cells.data.metadata : null;
|
? state.cells.cells.data.ranges
|
||||||
const initializeRanges = state.initialize.data && state.initialize.data.data.ranges ? state.initialize.data.data.ranges : null;
|
: null;
|
||||||
|
const metadata =
|
||||||
|
state.cells.cells && state.cells.cells.data.metadata
|
||||||
|
? state.cells.cells.data.metadata
|
||||||
|
: null;
|
||||||
|
const initializeRanges =
|
||||||
|
state.initialize.data && state.initialize.data.data.ranges
|
||||||
|
? state.initialize.data.data.ranges
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ranges,
|
ranges,
|
||||||
@@ -40,8 +44,8 @@ import {
|
|||||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||||
opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
|
opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
|
||||||
differential: state.differential,
|
differential: state.differential,
|
||||||
expression: state.expression,
|
expression: state.expression
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
class Scatterplot extends React.Component {
|
class Scatterplot extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -53,39 +57,31 @@ class Scatterplot extends React.Component {
|
|||||||
axes: null,
|
axes: null,
|
||||||
dimensions: null,
|
dimensions: null,
|
||||||
xScale: null,
|
xScale: null,
|
||||||
yScale: null,
|
yScale: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const { svg } = setupScatterplot(width, height, margin);
|
||||||
svg
|
|
||||||
} = setupScatterplot(
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
margin
|
|
||||||
);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
svg
|
svg
|
||||||
})
|
});
|
||||||
|
|
||||||
const camera = _camera(this.reglCanvas, {scale: true, rotate: false});
|
const camera = _camera(this.reglCanvas, { scale: true, rotate: false });
|
||||||
const regl = _regl(this.reglCanvas)
|
const regl = _regl(this.reglCanvas);
|
||||||
|
|
||||||
const drawPoints = _drawPoints(regl)
|
const drawPoints = _drawPoints(regl);
|
||||||
|
|
||||||
// preallocate buffers
|
// preallocate buffers
|
||||||
const pointBuffer = regl.buffer()
|
const pointBuffer = regl.buffer();
|
||||||
const colorBuffer = regl.buffer()
|
const colorBuffer = regl.buffer();
|
||||||
const sizeBuffer = regl.buffer()
|
const sizeBuffer = regl.buffer();
|
||||||
|
|
||||||
|
|
||||||
regl.frame(({viewportWidth, viewportHeight}) => {
|
|
||||||
|
|
||||||
|
regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||||
regl.clear({
|
regl.clear({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
color: [1, 1, 1, 1]
|
color: [1, 1, 1, 1]
|
||||||
})
|
});
|
||||||
|
|
||||||
drawPoints({
|
drawPoints({
|
||||||
distance: camera.distance,
|
distance: camera.distance,
|
||||||
@@ -95,29 +91,28 @@ class Scatterplot extends React.Component {
|
|||||||
count: this.count,
|
count: this.count,
|
||||||
view: camera.view(),
|
view: camera.view(),
|
||||||
scale: viewportHeight / viewportWidth
|
scale: viewportHeight / viewportWidth
|
||||||
})
|
});
|
||||||
|
|
||||||
camera.tick()
|
camera.tick();
|
||||||
})
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
regl,
|
regl,
|
||||||
sizeBuffer,
|
sizeBuffer,
|
||||||
pointBuffer,
|
pointBuffer,
|
||||||
colorBuffer,
|
colorBuffer
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
|
||||||
this.maybeSetupScalesAndDrawAxes(nextProps);
|
this.maybeSetupScalesAndDrawAxes(nextProps);
|
||||||
|
|
||||||
}
|
}
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (
|
if (
|
||||||
(this.state.xScale && this.state.yScale) &&
|
(this.state.xScale &&
|
||||||
(this.props.scatterplotXXaccessor && this.props.scatterplotYYaccessor) &&
|
this.state.yScale &&
|
||||||
this.props.scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc
|
(this.props.scatterplotXXaccessor &&
|
||||||
|
this.props.scatterplotYYaccessor) &&
|
||||||
|
this.props.scatterplotXXaccessor !== prevProps.scatterplotXXaccessor) || // was CLU now FTH1 etc
|
||||||
this.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor
|
this.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor
|
||||||
) {
|
) {
|
||||||
this.drawAxesSVG(this.state.xScale, this.state.yScale);
|
this.drawAxesSVG(this.state.xScale, this.state.yScale);
|
||||||
@@ -136,20 +131,24 @@ class Scatterplot extends React.Component {
|
|||||||
this.state.xScale &&
|
this.state.xScale &&
|
||||||
this.state.yScale
|
this.state.yScale
|
||||||
) {
|
) {
|
||||||
const _currentCellSelectionMap = _.keyBy(this.props.currentCellSelection, "CellName"); /* move me to the reducer */
|
const _currentCellSelectionMap = _.keyBy(
|
||||||
|
this.props.currentCellSelection,
|
||||||
|
"CellName"
|
||||||
|
); /* move me to the reducer */
|
||||||
|
|
||||||
const positions = [];
|
const positions = [];
|
||||||
const colors = [];
|
const colors = [];
|
||||||
const sizes = [];
|
const sizes = [];
|
||||||
|
|
||||||
const glScaleX = d3.scaleLinear()
|
const glScaleX = d3
|
||||||
|
.scaleLinear()
|
||||||
.domain([0, width])
|
.domain([0, width])
|
||||||
.range([-.95, .95]) /* padding */
|
.range([-0.95, 0.95]); /* padding */
|
||||||
|
|
||||||
const glScaleY = d3.scaleLinear()
|
const glScaleY = d3
|
||||||
|
.scaleLinear()
|
||||||
.domain([0, height])
|
.domain([0, height])
|
||||||
.range([-1, 1])
|
.range([-1, 1]);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Construct Vectors
|
Construct Vectors
|
||||||
@@ -159,21 +158,40 @@ class Scatterplot extends React.Component {
|
|||||||
this if is necessary until we are no longer getting expression for all cells, but only for 'world'
|
this if is necessary until we are no longer getting expression for all cells, but only for 'world'
|
||||||
...which will mean refetching when we regraph, or 'go back up to all cells'
|
...which will mean refetching when we regraph, or 'go back up to all cells'
|
||||||
*/
|
*/
|
||||||
if (_currentCellSelectionMap[cell.cellname]) { /* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
|
if (_currentCellSelectionMap[cell.cellname]) {
|
||||||
|
/* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
|
||||||
positions.push([
|
positions.push([
|
||||||
glScaleX(this.state.xScale(cell.e[this.props.expression.data.genes.indexOf(this.props.scatterplotXXaccessor)])), /* scale each point first to the window as we calculate extents separately below, so no need to repeat */
|
glScaleX(
|
||||||
glScaleY(this.state.yScale(cell.e[this.props.expression.data.genes.indexOf(this.props.scatterplotYYaccessor)]))
|
this.state.xScale(
|
||||||
])
|
cell.e[
|
||||||
|
this.props.expression.data.genes.indexOf(
|
||||||
|
this.props.scatterplotXXaccessor
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
) /* scale each point first to the window as we calculate extents separately below, so no need to repeat */,
|
||||||
|
glScaleY(
|
||||||
|
this.state.yScale(
|
||||||
|
cell.e[
|
||||||
|
this.props.expression.data.genes.indexOf(
|
||||||
|
this.props.scatterplotYYaccessor
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
colors.push(_currentCellSelectionMap[cell.cellname]["__colorRGB__"])
|
colors.push(_currentCellSelectionMap[cell.cellname]["__colorRGB__"]);
|
||||||
sizes.push(_currentCellSelectionMap[cell.cellname]["__selected__"] ? 4 : .2) /* make this a function of the number of total cells, including regraph */
|
sizes.push(
|
||||||
|
_currentCellSelectionMap[cell.cellname]["__selected__"] ? 4 : 0.2
|
||||||
|
); /* make this a function of the number of total cells, including regraph */
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
this.state.pointBuffer(positions)
|
this.state.pointBuffer(positions);
|
||||||
this.state.colorBuffer(colors)
|
this.state.colorBuffer(colors);
|
||||||
this.state.sizeBuffer(sizes)
|
this.state.sizeBuffer(sizes);
|
||||||
this.count = positions.length
|
this.count = positions.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maybeSetupScalesAndDrawAxes(nextProps) {
|
maybeSetupScalesAndDrawAxes(nextProps) {
|
||||||
@@ -183,61 +201,75 @@ class Scatterplot extends React.Component {
|
|||||||
nextProps.scatterplotXXaccessor &&
|
nextProps.scatterplotXXaccessor &&
|
||||||
nextProps.scatterplotYYaccessor
|
nextProps.scatterplotYYaccessor
|
||||||
) {
|
) {
|
||||||
const xScale = d3.scaleLinear()
|
const xScale = d3
|
||||||
.domain(d3.extent(nextProps.expression.data.cells, (cell, i) => {
|
.scaleLinear()
|
||||||
return cell.e[nextProps.expression.data.genes.indexOf(nextProps.scatterplotXXaccessor)]
|
.domain(
|
||||||
}))
|
d3.extent(nextProps.expression.data.cells, (cell, i) => {
|
||||||
.range([0, width])
|
return cell.e[
|
||||||
|
nextProps.expression.data.genes.indexOf(
|
||||||
|
nextProps.scatterplotXXaccessor
|
||||||
|
)
|
||||||
|
];
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.range([0, width]);
|
||||||
|
|
||||||
const yScale = d3.scaleLinear()
|
const yScale = d3
|
||||||
.domain(d3.extent(nextProps.expression.data.cells, (cell) => {
|
.scaleLinear()
|
||||||
return cell.e[nextProps.expression.data.genes.indexOf(nextProps.scatterplotYYaccessor)]
|
.domain(
|
||||||
}))
|
d3.extent(nextProps.expression.data.cells, cell => {
|
||||||
.range([height, 0])
|
return cell.e[
|
||||||
|
nextProps.expression.data.genes.indexOf(
|
||||||
|
nextProps.scatterplotYYaccessor
|
||||||
|
)
|
||||||
|
];
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.range([height, 0]);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
xScale,
|
xScale,
|
||||||
yScale
|
yScale
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawAxesSVG(xScale, yScale) {
|
drawAxesSVG(xScale, yScale) {
|
||||||
|
|
||||||
this.state.svg.selectAll("*").remove();
|
this.state.svg.selectAll("*").remove();
|
||||||
|
|
||||||
// the axes are much cleaner and easier now. No need to rotate and orient the axis, just call axisBottom, axisLeft etc.
|
// the axes are much cleaner and easier now. No need to rotate and orient the axis, just call axisBottom, axisLeft etc.
|
||||||
var xAxis = d3.axisBottom()
|
var xAxis = d3.axisBottom().scale(xScale);
|
||||||
.scale(xScale);
|
|
||||||
|
|
||||||
var yAxis = d3.axisLeft()
|
var yAxis = d3.axisLeft().scale(yScale);
|
||||||
.scale(yScale);
|
|
||||||
|
|
||||||
// adding axes is also simpler now, just translate x-axis to (0,height) and it's alread defined to be a bottom axis.
|
// adding axes is also simpler now, just translate x-axis to (0,height) and it's alread defined to be a bottom axis.
|
||||||
this.state.svg.append('g')
|
this.state.svg
|
||||||
.attr('transform', 'translate(0,' + height + ')')
|
.append("g")
|
||||||
.attr('class', 'x axis')
|
.attr("transform", "translate(0," + height + ")")
|
||||||
|
.attr("class", "x axis")
|
||||||
.call(xAxis);
|
.call(xAxis);
|
||||||
|
|
||||||
// y-axis is translated to (0,0)
|
// y-axis is translated to (0,0)
|
||||||
this.state.svg.append('g')
|
this.state.svg
|
||||||
.attr('transform', 'translate(0,0)')
|
.append("g")
|
||||||
.attr('class', 'y axis')
|
.attr("transform", "translate(0,0)")
|
||||||
|
.attr("class", "y axis")
|
||||||
.call(yAxis);
|
.call(yAxis);
|
||||||
|
|
||||||
// adding label. For x-axis, it's at (10, 10), and for y-axis at (width, height-10).
|
// adding label. For x-axis, it's at (10, 10), and for y-axis at (width, height-10).
|
||||||
this.state.svg.append('text')
|
this.state.svg
|
||||||
.attr('x', 10)
|
.append("text")
|
||||||
.attr('y', 10)
|
.attr("x", 10)
|
||||||
.attr('class', 'label')
|
.attr("y", 10)
|
||||||
|
.attr("class", "label")
|
||||||
.text(this.props.scatterplotYYaccessor);
|
.text(this.props.scatterplotYYaccessor);
|
||||||
|
|
||||||
this.state.svg.append('text')
|
this.state.svg
|
||||||
.attr('x', width)
|
.append("text")
|
||||||
.attr('y', height - 10)
|
.attr("x", width)
|
||||||
.attr('text-anchor', 'end')
|
.attr("y", height - 10)
|
||||||
.attr('class', 'label')
|
.attr("text-anchor", "end")
|
||||||
|
.attr("class", "label")
|
||||||
.text(this.props.scatterplotXXaccessor);
|
.text(this.props.scatterplotXXaccessor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -248,15 +280,16 @@ class Scatterplot extends React.Component {
|
|||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
marginTop: 15,
|
marginTop: 15,
|
||||||
paddingBottom: 20,
|
paddingBottom: 20,
|
||||||
boxShadow: "3px 4px 13px 0px rgba(201,201,201,1)",
|
boxShadow: "3px 4px 13px 0px rgba(201,201,201,1)"
|
||||||
}}
|
}}
|
||||||
id="scatterplot_wrapper">
|
id="scatterplot_wrapper"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={styles.scatterplot}
|
className={styles.scatterplot}
|
||||||
id="scatterplot"
|
id="scatterplot"
|
||||||
style={{
|
style={{
|
||||||
width: width + margin.left + margin.right + "px",
|
width: width + margin.left + margin.right + "px",
|
||||||
height: height + margin.top + margin.bottom + "px",
|
height: height + margin.top + margin.bottom + "px"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
@@ -266,14 +299,16 @@ class Scatterplot extends React.Component {
|
|||||||
marginLeft: margin.left - 7,
|
marginLeft: margin.left - 7,
|
||||||
marginTop: margin.top
|
marginTop: margin.top
|
||||||
}}
|
}}
|
||||||
ref={(canvas) => { this.reglCanvas = canvas}}/>
|
ref={canvas => {
|
||||||
|
this.reglCanvas = canvas;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Scatterplot;
|
export default Scatterplot;
|
||||||
|
|
||||||
|
|
||||||
// <SectionHeader text="Continuous Metadata"/>
|
// <SectionHeader text="Continuous Metadata"/>
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
/*****************************************
|
/*****************************************
|
||||||
******************************************
|
******************************************
|
||||||
Setup SVG & Canvas elements
|
Setup SVG & Canvas elements
|
||||||
******************************************
|
******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
|
|
||||||
const setupScatterplot = (
|
const setupScatterplot = (width, height, margin) => {
|
||||||
width,
|
var container = d3.select("#scatterplot");
|
||||||
height,
|
|
||||||
margin
|
|
||||||
) => {
|
|
||||||
|
|
||||||
var container = d3.select("#scatterplot")
|
var svg = container
|
||||||
|
.append("svg")
|
||||||
var svg = container.append("svg")
|
.attr("width", width + margin.left + margin.right)
|
||||||
.attr("width", width + margin.left + margin.right)
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
svg,
|
svg
|
||||||
}
|
};
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default setupScatterplot;
|
export default setupScatterplot;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
const paddingRight = 120;
|
const paddingRight = 120;
|
||||||
const continuousChartWidth = 340;
|
const continuousChartWidth = 340;
|
||||||
|
|
||||||
export const margin = {top: 66, right: 110, bottom: 20, left: 60};
|
export const margin = { top: 66, right: 110, bottom: 20, left: 60 };
|
||||||
export const width = 340;
|
export const width = 340;
|
||||||
export const height = 340 - margin.top - margin.bottom;
|
export const height = 340 - margin.top - margin.bottom;
|
||||||
export const innerHeight = height - 2;
|
export const innerHeight = height - 2;
|
||||||
|
|||||||
+138
-36
@@ -1,5 +1,13 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
/* these will be either (preferably) specified or inferred */
|
/* these will be either (preferably) specified or inferred */
|
||||||
export const categories = ["Sample.type", "Selection", "Location", "Sample.name", "Class", "Neoplastic"];
|
export const categories = [
|
||||||
|
"Sample.type",
|
||||||
|
"Selection",
|
||||||
|
"Location",
|
||||||
|
"Sample.name",
|
||||||
|
"Class",
|
||||||
|
"Neoplastic"
|
||||||
|
];
|
||||||
export const continuous = [
|
export const continuous = [
|
||||||
"Total_reads",
|
"Total_reads",
|
||||||
"Unique_reads",
|
"Unique_reads",
|
||||||
@@ -18,7 +26,7 @@ export const continuous = [
|
|||||||
"Unmapped_mismatch",
|
"Unmapped_mismatch",
|
||||||
"Unmapped_other",
|
"Unmapped_other",
|
||||||
"Unmapped_short"
|
"Unmapped_short"
|
||||||
]
|
];
|
||||||
|
|
||||||
/* colors */
|
/* colors */
|
||||||
export const blue = "#4a90e2";
|
export const blue = "#4a90e2";
|
||||||
@@ -44,55 +52,149 @@ export let API = {
|
|||||||
// prefix: "http://pbmc33k.cxg.czi.technology/api/",
|
// prefix: "http://pbmc33k.cxg.czi.technology/api/",
|
||||||
|
|
||||||
// prefix: "http://api-staging.clustering.czi.technology/api/",
|
// prefix: "http://api-staging.clustering.czi.technology/api/",
|
||||||
version: "v0.1/",
|
version: "v0.1/"
|
||||||
}
|
};
|
||||||
|
|
||||||
if (window.CELLXGENE && window.CELLXGENE.API) API = window.CELLXGENE.API;
|
if (window.CELLXGENE && window.CELLXGENE.API) API = window.CELLXGENE.API;
|
||||||
|
|
||||||
export let datasetTitle = "";
|
export let datasetTitle = "";
|
||||||
|
|
||||||
if (window.CELLXGENE && window.CELLXGENE.datasetTitle) datasetTitle = window.CELLXGENE.datasetTitle;
|
if (window.CELLXGENE && window.CELLXGENE.datasetTitle)
|
||||||
|
datasetTitle = window.CELLXGENE.datasetTitle;
|
||||||
|
|
||||||
export const accentFont = "Georgia,Times,Times New Roman,serif";
|
export const accentFont = "Georgia,Times,Times New Roman,serif";
|
||||||
export const maxParagraphWidth = 600;
|
export const maxParagraphWidth = 600;
|
||||||
export const maxControlsWidth = 800;
|
export const maxControlsWidth = 800;
|
||||||
|
|
||||||
export const graphMargin = {top: 20, right: 10, bottom: 30, left: 40};
|
export const graphMargin = { top: 20, right: 10, bottom: 30, left: 40 };
|
||||||
// export const graphWidth = 1440 /* window width */ - 410 /* sidebar */ - (15 + 15) /* left right padding */ /* but responsive */;
|
// export const graphWidth = 1440 /* window width */ - 410 /* sidebar */ - (15 + 15) /* left right padding */ /* but responsive */;
|
||||||
// export const graphHeight = 500;
|
// export const graphHeight = 500;
|
||||||
export const graphWidth = 960;
|
export const graphWidth = 960;
|
||||||
export const graphHeight = 960;
|
export const graphHeight = 960;
|
||||||
|
|
||||||
export const graphXScale = d3.scaleLinear()
|
export const graphXScale = d3
|
||||||
.domain([0, 1]) /* while this is the default for d3, our data is normalized so better to be explicit */
|
.scaleLinear()
|
||||||
.range([
|
.domain([
|
||||||
0 + graphMargin.left,
|
0,
|
||||||
graphWidth - graphMargin.right
|
1
|
||||||
]);
|
]) /* while this is the default for d3, our data is normalized so better to be explicit */
|
||||||
|
.range([0 + graphMargin.left, graphWidth - graphMargin.right]);
|
||||||
export const graphYScale = d3.scaleLinear()
|
|
||||||
.domain([0, 1]) /* while this is the default for d3, our data is normalized so better to be explicit */
|
|
||||||
.range([
|
|
||||||
graphHeight - graphMargin.bottom,
|
|
||||||
0 + graphMargin.top
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
export const graphYScale = d3
|
||||||
|
.scaleLinear()
|
||||||
|
.domain([
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]) /* while this is the default for d3, our data is normalized so better to be explicit */
|
||||||
|
.range([graphHeight - graphMargin.bottom, 0 + graphMargin.top]);
|
||||||
|
|
||||||
export const ordinalColors = [
|
export const ordinalColors = [
|
||||||
'#0ac115', '#c10ab6', '#c1710a', '#0a5ac1', '#c1150a', '#0ab6c1', '#5ac10a',
|
"#0ac115",
|
||||||
'#710ac1', '#0ac171', '#c10a5a', '#b6c10a', '#150ac1', '#b2ffb7', '#ffb2fa',
|
"#c10ab6",
|
||||||
'#ffddb2', '#b2d4ff', '#ffb7b2', '#b2faff', '#d4ffb2', '#ddb2ff', '#b2ffdd',
|
"#c1710a",
|
||||||
'#ffb2d4', '#faffb2', '#b7b2ff', '#27a908', '#8b08a9', '#a93a08', '#0877a9',
|
"#0a5ac1",
|
||||||
'#a90827', '#08a98b', '#77a908', '#3a08a9', '#08a93a', '#a90877', '#a98b08',
|
"#c1150a",
|
||||||
'#0827a9', '#00ff0f', '#ff00ef', '#ff8e00', '#0070ff', '#ff0f00', '#00efff',
|
"#0ab6c1",
|
||||||
'#70ff00', '#8e00ff', '#00ff8e', '#ff0070', '#efff00', '#0f00ff', '#006606',
|
"#5ac10a",
|
||||||
'#66005f', '#663900', '#002c66', '#660600', '#005f66', '#2c6600', '#390066',
|
"#710ac1",
|
||||||
'#006639', '#66002c', '#5f6600', '#060066', '#83ff65', '#e165ff', '#ff9565',
|
"#0ac171",
|
||||||
'#65cfff', '#ff6583', '#65ffe1', '#cfff65', '#9565ff', '#65ff95', '#ff65cf',
|
"#c10a5a",
|
||||||
'#ffe165', '#6583ff', '#009909', '#99008f', '#995500', '#004399', '#990900',
|
"#b6c10a",
|
||||||
'#008f99', '#439900', '#550099', '#009955', '#990043', '#8f9900', '#090099',
|
"#150ac1",
|
||||||
'#d9fecc', '#f1ccfe', '#fed7cc', '#ccf3fe', '#feccd9', '#ccfef1', '#f3fecc',
|
"#b2ffb7",
|
||||||
'#d7ccfe', '#ccfed7', '#feccf3', '#fef1cc', '#ccd9fe', '#47ea51', '#ea47e0',
|
"#ffb2fa",
|
||||||
'#eaa247', '#478fea', '#ea5147', '#47e0ea', '#8fea47', '#a247ea', '#47eaa2',
|
"#ffddb2",
|
||||||
'#ea478f', '#e0ea47', '#5147ea'
|
"#b2d4ff",
|
||||||
]
|
"#ffb7b2",
|
||||||
|
"#b2faff",
|
||||||
|
"#d4ffb2",
|
||||||
|
"#ddb2ff",
|
||||||
|
"#b2ffdd",
|
||||||
|
"#ffb2d4",
|
||||||
|
"#faffb2",
|
||||||
|
"#b7b2ff",
|
||||||
|
"#27a908",
|
||||||
|
"#8b08a9",
|
||||||
|
"#a93a08",
|
||||||
|
"#0877a9",
|
||||||
|
"#a90827",
|
||||||
|
"#08a98b",
|
||||||
|
"#77a908",
|
||||||
|
"#3a08a9",
|
||||||
|
"#08a93a",
|
||||||
|
"#a90877",
|
||||||
|
"#a98b08",
|
||||||
|
"#0827a9",
|
||||||
|
"#00ff0f",
|
||||||
|
"#ff00ef",
|
||||||
|
"#ff8e00",
|
||||||
|
"#0070ff",
|
||||||
|
"#ff0f00",
|
||||||
|
"#00efff",
|
||||||
|
"#70ff00",
|
||||||
|
"#8e00ff",
|
||||||
|
"#00ff8e",
|
||||||
|
"#ff0070",
|
||||||
|
"#efff00",
|
||||||
|
"#0f00ff",
|
||||||
|
"#006606",
|
||||||
|
"#66005f",
|
||||||
|
"#663900",
|
||||||
|
"#002c66",
|
||||||
|
"#660600",
|
||||||
|
"#005f66",
|
||||||
|
"#2c6600",
|
||||||
|
"#390066",
|
||||||
|
"#006639",
|
||||||
|
"#66002c",
|
||||||
|
"#5f6600",
|
||||||
|
"#060066",
|
||||||
|
"#83ff65",
|
||||||
|
"#e165ff",
|
||||||
|
"#ff9565",
|
||||||
|
"#65cfff",
|
||||||
|
"#ff6583",
|
||||||
|
"#65ffe1",
|
||||||
|
"#cfff65",
|
||||||
|
"#9565ff",
|
||||||
|
"#65ff95",
|
||||||
|
"#ff65cf",
|
||||||
|
"#ffe165",
|
||||||
|
"#6583ff",
|
||||||
|
"#009909",
|
||||||
|
"#99008f",
|
||||||
|
"#995500",
|
||||||
|
"#004399",
|
||||||
|
"#990900",
|
||||||
|
"#008f99",
|
||||||
|
"#439900",
|
||||||
|
"#550099",
|
||||||
|
"#009955",
|
||||||
|
"#990043",
|
||||||
|
"#8f9900",
|
||||||
|
"#090099",
|
||||||
|
"#d9fecc",
|
||||||
|
"#f1ccfe",
|
||||||
|
"#fed7cc",
|
||||||
|
"#ccf3fe",
|
||||||
|
"#feccd9",
|
||||||
|
"#ccfef1",
|
||||||
|
"#f3fecc",
|
||||||
|
"#d7ccfe",
|
||||||
|
"#ccfed7",
|
||||||
|
"#feccf3",
|
||||||
|
"#fef1cc",
|
||||||
|
"#ccd9fe",
|
||||||
|
"#47ea51",
|
||||||
|
"#ea47e0",
|
||||||
|
"#eaa247",
|
||||||
|
"#478fea",
|
||||||
|
"#ea5147",
|
||||||
|
"#47e0ea",
|
||||||
|
"#8fea47",
|
||||||
|
"#a247ea",
|
||||||
|
"#47eaa2",
|
||||||
|
"#ea478f",
|
||||||
|
"#e0ea47",
|
||||||
|
"#5147ea"
|
||||||
|
];
|
||||||
|
|||||||
+11
-10
@@ -1,9 +1,10 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from "react-dom";
|
||||||
import { AppContainer } from 'react-hot-loader';
|
import { AppContainer } from "react-hot-loader";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import Redbox from 'redbox-react';
|
import Redbox from "redbox-react";
|
||||||
|
|
||||||
/* our code */
|
/* our code */
|
||||||
import App from "./components/app";
|
import App from "./components/app";
|
||||||
@@ -12,23 +13,23 @@ import store from "./reducers";
|
|||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AppContainer errorReporter={Redbox}>
|
<AppContainer errorReporter={Redbox}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App/>
|
<App />
|
||||||
</Provider>
|
</Provider>
|
||||||
</AppContainer>,
|
</AppContainer>,
|
||||||
document.getElementById('root')
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hot Module Replacement API
|
// Hot Module Replacement API
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept('./components/app', () => {
|
module.hot.accept("./components/app", () => {
|
||||||
const NextApp = require('./components/app').default;
|
const NextApp = require("./components/app").default;
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<NextApp/>
|
<NextApp />
|
||||||
</Provider>
|
</Provider>
|
||||||
</AppContainer>,
|
</AppContainer>,
|
||||||
document.getElementById('root')
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import uri from "urijs";
|
import uri from "urijs";
|
||||||
import * as globals from "../globals";
|
import * as globals from "../globals";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import { parseRGB } from "../util/parseRGB";
|
||||||
parseRGB
|
|
||||||
} from "../util/parseRGB";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6
|
https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6
|
||||||
@@ -21,27 +20,26 @@ import {
|
|||||||
This is nice because we keep a lot of filtering business logic centralized (what it means in practice to be selected)
|
This is nice because we keep a lot of filtering business logic centralized (what it means in practice to be selected)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const updateCellSelectionMiddleware = store => {
|
||||||
const updateCellSelectionMiddleware = (store) => {
|
return next => {
|
||||||
return (next) => {
|
return action => {
|
||||||
return (action) => {
|
|
||||||
const s = store.getState();
|
const s = store.getState();
|
||||||
|
|
||||||
/* this is a hardcoded map of the things we need to keep an eye on and update global cell selection in response to */
|
/* this is a hardcoded map of the things we need to keep an eye on and update global cell selection in response to */
|
||||||
const filterJustChanged =
|
const filterJustChanged =
|
||||||
action.type === "color by expression" ||
|
action.type === "color by expression" ||
|
||||||
action.type === "color by continuous metadata" ||
|
action.type === "color by continuous metadata" ||
|
||||||
action.type === "color by categorical metadata"
|
action.type === "color by categorical metadata";
|
||||||
;
|
|
||||||
|
|
||||||
if (
|
if (!filterJustChanged || !s.controls.allCellsOnClient) {
|
||||||
!filterJustChanged ||
|
return next(
|
||||||
!s.controls.allCellsOnClient
|
action
|
||||||
) {
|
); /* if the cells haven't loaded or the action wasn't a color change, bail */
|
||||||
return next(action); /* if the cells haven't loaded or the action wasn't a color change, bail */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentSelectionWithUpdatedColors = s.controls.currentCellSelection.slice(0);
|
let currentSelectionWithUpdatedColors = s.controls.currentCellSelection.slice(
|
||||||
|
0
|
||||||
|
);
|
||||||
let colorScale;
|
let colorScale;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -54,45 +52,32 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
if (action.type === "color by categorical metadata") {
|
if (action.type === "color by categorical metadata") {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
colorScale = d3.scaleOrdinal().range(globals.ordinalColors);
|
colorScale = d3.scaleOrdinal().range(globals.ordinalColors);
|
||||||
|
|
||||||
_.each(currentSelectionWithUpdatedColors, (cell, i) => {
|
_.each(currentSelectionWithUpdatedColors, (cell, i) => {
|
||||||
let c = colorScale(
|
let c = colorScale(cell[action.colorAccessor]);
|
||||||
cell[action.colorAccessor]
|
|
||||||
);
|
|
||||||
currentSelectionWithUpdatedColors[i]["__color__"] = c;
|
currentSelectionWithUpdatedColors[i]["__color__"] = c;
|
||||||
currentSelectionWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
currentSelectionWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === "color by continuous metadata") {
|
if (action.type === "color by continuous metadata") {
|
||||||
|
colorScale = d3
|
||||||
colorScale = d3.scaleLinear()
|
.scaleLinear()
|
||||||
.domain([0, action.rangeMaxForColorAccessor])
|
.domain([0, action.rangeMaxForColorAccessor])
|
||||||
.range([1,0])
|
.range([1, 0]);
|
||||||
|
|
||||||
_.each(currentSelectionWithUpdatedColors, (cell, i) => {
|
_.each(currentSelectionWithUpdatedColors, (cell, i) => {
|
||||||
let c = d3.interpolateViridis(
|
let c = d3.interpolateViridis(colorScale(cell[action.colorAccessor]));
|
||||||
colorScale(
|
|
||||||
cell[action.colorAccessor]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
currentSelectionWithUpdatedColors[i]["__color__"] = c;
|
currentSelectionWithUpdatedColors[i]["__color__"] = c;
|
||||||
currentSelectionWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
currentSelectionWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === "color by expression") {
|
if (action.type === "color by expression") {
|
||||||
|
const indexOfGene = 0; /* we only get one, this comes from server as needed now */
|
||||||
|
|
||||||
|
const expressionMap = {};
|
||||||
const indexOfGene = 0 /* we only get one, this comes from server as needed now */
|
|
||||||
|
|
||||||
const expressionMap = {}
|
|
||||||
/*
|
/*
|
||||||
converts [{cellname: cell123, e}, {}]
|
converts [{cellname: cell123, e}, {}]
|
||||||
|
|
||||||
@@ -101,50 +86,52 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
cell789: [0, 8]
|
cell789: [0, 8]
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
_.each(action.data.data.cells, (cell) => { /* this action is coming directly from the server */
|
_.each(action.data.data.cells, cell => {
|
||||||
expressionMap[cell.cellname] = cell.e
|
/* this action is coming directly from the server */
|
||||||
})
|
expressionMap[cell.cellname] = cell.e;
|
||||||
|
});
|
||||||
|
|
||||||
const minExpressionCell = _.minBy(action.data.data.cells, (cell) => {
|
const minExpressionCell = _.minBy(action.data.data.cells, cell => {
|
||||||
return cell.e[indexOfGene]
|
return cell.e[indexOfGene];
|
||||||
})
|
});
|
||||||
|
|
||||||
const maxExpressionCell = _.maxBy(action.data.data.cells, (cell) => {
|
const maxExpressionCell = _.maxBy(action.data.data.cells, cell => {
|
||||||
return cell.e[indexOfGene]
|
return cell.e[indexOfGene];
|
||||||
})
|
});
|
||||||
|
|
||||||
// console.log('middle', action, expressionMap, minExpressionCell)
|
// console.log('middle', action, expressionMap, minExpressionCell)
|
||||||
|
|
||||||
colorScale = d3.scaleLinear()
|
colorScale = d3
|
||||||
.domain([minExpressionCell.e[indexOfGene], maxExpressionCell.e[indexOfGene]])
|
.scaleLinear()
|
||||||
.range([1,0]) /* invert viridis... probably pass this scale through to others */
|
.domain([
|
||||||
|
minExpressionCell.e[indexOfGene],
|
||||||
|
maxExpressionCell.e[indexOfGene]
|
||||||
|
])
|
||||||
|
.range([
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
]); /* invert viridis... probably pass this scale through to others */
|
||||||
|
|
||||||
_.each(currentSelectionWithUpdatedColors, (cell, i) => {
|
_.each(currentSelectionWithUpdatedColors, (cell, i) => {
|
||||||
let c = d3.interpolateViridis(
|
let c = d3.interpolateViridis(
|
||||||
colorScale(
|
colorScale(expressionMap[cell.CellName][indexOfGene])
|
||||||
expressionMap[cell.CellName][indexOfGene]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
currentSelectionWithUpdatedColors[i]["__color__"] = c;
|
currentSelectionWithUpdatedColors[i]["__color__"] = c;
|
||||||
currentSelectionWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
currentSelectionWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
append the result of all the filters to the action the user just triggered
|
append the result of all the filters to the action the user just triggered
|
||||||
*/
|
*/
|
||||||
let modifiedAction = Object.assign(
|
let modifiedAction = Object.assign({}, action, {
|
||||||
{},
|
currentSelectionWithUpdatedColors,
|
||||||
action,
|
colorScale
|
||||||
{
|
});
|
||||||
currentSelectionWithUpdatedColors,
|
|
||||||
colorScale
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return next(modifiedAction);
|
return next(modifiedAction);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updateCellSelectionMiddleware;
|
export default updateCellSelectionMiddleware;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import uri from "urijs";
|
import uri from "urijs";
|
||||||
import * as globals from "../globals";
|
import * as globals from "../globals";
|
||||||
|
|
||||||
@@ -17,9 +18,9 @@ import * as globals from "../globals";
|
|||||||
This is nice because we keep a lot of filtering business logic centralized (what it means in practice to be selected)
|
This is nice because we keep a lot of filtering business logic centralized (what it means in practice to be selected)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const updateCellSelectionMiddleware = (store) => {
|
const updateCellSelectionMiddleware = store => {
|
||||||
return (next) => {
|
return next => {
|
||||||
return (action) => {
|
return action => {
|
||||||
const s = store.getState();
|
const s = store.getState();
|
||||||
|
|
||||||
/* this is a hardcoded map of the things we need to keep an eye on and update global cell selection in response to */
|
/* this is a hardcoded map of the things we need to keep an eye on and update global cell selection in response to */
|
||||||
@@ -31,24 +32,26 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
action.type === "categorical metadata filter deselect" ||
|
action.type === "categorical metadata filter deselect" ||
|
||||||
action.type === "categorical metadata filter select" ||
|
action.type === "categorical metadata filter select" ||
|
||||||
action.type === "categorical metadata filter none of these" ||
|
action.type === "categorical metadata filter none of these" ||
|
||||||
action.type === "categorical metadata filter all of these"
|
action.type === "categorical metadata filter all of these";
|
||||||
;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!filterJustChanged ||
|
!filterJustChanged ||
|
||||||
!s.controls.allCellsOnClient
|
!s.controls.allCellsOnClient
|
||||||
/* graphMap is set at the same time as allCells, so we assume it exists */
|
/* graphMap is set at the same time as allCells, so we assume it exists */
|
||||||
) {
|
) {
|
||||||
return next(action); /* if the cells haven't loaded or the action wasn't a filter, bail */
|
return next(
|
||||||
|
action
|
||||||
|
); /* if the cells haven't loaded or the action wasn't a filter, bail */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
- make a FRESH copy of all of the cells
|
- make a FRESH copy of all of the cells
|
||||||
- metadata has cellname, and that's all we ever need (is a key to graphMap)
|
- metadata has cellname, and that's all we ever need (is a key to graphMap)
|
||||||
*/
|
*/
|
||||||
let newSelection = s.controls.currentCellSelection.slice(0);
|
let newSelection = s.controls.currentCellSelection.slice(0);
|
||||||
_.each(newSelection, (cell) => { cell["__selected__"] = true } );
|
_.each(newSelection, cell => {
|
||||||
|
cell["__selected__"] = true;
|
||||||
|
});
|
||||||
/*
|
/*
|
||||||
in plain language...
|
in plain language...
|
||||||
|
|
||||||
@@ -61,57 +64,59 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if ( /* is there a 2d graph brush selection ? */
|
if (
|
||||||
|
/* is there a 2d graph brush selection ? */
|
||||||
action.type === "graph brush selection change" ||
|
action.type === "graph brush selection change" ||
|
||||||
s.controls.graphBrushSelection
|
s.controls.graphBrushSelection
|
||||||
) {
|
) {
|
||||||
|
const graphBrushSelection /* it exists, so is it new or old */ =
|
||||||
const graphBrushSelection = /* it exists, so is it new or old */
|
action.type === "graph brush selection change"
|
||||||
action.type === "graph brush selection change" ? action.brushCoords :
|
? action.brushCoords
|
||||||
s.controls.graphBrushSelection
|
: s.controls.graphBrushSelection;
|
||||||
|
|
||||||
_.each(newSelection, (cell, i) => {
|
_.each(newSelection, (cell, i) => {
|
||||||
|
|
||||||
if (!s.controls.graphMap[cell["CellName"]]) {
|
if (!s.controls.graphMap[cell["CellName"]]) {
|
||||||
newSelection[i]["__selected__"] = false; /* make a toggle in future */
|
newSelection[i][
|
||||||
return
|
"__selected__"
|
||||||
|
] = false; /* make a toggle in future */
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coords = s.controls.graphMap[cell["CellName"]]; // [0.08005009151334168, 0.6907652173913044]
|
const coords = s.controls.graphMap[cell["CellName"]]; // [0.08005009151334168, 0.6907652173913044]
|
||||||
|
|
||||||
const pointIsInsideBrushBounds = (
|
const pointIsInsideBrushBounds =
|
||||||
globals.graphXScale(coords[0]) >= graphBrushSelection.northwestX &&
|
globals.graphXScale(coords[0]) >= graphBrushSelection.northwestX &&
|
||||||
globals.graphXScale(coords[0]) <= graphBrushSelection.southeastX &&
|
globals.graphXScale(coords[0]) <= graphBrushSelection.southeastX &&
|
||||||
globals.graphYScale(coords[1]) >= graphBrushSelection.northwestY &&
|
globals.graphYScale(coords[1]) >= graphBrushSelection.northwestY &&
|
||||||
globals.graphYScale(coords[1]) <= graphBrushSelection.southeastY
|
globals.graphYScale(coords[1]) <= graphBrushSelection.southeastY;
|
||||||
);
|
|
||||||
|
|
||||||
if (!pointIsInsideBrushBounds) {
|
if (!pointIsInsideBrushBounds) {
|
||||||
newSelection[i]["__selected__"] = false;
|
newSelection[i]["__selected__"] = false;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
action.type === "continuous selection using parallel coords brushing" && s.controls.continuousSelection ||
|
(action.type ===
|
||||||
|
"continuous selection using parallel coords brushing" &&
|
||||||
|
s.controls.continuousSelection) ||
|
||||||
s.controls.continuousSelection
|
s.controls.continuousSelection
|
||||||
) {
|
) {
|
||||||
|
|
||||||
_.each(newSelection, (cell, i) => {
|
_.each(newSelection, (cell, i) => {
|
||||||
|
const cellExtentsAreWithinContinuousSelectionBounds = s.controls.continuousSelection.every(
|
||||||
const cellExtentsAreWithinContinuousSelectionBounds = s.controls.continuousSelection.every((active) => {
|
active => {
|
||||||
// test if point is within extents for each active brush
|
// test if point is within extents for each active brush
|
||||||
return active.dimension.type.within(
|
return active.dimension.type.within(
|
||||||
cell[active.dimension.key],
|
cell[active.dimension.key],
|
||||||
active.extent,
|
active.extent,
|
||||||
active.dimension
|
active.dimension
|
||||||
);
|
);
|
||||||
})
|
|
||||||
|
|
||||||
if (!cellExtentsAreWithinContinuousSelectionBounds) {
|
|
||||||
newSelection[i]["__selected__"] = false;
|
|
||||||
}
|
}
|
||||||
})
|
);
|
||||||
|
|
||||||
|
if (!cellExtentsAreWithinContinuousSelectionBounds) {
|
||||||
|
newSelection[i]["__selected__"] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -121,8 +126,8 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
Filter based on them
|
Filter based on them
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let newContinuousUserDefinedRanges = s.controls.continuousUserDefinedRanges;
|
let newContinuousUserDefinedRanges =
|
||||||
|
s.controls.continuousUserDefinedRanges;
|
||||||
|
|
||||||
/* check if this is the action and take care of that metadata field */
|
/* check if this is the action and take care of that metadata field */
|
||||||
if (action.type === "continuous metadata histogram brush") {
|
if (action.type === "continuous metadata histogram brush") {
|
||||||
@@ -139,13 +144,13 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
|
|
||||||
_.each(newContinuousUserDefinedRanges, (value, key, i) => {
|
_.each(newContinuousUserDefinedRanges, (value, key, i) => {
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
activeContinuousHistogramFilters.push(key)
|
activeContinuousHistogramFilters.push(key);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
/* see if there are others from previous... */
|
/* see if there are others from previous... */
|
||||||
if (activeContinuousHistogramFilters.length > 0) {
|
if (activeContinuousHistogramFilters.length > 0) {
|
||||||
_.each(activeContinuousHistogramFilters, (key) => {
|
_.each(activeContinuousHistogramFilters, key => {
|
||||||
_.each(newSelection, (cell, i) => {
|
_.each(newSelection, (cell, i) => {
|
||||||
if (
|
if (
|
||||||
+cell[key] < newContinuousUserDefinedRanges[key][0] ||
|
+cell[key] < newContinuousUserDefinedRanges[key][0] ||
|
||||||
@@ -153,8 +158,8 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
) {
|
) {
|
||||||
newSelection[i]["__selected__"] = false;
|
newSelection[i]["__selected__"] = false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -177,7 +182,7 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
...s.controls.categoricalAsBooleansMap[action.metadataField],
|
...s.controls.categoricalAsBooleansMap[action.metadataField],
|
||||||
[action.value]: true
|
[action.value]: true
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} else if (action.type === "categorical metadata filter deselect") {
|
} else if (action.type === "categorical metadata filter deselect") {
|
||||||
newCategoricalAsBooleansMap = {
|
newCategoricalAsBooleansMap = {
|
||||||
...s.controls.categoricalAsBooleansMap,
|
...s.controls.categoricalAsBooleansMap,
|
||||||
@@ -185,61 +190,73 @@ const updateCellSelectionMiddleware = (store) => {
|
|||||||
...s.controls.categoricalAsBooleansMap[action.metadataField],
|
...s.controls.categoricalAsBooleansMap[action.metadataField],
|
||||||
[action.value]: false
|
[action.value]: false
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} else if (action.type === "categorical metadata filter none of these") {
|
} else if (action.type === "categorical metadata filter none of these") {
|
||||||
|
|
||||||
const metadataFieldWithAllOfTheseValueSelected = {};
|
const metadataFieldWithAllOfTheseValueSelected = {};
|
||||||
|
|
||||||
/* set EVERYTHING to false in this intermediate object */
|
/* set EVERYTHING to false in this intermediate object */
|
||||||
_.each(s.controls.categoricalAsBooleansMap[action.metadataField], (isActive, option) => {
|
_.each(
|
||||||
metadataFieldWithAllOfTheseValueSelected[option] = false;
|
s.controls.categoricalAsBooleansMap[action.metadataField],
|
||||||
})
|
(isActive, option) => {
|
||||||
|
metadataFieldWithAllOfTheseValueSelected[option] = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
newCategoricalAsBooleansMap = {
|
newCategoricalAsBooleansMap = {
|
||||||
...s.controls.categoricalAsBooleansMap,
|
...s.controls.categoricalAsBooleansMap,
|
||||||
[action.metadataField]: metadataFieldWithAllOfTheseValueSelected
|
[action.metadataField]: metadataFieldWithAllOfTheseValueSelected
|
||||||
}
|
};
|
||||||
|
|
||||||
} else if (action.type === "categorical metadata filter all of these") {
|
} else if (action.type === "categorical metadata filter all of these") {
|
||||||
const metadataFieldWithAllOfTheseValueSelected = {};
|
const metadataFieldWithAllOfTheseValueSelected = {};
|
||||||
|
|
||||||
/* set EVERYTHING to true in this intermediate object */
|
/* set EVERYTHING to true in this intermediate object */
|
||||||
_.each(s.controls.categoricalAsBooleansMap[action.metadataField], (isActive, option) => {
|
_.each(
|
||||||
metadataFieldWithAllOfTheseValueSelected[option] = true;
|
s.controls.categoricalAsBooleansMap[action.metadataField],
|
||||||
})
|
(isActive, option) => {
|
||||||
|
metadataFieldWithAllOfTheseValueSelected[option] = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
newCategoricalAsBooleansMap = {
|
newCategoricalAsBooleansMap = {
|
||||||
...s.controls.categoricalAsBooleansMap,
|
...s.controls.categoricalAsBooleansMap,
|
||||||
[action.metadataField]: metadataFieldWithAllOfTheseValueSelected
|
[action.metadataField]: metadataFieldWithAllOfTheseValueSelected
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const inactiveCategories = [];
|
const inactiveCategories = [];
|
||||||
_.each(newCategoricalAsBooleansMap, (options, category) => {
|
_.each(newCategoricalAsBooleansMap, (options, category) => {
|
||||||
_.each(options, (isActive, option) => {
|
_.each(options, (isActive, option) => {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
inactiveCategories.push({category, option})
|
inactiveCategories.push({ category, option });
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
if (inactiveCategories.length > 0) {
|
if (inactiveCategories.length > 0) {
|
||||||
_.each(inactiveCategories, (d) => {
|
_.each(inactiveCategories, d => {
|
||||||
if (s.controls.categoricalAsCellsMap[d.category] && s.controls.categoricalAsCellsMap[d.category][d.option]) {
|
if (
|
||||||
_.forEach(s.controls.categoricalAsCellsMap[d.category][d.option], (c) => { c.__selected__ = false; });
|
s.controls.categoricalAsCellsMap[d.category] &&
|
||||||
|
s.controls.categoricalAsCellsMap[d.category][d.option]
|
||||||
|
) {
|
||||||
|
_.forEach(
|
||||||
|
s.controls.categoricalAsCellsMap[d.category][d.option],
|
||||||
|
c => {
|
||||||
|
c.__selected__ = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let modifiedAction = Object.assign({}, action, {
|
let modifiedAction = Object.assign({}, action, {
|
||||||
newSelection,
|
newSelection,
|
||||||
newCategoricalAsBooleansMap,
|
newCategoricalAsBooleansMap,
|
||||||
newContinuousUserDefinedRanges,
|
newContinuousUserDefinedRanges
|
||||||
}) /* append the result of all the filters to the action the user just triggered */
|
}); /* append the result of all the filters to the action the user just triggered */
|
||||||
|
|
||||||
return next(modifiedAction);
|
return next(modifiedAction);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updateCellSelectionMiddleware;
|
export default updateCellSelectionMiddleware;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import uri from "urijs";
|
import uri from "urijs";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -5,14 +6,13 @@ import uri from "urijs";
|
|||||||
storeInstance => functionToCallWithAnActionThatWillSendItToTheNextMiddleware => actionThatDispatchWasCalledWith => valueToUseAsTheReturnValueOfTheDispatchCall
|
storeInstance => functionToCallWithAnActionThatWillSendItToTheNextMiddleware => actionThatDispatchWasCalledWith => valueToUseAsTheReturnValueOfTheDispatchCall
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const updateURLMiddleware = store => {
|
||||||
const updateURLMiddleware = (store) => {
|
return next => {
|
||||||
return (next) => {
|
return action => {
|
||||||
return (action) => {
|
|
||||||
const oldState = store.getState();
|
const oldState = store.getState();
|
||||||
const nextAction = next(action);
|
const nextAction = next(action);
|
||||||
|
|
||||||
if (action.type === 'url changed') {
|
if (action.type === "url changed") {
|
||||||
/* we don't handle pop state here - we handle it in the url reducer */
|
/* we don't handle pop state here - we handle it in the url reducer */
|
||||||
return nextAction;
|
return nextAction;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,6 @@ const updateURLMiddleware = (store) => {
|
|||||||
//
|
//
|
||||||
// window.history.pushState("", "", newURL)
|
// window.history.pushState("", "", newURL)
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// // Internal helper for working with URIs
|
// // Internal helper for working with URIs
|
||||||
// const oldURI = new URI(window.location.href);
|
// const oldURI = new URI(window.location.href);
|
||||||
@@ -77,8 +76,8 @@ const updateURLMiddleware = (store) => {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
return nextAction;
|
return nextAction;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updateURLMiddleware;
|
export default updateURLMiddleware;
|
||||||
|
|||||||
+27
-23
@@ -1,27 +1,31 @@
|
|||||||
const Cells = (state = {
|
// jshint esversion: 6
|
||||||
cells: null,
|
const Cells = (
|
||||||
loading: null,
|
state = {
|
||||||
error: null,
|
cells: null,
|
||||||
}, action) => {
|
loading: null,
|
||||||
|
error: null
|
||||||
|
},
|
||||||
|
action
|
||||||
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "request cells started":
|
case "request cells started":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null
|
error: null
|
||||||
});
|
});
|
||||||
case "request cells success":
|
case "request cells success":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
error: null,
|
error: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
cells: action.data,
|
cells: action.data
|
||||||
});
|
});
|
||||||
case "request cells error":
|
case "request cells error":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: action.data
|
error: action.data
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Vendored
+184
-159
@@ -1,184 +1,209 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import { parseRGB } from "../util/parseRGB";
|
||||||
parseRGB
|
|
||||||
} from "../util/parseRGB";
|
|
||||||
|
|
||||||
const Controls = (state = {
|
const Controls = (
|
||||||
_ranges: null, /* this comes from initialize, this is universe */
|
state = {
|
||||||
allGeneNames: null,
|
_ranges: null /* this comes from initialize, this is universe */,
|
||||||
allCellsOnClient: null, /* this comes from cells endpoint, this is world */
|
allGeneNames: null,
|
||||||
currentCellSelection: null, /* this comes from user actions, all draw components use this, it is created by middleware */
|
allCellsOnClient: null /* this comes from cells endpoint, this is world */,
|
||||||
graphMap: null,
|
currentCellSelection: null /* this comes from user actions, all draw components use this, it is created by middleware */,
|
||||||
categoricalAsBooleansMap: null,
|
graphMap: null,
|
||||||
colorAccessor: null,
|
categoricalAsBooleansMap: null,
|
||||||
colorScale: null,
|
colorAccessor: null,
|
||||||
opacityForDeselectedCells: .2,
|
colorScale: null,
|
||||||
graphBrushSelection: null,
|
opacityForDeselectedCells: 0.2,
|
||||||
continuousSelection: null,
|
graphBrushSelection: null,
|
||||||
scatterplotXXaccessor: null, // just easier to read
|
continuousSelection: null,
|
||||||
scatterplotYYaccessor: null,
|
scatterplotXXaccessor: null, // just easier to read
|
||||||
axesHaveBeenDrawn: false,
|
scatterplotYYaccessor: null,
|
||||||
__storedStateForCelllist1__: null, /* will need procedural control of brush ie., brush.extent https://bl.ocks.org/micahstubbs/3cda05ca68cba260cb81 */
|
axesHaveBeenDrawn: false,
|
||||||
__storedStateForCelllist2__: null,
|
__storedStateForCelllist1__: null /* will need procedural control of brush ie., brush.extent https://bl.ocks.org/micahstubbs/3cda05ca68cba260cb81 */,
|
||||||
}, action) => {
|
__storedStateForCelllist2__: null
|
||||||
|
},
|
||||||
|
action
|
||||||
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
/**********************************
|
/**********************************
|
||||||
Keep a copy of 'universe'
|
Keep a copy of 'universe'
|
||||||
***********************************/
|
***********************************/
|
||||||
case "initialize success":
|
case "initialize success":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
_ranges: action.data.data.ranges,
|
_ranges: action.data.data.ranges,
|
||||||
allGeneNames: action.data.data.genes
|
allGeneNames: action.data.data.genes
|
||||||
});
|
});
|
||||||
case "request cells success":
|
case "request cells success":
|
||||||
const graphMap = {};
|
const graphMap = {};
|
||||||
const currentCellSelection = action.data.data.metadata.slice(0);
|
const currentCellSelection = action.data.data.metadata.slice(0);
|
||||||
_.each(action.data.data.graph, (g) => { graphMap[g[0]] = [g[1], g[2]] });
|
_.each(action.data.data.graph, g => {
|
||||||
|
graphMap[g[0]] = [g[1], g[2]];
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
construct a copy of the ranges object that only has categorical
|
construct a copy of the ranges object that only has categorical
|
||||||
replace all counts with bool flags
|
replace all counts with bool flags
|
||||||
ie., everything starts out checked
|
ie., everything starts out checked
|
||||||
we mutate this map in the actions below
|
we mutate this map in the actions below
|
||||||
*/
|
*/
|
||||||
const categoricalAsBooleansMap = {}, categoricalAsCellsMap = {};
|
const categoricalAsBooleansMap = {},
|
||||||
const continuousUserDefinedRanges = {};
|
categoricalAsCellsMap = {};
|
||||||
_.each(action.data.data.ranges, (value, key) => {
|
const continuousUserDefinedRanges = {};
|
||||||
if (
|
_.each(action.data.data.ranges, (value, key) => {
|
||||||
key !== "CellName" &&
|
if (
|
||||||
value.options /* it's categorical, it has options instead of ranges */
|
key !== "CellName" &&
|
||||||
) {
|
value.options /* it's categorical, it has options instead of ranges */
|
||||||
const optionsAsBooleans = {}, optionsAsCells = {};
|
) {
|
||||||
_.each(value.options, (_value, _key) => {
|
const optionsAsBooleans = {},
|
||||||
optionsAsBooleans[_key] = true;
|
optionsAsCells = {};
|
||||||
optionsAsCells[_key] = [];
|
_.each(value.options, (_value, _key) => {
|
||||||
})
|
optionsAsBooleans[_key] = true;
|
||||||
categoricalAsBooleansMap[key] = optionsAsBooleans;
|
optionsAsCells[_key] = [];
|
||||||
categoricalAsCellsMap[key] = optionsAsCells;
|
});
|
||||||
} else if (
|
categoricalAsBooleansMap[key] = optionsAsBooleans;
|
||||||
key !== "CellName" &&
|
categoricalAsCellsMap[key] = optionsAsCells;
|
||||||
value.range
|
} else if (key !== "CellName" && value.range) {
|
||||||
) {
|
continuousUserDefinedRanges[key] = null;
|
||||||
continuousUserDefinedRanges[key] = null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
_.each(currentCellSelection, (cell) => {
|
|
||||||
cell["__selected__"] = true;
|
|
||||||
cell["__color__"] = "rgba(0,0,0,1)" /* initial color for all cells in all charts */
|
|
||||||
cell["__colorRGB__"] = parseRGB(cell["__color__"]);
|
|
||||||
|
|
||||||
// Add each cell to its categorical metadata set.
|
|
||||||
_.forEach(cell, (_value, key) => {
|
|
||||||
if (categoricalAsCellsMap[key] && categoricalAsCellsMap[key][_value]) {
|
|
||||||
const s = categoricalAsCellsMap[key][_value];
|
|
||||||
if (s) s.push(cell);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return Object.assign({}, state, {
|
_.each(currentCellSelection, cell => {
|
||||||
allCellsOnClient: action.data.data,
|
cell["__selected__"] = true;
|
||||||
currentCellSelection,
|
cell["__color__"] =
|
||||||
graphMap,
|
"rgba(0,0,0,1)"; /* initial color for all cells in all charts */
|
||||||
categoricalAsBooleansMap,
|
cell["__colorRGB__"] = parseRGB(cell["__color__"]);
|
||||||
categoricalAsCellsMap,
|
|
||||||
continuousUserDefinedRanges,
|
// Add each cell to its categorical metadata set.
|
||||||
graphBrushSelection: null, /* if we are getting new cells from the server, the layout (probably? definitely?) just changed, so this is now irrelevant, and we WILL need to call a function to reset state of this kind when cells success happens */
|
_.forEach(cell, (_value, key) => {
|
||||||
});
|
if (
|
||||||
/* * * * * * * * * * * * * * * * * *
|
categoricalAsCellsMap[key] &&
|
||||||
|
categoricalAsCellsMap[key][_value]
|
||||||
|
) {
|
||||||
|
const s = categoricalAsCellsMap[key][_value];
|
||||||
|
if (s) s.push(cell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
allCellsOnClient: action.data.data,
|
||||||
|
currentCellSelection,
|
||||||
|
graphMap,
|
||||||
|
categoricalAsBooleansMap,
|
||||||
|
categoricalAsCellsMap,
|
||||||
|
continuousUserDefinedRanges,
|
||||||
|
graphBrushSelection: null /* if we are getting new cells from the server, the layout (probably? definitely?) just changed, so this is now irrelevant, and we WILL need to call a function to reset state of this kind when cells success happens */
|
||||||
|
});
|
||||||
|
/* * * * * * * * * * * * * * * * * *
|
||||||
User events
|
User events
|
||||||
* * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * */
|
||||||
case "parallel coordinates axes have been drawn":
|
case "parallel coordinates axes have been drawn":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
axesHaveBeenDrawn: true
|
axesHaveBeenDrawn: true
|
||||||
});
|
});
|
||||||
case "continuous selection using parallel coords brushing": {
|
case "continuous selection using parallel coords brushing": {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
continuousSelection: action.data,
|
continuousSelection: action.data,
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
currentCellSelection:
|
||||||
});
|
action.newSelection /* this comes from middleware */
|
||||||
}
|
});
|
||||||
case "graph brush selection change":
|
}
|
||||||
return Object.assign({}, state, {
|
case "graph brush selection change":
|
||||||
graphBrushSelection: action.brushCoords, /* this has already been applied in middleware but store it for next time */
|
return Object.assign({}, state, {
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
graphBrushSelection:
|
||||||
})
|
action.brushCoords /* this has already been applied in middleware but store it for next time */,
|
||||||
case "graph brush deselect":
|
currentCellSelection:
|
||||||
return Object.assign({}, state, {
|
action.newSelection /* this comes from middleware */
|
||||||
graphBrushSelection: null,
|
});
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
case "graph brush deselect":
|
||||||
})
|
return Object.assign({}, state, {
|
||||||
case "continuous metadata histogram brush":
|
graphBrushSelection: null,
|
||||||
return Object.assign({}, state, {
|
currentCellSelection:
|
||||||
newContinuousUserDefinedRanges: action.newContinuousUserDefinedRanges, /* this has already been applied in middleware but store it for next time */
|
action.newSelection /* this comes from middleware */
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
});
|
||||||
})
|
case "continuous metadata histogram brush":
|
||||||
case "change opacity deselected cells in 2d graph background":
|
return Object.assign({}, state, {
|
||||||
return Object.assign({}, state, {
|
newContinuousUserDefinedRanges:
|
||||||
opacityForDeselectedCells: action.data,
|
action.newContinuousUserDefinedRanges /* this has already been applied in middleware but store it for next time */,
|
||||||
})
|
currentCellSelection:
|
||||||
/*******************************
|
action.newSelection /* this comes from middleware */
|
||||||
|
});
|
||||||
|
case "change opacity deselected cells in 2d graph background":
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
opacityForDeselectedCells: action.data
|
||||||
|
});
|
||||||
|
/*******************************
|
||||||
Categorical metadata
|
Categorical metadata
|
||||||
*******************************/
|
*******************************/
|
||||||
case "categorical metadata filter select":
|
case "categorical metadata filter select":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
categoricalAsBooleansMap: action.newCategoricalAsBooleansMap, /* this comes from middleware */
|
categoricalAsBooleansMap:
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
action.newCategoricalAsBooleansMap /* this comes from middleware */,
|
||||||
})
|
currentCellSelection:
|
||||||
case "categorical metadata filter deselect":
|
action.newSelection /* this comes from middleware */
|
||||||
return Object.assign({}, state, {
|
});
|
||||||
categoricalAsBooleansMap: action.newCategoricalAsBooleansMap, /* this comes from middleware */
|
case "categorical metadata filter deselect":
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
return Object.assign({}, state, {
|
||||||
})
|
categoricalAsBooleansMap:
|
||||||
case "categorical metadata filter none of these":
|
action.newCategoricalAsBooleansMap /* this comes from middleware */,
|
||||||
return Object.assign({}, state, {
|
currentCellSelection:
|
||||||
categoricalAsBooleansMap: action.newCategoricalAsBooleansMap, /* this comes from middleware */
|
action.newSelection /* this comes from middleware */
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
});
|
||||||
})
|
case "categorical metadata filter none of these":
|
||||||
case "categorical metadata filter all of these":
|
return Object.assign({}, state, {
|
||||||
return Object.assign({}, state, {
|
categoricalAsBooleansMap:
|
||||||
categoricalAsBooleansMap: action.newCategoricalAsBooleansMap, /* this comes from middleware */
|
action.newCategoricalAsBooleansMap /* this comes from middleware */,
|
||||||
currentCellSelection: action.newSelection /* this comes from middleware */
|
currentCellSelection:
|
||||||
})
|
action.newSelection /* this comes from middleware */
|
||||||
/*******************************
|
});
|
||||||
|
case "categorical metadata filter all of these":
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
categoricalAsBooleansMap:
|
||||||
|
action.newCategoricalAsBooleansMap /* this comes from middleware */,
|
||||||
|
currentCellSelection:
|
||||||
|
action.newSelection /* this comes from middleware */
|
||||||
|
});
|
||||||
|
/*******************************
|
||||||
Color Scale
|
Color Scale
|
||||||
*******************************/
|
*******************************/
|
||||||
case "color by continuous metadata":
|
case "color by continuous metadata":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
colorAccessor: action.colorAccessor,
|
colorAccessor: action.colorAccessor,
|
||||||
currentCellSelection: action.currentSelectionWithUpdatedColors, /* this comes from middleware */
|
currentCellSelection:
|
||||||
colorScale: action.colorScale,
|
action.currentSelectionWithUpdatedColors /* this comes from middleware */,
|
||||||
});
|
colorScale: action.colorScale
|
||||||
case "color by expression":
|
});
|
||||||
return Object.assign({}, state, {
|
case "color by expression":
|
||||||
colorAccessor: action.gene,
|
return Object.assign({}, state, {
|
||||||
currentCellSelection: action.currentSelectionWithUpdatedColors, /* this comes from middleware */
|
colorAccessor: action.gene,
|
||||||
colorScale: action.colorScale,
|
currentCellSelection:
|
||||||
})
|
action.currentSelectionWithUpdatedColors /* this comes from middleware */,
|
||||||
case "color by categorical metadata":
|
colorScale: action.colorScale
|
||||||
return Object.assign({}, state, {
|
});
|
||||||
colorAccessor: action.colorAccessor, /* pass the scale through additionally, and it's a legend! */
|
case "color by categorical metadata":
|
||||||
currentCellSelection: action.currentSelectionWithUpdatedColors, /* this comes from middleware */
|
return Object.assign({}, state, {
|
||||||
colorScale: action.colorScale,
|
colorAccessor:
|
||||||
})
|
action.colorAccessor /* pass the scale through additionally, and it's a legend! */,
|
||||||
case "store current cell selection as differential set 1":
|
currentCellSelection:
|
||||||
return Object.assign({}, state, {
|
action.currentSelectionWithUpdatedColors /* this comes from middleware */,
|
||||||
__storedStateForCelllist1__: action.data
|
colorScale: action.colorScale
|
||||||
});
|
});
|
||||||
/*******************************
|
case "store current cell selection as differential set 1":
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
__storedStateForCelllist1__: action.data
|
||||||
|
});
|
||||||
|
/*******************************
|
||||||
Scatterplot
|
Scatterplot
|
||||||
*******************************/
|
*******************************/
|
||||||
case "set scatterplot x":
|
case "set scatterplot x":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
scatterplotXXaccessor: action.data
|
scatterplotXXaccessor: action.data
|
||||||
});
|
});
|
||||||
case "set scatterplot y":
|
case "set scatterplot y":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
scatterplotYYaccessor: action.data
|
scatterplotYYaccessor: action.data
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,41 @@
|
|||||||
const Differential = (state = {
|
// jshint esversion: 6
|
||||||
diffExp: null,
|
const Differential = (
|
||||||
loading: null,
|
state = {
|
||||||
error: null,
|
diffExp: null,
|
||||||
celllist1: null,
|
loading: null,
|
||||||
celllist2: null,
|
error: null,
|
||||||
}, action) => {
|
celllist1: null,
|
||||||
|
celllist2: null
|
||||||
|
},
|
||||||
|
action
|
||||||
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "request differential expression started":
|
case "request differential expression started":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null
|
error: null
|
||||||
});
|
});
|
||||||
case "request differential expression success":
|
case "request differential expression success":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
error: null,
|
error: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
diffExp: action.data,
|
diffExp: action.data
|
||||||
});
|
});
|
||||||
case "request differential expression error":
|
case "request differential expression error":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: action.data
|
error: action.data
|
||||||
});
|
});
|
||||||
case "store current cell selection as differential set 1":
|
case "store current cell selection as differential set 1":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
celllist1: action.data
|
celllist1: action.data
|
||||||
});
|
});
|
||||||
case "store current cell selection as differential set 2":
|
case "store current cell selection as differential set 2":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
celllist2: action.data
|
celllist2: action.data
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+28
-24
@@ -1,28 +1,32 @@
|
|||||||
const Expression = (state = {
|
// jshint esversion: 6
|
||||||
data: null,
|
const Expression = (
|
||||||
loading: null,
|
state = {
|
||||||
error: null,
|
data: null,
|
||||||
}, action) => {
|
loading: null,
|
||||||
|
error: null
|
||||||
|
},
|
||||||
|
action
|
||||||
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "get expression started":
|
case "get expression started":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null
|
error: null
|
||||||
});
|
});
|
||||||
case "get expression success":
|
case "get expression success":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
error: null,
|
error: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
data: action.data.data,
|
data: action.data.data
|
||||||
});
|
});
|
||||||
case "get expression error":
|
case "get expression error":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
data: null,
|
data: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: action.data
|
error: action.data
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { combineReducers, createStore, applyMiddleware } from 'redux';
|
// jshint esversion: 6
|
||||||
|
import { combineReducers, createStore, applyMiddleware } from "redux";
|
||||||
import updateURLMiddleware from "../middleware/updateURLMiddleware";
|
import updateURLMiddleware from "../middleware/updateURLMiddleware";
|
||||||
import updateCellSelectionMiddleware from "../middleware/updateCellSelectionMiddleware";
|
import updateCellSelectionMiddleware from "../middleware/updateCellSelectionMiddleware";
|
||||||
import updateCellColors from "../middleware/updateCellColors";
|
import updateCellColors from "../middleware/updateCellColors";
|
||||||
@@ -16,8 +17,8 @@ const Reducer = combineReducers({
|
|||||||
cells,
|
cells,
|
||||||
expression,
|
expression,
|
||||||
controls,
|
controls,
|
||||||
differential,
|
differential
|
||||||
})
|
});
|
||||||
|
|
||||||
let store = createStore(
|
let store = createStore(
|
||||||
Reducer,
|
Reducer,
|
||||||
|
|||||||
+28
-24
@@ -1,28 +1,32 @@
|
|||||||
const Initialize = (state = {
|
// jshint esversion: 6
|
||||||
data: null,
|
const Initialize = (
|
||||||
loading: null,
|
state = {
|
||||||
error: null,
|
data: null,
|
||||||
}, action) => {
|
loading: null,
|
||||||
|
error: null
|
||||||
|
},
|
||||||
|
action
|
||||||
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "initialize started":
|
case "initialize started":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null
|
error: null
|
||||||
});
|
});
|
||||||
case "initialize success":
|
case "initialize success":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
error: null,
|
error: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
data: action.data,
|
data: action.data
|
||||||
});
|
});
|
||||||
case "initialize error":
|
case "initialize error":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
data: null,
|
data: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: action.data
|
error: action.data
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+15
-11
@@ -1,15 +1,19 @@
|
|||||||
const Responsive = (state = {
|
// jshint esversion: 6
|
||||||
width: null,
|
const Responsive = (
|
||||||
height: null,
|
state = {
|
||||||
}, action) => {
|
width: null,
|
||||||
|
height: null
|
||||||
|
},
|
||||||
|
action
|
||||||
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "resize event":
|
case "resize event":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
width: action.data,
|
width: action.data,
|
||||||
height: action.data
|
height: action.data
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+44
-41
@@ -1,70 +1,73 @@
|
|||||||
var createCamera = require('orbit-camera')
|
// jshint esversion: 6
|
||||||
var createScroll = require('scroll-speed')
|
var createCamera = require("orbit-camera");
|
||||||
var mp = require('mouse-position')
|
var createScroll = require("scroll-speed");
|
||||||
var mb = require('mouse-pressed')
|
var mp = require("mouse-position");
|
||||||
var key = require('key-pressed')
|
var mb = require("mouse-pressed");
|
||||||
|
var key = require("key-pressed");
|
||||||
|
|
||||||
const panSpeed = 0.4
|
const panSpeed = 0.4;
|
||||||
const scaleSpeed = 0.5
|
const scaleSpeed = 0.5;
|
||||||
const scaleMax = 3
|
const scaleMax = 3;
|
||||||
// const scaleMin = 1.15
|
// const scaleMin = 1.15
|
||||||
const scaleMin = 1.03
|
const scaleMin = 1.03;
|
||||||
|
|
||||||
function attachCamera(canvas, opts) {
|
function attachCamera(canvas, opts) {
|
||||||
opts = opts || {}
|
opts = opts || {};
|
||||||
opts.pan = opts.pan !== false
|
opts.pan = opts.pan !== false;
|
||||||
opts.scale = opts.scale !== false
|
opts.scale = opts.scale !== false;
|
||||||
opts.rotate = opts.rotate !== false
|
opts.rotate = opts.rotate !== false;
|
||||||
|
|
||||||
var scroll = createScroll(canvas, opts.scale)
|
var scroll = createScroll(canvas, opts.scale);
|
||||||
var mbut = mb(canvas, opts.rotate)
|
var mbut = mb(canvas, opts.rotate);
|
||||||
var mpos = mp(canvas)
|
var mpos = mp(canvas);
|
||||||
var camera = createCamera(
|
var camera = createCamera([0, 0, 1], [0, 0, -1], [0, 1, 0]);
|
||||||
[0, 0, 1]
|
|
||||||
, [0, 0, -1]
|
|
||||||
, [0, 1, 0]
|
|
||||||
)
|
|
||||||
|
|
||||||
camera.tick = tick
|
camera.tick = tick;
|
||||||
|
|
||||||
return camera
|
return camera;
|
||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
var ctrl = key('<control>') || key('<alt>')
|
var ctrl = key("<control>") || key("<alt>");
|
||||||
var alt = key('<shift>')
|
var alt = key("<shift>");
|
||||||
var height = canvas.height
|
var height = canvas.height;
|
||||||
var width = canvas.width
|
var width = canvas.width;
|
||||||
|
|
||||||
if (opts.rotate && mbut.left && ctrl && !alt) {
|
if (opts.rotate && mbut.left && ctrl && !alt) {
|
||||||
camera.rotate(
|
camera.rotate(
|
||||||
[ mpos.x / width - 0.5, mpos.y / height - 0.5 ]
|
[mpos.x / width - 0.5, mpos.y / height - 0.5],
|
||||||
, [ mpos.prevX / width - 0.5, mpos.prevY / height - 0.5 ]
|
[mpos.prevX / width - 0.5, mpos.prevY / height - 0.5]
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.pan && mbut.right || (mbut.left && !ctrl && !alt)) {
|
if ((opts.pan && mbut.right) || (mbut.left && !ctrl && !alt)) {
|
||||||
camera.pan([
|
camera.pan([
|
||||||
(panSpeed * (mpos[0] - mpos.prev[0]) / width) * Math.pow(camera.distance, 1)
|
panSpeed *
|
||||||
, (panSpeed * (mpos[1] - mpos.prev[1]) / height) * Math.pow(camera.distance, 1)
|
(mpos[0] - mpos.prev[0]) /
|
||||||
])
|
width *
|
||||||
|
Math.pow(camera.distance, 1),
|
||||||
|
panSpeed *
|
||||||
|
(mpos[1] - mpos.prev[1]) /
|
||||||
|
height *
|
||||||
|
Math.pow(camera.distance, 1)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.scale && scroll[1]) {
|
if (opts.scale && scroll[1]) {
|
||||||
camera.distance *= Math.exp(scroll[1] * scaleSpeed / height)
|
camera.distance *= Math.exp(scroll[1] * scaleSpeed / height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.scale && (mbut.middle || (mbut.left && !ctrl && alt))) {
|
if (opts.scale && (mbut.middle || (mbut.left && !ctrl && alt))) {
|
||||||
var d = (mpos.y - mpos.prevY)
|
var d = mpos.y - mpos.prevY;
|
||||||
if (!d) return;
|
if (!d) return;
|
||||||
|
|
||||||
camera.distance *= Math.exp(d / height)
|
camera.distance *= Math.exp(d / height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (camera.distance > scaleMax) camera.distance = scaleMax
|
if (camera.distance > scaleMax) camera.distance = scaleMax;
|
||||||
if (camera.distance < scaleMin) camera.distance = scaleMin
|
if (camera.distance < scaleMin) camera.distance = scaleMin;
|
||||||
|
|
||||||
scroll.flush()
|
scroll.flush();
|
||||||
mpos.flush()
|
mpos.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import {scaleRGB} from "./scaleRGB";
|
// jshint esversion: 6
|
||||||
|
import { scaleRGB } from "./scaleRGB";
|
||||||
|
|
||||||
export const parseRGB = (c) => {
|
export const parseRGB = c => {
|
||||||
if (c[0] !== "#") {
|
if (c[0] !== "#") {
|
||||||
const _c = c.replace(/[^\d,.]/g, '').split(',');
|
const _c = c.replace(/[^\d,.]/g, "").split(",");
|
||||||
return [
|
return [scaleRGB(+_c[0]), scaleRGB(+_c[1]), scaleRGB(+_c[2])];
|
||||||
scaleRGB(+_c[0]),
|
|
||||||
scaleRGB(+_c[1]),
|
|
||||||
scaleRGB(+_c[2])
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
var parsedHex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
|
var parsedHex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
|
||||||
return [
|
return [
|
||||||
|
|||||||
+18
-14
@@ -1,14 +1,15 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
/*****************************************
|
/*****************************************
|
||||||
******************************************
|
******************************************
|
||||||
Render Queue via http://bl.ocks.org/syntagmatic/raw/3341641/render-queue.js
|
Render Queue via http://bl.ocks.org/syntagmatic/raw/3341641/render-queue.js
|
||||||
******************************************
|
******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
|
|
||||||
const renderQueue = (function(callback1234) {
|
const renderQueue = function(callback1234) {
|
||||||
var _queue = [], // data to be rendered
|
var _queue = [], // data to be rendered
|
||||||
_rate = 300, // number of calls per frame
|
_rate = 300, // number of calls per frame
|
||||||
_invalidate = function() {}, // invalidate last render queue
|
_invalidate = function() {}, // invalidate last render queue
|
||||||
_clear = function() {}; // clearing function
|
_clear = function() {}; // clearing function
|
||||||
|
|
||||||
var rq = function(ARRAY_FROM_CELLXGENE) {
|
var rq = function(ARRAY_FROM_CELLXGENE) {
|
||||||
if (ARRAY_FROM_CELLXGENE) rq.data(ARRAY_FROM_CELLXGENE);
|
if (ARRAY_FROM_CELLXGENE) rq.data(ARRAY_FROM_CELLXGENE);
|
||||||
@@ -25,7 +26,7 @@ const renderQueue = (function(callback1234) {
|
|||||||
|
|
||||||
function doFrame() {
|
function doFrame() {
|
||||||
if (!valid) return true;
|
if (!valid) return true;
|
||||||
var chunk = _queue.splice(0,_rate);
|
var chunk = _queue.splice(0, _rate);
|
||||||
chunk.map(callback1234);
|
chunk.map(callback1234);
|
||||||
timer_frame(doFrame);
|
timer_frame(doFrame);
|
||||||
}
|
}
|
||||||
@@ -35,7 +36,7 @@ const renderQueue = (function(callback1234) {
|
|||||||
|
|
||||||
rq.data = function(ARRAY_FROM_CELLXGENE) {
|
rq.data = function(ARRAY_FROM_CELLXGENE) {
|
||||||
_invalidate();
|
_invalidate();
|
||||||
_queue = ARRAY_FROM_CELLXGENE.slice(0); // creates a copy of the data
|
_queue = ARRAY_FROM_CELLXGENE.slice(0); // creates a copy of the data
|
||||||
return rq;
|
return rq;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,14 +66,17 @@ const renderQueue = (function(callback1234) {
|
|||||||
|
|
||||||
rq.invalidate = _invalidate;
|
rq.invalidate = _invalidate;
|
||||||
|
|
||||||
var timer_frame = window.requestAnimationFrame
|
var timer_frame =
|
||||||
|| window.webkitRequestAnimationFrame
|
window.requestAnimationFrame ||
|
||||||
|| window.mozRequestAnimationFrame
|
window.webkitRequestAnimationFrame ||
|
||||||
|| window.oRequestAnimationFrame
|
window.mozRequestAnimationFrame ||
|
||||||
|| window.msRequestAnimationFrame
|
window.oRequestAnimationFrame ||
|
||||||
|| function(callback) { setTimeout(callback, 17); };
|
window.msRequestAnimationFrame ||
|
||||||
|
function(callback) {
|
||||||
|
setTimeout(callback, 17);
|
||||||
|
};
|
||||||
|
|
||||||
return rq;
|
return rq;
|
||||||
});
|
};
|
||||||
|
|
||||||
export default renderQueue;
|
export default renderQueue;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export const scaleRGB = (input) => {
|
// jshint esversion: 6
|
||||||
|
export const scaleRGB = input => {
|
||||||
const outputMax = 1;
|
const outputMax = 1;
|
||||||
const outputMin = 0;
|
const outputMin = 0;
|
||||||
|
|
||||||
@@ -7,4 +8,4 @@ export const scaleRGB = (input) => {
|
|||||||
|
|
||||||
const percent = (input - inputMin) / (inputMax - inputMin);
|
const percent = (input - inputMin) / (inputMax - inputMin);
|
||||||
return percent * (outputMax - outputMin) + outputMin;
|
return percent * (outputMax - outputMin) + outputMin;
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user