Updating to latest cellxgene client code

Fixes the build error in the old version. 

Reverted the API version since we changed directions from updating the API to refactoring the server structure instead.
This commit is contained in:
Charlotte Weaver
2018-07-05 13:37:26 -07:00
parent 2da62720dd
commit 85506db971
18 changed files with 845 additions and 147 deletions
+184
View File
@@ -0,0 +1,184 @@
// jshint esversion: 6
const BitArray = require("../../src/util/typedCrossfilter/bitArray");
const defaultTestLength = 8;
describe("default select state", () => {
test("newly created Bitarray should be deselected", () => {
const ba = new BitArray(defaultTestLength);
expect(ba).toBeDefined();
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
const dim = ba.allocDimension();
expect(dim).toBeDefined();
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
ba.freeDimension(dim);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
});
});
describe("select and deselect", () => {
test("selectAll and deselectAll", () => {
const ba = new BitArray(defaultTestLength);
expect(ba).toBeDefined();
const dim1 = ba.allocDimension();
expect(dim1).toBeDefined();
ba.selectAll(dim1);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(true);
}
const dim2 = ba.allocDimension();
expect(dim2).toBeDefined();
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
ba.selectAll(dim2);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(true);
}
ba.deselectAll(dim1);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
ba.deselectAll(dim2);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
ba.selectAll(dim1);
ba.selectAll(dim2);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(true);
}
ba.freeDimension(dim1);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(true);
}
ba.freeDimension(dim2);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
});
test("selectOne and deselectOne", () => {
const ba = new BitArray(defaultTestLength);
expect(ba).toBeDefined();
const dim = ba.allocDimension();
expect(dim).toBeDefined();
ba.selectOne(dim, 0);
expect(ba.isSelected(0)).toEqual(true);
for (let i = 1; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
ba.deselectOne(dim, 0);
for (let i = 0; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
ba.selectOne(dim, 1);
expect(ba.isSelected(1)).toEqual(true);
expect(ba.isSelected(0)).toEqual(false);
for (let i = 2; i < defaultTestLength; i++) {
expect(ba.isSelected(i)).toEqual(false);
}
ba.selectAll(dim);
ba.deselectOne(dim, defaultTestLength - 1);
expect(ba.isSelected(defaultTestLength - 1)).toEqual(false);
for (let i = 0; i < defaultTestLength - 1; i++) {
expect(ba.isSelected(i)).toEqual(true);
}
});
});
describe("selectionCount", () => {
test("simple", () => {
const ba = new BitArray(defaultTestLength);
expect(ba).toBeDefined();
const dim1 = ba.allocDimension();
expect(dim1).toBeDefined();
const dim2 = ba.allocDimension();
expect(dim2).toBeDefined();
expect(ba.selectionCount).toEqual(0);
ba.selectAll(dim1);
expect(ba.selectionCount).toEqual(0);
ba.selectAll(dim2);
expect(ba.selectionCount).toEqual(defaultTestLength);
for (let i = 0; i < defaultTestLength; i++) {
ba.deselectOne(dim1, i);
expect(ba.selectionCount).toEqual(defaultTestLength - i - 1);
expect(ba.selectionCount).toEqual(ba.countAllOnes());
}
ba.freeDimension(dim1);
ba.freeDimension(dim2);
});
});
describe("fillBySelection", () => {
test("sets values correctly", () => {
const ba = new BitArray(defaultTestLength);
expect(ba).toBeDefined();
const dim = ba.allocDimension();
expect(dim).toBeDefined();
const arr = new Int32Array(defaultTestLength);
arr.fill(0);
const truth = new Int32Array(defaultTestLength);
truth.fill(0);
// initial state should be deselected
ba.fillBySelection(arr, 1, 0);
expect(arr).toEqual(expect.not.arrayContaining([1]));
// selectAll
ba.selectAll(dim);
ba.fillBySelection(arr, 1, 0);
expect(arr).toEqual(expect.not.arrayContaining([0]));
// deselectOne
ba.deselectOne(dim, 3);
ba.fillBySelection(arr, 1, 0);
truth.fill(1);
truth[3] = 0;
expect(arr).toEqual(truth);
// deselectAll
ba.deselectAll(dim);
ba.fillBySelection(arr, 1, 0);
truth.fill(0);
expect(arr).toEqual(truth);
// selectOne
ba.selectOne(dim, 5);
ba.fillBySelection(arr, 6, 1);
truth.fill(1);
truth[5] = 6;
expect(arr).toEqual(truth);
// should be deselected after dimension disposal
ba.freeDimension(dim);
ba.fillBySelection(arr, 3, 9);
truth.fill(9);
expect(arr).toEqual(truth);
});
});
@@ -0,0 +1,140 @@
// jshint esversion: 6
const PositiveIntervals = require("../../src/util/typedCrossfilter/positiveIntervals");
describe("canonicalize", () => {
test("empty", () => {
expect(PositiveIntervals.canonicalize([])).toEqual([]);
});
test("simple, already correct", () => {
expect(PositiveIntervals.canonicalize([[0, 1]])).toEqual([[0, 1]]);
expect(PositiveIntervals.canonicalize([[0, 1], [2, 3]])).toEqual([
[0, 1],
[2, 3]
]);
});
test("non-canonical, need to be canonicalized", () => {
expect(PositiveIntervals.canonicalize([[0, 1], [1, 2]])).toEqual([[0, 2]]);
expect(PositiveIntervals.canonicalize([[1, 2], [2, 3]])).toEqual([[1, 3]]);
});
});
describe("union", () => {
test("empty range", () => {
expect(PositiveIntervals.union([], [])).toEqual([]);
expect(PositiveIntervals.union([], [[1, 2]])).toEqual([[1, 2]]);
expect(PositiveIntervals.union([], [[1, 2], [3, 4]])).toEqual([
[1, 2],
[3, 4]
]);
expect(PositiveIntervals.union([[3, 4]], [])).toEqual([[3, 4]]);
expect(PositiveIntervals.union([[1, 2], [3, 4]], [])).toEqual([
[1, 2],
[3, 4]
]);
expect(PositiveIntervals.union([[3, 3]], [])).toEqual([[3, 3]]);
expect(PositiveIntervals.union([], [[3, 3]])).toEqual([[3, 3]]);
});
test("simple ranges", () => {
expect(PositiveIntervals.union([[1, 2]], [[2, 3]])).toEqual([[1, 3]]);
expect(PositiveIntervals.union([[2, 3]], [[1, 2]])).toEqual([[1, 3]]);
expect(PositiveIntervals.union([[1, 2]], [[3, 4]])).toEqual([
[1, 2],
[3, 4]
]);
expect(
PositiveIntervals.union([[1, 2], [3, 4]], [[6, 7], [19, 40]])
).toEqual([[1, 2], [3, 4], [6, 7], [19, 40]]);
expect(PositiveIntervals.union([[1, 4]], [[1, 1], [3, 4]])).toEqual([
[1, 4]
]);
expect(PositiveIntervals.union([[3, 3]], [[4, 4]])).toEqual([
[3, 3],
[4, 4]
]);
});
});
describe("intersection", () => {
test("empty range", () => {
expect(PositiveIntervals.intersection([], [])).toEqual([]);
expect(PositiveIntervals.intersection([], [[1, 2]])).toEqual([]);
expect(PositiveIntervals.intersection([[1, 2]], [])).toEqual([]);
});
test("simple", () => {
expect(PositiveIntervals.intersection([[1, 2]], [[2, 3]])).toEqual([]);
expect(PositiveIntervals.intersection([[2, 3]], [[1, 2]])).toEqual([]);
expect(PositiveIntervals.intersection([[1, 10]], [[1, 10]])).toEqual([
[1, 10]
]);
expect(PositiveIntervals.intersection([[1, 10]], [[2, 8]])).toEqual([
[2, 8]
]);
expect(PositiveIntervals.intersection([[2, 8]], [[1, 10]])).toEqual([
[2, 8]
]);
expect(PositiveIntervals.intersection([[1, 10]], [[2, 12]])).toEqual([
[2, 10]
]);
expect(PositiveIntervals.intersection([[2, 12]], [[1, 10]])).toEqual([
[2, 10]
]);
expect(PositiveIntervals.intersection([[1, 10]], [[1, 8]])).toEqual([
[1, 8]
]);
expect(PositiveIntervals.intersection([[1, 8]], [[1, 10]])).toEqual([
[1, 8]
]);
expect(PositiveIntervals.intersection([[1, 10]], [[1, 2], [6, 9]])).toEqual(
[[1, 2], [6, 9]]
);
expect(PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])).toEqual(
[[1363, 2638]]
);
expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([
[1, 2]
]);
});
});
describe("difference", () => {
test("empty", () => {
expect(PositiveIntervals.difference([], [])).toEqual([]);
expect(PositiveIntervals.difference([], [[1, 10]])).toEqual([]);
expect(PositiveIntervals.difference([[1, 10]], [])).toEqual([[1, 10]]);
});
test("simple", () => {
expect(PositiveIntervals.difference([[1, 2], [3, 4]], [])).toEqual([
[1, 2],
[3, 4]
]);
expect(PositiveIntervals.difference([[1, 2], [3, 10]], [[5, 10]])).toEqual([
[1, 2],
[3, 5]
]);
expect(PositiveIntervals.difference([[1, 2], [3, 10]], [[0, 5]])).toEqual([
[5, 10]
]);
expect(
PositiveIntervals.difference([[0, 2638]], [[0, 1363], [2055, 2638]])
).toEqual([[1363, 2055]]);
expect(
PositiveIntervals.difference([[0, 1363], [2055, 2638]], [[0, 2638]])
).toEqual([]);
expect(PositiveIntervals.difference([[0, 10]], [[0, 1]])).toEqual([
[1, 10]
]);
expect(PositiveIntervals.difference([[0, 10]], [[1, 2]])).toEqual([
[0, 1],
[2, 10]
]);
expect(PositiveIntervals.difference([[0, 10]], [[9, 10]])).toEqual([
[0, 9]
]);
});
});
@@ -0,0 +1,276 @@
// jshint esversion: 6
const _ = require("lodash");
const crossfilter = require("../../src/util/typedCrossfilter");
const someData = [
{
date: "2011-11-14T16:17:54Z",
quantity: 2,
total: 190,
tip: 100,
type: "tab",
productIDs: ["001"]
},
{
date: "2011-11-14T16:20:19Z",
quantity: 2,
total: 190,
tip: 100,
type: "tab",
productIDs: ["001", "005"]
},
{
date: "2011-11-14T16:28:54Z",
quantity: 1,
total: 300,
tip: 200,
type: "visa",
productIDs: ["004", "005"]
},
{
date: "2011-11-14T16:30:43Z",
quantity: 2,
total: 90,
tip: 0,
type: "tab",
productIDs: ["001", "002"]
},
{
date: "2011-11-14T16:48:46Z",
quantity: 2,
total: 90,
tip: 0,
type: "tab",
productIDs: ["005"]
},
{
date: "2011-11-14T16:53:41Z",
quantity: 2,
total: 90,
tip: 0,
type: "tab",
productIDs: ["001", "004", "005"]
},
{
date: "2011-11-14T16:54:06Z",
quantity: 1,
total: 100,
tip: 0,
type: "cash",
productIDs: ["001", "002", "003", "004", "005"]
},
{
date: "2011-11-14T16:58:03Z",
quantity: 2,
total: 90,
tip: 0,
type: "tab",
productIDs: ["001"]
},
{
date: "2011-11-14T17:07:21Z",
quantity: 2,
total: 90,
tip: 0,
type: "tab",
productIDs: ["004", "005"]
},
{
date: "2011-11-14T17:22:59Z",
quantity: 2,
total: 90,
tip: 0,
type: "tab",
productIDs: ["001", "002", "004", "005"]
},
{
date: "2011-11-14T17:25:45Z",
quantity: 2,
total: 200,
tip: 0,
type: "cash",
productIDs: ["002"]
},
{
date: "2011-11-14T17:29:52Z",
quantity: 1,
total: 200,
tip: 100,
type: "visa",
productIDs: ["004"]
}
];
var payments = null;
beforeEach(() => {
payments = crossfilter(someData);
});
describe("typedCrossfilter", () => {
test("alloc and free", () => {
expect(payments).toBeDefined();
expect(payments.size()).toEqual(someData.length);
expect(payments.all()).toEqual(someData);
const quantity = payments.dimension(r => r.quantity, Int32Array);
expect(quantity).toBeDefined();
expect(quantity.id()).toBeDefined();
quantity.dispose();
expect(payments.size()).toEqual(someData.length);
expect(payments.all()).toEqual(someData);
});
test("filterAll and filterNone", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(r => r.quantity, Int32Array);
const tip = payments.dimension(r => r.tip, Float32Array);
const total = payments.dimension(r => r.total, Float32Array);
const type = payments.dimension(r => r.type, "enum");
expect(quantity).toBeDefined();
expect(tip).toBeDefined();
expect(total).toBeDefined();
expect(type).toBeDefined();
// initially, all should be filtered
expect(payments.allFiltered().length).toEqual(payments.size());
expect(payments.allFiltered()).toEqual(payments.all());
expect(payments.countFiltered()).toEqual(someData.length);
// filterAll
tip.filterAll(); // should change nothing
expect(payments.allFiltered()).toEqual(payments.all());
expect(payments.countFiltered()).toEqual(someData.length);
// ditto
total.filterAll();
expect(payments.allFiltered()).toEqual(payments.all());
expect(payments.countFiltered()).toEqual(someData.length);
// filterNone
type.filterNone();
expect(payments.allFiltered()).toEqual([]);
expect(payments.countFiltered()).toEqual(0);
quantity.filterNone();
expect(payments.allFiltered()).toEqual([]);
expect(payments.countFiltered()).toEqual(0);
// invert the first none; should have no effect because type is
// still not filtered
quantity.filterAll();
expect(payments.allFiltered()).toEqual([]);
expect(payments.countFiltered()).toEqual(0);
// filter all of type; should select all
type.filterAll();
expect(payments.allFiltered()).toEqual(payments.all());
expect(payments.countFiltered()).toEqual(payments.size());
});
test("filterExact", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(r => r.quantity, Int32Array);
const tip = payments.dimension(r => r.tip, Float32Array);
const total = payments.dimension(r => r.total, Float32Array);
const type = payments.dimension(r => r.type, "enum");
quantity.filterExact(1);
expect(payments.countFiltered()).toEqual(
_.countBy(someData, "quantity")[1]
);
expect(payments.allFiltered()).toEqual(_.filter(someData, { quantity: 1 }));
tip.filterExact(0);
expect(payments.allFiltered()).toEqual(
_.filter(someData, { tip: 0, quantity: 1 })
);
type.filterExact("cash");
expect(payments.allFiltered()).toEqual(
_.filter(someData, { tip: 0, quantity: 1, type: "cash" })
);
});
test("filterRange", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(r => r.quantity, Int32Array);
const tip = payments.dimension(r => r.tip, Float32Array);
const total = payments.dimension(r => r.total, Float32Array);
const type = payments.dimension(r => r.type, "enum");
tip.filterRange([0, 91]);
expect(payments.allFiltered()).toEqual(
_(someData)
.filter(r => r.tip >= 0 && r.tip < 91)
.value()
);
tip.filterRange([0, 90]);
expect(payments.allFiltered()).toEqual(
_(someData)
.filter(r => r.tip >= 0 && r.tip < 90)
.value()
);
tip.filterRange([1, 90]);
expect(payments.allFiltered()).toEqual(
_(someData)
.filter(r => r.tip >= 1 && r.tip < 91)
.value()
);
});
test("filterEnum", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(r => r.quantity, Int32Array);
const tip = payments.dimension(r => r.tip, Float32Array);
const total = payments.dimension(r => r.total, Float32Array);
const type = payments.dimension(r => r.type, "enum");
type.filterEnum(["tab", "cash"]);
expect(payments.allFiltered()).toEqual(
_(someData)
.filter(r => r.type === "cash" || r.type === "tab")
.value()
);
tip.filterEnum([0, 100]);
expect(payments.allFiltered()).toEqual(
_(someData)
.filter(r => r.type === "cash" || r.type === "tab")
.filter(r => r.tip === 0 || r.tip === 100)
.value()
);
});
test("more than 32 dimensions", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(r => r.quantity, Int32Array);
const tip = payments.dimension(r => r.tip, Float32Array);
const total = payments.dimension(r => r.total, Float32Array);
const type = payments.dimension(r => r.type, "enum");
// Create a bunch of fake dimensions to ensure we can handle > 32
let dimMap = {};
for (let i = 0; i < 65; i++) {
dimMap[i] = payments.dimension(r => Math.random(), Float32Array);
expect(dimMap[i]).toBeDefined();
expect(dimMap[i].id()).toBeDefined();
}
// everything should start as selected/filtered
expect(payments.countFiltered()).toEqual(someData.length);
dimMap[0].filterAll();
dimMap[64].filterAll();
expect(payments.countFiltered()).toEqual(someData.length);
dimMap[33].filterNone();
expect(payments.allFiltered()).toEqual([]);
dimMap[33].filterAll();
expect(payments.allFiltered()).toEqual(someData);
});
});
+8 -1
View File
@@ -9,7 +9,8 @@
"clean": "rimraf build", "clean": "rimraf build",
"start": "node server/development.js", "start": "node server/development.js",
"lint": "eslint src", "lint": "eslint src",
"prod": "cross-env NODE_ENV=production PORT=3000 node server/production.js" "prod": "cross-env NODE_ENV=production PORT=3000 node server/production.js",
"test": "jest"
}, },
"engineStrict": true, "engineStrict": true,
"engines": { "engines": {
@@ -90,6 +91,7 @@
"gzip-size": "^3.0.0", "gzip-size": "^3.0.0",
"html-webpack-inline-source-plugin": "0.0.6", "html-webpack-inline-source-plugin": "0.0.6",
"html-webpack-plugin": "^2.22.0", "html-webpack-plugin": "^2.22.0",
"jest": "^23.0.1",
"jsdom": "^9.4.1", "jsdom": "^9.4.1",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"nyc": "^10.0.0", "nyc": "^10.0.0",
@@ -106,5 +108,10 @@
"webpack-dev-middleware": "^1.6.1", "webpack-dev-middleware": "^1.6.1",
"webpack-hot-middleware": "^2.12.2", "webpack-hot-middleware": "^2.12.2",
"whatwg-fetch": "^2.0.1" "whatwg-fetch": "^2.0.1"
},
"jest": {
"testMatch": [
"**/__tests__/**/?(*.)(spec|test).js?(x)"
]
} }
} }
@@ -15,7 +15,8 @@ import FaPaintBrush from "react-icons/lib/fa/paint-brush";
@connect(state => { @connect(state => {
return { return {
colorAccessor: state.controls.colorAccessor colorAccessor: state.controls.colorAccessor,
categoricalAsBooleansMap: state.controls.categoricalAsBooleansMap
}; };
}) })
class Category extends React.Component { class Category extends React.Component {
@@ -26,7 +27,24 @@ class Category extends React.Component {
isExpanded: false isExpanded: false
}; };
} }
componentDidUpdate() {
const valuesAsBool = _.values(
this.props.categoricalAsBooleansMap[this.props.metadataField]
)
/* count categories toggled on by counting true values */
const categoriesToggledOn = _.values(valuesAsBool).filter(v => v).length;
if (categoriesToggledOn === valuesAsBool.length) {
/* everything is on, so not indeterminate */
this.checkbox.indeterminate = false;
} else if (categoriesToggledOn === 0) {
/* nothing is on, so no */
this.checkbox.indeterminate = false;
} else if (categoriesToggledOn < valuesAsBool.length) {
/* to be explicit... */
this.checkbox.indeterminate = true;
}
}
handleColorChange() { handleColorChange() {
this.props.dispatch({ this.props.dispatch({
type: "color by categorical metadata", type: "color by categorical metadata",
@@ -61,6 +79,16 @@ class Category extends React.Component {
); );
}); });
} }
handleToggleAllClick() {
// || this.checkbox.indeterminate === false
if (this.state.isChecked) {
console.log('checked, firing toggle none')
this.toggleNone();
} else if (!this.state.isChecked) {
console.log('!checked, firing toggle all')
this.toggleAll()
}
}
render() { render() {
return ( return (
<div <div
@@ -102,11 +130,8 @@ class Category extends React.Component {
</span> </span>
{this.props.metadataField} {this.props.metadataField}
<input <input
onChange={ onChange={this.handleToggleAllClick.bind(this)}
this.state.isChecked ref={el => this.checkbox = el}
? this.toggleNone.bind(this)
: this.toggleAll.bind(this)
}
checked={this.state.isChecked} checked={this.state.isChecked}
type="checkbox" type="checkbox"
/> />
@@ -7,8 +7,11 @@ https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771
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 FaPaintBrush from "react-icons/lib/fa/paint-brush";
import * as globals from "../../globals";
@connect(state => { @connect(state => {
console.log("state in histo brush", state)
const ranges = const ranges =
state.cells.cells && state.cells.cells.data.ranges state.cells.cells && state.cells.cells.data.ranges
? state.cells.cells.data.ranges ? state.cells.cells.data.ranges
@@ -26,7 +29,8 @@ import { connect } from "react-redux";
return { return {
colorAccessor: state.controls.colorAccessor, colorAccessor: state.controls.colorAccessor,
colorScale: state.controls.colorScale, colorScale: state.controls.colorScale,
cellsMetadata: state.controls.cellsMetadata cellsMetadata: state.controls.cellsMetadata,
initializeRanges
}; };
}) })
class HistogramBrush extends React.Component { class HistogramBrush extends React.Component {
@@ -154,7 +158,7 @@ class HistogramBrush extends React.Component {
) )
.call(d3.axisBottom(x).ticks(5)) .call(d3.axisBottom(x).ticks(5))
.append("text") .append("text")
.attr("x", 300) .attr("x", this.width - 2)
.attr("y", -6) .attr("y", -6)
.attr("fill", "#000") .attr("fill", "#000")
.attr("text-anchor", "end") .attr("text-anchor", "end")
@@ -164,13 +168,40 @@ class HistogramBrush extends React.Component {
this.setState({ brush, xAxis }); this.setState({ brush, xAxis });
} }
} }
handleColorAction() {
this.props.dispatch({
type: "color by continuous metadata",
colorAccessor: this.props.metadataField,
rangeMaxForColorAccessor: this.props.initializeRanges[this.props.metadataField].range.max
});
}
render() { render() {
return ( return (
<div <div
style={{ marginTop: 10 }} style={{ marginTop: 10, position: "relative" }}
id={"histogram_" + this.props.metadataField} id={"histogram_" + this.props.metadataField}
> >
<span
onClick={this.handleColorAction.bind(this)}
style={{
fontSize: 16,
marginLeft: 4,
// padding: this.props.colorAccessor === this.props.metadataField ? 3 : "auto",
borderRadius: 3,
color:
this.props.colorAccessor === this.props.metadataField
? globals.brightBlue
: "black",
// backgroundColor: this.props.colorAccessor === this.props.metadataField ? globals.brightBlue : "inherit",
display: "inline-block",
position: "relative",
left: this.width,
top: this.height - 22,
cursor: "pointer"
}}
>
<FaPaintBrush />
</span>
<svg <svg
width={this.width} width={this.width}
height={this.height} height={this.height}
@@ -103,7 +103,11 @@ class ContinuousLegend extends React.Component {
this.state = {}; this.state = {};
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.colorAccessor !== this.props.colorAccessor) { if (
nextProps.colorAccessor !== this.props.colorAccessor ||
nextProps.responsive.height !== this.props.responsive.height ||
nextProps.responsive.width !== this.props.responsive.width
) {
/* always remove it, if it's not continuous we don't put it back. */ /* always remove it, if it's not continuous we don't put it back. */
d3 d3
.select("#continuous_legend") .select("#continuous_legend")
+45 -29
View File
@@ -164,28 +164,29 @@ class Graph extends React.Component {
nextProps.responsive.width !== this.props.responsive.width nextProps.responsive.width !== this.props.responsive.width
) { ) {
/* clear out whatever was on the div, even if nothing, but usually the brushes etc */ /* clear out whatever was on the div, even if nothing, but usually the brushes etc */
d3 d3.select("#graphAttachPoint")
.select("#graphAttachPoint")
.selectAll("*") .selectAll("*")
.remove(); .remove();
const { svg } = setupSVGandBrushElements( const { svg, brush, brushContainer } = setupSVGandBrushElements(
this.handleBrushSelectAction.bind(this), this.handleBrushSelectAction.bind(this),
this.handleBrushDeselectAction.bind(this), this.handleBrushDeselectAction.bind(this),
nextProps.responsive, nextProps.responsive,
this.graphPaddingTop this.graphPaddingTop
); );
this.setState({ svg }); this.setState({ svg, brush, brushContainer });
} }
} }
handleBrushSelectAction() { handleBrushSelectAction() {
/* /* This conditional handles procedural brush deselect. Brush emits an event on procedural deselect because it is move: null */
if (d3.event.sourceEvent !== null) {
/*
No idea why d3 event scope works like this No idea why d3 event scope works like this
but apparently but apparently
it does it does
https://bl.ocks.org/EfratVil/0e542f5fc426065dd1d4b6daaa345a9f https://bl.ocks.org/EfratVil/0e542f5fc426065dd1d4b6daaa345a9f
*/ */
const s = d3.event.selection; const s = d3.event.selection;
/* /*
event describing brush position: event describing brush position:
@-------| @-------|
| | | |
@@ -193,33 +194,47 @@ class Graph extends React.Component {
|-------@ |-------@
*/ */
// compute inverse view matrix // compute inverse view matrix
const inverse = mat4.invert([], this.state.camera.view()); const inverse = mat4.invert([], this.state.camera.view());
// transform screen coordinates -> cell coordinates // transform screen coordinates -> cell coordinates
const invert = pin => { const invert = pin => {
const x = const x =
2 * pin[0] / (this.props.responsive.height - this.graphPaddingTop) - 1; (2 * pin[0]) / (this.props.responsive.height - this.graphPaddingTop) -
const y = 1;
2 * const y =
(1 - pin[1] / (this.props.responsive.height - this.graphPaddingTop)) - 2 *
1; (1 -
const pout = [x + inverse[12], y + inverse[13]]; pin[1] / (this.props.responsive.height - this.graphPaddingTop)) -
return [(pout[0] + 1) / 2, (pout[1] + 1) / 2]; 1;
}; const pout = [
x * inverse[14] + inverse[12],
y * inverse[14] + inverse[13]
];
return [(pout[0] + 1) / 2, (pout[1] + 1) / 2];
};
const brushCoords = { const brushCoords = {
northwest: invert([s[0][0], s[0][1]]), northwest: invert([s[0][0], s[0][1]]),
southeast: invert([s[1][0], s[1][1]]) southeast: invert([s[1][0], s[1][1]])
}; };
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 && !d3.event.selection) {
this.props.dispatch({
type: "graph brush deselect"
});
}
if (!d3.event) {
/* this line clears the brush procedurally, ie., zoom button clicked, not a click away from brush on svg */
this.state.svg.select(".graph_brush").call(this.state.brush.move, null);
this.props.dispatch({ this.props.dispatch({
type: "graph brush deselect" type: "graph brush deselect"
}); });
@@ -302,6 +317,7 @@ class Graph extends React.Component {
</button> </button>
<button <button
onClick={() => { onClick={() => {
this.handleBrushDeselectAction();
this.setState({ mode: "zoom" }); this.setState({ mode: "zoom" });
}} }}
style={{ style={{
+16 -14
View File
@@ -23,21 +23,23 @@ export const setupSVGandBrushElements = (
.attr("height", side) .attr("height", side)
.attr("class", `${styles.graphSVG}`); .attr("class", `${styles.graphSVG}`);
svg.append("g").call( const brush = d3
d3 .brush()
.brush() .extent([
.extent([ [0, 0],
[0, 0], [responsive.height - graphPaddingTop, responsive.height - graphPaddingTop]
[ ])
responsive.height - graphPaddingTop, .on("brush", handleBrushSelectAction)
responsive.height - graphPaddingTop .on("end", handleBrushDeselectAction);
]
]) const brushContainer = svg
.on("brush", handleBrushSelectAction) .append("g")
.on("end", handleBrushDeselectAction) .attr("class", "graph_brush")
); .call(brush);
return { return {
svg svg,
brushContainer,
brush
}; };
}; };
@@ -51,6 +51,7 @@ class Scatterplot extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.count = 0; this.count = 0;
this.axes = false;
this.state = { this.state = {
svg: null, svg: null,
// ctx: null, // ctx: null,
@@ -63,8 +64,27 @@ class Scatterplot extends React.Component {
componentDidMount() { componentDidMount() {
const { svg } = setupScatterplot(width, height, margin); const { svg } = setupScatterplot(width, height, margin);
let scales;
/* if we've already got the data, user clicked back and forth between tabs, so render the scatterplot */
if (
this.props.expression &&
this.props.expression.data &&
this.props.scatterplotXXaccessor &&
this.props.scatterplotYYaccessor
) {
scales = this.setupScales(
this.props.expression,
this.props.scatterplotXXaccessor,
this.props.scatterplotYYaccessor
);
this.drawAxesSVG(scales.xScale, scales.yScale, svg);
}
this.setState({ this.setState({
svg svg,
xScale: scales ? scales.xScale : null,
yScale: scales ? scales.yScale : null
}); });
const camera = _camera(this.reglCanvas, { scale: true, rotate: false }); const camera = _camera(this.reglCanvas, { scale: true, rotate: false });
@@ -104,21 +124,36 @@ class Scatterplot extends React.Component {
}); });
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
this.maybeSetupScalesAndDrawAxes(nextProps); if (
nextProps.expression &&
nextProps.expression.data &&
nextProps.scatterplotXXaccessor &&
nextProps.scatterplotYYaccessor
) {
const scales = this.setupScales(
nextProps.expression,
nextProps.scatterplotXXaccessor,
nextProps.scatterplotYYaccessor
);
this.setState(scales);
}
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if ( if (
this.state.svg &&
this.state.xScale && this.state.xScale &&
this.state.yScale && this.state.yScale &&
this.props.scatterplotXXaccessor && this.props.scatterplotXXaccessor &&
this.props.scatterplotYYaccessor && this.props.scatterplotYYaccessor &&
(this.props.scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc (this.props.scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc
this.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor) this.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor || // was CLU now FTH1 etc
!this.axes) // clicked off the tab and back again, rerender
) { ) {
this.drawAxesSVG(this.state.xScale, this.state.yScale); this.drawAxesSVG(this.state.xScale, this.state.yScale, this.state.svg);
} }
if ( if (
this.props.metadata &&
this.state.regl && this.state.regl &&
this.state.pointBuffer && this.state.pointBuffer &&
this.state.colorBuffer && this.state.colorBuffer &&
@@ -179,47 +214,32 @@ class Scatterplot extends React.Component {
this.count = cellCount; this.count = cellCount;
} }
} }
maybeSetupScalesAndDrawAxes(nextProps) { setupScales(expression, scatterplotXXaccessor, scatterplotYYaccessor) {
if ( const xScale = d3
nextProps.expression && .scaleLinear()
nextProps.expression.data && .domain(
nextProps.scatterplotXXaccessor && d3.extent(expression.data.cells, (cell, i) => {
nextProps.scatterplotYYaccessor return cell.e[expression.data.genes.indexOf(scatterplotXXaccessor)];
) { })
const xScale = d3 )
.scaleLinear() .range([0, width]);
.domain(
d3.extent(nextProps.expression.data.cells, (cell, i) => {
return cell.e[
nextProps.expression.data.genes.indexOf(
nextProps.scatterplotXXaccessor
)
];
})
)
.range([0, width]);
const yScale = d3 const yScale = d3
.scaleLinear() .scaleLinear()
.domain( .domain(
d3.extent(nextProps.expression.data.cells, cell => { d3.extent(expression.data.cells, cell => {
return cell.e[ return cell.e[expression.data.genes.indexOf(scatterplotYYaccessor)];
nextProps.expression.data.genes.indexOf( })
nextProps.scatterplotYYaccessor )
) .range([height, 0]);
];
})
)
.range([height, 0]);
this.setState({ return {
xScale, xScale,
yScale yScale
}); };
}
} }
drawAxesSVG(xScale, yScale) { drawAxesSVG(xScale, yScale, svg) {
this.state.svg.selectAll("*").remove(); 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().scale(xScale); var xAxis = d3.axisBottom().scale(xScale);
@@ -227,28 +247,28 @@ class Scatterplot extends React.Component {
var yAxis = d3.axisLeft().scale(yScale); var yAxis = d3.axisLeft().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 svg
.append("g") .append("g")
.attr("transform", "translate(0," + height + ")") .attr("transform", "translate(0," + height + ")")
.attr("class", "x axis") .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 svg
.append("g") .append("g")
.attr("transform", "translate(0,0)") .attr("transform", "translate(0,0)")
.attr("class", "y axis") .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 svg
.append("text") .append("text")
.attr("x", 10) .attr("x", 10)
.attr("y", 10) .attr("y", 10)
.attr("class", "label") .attr("class", "label")
.text(this.props.scatterplotYYaccessor); .text(this.props.scatterplotYYaccessor);
this.state.svg svg
.append("text") .append("text")
.attr("x", width) .attr("x", width)
.attr("y", height - 10) .attr("y", height - 10)
+2 -2
View File
@@ -48,10 +48,10 @@ export const bolder = 700;
export let API = { export let API = {
// prefix: "http://api.clustering.czi.technology/api/", // prefix: "http://api.clustering.czi.technology/api/",
//prefix: "http://tabulamuris.cxg.czi.technology/api/", //prefix: "http://tabulamuris.cxg.czi.technology/api/",
prefix: "http://pbmc3k.cxg.czi.technology/api/", // prefix: "http://pbmc3k.cxg.czi.technology/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/"
}; };
+1 -1
View File
@@ -2,7 +2,7 @@
import _ from "lodash"; import _ from "lodash";
import { parseRGB } from "../util/parseRGB"; import { parseRGB } from "../util/parseRGB";
import { createSchemaByDataSniffing } from "../util/schema"; import { createSchemaByDataSniffing } from "../util/schema";
var crossfilter = require("../util/typedCrossfilter"); import crossfilter from "../util/typedCrossfilter";
// Deduce the correct crossfilter dimension type from a metadata // Deduce the correct crossfilter dimension type from a metadata
// schema description. // schema description.
+5 -4
View File
@@ -140,7 +140,7 @@ class BitArray {
const col = dim >>> 5; const col = dim >>> 5;
const before = this.bitarray[col * this.length + index]; const before = this.bitarray[col * this.length + index];
const after = before | (1 << (dim % 32)); const after = before | (1 << (dim % 32));
this.bitarray[col] = after; this.bitarray[col * this.length + index] = after;
} }
// deselect index on dimension // deselect index on dimension
@@ -149,7 +149,7 @@ class BitArray {
const col = dim >>> 5; const col = dim >>> 5;
const before = this.bitarray[col * this.length + index]; const before = this.bitarray[col * this.length + index];
const after = before & ~(1 << (dim % 32)); const after = before & ~(1 << (dim % 32));
this.bitarray[col] = after; this.bitarray[col * this.length + index] = after;
} }
// select all indices on dimension. // select all indices on dimension.
@@ -214,7 +214,8 @@ class BitArray {
const bitmask = this.bitmask[0]; const bitmask = this.bitmask[0];
const bitarray = this.bitarray; const bitarray = this.bitarray;
for (let i = 0, len = this.length; i < len; i++) { for (let i = 0, len = this.length; i < len; i++) {
result[i] = bitarray[i] === bitmask ? selectedValue : deselectedValue; result[i] =
bitmask && bitarray[i] === bitmask ? selectedValue : deselectedValue;
} }
} else { } else {
for (let i = 0, len = this.length; i < len; i++) { for (let i = 0, len = this.length; i < len; i++) {
@@ -225,4 +226,4 @@ class BitArray {
} }
} }
module.exports = BitArray; export default BitArray;
+23 -23
View File
@@ -29,9 +29,9 @@ more complex API. In a few cases, elements of that API were incorporated.
*/ */
var PositiveIntervals = require("./positiveIntervals"); import PositiveIntervals from "./positiveIntervals";
var BitArray = require("./bitArray"); import BitArray from "./bitArray";
var Util = require("./util"); import {fillRange, lowerBound, lowerBoundIndirect, upperBound, upperBoundIndirect} from "./util";
class TypedCrossfilter { class TypedCrossfilter {
constructor(data) { constructor(data) {
@@ -65,7 +65,7 @@ class TypedCrossfilter {
_freeDimension(id) { _freeDimension(id) {
this.selection.freeDimension(id); this.selection.freeDimension(id);
this.filters = this.filters.filter(f => f.id != id); this.filters = this.filters.filter(f => f._id != id);
} }
// return array of all records that are selected/filtered // return array of all records that are selected/filtered
@@ -105,7 +105,7 @@ class TypedCrossfilter {
class ScalarDimension { class ScalarDimension {
constructor(value, valueArrayType, crossfilter, id) { constructor(value, valueArrayType, crossfilter, id) {
this.crossfilter = crossfilter; this.crossfilter = crossfilter;
this.id = id; this._id = id;
// current selection filter, expressed as PostiveIntervals. // current selection filter, expressed as PostiveIntervals.
this.currentFilter = []; this.currentFilter = [];
@@ -118,7 +118,7 @@ class ScalarDimension {
this.value = array; this.value = array;
// create sort index // create sort index
this.index = Util.fillRange(new Uint32Array(this.crossfilter.data.length)); this.index = fillRange(new Uint32Array(this.crossfilter.data.length));
this.index.sort((a, b) => array[a] - array[b]); this.index.sort((a, b) => array[a] - array[b]);
} }
@@ -133,11 +133,11 @@ class ScalarDimension {
} }
dispose() { dispose() {
this.crossfilter._freeDimension(this.id); this.crossfilter._freeDimension(this._id);
} }
id() { id() {
return this.id; return this._id;
} }
_updateFilters(newFilter) { _updateFilters(newFilter) {
@@ -147,26 +147,26 @@ class ScalarDimension {
// more complex work and just clobber everything. // more complex work and just clobber everything.
// //
if (newFilter.length === 0) { if (newFilter.length === 0) {
this.crossfilter.selection.deselectAll(this.id); this.crossfilter.selection.deselectAll(this._id);
} else if ( } else if (
newFilter.length === 1 && newFilter.length === 1 &&
newFilter[0][0] === 0 && newFilter[0][0] === 0 &&
newFilter[0][1] == this.index.length newFilter[0][1] == this.index.length
) { ) {
this.crossfilter.selection.selectAll(this.id); this.crossfilter.selection.selectAll(this._id);
} else { } else {
const adds = PositiveIntervals.difference(newFilter, this.currentFilter); const adds = PositiveIntervals.difference(newFilter, this.currentFilter);
const dels = PositiveIntervals.difference(this.currentFilter, newFilter); const dels = PositiveIntervals.difference(this.currentFilter, newFilter);
dels.forEach(interval => dels.forEach(interval =>
this.crossfilter.selection.deselectIndirectFromRange( this.crossfilter.selection.deselectIndirectFromRange(
this.id, this._id,
this.index, this.index,
interval interval
) )
); );
adds.forEach(interval => adds.forEach(interval =>
this.crossfilter.selection.selectIndirectFromRange( this.crossfilter.selection.selectIndirectFromRange(
this.id, this._id,
this.index, this.index,
interval interval
) )
@@ -179,14 +179,14 @@ class ScalarDimension {
// filter by value - exact match // filter by value - exact match
filterExact(value) { filterExact(value) {
const newFilter = [ const newFilter = [
Util.lowerBoundIndirect( lowerBoundIndirect(
this.value, this.value,
this.index, this.index,
value, value,
0, 0,
this.value.length this.value.length
), ),
Util.upperBoundIndirect( upperBoundIndirect(
this.value, this.value,
this.index, this.index,
value, value,
@@ -207,14 +207,14 @@ class ScalarDimension {
const newFilter = []; const newFilter = [];
for (let v = 0, len = values.length; v < len; v++) { for (let v = 0, len = values.length; v < len; v++) {
const intv = [ const intv = [
Util.lowerBoundIndirect( lowerBoundIndirect(
this.value, this.value,
this.index, this.index,
values[v], values[v],
0, 0,
this.value.length this.value.length
), ),
Util.upperBoundIndirect( upperBoundIndirect(
this.value, this.value,
this.index, this.index,
values[v], values[v],
@@ -233,14 +233,14 @@ class ScalarDimension {
filterRange(range) { filterRange(range) {
const newFilter = []; const newFilter = [];
const intv = [ const intv = [
Util.lowerBoundIndirect( lowerBoundIndirect(
this.value, this.value,
this.index, this.index,
range[0], range[0],
0, 0,
this.value.length this.value.length
), ),
Util.upperBoundIndirect( upperBoundIndirect(
this.value, this.value,
this.index, this.index,
range[1], range[1],
@@ -350,7 +350,7 @@ class EnumDimension extends ScalarDimension {
const enumLen = this.enumIndex.length; const enumLen = this.enumIndex.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const v = value(data[i]); const v = value(data[i]);
const e = Util.lowerBound(this.enumIndex, v, 0, enumLen); const e = lowerBound(this.enumIndex, v, 0, enumLen);
array[i] = e; array[i] = e;
} }
return array; return array;
@@ -358,14 +358,14 @@ class EnumDimension extends ScalarDimension {
filterExact(value) { filterExact(value) {
return super.filterExact( return super.filterExact(
Util.lowerBound(this.enumIndex, value, 0, this.enumIndex.length) lowerBound(this.enumIndex, value, 0, this.enumIndex.length)
); );
} }
filterEnum(values) { filterEnum(values) {
return super.filterEnum( return super.filterEnum(
values.map(v => values.map(v =>
Util.lowerBound(this.enumIndex, v, 0, this.enumIndex.length) lowerBound(this.enumIndex, v, 0, this.enumIndex.length)
) )
); );
} }
@@ -373,7 +373,7 @@ class EnumDimension extends ScalarDimension {
filterRange(range) { filterRange(range) {
return super.filterEnum( return super.filterEnum(
range.map(v => range.map(v =>
Util.lowerBound(this.enumIndex, v, 0, this.enumIndex.length) lowerBound(this.enumIndex, v, 0, this.enumIndex.length)
) )
); );
} }
@@ -391,4 +391,4 @@ crossfilter.TypedCrossfilter = TypedCrossfilter;
crossfilter.ScalarDimension = ScalarDimension; crossfilter.ScalarDimension = ScalarDimension;
crossfilter.EnumDimension = EnumDimension; crossfilter.EnumDimension = EnumDimension;
module.exports = crossfilter; export default crossfilter;
@@ -129,4 +129,4 @@ class PositiveIntervals {
} }
} }
module.exports = PositiveIntervals; export default PositiveIntervals;
+5 -13
View File
@@ -8,7 +8,7 @@
// fill an array or typedarray with a sequential range of numbers, // fill an array or typedarray with a sequential range of numbers,
// starting with `start` // starting with `start`
// //
function fillRange(arr, start = 0) { export function fillRange(arr, start = 0) {
for (let i = 0, len = arr.length; i < len; i++) { for (let i = 0, len = arr.length; i < len; i++) {
arr[i] = i + start; arr[i] = i + start;
} }
@@ -30,7 +30,7 @@ function fillRange(arr, start = 0) {
// a factory version of lowerBound that takes an accessor (rather than having // a factory version of lowerBound that takes an accessor (rather than having
// a special-cased version for lining the indirection). // a special-cased version for lining the indirection).
// //
function lowerBound(valueArray, value, first, last) { export function lowerBound(valueArray, value, first, last) {
// this is just a binary search // this is just a binary search
while (first < last) { while (first < last) {
const middle = (first + last) >>> 1; const middle = (first + last) >>> 1;
@@ -45,7 +45,7 @@ function lowerBound(valueArray, value, first, last) {
// Inlined performance optimization - used to indirect through a sort map. // Inlined performance optimization - used to indirect through a sort map.
// //
function lowerBoundIndirect(valueArray, indexArray, value, first, last) { export function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
// this is just a binary search // this is just a binary search
while (first < last) { while (first < last) {
const middle = (first + last) >>> 1; const middle = (first + last) >>> 1;
@@ -69,7 +69,7 @@ function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
// C++: upper_bound() // C++: upper_bound()
// Python: bisect.bisect_right() // Python: bisect.bisect_right()
// //
function upperBound(valueArray, value, first, last) { export function upperBound(valueArray, value, first, last) {
// this is just a binary search // this is just a binary search
while (first < last) { while (first < last) {
const middle = (first + last) >>> 1; const middle = (first + last) >>> 1;
@@ -84,7 +84,7 @@ function upperBound(valueArray, value, first, last) {
// Inline performance optimization // Inline performance optimization
// //
function upperBoundIndirect(valueArray, indexArray, value, first, last) { export function upperBoundIndirect(valueArray, indexArray, value, first, last) {
// this is just a binary search // this is just a binary search
while (first < last) { while (first < last) {
const middle = (first + last) >>> 1; const middle = (first + last) >>> 1;
@@ -96,11 +96,3 @@ function upperBoundIndirect(valueArray, indexArray, value, first, last) {
} }
return first; return first;
} }
module.exports = {
fillRange,
lowerBound,
lowerBoundIndirect,
upperBound,
upperBoundIndirect
};
+1 -1
View File
@@ -432,7 +432,7 @@ class DifferentialExpressionAPI(Resource):
def get_api_resources(): def get_api_resources():
bp = Blueprint("api", __name__, url_prefix="/api/v2.0") bp = Blueprint("api", __name__, url_prefix="/api/v0.1")
api = Api(bp, add_api_spec_resource=False) api = Api(bp, add_api_spec_resource=False)
api.add_resource(InitializeAPI, "/initialize") api.add_resource(InitializeAPI, "/initialize")
api.add_resource(CellsAPI, "/cells") api.add_resource(CellsAPI, "/cells")
+1 -1
View File
@@ -8,7 +8,7 @@ class EndPoints(unittest.TestCase):
def setUp(self): def setUp(self):
# Local # Local
self.url_base = "http://0.0.0.0:5005/api/" + "v2.0/" self.url_base = "http://0.0.0.0:5005/api/" + "v0.1/"
self.session = requests.Session() self.session = requests.Session()
def test_cells(self): def test_cells(self):