@aics/vole-app 2.11.0 → 2.12.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.
@@ -23,7 +23,7 @@ import { Layout } from "antd";
23
23
  import { debounce, isEqual } from "lodash";
24
24
  import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
25
25
  import { Box3, Vector3 } from "three";
26
- import { AXIS_MARGIN_DEFAULT, CACHE_MAX_SIZE, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, getDefaultChannelColor, getDefaultViewerState, QUEUE_MAX_LOW_PRIORITY_SIZE, QUEUE_MAX_SIZE, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
26
+ import { AXIS_MARGIN_DEFAULT, CACHE_MAX_SIZE, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, DTYPE_RANGE, getDefaultChannelColor, getDefaultViewerState, QUEUE_MAX_LOW_PRIORITY_SIZE, QUEUE_MAX_SIZE, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
27
27
  import { ImageType, RenderMode, ViewMode } from "../../shared/enums";
28
28
  import { activeAxisMap } from "../../shared/types";
29
29
  import { colorArrayToFloats } from "../../shared/utils/colorRepresentations";
@@ -288,7 +288,9 @@ var App = function App(props) {
288
288
  controlPoints = _initializeLut.controlPoints;
289
289
  changeChannelSetting(channelIndex, {
290
290
  controlPoints: controlPoints,
291
- ramp: controlPointsToRamp(ramp)
291
+ ramp: controlPointsToRamp(ramp),
292
+ // set the default range of the transfer function editor to cover the full range of the data type
293
+ plotMax: DTYPE_RANGE[thisChannel.dtype].max
292
294
  });
293
295
  onResetChannel(channelIndex);
294
296
  } else {
@@ -301,8 +303,8 @@ var App = function App(props) {
301
303
  var oldRange = channelRangesRef.current[channelIndex];
302
304
  if (thisChannelsSettings.useControlPoints) {
303
305
  // control points were just automatically remapped - update in state
304
- // now manually remap ramp using the channel's old range
305
306
  var rampControlPoints = rampToControlPoints(thisChannelsSettings.ramp);
307
+ // now manually remap ramp using the channel's old range
306
308
  var remappedRampControlPoints = remapControlPointsForChannel(rampControlPoints, oldRange, thisChannel);
307
309
  changeChannelSetting(channelIndex, {
308
310
  ramp: controlPointsToRamp(remappedRampControlPoints),
@@ -572,12 +574,24 @@ var App = function App(props) {
572
574
  if (imageMetadata && metadataFormatter) {
573
575
  imageMetadata = metadataFormatter(imageMetadata);
574
576
  }
577
+ var sceneMeta;
578
+ if (Array.isArray(metadata)) {
579
+ // If metadata is an array, try to index it by scene
580
+ if (metadata.length >= numScenes) {
581
+ sceneMeta = metadata[viewerState.current.scene];
582
+ } else {
583
+ sceneMeta = metadata[0];
584
+ }
585
+ } else {
586
+ sceneMeta = metadata;
587
+ }
575
588
  if (imageMetadata && Object.keys(imageMetadata).length > 0) {
576
589
  return _objectSpread({
577
590
  Image: imageMetadata
578
- }, metadata);
591
+ }, sceneMeta);
579
592
  } else {
580
- return metadata || {};
593
+ var _sceneMeta;
594
+ return (_sceneMeta = sceneMeta) !== null && _sceneMeta !== void 0 ? _sceneMeta : {};
581
595
  }
582
596
  }, [props.metadata, props.metadataFormatter, image]);
583
597
 
@@ -29,23 +29,30 @@ var SliderRow = function SliderRow(_ref) {
29
29
  onStart = _ref.onStart,
30
30
  onEnd = _ref.onEnd;
31
31
  var isRange = vals.length > 1;
32
- // If slider is a range, handles represent slice *edges*: the range around only the max slice is [max-1, max], e.g.
33
- // If slider is not a range, handle represents 0-indexed slices: last slice is at max-1
34
- var inputMax = isRange ? max : max - 1;
32
+ // If slider is a range, handles represent slice *edges*: the range around only the 1st slice is [0, 1]; the range
33
+ // around only the last is [max-1, max].
34
+ // If slider is not a range, just work with slices, but don't 0-index: 1st slice is 1, last is max
35
+ var min = isRange ? 0 : 1;
36
+ var wrappedOnSlide = isRange ? onSlide : function (values) {
37
+ return onSlide === null || onSlide === void 0 ? void 0 : onSlide([values[0] - 1]);
38
+ };
39
+ var wrappedOnChange = isRange ? _onChange : function (values) {
40
+ return _onChange === null || _onChange === void 0 ? void 0 : _onChange([values[0] - 1]);
41
+ };
35
42
  return /*#__PURE__*/React.createElement("span", {
36
43
  className: "axis-slider-container"
37
44
  }, /*#__PURE__*/React.createElement("span", {
38
45
  className: "slider-name"
39
- }, label), inputMax === 0 ? /*#__PURE__*/React.createElement("i", null, "No values to adjust") : /*#__PURE__*/React.createElement("span", {
46
+ }, label), max === 0 ? /*#__PURE__*/React.createElement("i", null, "No values to adjust") : /*#__PURE__*/React.createElement("span", {
40
47
  className: "axis-slider"
41
48
  }, /*#__PURE__*/React.createElement(SmarterSlider, {
42
49
  className: isRange ? "" : "slider-single-handle",
43
50
  connect: true,
44
51
  range: {
45
- min: 0,
46
- max: inputMax
52
+ min: min,
53
+ max: max
47
54
  },
48
- start: vals,
55
+ start: isRange ? vals : [vals[0] + 1],
49
56
  step: 1,
50
57
  margin: 1,
51
58
  behaviour: "drag",
@@ -66,25 +73,27 @@ var SliderRow = function SliderRow(_ref) {
66
73
  to: Math.round,
67
74
  from: parseInt
68
75
  },
69
- onSlide: onSlide,
70
- onChange: _onChange,
76
+ onSlide: wrappedOnSlide,
77
+ onChange: wrappedOnChange,
71
78
  onStart: onStart,
72
79
  onEnd: onEnd
73
- })), inputMax > 0 && /*#__PURE__*/React.createElement("span", {
80
+ })), max > 0 && /*#__PURE__*/React.createElement("span", {
74
81
  className: "slider-values"
75
82
  }, /*#__PURE__*/React.createElement(NumericInput, {
76
- max: inputMax,
77
- value: valsReadout[0],
83
+ min: min,
84
+ max: max,
85
+ value: valsReadout[0] + (isRange ? 0 : 1),
78
86
  onChange: function onChange(value) {
79
- return _onChange === null || _onChange === void 0 ? void 0 : _onChange(isRange ? [value, vals[1]] : [value]);
87
+ return _onChange === null || _onChange === void 0 ? void 0 : _onChange(isRange ? [value, vals[1]] : [value - 1]);
80
88
  }
81
89
  }), isRange && /*#__PURE__*/React.createElement(React.Fragment, null, " , ", /*#__PURE__*/React.createElement(NumericInput, {
82
- max: inputMax,
90
+ min: min,
91
+ max: max,
83
92
  value: valsReadout[1],
84
93
  onChange: function onChange(value) {
85
94
  return _onChange === null || _onChange === void 0 ? void 0 : _onChange([vals[0], value]);
86
95
  }
87
- })), " / ", max - 1));
96
+ })), " / ", max));
88
97
  };
89
98
  /** Wrapper around `SliderRow` that adds a play button and accounts for the case where not all of an axis is loaded */
90
99
  var PlaySliderRow = function PlaySliderRow(props) {
@@ -93,7 +93,9 @@ var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
93
93
  colorizeEnabled = channelState.colorizeEnabled,
94
94
  colorizeAlpha = channelState.colorizeAlpha,
95
95
  useControlPoints = channelState.useControlPoints,
96
- ramp = channelState.ramp;
96
+ ramp = channelState.ramp,
97
+ lockPlotToDataRange = channelState.lockPlotToDataRange,
98
+ plotMax = channelState.plotMax;
97
99
  return /*#__PURE__*/React.createElement(TfEditor, {
98
100
  id: "TFEditor" + index,
99
101
  width: 418,
@@ -104,7 +106,9 @@ var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
104
106
  colorizeEnabled: colorizeEnabled,
105
107
  colorizeAlpha: colorizeAlpha,
106
108
  useControlPoints: useControlPoints,
107
- ramp: ramp
109
+ ramp: ramp,
110
+ lockPlotToDataRange: lockPlotToDataRange,
111
+ plotMax: plotMax
108
112
  });
109
113
  };
110
114
  var renderSurfaceControls = function renderSurfaceControls() {
@@ -16,6 +16,9 @@
16
16
  .load-error-alert .ant-btn-text{
17
17
  color:var(--color-text-link);
18
18
  }
19
+ .load-error-alert .ant-btn-text:hover{
20
+ color:#25affe !important;
21
+ }
19
22
  .load-error-alert .ant-alert-message div:first-child{
20
23
  color:var(--color-text-section);
21
24
  font-weight:600;
@@ -134,7 +134,7 @@ var theme = {
134
134
  }
135
135
  };
136
136
  /** Makes theme styling available to child components via CSS variables. */
137
- var CssProvider = styled.div(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n width: 100%;\n height: 100%;\n\n ", "\n\n h1, h2, h3, h4, p {\n font-family: var(--font-family);\n }\n\n h1 {\n color: var(--color-text-header);\n font-size: 28px;\n font-weight: 400;\n }\n\n h2 {\n color: var(--color-text-section);\n font-size: 19px;\n font-weight: 400;\n }\n\n h3 {\n color: var(--color-text-header);\n font-size: 16px;\n font-weight: 600;\n }\n\n h4 {\n color: var(--color-text-header);\n font-size: 14px;\n font-weight: 400;\n }\n\n p {\n color: var(--color-text-body);\n font-size: 14px;\n font-weight: 400;\n }\n\n a {\n color: var(--color-text-link);\n &:not(:focus-visible) {\n text-decoration: none;\n }\n\n &:focus-visible {\n text-decoration: underline;\n }\n }\n\n & *::selection {\n background-color: var(--color-text-selection-bg);\n color: var(--color-text-selection-text);\n }\n\n /** TODO: Go through these styles and see if we can use Ant ConfigProvider for them instead. */\n .ant-btn-link,\n .ant-btn-text,\n .ant-btn-primary,\n .ant-btn-icon-only {\n box-shadow: none;\n\n &:hover:not(:disabled),\n &:focus-visible:not(:disabled) {\n background-color: var(--color-button-primary-hover-bg);\n border-color: var(--color-button-primary-hover-bg);\n color: var(--color-button-primary-text);\n }\n\n &:active:not(:disabled) {\n background-color: var(--color-button-primary-hover-bg);\n border: 1px solid var(--color-button-primary-active-outline);\n color: var(--color-button-primary-text);\n }\n }\n\n /* Let us use buttons with type=\"text\" as purely semantic containers - don't bring any styling! */\n .ant-btn-text {\n width: unset;\n height: unset;\n padding: unset;\n\n &:hover:not(:disabled),\n &:focus-visible:not(:disabled) {\n background-color: unset;\n border-color: transparent;\n }\n }\n\n .ant-btn-icon-only:disabled {\n color: var(--color-button-icon-disabled-text);\n }\n\n .ant-btn-link:not(:disabled) {\n // Change from default blue link text\n color: var(--color-button-link-text);\n }\n\n /**\n * Tertiary style buttons. Grey outline by default, turns to light\n * purple outline on hover.\n */\n .ant-btn-default:not(.ant-btn-icon-only) {\n background-color: var(--color-button-tertiary-bg);\n color: var(--color-button-tertiary-text);\n border-color: var(--color-button-tertiary-outline);\n\n &:hover:not(:disabled),\n &:focus-visible:not(:disabled) {\n background-color: transparent;\n border-color: var(--color-button-tertiary-hover-bg);\n color: var(--color-button-tertiary-hover-text);\n }\n\n &:active:not(:disabled) {\n background-color: transparent;\n border-color: var(--color-button-tertiary-active-outline);\n color: var(--color-button-tertiary-active-text);\n }\n }\n\n // Overrides for checkbox styling\n & .ant-checkbox-input {\n &:hover,\n &:focus-visible {\n border: 1px solid white;\n }\n }\n\n & .ant-checkbox.ant-checkbox-checked {\n background-color: var(--color-checkbox-bg);\n }\n\n .ant-checkbox-inner {\n background-color: transparent;\n }\n\n & .ant-checkbox-indeterminate.checked .ant-checkbox-inner {\n background-color: var(--color-checkbox-bg);\n }\n\n // Add outlines to modals and dropdowns\n & .ant-select-dropdown,\n & .ant-dropdown-menu,\n & .ant-modal-content {\n border: 1px solid var(--color-modal-border);\n }\n\n // Force active/hovered text to be white instead of grey for better contrast\n & .ant-dropdown-menu-item-active {\n color: var(--color-menu-hover-text);\n }\n\n // Remove padding in dropdown menus and make items reactangular + flush with the edge\n & .ant-dropdown-menu,\n & .ant-select-dropdown {\n padding: 0;\n overflow: hidden;\n\n & .ant-dropdown-menu-item,\n & .ant-select-item {\n border-radius: 0;\n }\n }\n"])), function (_ref) {
137
+ var CssProvider = styled.div(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n width: 100%;\n height: 100%;\n\n ", "\n\n h1, h2, h3, h4, p {\n font-family: var(--font-family);\n }\n\n h1 {\n color: var(--color-text-header);\n font-size: 28px;\n font-weight: 400;\n }\n\n h2 {\n color: var(--color-text-section);\n font-size: 19px;\n font-weight: 400;\n }\n\n h3 {\n color: var(--color-text-header);\n font-size: 16px;\n font-weight: 600;\n }\n\n h4 {\n color: var(--color-text-header);\n font-size: 14px;\n font-weight: 400;\n }\n\n p {\n color: var(--color-text-body);\n font-size: 14px;\n font-weight: 400;\n }\n\n a {\n color: var(--color-text-link);\n }\n\n & *::selection {\n background-color: var(--color-text-selection-bg);\n color: var(--color-text-selection-text);\n }\n\n /** TODO: Go through these styles and see if we can use Ant ConfigProvider for them instead. */\n .ant-btn-link,\n .ant-btn-text,\n .ant-btn-primary,\n .ant-btn-icon-only {\n box-shadow: none;\n\n &:hover:not(:disabled),\n &:focus-visible:not(:disabled) {\n background-color: var(--color-button-primary-hover-bg);\n border-color: var(--color-button-primary-hover-bg);\n color: var(--color-button-primary-text);\n }\n\n &:active:not(:disabled) {\n background-color: var(--color-button-primary-hover-bg);\n border: 1px solid var(--color-button-primary-active-outline);\n color: var(--color-button-primary-text);\n }\n }\n\n /* Let us use buttons with type=\"text\" as purely semantic containers - don't bring any styling! */\n .ant-btn-text {\n width: unset;\n height: unset;\n padding: unset;\n\n &:hover:not(:disabled),\n &:focus-visible:not(:disabled) {\n background-color: unset;\n border-color: transparent;\n }\n }\n\n .ant-btn-icon-only:disabled {\n color: var(--color-button-icon-disabled-text);\n }\n\n .ant-btn-link:not(:disabled) {\n // Change from default blue link text\n color: var(--color-button-link-text);\n }\n\n /**\n * Tertiary style buttons. Grey outline by default, turns to light\n * purple outline on hover.\n */\n .ant-btn-default:not(.ant-btn-icon-only) {\n background-color: var(--color-button-tertiary-bg);\n color: var(--color-button-tertiary-text);\n border-color: var(--color-button-tertiary-outline);\n\n &:hover:not(:disabled),\n &:focus-visible:not(:disabled) {\n background-color: transparent;\n border-color: var(--color-button-tertiary-hover-bg);\n color: var(--color-button-tertiary-hover-text);\n }\n\n &:active:not(:disabled) {\n background-color: transparent;\n border-color: var(--color-button-tertiary-active-outline);\n color: var(--color-button-tertiary-active-text);\n }\n }\n\n // Overrides for checkbox styling\n & .ant-checkbox-input {\n &:hover,\n &:focus-visible {\n border: 1px solid white;\n }\n }\n\n & .ant-checkbox.ant-checkbox-checked {\n background-color: var(--color-checkbox-bg);\n }\n\n .ant-checkbox-inner {\n background-color: transparent !important;\n }\n\n // Add outlines to modals and dropdowns\n & .ant-select-dropdown,\n & .ant-dropdown-menu,\n & .ant-modal-content {\n border: 1px solid var(--color-modal-border);\n }\n\n // Force active/hovered text to be white instead of grey for better contrast\n & .ant-dropdown-menu-item-active {\n color: var(--color-menu-hover-text);\n }\n\n // Remove padding in dropdown menus and make items reactangular + flush with the edge\n & .ant-dropdown-menu,\n & .ant-select-dropdown {\n padding: 0;\n overflow: hidden;\n\n & .ant-dropdown-menu-item,\n & .ant-select-item {\n border-radius: 0;\n }\n }\n"])), function (_ref) {
138
138
  var $theme = _ref.$theme;
139
139
  return css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n /* Component and color variables. */\n // TODO: Fix inconsistent use of border vs. outline\n // TODO: Remove variables that aren't used in other CSS files. These should be set directly\n // from the $theme object to reduce unnecessary variables.\n --color-text-link: ", ";\n --color-text-header: ", ";\n --color-text-section: ", ";\n --color-text-body: ", ";\n --color-text-error: ", ";\n\n --color-text-selection-bg: ", ";\n --color-text-selection-text: ", ";\n\n --color-header-title: ", ";\n --color-header-hover-title: ", ";\n --color-header-bg: ", ";\n --color-header-border: ", ";\n\n --color-button-primary-bg: ", ";\n --color-button-primary-text: ", ";\n --color-button-primary-hover-bg: ", ";\n --color-button-primary-active-bg: ", ";\n --color-button-primary-active-outline: ", ";\n --color-button-primary-disabled-bg: ", ";\n\n --color-button-link-text: ", ";\n\n --color-button-secondary-bg: ", ";\n --color-button-secondary-text: ", ";\n --color-button-secondary-outline: ", ";\n\n --color-button-tertiary-bg: transparent;\n --color-button-tertiary-text: ", ";\n --color-button-tertiary-outline: ", ";\n --color-button-tertiary-hover-outline: ", ";\n --color-button-tertiary-hover-text: ", ";\n --color-button-tertiary-active-outline: ", ";\n --color-button-tertiary-active-text: ", ";\n\n --color-button-icon-disabled-text: ", ";\n --color-button-icon-disabled-text: ", ";\n --color-button-icon-activated-text: ", ";\n --color-button-icon-activated-bg: ", ";\n --color-button-icon-activated-outline: ", ";\n\n --color-toolbar-button-bg: ", ";\n\n --color-controlpanel-bg: ", ";\n --color-controlpanel-border: ", ";\n --color-controlpanel-section-text: ", ";\n --color-controlpanel-text: ", ";\n --color-controlpanel-section-bg: ", ";\n --color-controlpanel-drawer-bg: ", ";\n --color-controlpanel-ramp-slider: ", ";\n\n --color-landingpage-bg: ", ";\n --color-landingpage-bg-alt: ", ";\n --color-landingpage-text: ", ";\n --color-landingpage-banner-highlight-bg: ", ";\n\n --color-statusflag-border: ", ";\n --color-statusflag-text: ", ";\n\n --color-layout-dividers: ", ";\n\n --color-modal-border: ", ";\n\n --color-menu-hover-text: ", ";\n --color-menu-selected-text: ", ";\n\n --color-checkbox-bg: ", ";\n\n --font-family: ", ";\n "])), $theme.colors.text.link, $theme.colors.text.header, $theme.colors.text.section, $theme.colors.text.body, $theme.colors.text.error, $theme.colors.text.selectionBg, $theme.colors.text.selectionText, $theme.colors.header.title, $theme.colors.header.hoverTitle, $theme.colors.header.bg, $theme.colors.header.border, $theme.colors.button.primary.bg, $theme.colors.button.primary.text, $theme.colors.button.primary.hoverBg, $theme.colors.button.primary.hoverBg, $theme.colors.button.primary.activeOutline, $theme.colors.button.primary.disabledBg, $theme.colors.button.link.text, $theme.colors.button.secondary.bg, $theme.colors.button.secondary.text, $theme.colors.button.secondary.outline, $theme.colors.button.tertiary.text, $theme.colors.button.tertiary.outline, $theme.colors.button.tertiary.hoverOutline, $theme.colors.button.tertiary.hoverText, $theme.colors.button.tertiary.activeOutline, $theme.colors.button.tertiary.hoverText, $theme.colors.button.tertiary.disabledText, $theme.colors.button.tertiary.disabledText, $theme.colors.button.tertiary.activatedText, $theme.colors.button.tertiary.activatedBg, $theme.colors.button.tertiary.activatedOutline, $theme.colors.toolbar.buttonBg, $theme.colors.controlPanel.bg, $theme.colors.controlPanel.border, $theme.colors.controlPanel.sectionText, $theme.colors.controlPanel.text, $theme.colors.controlPanel.sectionBg, $theme.colors.controlPanel.drawerBg, $theme.colors.controlPanel.rampSlider, $theme.colors.landingPage.bg, $theme.colors.landingPage.bgAlt, $theme.colors.landingPage.text, $theme.colors.landingPage.bannerBg, $theme.colors.statusFlag.border, $theme.colors.statusFlag.text, $theme.colors.layout.dividers, $theme.colors.modal.border, $theme.colors.menu.hoverText, $theme.colors.menu.selectedText, $theme.colors.checkbox.bg, $theme.fonts.family);
140
140
  });
@@ -1,10 +1,10 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
2
  function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
5
3
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
6
4
  function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
7
5
  function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
6
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
7
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
8
8
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
9
9
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
10
10
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
@@ -20,7 +20,7 @@ import * as d3 from "d3";
20
20
  import "nouislider/distribute/nouislider.css";
21
21
  import React, { useCallback, useMemo, useRef, useState } from "react";
22
22
  import { SketchPicker } from "react-color";
23
- import { LUT_MAX_PERCENTILE, LUT_MIN_PERCENTILE, TFEDITOR_DEFAULT_COLOR } from "../../shared/constants";
23
+ import { DTYPE_RANGE, LUT_MAX_PERCENTILE, LUT_MIN_PERCENTILE, TFEDITOR_DEFAULT_COLOR } from "../../shared/constants";
24
24
  import { colorArrayToObject, colorArrayToString, colorObjectToArray } from "../../shared/utils/colorRepresentations";
25
25
  import { controlPointsToRamp, rampToControlPoints } from "../../shared/utils/controlPointsToLut";
26
26
  import { useRefWithSetter } from "../../shared/utils/hooks";
@@ -31,8 +31,17 @@ import "./styles.css";
31
31
  var TFEDITOR_COLOR_PICKER_MARGIN_X_PX = 2;
32
32
  /** If a control point is within this distance of the bottom of the screen, open the color picker upward */
33
33
  var TFEDITOR_COLOR_PICKER_OPEN_UPWARD_MARGIN_PX = 310;
34
- var TFEDITOR_GRADIENT_MAX_OPACITY = 0.9;
34
+ var TFEDITOR_GRADIENT_MAX_OPACITY = 0.75;
35
35
  var TFEDITOR_NUM_TICKS = 4;
36
+ /**
37
+ * If the first or last "round" tick mark is within this ratio of the end of the x axis, remove it to get it out of the
38
+ * way of the tick mark right at the end.
39
+ *
40
+ * For instance, if the x range is [0, 255], the last tick mark d3 generates will likely be at 250. That should be
41
+ * removed to get it out of the way of the tick mark at 255! But if the range is [0, 390], it may be that the last tick
42
+ * mark is at 300. It would make no sense to remove that tick mark to make space for one at 390.
43
+ */
44
+ var TFEDITOR_END_TICK_MARGIN = 0.1;
36
45
  var TFEDITOR_MARGINS = {
37
46
  top: 18,
38
47
  right: 20,
@@ -112,6 +121,82 @@ function controlPointToAbsolute(cp, channel) {
112
121
  return u8ToAbsolute(cp.x, channel);
113
122
  }
114
123
 
124
+ /** For when all control points are outside the plot's range: just fill the plot with the settings from 1 point */
125
+ var coverRangeWithPoint = function coverRangeWithPoint(point, plotMin, plotMax) {
126
+ return [_objectSpread(_objectSpread({}, point), {}, {
127
+ x: plotMin
128
+ }), _objectSpread(_objectSpread({}, point), {}, {
129
+ x: plotMax
130
+ })];
131
+ };
132
+
133
+ /** For when some control points are outside the plot's range: create an intermediate point on the edge of the range */
134
+ var createPointOnRangeBoundary = function createPointOnRangeBoundary(outOfRangePt, inRangePt, x) {
135
+ var rangeRatio = (x - outOfRangePt.x) / (inRangePt.x - outOfRangePt.x);
136
+ var opacity = outOfRangePt.opacity + (inRangePt.opacity - outOfRangePt.opacity) * rangeRatio;
137
+ var color = outOfRangePt.color.map(function (c, i) {
138
+ return c + (inRangePt.color[i] - c) * rangeRatio;
139
+ });
140
+ return {
141
+ x: x,
142
+ opacity: opacity,
143
+ color: color
144
+ };
145
+ };
146
+
147
+ /**
148
+ * Ensures the list of `controlPoints` exactly covers the range from `plotMin` to `plotMax` by removing any
149
+ * out-of-range points and adding new points right at the edges of the range.
150
+ */
151
+ var fitControlPointsToRange = function fitControlPointsToRange(controlPoints, plotMin, plotMax) {
152
+ var points = controlPoints.slice();
153
+ var firstPoint = points[0];
154
+ var lastPoint = points[points.length - 1];
155
+
156
+ // If all control points are outside the range, just fill the range with the first or last point.
157
+ if (lastPoint.x < plotMin) {
158
+ return coverRangeWithPoint(lastPoint, plotMin, plotMax);
159
+ }
160
+ if (firstPoint.x > plotMax) {
161
+ return coverRangeWithPoint(firstPoint, plotMin, plotMax);
162
+ }
163
+ if (firstPoint.x > plotMin) {
164
+ // If the control points don't go all the way to the min, add a new point at the min.
165
+ points.unshift(_objectSpread(_objectSpread({}, firstPoint), {}, {
166
+ x: plotMin
167
+ }));
168
+ } else {
169
+ // If some control points are out of range, remove those points...
170
+ var outOfRangePoint = undefined;
171
+ while (points[0].x < plotMin && points.length > 1) {
172
+ outOfRangePoint = points.shift();
173
+ }
174
+
175
+ // ...and create a new point at the edge of the range.
176
+ if (outOfRangePoint !== undefined) {
177
+ points.unshift(createPointOnRangeBoundary(outOfRangePoint, points[0], plotMin));
178
+ }
179
+ }
180
+ if (lastPoint.x < plotMax) {
181
+ // If the control points don't go all the way to the max, add a new point at the max.
182
+ points.push(_objectSpread(_objectSpread({}, lastPoint), {}, {
183
+ x: plotMax
184
+ }));
185
+ } else {
186
+ // If some control points are out of range, remove those points...
187
+ var _outOfRangePoint = undefined;
188
+ while (points[points.length - 1].x > plotMax && points.length > 1) {
189
+ _outOfRangePoint = points.pop();
190
+ }
191
+
192
+ // ...and create a new point at the edge of the range.
193
+ if (_outOfRangePoint !== undefined) {
194
+ points.push(createPointOnRangeBoundary(_outOfRangePoint, points[points.length - 1], plotMax));
195
+ }
196
+ }
197
+ return points;
198
+ };
199
+
115
200
  /** Defines an SVG gradient with id `id` based on the provided `controlPoints` */
116
201
  var ControlPointGradientDef = function ControlPointGradientDef(_ref) {
117
202
  var controlPoints = _ref.controlPoints,
@@ -206,11 +291,25 @@ var TfEditor = function TfEditor(props) {
206
291
  var lastColorRef = useRef(TFEDITOR_DEFAULT_COLOR);
207
292
  var svgRef = useRef(null); // need access to SVG element to measure mouse position
208
293
 
294
+ var _props$channelData = props.channelData,
295
+ rawMin = _props$channelData.rawMin,
296
+ rawMax = _props$channelData.rawMax,
297
+ dtype = _props$channelData.dtype;
298
+ var typeRange = DTYPE_RANGE[dtype];
299
+
209
300
  // d3 scales define the mapping between data and screen space (and do the heavy lifting of generating plot axes)
210
301
  /** `xScale` is in raw intensity range, not U8 range. We use `u8ToAbsolute` and `absoluteToU8` to translate to U8. */
211
- var xScale = useMemo(function () {
212
- return d3.scaleLinear().domain([props.channelData.rawMin, props.channelData.rawMax]).range([0, innerWidth]);
213
- }, [innerWidth, props.channelData.rawMin, props.channelData.rawMax]);
302
+ var _useMemo = useMemo(function () {
303
+ var domain = props.lockPlotToDataRange ? [rawMin, rawMax] : [typeRange.min, props.plotMax];
304
+ var scale = d3.scaleLinear().domain(domain).range([0, innerWidth]);
305
+ var plotMinU8 = absoluteToU8(domain[0], props.channelData);
306
+ var plotMaxU8 = absoluteToU8(domain[1], props.channelData);
307
+ return [scale, plotMinU8, plotMaxU8];
308
+ }, [innerWidth, rawMin, rawMax, typeRange, props.lockPlotToDataRange, props.plotMax]),
309
+ _useMemo2 = _slicedToArray(_useMemo, 3),
310
+ xScale = _useMemo2[0],
311
+ plotMinU8 = _useMemo2[1],
312
+ plotMaxU8 = _useMemo2[2];
214
313
  var yScale = useMemo(function () {
215
314
  return d3.scaleLinear().domain([0, 1]).range([innerHeight, 0]);
216
315
  }, [innerHeight]);
@@ -347,8 +446,9 @@ var TfEditor = function TfEditor(props) {
347
446
  }
348
447
  };
349
448
  var controlPointsToRender = useMemo(function () {
350
- return props.useControlPoints ? props.controlPoints : rampToControlPoints(props.ramp);
351
- }, [props.controlPoints, props.ramp, props.useControlPoints]);
449
+ var points = props.useControlPoints ? props.controlPoints.slice() : rampToControlPoints(props.ramp);
450
+ return fitControlPointsToRange(points, plotMinU8, plotMaxU8);
451
+ }, [props.controlPoints, props.ramp, props.useControlPoints, plotMinU8, plotMaxU8]);
352
452
 
353
453
  /** d3-generated svg data string representing both the line between points and the region filled with gradient */
354
454
  var areaPath = useMemo(function () {
@@ -372,8 +472,27 @@ var TfEditor = function TfEditor(props) {
372
472
  // identity changes (i.e. whenever their dependencies change).
373
473
 
374
474
  var xAxisRef = useCallback(function (el) {
475
+ // generate tick marks
375
476
  var ticks = xScale.ticks(TFEDITOR_NUM_TICKS);
376
- ticks[ticks.length - 1] = xScale.domain()[1];
477
+
478
+ // make sure we have sensible tick marks right at the min and max of the x axis
479
+ var _xScale$domain = xScale.domain(),
480
+ _xScale$domain2 = _slicedToArray(_xScale$domain, 2),
481
+ min = _xScale$domain2[0],
482
+ max = _xScale$domain2[1];
483
+ var domain = max - min;
484
+ if ((ticks[0] - min) / domain < TFEDITOR_END_TICK_MARGIN) {
485
+ ticks[0] = min;
486
+ } else {
487
+ ticks.unshift(min);
488
+ }
489
+ if ((ticks[ticks.length - 1] - min) / domain > 1 - TFEDITOR_END_TICK_MARGIN) {
490
+ ticks[ticks.length - 1] = max;
491
+ } else {
492
+ ticks.push(max);
493
+ }
494
+
495
+ // now make the axis!
377
496
  d3.select(el).call(d3.axisBottom(xScale).tickValues(ticks).tickPadding(props.useControlPoints ? 3 : 10) // get tick labels out of the way of sliders in "basic" mode
378
497
  );
379
498
  }, [xScale, props.useControlPoints]);
@@ -384,26 +503,30 @@ var TfEditor = function TfEditor(props) {
384
503
  if (el === null) {
385
504
  return;
386
505
  }
387
- if (props.channelData.histogram.getNumBins() < 1) {
506
+ var numBins = props.channelData.histogram.getNumBins();
507
+ if (numBins < 1) {
388
508
  return;
389
509
  }
390
510
  var _getHistogramBinLengt = getHistogramBinLengths(props.channelData.histogram),
391
511
  binLengths = _getHistogramBinLengt.binLengths,
392
512
  max = _getHistogramBinLengt.max;
393
- var barWidth = innerWidth / props.channelData.histogram.getNumBins();
513
+ var start = Math.max(0, Math.ceil(plotMinU8));
514
+ var end = Math.min(numBins, Math.floor(plotMaxU8));
515
+ var binLengthsToRender = binLengths.slice(start, end);
516
+ var barWidth = innerWidth / (plotMaxU8 - plotMinU8);
394
517
  var binScale = d3.scaleLog().domain([0.1, max]).range([innerHeight, 0]).base(2).clamp(true);
395
518
  d3.select(el).selectAll(".bar") // select all the bars of the histogram
396
- .data(binLengths) // bind the histogram bins to this selection
519
+ .data(binLengthsToRender) // bind the histogram bins to this selection
397
520
  .join("rect") // ensure we have exactly as many bound `rect` elements in the DOM as we have histogram bins
398
521
  .attr("class", "bar").attr("width", barWidth).attr("x", function (_len, idx) {
399
- return xScale(u8ToAbsolute(idx, props.channelData));
522
+ return xScale(u8ToAbsolute(idx + start, props.channelData));
400
523
  }) // set position and height from data
401
524
  .attr("y", function (len) {
402
525
  return binScale(len);
403
526
  }).attr("height", function (len) {
404
527
  return innerHeight - binScale(len);
405
528
  });
406
- }, [props.channelData.histogram, innerWidth, innerHeight]);
529
+ }, [xScale, props.channelData, props.channelData.histogram, innerWidth, innerHeight, plotMinU8, plotMaxU8]);
407
530
  var applyTFGenerator = useCallback(function (generator) {
408
531
  setSelectedPointIdx(null);
409
532
  lastColorRef.current = TFEDITOR_DEFAULT_COLOR;
@@ -430,7 +553,10 @@ var TfEditor = function TfEditor(props) {
430
553
  };
431
554
 
432
555
  // create one svg circle element for each control point
433
- var controlPointCircles = props.useControlPoints ? props.controlPoints.map(function (cp, i) {
556
+ var controlPointCircles = props.useControlPoints ? props.controlPoints.filter(function (cp) {
557
+ return plotMinU8 <= cp.x && cp.x <= plotMaxU8;
558
+ }) // filter out-of-range points
559
+ .map(function (cp, i) {
434
560
  return /*#__PURE__*/React.createElement("circle", {
435
561
  key: i,
436
562
  className: i === selectedPointIdx ? "selected" : "",
@@ -463,7 +589,29 @@ var TfEditor = function TfEditor(props) {
463
589
  style: {
464
590
  marginLeft: "auto"
465
591
  }
466
- }, "Advanced")), colorPickerPosition !== null && /*#__PURE__*/React.createElement("div", {
592
+ }, "Advanced")), !props.useControlPoints && /*#__PURE__*/React.createElement("div", {
593
+ className: "tf-editor-control-row ramp-row"
594
+ }, "Ramp min/max", /*#__PURE__*/React.createElement(InputNumber, {
595
+ value: u8ToAbsolute(props.ramp[0], props.channelData),
596
+ onChange: function onChange(v) {
597
+ return v !== null && setRamp([absoluteToU8(v, props.channelData), props.ramp[1]]);
598
+ },
599
+ formatter: numberFormatter,
600
+ min: typeRange.min,
601
+ max: Math.min(u8ToAbsolute(props.ramp[1], props.channelData), typeRange.max),
602
+ size: "small",
603
+ controls: false
604
+ }), /*#__PURE__*/React.createElement(InputNumber, {
605
+ value: u8ToAbsolute(props.ramp[1], props.channelData),
606
+ onChange: function onChange(v) {
607
+ return v !== null && setRamp([props.ramp[0], absoluteToU8(v, props.channelData)]);
608
+ },
609
+ formatter: numberFormatter,
610
+ min: Math.max(typeRange.min, u8ToAbsolute(props.ramp[0], props.channelData)),
611
+ max: typeRange.max,
612
+ size: "small",
613
+ controls: false
614
+ })), colorPickerPosition !== null && /*#__PURE__*/React.createElement("div", {
467
615
  className: "tf-editor-popover"
468
616
  }, /*#__PURE__*/React.createElement("div", {
469
617
  className: "tf-editor-cover",
@@ -504,7 +652,7 @@ var TfEditor = function TfEditor(props) {
504
652
  className: "axis"
505
653
  }), controlPointCircles, !props.useControlPoints && /*#__PURE__*/React.createElement("g", {
506
654
  className: "ramp-sliders"
507
- }, /*#__PURE__*/React.createElement("g", {
655
+ }, plotMinU8 <= props.ramp[0] && props.ramp[0] <= plotMaxU8 && /*#__PURE__*/React.createElement("g", {
508
656
  transform: "translate(".concat(xScale(u8ToAbsolute(props.ramp[0], props.channelData)), ")")
509
657
  }, /*#__PURE__*/React.createElement("line", {
510
658
  y1: innerHeight,
@@ -523,7 +671,7 @@ var TfEditor = function TfEditor(props) {
523
671
  onPointerDown: function onPointerDown() {
524
672
  return setDraggedPointIdx(TfEditorRampSliderHandle.Min);
525
673
  }
526
- })), /*#__PURE__*/React.createElement("g", {
674
+ })), plotMinU8 <= props.ramp[1] && props.ramp[1] <= plotMaxU8 && /*#__PURE__*/React.createElement("g", {
527
675
  transform: "translate(".concat(xScale(u8ToAbsolute(props.ramp[1], props.channelData)), ")")
528
676
  }, /*#__PURE__*/React.createElement("line", {
529
677
  y1: innerHeight,
@@ -541,27 +689,27 @@ var TfEditor = function TfEditor(props) {
541
689
  onPointerDown: function onPointerDown() {
542
690
  return setDraggedPointIdx(TfEditorRampSliderHandle.Max);
543
691
  }
544
- }))))), !props.useControlPoints && /*#__PURE__*/React.createElement("div", {
545
- className: "tf-editor-numeric-input-row"
546
- }, /*#__PURE__*/React.createElement("span", null, "Min", " ", /*#__PURE__*/React.createElement(InputNumber, {
547
- value: u8ToAbsolute(props.ramp[0], props.channelData),
548
- onChange: function onChange(v) {
549
- return v !== null && setRamp([absoluteToU8(v, props.channelData), props.ramp[1]]);
550
- },
551
- formatter: numberFormatter,
552
- min: 0,
553
- max: Math.min(u8ToAbsolute(props.ramp[1], props.channelData), props.channelData.rawMax),
554
- size: "small"
555
- })), /*#__PURE__*/React.createElement("span", null, "Max", " ", /*#__PURE__*/React.createElement(InputNumber, {
556
- value: u8ToAbsolute(props.ramp[1], props.channelData),
692
+ }))))), /*#__PURE__*/React.createElement("div", {
693
+ className: "tf-editor-control-row plot-range-row"
694
+ }, /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement(Checkbox, {
695
+ checked: props.lockPlotToDataRange,
696
+ onChange: function onChange(e) {
697
+ return changeChannelSetting({
698
+ lockPlotToDataRange: e.target.checked
699
+ });
700
+ }
701
+ }, "Lock to data range")), !props.lockPlotToDataRange && /*#__PURE__*/React.createElement("span", null, "Plot max", " ", /*#__PURE__*/React.createElement(InputNumber, {
702
+ value: props.plotMax,
557
703
  onChange: function onChange(v) {
558
- return v !== null && setRamp([props.ramp[0], absoluteToU8(v, props.channelData)]);
704
+ return v !== null && changeChannelSetting({
705
+ plotMax: v
706
+ });
559
707
  },
560
708
  formatter: numberFormatter,
561
- min: Math.max(0, u8ToAbsolute(props.ramp[0], props.channelData)),
562
- max: props.channelData.rawMax,
709
+ min: typeRange.min,
710
+ max: typeRange.max,
563
711
  size: "small",
564
- width: 45
712
+ controls: false
565
713
  }))), /*#__PURE__*/React.createElement(SliderRow, {
566
714
  label: /*#__PURE__*/React.createElement(Checkbox, {
567
715
  checked: props.colorizeEnabled,
@@ -20,19 +20,32 @@
20
20
  bottom:0;
21
21
  }
22
22
 
23
- .channel-row .tf-editor-numeric-input-row{
23
+ .channel-row .tf-editor-control-row{
24
24
  display:flex;
25
- align-items:center;
26
- justify-content:space-between;
27
- margin-bottom:20px;
28
25
  color:var(--color-controlpanel-text);
26
+ align-items:center;
27
+ min-height:24px;
29
28
  }
30
29
 
31
- .channel-row .tf-editor-numeric-input-row .ant-input-number{
30
+ .channel-row .tf-editor-control-row.ramp-row{
31
+ gap:6px;
32
+ margin-top:15px;
33
+ }
34
+
35
+ .channel-row .tf-editor-control-row.plot-range-row{
36
+ justify-content:space-between;
37
+ margin-bottom:20px;
38
+ }
39
+
40
+ .channel-row .tf-editor-control-row .ant-input-number{
32
41
  border-color:var(--color-button-tertiary-outline);
33
- width:60px;
42
+ width:72px;
34
43
  }
35
44
 
45
+ .channel-row .tf-editor-control-row .ant-input-number input{
46
+ text-align:right;
47
+ }
48
+
36
49
  .channel-row .tick text{
37
50
  fill:var(--color-controlpanel-text);
38
51
  user-select:none;
@@ -58,7 +71,7 @@
58
71
 
59
72
  .channel-row .line{
60
73
  stroke:#aeacae;
61
- stroke-width:1.5px;
74
+ stroke-width:1px;
62
75
  }
63
76
 
64
77
  .channel-row .ramp-sliders path{
@@ -31,6 +31,44 @@ export var TFEDITOR_MAX_BIN = 255;
31
31
  export var CACHE_MAX_SIZE = 1000000000;
32
32
  export var QUEUE_MAX_SIZE = 10;
33
33
  export var QUEUE_MAX_LOW_PRIORITY_SIZE = 4;
34
+
35
+ /** Maps channel data types to their minimum and maximum values */
36
+ export var DTYPE_RANGE = {
37
+ int8: {
38
+ min: -Math.pow(2, 7),
39
+ max: Math.pow(2, 7) - 1
40
+ },
41
+ int16: {
42
+ min: -Math.pow(2, 15),
43
+ max: Math.pow(2, 15) - 1
44
+ },
45
+ int32: {
46
+ min: -Math.pow(2, 31),
47
+ max: Math.pow(2, 31) - 1
48
+ },
49
+ uint8: {
50
+ min: 0,
51
+ max: Math.pow(2, 8) - 1
52
+ },
53
+ uint16: {
54
+ min: 0,
55
+ max: Math.pow(2, 16) - 1
56
+ },
57
+ uint32: {
58
+ min: 0,
59
+ max: Math.pow(2, 32) - 1
60
+ },
61
+ // These are obviously not the actual min and max representable values for floats, but the actual ones (`-Infinity`
62
+ // and `Infinity`) would give us nonsense. These may also produce nonsense, but these types should be rare anyways.
63
+ float32: {
64
+ min: 0,
65
+ max: Math.pow(2, 8) - 1
66
+ },
67
+ float64: {
68
+ min: 0,
69
+ max: Math.pow(2, 8) - 1
70
+ }
71
+ };
34
72
  export var PRESET_COLORS_1 = [[190, 68, 171], [189, 211, 75], [61, 155, 169], [128, 128, 128], [255, 255, 255], [239, 27, 45], [238, 77, 245], [96, 255, 255]];
35
73
  export var PRESET_COLORS_2 = [[128, 0, 0], [0, 128, 0], [0, 0, 128], [32, 32, 32], [255, 255, 0], [255, 0, 255], [0, 255, 0], [0, 0, 255]];
36
74
  export var PRESET_COLORS_3 = [[128, 0, 128], [128, 128, 128], [0, 128, 128], [128, 128, 0], [255, 255, 255], [255, 0, 0], [255, 0, 255], [0, 255, 255]];
@@ -159,6 +197,8 @@ export var getDefaultChannelState = function getDefaultChannelState() {
159
197
  x: 255,
160
198
  opacity: 1,
161
199
  color: [255, 255, 255]
162
- }]
200
+ }],
201
+ lockPlotToDataRange: true,
202
+ plotMax: TFEDITOR_MAX_BIN
163
203
  };
164
204
  };
@@ -64,6 +64,8 @@ export function initializeOneChannelSetting(channelName, index, defaultColor, vi
64
64
  color: (_colorHexToArray = colorHexToArray((_initSettings$color = initSettings.color) !== null && _initSettings$color !== void 0 ? _initSettings$color : "")) !== null && _colorHexToArray !== void 0 ? _colorHexToArray : defaultColor,
65
65
  useControlPoints: (_initSettings$control = initSettings.controlPointsEnabled) !== null && _initSettings$control !== void 0 ? _initSettings$control : defaultChannelState.useControlPoints,
66
66
  controlPoints: (_initSettings$control2 = initSettings.controlPoints) !== null && _initSettings$control2 !== void 0 ? _initSettings$control2 : defaultChannelState.controlPoints,
67
- ramp: (_initSettings$ramp = initSettings.ramp) !== null && _initSettings$ramp !== void 0 ? _initSettings$ramp : defaultChannelState.ramp
67
+ ramp: (_initSettings$ramp = initSettings.ramp) !== null && _initSettings$ramp !== void 0 ? _initSettings$ramp : defaultChannelState.ramp,
68
+ lockPlotToDataRange: defaultChannelState.lockPlotToDataRange,
69
+ plotMax: defaultChannelState.plotMax
68
70
  };
69
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aics/vole-app",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "description": "web view of cell volume images",
5
5
  "repository": "github:allen-cell-animated/vole-app",
6
6
  "main": "es/index.js",
@@ -41,7 +41,7 @@
41
41
  "author": "Megan Riel-Mehan",
42
42
  "license": "ISC",
43
43
  "dependencies": {
44
- "@aics/vole-core": "^3.12.4",
44
+ "@aics/vole-core": "^3.15.2",
45
45
  "@ant-design/icons": "^5.2.5",
46
46
  "@fortawesome/fontawesome-svg-core": "^6.5.2",
47
47
  "@fortawesome/free-solid-svg-icons": "^6.5.2",
@@ -37,7 +37,7 @@ export interface AppProps {
37
37
  translation: [number, number, number];
38
38
  rotation: [number, number, number];
39
39
  };
40
- metadata?: MetadataRecord;
40
+ metadata?: MetadataRecord | MetadataRecord[];
41
41
  view3dRef?: MutableRefObject<View3d | null>;
42
42
  metadataFormatter?: (metadata: MetadataRecord) => MetadataRecord;
43
43
  onControlPanelToggle?: (collapsed: boolean) => void;
@@ -14,6 +14,8 @@ type TfEditorProps = {
14
14
  useControlPoints: boolean;
15
15
  controlPoints: ControlPoint[];
16
16
  ramp: [number, number];
17
+ lockPlotToDataRange: boolean;
18
+ plotMax: number;
17
19
  };
18
20
  declare const TfEditor: React.FC<TfEditorProps>;
19
21
  export default TfEditor;
@@ -58,6 +58,8 @@ export interface ChannelState {
58
58
  ramp: [number, number];
59
59
  useControlPoints: boolean;
60
60
  controlPoints: ControlPoint[];
61
+ lockPlotToDataRange: boolean;
62
+ plotMax: number;
61
63
  }
62
64
  export type ChannelStateKey = keyof ChannelState;
63
65
  export type ChannelSettingUpdater = <K extends ChannelStateKey>(index: number | number[], value: Partial<Record<K, ChannelState[K]>>) => void;
@@ -1,4 +1,4 @@
1
- import { CameraState } from "@aics/vole-core";
1
+ import type { CameraState, Channel } from "@aics/vole-core";
2
2
  import { ChannelState, ViewerState } from "../components/ViewerStateProvider/types";
3
3
  import { ViewMode } from "./enums";
4
4
  import { ColorArray } from "./utils/colorRepresentations";
@@ -10,6 +10,13 @@ export declare const TFEDITOR_MAX_BIN = 255;
10
10
  export declare const CACHE_MAX_SIZE = 1000000000;
11
11
  export declare const QUEUE_MAX_SIZE = 10;
12
12
  export declare const QUEUE_MAX_LOW_PRIORITY_SIZE = 4;
13
+ /** Maps channel data types to their minimum and maximum values */
14
+ export declare const DTYPE_RANGE: {
15
+ [T in Channel["dtype"]]: {
16
+ min: number;
17
+ max: number;
18
+ };
19
+ };
13
20
  export declare const PRESET_COLORS_1: ColorArray[];
14
21
  export declare const PRESET_COLORS_2: ColorArray[];
15
22
  export declare const PRESET_COLORS_3: ColorArray[];
@@ -9,7 +9,7 @@ export type IsosurfaceFormat = "GLTF" | "STL";
9
9
  export type Styles = {
10
10
  [key: string]: CSSProperties;
11
11
  };
12
- export type MetadataEntry = string | number | boolean | MetadataRecord | null | undefined;
12
+ export type MetadataEntry = string | number | boolean | MetadataRecord | MetadataEntry[] | null | undefined;
13
13
  export type MetadataRecord = {
14
14
  [key: string]: MetadataEntry;
15
- } | MetadataEntry[];
15
+ };