mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-05 01:58:11 +08:00
Gene sets UI, right sidebar refactor (#2097)
* prototyping
* render histos on open gene set
* prototyping
* render histos on open gene set
* factor out add genes to own component
* remove unused import
* mock reducer
* color by geneset stub
* menus and buttons
* geneset dialogue stub
* remove heatmap mock
* componetize histogram
* reenable add genes
* re-add isuserdefined
* test data
* remove have fetched
* add isExpanded state to gene, and pass to histogram
* expand button
* toggleable
* mini
* bump number of genes to 50
* don't clear diffexp on subset
* move create category to top
* render diffexp as geneset
* geneset show mean expression
* gene set reducer
* add geneset UI reducer
* wire e2e gene set loading prototype
* fix sniffing bug
* fix typo
* add gene modals
* client/src/actions/
* add autosave
* rename data-dir cli param
* add geneset, add gene, delete set
* prototype: remove csv upload placeholder
* handle delete gene from set
* prepopulate geneset with genes from modal
* add geneset: rename action
* icons, language consistency
* chevron after
* handle empty string case on genes for create geneset
* edit geneset
* fix language on create
* copy correction
* add popper2
upgrade react popper
upgrade react popper
adding popover2 package
* truncate uses tooltip2
* gene set button text typo
* remove logging
* moving server over
* remove test imports
* don't try to destructure map, use array.from
* fix add gene map datastructure error
* Revert "fix add gene map datastructure error"
This reverts commit b0eed45952.
* name --> genesetName, genes --> geneSymbols
* add gene to geneset, temporary format
* handle empty case, clear form input
* lint -- genesets wasn't passed via props
* userinfo
* move genes string to object conversion to action
* remove tmp gene description
* emptystring default for description
* remove empty string
* remove top level package json
* remove package lock as well
* remove flag for feature toggle
* remove comments in geneset
* comment cleanup
* remove comment
* revert diffexp genes to 10
* color by gene set
* disable color by gene set
* Gene menus are now inline, remove dead prototype code
* remove todo, magic number to variable
* remove jshint in rightsidebar
Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
* remove unused geneset validation code
* tmp format pending geneset description
* move magic number into variable
* reorganize genesetsUI reducer pending tests
* rewire edit given new action name
* add basic validation and feedback for geneset name uniqueness
* mv annoDialog
* mv label, repair paths
* Update client/src/components/brushableHistogram/header.js
Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
* add imports for icon in histo
* update jest snapshots given blueprint/tooltip2 usage of index -1
* ensure no empty paragraph
* intent from blueprint
* remove remainder of jshint references
* do not push undo when autosave fires
* fix autosave bugs
* remove todos
* clamp to util
* scient to util
* revert clearing diffexp
* rename value to be more specific stacked bar
* clean up logging and commetns
* remove gene entry tests pending rewrite
* tab index -1
* update jest snapshot, blueprint tooltip 2
* caret margin
* snapshot update
* ensure histogram is centered
* add geneset actions to config
* comment maybeScientific
* comment clamp
* comment ui reducer
* remove prototype code
* remove error log
* remove references to bl.ocks
* componetize parseBulkGeneString
* catch case where geneset rename same name
* genesetui reducer tests
* add geneset ui to index reducer config
Co-authored-by: bkmartinjr <bruce@chanzuckerberg.com>
Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
This commit is contained in:
co-authored by
Severiano Badajoz
bkmartinjr
parent
5335c39184
commit
e6e358ddc8
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
exports[`did launch page launched 1`] = `"<span style=\\"max-width: 155px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`;
|
exports[`did launch page launched 1`] = `"<span style=\\"max-width: 155px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`;
|
||||||
|
|
||||||
exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\" style=\\"max-width: 265px;\\"><span data-testid=\\"louvain:category-label\\" aria-label=\\"louvain\\" class=\\"\\" tabindex=\\"0\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div><div></div>"`;
|
exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover2-target\\" tabindex=\\"0\\"><span data-testid=\\"louvain:category-label\\" aria-label=\\"louvain\\" class=\\"\\" tabindex=\\"-1\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div><div></div>"`;
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
exports[`annotations stacked bar graph renders 1`] = `
|
exports[`annotations stacked bar graph renders 1`] = `
|
||||||
Array [
|
Array [
|
||||||
"<div 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\\" 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 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: 100%; 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; padding: 0px;\\"><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 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-popover2-target\\" tabindex=\\"0\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"-1\\" 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: 100%; 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; padding: 0px;\\"><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></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: 15px; height: 15px; 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 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\\" 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 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: 100%; 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; padding: 0px;\\"><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 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;\\">2133</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 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-popover2-target\\" tabindex=\\"0\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"-1\\" 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: 100%; 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; padding: 0px;\\"><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></div><span style=\\"flex-shrink: 0;\\"><canvas 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;\\">2133</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; 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`] = `
|
exports[`annotations stacked bar graph renders 2`] = `
|
||||||
Array [
|
Array [
|
||||||
"<div 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\\" 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 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: 100%; 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; padding: 0px;\\"><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 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-popover2-target\\" tabindex=\\"0\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"-1\\" 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: 100%; 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; padding: 0px;\\"><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></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: 15px; height: 15px; 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 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\\" 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 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: 100%; 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; padding: 0px;\\"><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 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 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-popover2-target\\" tabindex=\\"0\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"-1\\" 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: 100%; 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; padding: 0px;\\"><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></div><span style=\\"flex-shrink: 0;\\"><canvas 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: 15px; height: 15px; 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 truncate midpoint whitespace 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
exports[`annotations truncate midpoint whitespace 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"-1\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
||||||
|
|
||||||
exports[`annotations truncate midpoint whitespace 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
exports[`annotations truncate midpoint whitespace 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"-1\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
||||||
|
|
||||||
exports[`annotations truncate single character 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
exports[`annotations truncate single character 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"-1\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
||||||
|
|
||||||
exports[`annotations truncate single character 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
exports[`annotations truncate single character 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"-1\\" style=\\"width: 187px; 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: 100%; 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; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
||||||
|
|||||||
@@ -15,23 +15,17 @@ import {
|
|||||||
getOneElementInnerHTML,
|
getOneElementInnerHTML,
|
||||||
getTestId,
|
getTestId,
|
||||||
goToPage,
|
goToPage,
|
||||||
typeInto,
|
|
||||||
waitByID,
|
waitByID,
|
||||||
clickOnUntil,
|
clickOnUntil,
|
||||||
} from "./puppeteerUtils";
|
} from "./puppeteerUtils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addGeneToSearch,
|
|
||||||
bulkAddGenes,
|
|
||||||
calcDragCoordinates,
|
calcDragCoordinates,
|
||||||
clip,
|
clip,
|
||||||
drag,
|
drag,
|
||||||
getAllCategoriesAndCounts,
|
getAllCategoriesAndCounts,
|
||||||
getAllHistograms,
|
|
||||||
getCellSetCount,
|
getCellSetCount,
|
||||||
runDiffExp,
|
|
||||||
selectCategory,
|
selectCategory,
|
||||||
subset,
|
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
} from "./cellxgeneActions";
|
} from "./cellxgeneActions";
|
||||||
@@ -149,47 +143,6 @@ describe("cell selection", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("gene entry", () => {
|
|
||||||
test("search for single gene", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
await addGeneToSearch(data.genes.search);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("bulk add genes", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
|
|
||||||
const testGenes = data.genes.bulkadd;
|
|
||||||
|
|
||||||
await bulkAddGenes(testGenes);
|
|
||||||
const allHistograms = await getAllHistograms(
|
|
||||||
"histogram-user-gene",
|
|
||||||
testGenes
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(allHistograms).toEqual(expect.arrayContaining(testGenes));
|
|
||||||
expect(allHistograms).toHaveLength(testGenes.length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("differential expression", () => {
|
|
||||||
test("selects cells, saves them and performs diffexp", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
|
|
||||||
await runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
|
|
||||||
|
|
||||||
const allHistograms = await getAllHistograms(
|
|
||||||
"histogram-diffexp",
|
|
||||||
data.diffexp["gene-results"]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(allHistograms).toEqual(
|
|
||||||
expect.arrayContaining(data.diffexp["gene-results"])
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(allHistograms).toHaveLength(data.diffexp["gene-results"].length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("subset", () => {
|
describe("subset", () => {
|
||||||
test("subset - cell count matches", async () => {
|
test("subset - cell count matches", async () => {
|
||||||
await goToPage(appUrlBase);
|
await goToPage(appUrlBase);
|
||||||
@@ -236,94 +189,6 @@ describe("subset", () => {
|
|||||||
const cellCount = await getCellSetCount(1);
|
const cellCount = await getCellSetCount(1);
|
||||||
expect(cellCount).toBe(data.subset.lasso.count);
|
expect(cellCount).toBe(data.subset.lasso.count);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("undo selection appends the top diff exp genes to user defined genes", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
|
|
||||||
const userDefinedGenes = data.genes.bulkadd;
|
|
||||||
const diffExpGenes = data.diffexp["gene-results"];
|
|
||||||
|
|
||||||
await bulkAddGenes(userDefinedGenes);
|
|
||||||
const userDefinedHistograms = await getAllHistograms(
|
|
||||||
"histogram-user-gene",
|
|
||||||
userDefinedGenes
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(userDefinedHistograms).toEqual(
|
|
||||||
expect.arrayContaining(userDefinedGenes)
|
|
||||||
);
|
|
||||||
|
|
||||||
await subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 });
|
|
||||||
await runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
|
|
||||||
|
|
||||||
const diffExpHistograms = await getAllHistograms(
|
|
||||||
"histogram-diffexp",
|
|
||||||
diffExpGenes
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
|
|
||||||
|
|
||||||
await clickOn("reset-subset-button");
|
|
||||||
const expected = [].concat(userDefinedGenes, diffExpGenes);
|
|
||||||
const userDefinedHistogramsAfterSubset = await getAllHistograms(
|
|
||||||
"histogram-user-gene",
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(userDefinedHistogramsAfterSubset).toEqual(
|
|
||||||
expect.arrayContaining(expected)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("subset selection appends the top diff exp genes to user defined genes", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
|
|
||||||
const userDefinedGenes = data.genes.bulkadd;
|
|
||||||
const diffExpGenes = data.diffexp["gene-results"];
|
|
||||||
|
|
||||||
await bulkAddGenes(userDefinedGenes);
|
|
||||||
const userDefinedHistograms = await getAllHistograms(
|
|
||||||
"histogram-user-gene",
|
|
||||||
userDefinedGenes
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(userDefinedHistograms).toEqual(
|
|
||||||
expect.arrayContaining(userDefinedGenes)
|
|
||||||
);
|
|
||||||
|
|
||||||
await subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 });
|
|
||||||
await runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
|
|
||||||
|
|
||||||
const diffExpHistograms = await getAllHistograms(
|
|
||||||
"histogram-diffexp",
|
|
||||||
diffExpGenes
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
|
|
||||||
|
|
||||||
await subset({ x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97 });
|
|
||||||
|
|
||||||
const expected = [].concat(userDefinedGenes, diffExpGenes);
|
|
||||||
const userDefinedHistogramsAfterSubset = await getAllHistograms(
|
|
||||||
"histogram-user-gene",
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(userDefinedHistogramsAfterSubset).toEqual(
|
|
||||||
expect.arrayContaining(expected)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("scatter plot", () => {
|
|
||||||
test("scatter plot appears", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
|
|
||||||
await bulkAddGenes(Object.values(data.scatter.genes));
|
|
||||||
await clickOn(`plot-x-${data.scatter.genes.x}`);
|
|
||||||
await clickOn(`plot-y-${data.scatter.genes.y}`);
|
|
||||||
await waitByID("scatterplot");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("clipping", () => {
|
describe("clipping", () => {
|
||||||
@@ -340,30 +205,6 @@ describe("clipping", () => {
|
|||||||
const cellCount = await getCellSetCount(1);
|
const cellCount = await getCellSetCount(1);
|
||||||
expect(cellCount).toBe(data.clip.count);
|
expect(cellCount).toBe(data.clip.count);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("clip gene", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
|
|
||||||
await typeInto("gene-search", data.clip.gene);
|
|
||||||
await page.keyboard.press("Enter");
|
|
||||||
|
|
||||||
await page.waitForSelector(`[data-testid='histogram-${data.clip.gene}']`);
|
|
||||||
|
|
||||||
await clip(data.clip.min, data.clip.max);
|
|
||||||
|
|
||||||
const histBrushableAreaId = `histogram-${data.clip.gene}-plot-brushable-area`;
|
|
||||||
|
|
||||||
const coords = await calcDragCoordinates(
|
|
||||||
histBrushableAreaId,
|
|
||||||
data.clip["coordinates-as-percent"]
|
|
||||||
);
|
|
||||||
|
|
||||||
await drag(histBrushableAreaId, coords.start, coords.end);
|
|
||||||
|
|
||||||
const cellCount = await getCellSetCount(1);
|
|
||||||
|
|
||||||
expect(cellCount).toBe(data.clip["gene-cell-count"]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// interact with UI elements just that they do not break
|
// interact with UI elements just that they do not break
|
||||||
@@ -381,17 +222,6 @@ describe("ui elements don't error", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("color by for gene", async () => {
|
|
||||||
await goToPage(appUrlBase);
|
|
||||||
|
|
||||||
await typeInto("gene-search", data.genes.search);
|
|
||||||
await page.keyboard.press("Enter");
|
|
||||||
await page.waitForSelector(
|
|
||||||
`[data-testid='histogram-${data.genes.search}']`
|
|
||||||
);
|
|
||||||
await clickOn(`colorby-${data.genes.search}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("pan and zoom", async () => {
|
test("pan and zoom", async () => {
|
||||||
await goToPage(appUrlBase);
|
await goToPage(appUrlBase);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import genesetsUIReducer from "../../src/reducers/genesetsUI";
|
||||||
|
|
||||||
|
// Format: GeneSetsUI(state,action)
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
createGenesetModeActive: false,
|
||||||
|
isEditingGenesetName: false,
|
||||||
|
isAddingGenesToGeneset: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* initial */
|
||||||
|
describe("geneset UI states", () => {
|
||||||
|
test("initial state, some other action", () => {
|
||||||
|
expect(
|
||||||
|
genesetsUIReducer(undefined, {
|
||||||
|
type: "foo",
|
||||||
|
})
|
||||||
|
).toMatchObject(initialState);
|
||||||
|
});
|
||||||
|
test("geneset: activate add new geneset mode", () => {
|
||||||
|
expect(
|
||||||
|
genesetsUIReducer(initialState, {
|
||||||
|
type: "geneset: activate add new geneset mode",
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
createGenesetModeActive: true,
|
||||||
|
isEditingGenesetName: false,
|
||||||
|
isAddingGenesToGeneset: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test("geneset: disable create geneset mode", () => {
|
||||||
|
expect(
|
||||||
|
genesetsUIReducer(undefined, { isEditingGenesetName: false })
|
||||||
|
).toMatchObject(initialState);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("activate add new genes mode", () => {
|
||||||
|
expect(
|
||||||
|
genesetsUIReducer(undefined, {
|
||||||
|
type: "geneset: activate add new genes mode",
|
||||||
|
geneset: "a geneset name",
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
createGenesetModeActive: false,
|
||||||
|
isEditingGenesetName: false,
|
||||||
|
isAddingGenesToGeneset: "a geneset name",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test("disable create geneset mode", () => {
|
||||||
|
expect(
|
||||||
|
genesetsUIReducer(undefined, {
|
||||||
|
type: "geneset: disable create geneset mode",
|
||||||
|
})
|
||||||
|
).toMatchObject(initialState);
|
||||||
|
});
|
||||||
|
test("activate rename geneset mode", () => {
|
||||||
|
expect(
|
||||||
|
genesetsUIReducer(undefined, {
|
||||||
|
type: "geneset: activate rename geneset mode",
|
||||||
|
data: "a geneset name",
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
createGenesetModeActive: false,
|
||||||
|
isEditingGenesetName: "a geneset name",
|
||||||
|
isAddingGenesToGeneset: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test("disable rename geneset mode", () => {
|
||||||
|
expect(
|
||||||
|
genesetsUIReducer(undefined, {
|
||||||
|
type: "geneset: disable rename geneset mode",
|
||||||
|
})
|
||||||
|
).toMatchObject(initialState);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
|
|
||||||
import BitArray from "../../../src/util/typedCrossfilter/bitArray";
|
import BitArray from "../../../src/util/typedCrossfilter/bitArray";
|
||||||
|
|
||||||
const defaultTestLength = 8;
|
const defaultTestLength = 8;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
|
|
||||||
// const PositiveIntervals = require("../../src/util/typedCrossfilter/positiveIntervals");
|
// const PositiveIntervals = require("../../src/util/typedCrossfilter/positiveIntervals");
|
||||||
import PositiveIntervals from "../../../src/util/typedCrossfilter/positiveIntervals";
|
import PositiveIntervals from "../../../src/util/typedCrossfilter/positiveIntervals";
|
||||||
|
|
||||||
|
|||||||
Generated
+168
-87
@@ -4154,6 +4154,21 @@
|
|||||||
"react-transition-group": "^2.9.0",
|
"react-transition-group": "^2.9.0",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"tslib": "~1.10.0"
|
"tslib": "~1.10.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-popper": {
|
||||||
|
"version": "1.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.10.tgz",
|
||||||
|
"integrity": "sha512-sZfwHtHCMst0L0G/c83/Y/K1f9fNWMEKsk/cGAor68rQBHB75WuDQ7k95tkce8QNaUHhg9uFXIJbZO0eWRvJbw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"@hypnosphi/create-react-context": "^0.3.1",
|
||||||
|
"popper.js": "^1.14.4",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"typed-styles": "^0.0.7",
|
||||||
|
"warning": "^4.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@blueprintjs/eslint-plugin": {
|
"@blueprintjs/eslint-plugin": {
|
||||||
@@ -4375,6 +4390,68 @@
|
|||||||
"tslib": "~1.10.0"
|
"tslib": "~1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@blueprintjs/popover2": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blueprintjs/popover2/-/popover2-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-EtxKoaafkn3FRLKYh0V2CdtgPBgtSoOxIzrT7eZil5XBgEQiYap1lzyc/Rm+N+4yeW1+iOB6uY/3gdE4CRiPIg==",
|
||||||
|
"requires": {
|
||||||
|
"@blueprintjs/core": "^3.39.0",
|
||||||
|
"@popperjs/core": "^2.5.4",
|
||||||
|
"classnames": "^2.2",
|
||||||
|
"dom4": "^2.1.5",
|
||||||
|
"react-popper": "^2.2.4",
|
||||||
|
"tslib": "~1.13.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@blueprintjs/core": {
|
||||||
|
"version": "3.39.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blueprintjs/core/-/core-3.39.0.tgz",
|
||||||
|
"integrity": "sha512-Pl92Qvfo0iowvJ81hOxfOXgBUuqHdMgtYao7hCvhF5hvz8wYRfOr1GHChVsiD5NvpNmG2rtUTYqkfft06Tyj5A==",
|
||||||
|
"requires": {
|
||||||
|
"@blueprintjs/icons": "^3.24.0",
|
||||||
|
"@types/dom4": "^2.0.1",
|
||||||
|
"classnames": "^2.2",
|
||||||
|
"dom4": "^2.1.5",
|
||||||
|
"normalize.css": "^8.0.1",
|
||||||
|
"popper.js": "^1.16.1",
|
||||||
|
"react-lifecycles-compat": "^3.0.4",
|
||||||
|
"react-popper": "^1.3.7",
|
||||||
|
"react-transition-group": "^2.9.0",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
|
"tslib": "~1.13.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-popper": {
|
||||||
|
"version": "1.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.10.tgz",
|
||||||
|
"integrity": "sha512-sZfwHtHCMst0L0G/c83/Y/K1f9fNWMEKsk/cGAor68rQBHB75WuDQ7k95tkce8QNaUHhg9uFXIJbZO0eWRvJbw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"@hypnosphi/create-react-context": "^0.3.1",
|
||||||
|
"popper.js": "^1.14.4",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"typed-styles": "^0.0.7",
|
||||||
|
"warning": "^4.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@blueprintjs/icons": {
|
||||||
|
"version": "3.24.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.24.0.tgz",
|
||||||
|
"integrity": "sha512-OvDDI5EUueS1Y3t594iS8LAGoHhLhYjC2GuN/01a85n+ASLSp0jf0/+uix2JeCOj41iTdRRCINbWuRwVQNNGPw==",
|
||||||
|
"requires": {
|
||||||
|
"classnames": "^2.2",
|
||||||
|
"tslib": "~1.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@blueprintjs/select": {
|
"@blueprintjs/select": {
|
||||||
"version": "3.13.5",
|
"version": "3.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@blueprintjs/select/-/select-3.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@blueprintjs/select/-/select-3.13.5.tgz",
|
||||||
@@ -4503,6 +4580,15 @@
|
|||||||
"@hapi/hoek": "^8.3.0"
|
"@hapi/hoek": "^8.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@hypnosphi/create-react-context": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==",
|
||||||
|
"requires": {
|
||||||
|
"gud": "^1.0.0",
|
||||||
|
"warning": "^4.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@istanbuljs/load-nyc-config": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||||
@@ -5460,6 +5546,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@popperjs/core": {
|
||||||
|
"version": "2.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.8.4.tgz",
|
||||||
|
"integrity": "sha512-h0lY7g36rhjNV8KVHKS3/BEOgfsxu0AiRI8+ry5IFBGEsQFkpjxtcpVc9ndN8zrKUeMZXAWMc7eQMepfgykpxQ=="
|
||||||
|
},
|
||||||
"@sentry/cli": {
|
"@sentry/cli": {
|
||||||
"version": "1.55.0",
|
"version": "1.55.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.55.0.tgz",
|
||||||
@@ -8229,15 +8320,6 @@
|
|||||||
"sha.js": "^2.4.8"
|
"sha.js": "^2.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create-react-context": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz",
|
|
||||||
"integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==",
|
|
||||||
"requires": {
|
|
||||||
"gud": "^1.0.0",
|
|
||||||
"warning": "^4.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cross-fetch": {
|
"cross-fetch": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz",
|
||||||
@@ -8935,19 +9017,6 @@
|
|||||||
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
|
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"deep-equal": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
|
|
||||||
"requires": {
|
|
||||||
"is-arguments": "^1.0.4",
|
|
||||||
"is-date-object": "^1.0.1",
|
|
||||||
"is-regex": "^1.0.4",
|
|
||||||
"object-is": "^1.0.1",
|
|
||||||
"object-keys": "^1.1.1",
|
|
||||||
"regexp.prototype.flags": "^1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deep-extend": {
|
"deep-extend": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
@@ -8968,6 +9037,7 @@
|
|||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||||
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"object-keys": "^1.0.12"
|
"object-keys": "^1.0.12"
|
||||||
}
|
}
|
||||||
@@ -9454,6 +9524,7 @@
|
|||||||
"version": "1.17.4",
|
"version": "1.17.4",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
|
||||||
"integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
|
"integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"es-to-primitive": "^1.2.1",
|
"es-to-primitive": "^1.2.1",
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
@@ -9472,6 +9543,7 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
||||||
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-callable": "^1.1.4",
|
"is-callable": "^1.1.4",
|
||||||
"is-date-object": "^1.0.1",
|
"is-date-object": "^1.0.1",
|
||||||
@@ -11303,7 +11375,8 @@
|
|||||||
"function-bind": {
|
"function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"functional-red-black-tree": {
|
"functional-red-black-tree": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@@ -11633,6 +11706,7 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
}
|
}
|
||||||
@@ -11645,7 +11719,8 @@
|
|||||||
"has-symbols": {
|
"has-symbols": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
|
||||||
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
|
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"has-unicode": {
|
"has-unicode": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -12276,7 +12351,6 @@
|
|||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.0.0"
|
"loose-envify": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -12316,11 +12390,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"is-arguments": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA=="
|
|
||||||
},
|
|
||||||
"is-arrayish": {
|
"is-arrayish": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
@@ -12344,7 +12413,8 @@
|
|||||||
"is-callable": {
|
"is-callable": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
|
||||||
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
|
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-ci": {
|
"is-ci": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -12389,7 +12459,8 @@
|
|||||||
"is-date-object": {
|
"is-date-object": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
|
||||||
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
|
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-descriptor": {
|
"is-descriptor": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
@@ -12543,6 +12614,7 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
|
||||||
"integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
|
"integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"has": "^1.0.3"
|
"has": "^1.0.3"
|
||||||
}
|
}
|
||||||
@@ -12589,6 +12661,7 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
|
||||||
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
|
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-symbols": "^1.0.1"
|
"has-symbols": "^1.0.1"
|
||||||
}
|
}
|
||||||
@@ -14215,6 +14288,11 @@
|
|||||||
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
|
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.assign": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
|
||||||
|
},
|
||||||
"lodash.camelcase": {
|
"lodash.camelcase": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
@@ -14233,6 +14311,16 @@
|
|||||||
"integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==",
|
"integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.flatten": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
|
||||||
|
},
|
||||||
|
"lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
|
||||||
|
},
|
||||||
"lodash.kebabcase": {
|
"lodash.kebabcase": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
|
||||||
@@ -15180,54 +15268,14 @@
|
|||||||
"object-inspect": {
|
"object-inspect": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
|
||||||
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw=="
|
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
|
||||||
},
|
"dev": true
|
||||||
"object-is": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==",
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "^1.1.3",
|
|
||||||
"es-abstract": "^1.17.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"es-abstract": {
|
|
||||||
"version": "1.17.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
|
|
||||||
"integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
|
|
||||||
"requires": {
|
|
||||||
"es-to-primitive": "^1.2.1",
|
|
||||||
"function-bind": "^1.1.1",
|
|
||||||
"has": "^1.0.3",
|
|
||||||
"has-symbols": "^1.0.1",
|
|
||||||
"is-callable": "^1.2.0",
|
|
||||||
"is-regex": "^1.1.0",
|
|
||||||
"object-inspect": "^1.7.0",
|
|
||||||
"object-keys": "^1.1.1",
|
|
||||||
"object.assign": "^4.1.0",
|
|
||||||
"string.prototype.trimend": "^1.0.1",
|
|
||||||
"string.prototype.trimstart": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-callable": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw=="
|
|
||||||
},
|
|
||||||
"is-regex": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==",
|
|
||||||
"requires": {
|
|
||||||
"has-symbols": "^1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"object-keys": {
|
"object-keys": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"object-visit": {
|
"object-visit": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@@ -15241,6 +15289,7 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
|
||||||
"integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
|
"integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.2",
|
"define-properties": "^1.1.2",
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
@@ -16822,6 +16871,15 @@
|
|||||||
"scheduler": "^0.19.1"
|
"scheduler": "^0.19.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-easy-emoji": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-easy-emoji/-/react-easy-emoji-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-TcufijpuWKgYgzbySEBukNef+y0HI/4PAJ4gc9vb1CF7Q4CcAS2ZV8VMZk0ObtKKwJJfVgAHVt86nXWOed8QXg==",
|
||||||
|
"requires": {
|
||||||
|
"lodash.assign": "^4.0.8",
|
||||||
|
"string-replace-to-array": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-fast-compare": {
|
"react-fast-compare": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
|
||||||
@@ -16866,17 +16924,19 @@
|
|||||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||||
},
|
},
|
||||||
"react-popper": {
|
"react-popper": {
|
||||||
"version": "1.3.7",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.4.tgz",
|
||||||
"integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
|
"integrity": "sha512-NacOu4zWupdQjVXq02XpTD3yFPSfg5a7fex0wa3uGKVkFK7UN6LvVxgcb+xYr56UCuWiNPMH20tntdVdJRwYew==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.1.2",
|
"react-fast-compare": "^3.0.1",
|
||||||
"create-react-context": "^0.3.0",
|
|
||||||
"deep-equal": "^1.1.1",
|
|
||||||
"popper.js": "^1.14.4",
|
|
||||||
"prop-types": "^15.6.1",
|
|
||||||
"typed-styles": "^0.0.7",
|
|
||||||
"warning": "^4.0.2"
|
"warning": "^4.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-fast-compare": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-redux": {
|
"react-redux": {
|
||||||
@@ -17090,6 +17150,7 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
|
||||||
"integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
|
"integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.0-next.1"
|
"es-abstract": "^1.17.0-next.1"
|
||||||
@@ -18438,6 +18499,16 @@
|
|||||||
"strip-ansi": "^6.0.0"
|
"strip-ansi": "^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string-replace-to-array": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-replace-to-array/-/string-replace-to-array-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-yT66mZpe4k1zGuu69auja18Y978=",
|
||||||
|
"requires": {
|
||||||
|
"invariant": "^2.2.1",
|
||||||
|
"lodash.flatten": "^4.2.0",
|
||||||
|
"lodash.isstring": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||||
@@ -18484,6 +18555,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
|
||||||
"integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
|
"integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.5"
|
"es-abstract": "^1.17.5"
|
||||||
@@ -18493,6 +18565,7 @@
|
|||||||
"version": "1.17.6",
|
"version": "1.17.6",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
|
||||||
"integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
|
"integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"es-to-primitive": "^1.2.1",
|
"es-to-primitive": "^1.2.1",
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
@@ -18510,12 +18583,14 @@
|
|||||||
"is-callable": {
|
"is-callable": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
|
||||||
"integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw=="
|
"integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-regex": {
|
"is-regex": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
|
||||||
"integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==",
|
"integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-symbols": "^1.0.1"
|
"has-symbols": "^1.0.1"
|
||||||
}
|
}
|
||||||
@@ -18526,6 +18601,7 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
|
||||||
"integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
|
"integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
@@ -18535,6 +18611,7 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
|
||||||
"integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
|
"integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
@@ -18544,6 +18621,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
|
||||||
"integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
|
"integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.5"
|
"es-abstract": "^1.17.5"
|
||||||
@@ -18553,6 +18631,7 @@
|
|||||||
"version": "1.17.6",
|
"version": "1.17.6",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
|
||||||
"integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
|
"integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"es-to-primitive": "^1.2.1",
|
"es-to-primitive": "^1.2.1",
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
@@ -18570,12 +18649,14 @@
|
|||||||
"is-callable": {
|
"is-callable": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
|
||||||
"integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw=="
|
"integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-regex": {
|
"is-regex": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
|
||||||
"integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==",
|
"integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-symbols": "^1.0.1"
|
"has-symbols": "^1.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blueprintjs/core": "^3.30.0",
|
"@blueprintjs/core": "^3.30.0",
|
||||||
"@blueprintjs/icons": "^3.19.0",
|
"@blueprintjs/icons": "^3.19.0",
|
||||||
|
"@blueprintjs/popover2": "^0.3.0",
|
||||||
"@blueprintjs/select": "^3.13.5",
|
"@blueprintjs/select": "^3.13.5",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
@@ -58,9 +59,11 @@
|
|||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-async": "^10.0.1",
|
"react-async": "^10.0.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
"react-easy-emoji": "^1.4.0",
|
||||||
"react-flip-toolkit": "^7.0.12",
|
"react-flip-toolkit": "^7.0.12",
|
||||||
"react-helmet": "^5.2.1",
|
"react-helmet": "^5.2.1",
|
||||||
"react-icons": "^3.10.0",
|
"react-icons": "^3.10.0",
|
||||||
|
"react-popper": "^2.2.4",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
|
|||||||
@@ -393,6 +393,8 @@ export const saveGenesetsAction = () => async (dispatch, getState) => {
|
|||||||
|
|
||||||
// bail if gene sets not available, or in readonly mode.
|
// bail if gene sets not available, or in readonly mode.
|
||||||
const { config } = state;
|
const { config } = state;
|
||||||
|
const { lastTid, genesets } = state.genesets;
|
||||||
|
|
||||||
const genesetsAreAvailable =
|
const genesetsAreAvailable =
|
||||||
config?.parameters?.["annotations_genesets"] ?? false;
|
config?.parameters?.["annotations_genesets"] ?? false;
|
||||||
const genesetsReadonly =
|
const genesetsReadonly =
|
||||||
@@ -401,16 +403,18 @@ export const saveGenesetsAction = () => async (dispatch, getState) => {
|
|||||||
// our non-save was completed!
|
// our non-save was completed!
|
||||||
return dispatch({
|
return dispatch({
|
||||||
type: "autosave: genesets complete",
|
type: "autosave: genesets complete",
|
||||||
lastSavedGenesets: state.genesets,
|
lastSavedGenesets: genesets,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lastTid, genesets: lastGenesets } = state.genesets;
|
dispatch({
|
||||||
|
type: "autosave: genesets started",
|
||||||
|
});
|
||||||
|
|
||||||
/* Create the JSON OTA data structure */
|
/* Create the JSON OTA data structure */
|
||||||
const tid = (lastTid ?? 0) + 1;
|
const tid = (lastTid ?? 0) + 1;
|
||||||
const genesets = [];
|
const genesetsOTA = [];
|
||||||
for (const [name, gs] of lastGenesets) {
|
for (const [name, gs] of genesets) {
|
||||||
const genes = [];
|
const genes = [];
|
||||||
for (const g of gs.genes.values()) {
|
for (const g of gs.genes.values()) {
|
||||||
genes.push({
|
genes.push({
|
||||||
@@ -418,7 +422,7 @@ export const saveGenesetsAction = () => async (dispatch, getState) => {
|
|||||||
gene_description: g.geneDescription,
|
gene_description: g.geneDescription,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
genesets.push({
|
genesetsOTA.push({
|
||||||
geneset_name: name,
|
geneset_name: name,
|
||||||
geneset_description: gs.genesetDescription,
|
geneset_description: gs.genesetDescription,
|
||||||
genes,
|
genes,
|
||||||
@@ -426,7 +430,7 @@ export const saveGenesetsAction = () => async (dispatch, getState) => {
|
|||||||
}
|
}
|
||||||
const ota = {
|
const ota = {
|
||||||
tid,
|
tid,
|
||||||
genesets,
|
genesets: genesetsOTA,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Save to server */
|
/* Save to server */
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class AnnoDialog extends React.PureComponent {
|
|||||||
validationError,
|
validationError,
|
||||||
annoSelect,
|
annoSelect,
|
||||||
annoInput,
|
annoInput,
|
||||||
|
secondaryInstructions,
|
||||||
|
secondaryInput,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
primaryButtonText,
|
primaryButtonText,
|
||||||
@@ -46,6 +48,12 @@ class AnnoDialog extends React.PureComponent {
|
|||||||
>
|
>
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</p>
|
</p>
|
||||||
|
{secondaryInstructions && (
|
||||||
|
<p style={{ marginTop: secondaryInstructions ? 20 : 0 }}>
|
||||||
|
{secondaryInstructions}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{secondaryInput || null}
|
||||||
</div>
|
</div>
|
||||||
{annoSelect || null}
|
{annoSelect || null}
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Helmet from "react-helmet";
|
import Helmet from "react-helmet";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import {
|
|||||||
auth: state.config?.authentication,
|
auth: state.config?.authentication,
|
||||||
userInfo: state.userInfo,
|
userInfo: state.userInfo,
|
||||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||||
|
writableGenesetsEnabled: !(
|
||||||
|
state.config?.parameters?.["annotations_genesets_readonly"] ?? true
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
class FilenameDialog extends React.Component {
|
class FilenameDialog extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -95,13 +98,14 @@ class FilenameDialog extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
writableCategoriesEnabled,
|
writableCategoriesEnabled,
|
||||||
|
writableGenesetsEnabled,
|
||||||
annotations,
|
annotations,
|
||||||
idhash,
|
idhash,
|
||||||
userInfo,
|
userInfo,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { filenameText } = this.state;
|
const { filenameText } = this.state;
|
||||||
|
|
||||||
return writableCategoriesEnabled &&
|
return (writableCategoriesEnabled || writableGenesetsEnabled) &&
|
||||||
annotations.promptForFilename &&
|
annotations.promptForFilename &&
|
||||||
!annotations.dataCollectionNameIsReadOnly &&
|
!annotations.dataCollectionNameIsReadOnly &&
|
||||||
!annotations.dataCollectionName &&
|
!annotations.dataCollectionName &&
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class Autosave extends React.Component {
|
|||||||
needToSaveGenesets = () => {
|
needToSaveGenesets = () => {
|
||||||
/* return true if we need to save gene ses, false if we do not */
|
/* return true if we need to save gene ses, false if we do not */
|
||||||
const { genesets, lastSavedGenesets } = this.props;
|
const { genesets, lastSavedGenesets } = this.props;
|
||||||
return genesets.initialized && genesets !== lastSavedGenesets;
|
return genesets.initialized && genesets.genesets !== lastSavedGenesets;
|
||||||
};
|
};
|
||||||
|
|
||||||
needToSave() {
|
needToSave() {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
|
const ErrorLoading = ({ displayName, zebra }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||||
|
fontStyle: "italic",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{`Failure loading ${displayName}`}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorLoading;
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const HistogramFooter = React.memo(
|
||||||
|
({
|
||||||
|
displayName,
|
||||||
|
hideRanges,
|
||||||
|
rangeMin,
|
||||||
|
rangeMax,
|
||||||
|
rangeColorMin,
|
||||||
|
rangeColorMax,
|
||||||
|
logFoldChange,
|
||||||
|
pvalAdj,
|
||||||
|
isObs,
|
||||||
|
}) => {
|
||||||
|
/*
|
||||||
|
Footer of each histogram. Will render range, title, and optionally
|
||||||
|
differential expression info.
|
||||||
|
|
||||||
|
Required props:
|
||||||
|
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
|
||||||
|
* hideRanges - true/false, enables/disable rendering of ranges
|
||||||
|
* range - length two array, [min, max], containing the range values to display
|
||||||
|
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
|
||||||
|
* logFoldChange - lfc to display, optional.
|
||||||
|
* pValue - pValue to display, optional.
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: hideRanges ? "center" : "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: rangeColorMin,
|
||||||
|
display: hideRanges ? "none" : "block",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
min {rangeMin.toPrecision(4)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
data-testclass="brushable-histogram-field-name"
|
||||||
|
style={{ fontStyle: "italic" }}
|
||||||
|
>
|
||||||
|
{isObs ? displayName : null}
|
||||||
|
</span>
|
||||||
|
<div style={{ display: hideRanges ? "block" : "none" }}>
|
||||||
|
: {rangeMin}
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: rangeColorMax,
|
||||||
|
display: hideRanges ? "none" : "block",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
max {rangeMax.toPrecision(4)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{logFoldChange && pvalAdj ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "baseline",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<strong>log fold change:</strong>
|
||||||
|
{` ${logFoldChange.toPrecision(4)}`}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: 7,
|
||||||
|
padding: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>p-value (adj):</strong>
|
||||||
|
{pvalAdj < 0.0001 ? " < 0.0001" : ` ${pvalAdj.toFixed(4)}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default HistogramFooter;
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { Button, ButtonGroup, Tooltip, Icon } from "@blueprintjs/core";
|
||||||
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
|
const HistogramHeader = React.memo(
|
||||||
|
({
|
||||||
|
fieldId,
|
||||||
|
isColorBy,
|
||||||
|
onColorByClick,
|
||||||
|
onRemoveClick,
|
||||||
|
isScatterPlotX,
|
||||||
|
isScatterPlotY,
|
||||||
|
onScatterPlotXClick,
|
||||||
|
onScatterPlotYClick,
|
||||||
|
isObs,
|
||||||
|
}) => {
|
||||||
|
/*
|
||||||
|
Render the toolbar for the histogram. Props:
|
||||||
|
* fieldId - field identifier, used for various IDs
|
||||||
|
* isColorBy - true/false, is this the current color-by
|
||||||
|
* onColorByClick - color-by click handler
|
||||||
|
* onRemoveClick - optional handler for remove. Button will not render if not defined.
|
||||||
|
* isScatterPlotX - optional, true/false if currently the X scatterplot field
|
||||||
|
* isScatterPlotY - optional, true/false if currently the Y scatterplot field
|
||||||
|
* onScatterPlotXClick - optional, handler for scatterPlot X button.
|
||||||
|
* onScatterPlotYClick - optional, handler for scatterPlot X button.
|
||||||
|
|
||||||
|
Scatterplot controls will not render if either handler unspecified.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const memoizedColorByCallback = useCallback(
|
||||||
|
() => onColorByClick(fieldId, isObs),
|
||||||
|
[fieldId, isObs]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
paddingBottom: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{onScatterPlotXClick && onScatterPlotYClick ? (
|
||||||
|
<span>
|
||||||
|
<Icon icon={IconNames.SCATTER_PLOT} style={{ marginRight: 7 }} />
|
||||||
|
<ButtonGroup style={{ marginRight: 7 }}>
|
||||||
|
<Button
|
||||||
|
data-testid={`plot-x-${fieldId}`}
|
||||||
|
onClick={onScatterPlotXClick}
|
||||||
|
active={isScatterPlotX}
|
||||||
|
intent={isScatterPlotX ? "primary" : "none"}
|
||||||
|
>
|
||||||
|
plot x
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
data-testid={`plot-y-${fieldId}`}
|
||||||
|
onClick={onScatterPlotYClick}
|
||||||
|
active={isScatterPlotY}
|
||||||
|
intent={isScatterPlotY ? "primary" : "none"}
|
||||||
|
>
|
||||||
|
plot y
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{onRemoveClick ? (
|
||||||
|
<Button
|
||||||
|
minimal
|
||||||
|
onClick={onRemoveClick}
|
||||||
|
style={{
|
||||||
|
color: globals.blue,
|
||||||
|
cursor: "pointer",
|
||||||
|
marginLeft: 7,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
remove
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
<Tooltip
|
||||||
|
content="Use as color scale"
|
||||||
|
position="bottom"
|
||||||
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={memoizedColorByCallback}
|
||||||
|
active={isColorBy}
|
||||||
|
intent={isColorBy ? "primary" : "none"}
|
||||||
|
data-testclass="colorby"
|
||||||
|
data-testid={`colorby-${fieldId}`}
|
||||||
|
icon="tint"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default HistogramHeader;
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { interpolateCool } from "d3-scale-chromatic";
|
||||||
|
import * as d3 from "d3";
|
||||||
|
|
||||||
|
import maybeScientific from "../../util/maybeScientific";
|
||||||
|
import clamp from "../../util/clamp";
|
||||||
|
|
||||||
|
const Histogram = ({
|
||||||
|
field,
|
||||||
|
fieldForId,
|
||||||
|
display,
|
||||||
|
histogram,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
onBrush,
|
||||||
|
onBrushEnd,
|
||||||
|
margin,
|
||||||
|
isColorBy,
|
||||||
|
selectionRange,
|
||||||
|
mini,
|
||||||
|
}) => {
|
||||||
|
const svgRef = useRef(null);
|
||||||
|
const [brush, setBrush] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/*
|
||||||
|
Create the d3 histogram
|
||||||
|
*/
|
||||||
|
const { marginLeft, marginRight, marginBottom, marginTop } = margin;
|
||||||
|
const { x, y, bins, binStart, binEnd, binWidth } = histogram;
|
||||||
|
const svg = d3.select(svgRef.current);
|
||||||
|
const binPadding = mini ? 0 : -1;
|
||||||
|
const defaultBarColor = mini ? "black" : "#bbb";
|
||||||
|
|
||||||
|
/* Remove everything */
|
||||||
|
svg.selectAll("*").remove();
|
||||||
|
|
||||||
|
/* Set margins within the SVG */
|
||||||
|
const container = svg
|
||||||
|
.attr("width", width + marginLeft + marginRight)
|
||||||
|
.attr("height", height + marginTop + marginBottom)
|
||||||
|
.append("g")
|
||||||
|
.attr("class", "histogram-container")
|
||||||
|
.attr("transform", `translate(${marginLeft},${marginTop})`);
|
||||||
|
|
||||||
|
const colorScale = d3
|
||||||
|
.scaleSequential(interpolateCool)
|
||||||
|
.domain([0, bins.length]);
|
||||||
|
|
||||||
|
const histogramScale = d3
|
||||||
|
.scaleLinear()
|
||||||
|
.domain(x.domain())
|
||||||
|
.range([
|
||||||
|
colorScale.domain()[1],
|
||||||
|
colorScale.domain()[0],
|
||||||
|
]); /* we flip this to make colors dark if high in the color scale */
|
||||||
|
|
||||||
|
if (binWidth > 0) {
|
||||||
|
/* BINS */
|
||||||
|
container
|
||||||
|
.insert("g", "*")
|
||||||
|
.selectAll("rect")
|
||||||
|
.data(bins)
|
||||||
|
.enter()
|
||||||
|
.append("rect")
|
||||||
|
.attr("x", (d, i) => x(binStart(i)) + 1)
|
||||||
|
.attr("y", (d) => y(d))
|
||||||
|
.attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - binPadding)
|
||||||
|
.attr("height", (d) => y(0) - y(d))
|
||||||
|
.style(
|
||||||
|
"fill",
|
||||||
|
isColorBy
|
||||||
|
? (d, i) => colorScale(histogramScale(binStart(i)))
|
||||||
|
: defaultBarColor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mini) {
|
||||||
|
// BRUSH
|
||||||
|
// Note the brushable area is bounded by the data on three sides, but goes down to cover the x-axis
|
||||||
|
const brushX = d3
|
||||||
|
.brushX()
|
||||||
|
.extent([
|
||||||
|
[x.range()[0], y.range()[1]],
|
||||||
|
[x.range()[1], marginTop + height + marginBottom],
|
||||||
|
])
|
||||||
|
/*
|
||||||
|
emit start so that the Undoable history can save an undo point
|
||||||
|
upon drag start, and ignore the subsequent intermediate drag events.
|
||||||
|
*/
|
||||||
|
.on("start", onBrush(field, x.invert, "start"))
|
||||||
|
.on("brush", onBrush(field, x.invert, "brush"))
|
||||||
|
.on("end", onBrushEnd(field, x.invert));
|
||||||
|
|
||||||
|
const brushXselection = container
|
||||||
|
.insert("g")
|
||||||
|
.attr("class", "brush")
|
||||||
|
.attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
|
||||||
|
.call(brushX);
|
||||||
|
|
||||||
|
/* X AXIS */
|
||||||
|
container
|
||||||
|
.insert("g")
|
||||||
|
.attr("class", "axis axis--x")
|
||||||
|
.attr("transform", `translate(0,${marginTop + height})`)
|
||||||
|
.call(
|
||||||
|
d3
|
||||||
|
.axisBottom(x)
|
||||||
|
.ticks(4)
|
||||||
|
.tickFormat(d3.format(maybeScientific(x)))
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Y AXIS */
|
||||||
|
container
|
||||||
|
.insert("g")
|
||||||
|
.attr("class", "axis axis--y")
|
||||||
|
.attr("transform", `translate(${marginLeft + width},0)`)
|
||||||
|
.call(
|
||||||
|
d3
|
||||||
|
.axisRight(y)
|
||||||
|
.ticks(3)
|
||||||
|
.tickFormat(
|
||||||
|
d3.format(
|
||||||
|
y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* axis style */
|
||||||
|
svg.selectAll(".axis text").style("fill", "rgb(80,80,80)");
|
||||||
|
svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
|
||||||
|
svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
|
||||||
|
|
||||||
|
setBrush({ brushX, brushXselection });
|
||||||
|
}
|
||||||
|
}, [histogram, isColorBy]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/*
|
||||||
|
paint/update selection brush
|
||||||
|
*/
|
||||||
|
if (!brush) return;
|
||||||
|
const { brushX, brushXselection } = brush;
|
||||||
|
const selection = d3.brushSelection(brushXselection.node());
|
||||||
|
if (!selectionRange && selection) {
|
||||||
|
/* no active selection - clear brush */
|
||||||
|
brushXselection.call(brushX.move, null);
|
||||||
|
} else if (selectionRange) {
|
||||||
|
const { x, domain } = histogram;
|
||||||
|
const [min, max] = domain;
|
||||||
|
const x0 = x(clamp(selectionRange[0], [min, max]));
|
||||||
|
const x1 = x(clamp(selectionRange[1], [min, max]));
|
||||||
|
if (!selection) {
|
||||||
|
/* there is an active selection, but no brush - set the brush */
|
||||||
|
brushXselection.call(brushX.move, [x0, x1]);
|
||||||
|
} else {
|
||||||
|
/* there is an active selection and a brush - make sure they match */
|
||||||
|
const moveDeltaThreshold = 1;
|
||||||
|
const dX0 = Math.abs(x0 - selection[0]);
|
||||||
|
const dX1 = Math.abs(x1 - selection[1]);
|
||||||
|
/*
|
||||||
|
only update the brush if it is grossly incorrect,
|
||||||
|
as defined by the moveDeltaThreshold
|
||||||
|
*/
|
||||||
|
if (dX0 > moveDeltaThreshold || dX1 > moveDeltaThreshold) {
|
||||||
|
brushXselection.call(brushX.move, [x0, x1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [brush, selectionRange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
style={{ display }}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
id={`histogram_${fieldForId}_svg`}
|
||||||
|
data-testclass="histogram-plot"
|
||||||
|
data-testid={`histogram-${field}-plot`}
|
||||||
|
ref={svgRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Histogram;
|
||||||
@@ -1,442 +1,17 @@
|
|||||||
/*
|
import React from "react";
|
||||||
https://bl.ocks.org/mbostock/4341954
|
|
||||||
https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
|
|
||||||
https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771
|
|
||||||
https://bl.ocks.org/mbostock/3019563
|
|
||||||
*/
|
|
||||||
import React, { useEffect, useRef, useState, useCallback } from "react";
|
|
||||||
import { Button, ButtonGroup, Icon, Tooltip } from "@blueprintjs/core";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import { interpolateCool } from "d3-scale-chromatic";
|
|
||||||
import Async from "react-async";
|
import Async from "react-async";
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
import { IconNames } from "@blueprintjs/icons";
|
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import { histogramContinuous } from "../../util/dataframe/histogram";
|
import { histogramContinuous } from "../../util/dataframe/histogram";
|
||||||
import { makeContinuousDimensionName } from "../../util/nameCreators";
|
import { makeContinuousDimensionName } from "../../util/nameCreators";
|
||||||
import significantDigits from "../../util/significantDigits";
|
import HistogramHeader from "./header";
|
||||||
|
import Histogram from "./histogram";
|
||||||
function clamp(val, rng) {
|
import HistogramFooter from "./footer";
|
||||||
return Math.max(Math.min(val, rng[1]), rng[0]);
|
import StillLoading from "./loading";
|
||||||
}
|
import ErrorLoading from "./error";
|
||||||
|
|
||||||
function maybeScientific(x) {
|
|
||||||
let format = ",";
|
|
||||||
const _ticks = x.ticks(4);
|
|
||||||
|
|
||||||
if (x.domain().some((n) => Math.abs(n) >= 10000)) {
|
|
||||||
/*
|
|
||||||
heuristic: if the last tick d3 wants to render has one significant
|
|
||||||
digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render
|
|
||||||
4.20e+n
|
|
||||||
*/
|
|
||||||
format = significantDigits(_ticks[_ticks.length - 1]) === 1 ? ".0e" : ".2e";
|
|
||||||
}
|
|
||||||
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StillLoading = ({ zebra, displayName }) => {
|
|
||||||
/*
|
|
||||||
Render a loading indicator for the field.
|
|
||||||
*/
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: globals.leftSidebarSectionPadding,
|
|
||||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
justifyItems: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ minWidth: 30 }} />
|
|
||||||
<div style={{ display: "flex", alignSelf: "center" }}>
|
|
||||||
<span style={{ fontStyle: "italic" }}>{displayName}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button minimal loading intent="primary" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ErrorLoading = ({ displayName, error, zebra }) => {
|
|
||||||
console.log(error); // log to console as this is unexpected
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: globals.leftSidebarSectionPadding,
|
|
||||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{`Failure loading ${displayName}`}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const HistogramFooter = React.memo(
|
|
||||||
({
|
|
||||||
displayName,
|
|
||||||
hideRanges,
|
|
||||||
rangeMin,
|
|
||||||
rangeMax,
|
|
||||||
rangeColorMin,
|
|
||||||
rangeColorMax,
|
|
||||||
logFoldChange,
|
|
||||||
pvalAdj,
|
|
||||||
}) => {
|
|
||||||
/*
|
|
||||||
Footer of each histogram. Will render range, title, and optionally
|
|
||||||
differential expression info.
|
|
||||||
|
|
||||||
Required props:
|
|
||||||
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
|
|
||||||
* hideRanges - true/false, enables/disable rendering of ranges
|
|
||||||
* range - length two array, [min, max], containing the range values to display
|
|
||||||
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
|
|
||||||
* logFoldChange - lfc to display, optional.
|
|
||||||
* pValue - pValue to display, optional.
|
|
||||||
*/
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: hideRanges ? "center" : "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: rangeColorMin,
|
|
||||||
display: hideRanges ? "none" : "block",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
min {rangeMin.toPrecision(4)}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
data-testclass="brushable-histogram-field-name"
|
|
||||||
style={{ fontStyle: "italic" }}
|
|
||||||
>
|
|
||||||
{displayName}
|
|
||||||
</span>
|
|
||||||
<div style={{ display: hideRanges ? "block" : "none" }}>
|
|
||||||
: {rangeMin}
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: rangeColorMax,
|
|
||||||
display: hideRanges ? "none" : "block",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
max {rangeMax.toPrecision(4)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{logFoldChange !== undefined && pvalAdj !== undefined ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "baseline",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<strong>log fold change:</strong>
|
|
||||||
{` ${logFoldChange.toPrecision(4)}`}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
marginLeft: 7,
|
|
||||||
padding: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<strong>p-value (adj):</strong>
|
|
||||||
{pvalAdj < 0.0001 ? " < 0.0001" : ` ${pvalAdj.toFixed(4)}`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const HistogramHeader = React.memo(
|
|
||||||
({
|
|
||||||
fieldId,
|
|
||||||
isColorBy,
|
|
||||||
onColorByClick,
|
|
||||||
onRemoveClick,
|
|
||||||
isScatterPlotX,
|
|
||||||
isScatterPlotY,
|
|
||||||
onScatterPlotXClick,
|
|
||||||
onScatterPlotYClick,
|
|
||||||
isObs,
|
|
||||||
}) => {
|
|
||||||
/*
|
|
||||||
Render the toolbar for the histogram. Props:
|
|
||||||
* fieldId - field identifier, used for various IDs
|
|
||||||
* isColorBy - true/false, is this the current color-by
|
|
||||||
* onColorByClick - color-by click handler
|
|
||||||
* onRemoveClick - optional handler for remove. Button will not render if not defined.
|
|
||||||
* isScatterPlotX - optional, true/false if currently the X scatterplot field
|
|
||||||
* isScatterPlotY - optional, true/false if currently the Y scatterplot field
|
|
||||||
* onScatterPlotXClick - optional, handler for scatterPlot X button.
|
|
||||||
* onScatterPlotYClick - optional, handler for scatterPlot X button.
|
|
||||||
|
|
||||||
Scatterplot controls will not render if either handler unspecified.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const memoizedColorByCallback = useCallback(
|
|
||||||
() => onColorByClick(fieldId, isObs),
|
|
||||||
[fieldId, isObs]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
paddingBottom: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{onScatterPlotXClick && onScatterPlotYClick ? (
|
|
||||||
<span>
|
|
||||||
<Icon icon={IconNames.SCATTER_PLOT} style={{ marginRight: 7 }} />
|
|
||||||
<ButtonGroup style={{ marginRight: 7 }}>
|
|
||||||
<Button
|
|
||||||
data-testid={`plot-x-${fieldId}`}
|
|
||||||
onClick={onScatterPlotXClick}
|
|
||||||
active={isScatterPlotX}
|
|
||||||
intent={isScatterPlotX ? "primary" : "none"}
|
|
||||||
>
|
|
||||||
plot x
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
data-testid={`plot-y-${fieldId}`}
|
|
||||||
onClick={onScatterPlotYClick}
|
|
||||||
active={isScatterPlotY}
|
|
||||||
intent={isScatterPlotY ? "primary" : "none"}
|
|
||||||
>
|
|
||||||
plot y
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
{onRemoveClick ? (
|
|
||||||
<Button
|
|
||||||
minimal
|
|
||||||
onClick={onRemoveClick}
|
|
||||||
style={{
|
|
||||||
color: globals.blue,
|
|
||||||
cursor: "pointer",
|
|
||||||
marginLeft: 7,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
remove
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
<Tooltip
|
|
||||||
content="Use as color scale"
|
|
||||||
position="bottom"
|
|
||||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={memoizedColorByCallback}
|
|
||||||
active={isColorBy}
|
|
||||||
intent={isColorBy ? "primary" : "none"}
|
|
||||||
data-testclass="colorby"
|
|
||||||
data-testid={`colorby-${fieldId}`}
|
|
||||||
icon="tint"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const Histogram = ({
|
|
||||||
field,
|
|
||||||
fieldForId,
|
|
||||||
display,
|
|
||||||
histogram,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
onBrush,
|
|
||||||
onBrushEnd,
|
|
||||||
margin,
|
|
||||||
isColorBy,
|
|
||||||
selectionRange,
|
|
||||||
}) => {
|
|
||||||
const svgRef = useRef(null);
|
|
||||||
const [brush, setBrush] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
/*
|
|
||||||
Create the d3 histogram
|
|
||||||
*/
|
|
||||||
const { marginLeft, marginRight, marginBottom, marginTop } = margin;
|
|
||||||
const { x, y, bins, binStart, binEnd, binWidth } = histogram;
|
|
||||||
const svg = d3.select(svgRef.current);
|
|
||||||
|
|
||||||
/* Remove everything */
|
|
||||||
svg.selectAll("*").remove();
|
|
||||||
|
|
||||||
/* Set margins within the SVG */
|
|
||||||
const container = svg
|
|
||||||
.attr("width", width + marginLeft + marginRight)
|
|
||||||
.attr("height", height + marginTop + marginBottom)
|
|
||||||
.append("g")
|
|
||||||
.attr("class", "histogram-container")
|
|
||||||
.attr("transform", `translate(${marginLeft},${marginTop})`);
|
|
||||||
|
|
||||||
const colorScale = d3
|
|
||||||
.scaleSequential(interpolateCool)
|
|
||||||
.domain([0, bins.length]);
|
|
||||||
|
|
||||||
const histogramScale = d3
|
|
||||||
.scaleLinear()
|
|
||||||
.domain(x.domain())
|
|
||||||
.range([
|
|
||||||
colorScale.domain()[1],
|
|
||||||
colorScale.domain()[0],
|
|
||||||
]); /* we flip this to make colors dark if high in the color scale */
|
|
||||||
|
|
||||||
if (binWidth > 0) {
|
|
||||||
/* BINS */
|
|
||||||
container
|
|
||||||
.insert("g", "*")
|
|
||||||
.selectAll("rect")
|
|
||||||
.data(bins)
|
|
||||||
.enter()
|
|
||||||
.append("rect")
|
|
||||||
.attr("x", (d, i) => x(binStart(i)) + 1)
|
|
||||||
.attr("y", (d) => y(d))
|
|
||||||
.attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - 1)
|
|
||||||
.attr("height", (d) => y(0) - y(d))
|
|
||||||
.style(
|
|
||||||
"fill",
|
|
||||||
isColorBy ? (d, i) => colorScale(histogramScale(binStart(i))) : "#bbb"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BRUSH
|
|
||||||
// Note the brushable area is bounded by the data on three sides, but goes down to cover the x-axis
|
|
||||||
const brushX = d3
|
|
||||||
.brushX()
|
|
||||||
.extent([
|
|
||||||
[x.range()[0], y.range()[1]],
|
|
||||||
[x.range()[1], marginTop + height + marginBottom],
|
|
||||||
])
|
|
||||||
/*
|
|
||||||
emit start so that the Undoable history can save an undo point
|
|
||||||
upon drag start, and ignore the subsequent intermediate drag events.
|
|
||||||
*/
|
|
||||||
.on("start", onBrush(field, x.invert, "start"))
|
|
||||||
.on("brush", onBrush(field, x.invert, "brush"))
|
|
||||||
.on("end", onBrushEnd(field, x.invert));
|
|
||||||
|
|
||||||
const brushXselection = container
|
|
||||||
.insert("g")
|
|
||||||
.attr("class", "brush")
|
|
||||||
.attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
|
|
||||||
.call(brushX);
|
|
||||||
|
|
||||||
/* X AXIS */
|
|
||||||
container
|
|
||||||
.insert("g")
|
|
||||||
.attr("class", "axis axis--x")
|
|
||||||
.attr("transform", `translate(0,${marginTop + height})`)
|
|
||||||
.call(
|
|
||||||
d3
|
|
||||||
.axisBottom(x)
|
|
||||||
.ticks(4)
|
|
||||||
.tickFormat(d3.format(maybeScientific(x)))
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Y AXIS */
|
|
||||||
container
|
|
||||||
.insert("g")
|
|
||||||
.attr("class", "axis axis--y")
|
|
||||||
.attr("transform", `translate(${marginLeft + width},0)`)
|
|
||||||
.call(
|
|
||||||
d3
|
|
||||||
.axisRight(y)
|
|
||||||
.ticks(3)
|
|
||||||
.tickFormat(
|
|
||||||
d3.format(
|
|
||||||
y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
/* axis style */
|
|
||||||
svg.selectAll(".axis text").style("fill", "rgb(80,80,80)");
|
|
||||||
svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
|
|
||||||
svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
|
|
||||||
|
|
||||||
setBrush({ brushX, brushXselection });
|
|
||||||
}, [histogram, isColorBy]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
/*
|
|
||||||
paint/update selection brush
|
|
||||||
*/
|
|
||||||
if (!brush) return;
|
|
||||||
const { brushX, brushXselection } = brush;
|
|
||||||
const selection = d3.brushSelection(brushXselection.node());
|
|
||||||
if (!selectionRange && selection) {
|
|
||||||
/* no active selection - clear brush */
|
|
||||||
brushXselection.call(brushX.move, null);
|
|
||||||
} else if (selectionRange) {
|
|
||||||
const { x, domain } = histogram;
|
|
||||||
const [min, max] = domain;
|
|
||||||
const x0 = x(clamp(selectionRange[0], [min, max]));
|
|
||||||
const x1 = x(clamp(selectionRange[1], [min, max]));
|
|
||||||
if (!selection) {
|
|
||||||
/* there is an active selection, but no brush - set the brush */
|
|
||||||
brushXselection.call(brushX.move, [x0, x1]);
|
|
||||||
} else {
|
|
||||||
/* there is an active selection and a brush - make sure they match */
|
|
||||||
const moveDeltaThreshold = 1;
|
|
||||||
const dX0 = Math.abs(x0 - selection[0]);
|
|
||||||
const dX1 = Math.abs(x1 - selection[1]);
|
|
||||||
/*
|
|
||||||
only update the brush if it is grossly incorrect,
|
|
||||||
as defined by the moveDeltaThreshold
|
|
||||||
*/
|
|
||||||
if (dX0 > moveDeltaThreshold || dX1 > moveDeltaThreshold) {
|
|
||||||
brushXselection.call(brushX.move, [x0, x1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [brush, selectionRange]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
style={{ display }}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
id={`histogram_${fieldForId}_svg`}
|
|
||||||
data-testclass="histogram-plot"
|
|
||||||
data-testid={`histogram-${field}-plot`}
|
|
||||||
ref={svgRef}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@connect((state, ownProps) => {
|
@connect((state, ownProps) => {
|
||||||
const { isObs, isUserDefined, isDiffExp, field } = ownProps;
|
const { isObs, isUserDefined, isDiffExp, field } = ownProps;
|
||||||
@@ -472,14 +47,25 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
const marginRight = 54; // space for Y axis & labels
|
const marginRight = 54; // space for Y axis & labels
|
||||||
const marginBottom = 25; // space for X axis & labels
|
const marginBottom = 25; // space for X axis & labels
|
||||||
const marginTop = 3;
|
const marginTop = 3;
|
||||||
this.margin = {
|
|
||||||
marginLeft,
|
this.state = {
|
||||||
marginRight,
|
margin: {
|
||||||
marginBottom,
|
marginLeft,
|
||||||
marginTop,
|
marginRight,
|
||||||
|
marginBottom,
|
||||||
|
marginTop,
|
||||||
|
},
|
||||||
|
width: 340 - marginLeft - marginRight,
|
||||||
|
height: 135 - marginTop - marginBottom,
|
||||||
|
marginMini: {
|
||||||
|
marginLeft: 0, // Space for 0 tick label on X axis
|
||||||
|
marginRight: 0, // space for Y axis & labels
|
||||||
|
marginBottom: 0, // space for X axis & labels
|
||||||
|
marginTop: 0,
|
||||||
|
},
|
||||||
|
widthMini: 120,
|
||||||
|
heightMini: 15,
|
||||||
};
|
};
|
||||||
this.width = 340 - marginLeft - marginRight;
|
|
||||||
this.height = 135 - marginTop - marginBottom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBrush = (selection, x, eventType) => {
|
onBrush = (selection, x, eventType) => {
|
||||||
@@ -609,6 +195,14 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
|
|
||||||
fetchAsyncProps = async () => {
|
fetchAsyncProps = async () => {
|
||||||
const { annoMatrix } = this.props;
|
const { annoMatrix } = this.props;
|
||||||
|
const {
|
||||||
|
margin,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
marginMini,
|
||||||
|
widthMini,
|
||||||
|
heightMini,
|
||||||
|
} = this.state;
|
||||||
const { isClipped } = annoMatrix;
|
const { isClipped } = annoMatrix;
|
||||||
|
|
||||||
const query = this.createQuery();
|
const query = this.createQuery();
|
||||||
@@ -636,11 +230,12 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
: globals.blue,
|
: globals.blue,
|
||||||
];
|
];
|
||||||
|
|
||||||
const histogram = this.calcHistogramCache(
|
const histogram = this.calcHistogramCache(column, margin, width, height);
|
||||||
|
const miniHistogram = this.calcHistogramCache(
|
||||||
column,
|
column,
|
||||||
this.margin,
|
marginMini,
|
||||||
this.width,
|
widthMini,
|
||||||
this.height
|
heightMini
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSingleValue = summary.min === summary.max;
|
const isSingleValue = summary.min === summary.max;
|
||||||
@@ -654,6 +249,7 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
histogram,
|
histogram,
|
||||||
|
miniHistogram,
|
||||||
range,
|
range,
|
||||||
unclippedRange,
|
unclippedRange,
|
||||||
unclippedRangeColor,
|
unclippedRangeColor,
|
||||||
@@ -667,13 +263,16 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
/*
|
/*
|
||||||
recalculate expensive stuff, notably bins, summaries, etc.
|
recalculate expensive stuff, notably bins, summaries, etc.
|
||||||
*/
|
*/
|
||||||
const histogramCache = {};
|
const histogramCache = {}; /* maybe change this so that it computes ... */
|
||||||
const summary = col.summarize();
|
const summary = col.summarize(); /* this is memoized, so it's free the second time you call it */
|
||||||
const { min: domainMin, max: domainMax } = summary;
|
const { min: domainMin, max: domainMax } = summary;
|
||||||
const numBins = 40;
|
const numBins = 40;
|
||||||
const { marginTop, marginLeft } = margin;
|
const { marginTop, marginLeft } = margin; /* changes with mini */
|
||||||
|
|
||||||
histogramCache.domain = [domainMin, domainMax];
|
histogramCache.domain = [
|
||||||
|
domainMin,
|
||||||
|
domainMax,
|
||||||
|
]; /* doesn't change with mini */
|
||||||
|
|
||||||
histogramCache.x = d3
|
histogramCache.x = d3
|
||||||
.scaleLinear()
|
.scaleLinear()
|
||||||
@@ -683,7 +282,7 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
histogramCache.bins = histogramContinuous(col, numBins, [
|
histogramCache.bins = histogramContinuous(col, numBins, [
|
||||||
domainMin,
|
domainMin,
|
||||||
domainMax,
|
domainMax,
|
||||||
]);
|
]); /* memoized */
|
||||||
histogramCache.binWidth = (domainMax - domainMin) / numBins;
|
histogramCache.binWidth = (domainMax - domainMin) / numBins;
|
||||||
|
|
||||||
histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth;
|
histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth;
|
||||||
@@ -733,7 +332,16 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
zebra,
|
zebra,
|
||||||
continuousSelectionRange,
|
continuousSelectionRange,
|
||||||
isObs,
|
isObs,
|
||||||
|
mini,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const {
|
||||||
|
margin,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
marginMini,
|
||||||
|
widthMini,
|
||||||
|
heightMini,
|
||||||
|
} = this.state;
|
||||||
const fieldForId = field.replace(/\s/g, "_");
|
const fieldForId = field.replace(/\s/g, "_");
|
||||||
const showScatterPlot = isDiffExp || isUserDefined;
|
const showScatterPlot = isDiffExp || isUserDefined;
|
||||||
|
|
||||||
@@ -761,48 +369,56 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
: "histogram-continuous-metadata"
|
: "histogram-continuous-metadata"
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
padding: globals.leftSidebarSectionPadding,
|
padding: mini ? 0 : globals.leftSidebarSectionPadding,
|
||||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HistogramHeader
|
{!mini && isObs ? (
|
||||||
fieldId={field}
|
<HistogramHeader
|
||||||
isColorBy={isColorAccessor}
|
fieldId={field}
|
||||||
isObs={isObs}
|
isColorBy={isColorAccessor}
|
||||||
onColorByClick={this.handleColorAction(dispatch)}
|
isObs={isObs}
|
||||||
onRemoveClick={isUserDefined ? this.removeHistogram : null}
|
onColorByClick={this.handleColorAction(dispatch)}
|
||||||
isScatterPlotX={isScatterplotXXaccessor}
|
onRemoveClick={isUserDefined ? this.removeHistogram : null}
|
||||||
isScatterPlotY={isScatterplotYYaccessor}
|
isScatterPlotX={isScatterplotXXaccessor}
|
||||||
onScatterPlotXClick={
|
isScatterPlotY={isScatterplotYYaccessor}
|
||||||
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
|
onScatterPlotXClick={
|
||||||
}
|
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
|
||||||
onScatterPlotYClick={
|
}
|
||||||
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
|
onScatterPlotYClick={
|
||||||
}
|
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<Histogram
|
<Histogram
|
||||||
field={field}
|
field={field}
|
||||||
fieldForId={fieldForId}
|
fieldForId={fieldForId}
|
||||||
display={asyncProps.isSingleValue ? "none" : "block"}
|
display={asyncProps.isSingleValue ? "none" : "block"}
|
||||||
histogram={asyncProps.histogram}
|
histogram={
|
||||||
width={this.width}
|
mini ? asyncProps.miniHistogram : asyncProps.histogram
|
||||||
height={this.height}
|
}
|
||||||
|
width={mini ? widthMini : width}
|
||||||
|
height={mini ? heightMini : height}
|
||||||
onBrush={this.onBrush}
|
onBrush={this.onBrush}
|
||||||
onBrushEnd={this.onBrushEnd}
|
onBrushEnd={this.onBrushEnd}
|
||||||
margin={this.margin}
|
margin={mini ? marginMini : margin}
|
||||||
isColorBy={isColorAccessor}
|
isColorBy={isColorAccessor}
|
||||||
selectionRange={continuousSelectionRange}
|
selectionRange={continuousSelectionRange}
|
||||||
|
mini={mini}
|
||||||
/>
|
/>
|
||||||
<HistogramFooter
|
{!mini ? (
|
||||||
displayName={field}
|
<HistogramFooter
|
||||||
hideRanges={asyncProps.isSingleValue}
|
isObs={isObs}
|
||||||
rangeMin={asyncProps.unclippedRange[0]}
|
displayName={field}
|
||||||
rangeMax={asyncProps.unclippedRange[1]}
|
hideRanges={asyncProps.isSingleValue}
|
||||||
rangeColorMin={asyncProps.unclippedRangeColor[0]}
|
rangeMin={asyncProps.unclippedRange[0]}
|
||||||
rangeColorMax={asyncProps.unclippedRangeColor[1]}
|
rangeMax={asyncProps.unclippedRange[1]}
|
||||||
logFoldChange={logFoldChange}
|
rangeColorMin={asyncProps.unclippedRangeColor[0]}
|
||||||
pvalAdj={pvalAdj}
|
rangeColorMax={asyncProps.unclippedRangeColor[1]}
|
||||||
/>
|
logFoldChange={logFoldChange}
|
||||||
|
pvalAdj={pvalAdj}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button } from "@blueprintjs/core";
|
||||||
|
|
||||||
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
|
const StillLoading = ({ zebra, displayName }) => {
|
||||||
|
/*
|
||||||
|
Render a loading indicator for the field.
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: globals.leftSidebarSectionPadding,
|
||||||
|
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
justifyItems: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ minWidth: 30 }} />
|
||||||
|
<div style={{ display: "flex", alignSelf: "center" }}>
|
||||||
|
<span style={{ fontStyle: "italic" }}>{displayName}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button minimal loading intent="primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StillLoading;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import AnnoDialog from "../annoDialog";
|
import AnnoDialog from "../../annoDialog";
|
||||||
import LabelInput from "../labelInput";
|
import LabelInput from "../../labelInput";
|
||||||
import { labelPrompt, isLabelErroneous } from "../labelUtil";
|
import { labelPrompt, isLabelErroneous } from "../labelUtil";
|
||||||
import actions from "../../../actions";
|
import actions from "../../../actions";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import AnnoDialog from "../annoDialog";
|
import AnnoDialog from "../../annoDialog";
|
||||||
import LabelInput from "../labelInput";
|
import LabelInput from "../../labelInput";
|
||||||
import { labelPrompt } from "../labelUtil";
|
import { labelPrompt } from "../labelUtil";
|
||||||
|
|
||||||
import { AnnotationsHelpers } from "../../../util/stateManager";
|
import { AnnotationsHelpers } from "../../../util/stateManager";
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Icon,
|
Icon,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
|
Intent,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
|
|
||||||
import * as globals from "../../../globals";
|
import * as globals from "../../../globals";
|
||||||
import actions from "../../../actions";
|
import actions from "../../../actions";
|
||||||
@@ -89,8 +91,8 @@ class AnnoMenuCategory extends React.PureComponent {
|
|||||||
text={editText}
|
text={editText}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="delete"
|
icon={IconNames.TRASH}
|
||||||
intent="danger"
|
intent={Intent.DANGER}
|
||||||
data-testclass="handleDeleteCategory"
|
data-testclass="handleDeleteCategory"
|
||||||
data-testid={`${metadataField}:delete-category`}
|
data-testid={`${metadataField}:delete-category`}
|
||||||
onClick={this.handleDeleteCategory}
|
onClick={this.handleDeleteCategory}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AnchorButton, Tooltip, Position } from "@blueprintjs/core";
|
import { AnchorButton, Tooltip, Position } from "@blueprintjs/core";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import Category from "./category";
|
import Category from "./category";
|
||||||
import { AnnotationsHelpers, ControlsHelpers } from "../../util/stateManager";
|
import { AnnotationsHelpers, ControlsHelpers } from "../../util/stateManager";
|
||||||
import AnnoDialog from "./annoDialog";
|
import AnnoDialog from "../annoDialog";
|
||||||
import AnnoSelect from "./annoSelect";
|
import AnnoSelect from "./annoSelect";
|
||||||
import LabelInput from "./labelInput";
|
import LabelInput from "../labelInput";
|
||||||
import { labelPrompt } from "./labelUtil";
|
import { labelPrompt } from "./labelUtil";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
|
|
||||||
@@ -182,6 +181,35 @@ class Categories extends React.Component {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{writableCategoriesEnabled ? (
|
||||||
|
<div style={{ marginBottom: 10 }}>
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
userInfo.is_authenticated
|
||||||
|
? "Create a new category"
|
||||||
|
: "You must be logged in to create new categorical fields"
|
||||||
|
}
|
||||||
|
position={Position.RIGHT}
|
||||||
|
boundary="viewport"
|
||||||
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
modifiers={{
|
||||||
|
preventOverflow: { enabled: false },
|
||||||
|
hide: { enabled: false },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AnchorButton
|
||||||
|
type="button"
|
||||||
|
data-testid="open-annotation-dialog"
|
||||||
|
onClick={this.handleEnableAnnoMode}
|
||||||
|
intent="primary"
|
||||||
|
disabled={!userInfo.is_authenticated}
|
||||||
|
>
|
||||||
|
Create new <strong>category</strong>
|
||||||
|
</AnchorButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{/* READ ONLY CATEGORICAL FIELDS */}
|
{/* READ ONLY CATEGORICAL FIELDS */}
|
||||||
{/* this is duplicative but flat, could be abstracted */}
|
{/* this is duplicative but flat, could be abstracted */}
|
||||||
{allCategoryNames.map((catName) =>
|
{allCategoryNames.map((catName) =>
|
||||||
@@ -209,33 +237,6 @@ class Categories extends React.Component {
|
|||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{writableCategoriesEnabled ? (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
userInfo.is_authenticated
|
|
||||||
? "Create a new category"
|
|
||||||
: "You must be logged in to create new categorical fields"
|
|
||||||
}
|
|
||||||
position={Position.RIGHT}
|
|
||||||
boundary="viewport"
|
|
||||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
|
||||||
modifiers={{
|
|
||||||
preventOverflow: { enabled: false },
|
|
||||||
hide: { enabled: false },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AnchorButton
|
|
||||||
type="button"
|
|
||||||
data-testid="open-annotation-dialog"
|
|
||||||
onClick={this.handleEnableAnnoMode}
|
|
||||||
intent="primary"
|
|
||||||
disabled={!userInfo.is_authenticated}
|
|
||||||
>
|
|
||||||
Create new category
|
|
||||||
</AnchorButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import {
|
|||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../../globals";
|
import * as globals from "../../../globals";
|
||||||
import styles from "../categorical.css";
|
import styles from "../categorical.css";
|
||||||
import AnnoDialog from "../annoDialog";
|
import AnnoDialog from "../../annoDialog";
|
||||||
import LabelInput from "../labelInput";
|
import LabelInput from "../../labelInput";
|
||||||
import Truncate from "../../util/truncate";
|
import Truncate from "../../util/truncate";
|
||||||
|
|
||||||
import { AnnotationsHelpers } from "../../../util/stateManager";
|
import { AnnotationsHelpers } from "../../../util/stateManager";
|
||||||
@@ -25,8 +25,8 @@ import MiniHistogram from "../../miniHistogram";
|
|||||||
import MiniStackedBar from "../../miniStackedBar";
|
import MiniStackedBar from "../../miniStackedBar";
|
||||||
import { CategoryCrossfilterContext } from "../categoryContext";
|
import { CategoryCrossfilterContext } from "../categoryContext";
|
||||||
|
|
||||||
const VALUE_HEIGHT = 11;
|
const STACKED_BAR_HEIGHT = 11;
|
||||||
const CHART_WIDTH = 100;
|
const STACKED_BAR_WIDTH = 100;
|
||||||
|
|
||||||
/* this is defined outside of the class so we can use it in connect() */
|
/* this is defined outside of the class so we can use it in connect() */
|
||||||
function _currentLabelAsString(ownProps) {
|
function _currentLabelAsString(ownProps) {
|
||||||
@@ -410,7 +410,7 @@ class CategoryValue extends React.Component {
|
|||||||
label,
|
label,
|
||||||
colorTable,
|
colorTable,
|
||||||
schema,
|
schema,
|
||||||
CHART_WIDTH
|
STACKED_BAR_WIDTH
|
||||||
) ?? {};
|
) ?? {};
|
||||||
|
|
||||||
if (!domainValues || !scale || !domain || !occupancy) {
|
if (!domainValues || !scale || !domain || !occupancy) {
|
||||||
@@ -426,8 +426,8 @@ class CategoryValue extends React.Component {
|
|||||||
domain,
|
domain,
|
||||||
occupancy,
|
occupancy,
|
||||||
}}
|
}}
|
||||||
height={VALUE_HEIGHT}
|
height={STACKED_BAR_HEIGHT}
|
||||||
width={CHART_WIDTH}
|
width={STACKED_BAR_WIDTH}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -460,8 +460,8 @@ class CategoryValue extends React.Component {
|
|||||||
colorAccessor,
|
colorAccessor,
|
||||||
colorData,
|
colorData,
|
||||||
label,
|
label,
|
||||||
CHART_WIDTH,
|
STACKED_BAR_WIDTH,
|
||||||
VALUE_HEIGHT
|
STACKED_BAR_HEIGHT
|
||||||
) ?? {}; // if createHistogramBins returns empty object assign null to deconstructed
|
) ?? {}; // if createHistogramBins returns empty object assign null to deconstructed
|
||||||
|
|
||||||
if (!xScale || !yScale || !bins) return null;
|
if (!xScale || !yScale || !bins) return null;
|
||||||
@@ -476,8 +476,8 @@ class CategoryValue extends React.Component {
|
|||||||
}}
|
}}
|
||||||
obsOrVarContinuousFieldDisplayName={colorAccessor}
|
obsOrVarContinuousFieldDisplayName={colorAccessor}
|
||||||
domainLabel={label}
|
domainLabel={label}
|
||||||
height={VALUE_HEIGHT}
|
height={STACKED_BAR_HEIGHT}
|
||||||
width={CHART_WIDTH}
|
width={STACKED_BAR_WIDTH}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -534,7 +534,7 @@ class CategoryValue extends React.Component {
|
|||||||
colorAccessor && !isColorBy
|
colorAccessor && !isColorBy
|
||||||
? globals.leftSidebarWidth -
|
? globals.leftSidebarWidth -
|
||||||
otherElementsWidth -
|
otherElementsWidth -
|
||||||
CHART_WIDTH -
|
STACKED_BAR_WIDTH -
|
||||||
CHART_MARGIN
|
CHART_MARGIN
|
||||||
: globals.leftSidebarWidth - otherElementsWidth;
|
: globals.leftSidebarWidth - otherElementsWidth;
|
||||||
|
|
||||||
@@ -677,8 +677,8 @@ class CategoryValue extends React.Component {
|
|||||||
display={isColorBy && categoryValueIndices ? "auto" : "none"}
|
display={isColorBy && categoryValueIndices ? "auto" : "none"}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
width: VALUE_HEIGHT,
|
width: 15,
|
||||||
height: VALUE_HEIGHT,
|
height: 15,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
isColorBy && categoryValueIndices
|
isColorBy && categoryValueIndices
|
||||||
? colorScale(categoryValueIndices.get(label))
|
? colorScale(categoryValueIndices.get(label))
|
||||||
@@ -739,7 +739,7 @@ class CategoryValue extends React.Component {
|
|||||||
) : null}
|
) : null}
|
||||||
{displayString !== globals.unassignedCategoryLabel ? (
|
{displayString !== globals.unassignedCategoryLabel ? (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="delete"
|
icon="trash"
|
||||||
intent="danger"
|
intent="danger"
|
||||||
data-testclass="handleDeleteValue"
|
data-testclass="handleDeleteValue"
|
||||||
data-testid={`${metadataField}:${displayString}:delete-label`}
|
data-testid={`${metadataField}:${displayString}:delete-label`}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
Setup SVG & Canvas elements
|
Setup SVG & Canvas elements
|
||||||
******************************************
|
******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
// jshint esversion: 6
|
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
|
||||||
const setupParallelCoordinates = (width, height, margin) => {
|
const setupParallelCoordinates = (width, height, margin) => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
@@ -10,7 +9,6 @@ import {
|
|||||||
} from "../../util/stateManager/colorHelpers";
|
} from "../../util/stateManager/colorHelpers";
|
||||||
|
|
||||||
// create continuous color legend
|
// create continuous color legend
|
||||||
// http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f
|
|
||||||
const continuous = (selectorId, colorScale, colorAccessor) => {
|
const continuous = (selectorId, colorScale, colorAccessor) => {
|
||||||
const legendHeight = 200;
|
const legendHeight = 200;
|
||||||
const legendWidth = 80;
|
const legendWidth = 80;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
function Container(props) {
|
function Container(props) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import { AnchorButton, Icon } from "@blueprintjs/core";
|
||||||
|
import Truncate from "../util/truncate";
|
||||||
|
import HistogramBrush from "../brushableHistogram";
|
||||||
|
|
||||||
|
import * as globals from "../../globals";
|
||||||
|
import actions from "../../actions";
|
||||||
|
|
||||||
|
@connect((state, ownProps) => {
|
||||||
|
const { gene } = ownProps;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isColorAccessor: state.colors.colorAccessor === gene,
|
||||||
|
isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === gene,
|
||||||
|
isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === gene,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
class Gene extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
geneIsExpanded: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onColorChangeClick = () => {
|
||||||
|
const { dispatch, gene } = this.props;
|
||||||
|
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(gene));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleGeneExpandClick = () => {
|
||||||
|
const { geneIsExpanded } = this.state;
|
||||||
|
this.setState({ geneIsExpanded: !geneIsExpanded });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSetGeneAsScatterplotX = () => {
|
||||||
|
const { dispatch, gene } = this.props;
|
||||||
|
dispatch({
|
||||||
|
type: "set scatterplot x",
|
||||||
|
data: gene,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSetGeneAsScatterplotY = () => {
|
||||||
|
const { dispatch, gene } = this.props;
|
||||||
|
dispatch({
|
||||||
|
type: "set scatterplot y",
|
||||||
|
data: gene,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDeleteGeneFromSet = () => {
|
||||||
|
const { dispatch, gene, geneset } = this.props;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: delete genes",
|
||||||
|
genesetName: geneset,
|
||||||
|
geneSymbols: [gene],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
gene,
|
||||||
|
isColorAccessor,
|
||||||
|
isScatterplotXXaccessor,
|
||||||
|
isScatterplotYYaccessor,
|
||||||
|
isDiffexp,
|
||||||
|
} = this.props;
|
||||||
|
const { geneIsExpanded } = this.state;
|
||||||
|
const genesetNameLengthVisible = 310; /* this magic number determines how much of a long geneset name we see */
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
marginRight: 0,
|
||||||
|
marginTop: 2,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="menuitem"
|
||||||
|
tabIndex="0"
|
||||||
|
data-testclass="gene-expand"
|
||||||
|
data-testid={`${gene}:gene-expand`}
|
||||||
|
onKeyPress={() => {}}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Icon
|
||||||
|
icon="drag-handle-horizontal"
|
||||||
|
iconSize={12}
|
||||||
|
style={{
|
||||||
|
marginRight: 7,
|
||||||
|
cursor: "grab",
|
||||||
|
position: "relative",
|
||||||
|
top: -1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Truncate>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: globals.leftSidebarWidth - genesetNameLengthVisible,
|
||||||
|
}}
|
||||||
|
data-testid={`${gene}:gene-label`}
|
||||||
|
>
|
||||||
|
{gene}
|
||||||
|
</span>
|
||||||
|
</Truncate>
|
||||||
|
</div>
|
||||||
|
{!geneIsExpanded ? (
|
||||||
|
<HistogramBrush isUserDefined field={gene} mini />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div style={{ flexShrink: 0, marginLeft: 2 }}>
|
||||||
|
{!isDiffexp ? (
|
||||||
|
<AnchorButton
|
||||||
|
minimal
|
||||||
|
small
|
||||||
|
data-testid={`delete-from-geneset-${gene}`}
|
||||||
|
onClick={this.handleDeleteGeneFromSet}
|
||||||
|
intent="none"
|
||||||
|
style={{ fontWeight: 700, marginRight: 2 }}
|
||||||
|
icon={<Icon icon="trash" iconSize={10} />}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<AnchorButton
|
||||||
|
minimal
|
||||||
|
small
|
||||||
|
data-testid={`plot-x-${gene}`}
|
||||||
|
onClick={this.handleSetGeneAsScatterplotX}
|
||||||
|
active={isScatterplotXXaccessor}
|
||||||
|
intent={isScatterplotXXaccessor ? "primary" : "none"}
|
||||||
|
style={{ fontWeight: 700, marginRight: 2 }}
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</AnchorButton>
|
||||||
|
<AnchorButton
|
||||||
|
minimal
|
||||||
|
small
|
||||||
|
data-testid={`plot-y-${gene}`}
|
||||||
|
onClick={this.handleSetGeneAsScatterplotY}
|
||||||
|
active={isScatterplotYYaccessor}
|
||||||
|
intent={isScatterplotYYaccessor ? "primary" : "none"}
|
||||||
|
style={{ fontWeight: 700, marginRight: 2 }}
|
||||||
|
>
|
||||||
|
y
|
||||||
|
</AnchorButton>
|
||||||
|
<AnchorButton
|
||||||
|
minimal
|
||||||
|
small
|
||||||
|
data-testclass="maximize"
|
||||||
|
data-testid={`maximize-${gene}`}
|
||||||
|
onClick={this.handleGeneExpandClick}
|
||||||
|
active={geneIsExpanded}
|
||||||
|
intent="none"
|
||||||
|
icon={<Icon icon="maximize" iconSize={10} />}
|
||||||
|
style={{ marginRight: 2 }}
|
||||||
|
/>
|
||||||
|
<AnchorButton
|
||||||
|
minimal
|
||||||
|
small
|
||||||
|
data-testclass="colorby"
|
||||||
|
data-testid={`colorby-${gene}`}
|
||||||
|
onClick={this.onColorChangeClick}
|
||||||
|
active={isColorAccessor}
|
||||||
|
intent={isColorAccessor ? "primary" : "none"}
|
||||||
|
icon={<Icon icon="tint" iconSize={12} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{geneIsExpanded ? <HistogramBrush isUserDefined field={gene} /> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Gene;
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Tooltip, Position, Switch } from "@blueprintjs/core";
|
||||||
|
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
||||||
|
import actions from "../../actions";
|
||||||
|
import Gene from "./gene";
|
||||||
|
import { memoize } from "../../util/dataframe/util";
|
||||||
|
import Truncate from "../util/truncate";
|
||||||
|
import * as globals from "../../globals";
|
||||||
|
import GenesetMenus from "./menus/genesetMenus";
|
||||||
|
|
||||||
|
@connect((state, ownProps) => {
|
||||||
|
return {
|
||||||
|
world: state.world,
|
||||||
|
userDefinedGenes: state.controls.userDefinedGenes,
|
||||||
|
userDefinedGenesLoading: state.controls.userDefinedGenesLoading,
|
||||||
|
isColorAccessor: state.colors.colorAccessor === ownProps.setName,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
class GeneSet extends React.Component {
|
||||||
|
_memoGenesToUpper = memoize(this._genesToUpper, (arr) => arr);
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOpen: false,
|
||||||
|
toggleSummaryHisto: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_genesToUpper = (listGenes) => {
|
||||||
|
// Has to be a Map to preserve index
|
||||||
|
const upperGenes = new Map();
|
||||||
|
for (let i = 0, { length } = listGenes; i < length; i += 1) {
|
||||||
|
upperGenes.set(listGenes[i].toUpperCase(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return upperGenes;
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchGenes = () => {
|
||||||
|
const { world, dispatch, setGenes } = this.props;
|
||||||
|
const varIndexName = world.schema.annotations.var.index;
|
||||||
|
|
||||||
|
const worldGenes = world.varAnnotations.col(varIndexName).asArray();
|
||||||
|
|
||||||
|
const upperGenes = this._genesToUpper(setGenes);
|
||||||
|
const upperWorldGenes = this._memoGenesToUpper(worldGenes);
|
||||||
|
|
||||||
|
dispatch({ type: "bulk user defined gene start" });
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
[...upperGenes.keys()].map((upperGene) => {
|
||||||
|
const indexOfGene = upperWorldGenes.get(upperGene);
|
||||||
|
|
||||||
|
return dispatch(
|
||||||
|
actions.requestUserDefinedGene(worldGenes[indexOfGene])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
).then(
|
||||||
|
() => dispatch({ type: "bulk user defined gene complete" }),
|
||||||
|
() => dispatch({ type: "bulk user defined gene error" })
|
||||||
|
);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
onGenesetMenuClick = () => {
|
||||||
|
const { isOpen } = this.state;
|
||||||
|
this.setState({ isOpen: !isOpen });
|
||||||
|
};
|
||||||
|
|
||||||
|
onColorChangeClick = () => {
|
||||||
|
// const { dispatch, setName } = this.props;
|
||||||
|
// dispatch({
|
||||||
|
// type: "color by gene set",
|
||||||
|
// colorAccessor: setName,
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleSummaryHisto = () => {
|
||||||
|
const { toggleSummaryHisto } = this.state;
|
||||||
|
this.setState({ toggleSummaryHisto: !toggleSummaryHisto });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { setName, setGenes, isDiffexp } = this.props;
|
||||||
|
const { isOpen, toggleSummaryHisto } = this.state;
|
||||||
|
const genesetNameLengthVisible = 120; /* this magic number determines how much of a long geneset name we see */
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 3 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "baseline",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
role="menuitem"
|
||||||
|
tabIndex="0"
|
||||||
|
data-testclass="geneset-expand"
|
||||||
|
data-testid={`${setName}:geneset-expand`}
|
||||||
|
onKeyPress={
|
||||||
|
/* TODO(colinmegill): #2101: click handler on span */ () => {}
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
onClick={this.onGenesetMenuClick}
|
||||||
|
>
|
||||||
|
<Truncate>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
maxWidth: globals.leftSidebarWidth - genesetNameLengthVisible,
|
||||||
|
}}
|
||||||
|
data-testid={`${setName}:geneset-label`}
|
||||||
|
>
|
||||||
|
{setName}
|
||||||
|
</span>
|
||||||
|
</Truncate>
|
||||||
|
{isOpen ? (
|
||||||
|
<FaChevronDown
|
||||||
|
data-testclass="geneset-expand-is-expanded"
|
||||||
|
style={{ fontSize: 10, marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FaChevronRight
|
||||||
|
data-testclass="geneset-expand-is-not-expanded"
|
||||||
|
style={{ fontSize: 10, marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<GenesetMenus genesetsEditable geneset={setName} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginLeft: 15, marginTop: 5, marginRight: 0 }}>
|
||||||
|
{isOpen ? (
|
||||||
|
<Tooltip
|
||||||
|
content="Aggregate all genes in this geneset, and allow coloring by and selecting on the entire set."
|
||||||
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
usePortal
|
||||||
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
modifiers={{
|
||||||
|
preventOverflow: { enabled: false },
|
||||||
|
hide: { enabled: false },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
style={{ fontStyle: "italic" }}
|
||||||
|
checked={toggleSummaryHisto}
|
||||||
|
label="Show mean expression across entire set"
|
||||||
|
alignIndicator="left"
|
||||||
|
innerLabel="off"
|
||||||
|
innerLabelChecked="on"
|
||||||
|
onChange={this.toggleSummaryHisto}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen && !toggleSummaryHisto
|
||||||
|
? _.map(setGenes, (gene) => {
|
||||||
|
return (
|
||||||
|
<Gene
|
||||||
|
key={gene}
|
||||||
|
gene={gene}
|
||||||
|
geneset={setName}
|
||||||
|
isDiffexp={isDiffexp}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GeneSet;
|
||||||
@@ -1,59 +1,78 @@
|
|||||||
/* rc slider https://www.npmjs.com/package/rc-slider */
|
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import HistogramBrush from "../brushableHistogram";
|
import { Button } from "@blueprintjs/core";
|
||||||
import * as globals from "../../globals";
|
import GeneSet from "./geneSet";
|
||||||
import AddGenes from "./addGenes";
|
|
||||||
|
import CreateGenesetDialogue from "./menus/createGenesetDialogue";
|
||||||
|
import EditGenesetNameDialogue from "./menus/editGenesetNameDialogue";
|
||||||
|
|
||||||
@connect((state) => {
|
@connect((state) => {
|
||||||
return {
|
return {
|
||||||
userDefinedGenes: state.controls.userDefinedGenes,
|
|
||||||
differential: state.differential,
|
differential: state.differential,
|
||||||
|
genesets: state.genesets.genesets,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
class GeneExpression extends React.Component {
|
class GeneExpression extends React.Component {
|
||||||
|
renderGeneSets = () => {
|
||||||
|
const sets = [];
|
||||||
|
const { genesets } = this.props;
|
||||||
|
|
||||||
|
for (const [name, genes] of genesets) {
|
||||||
|
sets.push(
|
||||||
|
<GeneSet
|
||||||
|
key={name}
|
||||||
|
setGenes={Array.from(genes.genes.keys())}
|
||||||
|
setName={name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return sets;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDiffexpGeneSets = () => {
|
||||||
|
const { differential } = this.props;
|
||||||
|
|
||||||
|
const setGenes = [];
|
||||||
|
|
||||||
|
if (differential.diffExp) {
|
||||||
|
differential.diffExp.forEach((diffexpGene) => {
|
||||||
|
setGenes.push(diffexpGene[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return differential.diffExp ? (
|
||||||
|
<GeneSet
|
||||||
|
key="Temp DiffExp Set"
|
||||||
|
setGenes={setGenes}
|
||||||
|
isDiffexp
|
||||||
|
setName="Temp DiffExp Set"
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleActivateCreateGenesetMode = () => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
dispatch({ type: "geneset: activate add new geneset mode" });
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { userDefinedGenes, differential } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div>
|
||||||
style={{
|
|
||||||
borderBottom: `1px solid ${globals.lighterGrey}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<AddGenes />
|
<div style={{ marginBottom: 10, position: "relative", top: -2 }}>
|
||||||
{userDefinedGenes.length > 0
|
<Button
|
||||||
? _.map(userDefinedGenes, (geneName, index) => {
|
data-testid="open-create-geneset-dialog"
|
||||||
return (
|
onClick={this.handleActivateCreateGenesetMode}
|
||||||
<HistogramBrush
|
intent="primary"
|
||||||
key={geneName}
|
>
|
||||||
field={geneName}
|
Create new <strong>gene set</strong>
|
||||||
zebra={index % 2 === 0}
|
</Button>
|
||||||
isUserDefined
|
</div>
|
||||||
/>
|
<CreateGenesetDialogue />
|
||||||
);
|
<EditGenesetNameDialogue />
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{differential.diffExp
|
|
||||||
? _.map(differential.diffExp, (value, index) => {
|
|
||||||
return (
|
|
||||||
<HistogramBrush
|
|
||||||
key={value[0]}
|
|
||||||
field={value[0]}
|
|
||||||
zebra={index % 2 === 0}
|
|
||||||
isDiffExp
|
|
||||||
logFoldChange={value[1]}
|
|
||||||
pval={value[2]}
|
|
||||||
pvalAdj={value[3]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>{this.renderDiffexpGeneSets()}</div>
|
||||||
|
<div>{this.renderGeneSets()}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import AnnoDialog from "../../annoDialog";
|
||||||
|
import LabelInput from "../../labelInput";
|
||||||
|
import parseBulkGeneString from "../../../util/parseBulkGeneString";
|
||||||
|
|
||||||
|
@connect((state) => ({
|
||||||
|
genesetsUI: state.genesetsUI,
|
||||||
|
}))
|
||||||
|
class AddGeneToGenesetDialogue extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
genesToAdd: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
disableAddGeneMode = () => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: disable add new genes mode",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleAddGeneToGeneSet = (e) => {
|
||||||
|
const { geneset, dispatch } = this.props;
|
||||||
|
const { genesToAdd } = this.state;
|
||||||
|
|
||||||
|
const genesTmpHardcodedFormat = [];
|
||||||
|
|
||||||
|
const genesArrayFromString = parseBulkGeneString(genesToAdd);
|
||||||
|
|
||||||
|
genesArrayFromString.forEach((_gene) => {
|
||||||
|
genesTmpHardcodedFormat.push({
|
||||||
|
geneSymbol: _gene,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: add genes",
|
||||||
|
genesetName: geneset,
|
||||||
|
genes: genesTmpHardcodedFormat,
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: disable add new genes mode",
|
||||||
|
});
|
||||||
|
if (e) e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.setState({ genesToAdd: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { geneset, genesetsUI } = this.props;
|
||||||
|
const { genesToAdd } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AnnoDialog
|
||||||
|
isActive={genesetsUI.isAddingGenesToGeneset === geneset}
|
||||||
|
inputProps={{ "data-testid": `${geneset}:create-label-dialog` }}
|
||||||
|
primaryButtonProps={{
|
||||||
|
"data-testid": `${geneset}:submit-label`,
|
||||||
|
}}
|
||||||
|
title="Add genes to gene set"
|
||||||
|
instruction={`Add gene to ${geneset}`}
|
||||||
|
cancelTooltipContent="Close this dialog without adding genes to gene set."
|
||||||
|
primaryButtonText="Add genes"
|
||||||
|
text={genesToAdd}
|
||||||
|
validationError={false}
|
||||||
|
annoInput={
|
||||||
|
<LabelInput
|
||||||
|
onChange={this.handleChange}
|
||||||
|
inputProps={{
|
||||||
|
"data-testid": "add-genes",
|
||||||
|
leftIcon: "manually-entered-data",
|
||||||
|
intent: "none",
|
||||||
|
autoFocus: true,
|
||||||
|
}}
|
||||||
|
newLabelMessage="New category"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
handleSubmit={this.handleAddGeneToGeneSet}
|
||||||
|
handleCancel={this.disableAddGeneMode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddGeneToGenesetDialogue;
|
||||||
+6
-8
@@ -1,7 +1,4 @@
|
|||||||
/* rc slider https://www.npmjs.com/package/rc-slider */
|
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
|
||||||
import fuzzysort from "fuzzysort";
|
import fuzzysort from "fuzzysort";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Suggest } from "@blueprintjs/select";
|
import { Suggest } from "@blueprintjs/select";
|
||||||
@@ -13,14 +10,15 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../../globals";
|
||||||
import actions from "../../actions";
|
import actions from "../../../actions";
|
||||||
import {
|
import {
|
||||||
postUserErrorToast,
|
postUserErrorToast,
|
||||||
keepAroundErrorToast,
|
keepAroundErrorToast,
|
||||||
} from "../framework/toasters";
|
} from "../../framework/toasters";
|
||||||
|
|
||||||
import { memoize } from "../../util/dataframe/util";
|
import { memoize } from "../../../util/dataframe/util";
|
||||||
|
import parseBulkGeneString from "../../../util/parseBulkGeneString";
|
||||||
|
|
||||||
const renderGene = (fuzzySortResult, { handleClick, modifiers }) => {
|
const renderGene = (fuzzySortResult, { handleClick, modifiers }) => {
|
||||||
if (!modifiers.matchesPredicate) {
|
if (!modifiers.matchesPredicate) {
|
||||||
@@ -101,7 +99,7 @@ class AddGenes extends React.Component {
|
|||||||
Apod,,, Cd74,, ,,, Foo, Bar-2,,
|
Apod,,, Cd74,, ,,, Foo, Bar-2,,
|
||||||
*/
|
*/
|
||||||
if (bulkAdd !== "") {
|
if (bulkAdd !== "") {
|
||||||
const genes = _.pull(_.uniq(bulkAdd.split(/[ ,]+/)), "");
|
const genes = parseBulkGeneString(bulkAdd);
|
||||||
if (genes.length === 0) {
|
if (genes.length === 0) {
|
||||||
return keepAroundErrorToast("Must enter a gene name.");
|
return keepAroundErrorToast("Must enter a gene name.");
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import _ from "lodash";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import AnnoDialog from "../../annoDialog";
|
||||||
|
import LabelInput from "../../labelInput";
|
||||||
|
|
||||||
|
@connect((state) => ({
|
||||||
|
annotations: state.annotations,
|
||||||
|
schema: state.annoMatrix?.schema,
|
||||||
|
ontology: state.ontology,
|
||||||
|
obsCrossfilter: state.obsCrossfilter,
|
||||||
|
genesets: state.genesets.genesets,
|
||||||
|
genesetsUI: state.genesetsUI,
|
||||||
|
}))
|
||||||
|
class CreateGenesetDialogue extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
genesetName: "",
|
||||||
|
genesToPopulateGeneset: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
disableCreateGenesetMode = (e) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
this.setState({
|
||||||
|
genesetName: "",
|
||||||
|
genesToPopulateGeneset: "",
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: disable create geneset mode",
|
||||||
|
});
|
||||||
|
if (e) e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
createGeneset = (e) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
const { genesetName, genesToPopulateGeneset } = this.state;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: create",
|
||||||
|
genesetName,
|
||||||
|
genesetDescription: "",
|
||||||
|
});
|
||||||
|
if (genesToPopulateGeneset) {
|
||||||
|
const genesTmpHardcodedFormat = [];
|
||||||
|
|
||||||
|
const genesArrayFromString = _.pull(
|
||||||
|
_.uniq(genesToPopulateGeneset.split(/[ ,]+/)),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
genesArrayFromString.forEach((_gene) => {
|
||||||
|
genesTmpHardcodedFormat.push({
|
||||||
|
geneSymbol: _gene,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: add genes",
|
||||||
|
genesetName,
|
||||||
|
genes: genesTmpHardcodedFormat,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: disable create geneset mode",
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
genesetName: "",
|
||||||
|
genesToPopulateGeneset: "",
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
genesetNameError = () => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.setState({ genesetName: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleGenesetInputChange = (e) => {
|
||||||
|
this.setState({ genesToPopulateGeneset: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
instruction = (genesetName, genesets) => {
|
||||||
|
return genesets.has(genesetName)
|
||||||
|
? "Geneset name must be unique."
|
||||||
|
: "New, unique geneset name";
|
||||||
|
};
|
||||||
|
|
||||||
|
validate = (genesetName, genesets) => {
|
||||||
|
return genesets.has(genesetName);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { genesetName } = this.state;
|
||||||
|
const { metadataField, genesetsUI, genesets } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AnnoDialog
|
||||||
|
isActive={genesetsUI.createGenesetModeActive}
|
||||||
|
inputProps={{
|
||||||
|
"data-testid": `${metadataField}:create-geneset-dialog`,
|
||||||
|
}}
|
||||||
|
primaryButtonProps={{
|
||||||
|
"data-testid": `${metadataField}:submit-geneset`,
|
||||||
|
}}
|
||||||
|
title="Create gene set"
|
||||||
|
instruction={this.instruction(genesetName, genesets)}
|
||||||
|
cancelTooltipContent="Close this dialog without creating a new gene set."
|
||||||
|
primaryButtonText="Create gene set"
|
||||||
|
text={genesetName}
|
||||||
|
validationError={this.validate(genesetName, genesets)}
|
||||||
|
annoInput={
|
||||||
|
<LabelInput
|
||||||
|
onChange={this.handleChange}
|
||||||
|
inputProps={{
|
||||||
|
"data-testid": "create-geneset-modal",
|
||||||
|
leftIcon: "manually-entered-data",
|
||||||
|
intent: "none",
|
||||||
|
autoFocus: true,
|
||||||
|
}}
|
||||||
|
newLabelMessage="Create gene set"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
secondaryInstructions="Optionally add a list of comma separated genes to populate the gene set"
|
||||||
|
secondaryInput={
|
||||||
|
<LabelInput
|
||||||
|
onChange={this.handleGenesetInputChange}
|
||||||
|
inputProps={{
|
||||||
|
"data-testid": "add-genes",
|
||||||
|
intent: "none",
|
||||||
|
autoFocus: false,
|
||||||
|
}}
|
||||||
|
newLabelMessage="populate geneset with genes"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
handleSubmit={this.createGeneset}
|
||||||
|
handleCancel={this.disableCreateGenesetMode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateGenesetDialogue;
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import AnnoDialog from "../../annoDialog";
|
||||||
|
import LabelInput from "../../labelInput";
|
||||||
|
|
||||||
|
@connect((state) => ({
|
||||||
|
annotations: state.annotations,
|
||||||
|
schema: state.annoMatrix?.schema,
|
||||||
|
ontology: state.ontology,
|
||||||
|
obsCrossfilter: state.obsCrossfilter,
|
||||||
|
genesetsUI: state.genesetsUI,
|
||||||
|
}))
|
||||||
|
class RenameGeneset extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
genesetName: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
disableEditGenesetNameMode = (e) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
this.setState({
|
||||||
|
genesetName: "",
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: disable rename geneset mode",
|
||||||
|
});
|
||||||
|
if (e) e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
renameGeneset = (e) => {
|
||||||
|
const { dispatch, genesetsUI } = this.props;
|
||||||
|
const { genesetName } = this.state;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: update",
|
||||||
|
genesetName: genesetsUI.isEditingGenesetName,
|
||||||
|
update: { genesetName },
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: disable rename geneset mode",
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
genesetNameError = () => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.setState({ genesetName: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { genesetName } = this.state;
|
||||||
|
const { genesetsUI } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AnnoDialog
|
||||||
|
isActive={genesetsUI.isEditingGenesetName}
|
||||||
|
inputProps={{
|
||||||
|
"data-testid": `${genesetsUI.isEditingGenesetName}:rename-geneset-dialog`,
|
||||||
|
}}
|
||||||
|
primaryButtonProps={{
|
||||||
|
"data-testid": `${genesetsUI.isEditingGenesetName}:submit-geneset`,
|
||||||
|
}}
|
||||||
|
title="Rename gene set"
|
||||||
|
instruction={`Rename ${genesetsUI.isEditingGenesetName}`}
|
||||||
|
cancelTooltipContent="Close this dialog without renaming the gene set."
|
||||||
|
primaryButtonText="Rename gene set"
|
||||||
|
text={genesetName}
|
||||||
|
validationError={genesetsUI.isEditingGenesetName === genesetName}
|
||||||
|
annoInput={
|
||||||
|
<LabelInput
|
||||||
|
onChange={this.handleChange}
|
||||||
|
inputProps={{
|
||||||
|
"data-testid": "rename-geneset-modal",
|
||||||
|
leftIcon: "manually-entered-data",
|
||||||
|
intent: "none",
|
||||||
|
autoFocus: true,
|
||||||
|
}}
|
||||||
|
newLabelMessage="Rename gene set"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
handleSubmit={this.renameGeneset}
|
||||||
|
handleCancel={this.disableEditGenesetNameMode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenameGeneset;
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Popover,
|
||||||
|
Position,
|
||||||
|
Tooltip,
|
||||||
|
Icon,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
|
import * as globals from "../../../globals";
|
||||||
|
import AddGeneToGenesetDialogue from "./addGeneToGenesetDialogue";
|
||||||
|
|
||||||
|
@connect((state) => {
|
||||||
|
return {
|
||||||
|
genesetsUI: state.genesetsUI,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
class GenesetMenus extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
activateAddGeneToGenesetMode = () => {
|
||||||
|
const { dispatch, geneset } = this.props;
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: activate add new genes mode",
|
||||||
|
geneset,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
activateEditGenesetNameMode = () => {
|
||||||
|
const { dispatch, geneset } = this.props;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: activate rename geneset mode",
|
||||||
|
data: geneset,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDeleteCategory = () => {
|
||||||
|
const { dispatch, geneset } = this.props;
|
||||||
|
dispatch({ type: "geneset: delete", genesetName: geneset });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { geneset, genesetsEditable, createText } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{genesetsEditable ? (
|
||||||
|
<>
|
||||||
|
<Tooltip
|
||||||
|
content={createText}
|
||||||
|
position={Position.BOTTOM}
|
||||||
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 0, marginRight: 2 }}
|
||||||
|
data-testclass="handleAddNewLabelToCategory"
|
||||||
|
data-testid={`${geneset}:add-new-label-to-category`}
|
||||||
|
icon={<Icon icon="plus" iconSize={10} />}
|
||||||
|
onClick={this.activateAddGeneToGenesetMode}
|
||||||
|
small
|
||||||
|
minimal
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<AddGeneToGenesetDialogue geneset={geneset} />
|
||||||
|
<Popover
|
||||||
|
interactionKind={PopoverInteractionKind.HOVER}
|
||||||
|
boundary="window"
|
||||||
|
position={Position.BOTTOM}
|
||||||
|
content={
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
icon="edit"
|
||||||
|
data-testclass="activateEditGenesetNameMode"
|
||||||
|
data-testid={`${geneset}:edit-genesetName-mode`}
|
||||||
|
onClick={this.activateEditGenesetNameMode}
|
||||||
|
text="Edit gene set name"
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
icon="trash"
|
||||||
|
intent="danger"
|
||||||
|
data-testclass="handleDeleteCategory"
|
||||||
|
data-testid={`${geneset}:delete-category`}
|
||||||
|
onClick={this.handleDeleteCategory}
|
||||||
|
text="Delete this gene set (destructive, will remove set and collection of genes)"
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 0, marginRight: 5 }}
|
||||||
|
data-testclass="seeActions"
|
||||||
|
data-testid={`${geneset}:see-actions`}
|
||||||
|
icon={<Icon icon="more" iconSize={10} />}
|
||||||
|
small
|
||||||
|
minimal
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
<Button
|
||||||
|
disabled
|
||||||
|
style={{ marginLeft: 0 }}
|
||||||
|
data-testclass="colorby-entire-geneset"
|
||||||
|
data-testid={`${geneset}:colorby-entire-geneset`}
|
||||||
|
icon={<Icon icon="tint" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GenesetMenus;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import { connect, shallowEqual } from "react-redux";
|
import { connect, shallowEqual } from "react-redux";
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// https://bl.ocks.org/pbeshai/8008075f9ce771ee8be39e8c38907570
|
|
||||||
|
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import { Colors } from "@blueprintjs/core";
|
import { Colors } from "@blueprintjs/core";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import Lasso from "./setupLasso";
|
import Lasso from "./setupLasso";
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import Categorical from "../categorical";
|
import Categorical from "../categorical";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import DynamicScatterplot from "../scatterplot/scatterplot";
|
import DynamicScatterplot from "../scatterplot/scatterplot";
|
||||||
import TopLeftLogoAndTitle from "./topLeftLogoAndTitle";
|
import TopLeftLogoAndTitle from "./topLeftLogoAndTitle";
|
||||||
|
import Continuous from "../continuous/continuous";
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
@@ -32,6 +32,7 @@ class LeftSideBar extends React.Component {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Categorical />
|
<Categorical />
|
||||||
|
<Continuous />
|
||||||
</div>
|
</div>
|
||||||
{scatterplotXXaccessor && scatterplotYYaccessor ? (
|
{scatterplotXXaccessor && scatterplotYYaccessor ? (
|
||||||
<DynamicScatterplot />
|
<DynamicScatterplot />
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button, Menu, MenuItem, Popover, Position } from "@blueprintjs/core";
|
import { Button, Menu, MenuItem, Popover, Position } from "@blueprintjs/core";
|
||||||
import { IconNames } from "@blueprintjs/icons";
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AnchorButton, Tooltip } from "@blueprintjs/core";
|
import { AnchorButton, Tooltip } from "@blueprintjs/core";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import Continuous from "../continuous/continuous";
|
|
||||||
import GeneExpression from "../geneExpression";
|
import GeneExpression from "../geneExpression";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
@@ -22,10 +20,10 @@ class RightSidebar extends React.Component {
|
|||||||
overflowY: "inherit",
|
overflowY: "inherit",
|
||||||
height: "inherit",
|
height: "inherit",
|
||||||
width: "inherit",
|
width: "inherit",
|
||||||
|
padding: globals.leftSidebarSectionPadding,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GeneExpression />
|
<GeneExpression />
|
||||||
<Continuous />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
export const margin = { top: 40, right: 5, bottom: 20, left: 60 };
|
export const margin = { top: 40, right: 5, bottom: 20, left: 60 };
|
||||||
export const width = 340;
|
export const width = 340;
|
||||||
export const height = 340 - margin.top - margin.bottom;
|
export const height = 340 - margin.top - margin.bottom;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { cloneElement } from "react";
|
import React, { cloneElement } from "react";
|
||||||
import { Tooltip, Position } from "@blueprintjs/core";
|
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||||
|
|
||||||
import { tooltipHoverOpenDelayQuick } from "../../globals";
|
import { tooltipHoverOpenDelayQuick } from "../../globals";
|
||||||
|
|
||||||
@@ -86,18 +86,12 @@ export default (props) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip2
|
||||||
content={originalString}
|
content={originalString}
|
||||||
hoverOpenDelay={tooltipHoverOpenDelayQuick}
|
hoverOpenDelay={tooltipHoverOpenDelayQuick}
|
||||||
position={Position.LEFT}
|
|
||||||
usePortal
|
|
||||||
modifiers={{
|
|
||||||
preventOverflow: { enabled: false },
|
|
||||||
hide: { enabled: false },
|
|
||||||
}}
|
|
||||||
targetProps={{ style: children.props.style }}
|
targetProps={{ style: children.props.style }}
|
||||||
>
|
>
|
||||||
{newChildren}
|
{newChildren}
|
||||||
</Tooltip>
|
</Tooltip2>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace mangling on these definitions. See webpack config for specifics.
|
|||||||
@import "~@blueprintjs/core/lib/css/blueprint.css";
|
@import "~@blueprintjs/core/lib/css/blueprint.css";
|
||||||
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
|
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||||
@import "~@blueprintjs/select/lib/css/blueprint-select.css";
|
@import "~@blueprintjs/select/lib/css/blueprint-select.css";
|
||||||
|
@import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css";
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Roboto Condensed";
|
font-family: "Roboto Condensed";
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const Autosave = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
genesetSaveInProgress: false,
|
genesetSaveInProgress: false,
|
||||||
lastSavedGenesets: nextSharedState.genesets,
|
lastSavedGenesets: nextSharedState.genesets.genesets,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
const Config = (
|
const Config = (
|
||||||
state = {
|
state = {
|
||||||
displayNames: null,
|
displayNames: null,
|
||||||
|
|||||||
Vendored
-2
@@ -1,5 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { subsetAndResetGeneLists } from "../util/stateManager/controlsHelpers";
|
import { subsetAndResetGeneLists } from "../util/stateManager/controlsHelpers";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
|
|
||||||
const Differential = (
|
const Differential = (
|
||||||
state = {
|
state = {
|
||||||
diffExp: null,
|
diffExp: null,
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ const GeneSets = (
|
|||||||
*/
|
*/
|
||||||
case "geneset: add genes": {
|
case "geneset: add genes": {
|
||||||
const { genesetName, genes } = action;
|
const { genesetName, genes } = action;
|
||||||
|
|
||||||
if (!state.genesets.has(genesetName))
|
if (!state.genesets.has(genesetName))
|
||||||
throw new Error("geneset: add genes -- geneset name does not exist.");
|
throw new Error("geneset: add genes -- geneset name does not exist.");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Reducers for geneset UI-state.
|
||||||
|
*/
|
||||||
|
const GeneSetsUI = (
|
||||||
|
state = {
|
||||||
|
createGenesetModeActive: false,
|
||||||
|
isEditingGenesetName: false,
|
||||||
|
isAddingGenesToGeneset: false,
|
||||||
|
},
|
||||||
|
action
|
||||||
|
) => {
|
||||||
|
switch (action.type) {
|
||||||
|
/**
|
||||||
|
* Activate interface for adding a new geneset
|
||||||
|
* No params, if the action is fired we flip
|
||||||
|
* a boolean here.
|
||||||
|
*/
|
||||||
|
case "geneset: activate add new geneset mode": {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
createGenesetModeActive: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disable interface for adding a new geneset
|
||||||
|
* No params, if the action is fired we flip
|
||||||
|
* a boolean here.
|
||||||
|
*/
|
||||||
|
case "geneset: disable create geneset mode": {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
createGenesetModeActive: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Activate the interface for adding new genes to a geneset
|
||||||
|
* isAddingGenesToGeneset {
|
||||||
|
* geneset: string, name of geneset
|
||||||
|
* },
|
||||||
|
*/
|
||||||
|
case "geneset: activate add new genes mode": {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isAddingGenesToGeneset: action.geneset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disable the interface for adding new genes to a geneset
|
||||||
|
* No params, if the action is fired we flip
|
||||||
|
* a boolean here.
|
||||||
|
*/
|
||||||
|
case "geneset: disable add new genes mode": {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isAddingGenesToGeneset: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Activate the interface for renaming a geneset
|
||||||
|
* isEditingGenesetName: {
|
||||||
|
* type: "geneset: activate rename geneset mode",
|
||||||
|
* data: geneset, // a string, name of geneset
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
case "geneset: activate rename geneset mode": {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isEditingGenesetName: action.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disable the interface for renaming a geneset
|
||||||
|
* No params, if the action is fired we flip
|
||||||
|
* a boolean here.
|
||||||
|
*/
|
||||||
|
case "geneset: disable rename geneset mode": {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isEditingGenesetName: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GeneSetsUI;
|
||||||
@@ -16,6 +16,7 @@ import layoutChoice from "./layoutChoice";
|
|||||||
import controls from "./controls";
|
import controls from "./controls";
|
||||||
import annotations from "./annotations";
|
import annotations from "./annotations";
|
||||||
import genesets from "./genesets";
|
import genesets from "./genesets";
|
||||||
|
import genesetsUI from "./genesetsUI";
|
||||||
import autosave from "./autosave";
|
import autosave from "./autosave";
|
||||||
import ontology from "./ontology";
|
import ontology from "./ontology";
|
||||||
import centroidLabels from "./centroidLabels";
|
import centroidLabels from "./centroidLabels";
|
||||||
@@ -33,6 +34,7 @@ const Reducer = undoable(
|
|||||||
["ontology", ontology],
|
["ontology", ontology],
|
||||||
["annotations", annotations],
|
["annotations", annotations],
|
||||||
["genesets", genesets],
|
["genesets", genesets],
|
||||||
|
["genesetsUI", genesetsUI],
|
||||||
["layoutChoice", layoutChoice],
|
["layoutChoice", layoutChoice],
|
||||||
["categoricalSelection", categoricalSelection],
|
["categoricalSelection", categoricalSelection],
|
||||||
["continuousSelection", continuousSelection],
|
["continuousSelection", continuousSelection],
|
||||||
@@ -58,6 +60,7 @@ const Reducer = undoable(
|
|||||||
"layoutChoice",
|
"layoutChoice",
|
||||||
"centroidLabels",
|
"centroidLabels",
|
||||||
"genesets",
|
"genesets",
|
||||||
|
"genesetsUI",
|
||||||
"annotations",
|
"annotations",
|
||||||
],
|
],
|
||||||
undoableConfig
|
undoableConfig
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const skipOnActions = new Set([
|
|||||||
"window resize",
|
"window resize",
|
||||||
"reset colorscale",
|
"reset colorscale",
|
||||||
"reset centroid labels",
|
"reset centroid labels",
|
||||||
|
"geneset: initial load",
|
||||||
|
"geneset: set tid",
|
||||||
|
|
||||||
"graph brush change",
|
"graph brush change",
|
||||||
"continuous metadata histogram brush",
|
"continuous metadata histogram brush",
|
||||||
@@ -30,6 +32,9 @@ const skipOnActions = new Set([
|
|||||||
"writable obs annotations - save complete",
|
"writable obs annotations - save complete",
|
||||||
"writable obs annotations - save started",
|
"writable obs annotations - save started",
|
||||||
"writable obs annotations - save error",
|
"writable obs annotations - save error",
|
||||||
|
"autosave: genesets started",
|
||||||
|
"autosave: genesets error",
|
||||||
|
"autosave: genesets complete",
|
||||||
|
|
||||||
/* annotation component action */
|
/* annotation component action */
|
||||||
"annotation: activate add new label mode",
|
"annotation: activate add new label mode",
|
||||||
@@ -39,6 +44,14 @@ const skipOnActions = new Set([
|
|||||||
"annotation: activate edit label mode",
|
"annotation: activate edit label mode",
|
||||||
"annotation: cancel edit label mode",
|
"annotation: cancel edit label mode",
|
||||||
"set annotations collection name",
|
"set annotations collection name",
|
||||||
|
|
||||||
|
/* geneset component action */
|
||||||
|
"geneset: activate add new geneset mode",
|
||||||
|
"geneset: disable create geneset mode",
|
||||||
|
"geneset: activate add new genes mode",
|
||||||
|
"geneset: disable add new genes mode",
|
||||||
|
"geneset: activate rename geneset mode",
|
||||||
|
"geneset: disable rename geneset mode",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -90,6 +103,14 @@ const saveOnActions = new Set([
|
|||||||
"annotation: label current cell selection",
|
"annotation: label current cell selection",
|
||||||
"annotation: delete label",
|
"annotation: delete label",
|
||||||
"annotation: category edited",
|
"annotation: category edited",
|
||||||
|
|
||||||
|
/* geneset component action */
|
||||||
|
"geneset: create",
|
||||||
|
"geneset: delete",
|
||||||
|
"geneset: update",
|
||||||
|
"geneset: add genes",
|
||||||
|
"geneset: delete genes",
|
||||||
|
"geneset: set gene description",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* The function will ensure a number is above or below given thresholds
|
||||||
|
* @param val - a number
|
||||||
|
* @param rng - an array of two numbers, a min and a max
|
||||||
|
* Ie., in the case of a histogram brush selection:
|
||||||
|
* const x0 = x(clamp(selectionRange[0], [min, max]));
|
||||||
|
* const x1 = x(clamp(selectionRange[1], [min, max]));
|
||||||
|
* @returns a number
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function clamp(val, rng) {
|
||||||
|
return Math.max(Math.min(val, rng[1]), rng[0]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import significantDigits from "./significantDigits";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function will conditionally convert a d3 axis label to scientific notation
|
||||||
|
* @param x - a d3 axis, ie:
|
||||||
|
* d3
|
||||||
|
* .axisBottom(x)
|
||||||
|
* .ticks(4)
|
||||||
|
* .tickFormat(d3.format(maybeScientific(x)))
|
||||||
|
* @returns - the number formatted as scientific, if it's big enough
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function maybeScientific(x) {
|
||||||
|
let format = ",";
|
||||||
|
const _ticks = x.ticks(4);
|
||||||
|
|
||||||
|
if (x.domain().some((n) => Math.abs(n) >= 10000)) {
|
||||||
|
/*
|
||||||
|
heuristic: if the last tick d3 wants to render has one significant
|
||||||
|
digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render
|
||||||
|
4.20e+n
|
||||||
|
*/
|
||||||
|
format = significantDigits(_ticks[_ticks.length - 1]) === 1 ? ".0e" : ".2e";
|
||||||
|
}
|
||||||
|
|
||||||
|
return format;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* The function will split string by comma or space, return unique non-empty strings
|
||||||
|
* @param geneString - a string of comma delimited genes
|
||||||
|
* @returns an array
|
||||||
|
*/
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
export default function parseBulkGeneString(geneString) {
|
||||||
|
return _.pull(_.uniq(geneString.split(/[ ,]+/)), "");
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import scaleRGB from "./scaleRGB";
|
import scaleRGB from "./scaleRGB";
|
||||||
|
|
||||||
// maintain a cache of already parsed RGB names, as it is reasonably expensive
|
// maintain a cache of already parsed RGB names, as it is reasonably expensive
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
|
|
||||||
// Substitute for a d3 linear scale - less flexible, more performant.
|
// Substitute for a d3 linear scale - less flexible, more performant.
|
||||||
// Returns a function which will scale a value.
|
// Returns a function which will scale a value.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
export default (input) => {
|
export default (input) => {
|
||||||
const outputMax = 1;
|
const outputMax = 1;
|
||||||
const outputMin = 0;
|
const outputMin = 0;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
|
|
||||||
// Interval operations - very simple version of interval set relationship
|
// Interval operations - very simple version of interval set relationship
|
||||||
// operators. An interval is a multi-interval list of [min, max),
|
// operators. An interval is a multi-interval list of [min, max),
|
||||||
// where min and max are mandatory. Constraints:
|
// where min and max are mandatory. Constraints:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
|
|
||||||
import { sortIndex } from "./sort";
|
import { sortIndex } from "./sort";
|
||||||
import { rangeFill as fillRange } from "../range";
|
import { rangeFill as fillRange } from "../range";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cellxgene",
|
|
||||||
"scripts": {
|
|
||||||
"postinstall": "npm install --prefix client && npm run build --prefix client && make copy-client-assets"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user