mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-25 13:58:12 +08:00
categorical UI improvements (#1649)
* return undefined for unknown fields * memoize fetch response * handle rename of user annotation * performance and cleanup * fix snapshots * fix comment * lint
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
|
||||
exports[`did launch page launched 1`] = `"<span style=\\"width: 185px; display: flex; overflow: hidden; justify-content: flex-start;\\"><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\\"><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;\\"><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><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-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><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;\\"><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>"`;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
exports[`annotations stacked bar graph renders 1`] = `
|
||||
Array [
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-flip-id=\\"1\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2132</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<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\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2132</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`annotations stacked bar graph renders 2`] = `
|
||||
Array [
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div data-flip-config=\\"{"translate":true,"scale":true,"opacity":true}\\" data-flip-id=\\"1\\" data-portal-key=\\"portal\\" class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<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\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Dataframe, IdentityInt32Index } from "../util/dataframe";
|
||||
import {
|
||||
Dataframe,
|
||||
IdentityInt32Index,
|
||||
dataframeMemo,
|
||||
} from "../util/dataframe";
|
||||
import {
|
||||
_getColumnDimensionNames,
|
||||
_getColumnSchema,
|
||||
@@ -9,6 +13,8 @@ import { indexEntireSchema } from "../util/stateManager/schemaHelpers";
|
||||
import { _whereCacheGet, _whereCacheMerge } from "./whereCache";
|
||||
import _shallowClone from "./clone";
|
||||
|
||||
const _dataframeCache = dataframeMemo(128);
|
||||
|
||||
export default class AnnoMatrix {
|
||||
/*
|
||||
Abstract base class for all AnnoMatrix objects. This class provides a proxy
|
||||
@@ -437,7 +443,9 @@ export default class AnnoMatrix {
|
||||
|
||||
/* everything we need is in the cache, so just cherry-pick requested columns */
|
||||
const requestedCacheKeys = this._resolveCachedQueries(field, queries);
|
||||
const response = this._cache[field].subset(null, requestedCacheKeys);
|
||||
const response = _dataframeCache(
|
||||
this._cache[field].subset(null, requestedCacheKeys)
|
||||
);
|
||||
this._gcUpdateStats(field, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import React from "react";
|
||||
import { Flipper, Flipped } from "react-flip-toolkit";
|
||||
|
||||
import * as globals from "../../../globals";
|
||||
import Value from "../value";
|
||||
|
||||
class Category extends React.PureComponent {
|
||||
renderCategoryItems(optTuples) {
|
||||
const {
|
||||
metadataField,
|
||||
isUserAnno,
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
colorTable,
|
||||
} = this.props;
|
||||
|
||||
return optTuples.map((tuple, i) => {
|
||||
return (
|
||||
<Flipped key={tuple[1]} flipId={tuple[1]}>
|
||||
{(flippedProps) => (
|
||||
<Value
|
||||
isUserAnno={isUserAnno}
|
||||
optTuples={optTuples}
|
||||
key={tuple[1]}
|
||||
metadataField={metadataField}
|
||||
categoryIndex={tuple[1]}
|
||||
i={i}
|
||||
flippedProps={flippedProps}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
)}
|
||||
</Flipped>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { metadataField, categorySummary, children, isExpanded } = this.props;
|
||||
const { isTruncated } = categorySummary;
|
||||
const optTuples = [...categorySummary.categoryValueIndices];
|
||||
const optTuplesAsKey = optTuples.map((t) => t[0]).join(""); // animation
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: globals.maxControlsWidth,
|
||||
}}
|
||||
data-testclass="category"
|
||||
data-testid={`category-${metadataField}`}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div style={{ marginLeft: 26 }}>
|
||||
<Flipper spring="veryGentle" flipKey={optTuplesAsKey}>
|
||||
{isExpanded ? this.renderCategoryItems(optTuples) : null}
|
||||
</Flipper>
|
||||
</div>
|
||||
<div>
|
||||
{isExpanded && isTruncated ? (
|
||||
<p style={{ paddingLeft: 15 }}>... truncated list ...</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Category;
|
||||
@@ -2,10 +2,11 @@ import React, { useRef, useEffect } from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
||||
import { AnchorButton, Button, Tooltip } from "@blueprintjs/core";
|
||||
import { Flipper, Flipped } from "react-flip-toolkit";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
|
||||
import CategoryFlipperLayout from "./categoryFlipperLayout";
|
||||
import Value from "../value";
|
||||
import AnnoMenu from "./annoMenuCategory";
|
||||
import AnnoDialogEditCategoryName from "./annoDialogEditCategoryName";
|
||||
import AnnoDialogAddLabel from "./annoDialogAddLabel";
|
||||
@@ -63,38 +64,16 @@ class Category extends React.PureComponent {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
static async fetchData(annoMatrix, metadataField, colors) {
|
||||
/*
|
||||
fetch our data and the color-by data if appropriate, and then build a summary
|
||||
of our category and a color table for the color-by annotation.
|
||||
*/
|
||||
const { schema } = annoMatrix;
|
||||
const { colorAccessor, colorMode } = colors;
|
||||
let colorDataPromise = Promise.resolve(null);
|
||||
if (colorAccessor) {
|
||||
const query = createColorQuery(colorMode, colorAccessor, schema);
|
||||
if (query) colorDataPromise = annoMatrix.fetch(...query);
|
||||
}
|
||||
const [categoryData, colorData] = await Promise.all([
|
||||
annoMatrix.fetch("obs", metadataField),
|
||||
colorDataPromise,
|
||||
]);
|
||||
createCategorySummaryFromDfCol = memoize(createCategorySummaryFromDfCol);
|
||||
|
||||
// our data
|
||||
const column = categoryData.icol(0);
|
||||
const colSchema = schema.annotations.obsByName[metadataField];
|
||||
const categorySummary = createCategorySummaryFromDfCol(column, colSchema);
|
||||
return [categoryData, categorySummary, colorData];
|
||||
}
|
||||
|
||||
getSelectionState = memoize((categorySummary) => {
|
||||
getSelectionState(categorySummary) {
|
||||
const { categoricalSelection, metadataField } = this.props;
|
||||
return Category.getSelectionState(
|
||||
categoricalSelection,
|
||||
metadataField,
|
||||
categorySummary
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
handleColorChange = () => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
@@ -133,7 +112,7 @@ class Category extends React.PureComponent {
|
||||
const { annoMatrix, metadataField, colors } = props.watchProps;
|
||||
const { crossfilter } = this.props;
|
||||
|
||||
const [categoryData, categorySummary, colorData] = await Category.fetchData(
|
||||
const [categoryData, categorySummary, colorData] = await this.fetchData(
|
||||
annoMatrix,
|
||||
metadataField,
|
||||
colors
|
||||
@@ -150,6 +129,33 @@ class Category extends React.PureComponent {
|
||||
};
|
||||
};
|
||||
|
||||
async fetchData(annoMatrix, metadataField, colors) {
|
||||
/*
|
||||
fetch our data and the color-by data if appropriate, and then build a summary
|
||||
of our category and a color table for the color-by annotation.
|
||||
*/
|
||||
const { schema } = annoMatrix;
|
||||
const { colorAccessor, colorMode } = colors;
|
||||
let colorDataPromise = Promise.resolve(null);
|
||||
if (colorAccessor) {
|
||||
const query = createColorQuery(colorMode, colorAccessor, schema);
|
||||
if (query) colorDataPromise = annoMatrix.fetch(...query);
|
||||
}
|
||||
const [categoryData, colorData] = await Promise.all([
|
||||
annoMatrix.fetch("obs", metadataField),
|
||||
colorDataPromise,
|
||||
]);
|
||||
|
||||
// our data
|
||||
const column = categoryData.icol(0);
|
||||
const colSchema = schema.annotations.obsByName[metadataField];
|
||||
const categorySummary = this.createCategorySummaryFromDfCol(
|
||||
column,
|
||||
colSchema
|
||||
);
|
||||
return [categoryData, categorySummary, colorData];
|
||||
}
|
||||
|
||||
updateColorTable(colorData) {
|
||||
// color table, which may be null
|
||||
const { schema, colors, metadataField } = this.props;
|
||||
@@ -228,7 +234,7 @@ class Category extends React.PureComponent {
|
||||
<ErrorLoading metadataField={metadataField} error={error} />
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled>
|
||||
<Async.Fulfilled persist>
|
||||
{(asyncProps) => {
|
||||
const {
|
||||
colorAccessor,
|
||||
@@ -239,15 +245,17 @@ class Category extends React.PureComponent {
|
||||
isColorAccessor,
|
||||
handleCategoryToggleAllClick,
|
||||
} = asyncProps;
|
||||
const isTruncated = !!categorySummary?.isTruncated;
|
||||
const selectionState = this.getSelectionState(categorySummary);
|
||||
return (
|
||||
<CategoryRender
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
isUserAnno={isUserAnno}
|
||||
isTruncated={!!categorySummary?.isTruncated}
|
||||
isTruncated={isTruncated}
|
||||
isExpanded={isExpanded}
|
||||
isColorAccessor={isColorAccessor}
|
||||
selectionState={this.getSelectionState(categorySummary)}
|
||||
selectionState={selectionState}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
@@ -498,30 +506,115 @@ const CategoryRender = React.memo(
|
||||
Otherwise, our normal multi-layout layout
|
||||
*/
|
||||
return (
|
||||
<CategoryFlipperLayout
|
||||
metadataField={metadataField}
|
||||
isExpanded={isExpanded}
|
||||
isUserAnno={isUserAnno}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
<div
|
||||
style={{
|
||||
maxWidth: globals.maxControlsWidth,
|
||||
}}
|
||||
data-testclass="category"
|
||||
data-testid={`category-${metadataField}`}
|
||||
>
|
||||
<CategoryHeader
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
isUserAnno={isUserAnno}
|
||||
isTruncated={isTruncated}
|
||||
isExpanded={isExpanded}
|
||||
isColorAccessor={isColorAccessor}
|
||||
selectionState={selectionState}
|
||||
onColorChangeClick={onColorChangeClick}
|
||||
onCategoryToggleAllClick={onCategoryToggleAllClick}
|
||||
onCategoryMenuClick={onCategoryMenuClick}
|
||||
onCategoryMenuKeyPress={onCategoryMenuKeyPress}
|
||||
/>
|
||||
</CategoryFlipperLayout>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
<CategoryHeader
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
isUserAnno={isUserAnno}
|
||||
isTruncated={isTruncated}
|
||||
isExpanded={isExpanded}
|
||||
isColorAccessor={isColorAccessor}
|
||||
selectionState={selectionState}
|
||||
onColorChangeClick={onColorChangeClick}
|
||||
onCategoryToggleAllClick={onCategoryToggleAllClick}
|
||||
onCategoryMenuClick={onCategoryMenuClick}
|
||||
onCategoryMenuKeyPress={onCategoryMenuKeyPress}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginLeft: 26 }}>
|
||||
{
|
||||
/* values*/
|
||||
isExpanded ? (
|
||||
<CategoryValueList
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
{isExpanded && isTruncated ? (
|
||||
<p style={{ paddingLeft: 15 }}>... truncated list ...</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CategoryValueList = React.memo(
|
||||
({
|
||||
isUserAnno,
|
||||
metadataField,
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
colorTable,
|
||||
}) => {
|
||||
const tuples = [...categorySummary.categoryValueIndices];
|
||||
|
||||
/*
|
||||
Render the value list. If this is a user annotation, we use a flipper
|
||||
animation, if read-only, we don't bother and save a few bits of perf.
|
||||
*/
|
||||
if (!isUserAnno) {
|
||||
return (
|
||||
<>
|
||||
{tuples.map(([value, index]) => (
|
||||
<Value
|
||||
key={value}
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryIndex={index}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* User annotation */
|
||||
const flipKey = tuples.map((t) => t[0]).join("");
|
||||
return (
|
||||
<Flipper flipKey={flipKey}>
|
||||
{tuples.map(([value, index]) => (
|
||||
<Flipped key={value} flipId={value}>
|
||||
<Value
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryIndex={index}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
</Flipped>
|
||||
))}
|
||||
</Flipper>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -29,24 +29,30 @@ const CHART_WIDTH = 100;
|
||||
|
||||
/* this is defined outside of the class so we can use it in connect() */
|
||||
function _currentLabelAsString(ownProps) {
|
||||
const { categorySummary, categoryIndex } = ownProps;
|
||||
const { label } = ownProps;
|
||||
// when called as a function, the String() constructor performs type conversion,
|
||||
// and returns a primitive string.
|
||||
return String(categorySummary.categoryValues[categoryIndex]);
|
||||
return String(label);
|
||||
}
|
||||
|
||||
@connect((state, ownProps) => {
|
||||
const { pointDilation, categoricalSelection } = state;
|
||||
const { metadataField } = ownProps;
|
||||
const { metadataField, categorySummary, categoryIndex } = ownProps;
|
||||
const isDilated =
|
||||
pointDilation.metadataField === metadataField &&
|
||||
pointDilation.categoryField === _currentLabelAsString(ownProps);
|
||||
|
||||
const category = categoricalSelection[metadataField];
|
||||
const label = categorySummary.categoryValues[categoryIndex];
|
||||
const isSelected = category.get(label) ?? true;
|
||||
|
||||
return {
|
||||
categoricalSelection,
|
||||
annotations: state.annotations,
|
||||
schema: state.annoMatrix?.schema,
|
||||
ontology: state.ontology,
|
||||
isDilated,
|
||||
isSelected,
|
||||
label,
|
||||
};
|
||||
})
|
||||
class CategoryValue extends React.Component {
|
||||
@@ -58,14 +64,8 @@ class CategoryValue extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
categoricalSelection,
|
||||
metadataField,
|
||||
categoryIndex,
|
||||
categorySummary,
|
||||
} = this.props;
|
||||
const { metadataField, categoryIndex, categorySummary } = this.props;
|
||||
if (
|
||||
prevProps.categoricalSelection !== categoricalSelection ||
|
||||
prevProps.metadataField !== metadataField ||
|
||||
prevProps.categoryIndex !== categoryIndex ||
|
||||
prevProps.categorySummary !== categorySummary
|
||||
@@ -84,28 +84,19 @@ class CategoryValue extends React.Component {
|
||||
return colorAccessor && !isColorBy && !annotations.isEditingLabelName;
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
const { categoryIndex, categorySummary } = this.props;
|
||||
const label = categorySummary.categoryValues[categoryIndex];
|
||||
return label;
|
||||
}
|
||||
|
||||
handleDeleteValue = () => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const label = this.getLabel();
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
dispatch(actions.annotationDeleteLabelFromCategory(metadataField, label));
|
||||
};
|
||||
|
||||
handleAddCurrentSelectionToThisLabel = () => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const label = this.getLabel();
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
dispatch(actions.annotationLabelCurrentSelection(metadataField, label));
|
||||
};
|
||||
|
||||
handleEditValue = (e) => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
const { editedLabelText } = this.state;
|
||||
const label = this.getLabel();
|
||||
this.cancelEditMode();
|
||||
dispatch(
|
||||
actions.annotationRenameLabelInCategory(
|
||||
@@ -118,8 +109,7 @@ class CategoryValue extends React.Component {
|
||||
};
|
||||
|
||||
handleCreateArbitraryLabel = (txt) => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const label = this.getLabel();
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
this.cancelEditMode();
|
||||
dispatch(
|
||||
actions.annotationRenameLabelInCategory(metadataField, label, txt)
|
||||
@@ -137,8 +127,7 @@ class CategoryValue extends React.Component {
|
||||
};
|
||||
|
||||
activateEditLabelMode = () => {
|
||||
const { dispatch, metadataField, categoryIndex } = this.props;
|
||||
const label = this.getLabel();
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
dispatch({
|
||||
type: "annotation: activate edit label mode",
|
||||
metadataField,
|
||||
@@ -148,8 +137,7 @@ class CategoryValue extends React.Component {
|
||||
};
|
||||
|
||||
cancelEditMode = () => {
|
||||
const { dispatch, metadataField, categoryIndex } = this.props;
|
||||
const label = this.getLabel();
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
this.setState({
|
||||
editedLabelText: this.currentLabelAsString(),
|
||||
});
|
||||
@@ -191,24 +179,17 @@ class CategoryValue extends React.Component {
|
||||
If and only if true, update the component
|
||||
*/
|
||||
const { props, state } = this;
|
||||
const {
|
||||
metadataField,
|
||||
categoryIndex,
|
||||
categoricalSelection,
|
||||
categorySummary,
|
||||
} = props;
|
||||
const { categoryIndex, categorySummary, isSelected } = props;
|
||||
const {
|
||||
categoryIndex: newCategoryIndex,
|
||||
categoricalSelection: newCategoricalSelection,
|
||||
categorySummary: newCategorySummary,
|
||||
isSelected: newIsSelected,
|
||||
} = nextProps;
|
||||
|
||||
const label = categorySummary.categoryValues[categoryIndex];
|
||||
const newLabel = newCategorySummary.categoryValues[newCategoryIndex];
|
||||
const labelChanged = label !== newLabel;
|
||||
const valueSelectionChange =
|
||||
categoricalSelection[metadataField].get(label) !==
|
||||
newCategoricalSelection[metadataField].get(newLabel);
|
||||
const valueSelectionChange = isSelected !== newIsSelected;
|
||||
|
||||
const colorAccessorChange = props.colorAccessor !== nextProps.colorAccessor;
|
||||
const annotationsChange = props.annotations !== nextProps.annotations;
|
||||
@@ -250,8 +231,7 @@ class CategoryValue extends React.Component {
|
||||
};
|
||||
|
||||
handleMouseEnter = () => {
|
||||
const { dispatch, metadataField, categoryIndex } = this.props;
|
||||
const label = this.getLabel();
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover start",
|
||||
metadataField,
|
||||
@@ -261,8 +241,7 @@ class CategoryValue extends React.Component {
|
||||
};
|
||||
|
||||
handleMouseExit = () => {
|
||||
const { dispatch, metadataField, categoryIndex } = this.props;
|
||||
const label = this.getLabel();
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover end",
|
||||
metadataField,
|
||||
@@ -393,21 +372,21 @@ class CategoryValue extends React.Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
renderMiniStackedBar = (categoryValue) => {
|
||||
renderMiniStackedBar = () => {
|
||||
const {
|
||||
categoricalSelection,
|
||||
colorAccessor,
|
||||
metadataField,
|
||||
categoryData,
|
||||
colorData,
|
||||
colorTable,
|
||||
schema,
|
||||
label,
|
||||
} = this.props;
|
||||
const isColorBy = metadataField === colorAccessor;
|
||||
|
||||
if (
|
||||
!this.shouldRenderStackedBarOrHistogram ||
|
||||
!categoricalSelection[colorAccessor] ||
|
||||
!AnnotationsHelpers.isCategoricalAnnotation(schema, colorAccessor) ||
|
||||
isColorBy
|
||||
) {
|
||||
return null;
|
||||
@@ -419,7 +398,7 @@ class CategoryValue extends React.Component {
|
||||
categoryData,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
categoryValue,
|
||||
label,
|
||||
colorTable,
|
||||
schema,
|
||||
CHART_WIDTH
|
||||
@@ -446,20 +425,21 @@ class CategoryValue extends React.Component {
|
||||
);
|
||||
};
|
||||
|
||||
renderMiniHistogram = (categoryValue) => {
|
||||
renderMiniHistogram = () => {
|
||||
const {
|
||||
categoricalSelection,
|
||||
colorAccessor,
|
||||
metadataField,
|
||||
colorData,
|
||||
categoryData,
|
||||
colorTable,
|
||||
schema,
|
||||
label,
|
||||
} = this.props;
|
||||
const colorScale = colorTable?.scale;
|
||||
|
||||
if (
|
||||
!this.shouldRenderStackedBarOrHistogram ||
|
||||
categoricalSelection[colorAccessor]
|
||||
!AnnotationsHelpers.isContinuousAnnotation(schema, colorAccessor)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -470,7 +450,7 @@ class CategoryValue extends React.Component {
|
||||
categoryData,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
categoryValue,
|
||||
label,
|
||||
CHART_WIDTH,
|
||||
VALUE_HEIGHT
|
||||
) ?? {};
|
||||
@@ -486,7 +466,7 @@ class CategoryValue extends React.Component {
|
||||
}}
|
||||
/* eslint-enable react/jsx-props-no-spreading -- enable */
|
||||
obsOrVarContinuousFieldDisplayName={colorAccessor}
|
||||
domainLabel={categoryValue}
|
||||
domainLabel={label}
|
||||
height={VALUE_HEIGHT}
|
||||
width={CHART_WIDTH}
|
||||
/>
|
||||
@@ -495,32 +475,24 @@ class CategoryValue extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
categoricalSelection,
|
||||
metadataField,
|
||||
categoryIndex,
|
||||
colorAccessor,
|
||||
colorTable,
|
||||
i,
|
||||
isUserAnno,
|
||||
annotations,
|
||||
ontology,
|
||||
// flippedProps is potentially brittle, their docs want {...flippedProps} on our div,
|
||||
// our lint doesn't like jsx spread, we are version pinned to prevent api change on their part
|
||||
flippedProps,
|
||||
isDilated,
|
||||
isSelected,
|
||||
categorySummary,
|
||||
label,
|
||||
} = this.props;
|
||||
const colorScale = colorTable?.scale;
|
||||
const ontologyEnabled = ontology?.enabled ?? false;
|
||||
|
||||
const { editedLabelText } = this.state;
|
||||
|
||||
if (!categoricalSelection) return null;
|
||||
|
||||
const category = categoricalSelection[metadataField];
|
||||
const selected = category.get(this.getLabel()) ?? true;
|
||||
const count = categorySummary.categoryValueCounts[categoryIndex];
|
||||
const value = categorySummary.categoryValues[categoryIndex];
|
||||
const displayString = this.currentLabelAsString();
|
||||
|
||||
/* this is the color scale, so add swatches below */
|
||||
@@ -559,10 +531,6 @@ class CategoryValue extends React.Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
data-flip-config={flippedProps["data-flip-config"]}
|
||||
data-flip-id={flippedProps["data-flip-id"]}
|
||||
data-portal-key={flippedProps["data-portal-key"]}
|
||||
className={
|
||||
/* This code is to change the styles on centroid label hover is causing over-rendering */
|
||||
`${styles.value}${isDilated ? ` ${styles.hover}` : ""}`
|
||||
@@ -597,10 +565,10 @@ class CategoryValue extends React.Component {
|
||||
>
|
||||
<input
|
||||
id={valueToggleLabel}
|
||||
onChange={selected ? this.toggleOff : this.toggleOn}
|
||||
onChange={isSelected ? this.toggleOff : this.toggleOn}
|
||||
data-testclass="categorical-value-select"
|
||||
data-testid={`categorical-value-select-${metadataField}-${displayString}`}
|
||||
checked={selected}
|
||||
checked={isSelected}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
@@ -673,8 +641,8 @@ class CategoryValue extends React.Component {
|
||||
) : null}
|
||||
</div>
|
||||
<span style={{ flexShrink: 0 }}>
|
||||
{this.renderMiniStackedBar(value)}
|
||||
{this.renderMiniHistogram(value)}
|
||||
{this.renderMiniStackedBar()}
|
||||
{this.renderMiniHistogram()}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -704,7 +672,7 @@ class CategoryValue extends React.Component {
|
||||
height: VALUE_HEIGHT,
|
||||
backgroundColor:
|
||||
isColorBy && categoryValueIndices
|
||||
? colorScale(categoryValueIndices.get(value))
|
||||
? colorScale(categoryValueIndices.get(label))
|
||||
: "inherit",
|
||||
}}
|
||||
/>
|
||||
@@ -745,7 +713,7 @@ class CategoryValue extends React.Component {
|
||||
disabled={this.isAddCurrentSelectionDisabled(
|
||||
crossfilter,
|
||||
metadataField,
|
||||
value
|
||||
label
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -33,6 +33,18 @@ const ColorsReducer = (
|
||||
return state;
|
||||
}
|
||||
|
||||
case "annotation: category edited": {
|
||||
const { colorAccessor } = state;
|
||||
if (action.metadataField !== colorAccessor) {
|
||||
return state;
|
||||
}
|
||||
/* else update colorAccessor */
|
||||
return {
|
||||
...state,
|
||||
colorAccessor: action.newCategoryText,
|
||||
};
|
||||
}
|
||||
|
||||
case "annotation: delete category": {
|
||||
const { colorAccessor } = state;
|
||||
if (action.metadataField !== colorAccessor) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
|
||||
DataframeCache - to improve memoization, we often want to have the same
|
||||
Dataframe object returned when the contents of the dataframe are identical.
|
||||
The core dataframe class does not support this, but it does attempt to maintain
|
||||
strict eq and immutability of columns indices.
|
||||
|
||||
This is a function that will maintain a least-recently created cache of Dataframe
|
||||
objects.
|
||||
*/
|
||||
|
||||
import { memoize } from "./util";
|
||||
|
||||
function hashDataframe(df) {
|
||||
if (df.isEmpty()) return "";
|
||||
return df.__columnsAccessor.map((c) => c.__id).join(",");
|
||||
}
|
||||
|
||||
function noop(df) {
|
||||
return df;
|
||||
}
|
||||
|
||||
const dataframeMemo = (capacity = 100) =>
|
||||
memoize(noop, hashDataframe, capacity);
|
||||
|
||||
export default dataframeMemo;
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isArrayOrTypedArray,
|
||||
callOnceLazy,
|
||||
memoize,
|
||||
__getMemoId,
|
||||
} from "./util";
|
||||
import {
|
||||
summarizeContinuous,
|
||||
@@ -72,17 +73,6 @@ Dataframe
|
||||
**/
|
||||
|
||||
class Dataframe {
|
||||
/**
|
||||
memoization helpers.
|
||||
**/
|
||||
static __DataframeId__ = 0;
|
||||
|
||||
static __getId() {
|
||||
const id = Dataframe.__DataframeId__;
|
||||
Dataframe.__DataframeId__ += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
Constructors & factories
|
||||
**/
|
||||
@@ -126,7 +116,7 @@ class Dataframe {
|
||||
this.length = nRows; // convenience accessor for row dimension
|
||||
this.rowIndex = rowIndex;
|
||||
this.colIndex = colIndex;
|
||||
this.__id = Dataframe.__getId();
|
||||
this.__id = __getMemoId();
|
||||
|
||||
this.__compile(__columnsAccessor);
|
||||
Object.freeze(this);
|
||||
@@ -202,7 +192,7 @@ class Dataframe {
|
||||
|
||||
*/
|
||||
const { length } = column;
|
||||
const __id = Dataframe.__getId();
|
||||
const __id = __getMemoId();
|
||||
|
||||
/* get value by row label */
|
||||
const get = function get(rlabel) {
|
||||
|
||||
@@ -5,3 +5,4 @@ export {
|
||||
KeyIndex,
|
||||
isLabelIndex,
|
||||
} from "./labelIndex";
|
||||
export { default as dataframeMemo } from "./cache";
|
||||
|
||||
@@ -5,6 +5,7 @@ for how this is used.
|
||||
**/
|
||||
|
||||
import { rangeFill as fillRange } from "../range";
|
||||
import { __getMemoId } from "./util";
|
||||
|
||||
/*
|
||||
Private utility functions
|
||||
@@ -32,6 +33,10 @@ class IdentityInt32Index {
|
||||
this.maxOffset = maxOffset;
|
||||
}
|
||||
|
||||
get __id() {
|
||||
return `IdentityInt32Index_${this.maxOffset}`;
|
||||
}
|
||||
|
||||
labels() {
|
||||
// memoize
|
||||
const k = fillRange(new Int32Array(this.maxOffset));
|
||||
@@ -162,6 +167,7 @@ class DenseInt32Index {
|
||||
this.minLabel = minLabel;
|
||||
this.rindex = labels;
|
||||
this.index = index;
|
||||
this.__id = __getMemoId();
|
||||
this.__compile();
|
||||
}
|
||||
|
||||
@@ -288,6 +294,7 @@ class KeyIndex {
|
||||
|
||||
this.index = index;
|
||||
this.rindex = rindex;
|
||||
this.__id = __getMemoId();
|
||||
this.__compile();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,3 +52,13 @@ export function memoize(fn, hashFn, maxResultsCached = -1) {
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
memoization helpers - just a global counter.
|
||||
**/
|
||||
let __DataframeMemoId__ = 0;
|
||||
export function __getMemoId() {
|
||||
const id = __DataframeMemoId__;
|
||||
__DataframeMemoId__ += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,24 @@ categorical annotations to be writable.
|
||||
*/
|
||||
|
||||
export function isCategoricalAnnotation(schema, name) {
|
||||
/* we treat any string, categorical or boolean as a categorical */
|
||||
const { type } = schema.annotations.obsByName[name];
|
||||
/*
|
||||
we treat any string, categorical or boolean as a categorical.
|
||||
Return true/false/undefined (for unkonwn fields)
|
||||
*/
|
||||
const colSchema = schema.annotations.obsByName[name];
|
||||
if (colSchema === undefined) return undefined;
|
||||
const { type } = colSchema;
|
||||
return type === "string" || type === "boolean" || type === "categorical";
|
||||
}
|
||||
|
||||
export function isContinuousAnnotation(schema, name) {
|
||||
return !isCategoricalAnnotation(schema, name);
|
||||
/*
|
||||
Return true/false/undefined
|
||||
*/
|
||||
const colSchema = schema.annotations.obsByName[name];
|
||||
if (colSchema === undefined) return undefined;
|
||||
const { type } = colSchema;
|
||||
return !(type === "string" || type === "boolean" || type === "categorical");
|
||||
}
|
||||
|
||||
function _isUserAnnotation(schema, name) {
|
||||
|
||||
Reference in New Issue
Block a user