From 846b8d15bd00f8aa8e834095bb5b433e2f32dcc7 Mon Sep 17 00:00:00 2001 From: Bruce Martin Date: Mon, 6 May 2019 17:28:32 -0700 Subject: [PATCH] lodash cleanup (#747) * add own range() function * lodash cleanup * remove redundant fill range implementations * remove use of _.get * sync test babel config with build * update tests to match new range implementation --- client/__tests__/util/range.test.js | 42 +++++++++++++++++ .../util/typedCrossfilter/util.test.js | 2 +- client/package.json | 4 +- .../src/components/categorical/occupancy.js | 5 +-- client/src/components/categorical/util.js | 3 +- client/src/components/categorical/value.js | 7 +-- .../src/components/continuous/continuous.js | 4 +- client/src/components/geneExpression/index.js | 2 +- client/src/components/leftsidebar.js | 3 +- client/src/reducers/categoricalSelection.js | 7 +-- client/src/util/dataframe/labelIndex.js | 10 +---- client/src/util/range.js | 45 +++++++++++++++++++ client/src/util/stateManager/colorHelpers.js | 10 ++--- .../src/util/stateManager/controlsHelpers.js | 2 +- client/src/util/stateManager/universe.js | 6 +-- client/src/util/typedCrossfilter/util.js | 12 +---- 16 files changed, 112 insertions(+), 52 deletions(-) create mode 100644 client/__tests__/util/range.test.js create mode 100644 client/src/util/range.js diff --git a/client/__tests__/util/range.test.js b/client/__tests__/util/range.test.js new file mode 100644 index 00000000..0567c9eb --- /dev/null +++ b/client/__tests__/util/range.test.js @@ -0,0 +1,42 @@ +import { range, rangeFill } from "../../src/util/range"; + +describe("range", () => { + test("no defaults", () => { + expect(range(0, 3, 1)).toMatchObject([0, 1, 2]); + }); + + test("range(stop)", () => { + expect(range(3)).toMatchObject([0, 1, 2]); + expect(range(0)).toMatchObject([]); + expect(range(1)).toMatchObject([0]); + }); + + test("range(start,stop)", () => { + expect(range(0, 0)).toMatchObject([]); + expect(range(0, 2)).toMatchObject([0, 1]); + expect(range(4, 8)).toMatchObject([4, 5, 6, 7]); + }); + + test("range(start, stop, step", () => { + expect(range(4, 0, -1)).toMatchObject([4, 3, 2, 1]); + expect(range(0, 4, 2)).toMatchObject([0, 2]); + }); +}); + +describe("rangefill", () => { + test("rangeFill(arr)", () => { + expect(rangeFill(new Int32Array(3))).toMatchObject( + new Int32Array([0, 1, 2]) + ); + }); + test("rangeFill(arr, start)", () => { + expect(rangeFill(new Int32Array(2), 1)).toMatchObject( + new Int32Array([1, 2]) + ); + }); + test("rangeFill(arr, start, step)", () => { + expect(rangeFill(new Int32Array(3), 2, -1)).toMatchObject( + new Int32Array([2, 1, 0]) + ); + }); +}); diff --git a/client/__tests__/util/typedCrossfilter/util.test.js b/client/__tests__/util/typedCrossfilter/util.test.js index 39ae43d2..33b026d9 100644 --- a/client/__tests__/util/typedCrossfilter/util.test.js +++ b/client/__tests__/util/typedCrossfilter/util.test.js @@ -1,8 +1,8 @@ import { - fillRange, sliceByIndex, makeSortIndex } from "../../../src/util/typedCrossfilter/util"; +import { rangeFill as fillRange } from "../../../src/util/range"; describe("fillRange", () => { test("Array", () => { diff --git a/client/package.json b/client/package.json index 25ad3496..3e593d6d 100644 --- a/client/package.json +++ b/client/package.json @@ -142,7 +142,9 @@ ], "@babel/plugin-proposal-export-namespace-from", "@babel/plugin-transform-react-constant-elements", - "@babel/plugin-transform-runtime" + "@babel/plugin-transform-runtime", + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-proposal-nullish-coalescing-operator" ] } } diff --git a/client/src/components/categorical/occupancy.js b/client/src/components/categorical/occupancy.js index 8b25866a..7c6bf0e0 100644 --- a/client/src/components/categorical/occupancy.js +++ b/client/src/components/categorical/occupancy.js @@ -1,6 +1,5 @@ // jshint esversion: 6 import React from "react"; -import _ from "lodash"; import { connect } from "react-redux"; import * as d3 from "d3"; @@ -17,9 +16,7 @@ class Occupancy extends React.Component { const width = 100; const height = 11; - const categories = _.filter(schema.annotations.obs, { - name: colorAccessor - })[0].categories; + const categories = schema.annotations.obsByName[colorAccessor]?.categories; const x = d3 .scaleLinear() diff --git a/client/src/components/categorical/util.js b/client/src/components/categorical/util.js index e645a5e4..086f2f65 100644 --- a/client/src/components/categorical/util.js +++ b/client/src/components/categorical/util.js @@ -5,7 +5,6 @@ // return sorted index import isNumber from "is-number"; -import _ from "lodash"; const sortedCategoryValues = values => { /* this sort could be memoized for perf */ @@ -13,7 +12,7 @@ const sortedCategoryValues = values => { const strings = []; const ints = []; - _.forEach(values, v => { + values.forEach(v => { if (isNumber(v[0])) { ints.push(v); } else { diff --git a/client/src/components/categorical/value.js b/client/src/components/categorical/value.js index f4309753..bb16bb85 100644 --- a/client/src/components/categorical/value.js +++ b/client/src/components/categorical/value.js @@ -1,7 +1,6 @@ // jshint esversion: 6 import { connect } from "react-redux"; import React from "react"; -import _ from "lodash"; import Occupancy from "./occupancy"; import { countCategoryValues2D } from "../../util/stateManager/worldUtil"; import * as globals from "../../globals"; @@ -10,7 +9,7 @@ import * as globals from "../../globals"; categoricalSelection: state.categoricalSelection, colorScale: state.colors.scale, colorAccessor: state.colors.colorAccessor, - schema: _.get(state.world, "schema", null), + schema: state.world?.schema, world: state.world })) class CategoryValue extends React.Component { @@ -60,9 +59,7 @@ class CategoryValue extends React.Component { let occupancy = null; if (isColorBy && schema) { - categories = _.filter(schema.annotations.obs, { - name: colorAccessor - })[0].categories; + categories = schema.annotations.obsByName[colorAccessor]?.categories; } if (colorAccessor && !isColorBy && categoricalSelection[colorAccessor]) { diff --git a/client/src/components/continuous/continuous.js b/client/src/components/continuous/continuous.js index 8e8438ba..6c56376b 100644 --- a/client/src/components/continuous/continuous.js +++ b/client/src/components/continuous/continuous.js @@ -9,10 +9,10 @@ import * as globals from "../../globals"; import HistogramBrush from "../brushableHistogram"; @connect(state => ({ - obsAnnotations: _.get(state.world, "obsAnnotations", null), + obsAnnotations: state.world?.obsAnnotations, colorAccessor: state.colors.colorAccessor, colorScale: state.colors.scale, - schema: _.get(state.world, "schema", null) + schema: state.world?.schema })) class Continuous extends React.Component { constructor(props) { diff --git a/client/src/components/geneExpression/index.js b/client/src/components/geneExpression/index.js index ee1653a6..023d848d 100644 --- a/client/src/components/geneExpression/index.js +++ b/client/src/components/geneExpression/index.js @@ -57,7 +57,7 @@ const filterGenes = (query, genes) => @connect(state => { return { - obsAnnotations: _.get(state.world, "obsAnnotations", null), + obsAnnotations: state.world?.obsAnnotations, userDefinedGenes: state.controls.userDefinedGenes, userDefinedGenesLoading: state.controls.userDefinedGenesLoading, world: state.world, diff --git a/client/src/components/leftsidebar.js b/client/src/components/leftsidebar.js index 0b5931be..93263155 100644 --- a/client/src/components/leftsidebar.js +++ b/client/src/components/leftsidebar.js @@ -1,5 +1,4 @@ // jshint esversion: 6 -import _ from "lodash"; import React from "react"; import { connect } from "react-redux"; import Categorical from "./categorical/categorical"; @@ -10,7 +9,7 @@ import DynamicScatterplot from "./scatterplot/scatterplot"; @connect(state => ({ responsive: state.responsive, - datasetTitle: _.get(state.config, "displayNames.dataset"), + datasetTitle: state.config?.displayNames?.dataset, scatterplotXXaccessor: state.controls.scatterplotXXaccessor, scatterplotYYaccessor: state.controls.scatterplotYYaccessor })) diff --git a/client/src/reducers/categoricalSelection.js b/client/src/reducers/categoricalSelection.js index 170ce8d4..d7779d2c 100644 --- a/client/src/reducers/categoricalSelection.js +++ b/client/src/reducers/categoricalSelection.js @@ -1,12 +1,9 @@ -import _ from "lodash"; - import { ControlsHelpers } from "../util/stateManager"; import * as globals from "../globals"; function maxCategoryItems(state) { - return _.get( - state.config, - "parameters.max-category-items", + return ( + state.config.parameters?.["max-category-items"] ?? globals.configDefaults.parameters["max-category-items"] ); } diff --git a/client/src/util/dataframe/labelIndex.js b/client/src/util/dataframe/labelIndex.js index 2b31da65..b10a8824 100644 --- a/client/src/util/dataframe/labelIndex.js +++ b/client/src/util/dataframe/labelIndex.js @@ -3,6 +3,8 @@ Label indexing - map a label to & from an integer offset. See Dataframe for how this is used. **/ +import { rangeFill as fillRange } from "../range"; + /* Private utility functions */ @@ -21,14 +23,6 @@ function extent(tarr) { return [min, max]; } -function fillRange(arr, start = 0) { - const larr = arr; - for (let i = 0, l = larr.length; i < l; i += 1) { - larr[i] = i + start; - } - return larr; -} - /* eslint-disable class-methods-use-this */ class IdentityInt32Index { /* diff --git a/client/src/util/range.js b/client/src/util/range.js new file mode 100644 index 00000000..972a6276 --- /dev/null +++ b/client/src/util/range.js @@ -0,0 +1,45 @@ +/* +Array range creation + +range(start, stop, step) -> Array + This is identical to https://docs.python.org/3/library/functions.html#func-range + Returns new array filled with a range of numbers. + + Usage: + + range(stop) - start defaults to zero, step defaults to 1 + range(start, stop, [step]) - step defaults to 1 + + Examples: + range(3) -> [0, 1, 2] + range(1, 3) -> [1, 2] + range(1, 5, 2) -> [1, 3] + + +rangeFill(array, start, step) -> array + Fill entire array with values, from start, by step. Returns first array. + start defaults to zero, step defaults to one. + +*/ + +function _doFill(arr, start, step, count) { + for (let idx = 0, val = start; idx < count; idx += 1, val += step) { + arr[idx] = val; + } + return arr; +} + +export function rangeFill(arr, start = 0, step = 1) { + return _doFill(arr, start, step, arr.length); +} + +export function range(start, stop, step) { + if (start === undefined) return []; + if (stop === undefined) { + stop = start; + start = 0; + } + step = step || 1; // catch undefind and zero + const len = Math.max(Math.ceil((stop - start) / step), 0); + return _doFill(new Array(len), start, step, len); +} diff --git a/client/src/util/stateManager/colorHelpers.js b/client/src/util/stateManager/colorHelpers.js index ced86459..f41bae7b 100644 --- a/client/src/util/stateManager/colorHelpers.js +++ b/client/src/util/stateManager/colorHelpers.js @@ -1,12 +1,12 @@ /* Helper functions for the embedded graph colors */ -import _ from "lodash"; import * as d3 from "d3"; import { interpolateRainbow, interpolateCool } from "d3-scale-chromatic"; import * as globals from "../../globals"; import parseRGB from "../parseRGB"; import finiteExtent from "../finiteExtent"; +import { range } from "../range"; /* create new colors state object. Paramters: @@ -37,9 +37,7 @@ function createColors(world, colorMode = null, colorAccessor = null) { } function createColorsByCategoricalMetadata(world, accessor) { - const { categories } = _.filter(world.schema.annotations.obs, { - name: accessor - })[0]; + const { categories } = world.schema.annotations.obsByName[accessor]; const scale = d3 .scaleSequential(interpolateRainbow) @@ -67,7 +65,7 @@ function createColorsByContinuousMetadata(world, accessor) { const scale = d3 .scaleQuantile() .domain([min, max]) - .range(_.range(colorBins - 1, -1, -1)); + .range(range(colorBins - 1, -1, -1)); /* pre-create colors - much faster than doing it for each obs */ const colors = new Array(colorBins); @@ -97,7 +95,7 @@ function createColorsByExpression(world, accessor) { const scale = d3 .scaleQuantile() .domain([min, max]) - .range(_.range(colorBins - 1, -1, -1)); + .range(range(colorBins - 1, -1, -1)); /* pre-create colors - much faster than doing it for each obs */ const colors = new Array(colorBins); diff --git a/client/src/util/stateManager/controlsHelpers.js b/client/src/util/stateManager/controlsHelpers.js index e4c8e92e..fa5f1e82 100644 --- a/client/src/util/stateManager/controlsHelpers.js +++ b/client/src/util/stateManager/controlsHelpers.js @@ -5,7 +5,7 @@ Helper functions for the controls reducer import _ from "lodash"; import * as globals from "../../globals"; -import { fillRange } from "../typedCrossfilter/util"; +import { rangeFill as fillRange } from "../range"; import { userDefinedDimensionName, diffexpDimensionName diff --git a/client/src/util/stateManager/universe.js b/client/src/util/stateManager/universe.js index 12e34643..014fb231 100644 --- a/client/src/util/stateManager/universe.js +++ b/client/src/util/stateManager/universe.js @@ -122,15 +122,15 @@ function reconcileSchemaCategoriesWithSummary(universe) { cases, add a 'categories' field to the schema so it is accessible. */ - _.forEach(universe.schema.annotations.obs, s => { + universe.schema.annotations.obs.forEach(s => { if ( s.type === "string" || s.type === "boolean" || s.type === "categorical" ) { const categories = _.union( - _.get(s, "categories", []), - _.get(universe.obsAnnotations.col(s.name).summarize(), "categories", []) + s.categories ?? [], + universe.obsAnnotations.col(s.name).summarize().categories ?? [] ); s.categories = categories; } diff --git a/client/src/util/typedCrossfilter/util.js b/client/src/util/typedCrossfilter/util.js index 0c994d77..4bd62940 100644 --- a/client/src/util/typedCrossfilter/util.js +++ b/client/src/util/typedCrossfilter/util.js @@ -1,22 +1,12 @@ // jshint esversion: 6 import { sortIndex } from "./sort"; +import { rangeFill as fillRange } from "../range"; /* Utility functions, private to this module. */ -// fill an array or typedarray with a sequential range of numbers, -// starting with `start` -// -export function fillRange(arr, start = 0) { - const larr = arr; - for (let i = 0, len = larr.length; i < len; i += 1) { - larr[i] = i + start; - } - return larr; -} - // slice out of one array into another, using an index array // export function sliceByIndex(src, index) {