mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-26 03:18:11 +08:00
renaming backend, cellxgene to server, client respectively
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
// jshint esversion: 6
|
||||
import uri from "urijs";
|
||||
import * as globals from "../globals";
|
||||
import _ from "lodash";
|
||||
import { parseRGB } from "../util/parseRGB";
|
||||
|
||||
/*
|
||||
https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6
|
||||
storeInstance => functionToCallWithAnActionThatWillSendItToTheNextMiddleware => actionThatDispatchWasCalledWith => valueToUseAsTheReturnValueOfTheDispatchCall
|
||||
*/
|
||||
|
||||
/*
|
||||
What this file does:
|
||||
|
||||
1. fire a filter action anywhere in the app
|
||||
2. ** this middleware checks to see the state of all the currently selected filters, including the new one
|
||||
3. ** create updated selection from a copy of all the cells presently on the client (this may be a subset of 'all')
|
||||
4. ** append that new selection to the action so that it magically appears in the reducer just because the action was fired
|
||||
|
||||
This is nice because we keep a lot of filtering business logic centralized (what it means in practice to be selected)
|
||||
*/
|
||||
|
||||
const updateCellColorsMiddleware = store => {
|
||||
return next => {
|
||||
return action => {
|
||||
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 */
|
||||
const filterJustChanged =
|
||||
action.type === "color by expression" ||
|
||||
action.type === "color by continuous metadata" ||
|
||||
action.type === "color by categorical metadata";
|
||||
|
||||
if (!filterJustChanged || !s.controls.cellsMetadata) {
|
||||
return next(
|
||||
action
|
||||
); /* if the cells haven't loaded or the action wasn't a color change, bail */
|
||||
}
|
||||
|
||||
let cellsMetadataWithUpdatedColors = s.controls.cellsMetadata.slice(0);
|
||||
let colorScale;
|
||||
|
||||
/*
|
||||
in plain language...
|
||||
|
||||
(a) once the cells have loaded.
|
||||
(b) each time a user changes a color control we need to update cellsMetadata colors
|
||||
|
||||
This is available to all the draw functions as cell["__color__"] and cell["__colorRGB__"]
|
||||
*/
|
||||
|
||||
if (action.type === "color by categorical metadata") {
|
||||
colorScale = d3.scaleOrdinal().range(globals.ordinalColors);
|
||||
|
||||
for (let i = 0; i < cellsMetadataWithUpdatedColors.length; i++) {
|
||||
const cell = cellsMetadataWithUpdatedColors[i];
|
||||
let c = colorScale(cell[action.colorAccessor]);
|
||||
cell.__color__ = c;
|
||||
cell.__colorRGB__ = parseRGB(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === "color by continuous metadata") {
|
||||
colorScale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, action.rangeMaxForColorAccessor])
|
||||
.range([1, 0]);
|
||||
|
||||
_.each(cellsMetadataWithUpdatedColors, (cell, i) => {
|
||||
let c = d3.interpolateViridis(colorScale(cell[action.colorAccessor]));
|
||||
cellsMetadataWithUpdatedColors[i]["__color__"] = c;
|
||||
cellsMetadataWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
||||
});
|
||||
}
|
||||
|
||||
if (action.type === "color by expression") {
|
||||
const indexOfGene = 0; /* we only get one, this comes from server as needed now */
|
||||
|
||||
const expressionMap = {};
|
||||
/*
|
||||
converts [{cellname: cell123, e}, {}]
|
||||
|
||||
expressionMap = {
|
||||
cell123: [123, 2],
|
||||
cell789: [0, 8]
|
||||
}
|
||||
*/
|
||||
_.each(action.data.data.cells, cell => {
|
||||
/* this action is coming directly from the server */
|
||||
expressionMap[cell.cellname] = cell.e;
|
||||
});
|
||||
|
||||
const minExpressionCell = _.minBy(action.data.data.cells, cell => {
|
||||
return cell.e[indexOfGene];
|
||||
});
|
||||
|
||||
const maxExpressionCell = _.maxBy(action.data.data.cells, cell => {
|
||||
return cell.e[indexOfGene];
|
||||
});
|
||||
|
||||
// console.log('middle', action, expressionMap, minExpressionCell)
|
||||
|
||||
colorScale = d3
|
||||
.scaleLinear()
|
||||
.domain([
|
||||
minExpressionCell.e[indexOfGene],
|
||||
maxExpressionCell.e[indexOfGene]
|
||||
])
|
||||
.range([
|
||||
1,
|
||||
0
|
||||
]); /* invert viridis... probably pass this scale through to others */
|
||||
|
||||
_.each(cellsMetadataWithUpdatedColors, (cell, i) => {
|
||||
let c = d3.interpolateViridis(
|
||||
colorScale(expressionMap[cell.CellName][indexOfGene])
|
||||
);
|
||||
cellsMetadataWithUpdatedColors[i]["__color__"] = c;
|
||||
cellsMetadataWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
append the result of all the filters to the action the user just triggered
|
||||
*/
|
||||
let modifiedAction = Object.assign({}, action, {
|
||||
cellsMetadataWithUpdatedColors,
|
||||
colorScale
|
||||
});
|
||||
|
||||
return next(modifiedAction);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export default updateCellColorsMiddleware;
|
||||
@@ -0,0 +1,105 @@
|
||||
// jshint esversion: 6
|
||||
import uri from "urijs";
|
||||
import * as globals from "../globals";
|
||||
|
||||
/*
|
||||
XXX: this file should be obsolete. We just need to complete the refactoring
|
||||
of parallel.js and it can be removed entirely.
|
||||
|
||||
It is currently not in use - the middleware constructor does not include include it
|
||||
*/
|
||||
|
||||
/*
|
||||
https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6
|
||||
storeInstance => functionToCallWithAnActionThatWillSendItToTheNextMiddleware => actionThatDispatchWasCalledWith => valueToUseAsTheReturnValueOfTheDispatchCall
|
||||
*/
|
||||
|
||||
/*
|
||||
What this file does:
|
||||
|
||||
1. fire a filter action anywhere in the app
|
||||
2. ** this middleware checks to see the state of all the currently selected filters, including the new one
|
||||
3. ** create updated selection from a copy of all the cells presently on the client (this may be a subset of 'all')
|
||||
4. ** append that new selection to the action so that it magically appears in the reducer just because the action was fired
|
||||
|
||||
This is nice because we keep a lot of filtering business logic centralized (what it means in practice to be selected)
|
||||
*/
|
||||
|
||||
const updateCellSelectionMiddleware = store => {
|
||||
return next => {
|
||||
return action => {
|
||||
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 */
|
||||
const filterJustChanged =
|
||||
action.type === "continuous selection using parallel coords brushing" ||
|
||||
action.type === "continuous metadata histogram brush" ||
|
||||
action.type === "graph brush selection change" ||
|
||||
action.type === "graph brush deselect" ||
|
||||
action.type === "categorical metadata filter deselect" ||
|
||||
action.type === "categorical metadata filter select" ||
|
||||
action.type === "categorical metadata filter none of these" ||
|
||||
action.type === "categorical metadata filter all of these";
|
||||
|
||||
if (!filterJustChanged || !s.controls.cellsMetadata) {
|
||||
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
|
||||
- metadata has cellname and index, and that's all we ever need to reference cell info
|
||||
*/
|
||||
let newSelection = s.controls.cellsMetadata.slice(0);
|
||||
// _.forEach(newSelection, cell => (cell.__selected__ = true));
|
||||
for (let i = 0; i < newSelection.length; i++) {
|
||||
newSelection[i].__selected__ = true;
|
||||
}
|
||||
|
||||
/*
|
||||
in plain language...
|
||||
|
||||
(a) once the cells have loaded.
|
||||
(b) each time a user changes ANY control we need to update cellsMetadata
|
||||
there are two states:
|
||||
|
||||
1. control state we already know about (state.foo)
|
||||
2. control states that override states we already know about (action.foo applied instead of state.foo)
|
||||
|
||||
*/
|
||||
|
||||
if (
|
||||
(action.type ===
|
||||
"continuous selection using parallel coords brushing" &&
|
||||
s.controls.continuousSelection) ||
|
||||
s.controls.continuousSelection
|
||||
) {
|
||||
_.each(newSelection, (cell, i) => {
|
||||
const cellExtentsAreWithinContinuousSelectionBounds = s.controls.continuousSelection.every(
|
||||
active => {
|
||||
// test if point is within extents for each active brush
|
||||
return active.dimension.type.within(
|
||||
cell[active.dimension.key],
|
||||
active.extent,
|
||||
active.dimension
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (!cellExtentsAreWithinContinuousSelectionBounds) {
|
||||
newSelection[i]["__selected__"] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let modifiedAction = Object.assign({}, action, {
|
||||
newSelection
|
||||
}); /* append the result of all the filters to the action the user just triggered */
|
||||
|
||||
return next(modifiedAction);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export default updateCellSelectionMiddleware;
|
||||
@@ -0,0 +1,83 @@
|
||||
// jshint esversion: 6
|
||||
import uri from "urijs";
|
||||
|
||||
/*
|
||||
https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6
|
||||
storeInstance => functionToCallWithAnActionThatWillSendItToTheNextMiddleware => actionThatDispatchWasCalledWith => valueToUseAsTheReturnValueOfTheDispatchCall
|
||||
*/
|
||||
|
||||
const updateURLMiddleware = store => {
|
||||
return next => {
|
||||
return action => {
|
||||
const oldState = store.getState();
|
||||
const nextAction = next(action);
|
||||
|
||||
if (action.type === "url changed") {
|
||||
/* we don't handle pop state here - we handle it in the url reducer */
|
||||
return nextAction;
|
||||
}
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
/************************************************************************
|
||||
*************************************************************************
|
||||
1. Redux app state just changed. Clear URL, and then update it.
|
||||
1a. We get the whole state tree to construct the url!
|
||||
1b. But (see reducers/url.js) we try to centralize it because...
|
||||
1c. ...the back button / initial load case ('url changed' return above)
|
||||
means that we have to listen for 'url changed' and construct state
|
||||
from the browser
|
||||
*************************************************************************
|
||||
************************************************************************/
|
||||
|
||||
// const oldURI = URI(window.location.href)
|
||||
// const newURI = URI(oldURI).setQuery({})
|
||||
|
||||
// if (window.location.search === "") {
|
||||
// newURL = uri.addQuery(category, value).toString(); /* #1 */
|
||||
// } else if (uri.hasQuery(category, value) || uri.hasQuery(category, value, true)) { /* true param here means check arrays as well http://medialize.github.io/URI.js/docs.html#search-has */
|
||||
// newURL = uri.removeQuery(category, value).toString(); /* #4 */
|
||||
// } else {
|
||||
// newURL = uri.addQuery(category, value).toString(); /* #2 & #3 are handled by URI */
|
||||
// }
|
||||
//
|
||||
// window.history.pushState("", "", newURL)
|
||||
|
||||
//
|
||||
// // Internal helper for working with URIs
|
||||
// const oldURI = new URI(window.location.href);
|
||||
// const newURI = new URI(oldURI).setQueryData({});
|
||||
//
|
||||
// newURI.setPath('/foo/bar');
|
||||
//
|
||||
// // Set the path based on state
|
||||
// if (!state.isOnLandingPage && state.project.id) {
|
||||
// newURI.setPath(newURI.getPath() + state.project.id + '/');
|
||||
// newURI.addQueryData('baz', state.mode);
|
||||
// newURI.addQueryData('bat', state.selection.activePageID);
|
||||
// } else {
|
||||
// newURI.setPath(newURI.getPath() + state.landingSection + '/');
|
||||
// }
|
||||
//
|
||||
// // Avoid URL thrashing by replacing state while loading instead of pushing
|
||||
// const newPath = newURI.toString();
|
||||
// const oldPath = oldURI.toString();
|
||||
// if (newPath !== oldPath) {
|
||||
// if (
|
||||
// (oldState.mode === 'asdf' &&
|
||||
// state.mode === 'asdf' &&
|
||||
// !oldState.isOnLandingPage) ||
|
||||
// oldState.isLoadingProject !== state.isLoadingProject
|
||||
// ) {
|
||||
// window.history.replaceState(null, null, newPath);
|
||||
// } else {
|
||||
// window.history.pushState(null, null, newPath);
|
||||
// }
|
||||
// }
|
||||
|
||||
return nextAction;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export default updateURLMiddleware;
|
||||
Reference in New Issue
Block a user