diff --git a/client/__tests__/util/bitArray.test.js b/client/__tests__/util/bitArray.test.js
new file mode 100644
index 00000000..72e91882
--- /dev/null
+++ b/client/__tests__/util/bitArray.test.js
@@ -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);
+ });
+});
diff --git a/client/__tests__/util/positiveInterval.test.js b/client/__tests__/util/positiveInterval.test.js
new file mode 100644
index 00000000..81c44496
--- /dev/null
+++ b/client/__tests__/util/positiveInterval.test.js
@@ -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]
+ ]);
+ });
+});
diff --git a/client/__tests__/util/typedCrossfilter.test.js b/client/__tests__/util/typedCrossfilter.test.js
new file mode 100644
index 00000000..00e68495
--- /dev/null
+++ b/client/__tests__/util/typedCrossfilter.test.js
@@ -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);
+ });
+});
diff --git a/client/package.json b/client/package.json
index 03568a9c..712021ed 100644
--- a/client/package.json
+++ b/client/package.json
@@ -9,7 +9,8 @@
"clean": "rimraf build",
"start": "node server/development.js",
"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,
"engines": {
@@ -90,6 +91,7 @@
"gzip-size": "^3.0.0",
"html-webpack-inline-source-plugin": "0.0.6",
"html-webpack-plugin": "^2.22.0",
+ "jest": "^23.0.1",
"jsdom": "^9.4.1",
"json-loader": "^0.5.4",
"nyc": "^10.0.0",
@@ -106,5 +108,10 @@
"webpack-dev-middleware": "^1.6.1",
"webpack-hot-middleware": "^2.12.2",
"whatwg-fetch": "^2.0.1"
+ },
+ "jest": {
+ "testMatch": [
+ "**/__tests__/**/?(*.)(spec|test).js?(x)"
+ ]
}
}
diff --git a/client/src/components/categorical/categorical.js b/client/src/components/categorical/categorical.js
index 1b04c388..997b3889 100644
--- a/client/src/components/categorical/categorical.js
+++ b/client/src/components/categorical/categorical.js
@@ -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 (
{this.props.metadataField}
this.checkbox = el}
checked={this.state.isChecked}
type="checkbox"
/>
diff --git a/client/src/components/continuous/histogramBrush.js b/client/src/components/continuous/histogramBrush.js
index 1b67e687..17ab9cec 100644
--- a/client/src/components/continuous/histogramBrush.js
+++ b/client/src/components/continuous/histogramBrush.js
@@ -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 (
+
+
+