mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-06 09:58:11 +08:00
Redux refactor (#1571)
* refactor categorical controls state * lint * fix race condition in tests * fix typo * add missing update on subset * remove obsolete code * update jest and puppeteer major version; update all minors * update when label changes * remove lint from tests; increase timeouts in e2e tests * initial refactoring to new async annomatrix * refine error handling * fix bad merge * add continuous legend * lint * fix memoization in color table creators * partial implementation of user defined annotations * add new annotations action creator file * first pass at user annotations * additional user annotation bug fixes * user annotation auto-save * unit test cleanup * lint * refactor into multiple files * cleanup * add column GC * fix several bugs in user annotations * remove debug code * no anonymous functions * undo redo cleanup * file cleanup * scatterplot * performance * cleanup * remove old code * render in parallel with load * fix race condition * simply graph rendering * render throttle DRY * fix category label order * fix typo in e2e test setup * re-fix the e2e test setup * be more tolerant of races * anno matrix unit tests * temp disable reembedding * pilot port continuous histo to react-async * name change * lint * fix repaint bug * typo fix * update snap to match new ids * world/universe name cleanup * move annoMatrix to src dir * use private underscore naming convention * fix corner case in all selected * name cleanup * add layout control * init edge case * lint * port scatterplot * fix label indexing bug and improve tests * port category to react-async * fix user annotation labelling while subset * select all of prev layout on layout switch * fix race with crossfilter update * prettier lint * fix misleading comment * fix url composition in loader * first pass at crossfilter tests * lint * lint * fix typo * improved error handling for network errors * fix memoization bug * add memo * refactor for performnce * add missing single-value handling in select exact parser * small bugs discovered by tests * lint * additional crossfilter unit tests * remove extraneous comment * add support for automatic category determination * lint * fix render bug in category * take advantage of schema categories guarantee * lint * do not clear history when resetting * enhanced annomatrix gc * lint * finish renaming to follow conventions; fix clone race bug * lint * add priority based loading to improve initial data load UX * crossfilter cache perf * perf tuning * remove timers * documentation * PR review changes * PR review changes * more PR review edits * improve clarity of comment * more PR review fixes * port centroidLabels to use react-async * remove dead code * pr review updates * oops, remove logging
This commit is contained in:
@@ -2,14 +2,14 @@
|
||||
|
||||
exports[`annotations stacked bar graph renders 1`] = `
|
||||
Array [
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-flip-id=\\"1\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2132</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-flip-id=\\"1\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2132</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`annotations stacked bar graph renders 2`] = `
|
||||
Array [
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-flip-id=\\"1\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-flip-id=\\"1\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -304,7 +304,7 @@ describe.each([
|
||||
node.getAttribute("aria-label")
|
||||
);
|
||||
|
||||
expect(result).toBe(categoryName);
|
||||
return expect(result).toBe(categoryName);
|
||||
}
|
||||
|
||||
async function assertLabelExists(categoryName, labelName) {
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
// these TWO statements MUST be first in the file, before any other imports
|
||||
import { enableFetchMocks } from "jest-fetch-mock";
|
||||
import * as serverMocks from "./serverMocks";
|
||||
// OK, continue on!
|
||||
|
||||
import {
|
||||
AnnoMatrixLoader,
|
||||
clip,
|
||||
isubset,
|
||||
isubsetMask,
|
||||
} from "../../../src/annoMatrix";
|
||||
import { Dataframe } from "../../../src/util/dataframe";
|
||||
|
||||
enableFetchMocks();
|
||||
|
||||
describe("AnnoMatrix", () => {
|
||||
let annoMatrix;
|
||||
|
||||
beforeEach(async () => {
|
||||
fetch.resetMocks(); // reset all fetch mocking state
|
||||
annoMatrix = new AnnoMatrixLoader(
|
||||
serverMocks.baseDataURL,
|
||||
serverMocks.schema.schema
|
||||
);
|
||||
});
|
||||
|
||||
describe("basics", () => {
|
||||
test("annomatrix static checks", () => {
|
||||
expect(annoMatrix).toBeDefined();
|
||||
expect(annoMatrix.schema).toMatchObject(serverMocks.schema.schema);
|
||||
expect(annoMatrix.nObs).toEqual(serverMocks.schema.schema.dataframe.nObs);
|
||||
expect(annoMatrix.nVar).toEqual(serverMocks.schema.schema.dataframe.nVar);
|
||||
expect(annoMatrix.isView).toBeFalsy();
|
||||
expect(annoMatrix.viewOf).toBeUndefined();
|
||||
expect(annoMatrix.rowIndex).toBeDefined();
|
||||
});
|
||||
|
||||
test("simple single column fetch", async () => {
|
||||
fetch.once(serverMocks.annotationsObs(["name_0"]));
|
||||
|
||||
const df = await annoMatrix.fetch("obs", "name_0");
|
||||
expect(df).toBeInstanceOf(Dataframe);
|
||||
expect(df.colIndex.labels()).toEqual(["name_0"]);
|
||||
expect(df.dims).toEqual([annoMatrix.nObs, 1]);
|
||||
});
|
||||
|
||||
test("simple multi column fetch", async () => {
|
||||
fetch
|
||||
.once(serverMocks.annotationsObs(["name_0"]))
|
||||
.once(serverMocks.annotationsObs(["n_genes"]));
|
||||
|
||||
await expect(
|
||||
annoMatrix.fetch("obs", ["name_0", "n_genes"])
|
||||
).resolves.toBeInstanceOf(Dataframe);
|
||||
});
|
||||
|
||||
describe("fetch from field", () => {
|
||||
const getLastTwo = async (field) => {
|
||||
const columnNames = annoMatrix.getMatrixColumns(field).slice(-2);
|
||||
fetch.mockResponses(...columnNames.map(() => serverMocks.responder));
|
||||
await expect(
|
||||
annoMatrix.fetch(field, columnNames)
|
||||
).resolves.toBeInstanceOf(Dataframe);
|
||||
};
|
||||
|
||||
test("obs", async () => getLastTwo("obs"));
|
||||
test("var", async () => getLastTwo("var"));
|
||||
test("emb", async () => getLastTwo("emb"));
|
||||
});
|
||||
|
||||
test("fetch - test all query forms", async () => {
|
||||
// single string is a column name
|
||||
fetch.once(serverMocks.annotationsObs(["n_genes"]));
|
||||
await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf(
|
||||
Dataframe
|
||||
);
|
||||
|
||||
// array of column names, expecting n_genes to be cached.
|
||||
fetch.once(serverMocks.annotationsObs(["percent_mito"]));
|
||||
await expect(
|
||||
annoMatrix.fetch("obs", ["n_genes", "percent_mito"])
|
||||
).resolves.toBeInstanceOf(Dataframe);
|
||||
|
||||
// more complex value filter query, enumerated
|
||||
fetch.once(serverMocks.responder);
|
||||
await expect(
|
||||
annoMatrix.fetch("X", {
|
||||
field: "var",
|
||||
column: annoMatrix.schema.annotations.var.index,
|
||||
value: "TYMP",
|
||||
})
|
||||
).resolves.toBeInstanceOf(Dataframe);
|
||||
|
||||
// more complex value filter query, range
|
||||
const varIndex = annoMatrix.schema.annotations.var.index;
|
||||
fetch
|
||||
.once(
|
||||
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]])
|
||||
)
|
||||
.once(
|
||||
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "TYMP"]])
|
||||
);
|
||||
await expect(
|
||||
annoMatrix.fetch("X", [
|
||||
{
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: "SUMO3",
|
||||
},
|
||||
{
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: "TYMP",
|
||||
},
|
||||
])
|
||||
).resolves.toBeInstanceOf(Dataframe);
|
||||
// XXX inspect the wherecache?
|
||||
});
|
||||
|
||||
test("push and pop views", async () => {
|
||||
const am1 = clip(annoMatrix, 0.1, 0.9);
|
||||
expect(am1.viewOf).toBe(annoMatrix);
|
||||
expect(am1.nObs).toEqual(annoMatrix.nObs);
|
||||
expect(am1.nVar).toEqual(annoMatrix.nVar);
|
||||
expect(am1.rowIndex).toBe(annoMatrix.rowIndex);
|
||||
|
||||
const am2 = clip(annoMatrix, 0.1, 0.9);
|
||||
expect(am2.viewOf).toBe(annoMatrix);
|
||||
expect(am2).not.toBe(am1);
|
||||
expect(am2.rowIndex).toBe(annoMatrix.rowIndex);
|
||||
});
|
||||
|
||||
test("schema accessors", () => {
|
||||
expect(annoMatrix.getMatrixFields()).toEqual(
|
||||
expect.arrayContaining(["X", "obs", "emb", "var"])
|
||||
);
|
||||
expect(annoMatrix.getMatrixColumns("obs")).toEqual(
|
||||
expect.arrayContaining(["name_0", "n_genes", "louvain"])
|
||||
);
|
||||
expect(annoMatrix.getColumnSchema("emb", "umap")).toEqual({
|
||||
name: "umap",
|
||||
dims: ["umap_0", "umap_1"],
|
||||
type: "float32",
|
||||
});
|
||||
expect(annoMatrix.getColumnDimensions("emb", "umap")).toEqual([
|
||||
"umap_0",
|
||||
"umap_1",
|
||||
]);
|
||||
});
|
||||
|
||||
/*
|
||||
test the mask & label access to subset via isubset and isubsetMask
|
||||
*/
|
||||
test("isubset", async () => {
|
||||
const rowList = [0, 10];
|
||||
const rowMask = new Uint8Array(annoMatrix.nObs);
|
||||
for (let i = 0; i < rowList.length; i += 1) {
|
||||
rowMask[rowList[i]] = 1;
|
||||
}
|
||||
|
||||
const am1 = isubset(annoMatrix, rowList);
|
||||
const am2 = isubsetMask(annoMatrix, rowMask);
|
||||
expect(am1).not.toBe(am2);
|
||||
expect(am1.nObs).toEqual(2);
|
||||
expect(am1.nObs).toEqual(am2.nObs);
|
||||
expect(am1.nVar).toEqual(am2.nVar);
|
||||
|
||||
fetch
|
||||
.once(serverMocks.annotationsObs(["n_genes"]))
|
||||
.once(serverMocks.annotationsObs(["n_genes"]));
|
||||
const ng1 = await am1.fetch("obs", "n_genes");
|
||||
const ng2 = await am2.fetch("obs", "n_genes");
|
||||
expect(ng1).toHaveLength(ng2.length);
|
||||
expect(ng1.colIndex.labels()).toEqual(ng2.colIndex.labels());
|
||||
expect(ng1.col("n_genes").asArray()).toEqual(
|
||||
ng2.col("n_genes").asArray()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("add/drop column", () => {
|
||||
async function addDrop(base) {
|
||||
expect(base.getMatrixColumns("obs")).not.toContain("foo");
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
await expect(base.fetch("obs", "foo")).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
|
||||
/* add */
|
||||
const am1 = base.addObsColumn(
|
||||
{ name: "foo", type: "float32", writable: true },
|
||||
Float32Array,
|
||||
0
|
||||
);
|
||||
expect(base.getMatrixColumns("obs")).not.toContain("foo");
|
||||
expect(am1.getMatrixColumns("obs")).toContain("foo");
|
||||
const foo = await am1.fetch("obs", "foo");
|
||||
expect(foo).toBeDefined();
|
||||
expect(foo).toBeInstanceOf(Dataframe);
|
||||
expect(foo).toHaveLength(am1.nObs);
|
||||
expect(foo.col("foo").asArray()).toEqual(
|
||||
new Float32Array(am1.nObs).fill(0)
|
||||
);
|
||||
|
||||
/* drop */
|
||||
const am2 = am1.dropObsColumn("foo");
|
||||
expect(base.getMatrixColumns("obs")).not.toContain("foo");
|
||||
expect(am2.getMatrixColumns("obs")).not.toContain("foo");
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
await expect(am2.fetch("obs", "foo")).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
}
|
||||
|
||||
test("add/drop column, without view", async () => {
|
||||
await addDrop(annoMatrix);
|
||||
});
|
||||
|
||||
test("add/drop column, with view", async () => {
|
||||
const am1 = clip(annoMatrix, 0.1, 0.9);
|
||||
await addDrop(am1);
|
||||
|
||||
const am2 = isubset(am1, [0, 1, 2, 20, 30, 400]);
|
||||
await addDrop(am2);
|
||||
|
||||
const am3 = isubset(annoMatrix, [10, 0, 7, 3]);
|
||||
await addDrop(am3);
|
||||
|
||||
const am4 = clip(am3, 0, 1);
|
||||
await addDrop(am4);
|
||||
|
||||
fetch.mockResponse(serverMocks.responder);
|
||||
|
||||
await am1.fetch("obs", am1.getMatrixColumns("obs"));
|
||||
await am2.fetch("obs", am2.getMatrixColumns("obs"));
|
||||
await am3.fetch("obs", am3.getMatrixColumns("obs"));
|
||||
await am4.fetch("obs", am4.getMatrixColumns("obs"));
|
||||
|
||||
fetch.resetMocks();
|
||||
|
||||
await addDrop(am1);
|
||||
await addDrop(am2);
|
||||
await addDrop(am3);
|
||||
await addDrop(am4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setObsColumnValues", () => {
|
||||
async function addSetDrop(base) {
|
||||
/* add column */
|
||||
let am = base.addObsColumn(
|
||||
{
|
||||
name: "test",
|
||||
type: "categorical",
|
||||
categories: ["unassigned", "red", "green"],
|
||||
writable: true,
|
||||
},
|
||||
Array,
|
||||
"unassigned"
|
||||
);
|
||||
|
||||
const testVal = await am.fetch("obs", "test");
|
||||
expect(testVal.col("test").asArray()).toEqual(
|
||||
new Array(am.nObs).fill("unassigned")
|
||||
);
|
||||
|
||||
/* set values in column */
|
||||
const whichRows = [1, 2, 10];
|
||||
const am1 = await am.setObsColumnValues("test", whichRows, "yo");
|
||||
const testVal1 = await am1.fetch("obs", "test");
|
||||
const expt = new Array(am1.nObs).fill("unassigned");
|
||||
for (let i = 0; i < whichRows.length; i += 1) {
|
||||
const offset = am1.rowIndex.getOffset(whichRows[i]);
|
||||
expt[offset] = "yo";
|
||||
}
|
||||
expect(testVal1).not.toBe(testVal);
|
||||
expect(testVal1.col("test").asArray()).toEqual(expt);
|
||||
expect(am1.getColumnSchema("obs", "test").type).toBe("categorical");
|
||||
expect(am1.getColumnSchema("obs", "test").categories).toEqual(
|
||||
expect.arrayContaining(["unassigned", "red", "green", "yo"])
|
||||
);
|
||||
|
||||
/* drop column */
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
am = am1.dropObsColumn("test");
|
||||
await expect(am.fetch("obs", "test")).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
}
|
||||
|
||||
test("set, without a view", async () => {
|
||||
await addSetDrop(annoMatrix);
|
||||
});
|
||||
|
||||
test("set, with a view", async () => {
|
||||
const am1 = clip(annoMatrix, 0.1, 0.9);
|
||||
await addSetDrop(am1);
|
||||
|
||||
const am2 = isubset(am1, [0, 1, 2, 10, 20, 30, 400]);
|
||||
await addSetDrop(am2);
|
||||
|
||||
const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]);
|
||||
await addSetDrop(am3);
|
||||
|
||||
fetch.mockResponse(serverMocks.responder);
|
||||
|
||||
await am1.fetch("obs", am1.getMatrixColumns("obs"));
|
||||
await am2.fetch("obs", am2.getMatrixColumns("obs"));
|
||||
await am3.fetch("obs", am3.getMatrixColumns("obs"));
|
||||
|
||||
await addSetDrop(am1);
|
||||
await addSetDrop(am2);
|
||||
await addSetDrop(am3);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,688 @@
|
||||
// these TWO statements MUST be first in the file, before any other imports
|
||||
import { enableFetchMocks } from "jest-fetch-mock";
|
||||
import * as serverMocks from "./serverMocks";
|
||||
// OK, continue on!
|
||||
|
||||
import obsLouvain from "./louvain.json";
|
||||
import obsNGenes from "./n_genes.json";
|
||||
import embUmap from "./umap.json";
|
||||
|
||||
import {
|
||||
AnnoMatrixLoader,
|
||||
AnnoMatrixObsCrossfilter,
|
||||
isubsetMask,
|
||||
} from "../../../src/annoMatrix";
|
||||
import { rangeFill } from "../../../src/util/range";
|
||||
|
||||
enableFetchMocks();
|
||||
|
||||
describe("AnnoMatrixCrossfilter", () => {
|
||||
let annoMatrix;
|
||||
let crossfilter;
|
||||
|
||||
beforeEach(async () => {
|
||||
fetch.resetMocks(); // reset all fetch mocking state
|
||||
annoMatrix = new AnnoMatrixLoader(
|
||||
serverMocks.baseDataURL,
|
||||
serverMocks.schema.schema
|
||||
);
|
||||
crossfilter = new AnnoMatrixObsCrossfilter(annoMatrix);
|
||||
});
|
||||
|
||||
test("initial state of crossfilter", () => {
|
||||
const { nObs } = annoMatrix;
|
||||
|
||||
expect(crossfilter).toBeDefined();
|
||||
expect(crossfilter.size()).toEqual(nObs);
|
||||
expect(crossfilter.annoMatrix).toBe(annoMatrix);
|
||||
|
||||
/* by default, everything should be selected, even if no data in cache */
|
||||
expect(crossfilter.countSelected()).toEqual(nObs);
|
||||
expect(crossfilter.allSelectedLabels()).toEqual(
|
||||
rangeFill(new Int32Array(nObs))
|
||||
);
|
||||
expect(crossfilter.allSelectedMask()).toEqual(new Uint8Array(nObs).fill(1));
|
||||
expect(crossfilter.fillByIsSelected(new Uint8Array(nObs), 2, 1)).toEqual(
|
||||
new Uint8Array(nObs).fill(2)
|
||||
);
|
||||
});
|
||||
|
||||
describe("select", () => {
|
||||
/*
|
||||
test the selection state via crossfilter proxy
|
||||
*/
|
||||
|
||||
test("select loads index", async () => {
|
||||
/*
|
||||
Select should transparently load/create dimension index.
|
||||
|
||||
Internal dimension names are field/col:col:col..., eg,
|
||||
|
||||
obs:louvain
|
||||
emb:umap_0:umap_1
|
||||
|
||||
*/
|
||||
expect(crossfilter.obsCrossfilter.dimensionNames()).toEqual([]);
|
||||
expect(
|
||||
crossfilter.obsCrossfilter.hasDimension("obs/louvain")
|
||||
).toBeFalsy();
|
||||
|
||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
||||
let newCrossfilter = await crossfilter.select("obs", "louvain", {
|
||||
mode: "none",
|
||||
});
|
||||
|
||||
expect(newCrossfilter.countSelected()).toEqual(0);
|
||||
expect(
|
||||
newCrossfilter.obsCrossfilter.hasDimension("obs/louvain")
|
||||
).toBeTruthy();
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
|
||||
newCrossfilter = await crossfilter.select("obs", "louvain", {
|
||||
mode: "all",
|
||||
});
|
||||
expect(newCrossfilter.countSelected()).toEqual(annoMatrix.nObs);
|
||||
});
|
||||
|
||||
test("simple column select", async () => {
|
||||
let xfltr;
|
||||
|
||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
||||
xfltr = await crossfilter.select("obs", "louvain", {
|
||||
mode: "exact",
|
||||
values: ["NK cells", "B cells"],
|
||||
});
|
||||
|
||||
expect(xfltr).toBeDefined();
|
||||
expect(xfltr.countSelected()).toEqual(496);
|
||||
expect(xfltr.allSelectedMask()).toEqual(
|
||||
Uint8Array.from(
|
||||
obsLouvain.map((val) =>
|
||||
val === "NK cells" || val === "B cells" ? 1 : 0
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(xfltr.allSelectedLabels()).toEqual(
|
||||
Int32Array.from(
|
||||
obsLouvain.reduce((acc, val, idx) => {
|
||||
if (val === "NK cells" || val === "B cells") acc.push(idx);
|
||||
return acc;
|
||||
}, [])
|
||||
)
|
||||
);
|
||||
expect(
|
||||
xfltr.fillByIsSelected(new Uint8Array(annoMatrix.nObs), 3, 1)
|
||||
).toEqual(
|
||||
Uint8Array.from(
|
||||
obsLouvain.map((val) =>
|
||||
val === "NK cells" || val === "B cells" ? 3 : 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const df = await annoMatrix.fetch("obs", "louvain");
|
||||
const values = df.col("louvain").asArray();
|
||||
const selected = xfltr.allSelectedMask();
|
||||
values.every(
|
||||
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
|
||||
);
|
||||
|
||||
fetch.once(
|
||||
serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)])
|
||||
);
|
||||
xfltr = await xfltr.select("obs", "n_genes", {
|
||||
mode: "range",
|
||||
lo: 0,
|
||||
hi: 500,
|
||||
inclusive: false,
|
||||
});
|
||||
expect(xfltr.countSelected()).toEqual(33);
|
||||
expect(xfltr.allSelectedLabels()).toEqual(
|
||||
Int32Array.from(
|
||||
obsNGenes.reduce((acc, val, idx) => {
|
||||
const louvain = obsLouvain[idx];
|
||||
if (
|
||||
val >= 0 &&
|
||||
val < 500 &&
|
||||
(louvain === "NK cells" || louvain === "B cells")
|
||||
)
|
||||
acc.push(idx);
|
||||
return acc;
|
||||
}, [])
|
||||
)
|
||||
);
|
||||
|
||||
xfltr = await xfltr.selectAll();
|
||||
expect(xfltr.countSelected()).toEqual(annoMatrix.nObs);
|
||||
});
|
||||
|
||||
test("join column select", async () => {
|
||||
const varIndex = annoMatrix.schema.annotations.var.index;
|
||||
|
||||
const { nObs } = annoMatrix.schema.dataframe;
|
||||
fetch.once(
|
||||
serverMocks.dataframeResponse(
|
||||
["TEST"],
|
||||
[rangeFill(new Float32Array(nObs), 0, 0.1)]
|
||||
)
|
||||
);
|
||||
|
||||
const xfltr = await crossfilter.select(
|
||||
"X",
|
||||
{
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: "TYMP",
|
||||
},
|
||||
{
|
||||
mode: "range",
|
||||
lo: 0,
|
||||
hi: 50,
|
||||
inclusive: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(xfltr).toBeDefined();
|
||||
expect(xfltr.countSelected()).toEqual(501);
|
||||
|
||||
const df = await annoMatrix.fetch("X", {
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: "TYMP",
|
||||
});
|
||||
const values = df.icol(0).asArray();
|
||||
const selected = xfltr.allSelectedMask();
|
||||
values.every((val, idx) => !(val >= 0 && val <= 50) !== !selected[idx]);
|
||||
expect(selected.reduce((acc, val) => (val ? acc + 1 : acc), 0)).toEqual(
|
||||
xfltr.countSelected()
|
||||
);
|
||||
});
|
||||
|
||||
test("spatial column select", async () => {
|
||||
fetch.once(
|
||||
serverMocks.dataframeResponse(
|
||||
["umap_0", "umap_1"],
|
||||
[Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])]
|
||||
)
|
||||
);
|
||||
const xfltr = await crossfilter.select("emb", "umap", {
|
||||
mode: "within-rect",
|
||||
minX: 0,
|
||||
minY: 0,
|
||||
maxX: 0.5,
|
||||
maxY: 0.5,
|
||||
});
|
||||
expect(xfltr.countSelected()).toEqual(16);
|
||||
});
|
||||
|
||||
test("select on subset", async () => {
|
||||
const mask = new Uint8Array(annoMatrix.nObs).fill(0);
|
||||
for (let i = 0; i < mask.length; i += 2) {
|
||||
mask[i] = true;
|
||||
}
|
||||
const annoMatrixSubset = isubsetMask(annoMatrix, mask);
|
||||
expect(annoMatrixSubset.nObs).toEqual(Math.floor(annoMatrix.nObs / 2));
|
||||
|
||||
let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset);
|
||||
expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs);
|
||||
|
||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
||||
xfltr = await xfltr.select("obs", "louvain", {
|
||||
mode: "exact",
|
||||
values: ["NK cells", "B cells"],
|
||||
});
|
||||
|
||||
expect(xfltr).toBeDefined();
|
||||
expect(xfltr.countSelected()).toEqual(240);
|
||||
|
||||
const df = await annoMatrixSubset.fetch("obs", "louvain");
|
||||
const values = df.col("louvain").asArray();
|
||||
const selected = xfltr.allSelectedMask();
|
||||
values.every(
|
||||
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
|
||||
);
|
||||
});
|
||||
|
||||
test("select catches errors", async () => {
|
||||
await expect(crossfilter.select("NADA", "foo")).rejects.toThrow(
|
||||
"Unknown field name"
|
||||
);
|
||||
|
||||
await expect(crossfilter.select("var", "foo")).rejects.toThrow(
|
||||
"unable to obsSelect upon the var dimension"
|
||||
);
|
||||
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
await expect(crossfilter.select("obs", "foo")).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mutate matrix", () => {
|
||||
/*
|
||||
test the matrix mutators via crossfilter proxy
|
||||
*/
|
||||
async function helperAddTestCol(cf, colName, colSchema = null) {
|
||||
expect(
|
||||
cf.annoMatrix.getMatrixColumns("obs").includes(colName)
|
||||
).toBeFalsy();
|
||||
|
||||
if (colSchema === null) {
|
||||
colSchema = {
|
||||
name: colName,
|
||||
type: "categorical",
|
||||
categories: ["toasty"],
|
||||
};
|
||||
}
|
||||
colSchema.name = colName;
|
||||
const initValue = colSchema.categories[0];
|
||||
const xfltr = cf.addObsColumn(colSchema, Array, initValue);
|
||||
expect(
|
||||
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
||||
(v) => v.name === colName
|
||||
)
|
||||
).toHaveLength(1);
|
||||
const df = await xfltr.annoMatrix.fetch("obs", colName);
|
||||
expect(df.hasCol(colName)).toBeTruthy();
|
||||
return xfltr;
|
||||
}
|
||||
|
||||
test("addObsColumn", async () => {
|
||||
expect(crossfilter.countSelected()).toBe(annoMatrix.nObs);
|
||||
expect(
|
||||
crossfilter.annoMatrix.getMatrixColumns("obs").includes("foo")
|
||||
).toBeFalsy();
|
||||
const xfltr = crossfilter.addObsColumn(
|
||||
{ name: "foo", type: "categorical", categories: ["A"] },
|
||||
Array,
|
||||
"A"
|
||||
);
|
||||
|
||||
// check schema updates correctly.
|
||||
expect(xfltr.countSelected()).toBe(annoMatrix.nObs);
|
||||
expect(
|
||||
xfltr.annoMatrix.getMatrixColumns("obs").includes("foo")
|
||||
).toBeTruthy();
|
||||
expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toMatchObject({
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
});
|
||||
expect(
|
||||
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
||||
(v) => v.name === "foo"
|
||||
)
|
||||
).toHaveLength(1);
|
||||
|
||||
// check data update.
|
||||
const df = await xfltr.annoMatrix.fetch("obs", "foo");
|
||||
expect(
|
||||
df
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.every((v) => v === "A")
|
||||
).toBeTruthy();
|
||||
|
||||
// check that we catch dups
|
||||
expect(() =>
|
||||
xfltr.addObsColumn(
|
||||
{ name: "foo", type: "categorical" },
|
||||
Array,
|
||||
"toasty"
|
||||
)
|
||||
).toThrow("column already exists");
|
||||
expect(() =>
|
||||
xfltr.addObsColumn(
|
||||
{ name: "louvain", type: "categorical" },
|
||||
Array,
|
||||
"toasty"
|
||||
)
|
||||
).toThrow("column already exists");
|
||||
});
|
||||
|
||||
test("dropObsColumn", async () => {
|
||||
let xfltr;
|
||||
|
||||
/* check that we catch attempt to drop readonly dimension */
|
||||
expect(() => crossfilter.dropObsColumn("louvain")).toThrow(
|
||||
"Unknown or readonly obs column"
|
||||
);
|
||||
/* non-existent column */
|
||||
expect(() => crossfilter.dropObsColumn("does-not-exist")).toThrow(
|
||||
"Unknown or readonly obs column"
|
||||
);
|
||||
|
||||
// add a column, then drop it.
|
||||
xfltr = await helperAddTestCol(crossfilter, "foo");
|
||||
xfltr = xfltr.dropObsColumn("foo");
|
||||
expect(
|
||||
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
||||
(v) => v.name === "foo"
|
||||
)
|
||||
).toHaveLength(0);
|
||||
expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toBeUndefined();
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
|
||||
// now same, but ensure we have built an index before doing the drop
|
||||
xfltr = await helperAddTestCol(crossfilter, "bar");
|
||||
xfltr = await xfltr.select("obs", "bar", {
|
||||
mode: "exact",
|
||||
values: "whatever",
|
||||
});
|
||||
xfltr = xfltr.dropObsColumn("bar");
|
||||
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
});
|
||||
|
||||
test("renameObsColumn", async () => {
|
||||
let xfltr;
|
||||
|
||||
/* catch attempts to rename non-existent or readonly columns */
|
||||
expect(() =>
|
||||
crossfilter.renameObsColumn("does-not-exist", "foo")
|
||||
).toThrow("Unknown or readonly obs column");
|
||||
expect(() => crossfilter.renameObsColumn("louvain", "foo")).toThrow(
|
||||
"Unknown or readonly obs column"
|
||||
);
|
||||
|
||||
// add a column, then rename it.
|
||||
xfltr = await helperAddTestCol(crossfilter, "foo");
|
||||
xfltr = xfltr.renameObsColumn("foo", "bar");
|
||||
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toBeUndefined();
|
||||
expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({
|
||||
name: "bar",
|
||||
type: "categorical",
|
||||
});
|
||||
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
const df = await xfltr.annoMatrix.fetch("obs", "bar");
|
||||
expect(df.hasCol("bar")).toBeTruthy();
|
||||
|
||||
// now same, but ensure we have built an index before doing the rename
|
||||
xfltr = await helperAddTestCol(crossfilter, "bar");
|
||||
xfltr = await xfltr.select("obs", "bar", {
|
||||
mode: "exact",
|
||||
values: "whatever",
|
||||
});
|
||||
xfltr = xfltr.renameObsColumn("bar", "xyz");
|
||||
|
||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
||||
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
|
||||
"unknown column name"
|
||||
);
|
||||
await expect(
|
||||
xfltr.select("obs", "xyz", { mode: "none" })
|
||||
).resolves.toBeInstanceOf(AnnoMatrixObsCrossfilter);
|
||||
});
|
||||
|
||||
test("addObsAnnoCategory", async () => {
|
||||
let xfltr;
|
||||
|
||||
// catch unknown or readonly columns
|
||||
expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow(
|
||||
"Unknown or readonly obs column"
|
||||
);
|
||||
expect(() =>
|
||||
crossfilter.addObsAnnoCategory("undefined-name", "mumble")
|
||||
).toThrow("Unknown or readonly obs column");
|
||||
|
||||
// add a column and then add category to it
|
||||
xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: ["unassigned"],
|
||||
});
|
||||
xfltr = xfltr.addObsAnnoCategory("foo", "a-new-label");
|
||||
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: expect.arrayContaining(["a-new-label", "unassigned"]),
|
||||
});
|
||||
|
||||
// do it again, dup; should throw
|
||||
expect(() => xfltr.addObsAnnoCategory("foo", "a-new-label")).toThrow(
|
||||
"category already exists"
|
||||
);
|
||||
|
||||
// now same, but ensure we have built an index before doing the operation
|
||||
xfltr = await helperAddTestCol(crossfilter, "bar", {
|
||||
name: "bar",
|
||||
type: "categorical",
|
||||
categories: ["unassigned"],
|
||||
});
|
||||
xfltr = await xfltr.select("obs", "bar", {
|
||||
mode: "exact",
|
||||
values: "something",
|
||||
});
|
||||
xfltr = xfltr.addObsAnnoCategory("bar", "a-new-label");
|
||||
expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({
|
||||
name: "bar",
|
||||
type: "categorical",
|
||||
categories: expect.arrayContaining(["a-new-label", "unassigned"]),
|
||||
});
|
||||
});
|
||||
|
||||
test("removeObsAnnoCategory", async () => {
|
||||
let xfltr;
|
||||
|
||||
// catch unknown or readonly categories
|
||||
await expect(() =>
|
||||
crossfilter.removeObsAnnoCategory("louvain", "mumble", "unassigned")
|
||||
).rejects.toThrow("Unknown or readonly obs column");
|
||||
await expect(() =>
|
||||
crossfilter.removeObsAnnoCategory("undefined-name", "mumble")
|
||||
).rejects.toThrow("Unknown or readonly obs column");
|
||||
|
||||
xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: ["unassigned", "red", "green", "blue"],
|
||||
});
|
||||
xfltr = await xfltr.select("obs", "foo", { mode: "all" });
|
||||
expect(
|
||||
(await xfltr.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.every((v) => v === "unassigned")
|
||||
).toBeTruthy();
|
||||
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: expect.arrayContaining([
|
||||
"unassigned",
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
]),
|
||||
});
|
||||
|
||||
// remove an unused category
|
||||
const xfltr1 = await xfltr.removeObsAnnoCategory("foo", "red", "mumble");
|
||||
expect(
|
||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.every((v) => v === "unassigned")
|
||||
).toBeTruthy();
|
||||
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: expect.arrayContaining([
|
||||
"unassigned",
|
||||
"green",
|
||||
"blue",
|
||||
"mumble",
|
||||
]),
|
||||
});
|
||||
|
||||
// remove a used category
|
||||
const xfltr2 = await xfltr.removeObsAnnoCategory(
|
||||
"foo",
|
||||
"unassigned",
|
||||
"red"
|
||||
);
|
||||
expect(
|
||||
(await xfltr2.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.every((v) => v === "red")
|
||||
).toBeTruthy();
|
||||
expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: expect.arrayContaining(["green", "blue", "red"]),
|
||||
});
|
||||
});
|
||||
|
||||
test("setObsColumnValues", async () => {
|
||||
// catch unknown or readonly categories
|
||||
await expect(() =>
|
||||
crossfilter.setObsColumnValues("louvain", [0, 1], "unassigned")
|
||||
).rejects.toThrow("Unknown or readonly obs column");
|
||||
await expect(() =>
|
||||
crossfilter.setObsColumnValues("undefined-name", [0], "mumble")
|
||||
).rejects.toThrow("Unknown or readonly obs column");
|
||||
|
||||
let xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: ["unassigned", "red", "green", "blue"],
|
||||
});
|
||||
xfltr = await xfltr.select("obs", "foo", { mode: "all" });
|
||||
|
||||
// catch unknown row label
|
||||
await expect(() =>
|
||||
xfltr.setObsColumnValues("foo", [-1], "red")
|
||||
).rejects.toThrow("Unknown row label");
|
||||
|
||||
// set a few rows
|
||||
expect(
|
||||
(await xfltr.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.every((v) => v === "unassigned")
|
||||
).toBeTruthy();
|
||||
const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
|
||||
expect(
|
||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.every(
|
||||
(v, i) =>
|
||||
v === "unassigned" || (v === "purple" && (i === 0 || i === 10))
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: expect.arrayContaining([
|
||||
"unassigned",
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
"purple",
|
||||
]),
|
||||
});
|
||||
|
||||
expect(xfltr1.countSelected()).toEqual(xfltr1.annoMatrix.nObs);
|
||||
const xfltr2 = await xfltr1.select("obs", "foo", {
|
||||
mode: "exact",
|
||||
values: ["purple"],
|
||||
});
|
||||
expect(xfltr2.countSelected()).toEqual(2);
|
||||
expect(xfltr2.allSelectedLabels()).toEqual(Int32Array.from([0, 10]));
|
||||
});
|
||||
|
||||
test("resetObsColumnValues", async () => {
|
||||
// catch unknown or readonly categories
|
||||
await expect(() =>
|
||||
crossfilter.resetObsColumnValues("louvain", "red", "blue")
|
||||
).rejects.toThrow("Unknown or readonly obs column");
|
||||
await expect(() =>
|
||||
crossfilter.resetObsColumnValues("undefined-name", "red", "blue")
|
||||
).rejects.toThrow("Unknown or readonly obs column");
|
||||
|
||||
let xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: ["unassigned", "red", "green", "blue"],
|
||||
});
|
||||
xfltr = await xfltr.select("obs", "foo", {
|
||||
mode: "exact",
|
||||
values: "red",
|
||||
});
|
||||
|
||||
// catch unknown category name label
|
||||
await expect(() =>
|
||||
xfltr.resetObsColumnValues("foo", "unknown-label", "red")
|
||||
).rejects.toThrow("unknown category");
|
||||
|
||||
let xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
|
||||
xfltr1 = await xfltr1.select("obs", "foo", {
|
||||
mode: "exact",
|
||||
values: "purple",
|
||||
});
|
||||
expect(
|
||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.filter((v) => v === "purple")
|
||||
).toHaveLength(2);
|
||||
|
||||
xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta");
|
||||
expect(
|
||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.filter((v) => v === "magenta")
|
||||
).toHaveLength(2);
|
||||
expect(
|
||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||
.col("foo")
|
||||
.asArray()
|
||||
.filter((v) => v === "purple")
|
||||
).toHaveLength(0);
|
||||
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||
name: "foo",
|
||||
type: "categorical",
|
||||
categories: expect.arrayContaining([
|
||||
"unassigned",
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
"purple",
|
||||
"magenta",
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
test("transition from empty annoMatrix", async () => {
|
||||
// select before fetch needs to work
|
||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
||||
const xfltr = await crossfilter.select("obs", "louvain", {
|
||||
mode: "exact",
|
||||
values: "B cells",
|
||||
});
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
expect(xfltr.obsCrossfilter.hasDimension("obs/louvain")).toBeTruthy();
|
||||
expect(xfltr.obsCrossfilter.all()).toBe(xfltr.annoMatrix._cache.obs);
|
||||
expect(xfltr.countSelected()).toEqual(
|
||||
obsLouvain.reduce(
|
||||
(count, v) => (v === "B cells" ? count + 1 : count),
|
||||
0
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
export const baseDataURL = "https://a.fake.url/api/v0.2";
|
||||
|
||||
window.CELLXGENE = {
|
||||
API: {
|
||||
prefix: baseDataURL,
|
||||
version: "v0.2/",
|
||||
},
|
||||
};
|
||||
|
||||
export { schema } from "./schema";
|
||||
export * from "./routes";
|
||||
@@ -0,0 +1,211 @@
|
||||
import { schema } from "./schema";
|
||||
import { Dataframe, KeyIndex } from "../../../../src/util/dataframe";
|
||||
import { encodeMatrixFBS } from "../../../../src/util/stateManager/matrix";
|
||||
|
||||
const indexedSchema = {
|
||||
obsByName: Object.fromEntries(
|
||||
schema.schema.annotations.obs.columns.map((v) => [v.name, v]) ?? []
|
||||
),
|
||||
varByName: Object.fromEntries(
|
||||
schema.schema.annotations.var.columns.map((v) => [v.name, v]) ?? []
|
||||
),
|
||||
embByName: Object.fromEntries(
|
||||
schema.schema.layout.obs.map((v) => [v.name, v]) ?? []
|
||||
),
|
||||
};
|
||||
|
||||
function makeMockColumn(s, length) {
|
||||
const { type } = s;
|
||||
switch (type) {
|
||||
case "int32":
|
||||
return new Int32Array(length).fill(Math.floor(99 * Math.random()));
|
||||
|
||||
case "string":
|
||||
return new Array(length).fill("test");
|
||||
|
||||
case "float32":
|
||||
return new Float32Array(length).fill(99 * Math.random());
|
||||
|
||||
case "boolean":
|
||||
return new Array(length).fill(false);
|
||||
|
||||
case "categorical":
|
||||
return new Array(length).fill(s.categories[0]);
|
||||
|
||||
default:
|
||||
throw new Error("unkonwn type");
|
||||
}
|
||||
}
|
||||
|
||||
function getEncodedDataframe(colNames, length, colSchemas) {
|
||||
const colIndex = new KeyIndex(colNames);
|
||||
const columns = colSchemas.map((s) => makeMockColumn(s, length));
|
||||
const df = new Dataframe([length, colNames.length], columns, null, colIndex);
|
||||
const body = encodeMatrixFBS(df);
|
||||
return body;
|
||||
}
|
||||
|
||||
export function dataframeResponse(colNames, columns) {
|
||||
const colIndex = new KeyIndex(colNames);
|
||||
const df = new Dataframe(
|
||||
[columns[0].length, colNames.length],
|
||||
columns,
|
||||
null,
|
||||
colIndex
|
||||
);
|
||||
const body = encodeMatrixFBS(df);
|
||||
const headers = new Headers({
|
||||
"Content-Type": "application/octet-stream",
|
||||
});
|
||||
return () => Promise.resolve({ body, init: { status: 200, headers } });
|
||||
}
|
||||
|
||||
function annotationObsResponse(request) {
|
||||
const url = new URL(request.url);
|
||||
const params = Array.from(url.searchParams.entries());
|
||||
const names = params
|
||||
.filter(([k]) => k === "annotation-name")
|
||||
.map(([, v]) => v);
|
||||
if (!names.every((n) => indexedSchema.obsByName[n])) {
|
||||
return Promise.reject(new Error("bad obs annotation name in URL"));
|
||||
}
|
||||
const colSchemas = names.map((n) => indexedSchema.obsByName[n]);
|
||||
const body = getEncodedDataframe(
|
||||
names,
|
||||
schema.schema.dataframe.nObs,
|
||||
colSchemas
|
||||
);
|
||||
|
||||
const headers = new Headers({
|
||||
"Content-Type": "application/octet-stream",
|
||||
});
|
||||
return Promise.resolve({
|
||||
body,
|
||||
init: { status: 200, headers },
|
||||
});
|
||||
}
|
||||
|
||||
function annotationVarResponse(request) {
|
||||
const url = new URL(request.url);
|
||||
const params = Array.from(url.searchParams.entries());
|
||||
const names = params
|
||||
.filter(([k]) => k === "annotation-name")
|
||||
.map(([, v]) => v);
|
||||
if (!names.every((n) => indexedSchema.varByName[n])) {
|
||||
return Promise.reject(new Error("bad var annotation name in URL"));
|
||||
}
|
||||
const colSchemas = names.map((n) => indexedSchema.varByName[n]);
|
||||
const body = getEncodedDataframe(
|
||||
names,
|
||||
schema.schema.dataframe.nVar,
|
||||
colSchemas
|
||||
);
|
||||
|
||||
const headers = new Headers({
|
||||
"Content-Type": "application/octet-stream",
|
||||
});
|
||||
return Promise.resolve({
|
||||
body,
|
||||
init: { status: 200, headers },
|
||||
});
|
||||
}
|
||||
|
||||
function layoutObsResponse(request) {
|
||||
const url = new URL(request.url);
|
||||
const params = Array.from(url.searchParams.entries());
|
||||
const names = params.filter(([k]) => k === "layout-name").map(([, v]) => v);
|
||||
if (!names.every((n) => indexedSchema.embByName[n])) {
|
||||
return Promise.reject(new Error("bad layout name in URL"));
|
||||
}
|
||||
const dims = names.map((n) => indexedSchema.embByName[n].dims).flat();
|
||||
const colSchemas = names
|
||||
.map((n) => [indexedSchema.embByName[n], indexedSchema.embByName[n]])
|
||||
.flat();
|
||||
const body = getEncodedDataframe(
|
||||
dims,
|
||||
schema.schema.dataframe.nObs,
|
||||
colSchemas
|
||||
);
|
||||
|
||||
const headers = new Headers({
|
||||
"Content-Type": "application/octet-stream",
|
||||
});
|
||||
return Promise.resolve({
|
||||
body,
|
||||
init: { status: 200, headers },
|
||||
});
|
||||
}
|
||||
|
||||
function dataVarResponse(request) {
|
||||
const url = new URL(request.url);
|
||||
const params = Array.from(url.searchParams.entries());
|
||||
|
||||
const colNames = params.map((v) => `${v[0]}/${v[1]}`);
|
||||
const colSchemas = colNames.map(() => schema.schema.dataframe);
|
||||
const body = getEncodedDataframe(
|
||||
colNames,
|
||||
schema.schema.dataframe.nObs,
|
||||
colSchemas
|
||||
);
|
||||
|
||||
const headers = new Headers({
|
||||
"Content-Type": "application/octet-stream",
|
||||
});
|
||||
return Promise.resolve({
|
||||
body,
|
||||
init: { status: 200, headers },
|
||||
});
|
||||
}
|
||||
|
||||
export function responder(request) {
|
||||
const url = new URL(request.url);
|
||||
const { pathname } = url;
|
||||
if (pathname.endsWith("/annotations/obs")) {
|
||||
return annotationObsResponse(request);
|
||||
}
|
||||
if (pathname.endsWith("/annotations/var")) {
|
||||
return annotationVarResponse(request);
|
||||
}
|
||||
if (pathname.endsWith("/layout/obs")) {
|
||||
return layoutObsResponse(request);
|
||||
}
|
||||
if (pathname.endsWith("/data/var")) {
|
||||
return dataVarResponse(request);
|
||||
}
|
||||
return Promise.reject(new Error("bad URL"));
|
||||
}
|
||||
|
||||
export function withExpected(expectedURL, expectedParams) {
|
||||
/*
|
||||
Do some additional error checking
|
||||
*/
|
||||
return (request) => {
|
||||
// if URL is bogus, reject the promise
|
||||
const url = new URL(request.url);
|
||||
if (!url.pathname.endsWith(expectedURL)) {
|
||||
return Promise.reject(new Error("Unexpected URL!"));
|
||||
}
|
||||
const params = Array.from(url.searchParams.entries()).sort(
|
||||
(a, b) => a[0] < b[0]
|
||||
);
|
||||
expectedParams = expectedParams.slice().sort((a, b) => a[0] < b[0]);
|
||||
|
||||
if (
|
||||
params.length !== expectedParams.length ||
|
||||
!params.every(
|
||||
(p, i) => p[0] === expectedParams[i][0] && p[1] === expectedParams[i][1]
|
||||
)
|
||||
) {
|
||||
return Promise.reject(new Error("unexpected name requested in URL"));
|
||||
}
|
||||
|
||||
return responder(request);
|
||||
};
|
||||
}
|
||||
|
||||
export function annotationsObs(names) {
|
||||
return withExpected(
|
||||
"/annotations/obs",
|
||||
names.map((name) => ["annotation-name", name])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
export const schema = {
|
||||
schema: {
|
||||
annotations: {
|
||||
obs: {
|
||||
columns: [
|
||||
{
|
||||
name: "name_0",
|
||||
type: "string",
|
||||
writable: false,
|
||||
},
|
||||
{
|
||||
name: "n_genes",
|
||||
type: "int32",
|
||||
writable: false,
|
||||
},
|
||||
{
|
||||
name: "percent_mito",
|
||||
type: "float32",
|
||||
writable: false,
|
||||
},
|
||||
{
|
||||
name: "n_counts",
|
||||
type: "float32",
|
||||
writable: false,
|
||||
},
|
||||
{
|
||||
name: "louvain",
|
||||
type: "string",
|
||||
writable: false,
|
||||
},
|
||||
],
|
||||
index: "name_0",
|
||||
},
|
||||
var: {
|
||||
columns: [
|
||||
{
|
||||
name: "name_0",
|
||||
type: "string",
|
||||
writable: false,
|
||||
},
|
||||
{
|
||||
name: "n_cells",
|
||||
type: "int32",
|
||||
writable: false,
|
||||
},
|
||||
],
|
||||
index: "name_0",
|
||||
},
|
||||
},
|
||||
dataframe: {
|
||||
nObs: 2638,
|
||||
nVar: 1838,
|
||||
type: "float32",
|
||||
},
|
||||
layout: {
|
||||
obs: [
|
||||
{
|
||||
dims: ["draw_graph_fr_0", "draw_graph_fr_1"],
|
||||
name: "draw_graph_fr",
|
||||
type: "float32",
|
||||
},
|
||||
{
|
||||
dims: ["pca_0", "pca_1"],
|
||||
name: "pca",
|
||||
type: "float32",
|
||||
},
|
||||
{
|
||||
dims: ["tsne_0", "tsne_1"],
|
||||
name: "tsne",
|
||||
type: "float32",
|
||||
},
|
||||
{
|
||||
dims: ["umap_0", "umap_1"],
|
||||
name: "umap",
|
||||
type: "float32",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,184 @@
|
||||
import {
|
||||
_whereCacheGet,
|
||||
_whereCacheCreate,
|
||||
_whereCacheMerge,
|
||||
} from "../../../src/annoMatrix/whereCache";
|
||||
|
||||
const schema = {};
|
||||
|
||||
describe("whereCache", () => {
|
||||
test("whereCacheGet - missing cache values", () => {
|
||||
expect(
|
||||
_whereCacheGet({}, schema, "X", {
|
||||
field: "var",
|
||||
column: "foo",
|
||||
value: "bar",
|
||||
})
|
||||
).toEqual([undefined]);
|
||||
expect(
|
||||
_whereCacheGet({ X: {} }, schema, "X", {
|
||||
field: "var",
|
||||
column: "foo",
|
||||
value: "bar",
|
||||
})
|
||||
).toEqual([undefined]);
|
||||
expect(
|
||||
_whereCacheGet({ X: { var: new Map() } }, schema, "X", {
|
||||
field: "var",
|
||||
column: "foo",
|
||||
value: "bar",
|
||||
})
|
||||
).toEqual([undefined]);
|
||||
expect(
|
||||
_whereCacheGet(
|
||||
{ X: { var: new Map([["foo", new Map()]]) } },
|
||||
schema,
|
||||
"X",
|
||||
{
|
||||
field: "var",
|
||||
column: "foo",
|
||||
value: "bar",
|
||||
}
|
||||
)
|
||||
).toEqual([undefined]);
|
||||
});
|
||||
|
||||
test("whereCacheGet - varied lookups", () => {
|
||||
const whereCache = {
|
||||
X: {
|
||||
var: new Map([
|
||||
[
|
||||
"foo",
|
||||
new Map([
|
||||
["bar", [0]],
|
||||
["baz", [1, 2]],
|
||||
]),
|
||||
],
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
_whereCacheGet(whereCache, schema, "X", {
|
||||
field: "var",
|
||||
column: "foo",
|
||||
value: "bar",
|
||||
})
|
||||
).toEqual([0]);
|
||||
expect(
|
||||
_whereCacheGet(whereCache, schema, "X", {
|
||||
field: "var",
|
||||
column: "foo",
|
||||
value: "baz",
|
||||
})
|
||||
).toEqual([1, 2]);
|
||||
expect(_whereCacheGet(whereCache, schema, "Y", {})).toEqual([undefined]);
|
||||
expect(
|
||||
_whereCacheGet(whereCache, schema, "X", {
|
||||
field: "whoknows",
|
||||
column: "whatever",
|
||||
value: "snork",
|
||||
})
|
||||
).toEqual([undefined]);
|
||||
expect(
|
||||
_whereCacheGet(whereCache, schema, "X", {
|
||||
field: "var",
|
||||
column: "whatever",
|
||||
value: "snork",
|
||||
})
|
||||
).toEqual([undefined]);
|
||||
expect(
|
||||
_whereCacheGet(whereCache, schema, "X", {
|
||||
field: "var",
|
||||
column: "foo",
|
||||
value: "snork",
|
||||
})
|
||||
).toEqual([undefined]);
|
||||
});
|
||||
|
||||
test("whereCacheCreate", () => {
|
||||
const query = {
|
||||
field: "queryField",
|
||||
column: "queryColumn",
|
||||
value: "queryValue",
|
||||
};
|
||||
const wc = _whereCacheCreate(
|
||||
"field",
|
||||
{ field: "queryField", column: "queryColumn", value: "queryValue" },
|
||||
[0, 1, 2]
|
||||
);
|
||||
expect(wc).toBeDefined();
|
||||
expect(wc).toEqual(
|
||||
expect.objectContaining({
|
||||
field: {
|
||||
queryField: expect.any(Map),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(wc.field.queryField.has("queryColumn")).toEqual(true);
|
||||
expect(wc.field.queryField.get("queryColumn")).toBeInstanceOf(Map);
|
||||
expect(wc.field.queryField.get("queryColumn").has("queryValue")).toEqual(
|
||||
true
|
||||
);
|
||||
expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
test("whereCacheMerge", () => {
|
||||
let wc;
|
||||
|
||||
// remember, will mutate dst
|
||||
const src = _whereCacheCreate(
|
||||
"field",
|
||||
{ field: "queryField", column: "queryColumn", value: "foo" },
|
||||
["foo"]
|
||||
);
|
||||
|
||||
const dst1 = _whereCacheCreate(
|
||||
"field",
|
||||
{ field: "queryField", column: "queryColumn", value: "bar" },
|
||||
["dst1"]
|
||||
);
|
||||
wc = _whereCacheMerge(dst1, src);
|
||||
expect(
|
||||
_whereCacheGet(wc, schema, "field", {
|
||||
field: "queryField",
|
||||
column: "queryColumn",
|
||||
value: "foo",
|
||||
})
|
||||
).toEqual(["foo"]);
|
||||
expect(
|
||||
_whereCacheGet(wc, schema, "field", {
|
||||
field: "queryField",
|
||||
column: "queryColumn",
|
||||
value: "bar",
|
||||
})
|
||||
).toEqual(["dst1"]);
|
||||
|
||||
const dst2 = _whereCacheCreate(
|
||||
"field",
|
||||
{ field: "queryField", column: "queryColumn", value: "bar" },
|
||||
["dst2"]
|
||||
);
|
||||
wc = _whereCacheMerge(dst2, dst1, src);
|
||||
expect(
|
||||
_whereCacheGet(wc, schema, "field", {
|
||||
field: "queryField",
|
||||
column: "queryColumn",
|
||||
value: "foo",
|
||||
})
|
||||
).toEqual(["foo"]);
|
||||
expect(
|
||||
_whereCacheGet(wc, schema, "field", {
|
||||
field: "queryField",
|
||||
column: "queryColumn",
|
||||
value: "bar",
|
||||
})
|
||||
).toEqual(["dst1"]);
|
||||
|
||||
wc = _whereCacheMerge({}, src);
|
||||
expect(wc).toEqual(src);
|
||||
|
||||
wc = _whereCacheMerge({ field: { queryField: new Map() } }, src);
|
||||
expect(wc).toEqual(src);
|
||||
});
|
||||
});
|
||||
@@ -1,40 +1,36 @@
|
||||
import _ from "lodash";
|
||||
|
||||
import calcCentroid from "../../src/util/centroid";
|
||||
|
||||
import quantile from "../../src/util/quantile";
|
||||
import * as Universe from "../../src/util/stateManager/universe";
|
||||
import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix";
|
||||
import * as World from "../../src/util/stateManager/world";
|
||||
import * as REST from "./stateManager/sampleResponses";
|
||||
import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers";
|
||||
import { _normalizeCategoricalSchema } from "../../src/annoMatrix/schema";
|
||||
|
||||
describe("centroid", () => {
|
||||
let world;
|
||||
let schema;
|
||||
let obsAnnotations;
|
||||
let obsLayout;
|
||||
|
||||
beforeAll(() => {
|
||||
// Create world + universe
|
||||
let universe = Universe.createUniverseFromResponse(
|
||||
_.cloneDeep(REST.config),
|
||||
_.cloneDeep(REST.schema)
|
||||
);
|
||||
schema = indexEntireSchema(_.cloneDeep(REST.schema.schema));
|
||||
obsAnnotations = matrixFBSToDataframe(REST.annotationsObs);
|
||||
obsLayout = matrixFBSToDataframe(REST.layoutObs);
|
||||
|
||||
universe = {
|
||||
...universe,
|
||||
...Universe.addObsAnnotations(
|
||||
universe,
|
||||
matrixFBSToDataframe(REST.annotationsObs)
|
||||
),
|
||||
...Universe.addVarAnnotations(
|
||||
universe,
|
||||
matrixFBSToDataframe(REST.annotationsVar)
|
||||
),
|
||||
...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)),
|
||||
};
|
||||
world = World.createWorldFromEntireUniverse(universe);
|
||||
_normalizeCategoricalSchema(
|
||||
schema.annotations.obsByName.field3,
|
||||
obsAnnotations.col("field3")
|
||||
);
|
||||
});
|
||||
|
||||
test("field4 (categorical obsAnnotation)", () => {
|
||||
const centroidResult = calcCentroid(world, "field4", ["umap_0", "umap_1"]);
|
||||
const centroidResult = calcCentroid(
|
||||
schema,
|
||||
"field4",
|
||||
obsAnnotations,
|
||||
{ current: "umap", currentDimNames: ["umap_0", "umap_1"] },
|
||||
obsLayout
|
||||
);
|
||||
|
||||
// Check to see that a centroid has been calculated for every categorical value
|
||||
const keysAsArray = Array.from(centroidResult.keys());
|
||||
@@ -44,8 +40,8 @@ describe("centroid", () => {
|
||||
|
||||
// This expected result assumes that all cells belong in all categorical values inside of sample response
|
||||
const expectedResult = [
|
||||
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
|
||||
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0],
|
||||
quantile([0.5], obsLayout.col("umap_0").asArray())[0],
|
||||
quantile([0.5], obsLayout.col("umap_1").asArray())[0],
|
||||
];
|
||||
|
||||
centroidResult.forEach((coordinate) => {
|
||||
@@ -54,7 +50,13 @@ describe("centroid", () => {
|
||||
});
|
||||
|
||||
test("field3 (boolean obsAnnotation)", () => {
|
||||
const centroidResult = calcCentroid(world, "field3", ["umap_0", "umap_1"]);
|
||||
const centroidResult = calcCentroid(
|
||||
schema,
|
||||
"field3",
|
||||
obsAnnotations,
|
||||
{ current: "umap", currentDimNames: ["umap_0", "umap_1"] },
|
||||
obsLayout
|
||||
);
|
||||
|
||||
// Check to see that a centroid has been calculated for every categorical value
|
||||
const keysAsArray = Array.from(centroidResult.keys());
|
||||
@@ -62,8 +64,8 @@ describe("centroid", () => {
|
||||
|
||||
// This expected result assumes that all cells belong in all categorical values inside of sample response
|
||||
const expectedResult = [
|
||||
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
|
||||
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0],
|
||||
quantile([0.5], obsLayout.col("umap_0").asArray())[0],
|
||||
quantile([0.5], obsLayout.col("umap_1").asArray())[0],
|
||||
];
|
||||
|
||||
centroidResult.forEach((coordinate) => {
|
||||
|
||||
@@ -918,115 +918,342 @@ describe("dataframe col", () => {
|
||||
});
|
||||
|
||||
describe("label indexing", () => {
|
||||
test("IdentityInt32Index", () => {
|
||||
describe("isLabelIndex", () => {
|
||||
expect(
|
||||
Dataframe.isLabelIndex(new Dataframe.IdentityInt32Index(4))
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
Dataframe.isLabelIndex(new Dataframe.DenseInt32Index([2, 4, 99]))
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
Dataframe.isLabelIndex(new Dataframe.KeyIndex(["a", 4, "toasty"]))
|
||||
).toBeTruthy();
|
||||
expect(Dataframe.isLabelIndex(false)).toBeFalsy();
|
||||
expect(Dataframe.isLabelIndex(undefined)).toBeFalsy();
|
||||
expect(Dataframe.isLabelIndex(null)).toBeFalsy();
|
||||
expect(Dataframe.isLabelIndex(true)).toBeFalsy();
|
||||
expect(Dataframe.isLabelIndex([])).toBeFalsy();
|
||||
expect(Dataframe.isLabelIndex({})).toBeFalsy();
|
||||
expect(Dataframe.isLabelIndex(Dataframe.IdentityInt32Index)).toBeFalsy();
|
||||
});
|
||||
|
||||
describe("IdentityInt32Index", () => {
|
||||
const idx = new Dataframe.IdentityInt32Index(12); // [0, 12)
|
||||
|
||||
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
||||
test("create", () => {
|
||||
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(idx.labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
|
||||
);
|
||||
expect(idx.getLabel(1)).toEqual(1);
|
||||
expect(idx.getOffset(1)).toEqual(1);
|
||||
expect(idx.getOffsets([1, 3])).toEqual([1, 3]);
|
||||
expect(idx.getLabels([1, 3])).toEqual([1, 3]);
|
||||
expect(idx.size()).toEqual(12);
|
||||
test("labels", () => {
|
||||
expect(idx.labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
|
||||
);
|
||||
expect(idx.getLabel(1)).toEqual(1);
|
||||
expect(idx.getLabels([1, 3])).toEqual([1, 3]);
|
||||
expect(idx.size()).toEqual(12);
|
||||
});
|
||||
|
||||
expect(idx.subset([2]).labels()).toEqual([2]);
|
||||
expect(idx.subset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4]));
|
||||
expect(idx.subset([0, 1, 2, 3]).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3])
|
||||
);
|
||||
test("offsets", () => {
|
||||
expect(idx.getOffset(1)).toEqual(1);
|
||||
expect(idx.getOffsets([1, 3])).toEqual([1, 3]);
|
||||
});
|
||||
|
||||
expect(idx.isubset([2]).labels()).toEqual([2]);
|
||||
expect(idx.isubset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4]));
|
||||
expect(idx.isubset([0, 1, 2, 3]).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3])
|
||||
);
|
||||
test("subset", () => {
|
||||
expect(idx.subset([2]).labels()).toEqual([2]);
|
||||
expect(idx.subset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4]));
|
||||
expect(idx.subset([0, 1, 2, 3]).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3])
|
||||
);
|
||||
expect(idx.subset([0, 1, 2, 3, 4])).toBeInstanceOf(
|
||||
Dataframe.IdentityInt32Index
|
||||
);
|
||||
expect(idx.subset([2, 1, 0])).toBeInstanceOf(
|
||||
Dataframe.IdentityInt32Index
|
||||
);
|
||||
expect(idx.subset([1, 2, 3, 4])).toBeInstanceOf(
|
||||
Dataframe.DenseInt32Index
|
||||
);
|
||||
expect(idx.subset([0, 1, 3, 4])).toBeInstanceOf(
|
||||
Dataframe.DenseInt32Index
|
||||
);
|
||||
expect(idx.subset([0, 1, 2, 3, 10])).toBeInstanceOf(
|
||||
Dataframe.DenseInt32Index
|
||||
);
|
||||
expect(idx.subset([4, 3, 2, 1])).toBeInstanceOf(
|
||||
Dataframe.DenseInt32Index
|
||||
);
|
||||
expect(idx.subset([4])).toBeInstanceOf(Dataframe.KeyIndex);
|
||||
});
|
||||
|
||||
expect(idx.subset([0, 1, 2, 3, 4])).toBeInstanceOf(
|
||||
Dataframe.IdentityInt32Index
|
||||
);
|
||||
expect(idx.subset([2, 1, 0])).toBeInstanceOf(Dataframe.IdentityInt32Index);
|
||||
expect(idx.subset([1, 2, 3, 4])).toBeInstanceOf(Dataframe.DenseInt32Index);
|
||||
expect(idx.subset([0, 1, 3, 4])).toBeInstanceOf(Dataframe.DenseInt32Index);
|
||||
expect(idx.subset([0, 1, 2, 3, 10])).toBeInstanceOf(
|
||||
Dataframe.DenseInt32Index
|
||||
);
|
||||
expect(idx.subset([4, 3, 2, 1])).toBeInstanceOf(Dataframe.DenseInt32Index);
|
||||
expect(idx.subset([4])).toBeInstanceOf(Dataframe.KeyIndex);
|
||||
test("isubset", () => {
|
||||
expect(idx.isubset([2]).labels()).toEqual([2]);
|
||||
expect(idx.isubset([2, 3, 4]).labels()).toEqual(
|
||||
new Int32Array([2, 3, 4])
|
||||
);
|
||||
expect(idx.isubset([0, 1, 2, 3]).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3])
|
||||
);
|
||||
expect(() => idx.isubset([-1001])).toThrow(RangeError);
|
||||
expect(() => idx.isubset([1001])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
expect(idx.withLabel(99).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99])
|
||||
);
|
||||
expect(idx.dropLabel(0).labels()).toEqual(
|
||||
new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
|
||||
);
|
||||
expect(idx.dropLabel(11).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
);
|
||||
expect(idx.dropLabel(5).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11])
|
||||
);
|
||||
test("isubsetMask", () => {
|
||||
expect(
|
||||
idx
|
||||
.isubsetMask([
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
])
|
||||
.labels()
|
||||
).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]));
|
||||
expect(
|
||||
idx
|
||||
.isubsetMask([
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
])
|
||||
.labels()
|
||||
).toEqual(new Int32Array([]));
|
||||
expect(
|
||||
idx
|
||||
.isubsetMask([
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
])
|
||||
.labels()
|
||||
).toEqual(new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]));
|
||||
expect(
|
||||
idx
|
||||
.isubsetMask([
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
])
|
||||
.labels()
|
||||
).toEqual(new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 11]));
|
||||
expect(
|
||||
idx
|
||||
.isubsetMask([
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
])
|
||||
.labels()
|
||||
).toEqual(new Int32Array([1, 2, 4, 5, 6, 7, 8, 9, 10]));
|
||||
expect(() => idx.isubsetMask([])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
test("withLabel", () => {
|
||||
expect(idx.withLabel(99).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99])
|
||||
);
|
||||
expect(idx.withLabel(12).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
|
||||
);
|
||||
expect(idx.withLabels([12, 13]).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
|
||||
);
|
||||
});
|
||||
|
||||
test("dropLabel", () => {
|
||||
expect(idx.dropLabel(0).labels()).toEqual(
|
||||
new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
|
||||
);
|
||||
expect(idx.dropLabel(11).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
);
|
||||
expect(idx.dropLabel(5).labels()).toEqual(
|
||||
new Int32Array([0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("DenseInt32Index", () => {
|
||||
describe("DenseInt32Index", () => {
|
||||
const idx = new Dataframe.DenseInt32Index([99, 1002, 48, 0, 22]);
|
||||
|
||||
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
||||
test("create", () => {
|
||||
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22]));
|
||||
expect(idx.size()).toEqual(5);
|
||||
expect(idx.getOffset(1002)).toEqual(1);
|
||||
expect(idx.getOffset(0)).toEqual(3);
|
||||
expect(idx.getLabel(0)).toEqual(99);
|
||||
expect(idx.getLabels(new Int32Array([2, 4]))).toEqual(
|
||||
new Int32Array([48, 22])
|
||||
);
|
||||
expect(idx.getLabels([2, 4])).toEqual([48, 22]);
|
||||
expect(idx.getOffsets([0, 48])).toEqual([3, 2]);
|
||||
test("labels", () => {
|
||||
expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22]));
|
||||
expect(idx.size()).toEqual(5);
|
||||
expect(idx.getLabel(0)).toEqual(99);
|
||||
expect(idx.getLabels(new Int32Array([2, 4]))).toEqual(
|
||||
new Int32Array([48, 22])
|
||||
);
|
||||
expect(idx.getLabels([2, 4])).toEqual([48, 22]);
|
||||
});
|
||||
|
||||
expect(idx.subset([1002, 0, 99]).labels()).toEqual(
|
||||
new Int32Array([1002, 0, 99])
|
||||
);
|
||||
expect(idx.getOffsets(idx.subset([1002, 0, 99]).labels())).toEqual(
|
||||
new Int32Array([1, 3, 0])
|
||||
);
|
||||
expect(idx.isubset([4, 1, 2]).labels()).toEqual(
|
||||
new Int32Array([22, 1002, 48])
|
||||
);
|
||||
test("offsets", () => {
|
||||
expect(idx.getOffset(1002)).toEqual(1);
|
||||
expect(idx.getOffset(0)).toEqual(3);
|
||||
expect(idx.getOffsets([0, 48])).toEqual([3, 2]);
|
||||
});
|
||||
|
||||
expect(idx.withLabel(88).labels()).toEqual(
|
||||
new Int32Array([99, 1002, 48, 0, 22, 88])
|
||||
);
|
||||
expect(idx.withLabel(88).getOffset(88)).toEqual(5);
|
||||
expect(idx.dropLabel(48).labels()).toEqual(
|
||||
new Int32Array([99, 1002, 0, 22])
|
||||
);
|
||||
test("subset", () => {
|
||||
expect(idx.subset([1002, 0, 99]).labels()).toEqual(
|
||||
new Int32Array([1002, 0, 99])
|
||||
);
|
||||
expect(idx.getOffsets(idx.subset([1002, 0, 99]).labels())).toEqual(
|
||||
new Int32Array([1, 3, 0])
|
||||
);
|
||||
expect(() => idx.subset([-1])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
test("isubset", () => {
|
||||
expect(idx.isubset([4, 1, 2]).labels()).toEqual(
|
||||
new Int32Array([22, 1002, 48])
|
||||
);
|
||||
expect(() => idx.isubset([-1001])).toThrow(RangeError);
|
||||
expect(() => idx.isubset([1001])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
test("isubsetMask", () => {
|
||||
expect(idx.isubsetMask([true, true, true, true, true]).labels()).toEqual(
|
||||
new Int32Array([99, 1002, 48, 0, 22])
|
||||
);
|
||||
expect(
|
||||
idx.isubsetMask([false, false, false, false, false]).labels()
|
||||
).toEqual(new Int32Array([]));
|
||||
expect(idx.isubsetMask([true, true, false, true, true]).labels()).toEqual(
|
||||
new Int32Array([99, 1002, 0, 22])
|
||||
);
|
||||
expect(
|
||||
idx.isubsetMask([false, true, true, true, false]).labels()
|
||||
).toEqual(new Int32Array([1002, 48, 0]));
|
||||
expect(() => idx.isubsetMask([])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
test("withLabel", () => {
|
||||
expect(idx.withLabel(88).labels()).toEqual(
|
||||
new Int32Array([99, 1002, 48, 0, 22, 88])
|
||||
);
|
||||
expect(idx.withLabel(88).getOffset(88)).toEqual(5);
|
||||
expect(idx.withLabels([88, 99]).labels()).toEqual(
|
||||
new Int32Array([99, 1002, 48, 0, 22, 88, 99])
|
||||
);
|
||||
});
|
||||
test("dropLabel", () => {
|
||||
expect(idx.dropLabel(48).labels()).toEqual(
|
||||
new Int32Array([99, 1002, 0, 22])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("KeyIndex", () => {
|
||||
describe("KeyIndex", () => {
|
||||
const idx = new Dataframe.KeyIndex(["red", "green", "blue"]);
|
||||
|
||||
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
||||
test("create", () => {
|
||||
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
||||
expect(() => new Dataframe.KeyIndex(["dup", "dup"])).toThrow(Error);
|
||||
expect(new Dataframe.KeyIndex().size()).toEqual(0);
|
||||
});
|
||||
|
||||
expect(idx.labels()).toEqual(["red", "green", "blue"]);
|
||||
expect(idx.size()).toEqual(3);
|
||||
expect(idx.getOffset("blue")).toEqual(2);
|
||||
expect(idx.getLabel(1)).toEqual("green");
|
||||
test("labels", () => {
|
||||
expect(idx.labels()).toEqual(["red", "green", "blue"]);
|
||||
expect(idx.size()).toEqual(3);
|
||||
expect(idx.getLabel(1)).toEqual("green");
|
||||
expect(idx.getLabels([2, 0])).toEqual(["blue", "red"]);
|
||||
});
|
||||
|
||||
expect(idx.subset(["green"]).labels()).toEqual(["green"]);
|
||||
expect(idx.subset(["green", "red"]).labels()).toEqual(["green", "red"]);
|
||||
expect(idx.isubset([2, 1, 0]).labels()).toEqual(["blue", "green", "red"]);
|
||||
test("offsets", () => {
|
||||
expect(idx.getOffset("blue")).toEqual(2);
|
||||
});
|
||||
|
||||
expect(idx.withLabel("yo").labels()).toEqual([
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
"yo",
|
||||
]);
|
||||
expect(idx.withLabel("yo").getOffset("yo")).toEqual(3);
|
||||
expect(idx.dropLabel("blue").labels()).toEqual(["red", "green"]);
|
||||
test("subset", () => {
|
||||
expect(idx.subset(["green"]).labels()).toEqual(["green"]);
|
||||
expect(idx.subset(["green", "red"]).labels()).toEqual(["green", "red"]);
|
||||
});
|
||||
|
||||
test("isubset", () => {
|
||||
expect(idx.isubset([2, 1, 0]).labels()).toEqual(["blue", "green", "red"]);
|
||||
expect(() => idx.isubset([-1001])).toThrow(RangeError);
|
||||
expect(() => idx.isubset([1001])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
test("isubsetMask", () => {
|
||||
expect(idx.isubsetMask([true, true, true]).labels()).toEqual([
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
]);
|
||||
expect(idx.isubsetMask([false, false, false]).labels()).toEqual([]);
|
||||
expect(idx.isubsetMask([true, false, true]).labels()).toEqual([
|
||||
"red",
|
||||
"blue",
|
||||
]);
|
||||
expect(() => idx.isubsetMask([])).toThrow(RangeError);
|
||||
});
|
||||
|
||||
test("withLabel", () => {
|
||||
expect(idx.withLabel("yo").labels()).toEqual([
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
"yo",
|
||||
]);
|
||||
expect(idx.withLabel("yo").getOffset("yo")).toEqual(3);
|
||||
expect(idx.withLabels(["hey", "there"]).labels()).toEqual([
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
"hey",
|
||||
"there",
|
||||
]);
|
||||
});
|
||||
|
||||
test("dropLabel", () => {
|
||||
expect(idx.dropLabel("blue").labels()).toEqual(["red", "green"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -71,4 +71,26 @@ describe("PromiseLimit", () => {
|
||||
]);
|
||||
expect(result).toEqual(["OK", "not OK", "OK", "not OK"]);
|
||||
});
|
||||
|
||||
test("priority queue", async () => {
|
||||
const plimit = new PromiseLimit(1);
|
||||
|
||||
let finishOrder = 0;
|
||||
const callback = () => async () => {
|
||||
await delay(100);
|
||||
const result = finishOrder;
|
||||
finishOrder += 1;
|
||||
return result;
|
||||
};
|
||||
|
||||
const result = await Promise.all([
|
||||
plimit.add(callback()),
|
||||
plimit.priorityAdd(4, callback()),
|
||||
plimit.priorityAdd(0, callback()),
|
||||
plimit.priorityAdd(1, callback()),
|
||||
plimit.priorityAdd(-1, callback()),
|
||||
]);
|
||||
|
||||
expect(result).toEqual([0, 4, 2, 3, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import * as Universe from "../../../src/util/stateManager/universe";
|
||||
import { matrixFBSToDataframe } from "../../../src/util/stateManager/matrix";
|
||||
import * as Dataframe from "../../../src/util/dataframe";
|
||||
import * as REST from "./sampleResponses";
|
||||
|
||||
describe("createUniverseFromResponse", () => {
|
||||
/*
|
||||
test createUniverseFromResponse - this function converts
|
||||
a set of REST 0.2 responses into a "new" Universe.
|
||||
|
||||
createUniverseFromResponse(
|
||||
configResponse,
|
||||
schemaResponse,
|
||||
annotationsObsResponse,
|
||||
annotationsVarResponse,
|
||||
layoutObsResponse
|
||||
) --> Universe
|
||||
|
||||
where:
|
||||
configResponse: GET /.../config
|
||||
schemaResponse: GET /.../schema
|
||||
annotationsObsResponse: GET /.../annotations/obs
|
||||
annotationsVarResponse: GET /.../annotations/var
|
||||
layoutObsResponse: GET /.../layout/obs
|
||||
|
||||
See spec in docs/REST_API.md.
|
||||
*/
|
||||
|
||||
test("create from test data", () => {
|
||||
/*
|
||||
create a universe from sample data nad validate its shape & contents
|
||||
*/
|
||||
const { nObs, nVar } = REST.schema.schema.dataframe;
|
||||
let universe = Universe.createUniverseFromResponse(
|
||||
REST.config,
|
||||
REST.schema
|
||||
);
|
||||
expect(universe).toBeDefined();
|
||||
expect(universe).toMatchObject(
|
||||
expect.objectContaining({
|
||||
nObs,
|
||||
nVar,
|
||||
schema: REST.schema.schema,
|
||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||
varAnnotations: expect.any(Dataframe.Dataframe),
|
||||
obsLayout: expect.any(Dataframe.Dataframe),
|
||||
varData: expect.any(Dataframe.Dataframe),
|
||||
})
|
||||
);
|
||||
|
||||
universe = {
|
||||
...universe,
|
||||
...Universe.addObsAnnotations(
|
||||
universe,
|
||||
matrixFBSToDataframe(REST.annotationsObs)
|
||||
),
|
||||
...Universe.addVarAnnotations(
|
||||
universe,
|
||||
matrixFBSToDataframe(REST.annotationsVar)
|
||||
),
|
||||
...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)),
|
||||
};
|
||||
|
||||
expect(universe).toMatchObject(
|
||||
expect.objectContaining({
|
||||
nObs,
|
||||
nVar,
|
||||
schema: REST.schema.schema,
|
||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||
varAnnotations: expect.any(Dataframe.Dataframe),
|
||||
obsLayout: expect.any(Dataframe.Dataframe),
|
||||
varData: expect.any(Dataframe.Dataframe),
|
||||
})
|
||||
);
|
||||
|
||||
expect(universe.obsAnnotations.dims).toEqual([
|
||||
nObs,
|
||||
REST.schema.schema.annotations.obs.columns.length,
|
||||
]);
|
||||
expect(universe.obsLayout.dims).toEqual([nObs, 2]);
|
||||
expect(universe.obsLayout.colIndex.labels()).toEqual(
|
||||
universe.schema.layout.obs[0].dims
|
||||
);
|
||||
expect(universe.varAnnotations.dims).toEqual([
|
||||
nVar,
|
||||
REST.schema.schema.annotations.var.columns.length,
|
||||
]);
|
||||
expect(universe.varData.isEmpty()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,202 +0,0 @@
|
||||
import _ from "lodash";
|
||||
import * as Universe from "../../../src/util/stateManager/universe";
|
||||
import { matrixFBSToDataframe } from "../../../src/util/stateManager/matrix";
|
||||
import * as World from "../../../src/util/stateManager/world";
|
||||
import * as Dataframe from "../../../src/util/dataframe";
|
||||
import Crossfilter from "../../../src/util/typedCrossfilter";
|
||||
import { DimTypes } from "../../../src/util/typedCrossfilter/crossfilter";
|
||||
import * as REST from "./sampleResponses";
|
||||
import {
|
||||
obsAnnoDimensionName,
|
||||
layoutDimensionName,
|
||||
} from "../../../src/util/nameCreators";
|
||||
|
||||
/*
|
||||
Helper - creates universe, world, corssfilter and dimensionMap from
|
||||
the default REST test response.
|
||||
*/
|
||||
const defaultBigBang = () => {
|
||||
/* create unverse, world, crossfilter and dimensionMap */
|
||||
/* create universe */
|
||||
let universe = Universe.createUniverseFromResponse(
|
||||
_.cloneDeep(REST.config),
|
||||
_.cloneDeep(REST.schema)
|
||||
);
|
||||
|
||||
universe = {
|
||||
...universe,
|
||||
...Universe.addObsAnnotations(
|
||||
universe,
|
||||
matrixFBSToDataframe(REST.annotationsObs)
|
||||
),
|
||||
...Universe.addVarAnnotations(
|
||||
universe,
|
||||
matrixFBSToDataframe(REST.annotationsVar)
|
||||
),
|
||||
...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)),
|
||||
};
|
||||
|
||||
/* create world */
|
||||
const world = World.createWorldFromEntireUniverse(universe);
|
||||
/* create crossfilter */
|
||||
const crossfilter = World.createObsDimensions(
|
||||
new Crossfilter(world.obsAnnotations),
|
||||
world,
|
||||
REST.schema.schema.layout.obs[0].dims
|
||||
);
|
||||
|
||||
return {
|
||||
universe,
|
||||
world,
|
||||
crossfilter,
|
||||
};
|
||||
};
|
||||
|
||||
describe("createWorldFromEntireUniverse", () => {
|
||||
test("create from REST sample", () => {
|
||||
const universe = Universe.createUniverseFromResponse(
|
||||
_.cloneDeep(REST.config),
|
||||
_.cloneDeep(REST.schema),
|
||||
matrixFBSToDataframe(_.cloneDeep(REST.annotationsObs)),
|
||||
matrixFBSToDataframe(_.cloneDeep(REST.annotationsVar)),
|
||||
matrixFBSToDataframe(_.cloneDeep(REST.layoutObs))
|
||||
);
|
||||
expect(universe).toBeDefined();
|
||||
|
||||
const world = World.createWorldFromEntireUniverse(universe);
|
||||
expect(world).toBeDefined();
|
||||
|
||||
expect(world).toMatchObject(
|
||||
expect.objectContaining({
|
||||
nObs: universe.nObs,
|
||||
nVar: universe.nVar,
|
||||
schema: universe.schema,
|
||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||
varAnnotations: expect.any(Dataframe.Dataframe),
|
||||
obsLayout: expect.any(Dataframe.Dataframe),
|
||||
varData: expect.any(Dataframe.Dataframe),
|
||||
clipQuantiles: { min: 0, max: 1 },
|
||||
unclipped: {
|
||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||
varData: expect.any(Dataframe.Dataframe),
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createWorldFromCurrentSelection", () => {
|
||||
test("create from REST sample", () => {
|
||||
const {
|
||||
universe,
|
||||
world: originalWorld,
|
||||
crossfilter: originalCrossfilter,
|
||||
} = defaultBigBang();
|
||||
|
||||
/* mock a selection */
|
||||
const crossfilter = originalCrossfilter
|
||||
.select(obsAnnoDimensionName("field1"), { mode: "range", lo: 0, hi: 5 })
|
||||
.select(obsAnnoDimensionName("field3"), {
|
||||
mode: "exact",
|
||||
values: [false],
|
||||
});
|
||||
|
||||
/* create the world from the selection */
|
||||
const world = World.createWorldBySelection(
|
||||
universe,
|
||||
originalWorld,
|
||||
crossfilter
|
||||
);
|
||||
expect(world).toBeDefined();
|
||||
expect(world.nObs).toEqual(crossfilter.countSelected());
|
||||
|
||||
/*
|
||||
calculate expected values and match against result
|
||||
*/
|
||||
|
||||
/* matchFilter must match the dimension filters above */
|
||||
const matchFilter = (df, row) => {
|
||||
const field1 = df.at(row, "field1");
|
||||
const field3 = df.at(row, "field3");
|
||||
return field1 >= 0 && field1 < 5 && !field3;
|
||||
};
|
||||
const matchingIndices = _()
|
||||
.range(universe.nObs)
|
||||
.filter((idx) => matchFilter(universe.obsAnnotations, idx))
|
||||
.value();
|
||||
|
||||
expect(world).toMatchObject(
|
||||
expect.objectContaining({
|
||||
nObs: matchingIndices.length,
|
||||
nVar: universe.nVar,
|
||||
schema: universe.schema,
|
||||
clipQuantiles: { min: 0, max: 1 },
|
||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||
varAnnotations: expect.any(Dataframe.Dataframe),
|
||||
obsLayout: expect.any(Dataframe.Dataframe),
|
||||
varData: expect.any(Dataframe.Dataframe),
|
||||
unclipped: {
|
||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||
varData: expect.any(Dataframe.Dataframe),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(world.obsAnnotations.rowIndex.labels()).toEqual(
|
||||
new Int32Array(matchingIndices)
|
||||
);
|
||||
expect(world.obsAnnotations.colIndex.labels()).toEqual(
|
||||
universe.obsAnnotations.colIndex.labels()
|
||||
);
|
||||
expect(world.obsLayout.rowIndex.labels()).toEqual(
|
||||
new Int32Array(matchingIndices)
|
||||
);
|
||||
expect(world.obsLayout.colIndex.labels()).toEqual(
|
||||
world.schema.layout.obs[0].dims
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createObsDimensionMap", () => {
|
||||
test("when universe eq world", () => {
|
||||
/*
|
||||
check for:
|
||||
- creates a dimension for all obsAnnotations, PLUS X/Y layout
|
||||
- check that dimension typing is sane
|
||||
*/
|
||||
|
||||
const { crossfilter } = defaultBigBang();
|
||||
const annotationNames = _.map(
|
||||
REST.schema.schema.annotations.obs.columns,
|
||||
(c) => c.name
|
||||
);
|
||||
const obsIndexColName = REST.schema.schema.annotations.obs.index;
|
||||
const schemaByObsName = _.keyBy(
|
||||
REST.schema.schema.annotations.obs.columns,
|
||||
"name"
|
||||
);
|
||||
expect(crossfilter).toBeDefined();
|
||||
annotationNames.forEach((name) => {
|
||||
const dim = crossfilter.dimensions[obsAnnoDimensionName(name)];
|
||||
if (name === obsIndexColName) {
|
||||
expect(dim).toBeUndefined();
|
||||
} else {
|
||||
const { type } = schemaByObsName[name];
|
||||
if (type === "string" || type === "boolean" || type === "categorical") {
|
||||
expect(dim.dim).toBeInstanceOf(DimTypes.enum);
|
||||
} else {
|
||||
expect(dim.dim).toBeInstanceOf(DimTypes.scalar);
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(
|
||||
crossfilter.dimensions[layoutDimensionName("XY")].dim
|
||||
).toBeInstanceOf(DimTypes.spatial);
|
||||
});
|
||||
});
|
||||
|
||||
describe("worldEqUniverse", () => {
|
||||
const { universe, world } = defaultBigBang();
|
||||
const result = World.worldEqUniverse(world, universe);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
@@ -253,6 +253,11 @@ describe("ImmutableTypedCrossfilter", () => {
|
||||
p.select("quantity", { mode: "exact", values: v }).countSelected()
|
||||
).toEqual(_.filter(someData, (d) => v.includes(d.quantity)).length)
|
||||
);
|
||||
test("single value exact", () => {
|
||||
expect(
|
||||
p.select("quantity", { mode: "exact", values: 2 }).countSelected()
|
||||
).toEqual(_.filter(someData, (d) => d.quantity === 2).length);
|
||||
});
|
||||
test.each([
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
@@ -295,6 +300,11 @@ describe("ImmutableTypedCrossfilter", () => {
|
||||
p.select("type", { mode: "exact", values: v }).countSelected()
|
||||
).toEqual(_.filter(someData, (d) => v.includes(d.type)).length)
|
||||
);
|
||||
test("single value exact", () => {
|
||||
expect(
|
||||
p.select("type", { mode: "exact", values: "tab" }).countSelected()
|
||||
).toEqual(_.filter(someData, (d) => d.type === "tab").length);
|
||||
});
|
||||
test("range", () => {
|
||||
expect(() => p.select("type", { mode: "range", lo: 0, hi: 9 })).toThrow(
|
||||
Error
|
||||
|
||||
Reference in New Issue
Block a user