@czi-sds/data-viz 2.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shared/ProteinStructureViewer.cjs.js +44 -18
- package/dist/shared/ProteinStructureViewer.esm.js +44 -18
- package/dist/shared/StackedBarChart.cjs.js +117 -58
- package/dist/shared/StackedBarChart.esm.js +118 -59
- package/dist/shared/index2.cjs.d.ts +11 -0
- package/dist/shared/index2.esm.d.ts +11 -0
- package/dist/shared/index4.cjs.d.ts +7 -1
- package/dist/shared/index4.esm.d.ts +7 -1
- package/package.json +3 -3
|
@@ -292,17 +292,35 @@ const MIN_WIDTH = 200;
|
|
|
292
292
|
*/
|
|
293
293
|
const VIEWER_CONTAINER_NAME = "sds-structure-viewer";
|
|
294
294
|
/**
|
|
295
|
-
* Height reserved for the sequence panel along the bottom
|
|
296
|
-
* width above which it grows. Measured against the
|
|
297
|
-
* the page, so a viewer embedded in a wider page
|
|
295
|
+
* Height reserved for the sequence panel along the bottom when the consumer
|
|
296
|
+
* sets none, and the viewer width above which it grows. Measured against the
|
|
297
|
+
* container above rather than the page, so a viewer embedded in a wider page
|
|
298
|
+
* sizes to the room it has.
|
|
298
299
|
*
|
|
299
|
-
* The legend sits directly on top of the panel and offsets itself by
|
|
300
|
-
* same
|
|
301
|
-
* Copies that drifted would leave the legend floating off the
|
|
300
|
+
* The legend sits directly on top of the panel and offsets itself by the same
|
|
301
|
+
* heights at the same breakpoint, so it reads them from here rather than
|
|
302
|
+
* restating them. Copies that drifted would leave the legend floating off the
|
|
303
|
+
* panel.
|
|
302
304
|
*/
|
|
303
305
|
const SEQUENCE_HEIGHT = "max(104px, 30%)";
|
|
304
306
|
const SEQUENCE_HEIGHT_WIDE = "max(134px, 32%)";
|
|
305
307
|
/**
|
|
308
|
+
* The sequence panel's height below `WIDE_VIEWER` and at or above it. A height
|
|
309
|
+
* the consumer sets holds at every width, a number is read as pixels, and an
|
|
310
|
+
* empty string leaves the defaults in place, as it would in a React style.
|
|
311
|
+
*/
|
|
312
|
+
function sequenceHeights(height) {
|
|
313
|
+
if (height === void 0 || height === "") return {
|
|
314
|
+
base: SEQUENCE_HEIGHT,
|
|
315
|
+
wide: SEQUENCE_HEIGHT_WIDE
|
|
316
|
+
};
|
|
317
|
+
const css = typeof height === "number" ? `${height}px` : height;
|
|
318
|
+
return {
|
|
319
|
+
base: css,
|
|
320
|
+
wide: css
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
306
324
|
* The element Mol* mounts into.
|
|
307
325
|
*
|
|
308
326
|
* This has to be its own node rather than the root: Mol* takes ownership of its
|
|
@@ -379,17 +397,20 @@ const ViewerRoot = (0, _emotion_styled.default)("div")`
|
|
|
379
397
|
/*
|
|
380
398
|
* Mol* puts the sequence panel in its "top" region; move it to the bottom and
|
|
381
399
|
* shrink the main viewport region to match, so the structure sits above the
|
|
382
|
-
* sequence rather than behind it. The panel grows on wider viewers
|
|
383
|
-
* viewport region's offset follows it.
|
|
400
|
+
* sequence rather than behind it. The panel grows on wider viewers unless the
|
|
401
|
+
* consumer has set its height, and the viewport region's offset follows it.
|
|
384
402
|
*/
|
|
385
403
|
${(props) => {
|
|
386
|
-
const { showSequenceViewer } = props;
|
|
404
|
+
const { sequenceViewerHeight, showSequenceViewer } = props;
|
|
405
|
+
const height = sequenceHeights(sequenceViewerHeight);
|
|
406
|
+
const mainBottom = showSequenceViewer ? height.base : "0";
|
|
407
|
+
const mainBottomWide = showSequenceViewer ? height.wide : "0";
|
|
387
408
|
return `
|
|
388
409
|
.msp-plugin .msp-layout-region.msp-layout-top {
|
|
389
410
|
display: ${showSequenceViewer ? "block" : "none"} !important;
|
|
390
411
|
position: absolute !important;
|
|
391
412
|
inset: auto 0 0 0 !important;
|
|
392
|
-
height: ${
|
|
413
|
+
height: ${height.base} !important;
|
|
393
414
|
}
|
|
394
415
|
|
|
395
416
|
.msp-plugin .msp-layout-region.msp-layout-main {
|
|
@@ -397,16 +418,16 @@ const ViewerRoot = (0, _emotion_styled.default)("div")`
|
|
|
397
418
|
top: 0 !important;
|
|
398
419
|
left: 0 !important;
|
|
399
420
|
right: 0 !important;
|
|
400
|
-
bottom: ${
|
|
421
|
+
bottom: ${mainBottom} !important;
|
|
401
422
|
}
|
|
402
423
|
|
|
403
424
|
@container ${VIEWER_CONTAINER_NAME} (min-width: 880px) {
|
|
404
425
|
.msp-plugin .msp-layout-region.msp-layout-top {
|
|
405
|
-
height: ${
|
|
426
|
+
height: ${height.wide} !important;
|
|
406
427
|
}
|
|
407
428
|
|
|
408
429
|
.msp-plugin .msp-layout-region.msp-layout-main {
|
|
409
|
-
bottom: ${
|
|
430
|
+
bottom: ${mainBottomWide} !important;
|
|
410
431
|
}
|
|
411
432
|
}
|
|
412
433
|
`;
|
|
@@ -448,12 +469,13 @@ const LegendOverlay = (0, _emotion_styled.default)("div")`
|
|
|
448
469
|
|
|
449
470
|
${(props) => {
|
|
450
471
|
const spaces = (0, _czi_sds_components.getSpaces)(props);
|
|
451
|
-
const { showSequenceViewer } = props;
|
|
472
|
+
const { sequenceViewerHeight, showSequenceViewer } = props;
|
|
473
|
+
const height = sequenceHeights(sequenceViewerHeight);
|
|
452
474
|
return `
|
|
453
475
|
right: ${spaces === null || spaces === void 0 ? void 0 : spaces.m}px;
|
|
454
476
|
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.xs}px;
|
|
455
477
|
padding-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.m}px;
|
|
456
|
-
bottom: ${showSequenceViewer ?
|
|
478
|
+
bottom: ${showSequenceViewer ? height.base : "0"};
|
|
457
479
|
|
|
458
480
|
/*
|
|
459
481
|
* Track the sequence panel as it grows on wider viewers. The panel is
|
|
@@ -461,7 +483,7 @@ const LegendOverlay = (0, _emotion_styled.default)("div")`
|
|
|
461
483
|
* would part the legend from it on a viewer narrower than its page.
|
|
462
484
|
*/
|
|
463
485
|
@container ${VIEWER_CONTAINER_NAME} (min-width: 880px) {
|
|
464
|
-
bottom: ${showSequenceViewer ?
|
|
486
|
+
bottom: ${showSequenceViewer ? height.wide : "0"};
|
|
465
487
|
}
|
|
466
488
|
|
|
467
489
|
/* Spacer balancing the axes widget pinned in the opposite corner. */
|
|
@@ -637,7 +659,7 @@ function readoutSlot(label, value, residueCount) {
|
|
|
637
659
|
* residue takes priority so the user can inspect others without losing their
|
|
638
660
|
* selection.
|
|
639
661
|
*/
|
|
640
|
-
function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChains, hoveredResidue = null, onChainHover, onChainSelect, onChainToggle, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedChains, selectedResidue = null, showSequenceViewer, stats, valueLabel }) {
|
|
662
|
+
function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChains, hoveredResidue = null, onChainHover, onChainSelect, onChainToggle, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedChains, selectedResidue = null, sequenceViewerHeight, showSequenceViewer, stats, valueLabel }) {
|
|
641
663
|
var _activeResidue$residu, _stats$, _stats$2, _stats$3;
|
|
642
664
|
const activeResidue = hoveredResidue !== null && hoveredResidue !== void 0 ? hoveredResidue : selectedResidue;
|
|
643
665
|
const showResidue = activeResidue !== null;
|
|
@@ -652,6 +674,7 @@ function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChai
|
|
|
652
674
|
showResidue ? readoutSlot("pLDDT", activeResidue.plddt, residueCount) : (_stats$3 = stats[2]) !== null && _stats$3 !== void 0 ? _stats$3 : null
|
|
653
675
|
];
|
|
654
676
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LegendOverlay, {
|
|
677
|
+
sequenceViewerHeight,
|
|
655
678
|
showSequenceViewer,
|
|
656
679
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsGrid, { children: Array.from({ length: SLOT_COUNT }, (_, i) => {
|
|
657
680
|
var _slots$i;
|
|
@@ -3638,6 +3661,7 @@ const _excluded = [
|
|
|
3638
3661
|
"sceneMode",
|
|
3639
3662
|
"selection",
|
|
3640
3663
|
"sequenceViewerBackgroundColor",
|
|
3664
|
+
"sequenceViewerHeight",
|
|
3641
3665
|
"showAxes",
|
|
3642
3666
|
"showChainLegend",
|
|
3643
3667
|
"showLegend",
|
|
@@ -3711,7 +3735,7 @@ function resolveScaleProps(colorBy, overlay) {
|
|
|
3711
3735
|
*/
|
|
3712
3736
|
const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
|
|
3713
3737
|
var _selection$chains;
|
|
3714
|
-
const { backgroundColor, chainColors: chainColorOverrides, colorBy: colorByProp, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, highlights, initialCamera, onCameraChange, onChainVisibilityChange, onChainsChange, molstarSpec, onDispose, onError, onReady, onResidueClick, onResidueHover, onSelectionChange, onStructureLoad, orientation, projection, representation = "cartoon", structure, plddt, residueOverlay, sceneMode = "managed", selection: selectionProp, sequenceViewerBackgroundColor, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
|
|
3738
|
+
const { backgroundColor, chainColors: chainColorOverrides, colorBy: colorByProp, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, highlights, initialCamera, onCameraChange, onChainVisibilityChange, onChainsChange, molstarSpec, onDispose, onError, onReady, onResidueClick, onResidueHover, onSelectionChange, onStructureLoad, orientation, projection, representation = "cartoon", structure, plddt, residueOverlay, sceneMode = "managed", selection: selectionProp, sequenceViewerBackgroundColor, sequenceViewerHeight, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
|
|
3715
3739
|
const theme = (0, _mui_material_styles.useTheme)();
|
|
3716
3740
|
const mode = (0, _czi_sds_components.getMode)({ theme });
|
|
3717
3741
|
const semanticColors = (0, _czi_sds_components.getSemanticColors)({ theme });
|
|
@@ -3892,6 +3916,7 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
|
|
|
3892
3916
|
const plddtColoringActive = colorBy === "plddt";
|
|
3893
3917
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ViewerRoot, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({
|
|
3894
3918
|
ref,
|
|
3919
|
+
sequenceViewerHeight,
|
|
3895
3920
|
showSequenceViewer
|
|
3896
3921
|
}, rest), {}, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginMount, { ref: pluginMountRef }), showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StructureLegend, require_objectSpread2._objectSpread2({
|
|
3897
3922
|
chainBandColors: plddtColoringActive ? require_renderStructureImage.PLDDT_BAND_COLORS : void 0,
|
|
@@ -3904,6 +3929,7 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
|
|
|
3904
3929
|
onChainToggle: toggleChain,
|
|
3905
3930
|
selectedChains,
|
|
3906
3931
|
selectedResidue: selectedReadout,
|
|
3932
|
+
sequenceViewerHeight,
|
|
3907
3933
|
showSequenceViewer,
|
|
3908
3934
|
stats: stats !== null && stats !== void 0 ? stats : []
|
|
3909
3935
|
}, scaleProps))] }));
|
|
@@ -290,17 +290,35 @@ const MIN_WIDTH = 200;
|
|
|
290
290
|
*/
|
|
291
291
|
const VIEWER_CONTAINER_NAME = "sds-structure-viewer";
|
|
292
292
|
/**
|
|
293
|
-
* Height reserved for the sequence panel along the bottom
|
|
294
|
-
* width above which it grows. Measured against the
|
|
295
|
-
* the page, so a viewer embedded in a wider page
|
|
293
|
+
* Height reserved for the sequence panel along the bottom when the consumer
|
|
294
|
+
* sets none, and the viewer width above which it grows. Measured against the
|
|
295
|
+
* container above rather than the page, so a viewer embedded in a wider page
|
|
296
|
+
* sizes to the room it has.
|
|
296
297
|
*
|
|
297
|
-
* The legend sits directly on top of the panel and offsets itself by
|
|
298
|
-
* same
|
|
299
|
-
* Copies that drifted would leave the legend floating off the
|
|
298
|
+
* The legend sits directly on top of the panel and offsets itself by the same
|
|
299
|
+
* heights at the same breakpoint, so it reads them from here rather than
|
|
300
|
+
* restating them. Copies that drifted would leave the legend floating off the
|
|
301
|
+
* panel.
|
|
300
302
|
*/
|
|
301
303
|
const SEQUENCE_HEIGHT = "max(104px, 30%)";
|
|
302
304
|
const SEQUENCE_HEIGHT_WIDE = "max(134px, 32%)";
|
|
303
305
|
/**
|
|
306
|
+
* The sequence panel's height below `WIDE_VIEWER` and at or above it. A height
|
|
307
|
+
* the consumer sets holds at every width, a number is read as pixels, and an
|
|
308
|
+
* empty string leaves the defaults in place, as it would in a React style.
|
|
309
|
+
*/
|
|
310
|
+
function sequenceHeights(height) {
|
|
311
|
+
if (height === void 0 || height === "") return {
|
|
312
|
+
base: SEQUENCE_HEIGHT,
|
|
313
|
+
wide: SEQUENCE_HEIGHT_WIDE
|
|
314
|
+
};
|
|
315
|
+
const css = typeof height === "number" ? `${height}px` : height;
|
|
316
|
+
return {
|
|
317
|
+
base: css,
|
|
318
|
+
wide: css
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
304
322
|
* The element Mol* mounts into.
|
|
305
323
|
*
|
|
306
324
|
* This has to be its own node rather than the root: Mol* takes ownership of its
|
|
@@ -377,17 +395,20 @@ const ViewerRoot = styled("div")`
|
|
|
377
395
|
/*
|
|
378
396
|
* Mol* puts the sequence panel in its "top" region; move it to the bottom and
|
|
379
397
|
* shrink the main viewport region to match, so the structure sits above the
|
|
380
|
-
* sequence rather than behind it. The panel grows on wider viewers
|
|
381
|
-
* viewport region's offset follows it.
|
|
398
|
+
* sequence rather than behind it. The panel grows on wider viewers unless the
|
|
399
|
+
* consumer has set its height, and the viewport region's offset follows it.
|
|
382
400
|
*/
|
|
383
401
|
${(props) => {
|
|
384
|
-
const { showSequenceViewer } = props;
|
|
402
|
+
const { sequenceViewerHeight, showSequenceViewer } = props;
|
|
403
|
+
const height = sequenceHeights(sequenceViewerHeight);
|
|
404
|
+
const mainBottom = showSequenceViewer ? height.base : "0";
|
|
405
|
+
const mainBottomWide = showSequenceViewer ? height.wide : "0";
|
|
385
406
|
return `
|
|
386
407
|
.msp-plugin .msp-layout-region.msp-layout-top {
|
|
387
408
|
display: ${showSequenceViewer ? "block" : "none"} !important;
|
|
388
409
|
position: absolute !important;
|
|
389
410
|
inset: auto 0 0 0 !important;
|
|
390
|
-
height: ${
|
|
411
|
+
height: ${height.base} !important;
|
|
391
412
|
}
|
|
392
413
|
|
|
393
414
|
.msp-plugin .msp-layout-region.msp-layout-main {
|
|
@@ -395,16 +416,16 @@ const ViewerRoot = styled("div")`
|
|
|
395
416
|
top: 0 !important;
|
|
396
417
|
left: 0 !important;
|
|
397
418
|
right: 0 !important;
|
|
398
|
-
bottom: ${
|
|
419
|
+
bottom: ${mainBottom} !important;
|
|
399
420
|
}
|
|
400
421
|
|
|
401
422
|
@container ${VIEWER_CONTAINER_NAME} (min-width: 880px) {
|
|
402
423
|
.msp-plugin .msp-layout-region.msp-layout-top {
|
|
403
|
-
height: ${
|
|
424
|
+
height: ${height.wide} !important;
|
|
404
425
|
}
|
|
405
426
|
|
|
406
427
|
.msp-plugin .msp-layout-region.msp-layout-main {
|
|
407
|
-
bottom: ${
|
|
428
|
+
bottom: ${mainBottomWide} !important;
|
|
408
429
|
}
|
|
409
430
|
}
|
|
410
431
|
`;
|
|
@@ -446,12 +467,13 @@ const LegendOverlay = styled("div")`
|
|
|
446
467
|
|
|
447
468
|
${(props) => {
|
|
448
469
|
const spaces = getSpaces(props);
|
|
449
|
-
const { showSequenceViewer } = props;
|
|
470
|
+
const { sequenceViewerHeight, showSequenceViewer } = props;
|
|
471
|
+
const height = sequenceHeights(sequenceViewerHeight);
|
|
450
472
|
return `
|
|
451
473
|
right: ${spaces === null || spaces === void 0 ? void 0 : spaces.m}px;
|
|
452
474
|
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.xs}px;
|
|
453
475
|
padding-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.m}px;
|
|
454
|
-
bottom: ${showSequenceViewer ?
|
|
476
|
+
bottom: ${showSequenceViewer ? height.base : "0"};
|
|
455
477
|
|
|
456
478
|
/*
|
|
457
479
|
* Track the sequence panel as it grows on wider viewers. The panel is
|
|
@@ -459,7 +481,7 @@ const LegendOverlay = styled("div")`
|
|
|
459
481
|
* would part the legend from it on a viewer narrower than its page.
|
|
460
482
|
*/
|
|
461
483
|
@container ${VIEWER_CONTAINER_NAME} (min-width: 880px) {
|
|
462
|
-
bottom: ${showSequenceViewer ?
|
|
484
|
+
bottom: ${showSequenceViewer ? height.wide : "0"};
|
|
463
485
|
}
|
|
464
486
|
|
|
465
487
|
/* Spacer balancing the axes widget pinned in the opposite corner. */
|
|
@@ -635,7 +657,7 @@ function readoutSlot(label, value, residueCount) {
|
|
|
635
657
|
* residue takes priority so the user can inspect others without losing their
|
|
636
658
|
* selection.
|
|
637
659
|
*/
|
|
638
|
-
function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChains, hoveredResidue = null, onChainHover, onChainSelect, onChainToggle, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedChains, selectedResidue = null, showSequenceViewer, stats, valueLabel }) {
|
|
660
|
+
function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChains, hoveredResidue = null, onChainHover, onChainSelect, onChainToggle, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedChains, selectedResidue = null, sequenceViewerHeight, showSequenceViewer, stats, valueLabel }) {
|
|
639
661
|
var _activeResidue$residu, _stats$, _stats$2, _stats$3;
|
|
640
662
|
const activeResidue = hoveredResidue !== null && hoveredResidue !== void 0 ? hoveredResidue : selectedResidue;
|
|
641
663
|
const showResidue = activeResidue !== null;
|
|
@@ -650,6 +672,7 @@ function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChai
|
|
|
650
672
|
showResidue ? readoutSlot("pLDDT", activeResidue.plddt, residueCount) : (_stats$3 = stats[2]) !== null && _stats$3 !== void 0 ? _stats$3 : null
|
|
651
673
|
];
|
|
652
674
|
return /* @__PURE__ */ jsxs(LegendOverlay, {
|
|
675
|
+
sequenceViewerHeight,
|
|
653
676
|
showSequenceViewer,
|
|
654
677
|
children: [/* @__PURE__ */ jsx(StatsGrid, { children: Array.from({ length: SLOT_COUNT }, (_, i) => {
|
|
655
678
|
var _slots$i;
|
|
@@ -3636,6 +3659,7 @@ const _excluded = [
|
|
|
3636
3659
|
"sceneMode",
|
|
3637
3660
|
"selection",
|
|
3638
3661
|
"sequenceViewerBackgroundColor",
|
|
3662
|
+
"sequenceViewerHeight",
|
|
3639
3663
|
"showAxes",
|
|
3640
3664
|
"showChainLegend",
|
|
3641
3665
|
"showLegend",
|
|
@@ -3698,7 +3722,7 @@ function resolveScaleProps(colorBy, overlay) {
|
|
|
3698
3722
|
}
|
|
3699
3723
|
var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
|
|
3700
3724
|
var _selection$chains;
|
|
3701
|
-
const { backgroundColor, chainColors: chainColorOverrides, colorBy: colorByProp, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, highlights, initialCamera, onCameraChange, onChainVisibilityChange, onChainsChange, molstarSpec, onDispose, onError, onReady, onResidueClick, onResidueHover, onSelectionChange, onStructureLoad, orientation, projection, representation = "cartoon", structure, plddt, residueOverlay, sceneMode = "managed", selection: selectionProp, sequenceViewerBackgroundColor, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = _objectWithoutProperties(props, _excluded);
|
|
3725
|
+
const { backgroundColor, chainColors: chainColorOverrides, colorBy: colorByProp, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, highlights, initialCamera, onCameraChange, onChainVisibilityChange, onChainsChange, molstarSpec, onDispose, onError, onReady, onResidueClick, onResidueHover, onSelectionChange, onStructureLoad, orientation, projection, representation = "cartoon", structure, plddt, residueOverlay, sceneMode = "managed", selection: selectionProp, sequenceViewerBackgroundColor, sequenceViewerHeight, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = _objectWithoutProperties(props, _excluded);
|
|
3702
3726
|
const theme = useTheme();
|
|
3703
3727
|
const mode = getMode({ theme });
|
|
3704
3728
|
const semanticColors = getSemanticColors({ theme });
|
|
@@ -3879,6 +3903,7 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
|
|
|
3879
3903
|
const plddtColoringActive = colorBy === "plddt";
|
|
3880
3904
|
return /* @__PURE__ */ jsxs(ViewerRoot, _objectSpread2(_objectSpread2({
|
|
3881
3905
|
ref,
|
|
3906
|
+
sequenceViewerHeight,
|
|
3882
3907
|
showSequenceViewer
|
|
3883
3908
|
}, rest), {}, { children: [/* @__PURE__ */ jsx(PluginMount, { ref: pluginMountRef }), showLegend && /* @__PURE__ */ jsx(StructureLegend, _objectSpread2({
|
|
3884
3909
|
chainBandColors: plddtColoringActive ? PLDDT_BAND_COLORS : void 0,
|
|
@@ -3891,6 +3916,7 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
|
|
|
3891
3916
|
onChainToggle: toggleChain,
|
|
3892
3917
|
selectedChains,
|
|
3893
3918
|
selectedResidue: selectedReadout,
|
|
3919
|
+
sequenceViewerHeight,
|
|
3894
3920
|
showSequenceViewer,
|
|
3895
3921
|
stats: stats !== null && stats !== void 0 ? stats : []
|
|
3896
3922
|
}, scaleProps))] }));
|
|
@@ -21,12 +21,12 @@ const TitleContainer = (0, _emotion_styled.default)("div")`
|
|
|
21
21
|
const spaces = (0, _czi_sds_components.getSpaces)(props);
|
|
22
22
|
return `
|
|
23
23
|
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
24
|
-
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
24
|
+
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
25
25
|
`;
|
|
26
26
|
}}
|
|
27
27
|
`;
|
|
28
28
|
const ChartTitle = (0, _emotion_styled.default)("div")`
|
|
29
|
-
${_czi_sds_components.
|
|
29
|
+
${_czi_sds_components.fontBodyMediumXs}
|
|
30
30
|
|
|
31
31
|
${(props) => {
|
|
32
32
|
var _semanticColors$base;
|
|
@@ -36,36 +36,19 @@ const ChartTitle = (0, _emotion_styled.default)("div")`
|
|
|
36
36
|
`;
|
|
37
37
|
}}
|
|
38
38
|
`;
|
|
39
|
-
const StyledBadge = (0, _emotion_styled.default)("div")`
|
|
40
|
-
${_czi_sds_components.fontTabularSemiboldXxxxs}
|
|
41
|
-
|
|
42
|
-
${(props) => {
|
|
43
|
-
var _semanticColors$base2, _semanticColors$base3;
|
|
44
|
-
const spaces = (0, _czi_sds_components.getSpaces)(props);
|
|
45
|
-
const corners = (0, _czi_sds_components.getCorners)(props);
|
|
46
|
-
const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
|
|
47
|
-
return `
|
|
48
|
-
padding: ${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px ${spaces === null || spaces === void 0 ? void 0 : spaces.xxs}px;
|
|
49
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
50
|
-
background-color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary};
|
|
51
|
-
color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.textSecondary};
|
|
52
|
-
`;
|
|
53
|
-
}}
|
|
54
|
-
`;
|
|
55
39
|
const BarContainer = (0, _emotion_styled.default)("div")`
|
|
56
40
|
${(props) => {
|
|
57
|
-
var _semanticColors$
|
|
41
|
+
var _semanticColors$base2;
|
|
58
42
|
const { width, barHeight, isEmpty } = props;
|
|
59
43
|
const corners = (0, _czi_sds_components.getCorners)(props);
|
|
60
44
|
const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
|
|
61
45
|
const finalWidth = typeof width === "number" ? `${width}px` : width;
|
|
62
46
|
return `
|
|
63
47
|
display: flex;
|
|
64
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.
|
|
48
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
65
49
|
width: ${finalWidth};
|
|
66
50
|
height: ${barHeight}px;
|
|
67
|
-
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$
|
|
68
|
-
overflow: hidden;
|
|
51
|
+
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary : "transparent"};
|
|
69
52
|
`;
|
|
70
53
|
}}
|
|
71
54
|
`;
|
|
@@ -73,14 +56,17 @@ const BarSegment = (0, _emotion_styled.default)("div")`
|
|
|
73
56
|
${(props) => {
|
|
74
57
|
const { color, height, percentage, opacity, disabled, isLast } = props;
|
|
75
58
|
const spaces = (0, _czi_sds_components.getSpaces)(props);
|
|
59
|
+
const corners = (0, _czi_sds_components.getCorners)(props);
|
|
76
60
|
const gap = (spaces === null || spaces === void 0 ? void 0 : spaces.xxxs) || 0;
|
|
61
|
+
const marginRight = percentage > 0 && !isLast ? `${gap}px` : 0;
|
|
77
62
|
return `
|
|
78
63
|
background-color: ${color};
|
|
64
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
79
65
|
height: ${height}px;
|
|
80
66
|
flex-grow: ${percentage};
|
|
81
67
|
flex-basis: 0;
|
|
82
68
|
flex-shrink: 1;
|
|
83
|
-
margin-right: ${
|
|
69
|
+
margin-right: ${marginRight};
|
|
84
70
|
opacity: ${opacity};
|
|
85
71
|
transition: opacity 0.2s ease-in-out, flex-grow 0.25s ease-in-out, margin-right 0.25s ease-out;
|
|
86
72
|
cursor: ${disabled ? "default" : "pointer"};
|
|
@@ -94,7 +80,7 @@ const StyledStackedBarChartWrapper = (0, _emotion_styled.default)("div")`
|
|
|
94
80
|
return `
|
|
95
81
|
display: flex;
|
|
96
82
|
flex-direction: column;
|
|
97
|
-
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
83
|
+
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
98
84
|
`;
|
|
99
85
|
}}
|
|
100
86
|
`;
|
|
@@ -124,17 +110,18 @@ const _excluded = [
|
|
|
124
110
|
"remainingLabel",
|
|
125
111
|
"remainingUnit",
|
|
126
112
|
"unit",
|
|
127
|
-
"colorGeneratorOptions"
|
|
113
|
+
"colorGeneratorOptions",
|
|
114
|
+
"maxItems"
|
|
128
115
|
];
|
|
129
116
|
const calculateBadgeText = (totalItems, selectedCount) => {
|
|
130
117
|
if (selectedCount === 0 || selectedCount === totalItems) return `${totalItems}`;
|
|
131
118
|
return `${selectedCount} of ${totalItems}`;
|
|
132
119
|
};
|
|
133
120
|
const calculateLegendValue = (item, showLegendValues, legendValueFormat, globalUnit) => {
|
|
121
|
+
var _ref, _item$unit;
|
|
134
122
|
if (!showLegendValues) return void 0;
|
|
135
123
|
if (legendValueFormat === "percentage") return `${Math.round(item.percentage)}%`;
|
|
136
|
-
|
|
137
|
-
return effectiveUnit ? `${item.value} ${effectiveUnit}` : `${item.value}`;
|
|
124
|
+
return `${item.value.toLocaleString()} ${(_ref = (_item$unit = item.unit) !== null && _item$unit !== void 0 ? _item$unit : globalUnit) !== null && _ref !== void 0 ? _ref : ""}`.trim();
|
|
138
125
|
};
|
|
139
126
|
const getSegmentOpacity = (index, hoveredIndex, selectedIndicesSet, selectionBehavior) => {
|
|
140
127
|
const hasSelection = selectedIndicesSet.size > 0;
|
|
@@ -181,7 +168,7 @@ const applySelectionStyling = (items, selectedIndicesSet, selectionBehavior, mod
|
|
|
181
168
|
return mode === "cumulative" ? applyCumulativeHideStyling(items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) : applyProportionalHideStyling(items, selectedIndicesSet, selectedTotal);
|
|
182
169
|
};
|
|
183
170
|
const buildLegendItems = (dataWithPercentages, options) => {
|
|
184
|
-
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, theme } = options;
|
|
171
|
+
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, remainingOpensMenu, theme } = options;
|
|
185
172
|
const legendItems = dataWithPercentages.map((item) => ({
|
|
186
173
|
name: item.name,
|
|
187
174
|
value: calculateLegendValue(item, showLegendValues, legendValueFormat, unit),
|
|
@@ -195,13 +182,13 @@ const buildLegendItems = (dataWithPercentages, options) => {
|
|
|
195
182
|
else if (legendValueFormat === "percentage") remainingValueDisplay = `${Math.round(remainingPercentage)}%`;
|
|
196
183
|
else {
|
|
197
184
|
const effectiveRemainingUnit = remainingUnit || unit || "";
|
|
198
|
-
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue} ${effectiveRemainingUnit}` : `${remainingValue}`;
|
|
185
|
+
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue.toLocaleString()} ${effectiveRemainingUnit}` : `${remainingValue.toLocaleString()}`;
|
|
199
186
|
}
|
|
200
187
|
legendItems.push({
|
|
201
188
|
name: remainingLabel,
|
|
202
189
|
value: remainingValueDisplay,
|
|
203
190
|
color: theme === null || theme === void 0 || (_theme$palette = theme.palette) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.sds) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.base) === null || _theme$palette === void 0 ? void 0 : _theme$palette.backgroundTertiary,
|
|
204
|
-
disabled:
|
|
191
|
+
disabled: !remainingOpensMenu
|
|
205
192
|
});
|
|
206
193
|
}
|
|
207
194
|
return legendItems;
|
|
@@ -237,10 +224,13 @@ const renderBarSegment = (item, options) => {
|
|
|
237
224
|
}, item.key) : (0, react.cloneElement)(barSegment, { key: item.key });
|
|
238
225
|
};
|
|
239
226
|
const StackedBarChart = (props) => {
|
|
240
|
-
var _theme$
|
|
241
|
-
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp =
|
|
227
|
+
var _theme$palette5;
|
|
228
|
+
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp = 8, showLegend = true, showLegendValues = true, legendValueFormat = "percentage", selectedIndices = [], onSelectionChange, onSegmentMouseEnter, onSegmentMouseLeave, onLegendItemMouseEnter, onLegendItemMouseLeave, onSegmentClick, onLegendItemClick, selectionBehavior = "dim", mode = "proportional", maxAmount, remainingLabel = "Remaining", remainingUnit, unit, colorGeneratorOptions, maxItems } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
|
|
242
229
|
const barHeight = Math.max(1, barHeightProp);
|
|
243
230
|
const theme = (0, _mui_material.useTheme)();
|
|
231
|
+
const visibleCount = mode === "cumulative" && maxItems !== void 0 && maxItems < data.length ? Math.max(0, maxItems) : data.length;
|
|
232
|
+
const chartData = (0, react.useMemo)(() => data.slice(0, visibleCount), [data, visibleCount]);
|
|
233
|
+
const hiddenItems = (0, react.useMemo)(() => data.slice(visibleCount), [data, visibleCount]);
|
|
244
234
|
const [hoveredIndex, setHoveredIndex] = (0, react.useState)(null);
|
|
245
235
|
const [animatedItems, setAnimatedItems] = (0, react.useState)([]);
|
|
246
236
|
const [autoColorMap, setAutoColorMap] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
@@ -263,8 +253,8 @@ const StackedBarChart = (props) => {
|
|
|
263
253
|
theme
|
|
264
254
|
]);
|
|
265
255
|
(0, react.useEffect)(() => {
|
|
266
|
-
const autoItems =
|
|
267
|
-
const currentDataKeys = new Set(
|
|
256
|
+
const autoItems = chartData.filter((item) => !item.color);
|
|
257
|
+
const currentDataKeys = new Set(chartData.map((item) => item.name));
|
|
268
258
|
const previousDataKeys = previousDataKeysRef.current;
|
|
269
259
|
const previousColorOptions = previousColorOptionsRef.current;
|
|
270
260
|
const colorOptionsChanged = !previousColorOptions || previousColorOptions.isDarkMode !== isDarkMode || previousColorOptions.options !== colorGeneratorOptions;
|
|
@@ -290,7 +280,7 @@ const StackedBarChart = (props) => {
|
|
|
290
280
|
if (hasChanges) setAutoColorMap(updatedMap);
|
|
291
281
|
}
|
|
292
282
|
}, [
|
|
293
|
-
|
|
283
|
+
chartData,
|
|
294
284
|
colorGeneratorOptions,
|
|
295
285
|
isDarkMode,
|
|
296
286
|
theme,
|
|
@@ -299,9 +289,10 @@ const StackedBarChart = (props) => {
|
|
|
299
289
|
const displayBadge = badge !== null && badge !== void 0 ? badge : calculateBadgeText(data.length, selectedIndices.length);
|
|
300
290
|
const { effectiveMaxAmount, hasRemaining, remainingValue, remainingPercentage } = (0, react.useMemo)(() => {
|
|
301
291
|
const total = data.reduce((sum, item) => sum + item.value, 0);
|
|
292
|
+
const visibleTotal = chartData.reduce((sum, item) => sum + item.value, 0);
|
|
302
293
|
const maxAmountValue = mode === "cumulative" && maxAmount ? maxAmount : total;
|
|
303
|
-
const hasRemainingSegment = mode === "cumulative" &&
|
|
304
|
-
const remaining = hasRemainingSegment ? maxAmountValue -
|
|
294
|
+
const hasRemainingSegment = mode === "cumulative" && visibleTotal < maxAmountValue;
|
|
295
|
+
const remaining = hasRemainingSegment ? maxAmountValue - visibleTotal : 0;
|
|
305
296
|
return {
|
|
306
297
|
effectiveMaxAmount: maxAmountValue,
|
|
307
298
|
hasRemaining: hasRemainingSegment,
|
|
@@ -310,11 +301,20 @@ const StackedBarChart = (props) => {
|
|
|
310
301
|
};
|
|
311
302
|
}, [
|
|
312
303
|
data,
|
|
304
|
+
chartData,
|
|
313
305
|
mode,
|
|
314
306
|
maxAmount
|
|
315
307
|
]);
|
|
308
|
+
const hasOverflow = hasRemaining && hiddenItems.length > 0;
|
|
309
|
+
const overflowIndex = data.length;
|
|
310
|
+
const hiddenColors = (0, react.useMemo)(() => hasOverflow ? (0, _czi_sds_components.generateDiscreteColors)(hiddenItems.length, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, colorGeneratorOptions || {}), {}, { isDarkMode })) : [], [
|
|
311
|
+
hasOverflow,
|
|
312
|
+
hiddenItems.length,
|
|
313
|
+
colorGeneratorOptions,
|
|
314
|
+
isDarkMode
|
|
315
|
+
]);
|
|
316
316
|
const dataWithPercentages = (0, react.useMemo)(() => {
|
|
317
|
-
return
|
|
317
|
+
return chartData.map((item, index) => {
|
|
318
318
|
var _theme$palette3;
|
|
319
319
|
const resolvedColor = item.color || autoColorMap.get(item.name) || (theme === null || theme === void 0 || (_theme$palette3 = theme.palette) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.sds) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.base) === null || _theme$palette3 === void 0 ? void 0 : _theme$palette3.ornamentSecondary);
|
|
320
320
|
return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, {
|
|
@@ -327,7 +327,7 @@ const StackedBarChart = (props) => {
|
|
|
327
327
|
});
|
|
328
328
|
});
|
|
329
329
|
}, [
|
|
330
|
-
|
|
330
|
+
chartData,
|
|
331
331
|
effectiveMaxAmount,
|
|
332
332
|
autoColorMap,
|
|
333
333
|
theme
|
|
@@ -368,10 +368,16 @@ const StackedBarChart = (props) => {
|
|
|
368
368
|
timeoutIds.forEach((id) => window.clearTimeout(id));
|
|
369
369
|
};
|
|
370
370
|
}, [dataWithPercentages, remainingPercentage]);
|
|
371
|
-
const
|
|
372
|
-
|
|
371
|
+
const toChartIndex = (0, react.useCallback)((index) => hasOverflow && index >= visibleCount ? overflowIndex : index, [
|
|
372
|
+
hasOverflow,
|
|
373
|
+
visibleCount,
|
|
374
|
+
overflowIndex
|
|
375
|
+
]);
|
|
376
|
+
const hoveredChartIndex = hoveredIndex === null ? null : toChartIndex(hoveredIndex);
|
|
377
|
+
const selectedChartIndices = (0, react.useMemo)(() => new Set(selectedIndices.map(toChartIndex)), [selectedIndices, toChartIndex]);
|
|
378
|
+
const { styledItems, updatedRemainingPercentage, updatedRemainingValue } = (0, react.useMemo)(() => applySelectionStyling(animatedItems, selectedChartIndices, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, animatedRemainingPercentage, remainingValue), [
|
|
373
379
|
animatedItems,
|
|
374
|
-
|
|
380
|
+
selectedChartIndices,
|
|
375
381
|
selectionBehavior,
|
|
376
382
|
mode,
|
|
377
383
|
hasRemaining,
|
|
@@ -392,6 +398,7 @@ const StackedBarChart = (props) => {
|
|
|
392
398
|
remainingPercentage: displayRemainingPercentage,
|
|
393
399
|
remainingLabel,
|
|
394
400
|
remainingUnit,
|
|
401
|
+
remainingOpensMenu: hasOverflow,
|
|
395
402
|
theme
|
|
396
403
|
}), [
|
|
397
404
|
dataWithPercentages,
|
|
@@ -404,19 +411,51 @@ const StackedBarChart = (props) => {
|
|
|
404
411
|
displayRemainingPercentage,
|
|
405
412
|
remainingLabel,
|
|
406
413
|
remainingUnit,
|
|
414
|
+
hasOverflow,
|
|
407
415
|
theme
|
|
408
416
|
]);
|
|
409
|
-
const
|
|
417
|
+
const toLegendIndex = (0, react.useCallback)((index) => {
|
|
418
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
419
|
+
return index === overflowIndex ? visibleCount : index + 1;
|
|
420
|
+
}, [
|
|
421
|
+
hasOverflow,
|
|
422
|
+
visibleCount,
|
|
423
|
+
overflowIndex
|
|
424
|
+
]);
|
|
425
|
+
const fromLegendIndex = (0, react.useCallback)((index) => {
|
|
426
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
427
|
+
return index === visibleCount ? overflowIndex : index - 1;
|
|
428
|
+
}, [
|
|
429
|
+
hasOverflow,
|
|
430
|
+
visibleCount,
|
|
431
|
+
overflowIndex
|
|
432
|
+
]);
|
|
433
|
+
const handleLegendItemHover = (0, react.useCallback)((_item, legendIndex) => {
|
|
434
|
+
const index = fromLegendIndex(legendIndex);
|
|
410
435
|
setHoveredIndex(index);
|
|
411
436
|
if (onLegendItemMouseEnter && data[index]) onLegendItemMouseEnter(data[index], index);
|
|
412
|
-
}, [
|
|
413
|
-
|
|
437
|
+
}, [
|
|
438
|
+
onLegendItemMouseEnter,
|
|
439
|
+
data,
|
|
440
|
+
fromLegendIndex
|
|
441
|
+
]);
|
|
442
|
+
const handleLegendItemLeave = (0, react.useCallback)((_item, legendIndex) => {
|
|
443
|
+
const index = fromLegendIndex(legendIndex);
|
|
414
444
|
setHoveredIndex(null);
|
|
415
445
|
if (onLegendItemMouseLeave && data[index]) onLegendItemMouseLeave(data[index], index);
|
|
416
|
-
}, [
|
|
417
|
-
|
|
446
|
+
}, [
|
|
447
|
+
onLegendItemMouseLeave,
|
|
448
|
+
data,
|
|
449
|
+
fromLegendIndex
|
|
450
|
+
]);
|
|
451
|
+
const handleLegendItemClickInternal = (0, react.useCallback)((_item, legendIndex) => {
|
|
452
|
+
const index = fromLegendIndex(legendIndex);
|
|
418
453
|
if (onLegendItemClick && data[index]) onLegendItemClick(data[index], index);
|
|
419
|
-
}, [
|
|
454
|
+
}, [
|
|
455
|
+
onLegendItemClick,
|
|
456
|
+
data,
|
|
457
|
+
fromLegendIndex
|
|
458
|
+
]);
|
|
420
459
|
const handleSelectionChange = (0, react.useCallback)((newSelectedIndices) => {
|
|
421
460
|
if (!onSelectionChange) return;
|
|
422
461
|
onSelectionChange(newSelectedIndices, data.filter((item, itemIndex) => newSelectedIndices.includes(itemIndex)));
|
|
@@ -424,15 +463,29 @@ const StackedBarChart = (props) => {
|
|
|
424
463
|
const handleSegmentClick = (0, react.useCallback)((index) => {
|
|
425
464
|
if (onSegmentClick && data[index]) onSegmentClick(data[index], index);
|
|
426
465
|
if (!onSelectionChange) return;
|
|
427
|
-
handleSelectionChange(
|
|
466
|
+
handleSelectionChange(selectedChartIndices.has(index) ? selectedIndices.filter((i) => i !== index) : [...selectedIndices, index]);
|
|
428
467
|
}, [
|
|
429
468
|
onSelectionChange,
|
|
430
469
|
onSegmentClick,
|
|
431
470
|
data,
|
|
432
471
|
selectedIndices,
|
|
433
|
-
|
|
472
|
+
selectedChartIndices,
|
|
434
473
|
handleSelectionChange
|
|
435
474
|
]);
|
|
475
|
+
const remainingMenuItems = (0, react.useMemo)(() => hasOverflow ? hiddenItems.map((item, index) => {
|
|
476
|
+
var _theme$palette4;
|
|
477
|
+
return {
|
|
478
|
+
name: item.name,
|
|
479
|
+
value: item.value,
|
|
480
|
+
color: item.color || hiddenColors[index] || (theme === null || theme === void 0 || (_theme$palette4 = theme.palette) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.sds) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.base) === null || _theme$palette4 === void 0 ? void 0 : _theme$palette4.ornamentSecondary),
|
|
481
|
+
disabled: item.disabled
|
|
482
|
+
};
|
|
483
|
+
}) : [], [
|
|
484
|
+
hasOverflow,
|
|
485
|
+
hiddenItems,
|
|
486
|
+
hiddenColors,
|
|
487
|
+
theme
|
|
488
|
+
]);
|
|
436
489
|
const lastVisibleIndex = (0, react.useMemo)(() => {
|
|
437
490
|
for (let i = styledItems.length - 1; i >= 0; i--) if (styledItems[i].percentage > 0) return i;
|
|
438
491
|
return -1;
|
|
@@ -444,8 +497,8 @@ const StackedBarChart = (props) => {
|
|
|
444
497
|
children: [styledItems.map((item, index) => {
|
|
445
498
|
return renderBarSegment(item, {
|
|
446
499
|
barHeight,
|
|
447
|
-
hoveredIndex,
|
|
448
|
-
selectedIndicesSet,
|
|
500
|
+
hoveredIndex: hoveredChartIndex,
|
|
501
|
+
selectedIndicesSet: selectedChartIndices,
|
|
449
502
|
selectionBehavior,
|
|
450
503
|
isLast: index === lastVisibleIndex && !hasRemaining,
|
|
451
504
|
onMouseEnter: () => {
|
|
@@ -461,25 +514,31 @@ const StackedBarChart = (props) => {
|
|
|
461
514
|
onClick: () => !item.isExiting && handleSegmentClick(item.originalIndex)
|
|
462
515
|
});
|
|
463
516
|
}), previousProcessedRef.current.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BarSegment, {
|
|
464
|
-
color: theme === null || theme === void 0 || (_theme$
|
|
517
|
+
color: theme === null || theme === void 0 || (_theme$palette5 = theme.palette) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.sds) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.base) === null || _theme$palette5 === void 0 ? void 0 : _theme$palette5.backgroundTertiary,
|
|
465
518
|
percentage: displayRemainingPercentage,
|
|
466
519
|
height: barHeight,
|
|
467
520
|
isLast: true,
|
|
468
|
-
opacity: 1,
|
|
521
|
+
opacity: hasOverflow ? getSegmentOpacity(overflowIndex, hoveredChartIndex, selectedChartIndices, selectionBehavior) : 1,
|
|
469
522
|
disabled: true
|
|
470
523
|
}, "remaining")]
|
|
471
524
|
}), showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Legend, {
|
|
472
525
|
items: legendItems,
|
|
526
|
+
lastItemMenuItems: remainingMenuItems,
|
|
473
527
|
showValues: showLegendValues,
|
|
474
528
|
onItemMouseEnter: handleLegendItemHover,
|
|
475
529
|
onItemMouseLeave: handleLegendItemLeave,
|
|
476
530
|
onItemClick: onLegendItemClick ? handleLegendItemClickInternal : void 0,
|
|
477
|
-
selectedIndices,
|
|
478
|
-
onSelectionChange: onSelectionChange ? handleSelectionChange : void 0,
|
|
479
|
-
hoveredIndex
|
|
531
|
+
selectedIndices: selectedIndices.map(toLegendIndex),
|
|
532
|
+
onSelectionChange: onSelectionChange ? (indices) => handleSelectionChange(indices.map(fromLegendIndex)) : void 0,
|
|
533
|
+
hoveredIndex: hoveredIndex === null ? null : toLegendIndex(hoveredIndex)
|
|
480
534
|
})] });
|
|
481
535
|
if (!title) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartWrapper, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({ width }, rest), {}, { children: chartContent }));
|
|
482
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartWrapper, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TitleContainer, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
536
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartWrapper, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TitleContainer, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Tag, {
|
|
537
|
+
label: displayBadge,
|
|
538
|
+
sdsStyle: "outline",
|
|
539
|
+
color: "neutral",
|
|
540
|
+
size: "small"
|
|
541
|
+
})] }), chartContent] }));
|
|
483
542
|
};
|
|
484
543
|
//#endregion
|
|
485
544
|
Object.defineProperty(exports, "StackedBarChart", {
|
|
@@ -3,7 +3,7 @@ import { n as _objectWithoutProperties, t as _objectSpread2 } from "./objectSpre
|
|
|
3
3
|
import { cloneElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import styled from "@emotion/styled";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { Legend, Tooltip, TooltipTable,
|
|
6
|
+
import { Legend, Tag, Tooltip, TooltipTable, fontBodyMediumXs, generateDiscreteColors, getCorners, getMode, getSemanticColors, getSpaces } from "@czi-sds/components";
|
|
7
7
|
import { useTheme } from "@mui/material";
|
|
8
8
|
//#region src/core/StackedBarChart/style.ts
|
|
9
9
|
const ChartWrapper = styled("div")`
|
|
@@ -19,12 +19,12 @@ const TitleContainer = styled("div")`
|
|
|
19
19
|
const spaces = getSpaces(props);
|
|
20
20
|
return `
|
|
21
21
|
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
22
|
-
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
22
|
+
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
23
23
|
`;
|
|
24
24
|
}}
|
|
25
25
|
`;
|
|
26
26
|
const ChartTitle = styled("div")`
|
|
27
|
-
${
|
|
27
|
+
${fontBodyMediumXs}
|
|
28
28
|
|
|
29
29
|
${(props) => {
|
|
30
30
|
var _semanticColors$base;
|
|
@@ -34,36 +34,19 @@ const ChartTitle = styled("div")`
|
|
|
34
34
|
`;
|
|
35
35
|
}}
|
|
36
36
|
`;
|
|
37
|
-
const StyledBadge = styled("div")`
|
|
38
|
-
${fontTabularSemiboldXxxxs}
|
|
39
|
-
|
|
40
|
-
${(props) => {
|
|
41
|
-
var _semanticColors$base2, _semanticColors$base3;
|
|
42
|
-
const spaces = getSpaces(props);
|
|
43
|
-
const corners = getCorners(props);
|
|
44
|
-
const semanticColors = getSemanticColors(props);
|
|
45
|
-
return `
|
|
46
|
-
padding: ${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px ${spaces === null || spaces === void 0 ? void 0 : spaces.xxs}px;
|
|
47
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
48
|
-
background-color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary};
|
|
49
|
-
color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.textSecondary};
|
|
50
|
-
`;
|
|
51
|
-
}}
|
|
52
|
-
`;
|
|
53
37
|
const BarContainer = styled("div")`
|
|
54
38
|
${(props) => {
|
|
55
|
-
var _semanticColors$
|
|
39
|
+
var _semanticColors$base2;
|
|
56
40
|
const { width, barHeight, isEmpty } = props;
|
|
57
41
|
const corners = getCorners(props);
|
|
58
42
|
const semanticColors = getSemanticColors(props);
|
|
59
43
|
const finalWidth = typeof width === "number" ? `${width}px` : width;
|
|
60
44
|
return `
|
|
61
45
|
display: flex;
|
|
62
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.
|
|
46
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
63
47
|
width: ${finalWidth};
|
|
64
48
|
height: ${barHeight}px;
|
|
65
|
-
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$
|
|
66
|
-
overflow: hidden;
|
|
49
|
+
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary : "transparent"};
|
|
67
50
|
`;
|
|
68
51
|
}}
|
|
69
52
|
`;
|
|
@@ -71,14 +54,17 @@ const BarSegment = styled("div")`
|
|
|
71
54
|
${(props) => {
|
|
72
55
|
const { color, height, percentage, opacity, disabled, isLast } = props;
|
|
73
56
|
const spaces = getSpaces(props);
|
|
57
|
+
const corners = getCorners(props);
|
|
74
58
|
const gap = (spaces === null || spaces === void 0 ? void 0 : spaces.xxxs) || 0;
|
|
59
|
+
const marginRight = percentage > 0 && !isLast ? `${gap}px` : 0;
|
|
75
60
|
return `
|
|
76
61
|
background-color: ${color};
|
|
62
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
77
63
|
height: ${height}px;
|
|
78
64
|
flex-grow: ${percentage};
|
|
79
65
|
flex-basis: 0;
|
|
80
66
|
flex-shrink: 1;
|
|
81
|
-
margin-right: ${
|
|
67
|
+
margin-right: ${marginRight};
|
|
82
68
|
opacity: ${opacity};
|
|
83
69
|
transition: opacity 0.2s ease-in-out, flex-grow 0.25s ease-in-out, margin-right 0.25s ease-out;
|
|
84
70
|
cursor: ${disabled ? "default" : "pointer"};
|
|
@@ -92,7 +78,7 @@ const StyledStackedBarChartWrapper = styled("div")`
|
|
|
92
78
|
return `
|
|
93
79
|
display: flex;
|
|
94
80
|
flex-direction: column;
|
|
95
|
-
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
81
|
+
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
96
82
|
`;
|
|
97
83
|
}}
|
|
98
84
|
`;
|
|
@@ -122,17 +108,18 @@ const _excluded = [
|
|
|
122
108
|
"remainingLabel",
|
|
123
109
|
"remainingUnit",
|
|
124
110
|
"unit",
|
|
125
|
-
"colorGeneratorOptions"
|
|
111
|
+
"colorGeneratorOptions",
|
|
112
|
+
"maxItems"
|
|
126
113
|
];
|
|
127
114
|
const calculateBadgeText = (totalItems, selectedCount) => {
|
|
128
115
|
if (selectedCount === 0 || selectedCount === totalItems) return `${totalItems}`;
|
|
129
116
|
return `${selectedCount} of ${totalItems}`;
|
|
130
117
|
};
|
|
131
118
|
const calculateLegendValue = (item, showLegendValues, legendValueFormat, globalUnit) => {
|
|
119
|
+
var _ref, _item$unit;
|
|
132
120
|
if (!showLegendValues) return void 0;
|
|
133
121
|
if (legendValueFormat === "percentage") return `${Math.round(item.percentage)}%`;
|
|
134
|
-
|
|
135
|
-
return effectiveUnit ? `${item.value} ${effectiveUnit}` : `${item.value}`;
|
|
122
|
+
return `${item.value.toLocaleString()} ${(_ref = (_item$unit = item.unit) !== null && _item$unit !== void 0 ? _item$unit : globalUnit) !== null && _ref !== void 0 ? _ref : ""}`.trim();
|
|
136
123
|
};
|
|
137
124
|
const getSegmentOpacity = (index, hoveredIndex, selectedIndicesSet, selectionBehavior) => {
|
|
138
125
|
const hasSelection = selectedIndicesSet.size > 0;
|
|
@@ -179,7 +166,7 @@ const applySelectionStyling = (items, selectedIndicesSet, selectionBehavior, mod
|
|
|
179
166
|
return mode === "cumulative" ? applyCumulativeHideStyling(items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) : applyProportionalHideStyling(items, selectedIndicesSet, selectedTotal);
|
|
180
167
|
};
|
|
181
168
|
const buildLegendItems = (dataWithPercentages, options) => {
|
|
182
|
-
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, theme } = options;
|
|
169
|
+
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, remainingOpensMenu, theme } = options;
|
|
183
170
|
const legendItems = dataWithPercentages.map((item) => ({
|
|
184
171
|
name: item.name,
|
|
185
172
|
value: calculateLegendValue(item, showLegendValues, legendValueFormat, unit),
|
|
@@ -193,13 +180,13 @@ const buildLegendItems = (dataWithPercentages, options) => {
|
|
|
193
180
|
else if (legendValueFormat === "percentage") remainingValueDisplay = `${Math.round(remainingPercentage)}%`;
|
|
194
181
|
else {
|
|
195
182
|
const effectiveRemainingUnit = remainingUnit || unit || "";
|
|
196
|
-
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue} ${effectiveRemainingUnit}` : `${remainingValue}`;
|
|
183
|
+
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue.toLocaleString()} ${effectiveRemainingUnit}` : `${remainingValue.toLocaleString()}`;
|
|
197
184
|
}
|
|
198
185
|
legendItems.push({
|
|
199
186
|
name: remainingLabel,
|
|
200
187
|
value: remainingValueDisplay,
|
|
201
188
|
color: theme === null || theme === void 0 || (_theme$palette = theme.palette) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.sds) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.base) === null || _theme$palette === void 0 ? void 0 : _theme$palette.backgroundTertiary,
|
|
202
|
-
disabled:
|
|
189
|
+
disabled: !remainingOpensMenu
|
|
203
190
|
});
|
|
204
191
|
}
|
|
205
192
|
return legendItems;
|
|
@@ -235,10 +222,13 @@ const renderBarSegment = (item, options) => {
|
|
|
235
222
|
}, item.key) : cloneElement(barSegment, { key: item.key });
|
|
236
223
|
};
|
|
237
224
|
const StackedBarChart = (props) => {
|
|
238
|
-
var _theme$
|
|
239
|
-
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp =
|
|
225
|
+
var _theme$palette5;
|
|
226
|
+
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp = 8, showLegend = true, showLegendValues = true, legendValueFormat = "percentage", selectedIndices = [], onSelectionChange, onSegmentMouseEnter, onSegmentMouseLeave, onLegendItemMouseEnter, onLegendItemMouseLeave, onSegmentClick, onLegendItemClick, selectionBehavior = "dim", mode = "proportional", maxAmount, remainingLabel = "Remaining", remainingUnit, unit, colorGeneratorOptions, maxItems } = props, rest = _objectWithoutProperties(props, _excluded);
|
|
240
227
|
const barHeight = Math.max(1, barHeightProp);
|
|
241
228
|
const theme = useTheme();
|
|
229
|
+
const visibleCount = mode === "cumulative" && maxItems !== void 0 && maxItems < data.length ? Math.max(0, maxItems) : data.length;
|
|
230
|
+
const chartData = useMemo(() => data.slice(0, visibleCount), [data, visibleCount]);
|
|
231
|
+
const hiddenItems = useMemo(() => data.slice(visibleCount), [data, visibleCount]);
|
|
242
232
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
243
233
|
const [animatedItems, setAnimatedItems] = useState([]);
|
|
244
234
|
const [autoColorMap, setAutoColorMap] = useState(/* @__PURE__ */ new Map());
|
|
@@ -261,8 +251,8 @@ const StackedBarChart = (props) => {
|
|
|
261
251
|
theme
|
|
262
252
|
]);
|
|
263
253
|
useEffect(() => {
|
|
264
|
-
const autoItems =
|
|
265
|
-
const currentDataKeys = new Set(
|
|
254
|
+
const autoItems = chartData.filter((item) => !item.color);
|
|
255
|
+
const currentDataKeys = new Set(chartData.map((item) => item.name));
|
|
266
256
|
const previousDataKeys = previousDataKeysRef.current;
|
|
267
257
|
const previousColorOptions = previousColorOptionsRef.current;
|
|
268
258
|
const colorOptionsChanged = !previousColorOptions || previousColorOptions.isDarkMode !== isDarkMode || previousColorOptions.options !== colorGeneratorOptions;
|
|
@@ -288,7 +278,7 @@ const StackedBarChart = (props) => {
|
|
|
288
278
|
if (hasChanges) setAutoColorMap(updatedMap);
|
|
289
279
|
}
|
|
290
280
|
}, [
|
|
291
|
-
|
|
281
|
+
chartData,
|
|
292
282
|
colorGeneratorOptions,
|
|
293
283
|
isDarkMode,
|
|
294
284
|
theme,
|
|
@@ -297,9 +287,10 @@ const StackedBarChart = (props) => {
|
|
|
297
287
|
const displayBadge = badge !== null && badge !== void 0 ? badge : calculateBadgeText(data.length, selectedIndices.length);
|
|
298
288
|
const { effectiveMaxAmount, hasRemaining, remainingValue, remainingPercentage } = useMemo(() => {
|
|
299
289
|
const total = data.reduce((sum, item) => sum + item.value, 0);
|
|
290
|
+
const visibleTotal = chartData.reduce((sum, item) => sum + item.value, 0);
|
|
300
291
|
const maxAmountValue = mode === "cumulative" && maxAmount ? maxAmount : total;
|
|
301
|
-
const hasRemainingSegment = mode === "cumulative" &&
|
|
302
|
-
const remaining = hasRemainingSegment ? maxAmountValue -
|
|
292
|
+
const hasRemainingSegment = mode === "cumulative" && visibleTotal < maxAmountValue;
|
|
293
|
+
const remaining = hasRemainingSegment ? maxAmountValue - visibleTotal : 0;
|
|
303
294
|
return {
|
|
304
295
|
effectiveMaxAmount: maxAmountValue,
|
|
305
296
|
hasRemaining: hasRemainingSegment,
|
|
@@ -308,11 +299,20 @@ const StackedBarChart = (props) => {
|
|
|
308
299
|
};
|
|
309
300
|
}, [
|
|
310
301
|
data,
|
|
302
|
+
chartData,
|
|
311
303
|
mode,
|
|
312
304
|
maxAmount
|
|
313
305
|
]);
|
|
306
|
+
const hasOverflow = hasRemaining && hiddenItems.length > 0;
|
|
307
|
+
const overflowIndex = data.length;
|
|
308
|
+
const hiddenColors = useMemo(() => hasOverflow ? generateDiscreteColors(hiddenItems.length, _objectSpread2(_objectSpread2({}, colorGeneratorOptions || {}), {}, { isDarkMode })) : [], [
|
|
309
|
+
hasOverflow,
|
|
310
|
+
hiddenItems.length,
|
|
311
|
+
colorGeneratorOptions,
|
|
312
|
+
isDarkMode
|
|
313
|
+
]);
|
|
314
314
|
const dataWithPercentages = useMemo(() => {
|
|
315
|
-
return
|
|
315
|
+
return chartData.map((item, index) => {
|
|
316
316
|
var _theme$palette3;
|
|
317
317
|
const resolvedColor = item.color || autoColorMap.get(item.name) || (theme === null || theme === void 0 || (_theme$palette3 = theme.palette) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.sds) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.base) === null || _theme$palette3 === void 0 ? void 0 : _theme$palette3.ornamentSecondary);
|
|
318
318
|
return _objectSpread2(_objectSpread2({}, item), {}, {
|
|
@@ -325,7 +325,7 @@ const StackedBarChart = (props) => {
|
|
|
325
325
|
});
|
|
326
326
|
});
|
|
327
327
|
}, [
|
|
328
|
-
|
|
328
|
+
chartData,
|
|
329
329
|
effectiveMaxAmount,
|
|
330
330
|
autoColorMap,
|
|
331
331
|
theme
|
|
@@ -366,10 +366,16 @@ const StackedBarChart = (props) => {
|
|
|
366
366
|
timeoutIds.forEach((id) => window.clearTimeout(id));
|
|
367
367
|
};
|
|
368
368
|
}, [dataWithPercentages, remainingPercentage]);
|
|
369
|
-
const
|
|
370
|
-
|
|
369
|
+
const toChartIndex = useCallback((index) => hasOverflow && index >= visibleCount ? overflowIndex : index, [
|
|
370
|
+
hasOverflow,
|
|
371
|
+
visibleCount,
|
|
372
|
+
overflowIndex
|
|
373
|
+
]);
|
|
374
|
+
const hoveredChartIndex = hoveredIndex === null ? null : toChartIndex(hoveredIndex);
|
|
375
|
+
const selectedChartIndices = useMemo(() => new Set(selectedIndices.map(toChartIndex)), [selectedIndices, toChartIndex]);
|
|
376
|
+
const { styledItems, updatedRemainingPercentage, updatedRemainingValue } = useMemo(() => applySelectionStyling(animatedItems, selectedChartIndices, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, animatedRemainingPercentage, remainingValue), [
|
|
371
377
|
animatedItems,
|
|
372
|
-
|
|
378
|
+
selectedChartIndices,
|
|
373
379
|
selectionBehavior,
|
|
374
380
|
mode,
|
|
375
381
|
hasRemaining,
|
|
@@ -390,6 +396,7 @@ const StackedBarChart = (props) => {
|
|
|
390
396
|
remainingPercentage: displayRemainingPercentage,
|
|
391
397
|
remainingLabel,
|
|
392
398
|
remainingUnit,
|
|
399
|
+
remainingOpensMenu: hasOverflow,
|
|
393
400
|
theme
|
|
394
401
|
}), [
|
|
395
402
|
dataWithPercentages,
|
|
@@ -402,19 +409,51 @@ const StackedBarChart = (props) => {
|
|
|
402
409
|
displayRemainingPercentage,
|
|
403
410
|
remainingLabel,
|
|
404
411
|
remainingUnit,
|
|
412
|
+
hasOverflow,
|
|
405
413
|
theme
|
|
406
414
|
]);
|
|
407
|
-
const
|
|
415
|
+
const toLegendIndex = useCallback((index) => {
|
|
416
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
417
|
+
return index === overflowIndex ? visibleCount : index + 1;
|
|
418
|
+
}, [
|
|
419
|
+
hasOverflow,
|
|
420
|
+
visibleCount,
|
|
421
|
+
overflowIndex
|
|
422
|
+
]);
|
|
423
|
+
const fromLegendIndex = useCallback((index) => {
|
|
424
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
425
|
+
return index === visibleCount ? overflowIndex : index - 1;
|
|
426
|
+
}, [
|
|
427
|
+
hasOverflow,
|
|
428
|
+
visibleCount,
|
|
429
|
+
overflowIndex
|
|
430
|
+
]);
|
|
431
|
+
const handleLegendItemHover = useCallback((_item, legendIndex) => {
|
|
432
|
+
const index = fromLegendIndex(legendIndex);
|
|
408
433
|
setHoveredIndex(index);
|
|
409
434
|
if (onLegendItemMouseEnter && data[index]) onLegendItemMouseEnter(data[index], index);
|
|
410
|
-
}, [
|
|
411
|
-
|
|
435
|
+
}, [
|
|
436
|
+
onLegendItemMouseEnter,
|
|
437
|
+
data,
|
|
438
|
+
fromLegendIndex
|
|
439
|
+
]);
|
|
440
|
+
const handleLegendItemLeave = useCallback((_item, legendIndex) => {
|
|
441
|
+
const index = fromLegendIndex(legendIndex);
|
|
412
442
|
setHoveredIndex(null);
|
|
413
443
|
if (onLegendItemMouseLeave && data[index]) onLegendItemMouseLeave(data[index], index);
|
|
414
|
-
}, [
|
|
415
|
-
|
|
444
|
+
}, [
|
|
445
|
+
onLegendItemMouseLeave,
|
|
446
|
+
data,
|
|
447
|
+
fromLegendIndex
|
|
448
|
+
]);
|
|
449
|
+
const handleLegendItemClickInternal = useCallback((_item, legendIndex) => {
|
|
450
|
+
const index = fromLegendIndex(legendIndex);
|
|
416
451
|
if (onLegendItemClick && data[index]) onLegendItemClick(data[index], index);
|
|
417
|
-
}, [
|
|
452
|
+
}, [
|
|
453
|
+
onLegendItemClick,
|
|
454
|
+
data,
|
|
455
|
+
fromLegendIndex
|
|
456
|
+
]);
|
|
418
457
|
const handleSelectionChange = useCallback((newSelectedIndices) => {
|
|
419
458
|
if (!onSelectionChange) return;
|
|
420
459
|
onSelectionChange(newSelectedIndices, data.filter((item, itemIndex) => newSelectedIndices.includes(itemIndex)));
|
|
@@ -422,15 +461,29 @@ const StackedBarChart = (props) => {
|
|
|
422
461
|
const handleSegmentClick = useCallback((index) => {
|
|
423
462
|
if (onSegmentClick && data[index]) onSegmentClick(data[index], index);
|
|
424
463
|
if (!onSelectionChange) return;
|
|
425
|
-
handleSelectionChange(
|
|
464
|
+
handleSelectionChange(selectedChartIndices.has(index) ? selectedIndices.filter((i) => i !== index) : [...selectedIndices, index]);
|
|
426
465
|
}, [
|
|
427
466
|
onSelectionChange,
|
|
428
467
|
onSegmentClick,
|
|
429
468
|
data,
|
|
430
469
|
selectedIndices,
|
|
431
|
-
|
|
470
|
+
selectedChartIndices,
|
|
432
471
|
handleSelectionChange
|
|
433
472
|
]);
|
|
473
|
+
const remainingMenuItems = useMemo(() => hasOverflow ? hiddenItems.map((item, index) => {
|
|
474
|
+
var _theme$palette4;
|
|
475
|
+
return {
|
|
476
|
+
name: item.name,
|
|
477
|
+
value: item.value,
|
|
478
|
+
color: item.color || hiddenColors[index] || (theme === null || theme === void 0 || (_theme$palette4 = theme.palette) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.sds) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.base) === null || _theme$palette4 === void 0 ? void 0 : _theme$palette4.ornamentSecondary),
|
|
479
|
+
disabled: item.disabled
|
|
480
|
+
};
|
|
481
|
+
}) : [], [
|
|
482
|
+
hasOverflow,
|
|
483
|
+
hiddenItems,
|
|
484
|
+
hiddenColors,
|
|
485
|
+
theme
|
|
486
|
+
]);
|
|
434
487
|
const lastVisibleIndex = useMemo(() => {
|
|
435
488
|
for (let i = styledItems.length - 1; i >= 0; i--) if (styledItems[i].percentage > 0) return i;
|
|
436
489
|
return -1;
|
|
@@ -442,8 +495,8 @@ const StackedBarChart = (props) => {
|
|
|
442
495
|
children: [styledItems.map((item, index) => {
|
|
443
496
|
return renderBarSegment(item, {
|
|
444
497
|
barHeight,
|
|
445
|
-
hoveredIndex,
|
|
446
|
-
selectedIndicesSet,
|
|
498
|
+
hoveredIndex: hoveredChartIndex,
|
|
499
|
+
selectedIndicesSet: selectedChartIndices,
|
|
447
500
|
selectionBehavior,
|
|
448
501
|
isLast: index === lastVisibleIndex && !hasRemaining,
|
|
449
502
|
onMouseEnter: () => {
|
|
@@ -459,25 +512,31 @@ const StackedBarChart = (props) => {
|
|
|
459
512
|
onClick: () => !item.isExiting && handleSegmentClick(item.originalIndex)
|
|
460
513
|
});
|
|
461
514
|
}), previousProcessedRef.current.length > 0 && /* @__PURE__ */ jsx(BarSegment, {
|
|
462
|
-
color: theme === null || theme === void 0 || (_theme$
|
|
515
|
+
color: theme === null || theme === void 0 || (_theme$palette5 = theme.palette) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.sds) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.base) === null || _theme$palette5 === void 0 ? void 0 : _theme$palette5.backgroundTertiary,
|
|
463
516
|
percentage: displayRemainingPercentage,
|
|
464
517
|
height: barHeight,
|
|
465
518
|
isLast: true,
|
|
466
|
-
opacity: 1,
|
|
519
|
+
opacity: hasOverflow ? getSegmentOpacity(overflowIndex, hoveredChartIndex, selectedChartIndices, selectionBehavior) : 1,
|
|
467
520
|
disabled: true
|
|
468
521
|
}, "remaining")]
|
|
469
522
|
}), showLegend && /* @__PURE__ */ jsx(Legend, {
|
|
470
523
|
items: legendItems,
|
|
524
|
+
lastItemMenuItems: remainingMenuItems,
|
|
471
525
|
showValues: showLegendValues,
|
|
472
526
|
onItemMouseEnter: handleLegendItemHover,
|
|
473
527
|
onItemMouseLeave: handleLegendItemLeave,
|
|
474
528
|
onItemClick: onLegendItemClick ? handleLegendItemClickInternal : void 0,
|
|
475
|
-
selectedIndices,
|
|
476
|
-
onSelectionChange: onSelectionChange ? handleSelectionChange : void 0,
|
|
477
|
-
hoveredIndex
|
|
529
|
+
selectedIndices: selectedIndices.map(toLegendIndex),
|
|
530
|
+
onSelectionChange: onSelectionChange ? (indices) => handleSelectionChange(indices.map(fromLegendIndex)) : void 0,
|
|
531
|
+
hoveredIndex: hoveredIndex === null ? null : toLegendIndex(hoveredIndex)
|
|
478
532
|
})] });
|
|
479
533
|
if (!title) return /* @__PURE__ */ jsx(ChartWrapper, _objectSpread2(_objectSpread2({ width }, rest), {}, { children: chartContent }));
|
|
480
|
-
return /* @__PURE__ */ jsxs(ChartWrapper, _objectSpread2(_objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ jsxs(TitleContainer, { children: [/* @__PURE__ */ jsx(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ jsx(
|
|
534
|
+
return /* @__PURE__ */ jsxs(ChartWrapper, _objectSpread2(_objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ jsxs(TitleContainer, { children: [/* @__PURE__ */ jsx(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ jsx(Tag, {
|
|
535
|
+
label: displayBadge,
|
|
536
|
+
sdsStyle: "outline",
|
|
537
|
+
color: "neutral",
|
|
538
|
+
size: "small"
|
|
539
|
+
})] }), chartContent] }));
|
|
481
540
|
};
|
|
482
541
|
//#endregion
|
|
483
542
|
export { StackedBarChart as t };
|
|
@@ -361,6 +361,17 @@ interface ProteinStructureViewerProps extends Omit<HTMLAttributes<HTMLDivElement
|
|
|
361
361
|
* @default true
|
|
362
362
|
*/
|
|
363
363
|
showSequenceViewer?: boolean;
|
|
364
|
+
/**
|
|
365
|
+
* Height of the sequence panel, as any CSS length; a number is read as
|
|
366
|
+
* pixels, and a percentage is of the viewer's own height. The 3D view and the
|
|
367
|
+
* legend move up to make room for it. Ignored while `showSequenceViewer` is
|
|
368
|
+
* off.
|
|
369
|
+
*
|
|
370
|
+
* Left undefined, the panel takes `max(104px, 30%)` of the viewer, growing to
|
|
371
|
+
* `max(134px, 32%)` once the viewer is 880px wide or more. A height set here
|
|
372
|
+
* holds at every width.
|
|
373
|
+
*/
|
|
374
|
+
sequenceViewerHeight?: number | string;
|
|
364
375
|
/**
|
|
365
376
|
* Show the stats and color scale legend overlaid on the viewer.
|
|
366
377
|
* @default true
|
|
@@ -361,6 +361,17 @@ interface ProteinStructureViewerProps extends Omit<HTMLAttributes<HTMLDivElement
|
|
|
361
361
|
* @default true
|
|
362
362
|
*/
|
|
363
363
|
showSequenceViewer?: boolean;
|
|
364
|
+
/**
|
|
365
|
+
* Height of the sequence panel, as any CSS length; a number is read as
|
|
366
|
+
* pixels, and a percentage is of the viewer's own height. The 3D view and the
|
|
367
|
+
* legend move up to make room for it. Ignored while `showSequenceViewer` is
|
|
368
|
+
* off.
|
|
369
|
+
*
|
|
370
|
+
* Left undefined, the panel takes `max(104px, 30%)` of the viewer, growing to
|
|
371
|
+
* `max(134px, 32%)` once the viewer is 880px wide or more. A height set here
|
|
372
|
+
* holds at every width.
|
|
373
|
+
*/
|
|
374
|
+
sequenceViewerHeight?: number | string;
|
|
364
375
|
/**
|
|
365
376
|
* Show the stats and color scale legend overlaid on the viewer.
|
|
366
377
|
* @default true
|
|
@@ -64,7 +64,7 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
64
64
|
/**
|
|
65
65
|
* Height of the bar in pixels. Values below 1 are clamped to 1, so the bar
|
|
66
66
|
* cannot disappear.
|
|
67
|
-
* @default
|
|
67
|
+
* @default 8
|
|
68
68
|
*/
|
|
69
69
|
barHeight?: number;
|
|
70
70
|
/**
|
|
@@ -139,6 +139,12 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
139
139
|
* between it and the sum of the data is drawn as the Remaining segment.
|
|
140
140
|
*/
|
|
141
141
|
maxAmount?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Cumulative mode only. How many items are drawn as their own segment. The
|
|
144
|
+
* rest fold into the Remaining segment, whose legend item opens a menu
|
|
145
|
+
* listing them.
|
|
146
|
+
*/
|
|
147
|
+
maxItems?: number;
|
|
142
148
|
/**
|
|
143
149
|
* Name of the grey gap segment in cumulative mode.
|
|
144
150
|
* @default "Remaining"
|
|
@@ -64,7 +64,7 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
64
64
|
/**
|
|
65
65
|
* Height of the bar in pixels. Values below 1 are clamped to 1, so the bar
|
|
66
66
|
* cannot disappear.
|
|
67
|
-
* @default
|
|
67
|
+
* @default 8
|
|
68
68
|
*/
|
|
69
69
|
barHeight?: number;
|
|
70
70
|
/**
|
|
@@ -139,6 +139,12 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
139
139
|
* between it and the sum of the data is drawn as the Remaining segment.
|
|
140
140
|
*/
|
|
141
141
|
maxAmount?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Cumulative mode only. How many items are drawn as their own segment. The
|
|
144
|
+
* rest fold into the Remaining segment, whose legend item opens a menu
|
|
145
|
+
* listing them.
|
|
146
|
+
*/
|
|
147
|
+
maxItems?: number;
|
|
142
148
|
/**
|
|
143
149
|
* Name of the grey gap segment in cumulative mode.
|
|
144
150
|
* @default "Remaining"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@czi-sds/data-viz",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"types": "dist/index.cjs.d.ts",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"namespace-check": "tsc --p tsconfig-namespace-check.json"
|
|
122
122
|
},
|
|
123
123
|
"peerDependencies": {
|
|
124
|
-
"@czi-sds/components": "^
|
|
124
|
+
"@czi-sds/components": "^25.0.0",
|
|
125
125
|
"@emotion/styled": "^11.11.0",
|
|
126
126
|
"@mui/material": "^9.0.0",
|
|
127
127
|
"@phosphor-icons/react": "^2.1.10",
|
|
@@ -157,5 +157,5 @@
|
|
|
157
157
|
"echarts": "^6.0.0",
|
|
158
158
|
"lodash": "^4.17.21"
|
|
159
159
|
},
|
|
160
|
-
"gitHead": "
|
|
160
|
+
"gitHead": "d38c18e3aca773081cb5c0413dbfefb10da774b8"
|
|
161
161
|
}
|