mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-27 06:18:12 +08:00
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:
@@ -15,7 +15,8 @@ import FaPaintBrush from "react-icons/lib/fa/paint-brush";
|
||||
|
||||
@connect(state => {
|
||||
return {
|
||||
colorAccessor: state.controls.colorAccessor
|
||||
colorAccessor: state.controls.colorAccessor,
|
||||
categoricalAsBooleansMap: state.controls.categoricalAsBooleansMap
|
||||
};
|
||||
})
|
||||
class Category extends React.Component {
|
||||
@@ -26,7 +27,24 @@ class Category extends React.Component {
|
||||
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() {
|
||||
this.props.dispatch({
|
||||
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() {
|
||||
return (
|
||||
<div
|
||||
@@ -102,11 +130,8 @@ class Category extends React.Component {
|
||||
</span>
|
||||
{this.props.metadataField}
|
||||
<input
|
||||
onChange={
|
||||
this.state.isChecked
|
||||
? this.toggleNone.bind(this)
|
||||
: this.toggleAll.bind(this)
|
||||
}
|
||||
onChange={this.handleToggleAllClick.bind(this)}
|
||||
ref={el => this.checkbox = el}
|
||||
checked={this.state.isChecked}
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
@@ -7,8 +7,11 @@ https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { connect } from "react-redux";
|
||||
import FaPaintBrush from "react-icons/lib/fa/paint-brush";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
@connect(state => {
|
||||
console.log("state in histo brush", state)
|
||||
const ranges =
|
||||
state.cells.cells && state.cells.cells.data.ranges
|
||||
? state.cells.cells.data.ranges
|
||||
@@ -26,7 +29,8 @@ import { connect } from "react-redux";
|
||||
return {
|
||||
colorAccessor: state.controls.colorAccessor,
|
||||
colorScale: state.controls.colorScale,
|
||||
cellsMetadata: state.controls.cellsMetadata
|
||||
cellsMetadata: state.controls.cellsMetadata,
|
||||
initializeRanges
|
||||
};
|
||||
})
|
||||
class HistogramBrush extends React.Component {
|
||||
@@ -154,7 +158,7 @@ class HistogramBrush extends React.Component {
|
||||
)
|
||||
.call(d3.axisBottom(x).ticks(5))
|
||||
.append("text")
|
||||
.attr("x", 300)
|
||||
.attr("x", this.width - 2)
|
||||
.attr("y", -6)
|
||||
.attr("fill", "#000")
|
||||
.attr("text-anchor", "end")
|
||||
@@ -164,13 +168,40 @@ class HistogramBrush extends React.Component {
|
||||
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() {
|
||||
return (
|
||||
<div
|
||||
style={{ marginTop: 10 }}
|
||||
style={{ marginTop: 10, position: "relative" }}
|
||||
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
|
||||
width={this.width}
|
||||
height={this.height}
|
||||
|
||||
@@ -103,7 +103,11 @@ class ContinuousLegend extends React.Component {
|
||||
this.state = {};
|
||||
}
|
||||
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. */
|
||||
d3
|
||||
.select("#continuous_legend")
|
||||
|
||||
@@ -164,28 +164,29 @@ class Graph extends React.Component {
|
||||
nextProps.responsive.width !== this.props.responsive.width
|
||||
) {
|
||||
/* clear out whatever was on the div, even if nothing, but usually the brushes etc */
|
||||
d3
|
||||
.select("#graphAttachPoint")
|
||||
d3.select("#graphAttachPoint")
|
||||
.selectAll("*")
|
||||
.remove();
|
||||
const { svg } = setupSVGandBrushElements(
|
||||
const { svg, brush, brushContainer } = setupSVGandBrushElements(
|
||||
this.handleBrushSelectAction.bind(this),
|
||||
this.handleBrushDeselectAction.bind(this),
|
||||
nextProps.responsive,
|
||||
this.graphPaddingTop
|
||||
);
|
||||
this.setState({ svg });
|
||||
this.setState({ svg, brush, brushContainer });
|
||||
}
|
||||
}
|
||||
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
|
||||
but apparently
|
||||
it does
|
||||
https://bl.ocks.org/EfratVil/0e542f5fc426065dd1d4b6daaa345a9f
|
||||
*/
|
||||
const s = d3.event.selection;
|
||||
/*
|
||||
const s = d3.event.selection;
|
||||
/*
|
||||
event describing brush position:
|
||||
@-------|
|
||||
| |
|
||||
@@ -193,33 +194,47 @@ class Graph extends React.Component {
|
||||
|-------@
|
||||
*/
|
||||
|
||||
// compute inverse view matrix
|
||||
const inverse = mat4.invert([], this.state.camera.view());
|
||||
// compute inverse view matrix
|
||||
const inverse = mat4.invert([], this.state.camera.view());
|
||||
|
||||
// transform screen coordinates -> cell coordinates
|
||||
const invert = pin => {
|
||||
const x =
|
||||
2 * pin[0] / (this.props.responsive.height - this.graphPaddingTop) - 1;
|
||||
const y =
|
||||
2 *
|
||||
(1 - pin[1] / (this.props.responsive.height - this.graphPaddingTop)) -
|
||||
1;
|
||||
const pout = [x + inverse[12], y + inverse[13]];
|
||||
return [(pout[0] + 1) / 2, (pout[1] + 1) / 2];
|
||||
};
|
||||
// transform screen coordinates -> cell coordinates
|
||||
const invert = pin => {
|
||||
const x =
|
||||
(2 * pin[0]) / (this.props.responsive.height - this.graphPaddingTop) -
|
||||
1;
|
||||
const y =
|
||||
2 *
|
||||
(1 -
|
||||
pin[1] / (this.props.responsive.height - this.graphPaddingTop)) -
|
||||
1;
|
||||
const pout = [
|
||||
x * inverse[14] + inverse[12],
|
||||
y * inverse[14] + inverse[13]
|
||||
];
|
||||
return [(pout[0] + 1) / 2, (pout[1] + 1) / 2];
|
||||
};
|
||||
|
||||
const brushCoords = {
|
||||
northwest: invert([s[0][0], s[0][1]]),
|
||||
southeast: invert([s[1][0], s[1][1]])
|
||||
};
|
||||
const brushCoords = {
|
||||
northwest: invert([s[0][0], s[0][1]]),
|
||||
southeast: invert([s[1][0], s[1][1]])
|
||||
};
|
||||
|
||||
this.props.dispatch({
|
||||
type: "graph brush selection change",
|
||||
brushCoords
|
||||
});
|
||||
this.props.dispatch({
|
||||
type: "graph brush selection change",
|
||||
brushCoords
|
||||
});
|
||||
}
|
||||
}
|
||||
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({
|
||||
type: "graph brush deselect"
|
||||
});
|
||||
@@ -302,6 +317,7 @@ class Graph extends React.Component {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
this.handleBrushDeselectAction();
|
||||
this.setState({ mode: "zoom" });
|
||||
}}
|
||||
style={{
|
||||
|
||||
@@ -23,21 +23,23 @@ export const setupSVGandBrushElements = (
|
||||
.attr("height", side)
|
||||
.attr("class", `${styles.graphSVG}`);
|
||||
|
||||
svg.append("g").call(
|
||||
d3
|
||||
.brush()
|
||||
.extent([
|
||||
[0, 0],
|
||||
[
|
||||
responsive.height - graphPaddingTop,
|
||||
responsive.height - graphPaddingTop
|
||||
]
|
||||
])
|
||||
.on("brush", handleBrushSelectAction)
|
||||
.on("end", handleBrushDeselectAction)
|
||||
);
|
||||
const brush = d3
|
||||
.brush()
|
||||
.extent([
|
||||
[0, 0],
|
||||
[responsive.height - graphPaddingTop, responsive.height - graphPaddingTop]
|
||||
])
|
||||
.on("brush", handleBrushSelectAction)
|
||||
.on("end", handleBrushDeselectAction);
|
||||
|
||||
const brushContainer = svg
|
||||
.append("g")
|
||||
.attr("class", "graph_brush")
|
||||
.call(brush);
|
||||
|
||||
return {
|
||||
svg
|
||||
svg,
|
||||
brushContainer,
|
||||
brush
|
||||
};
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@ class Scatterplot extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.count = 0;
|
||||
this.axes = false;
|
||||
this.state = {
|
||||
svg: null,
|
||||
// ctx: null,
|
||||
@@ -63,8 +64,27 @@ class Scatterplot extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
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({
|
||||
svg
|
||||
svg,
|
||||
xScale: scales ? scales.xScale : null,
|
||||
yScale: scales ? scales.yScale : null
|
||||
});
|
||||
|
||||
const camera = _camera(this.reglCanvas, { scale: true, rotate: false });
|
||||
@@ -104,21 +124,36 @@ class Scatterplot extends React.Component {
|
||||
});
|
||||
}
|
||||
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) {
|
||||
if (
|
||||
this.state.svg &&
|
||||
this.state.xScale &&
|
||||
this.state.yScale &&
|
||||
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 || // 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 (
|
||||
this.props.metadata &&
|
||||
this.state.regl &&
|
||||
this.state.pointBuffer &&
|
||||
this.state.colorBuffer &&
|
||||
@@ -179,47 +214,32 @@ class Scatterplot extends React.Component {
|
||||
this.count = cellCount;
|
||||
}
|
||||
}
|
||||
maybeSetupScalesAndDrawAxes(nextProps) {
|
||||
if (
|
||||
nextProps.expression &&
|
||||
nextProps.expression.data &&
|
||||
nextProps.scatterplotXXaccessor &&
|
||||
nextProps.scatterplotYYaccessor
|
||||
) {
|
||||
const xScale = d3
|
||||
.scaleLinear()
|
||||
.domain(
|
||||
d3.extent(nextProps.expression.data.cells, (cell, i) => {
|
||||
return cell.e[
|
||||
nextProps.expression.data.genes.indexOf(
|
||||
nextProps.scatterplotXXaccessor
|
||||
)
|
||||
];
|
||||
})
|
||||
)
|
||||
.range([0, width]);
|
||||
setupScales(expression, scatterplotXXaccessor, scatterplotYYaccessor) {
|
||||
const xScale = d3
|
||||
.scaleLinear()
|
||||
.domain(
|
||||
d3.extent(expression.data.cells, (cell, i) => {
|
||||
return cell.e[expression.data.genes.indexOf(scatterplotXXaccessor)];
|
||||
})
|
||||
)
|
||||
.range([0, width]);
|
||||
|
||||
const yScale = d3
|
||||
.scaleLinear()
|
||||
.domain(
|
||||
d3.extent(nextProps.expression.data.cells, cell => {
|
||||
return cell.e[
|
||||
nextProps.expression.data.genes.indexOf(
|
||||
nextProps.scatterplotYYaccessor
|
||||
)
|
||||
];
|
||||
})
|
||||
)
|
||||
.range([height, 0]);
|
||||
const yScale = d3
|
||||
.scaleLinear()
|
||||
.domain(
|
||||
d3.extent(expression.data.cells, cell => {
|
||||
return cell.e[expression.data.genes.indexOf(scatterplotYYaccessor)];
|
||||
})
|
||||
)
|
||||
.range([height, 0]);
|
||||
|
||||
this.setState({
|
||||
xScale,
|
||||
yScale
|
||||
});
|
||||
}
|
||||
return {
|
||||
xScale,
|
||||
yScale
|
||||
};
|
||||
}
|
||||
drawAxesSVG(xScale, yScale) {
|
||||
this.state.svg.selectAll("*").remove();
|
||||
drawAxesSVG(xScale, yScale, svg) {
|
||||
svg.selectAll("*").remove();
|
||||
|
||||
// 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);
|
||||
@@ -227,28 +247,28 @@ class Scatterplot extends React.Component {
|
||||
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.
|
||||
this.state.svg
|
||||
svg
|
||||
.append("g")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.attr("class", "x axis")
|
||||
.call(xAxis);
|
||||
|
||||
// y-axis is translated to (0,0)
|
||||
this.state.svg
|
||||
svg
|
||||
.append("g")
|
||||
.attr("transform", "translate(0,0)")
|
||||
.attr("class", "y axis")
|
||||
.call(yAxis);
|
||||
|
||||
// adding label. For x-axis, it's at (10, 10), and for y-axis at (width, height-10).
|
||||
this.state.svg
|
||||
svg
|
||||
.append("text")
|
||||
.attr("x", 10)
|
||||
.attr("y", 10)
|
||||
.attr("class", "label")
|
||||
.text(this.props.scatterplotYYaccessor);
|
||||
|
||||
this.state.svg
|
||||
svg
|
||||
.append("text")
|
||||
.attr("x", width)
|
||||
.attr("y", height - 10)
|
||||
|
||||
@@ -48,10 +48,10 @@ export const bolder = 700;
|
||||
export let API = {
|
||||
// prefix: "http://api.clustering.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://api-staging.clustering.czi.technology/api/",
|
||||
prefix: "http://api-staging.clustering.czi.technology/api/",
|
||||
version: "v0.1/"
|
||||
};
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -2,7 +2,7 @@
|
||||
import _ from "lodash";
|
||||
import { parseRGB } from "../util/parseRGB";
|
||||
import { createSchemaByDataSniffing } from "../util/schema";
|
||||
var crossfilter = require("../util/typedCrossfilter");
|
||||
import crossfilter from "../util/typedCrossfilter";
|
||||
|
||||
// Deduce the correct crossfilter dimension type from a metadata
|
||||
// schema description.
|
||||
|
||||
@@ -140,7 +140,7 @@ class BitArray {
|
||||
const col = dim >>> 5;
|
||||
const before = this.bitarray[col * this.length + index];
|
||||
const after = before | (1 << (dim % 32));
|
||||
this.bitarray[col] = after;
|
||||
this.bitarray[col * this.length + index] = after;
|
||||
}
|
||||
|
||||
// deselect index on dimension
|
||||
@@ -149,7 +149,7 @@ class BitArray {
|
||||
const col = dim >>> 5;
|
||||
const before = this.bitarray[col * this.length + index];
|
||||
const after = before & ~(1 << (dim % 32));
|
||||
this.bitarray[col] = after;
|
||||
this.bitarray[col * this.length + index] = after;
|
||||
}
|
||||
|
||||
// select all indices on dimension.
|
||||
@@ -214,7 +214,8 @@ class BitArray {
|
||||
const bitmask = this.bitmask[0];
|
||||
const bitarray = this.bitarray;
|
||||
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 {
|
||||
for (let i = 0, len = this.length; i < len; i++) {
|
||||
@@ -225,4 +226,4 @@ class BitArray {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BitArray;
|
||||
export default BitArray;
|
||||
|
||||
@@ -29,9 +29,9 @@ more complex API. In a few cases, elements of that API were incorporated.
|
||||
|
||||
*/
|
||||
|
||||
var PositiveIntervals = require("./positiveIntervals");
|
||||
var BitArray = require("./bitArray");
|
||||
var Util = require("./util");
|
||||
import PositiveIntervals from "./positiveIntervals";
|
||||
import BitArray from "./bitArray";
|
||||
import {fillRange, lowerBound, lowerBoundIndirect, upperBound, upperBoundIndirect} from "./util";
|
||||
|
||||
class TypedCrossfilter {
|
||||
constructor(data) {
|
||||
@@ -65,7 +65,7 @@ class TypedCrossfilter {
|
||||
|
||||
_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
|
||||
@@ -105,7 +105,7 @@ class TypedCrossfilter {
|
||||
class ScalarDimension {
|
||||
constructor(value, valueArrayType, crossfilter, id) {
|
||||
this.crossfilter = crossfilter;
|
||||
this.id = id;
|
||||
this._id = id;
|
||||
|
||||
// current selection filter, expressed as PostiveIntervals.
|
||||
this.currentFilter = [];
|
||||
@@ -118,7 +118,7 @@ class ScalarDimension {
|
||||
this.value = array;
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
@@ -133,11 +133,11 @@ class ScalarDimension {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.crossfilter._freeDimension(this.id);
|
||||
this.crossfilter._freeDimension(this._id);
|
||||
}
|
||||
|
||||
id() {
|
||||
return this.id;
|
||||
return this._id;
|
||||
}
|
||||
|
||||
_updateFilters(newFilter) {
|
||||
@@ -147,26 +147,26 @@ class ScalarDimension {
|
||||
// more complex work and just clobber everything.
|
||||
//
|
||||
if (newFilter.length === 0) {
|
||||
this.crossfilter.selection.deselectAll(this.id);
|
||||
this.crossfilter.selection.deselectAll(this._id);
|
||||
} else if (
|
||||
newFilter.length === 1 &&
|
||||
newFilter[0][0] === 0 &&
|
||||
newFilter[0][1] == this.index.length
|
||||
) {
|
||||
this.crossfilter.selection.selectAll(this.id);
|
||||
this.crossfilter.selection.selectAll(this._id);
|
||||
} else {
|
||||
const adds = PositiveIntervals.difference(newFilter, this.currentFilter);
|
||||
const dels = PositiveIntervals.difference(this.currentFilter, newFilter);
|
||||
dels.forEach(interval =>
|
||||
this.crossfilter.selection.deselectIndirectFromRange(
|
||||
this.id,
|
||||
this._id,
|
||||
this.index,
|
||||
interval
|
||||
)
|
||||
);
|
||||
adds.forEach(interval =>
|
||||
this.crossfilter.selection.selectIndirectFromRange(
|
||||
this.id,
|
||||
this._id,
|
||||
this.index,
|
||||
interval
|
||||
)
|
||||
@@ -179,14 +179,14 @@ class ScalarDimension {
|
||||
// filter by value - exact match
|
||||
filterExact(value) {
|
||||
const newFilter = [
|
||||
Util.lowerBoundIndirect(
|
||||
lowerBoundIndirect(
|
||||
this.value,
|
||||
this.index,
|
||||
value,
|
||||
0,
|
||||
this.value.length
|
||||
),
|
||||
Util.upperBoundIndirect(
|
||||
upperBoundIndirect(
|
||||
this.value,
|
||||
this.index,
|
||||
value,
|
||||
@@ -207,14 +207,14 @@ class ScalarDimension {
|
||||
const newFilter = [];
|
||||
for (let v = 0, len = values.length; v < len; v++) {
|
||||
const intv = [
|
||||
Util.lowerBoundIndirect(
|
||||
lowerBoundIndirect(
|
||||
this.value,
|
||||
this.index,
|
||||
values[v],
|
||||
0,
|
||||
this.value.length
|
||||
),
|
||||
Util.upperBoundIndirect(
|
||||
upperBoundIndirect(
|
||||
this.value,
|
||||
this.index,
|
||||
values[v],
|
||||
@@ -233,14 +233,14 @@ class ScalarDimension {
|
||||
filterRange(range) {
|
||||
const newFilter = [];
|
||||
const intv = [
|
||||
Util.lowerBoundIndirect(
|
||||
lowerBoundIndirect(
|
||||
this.value,
|
||||
this.index,
|
||||
range[0],
|
||||
0,
|
||||
this.value.length
|
||||
),
|
||||
Util.upperBoundIndirect(
|
||||
upperBoundIndirect(
|
||||
this.value,
|
||||
this.index,
|
||||
range[1],
|
||||
@@ -350,7 +350,7 @@ class EnumDimension extends ScalarDimension {
|
||||
const enumLen = this.enumIndex.length;
|
||||
for (let i = 0; i < len; 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;
|
||||
}
|
||||
return array;
|
||||
@@ -358,14 +358,14 @@ class EnumDimension extends ScalarDimension {
|
||||
|
||||
filterExact(value) {
|
||||
return super.filterExact(
|
||||
Util.lowerBound(this.enumIndex, value, 0, this.enumIndex.length)
|
||||
lowerBound(this.enumIndex, value, 0, this.enumIndex.length)
|
||||
);
|
||||
}
|
||||
|
||||
filterEnum(values) {
|
||||
return super.filterEnum(
|
||||
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) {
|
||||
return super.filterEnum(
|
||||
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.EnumDimension = EnumDimension;
|
||||
|
||||
module.exports = crossfilter;
|
||||
export default crossfilter;
|
||||
|
||||
@@ -129,4 +129,4 @@ class PositiveIntervals {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PositiveIntervals;
|
||||
export default PositiveIntervals;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// fill an array or typedarray with a sequential range of numbers,
|
||||
// starting with `start`
|
||||
//
|
||||
function fillRange(arr, start = 0) {
|
||||
export function fillRange(arr, start = 0) {
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
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 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
|
||||
while (first < last) {
|
||||
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.
|
||||
//
|
||||
function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
export function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
// this is just a binary search
|
||||
while (first < last) {
|
||||
const middle = (first + last) >>> 1;
|
||||
@@ -69,7 +69,7 @@ function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
// C++: upper_bound()
|
||||
// Python: bisect.bisect_right()
|
||||
//
|
||||
function upperBound(valueArray, value, first, last) {
|
||||
export function upperBound(valueArray, value, first, last) {
|
||||
// this is just a binary search
|
||||
while (first < last) {
|
||||
const middle = (first + last) >>> 1;
|
||||
@@ -84,7 +84,7 @@ function upperBound(valueArray, value, first, last) {
|
||||
|
||||
// Inline performance optimization
|
||||
//
|
||||
function upperBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
export function upperBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
// this is just a binary search
|
||||
while (first < last) {
|
||||
const middle = (first + last) >>> 1;
|
||||
@@ -96,11 +96,3 @@ function upperBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fillRange,
|
||||
lowerBound,
|
||||
lowerBoundIndirect,
|
||||
upperBound,
|
||||
upperBoundIndirect
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user