@aics/vole-app 2.11.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.
Files changed (135) hide show
  1. package/LICENSE +33 -0
  2. package/LICENSE.txt +26 -0
  3. package/README.md +45 -0
  4. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-500.eot +0 -0
  5. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-500.js +2337 -0
  6. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-500.svg +604 -0
  7. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-500.ttf +0 -0
  8. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-500.woff +0 -0
  9. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-500.woff2 +0 -0
  10. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-700.eot +0 -0
  11. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-700.js +2336 -0
  12. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-700.svg +604 -0
  13. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-700.ttf +0 -0
  14. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-700.woff +0 -0
  15. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-700.woff2 +0 -0
  16. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-regular.eot +0 -0
  17. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-regular.js +2337 -0
  18. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-regular.svg +604 -0
  19. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-regular.ttf +0 -0
  20. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-regular.woff +0 -0
  21. package/es/aics-image-viewer/assets/fonts/open-sans/open-sans-regular.woff2 +0 -0
  22. package/es/aics-image-viewer/assets/icons/axes.js +20 -0
  23. package/es/aics-image-viewer/assets/icons/boundingBox.js +18 -0
  24. package/es/aics-image-viewer/assets/icons/camera.js +15 -0
  25. package/es/aics-image-viewer/assets/icons/channels.js +15 -0
  26. package/es/aics-image-viewer/assets/icons/closePanel.js +18 -0
  27. package/es/aics-image-viewer/assets/icons/download.js +17 -0
  28. package/es/aics-image-viewer/assets/icons/dropdownArrow.js +15 -0
  29. package/es/aics-image-viewer/assets/icons/index.js +24 -0
  30. package/es/aics-image-viewer/assets/icons/metadata.js +15 -0
  31. package/es/aics-image-viewer/assets/icons/preferences.js +15 -0
  32. package/es/aics-image-viewer/assets/icons/resetView.js +16 -0
  33. package/es/aics-image-viewer/assets/icons/svg.d.js +0 -0
  34. package/es/aics-image-viewer/assets/icons/turnTable.js +18 -0
  35. package/es/aics-image-viewer/assets/styles/animatedEllipsis.css +21 -0
  36. package/es/aics-image-viewer/assets/styles/globals.css +35 -0
  37. package/es/aics-image-viewer/assets/styles/noui-slider-override.css +1 -0
  38. package/es/aics-image-viewer/assets/styles/variables.css +1 -0
  39. package/es/aics-image-viewer/components/App/ChannelUpdater.js +96 -0
  40. package/es/aics-image-viewer/components/App/index.js +849 -0
  41. package/es/aics-image-viewer/components/App/styles.css +60 -0
  42. package/es/aics-image-viewer/components/App/types.js +1 -0
  43. package/es/aics-image-viewer/components/AxisClipSliders/index.js +285 -0
  44. package/es/aics-image-viewer/components/AxisClipSliders/styles.css +257 -0
  45. package/es/aics-image-viewer/components/BottomPanel/index.js +70 -0
  46. package/es/aics-image-viewer/components/BottomPanel/styles.css +85 -0
  47. package/es/aics-image-viewer/components/CellViewerCanvasWrapper/index.js +113 -0
  48. package/es/aics-image-viewer/components/CellViewerCanvasWrapper/styles.css +12 -0
  49. package/es/aics-image-viewer/components/ChannelsWidget.js +100 -0
  50. package/es/aics-image-viewer/components/ChannelsWidgetRow/index.js +158 -0
  51. package/es/aics-image-viewer/components/ChannelsWidgetRow/styles.css +38 -0
  52. package/es/aics-image-viewer/components/ColorPicker.js +120 -0
  53. package/es/aics-image-viewer/components/ControlPanel/index.js +180 -0
  54. package/es/aics-image-viewer/components/ControlPanel/styles.css +169 -0
  55. package/es/aics-image-viewer/components/CustomizeWidget.js +46 -0
  56. package/es/aics-image-viewer/components/ErrorAlert/index.js +144 -0
  57. package/es/aics-image-viewer/components/ErrorAlert/styles.css +22 -0
  58. package/es/aics-image-viewer/components/GlobalVolumeControls.js +38 -0
  59. package/es/aics-image-viewer/components/MetadataViewer/index.js +99 -0
  60. package/es/aics-image-viewer/components/MetadataViewer/styles.css +81 -0
  61. package/es/aics-image-viewer/components/StyleProvider/index.js +210 -0
  62. package/es/aics-image-viewer/components/TfEditor/index.js +584 -0
  63. package/es/aics-image-viewer/components/TfEditor/styles.css +88 -0
  64. package/es/aics-image-viewer/components/Toolbar/DownloadButton.js +55 -0
  65. package/es/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.js +22 -0
  66. package/es/aics-image-viewer/components/Toolbar/index.js +274 -0
  67. package/es/aics-image-viewer/components/Toolbar/styles.css +241 -0
  68. package/es/aics-image-viewer/components/ViewerStateProvider/ResetStateProvider.js +160 -0
  69. package/es/aics-image-viewer/components/ViewerStateProvider/index.js +261 -0
  70. package/es/aics-image-viewer/components/ViewerStateProvider/types.js +1 -0
  71. package/es/aics-image-viewer/components/shared/NumericInput/index.js +147 -0
  72. package/es/aics-image-viewer/components/shared/NumericInput/styles.css +64 -0
  73. package/es/aics-image-viewer/components/shared/SharedCheckBox.js +17 -0
  74. package/es/aics-image-viewer/components/shared/SliderRow/index.js +31 -0
  75. package/es/aics-image-viewer/components/shared/SliderRow/styles.css +19 -0
  76. package/es/aics-image-viewer/components/shared/SmarterSlider.js +40 -0
  77. package/es/aics-image-viewer/components/shared/ViewerIcon.js +22 -0
  78. package/es/aics-image-viewer/shared/colorPalette.js +19 -0
  79. package/es/aics-image-viewer/shared/constants.js +164 -0
  80. package/es/aics-image-viewer/shared/enums.js +18 -0
  81. package/es/aics-image-viewer/shared/types.js +6 -0
  82. package/es/aics-image-viewer/shared/utils/colorRepresentations.js +16 -0
  83. package/es/aics-image-viewer/shared/utils/controlPointsToLut.js +182 -0
  84. package/es/aics-image-viewer/shared/utils/hooks.js +51 -0
  85. package/es/aics-image-viewer/shared/utils/httpClient.js +57 -0
  86. package/es/aics-image-viewer/shared/utils/playControls.js +97 -0
  87. package/es/aics-image-viewer/shared/utils/sceneStore.js +140 -0
  88. package/es/aics-image-viewer/shared/utils/sliderValuesToImageValues.js +36 -0
  89. package/es/aics-image-viewer/shared/utils/test/viewerChannelSettings.test.js +132 -0
  90. package/es/aics-image-viewer/shared/utils/utilsService.js +54 -0
  91. package/es/aics-image-viewer/shared/utils/viewerChannelSettings.js +152 -0
  92. package/es/aics-image-viewer/shared/utils/viewerState.js +69 -0
  93. package/es/aics-image-viewer/shared/utils/webgl-debug.js +1103 -0
  94. package/es/index.js +4 -0
  95. package/package.json +130 -0
  96. package/type-declarations/aics-image-viewer/assets/icons/index.d.ts +14 -0
  97. package/type-declarations/aics-image-viewer/components/App/ChannelUpdater.d.ts +16 -0
  98. package/type-declarations/aics-image-viewer/components/App/index.d.ts +6 -0
  99. package/type-declarations/aics-image-viewer/components/App/types.d.ts +45 -0
  100. package/type-declarations/aics-image-viewer/components/AxisClipSliders/index.d.ts +24 -0
  101. package/type-declarations/aics-image-viewer/components/BottomPanel/index.d.ts +11 -0
  102. package/type-declarations/aics-image-viewer/components/CellViewerCanvasWrapper/index.d.ts +33 -0
  103. package/type-declarations/aics-image-viewer/components/ChannelsWidget.d.ts +19 -0
  104. package/type-declarations/aics-image-viewer/components/ChannelsWidgetRow/index.d.ts +17 -0
  105. package/type-declarations/aics-image-viewer/components/ColorPicker.d.ts +13 -0
  106. package/type-declarations/aics-image-viewer/components/ControlPanel/index.d.ts +20 -0
  107. package/type-declarations/aics-image-viewer/components/CustomizeWidget.d.ts +15 -0
  108. package/type-declarations/aics-image-viewer/components/ErrorAlert/index.d.ts +12 -0
  109. package/type-declarations/aics-image-viewer/components/GlobalVolumeControls.d.ts +21 -0
  110. package/type-declarations/aics-image-viewer/components/MetadataViewer/index.d.ts +7 -0
  111. package/type-declarations/aics-image-viewer/components/StyleProvider/index.d.ts +5 -0
  112. package/type-declarations/aics-image-viewer/components/TfEditor/index.d.ts +19 -0
  113. package/type-declarations/aics-image-viewer/components/Toolbar/DownloadButton.d.ts +8 -0
  114. package/type-declarations/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.d.ts +8 -0
  115. package/type-declarations/aics-image-viewer/components/Toolbar/index.d.ts +31 -0
  116. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/ResetStateProvider.d.ts +34 -0
  117. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/index.d.ts +33 -0
  118. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/types.d.ts +105 -0
  119. package/type-declarations/aics-image-viewer/components/shared/NumericInput/index.d.ts +19 -0
  120. package/type-declarations/aics-image-viewer/components/shared/SharedCheckBox.d.ts +9 -0
  121. package/type-declarations/aics-image-viewer/components/shared/SliderRow/index.d.ts +17 -0
  122. package/type-declarations/aics-image-viewer/components/shared/SmarterSlider.d.ts +5 -0
  123. package/type-declarations/aics-image-viewer/components/shared/ViewerIcon.d.ts +9 -0
  124. package/type-declarations/aics-image-viewer/shared/constants.d.ts +40 -0
  125. package/type-declarations/aics-image-viewer/shared/enums.d.ts +15 -0
  126. package/type-declarations/aics-image-viewer/shared/types.d.ts +15 -0
  127. package/type-declarations/aics-image-viewer/shared/utils/colorRepresentations.d.ts +10 -0
  128. package/type-declarations/aics-image-viewer/shared/utils/controlPointsToLut.d.ts +53 -0
  129. package/type-declarations/aics-image-viewer/shared/utils/hooks.d.ts +14 -0
  130. package/type-declarations/aics-image-viewer/shared/utils/playControls.d.ts +27 -0
  131. package/type-declarations/aics-image-viewer/shared/utils/sceneStore.d.ts +17 -0
  132. package/type-declarations/aics-image-viewer/shared/utils/sliderValuesToImageValues.d.ts +10 -0
  133. package/type-declarations/aics-image-viewer/shared/utils/viewerChannelSettings.d.ts +46 -0
  134. package/type-declarations/aics-image-viewer/shared/utils/viewerState.d.ts +10 -0
  135. package/type-declarations/index.d.ts +7 -0
@@ -0,0 +1,584 @@
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
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
5
+ 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
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
7
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
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
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
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); }
11
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
12
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
13
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
14
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
15
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
16
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
17
+ import { Lut } from "@aics/vole-core";
18
+ import { Button, Checkbox, InputNumber, Tooltip } from "antd";
19
+ import * as d3 from "d3";
20
+ import "nouislider/distribute/nouislider.css";
21
+ import React, { useCallback, useMemo, useRef, useState } from "react";
22
+ import { SketchPicker } from "react-color";
23
+ import { LUT_MAX_PERCENTILE, LUT_MIN_PERCENTILE, TFEDITOR_DEFAULT_COLOR } from "../../shared/constants";
24
+ import { colorArrayToObject, colorArrayToString, colorObjectToArray } from "../../shared/utils/colorRepresentations";
25
+ import { controlPointsToRamp, rampToControlPoints } from "../../shared/utils/controlPointsToLut";
26
+ import { useRefWithSetter } from "../../shared/utils/hooks";
27
+ import SliderRow from "../shared/SliderRow";
28
+ import "./styles.css";
29
+
30
+ /**The color picker opens next to control points like a context menu. This constant gives it a bit of space. */
31
+ var TFEDITOR_COLOR_PICKER_MARGIN_X_PX = 2;
32
+ /** If a control point is within this distance of the bottom of the screen, open the color picker upward */
33
+ var TFEDITOR_COLOR_PICKER_OPEN_UPWARD_MARGIN_PX = 310;
34
+ var TFEDITOR_GRADIENT_MAX_OPACITY = 0.9;
35
+ var TFEDITOR_NUM_TICKS = 4;
36
+ var TFEDITOR_MARGINS = {
37
+ top: 18,
38
+ right: 20,
39
+ bottom: 30,
40
+ // includes space for x-axis
41
+ left: 25
42
+ };
43
+ var MOUSE_EVENT_BUTTONS_PRIMARY = 1;
44
+ var TfEditorRampSliderHandle = /*#__PURE__*/function (TfEditorRampSliderHandle) {
45
+ TfEditorRampSliderHandle["Min"] = "min";
46
+ TfEditorRampSliderHandle["Max"] = "max";
47
+ return TfEditorRampSliderHandle;
48
+ }(TfEditorRampSliderHandle || {});
49
+ var TF_GENERATORS = {
50
+ autoXF: function autoXF(histo) {
51
+ // Currently unused. min and max are the first and last bins whose values are >=10% that of the max bin
52
+ var _histo$findAutoMinMax = histo.findAutoMinMax(),
53
+ _histo$findAutoMinMax2 = _slicedToArray(_histo$findAutoMinMax, 2),
54
+ hmin = _histo$findAutoMinMax2[0],
55
+ hmax = _histo$findAutoMinMax2[1];
56
+ return new Lut().createFromMinMax(hmin, hmax);
57
+ },
58
+ auto2XF: function auto2XF(histo) {
59
+ var _histo$findAutoIJBins = histo.findAutoIJBins(),
60
+ _histo$findAutoIJBins2 = _slicedToArray(_histo$findAutoIJBins, 2),
61
+ hmin = _histo$findAutoIJBins2[0],
62
+ hmax = _histo$findAutoIJBins2[1];
63
+ return new Lut().createFromMinMax(hmin, hmax);
64
+ },
65
+ auto98XF: function auto98XF(histo) {
66
+ var hmin = histo.findBinOfPercentile(LUT_MIN_PERCENTILE);
67
+ var hmax = histo.findBinOfPercentile(LUT_MAX_PERCENTILE);
68
+ return new Lut().createFromMinMax(hmin, hmax);
69
+ },
70
+ bestFitXF: function bestFitXF(histo) {
71
+ var _histo$findBestFitBin = histo.findBestFitBins(),
72
+ _histo$findBestFitBin2 = _slicedToArray(_histo$findBestFitBin, 2),
73
+ hmin = _histo$findBestFitBin2[0],
74
+ hmax = _histo$findBestFitBin2[1];
75
+ return new Lut().createFromMinMax(hmin, hmax);
76
+ },
77
+ resetXF: function resetXF(_histo) {
78
+ return new Lut().createFullRange();
79
+ }
80
+ };
81
+
82
+ // *---*
83
+ // | |
84
+ // | |
85
+ // \ /
86
+ // *
87
+ // width: 0.65 * height; height of rectangle: 0.6 * height; height of triangle: 0.4 * height
88
+ var sliderHandleSymbol = {
89
+ draw: function draw(context, size) {
90
+ // size is symbol area in px^2
91
+ var height = Math.sqrt(size * 1.9);
92
+ var triangleHeight = height * 0.4;
93
+ var halfWidth = height * 0.325;
94
+ context.moveTo(-halfWidth, -height);
95
+ context.lineTo(halfWidth, -height);
96
+ context.lineTo(halfWidth, -triangleHeight);
97
+ context.lineTo(0, 0);
98
+ context.lineTo(-halfWidth, -triangleHeight);
99
+ context.closePath();
100
+ }
101
+ };
102
+ function u8ToAbsolute(value, channel) {
103
+ return channel.rawMin + value / 255 * (channel.rawMax - channel.rawMin);
104
+ }
105
+ function absoluteToU8(value, channel) {
106
+ return (value - channel.rawMin) / (channel.rawMax - channel.rawMin) * 255;
107
+ }
108
+ function controlPointToAbsolute(cp, channel) {
109
+ // the x value of the control point is in the range [0, 255]
110
+ // because of the way the histogram is generated
111
+ // (see LUT_ENTRIES and the fact that we use Uint8Array)
112
+ return u8ToAbsolute(cp.x, channel);
113
+ }
114
+
115
+ /** Defines an SVG gradient with id `id` based on the provided `controlPoints` */
116
+ var ControlPointGradientDef = function ControlPointGradientDef(_ref) {
117
+ var controlPoints = _ref.controlPoints,
118
+ id = _ref.id;
119
+ var range = controlPoints[controlPoints.length - 1].x - controlPoints[0].x;
120
+ return /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("linearGradient", {
121
+ id: id,
122
+ gradientUnits: "objectBoundingBox",
123
+ spreadMethod: "pad",
124
+ x2: "100%"
125
+ }, controlPoints.map(function (cp, i) {
126
+ var offset = "".concat((cp.x - controlPoints[0].x) / range * 100, "%");
127
+ var opacity = Math.min(cp.opacity, TFEDITOR_GRADIENT_MAX_OPACITY);
128
+ return /*#__PURE__*/React.createElement("stop", {
129
+ key: i,
130
+ stopColor: colorArrayToString(cp.color),
131
+ stopOpacity: opacity,
132
+ offset: offset
133
+ });
134
+ })));
135
+ };
136
+
137
+ /** Retrieves the bin contents and max bin value from `histogram` */
138
+ function getHistogramBinLengths(histogram) {
139
+ var binLengths = [];
140
+ // TODO: Change `histogram.bins` to be readable/readonly
141
+ // so we don't have to copy it here!
142
+ var max = Number.NEGATIVE_INFINITY;
143
+ for (var i = 0; i < histogram.getNumBins(); i++) {
144
+ var binLength = histogram.getBin(i);
145
+ binLengths.push(binLength);
146
+ max = Math.max(max, binLength);
147
+ }
148
+ return {
149
+ binLengths: binLengths,
150
+ max: max
151
+ };
152
+ }
153
+ var colorPickerPositionToStyle = function colorPickerPositionToStyle(_ref2) {
154
+ var _ref3 = _slicedToArray(_ref2, 2),
155
+ x = _ref3[0],
156
+ y = _ref3[1];
157
+ return _defineProperty(_defineProperty({
158
+ position: "absolute"
159
+ }, x < 0 ? "right" : "left", Math.abs(x)), y < 0 ? "bottom" : "top", y);
160
+ };
161
+ var numberFormatter = function numberFormatter(v) {
162
+ return v === undefined ? "" : Number(v).toFixed(0);
163
+ };
164
+ var clamp = function clamp(value, min, max) {
165
+ return Math.min(Math.max(value, min), max);
166
+ };
167
+ var TfEditor = function TfEditor(props) {
168
+ var changeChannelSetting = props.changeChannelSetting;
169
+ var innerWidth = props.width - TFEDITOR_MARGINS.left - TFEDITOR_MARGINS.right;
170
+ var innerHeight = props.height - TFEDITOR_MARGINS.top - TFEDITOR_MARGINS.bottom;
171
+ var _useState = useState(null),
172
+ _useState2 = _slicedToArray(_useState, 2),
173
+ selectedPointIdx = _useState2[0],
174
+ setSelectedPointIdx = _useState2[1];
175
+ var _useState3 = useState(null),
176
+ _useState4 = _slicedToArray(_useState3, 2),
177
+ draggedPointIdx = _useState4[0],
178
+ _setDraggedPointIdx = _useState4[1];
179
+ var _setCPs = useCallback(function (p) {
180
+ return changeChannelSetting({
181
+ controlPoints: p
182
+ });
183
+ }, [changeChannelSetting]);
184
+ var setRamp = useCallback(function (ramp) {
185
+ return changeChannelSetting({
186
+ ramp: ramp
187
+ });
188
+ }, [changeChannelSetting]);
189
+
190
+ // these bits of state need their freshest, most up-to-date values available in mouse event handlers. make refs!
191
+ var _useRefWithSetter = useRefWithSetter(_setCPs, props.controlPoints),
192
+ _useRefWithSetter2 = _slicedToArray(_useRefWithSetter, 2),
193
+ controlPointsRef = _useRefWithSetter2[0],
194
+ setControlPoints = _useRefWithSetter2[1];
195
+ var _useRefWithSetter3 = useRefWithSetter(_setDraggedPointIdx, draggedPointIdx),
196
+ _useRefWithSetter4 = _slicedToArray(_useRefWithSetter3, 2),
197
+ draggedPointIdxRef = _useRefWithSetter4[0],
198
+ setDraggedPointIdx = _useRefWithSetter4[1];
199
+
200
+ // Either `null` when the control panel is closed, or an x offset into the plot to position the color picker.
201
+ // Positive: offset right from the left edge of the plot; negative: offset left from the right edge of the plot.
202
+ var _useState5 = useState(null),
203
+ _useState6 = _slicedToArray(_useState5, 2),
204
+ colorPickerPosition = _useState6[0],
205
+ setColorPickerPosition = _useState6[1];
206
+ var lastColorRef = useRef(TFEDITOR_DEFAULT_COLOR);
207
+ var svgRef = useRef(null); // need access to SVG element to measure mouse position
208
+
209
+ // d3 scales define the mapping between data and screen space (and do the heavy lifting of generating plot axes)
210
+ /** `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]);
214
+ var yScale = useMemo(function () {
215
+ return d3.scaleLinear().domain([0, 1]).range([innerHeight, 0]);
216
+ }, [innerHeight]);
217
+ var mouseEventToControlPointValues = function mouseEventToControlPointValues(event) {
218
+ var _svgRef$current$getBo, _svgRef$current;
219
+ var svgRect = (_svgRef$current$getBo = (_svgRef$current = svgRef.current) === null || _svgRef$current === void 0 ? void 0 : _svgRef$current.getBoundingClientRect()) !== null && _svgRef$current$getBo !== void 0 ? _svgRef$current$getBo : {
220
+ x: 0,
221
+ y: 0
222
+ };
223
+ return [absoluteToU8(xScale.invert(clamp(event.clientX - svgRect.x - TFEDITOR_MARGINS.left, 0, innerWidth)), props.channelData), yScale.invert(clamp(event.clientY - svgRect.y - TFEDITOR_MARGINS.top, 0, innerHeight))];
224
+ };
225
+ var dragControlPoint = function dragControlPoint(draggedIdx, x, opacity) {
226
+ var newControlPoints = _toConsumableArray(controlPointsRef.current);
227
+ var draggedPoint = newControlPoints[draggedIdx];
228
+ draggedPoint.x = x;
229
+ draggedPoint.opacity = opacity;
230
+
231
+ // Remove control points to keep the list sorted by x value
232
+ var bisector = d3.bisector(function (a, b) {
233
+ return a.x - b.x;
234
+ });
235
+ var idxLeft = bisector.left(newControlPoints, draggedPoint);
236
+ var idxRight = bisector.right(newControlPoints, draggedPoint);
237
+ if (idxLeft < draggedIdx) {
238
+ var numPointsToRemove = draggedIdx - idxLeft; // should almost always be 1
239
+ newControlPoints.splice(idxLeft, numPointsToRemove);
240
+ var newIdx = draggedIdx - numPointsToRemove;
241
+ setDraggedPointIdx(newIdx);
242
+ setSelectedPointIdx(newIdx);
243
+ } else if (idxRight > draggedIdx + 1) {
244
+ newControlPoints.splice(draggedIdx + 1, idxRight - draggedIdx - 1);
245
+ }
246
+ setControlPoints(newControlPoints);
247
+ };
248
+ var dragRampSlider = function dragRampSlider(handle, x) {
249
+ if (handle === TfEditorRampSliderHandle.Min) {
250
+ var max = props.ramp[1];
251
+ setRamp([Math.min(x, max), max]);
252
+ } else {
253
+ var min = props.ramp[0];
254
+ setRamp([min, Math.max(x, min)]);
255
+ }
256
+ };
257
+ var handlePlotPointerDown = function handlePlotPointerDown(event) {
258
+ if (props.useControlPoints) {
259
+ // Advanced mode - we're either creating a new control point or selecting/dragging an existing one
260
+ if (draggedPointIdxRef.current === null && event.button === 0) {
261
+ // this click is not on an existing point - create a new one
262
+ var _mouseEventToControlP = mouseEventToControlPointValues(event),
263
+ _mouseEventToControlP2 = _slicedToArray(_mouseEventToControlP, 2),
264
+ x = _mouseEventToControlP2[0],
265
+ opacity = _mouseEventToControlP2[1];
266
+ var point = {
267
+ x: x,
268
+ opacity: opacity,
269
+ color: lastColorRef.current
270
+ };
271
+
272
+ // add new control point to controlPoints
273
+ var index = d3.bisector(function (a, b) {
274
+ return a.x - b.x;
275
+ }).left(props.controlPoints, point);
276
+ setDraggedPointIdx(index);
277
+ var newControlPoints = _toConsumableArray(props.controlPoints);
278
+ newControlPoints.splice(index, 0, point);
279
+ setControlPoints(newControlPoints);
280
+ } else {
281
+ // this click is on an existing point - update current points to ref (may have been remapped since last edit)
282
+ controlPointsRef.current = props.controlPoints;
283
+ }
284
+ if (typeof draggedPointIdxRef.current !== "string") {
285
+ setSelectedPointIdx(draggedPointIdxRef.current);
286
+ }
287
+ }
288
+ if (event.button === 0 && draggedPointIdxRef.current !== null) {
289
+ // get set up to drag the point around, even if the mouse leaves the SVG element
290
+ event.currentTarget.setPointerCapture(event.nativeEvent.pointerId);
291
+ } else {
292
+ setDraggedPointIdx(null);
293
+ }
294
+ };
295
+ var handlePlotPointerMove = function handlePlotPointerMove(event) {
296
+ if (draggedPointIdxRef.current === null) {
297
+ return;
298
+ }
299
+ if ((event.buttons & MOUSE_EVENT_BUTTONS_PRIMARY) === 0) {
300
+ handleDragEnd(event);
301
+ return;
302
+ }
303
+ event.stopPropagation();
304
+ event.preventDefault();
305
+ var _mouseEventToControlP3 = mouseEventToControlPointValues(event),
306
+ _mouseEventToControlP4 = _slicedToArray(_mouseEventToControlP3, 2),
307
+ x = _mouseEventToControlP4[0],
308
+ opacity = _mouseEventToControlP4[1];
309
+
310
+ // `draggedPointIdxRef` may either be a number (control point index) or a string (ramp slider handle).
311
+ // The result of this check should always be the same as `props.useControlPoints`, but this narrows the type for TS
312
+ if (typeof draggedPointIdxRef.current === "number") {
313
+ dragControlPoint(draggedPointIdxRef.current, x, opacity);
314
+ } else {
315
+ dragRampSlider(draggedPointIdxRef.current, x);
316
+ }
317
+ };
318
+ var handleDragEnd = function handleDragEnd(event) {
319
+ setDraggedPointIdx(null);
320
+ event.currentTarget.releasePointerCapture(event.pointerId);
321
+ };
322
+ var handleControlPointContextMenu = function handleControlPointContextMenu(event) {
323
+ event.preventDefault();
324
+ if (!event.target || !svgRef.current) {
325
+ setColorPickerPosition(null);
326
+ return;
327
+ }
328
+ var svgRect = svgRef.current.getBoundingClientRect();
329
+ var cpRect = event.target.getBoundingClientRect();
330
+ var cpRectCenter = cpRect.left + cpRect.width / 2;
331
+
332
+ // If the control point is closer to the left edge of the SVG, open the color picker to the right
333
+ var openLeft = cpRectCenter - svgRect.left < svgRect.width / 2;
334
+ var xPosition = openLeft ? cpRect.right - svgRect.left + TFEDITOR_COLOR_PICKER_MARGIN_X_PX : cpRect.left - svgRect.right - TFEDITOR_COLOR_PICKER_MARGIN_X_PX;
335
+
336
+ // If the control point is too close to the bottom of the screen, open the color picker upward
337
+ var openUp = window.innerHeight - cpRect.bottom < TFEDITOR_COLOR_PICKER_OPEN_UPWARD_MARGIN_PX;
338
+ var yPosition = openUp ? svgRect.top - cpRect.bottom : cpRect.top - svgRect.top;
339
+ setColorPickerPosition([xPosition, yPosition]);
340
+ };
341
+ var handleChangeColor = function handleChangeColor(color) {
342
+ lastColorRef.current = colorObjectToArray(color.rgb);
343
+ if (selectedPointIdx !== null) {
344
+ var newControlPoints = _toConsumableArray(props.controlPoints);
345
+ newControlPoints[selectedPointIdx].color = lastColorRef.current;
346
+ setControlPoints(newControlPoints);
347
+ }
348
+ };
349
+ var controlPointsToRender = useMemo(function () {
350
+ return props.useControlPoints ? props.controlPoints : rampToControlPoints(props.ramp);
351
+ }, [props.controlPoints, props.ramp, props.useControlPoints]);
352
+
353
+ /** d3-generated svg data string representing both the line between points and the region filled with gradient */
354
+ var areaPath = useMemo(function () {
355
+ var _areaGenerator;
356
+ var areaGenerator = d3.area().x(function (d) {
357
+ return xScale(controlPointToAbsolute(d, props.channelData));
358
+ }).y0(function (d) {
359
+ return yScale(d.opacity);
360
+ }).y1(innerHeight).curve(d3.curveLinear);
361
+ return (_areaGenerator = areaGenerator(controlPointsToRender)) !== null && _areaGenerator !== void 0 ? _areaGenerator : undefined;
362
+ }, [controlPointsToRender, xScale, yScale, innerHeight]);
363
+
364
+ /** d3-generated svg data string representing the "basic mode" min/max slider handles */
365
+ var sliderHandlePath = useMemo(function () {
366
+ var _d3$symbol$type$size;
367
+ return (_d3$symbol$type$size = d3.symbol().type(sliderHandleSymbol).size(80)()) !== null && _d3$symbol$type$size !== void 0 ? _d3$symbol$type$size : undefined;
368
+ }, []);
369
+
370
+ // The below `useCallback`s are used as "ref callbacks" - passed as the `ref` prop of SVG elements in order to render
371
+ // these elements' content using D3. They are called when the ref'd component mounts and unmounts, and whenever their
372
+ // identity changes (i.e. whenever their dependencies change).
373
+
374
+ var xAxisRef = useCallback(function (el) {
375
+ var ticks = xScale.ticks(TFEDITOR_NUM_TICKS);
376
+ ticks[ticks.length - 1] = xScale.domain()[1];
377
+ 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
+ );
379
+ }, [xScale, props.useControlPoints]);
380
+ var yAxisRef = useCallback(function (el) {
381
+ return d3.select(el).call(d3.axisLeft(yScale).ticks(TFEDITOR_NUM_TICKS));
382
+ }, [yScale]);
383
+ var histogramRef = useCallback(function (el) {
384
+ if (el === null) {
385
+ return;
386
+ }
387
+ if (props.channelData.histogram.getNumBins() < 1) {
388
+ return;
389
+ }
390
+ var _getHistogramBinLengt = getHistogramBinLengths(props.channelData.histogram),
391
+ binLengths = _getHistogramBinLengt.binLengths,
392
+ max = _getHistogramBinLengt.max;
393
+ var barWidth = innerWidth / props.channelData.histogram.getNumBins();
394
+ var binScale = d3.scaleLog().domain([0.1, max]).range([innerHeight, 0]).base(2).clamp(true);
395
+ d3.select(el).selectAll(".bar") // select all the bars of the histogram
396
+ .data(binLengths) // bind the histogram bins to this selection
397
+ .join("rect") // ensure we have exactly as many bound `rect` elements in the DOM as we have histogram bins
398
+ .attr("class", "bar").attr("width", barWidth).attr("x", function (_len, idx) {
399
+ return xScale(u8ToAbsolute(idx, props.channelData));
400
+ }) // set position and height from data
401
+ .attr("y", function (len) {
402
+ return binScale(len);
403
+ }).attr("height", function (len) {
404
+ return innerHeight - binScale(len);
405
+ });
406
+ }, [props.channelData.histogram, innerWidth, innerHeight]);
407
+ var applyTFGenerator = useCallback(function (generator) {
408
+ setSelectedPointIdx(null);
409
+ lastColorRef.current = TFEDITOR_DEFAULT_COLOR;
410
+ var lut = TF_GENERATORS[generator](props.channelData.histogram);
411
+ if (props.useControlPoints) {
412
+ setControlPoints(lut.controlPoints.map(function (cp) {
413
+ return _objectSpread(_objectSpread({}, cp), {}, {
414
+ color: TFEDITOR_DEFAULT_COLOR
415
+ });
416
+ }));
417
+ } else {
418
+ setRamp(controlPointsToRamp(lut.controlPoints));
419
+ }
420
+ }, [props.channelData.histogram, props.useControlPoints]);
421
+ var createTFGeneratorButton = function createTFGeneratorButton(generator, name, description) {
422
+ return /*#__PURE__*/React.createElement(Tooltip, {
423
+ title: description,
424
+ placement: "top"
425
+ }, /*#__PURE__*/React.createElement(Button, {
426
+ onClick: function onClick() {
427
+ return applyTFGenerator(generator);
428
+ }
429
+ }, name));
430
+ };
431
+
432
+ // create one svg circle element for each control point
433
+ var controlPointCircles = props.useControlPoints ? props.controlPoints.map(function (cp, i) {
434
+ return /*#__PURE__*/React.createElement("circle", {
435
+ key: i,
436
+ className: i === selectedPointIdx ? "selected" : "",
437
+ cx: xScale(controlPointToAbsolute(cp, props.channelData)),
438
+ cy: yScale(cp.opacity),
439
+ style: {
440
+ fill: colorArrayToString(cp.color)
441
+ },
442
+ r: 5,
443
+ onPointerDown: function onPointerDown() {
444
+ return setDraggedPointIdx(i);
445
+ },
446
+ onContextMenu: handleControlPointContextMenu
447
+ });
448
+ }) : null;
449
+ // move selected control point to the end so it's not occluded by other nearby points
450
+ if (controlPointCircles !== null && selectedPointIdx !== null) {
451
+ controlPointCircles.push(controlPointCircles.splice(selectedPointIdx, 1)[0]);
452
+ }
453
+ var viewerModeString = props.useControlPoints ? "advanced" : "basic";
454
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
455
+ className: "button-row"
456
+ }, createTFGeneratorButton("resetXF", "None", "Reset transfer function to full range."), createTFGeneratorButton("auto98XF", "Default", "Ramp from 50th percentile to 98th."), createTFGeneratorButton("auto2XF", "IJ Auto", "Emulates ImageJ's \"auto\" button."), createTFGeneratorButton("bestFitXF", "Auto 2", "Ramp over the middle 80% of data."), /*#__PURE__*/React.createElement(Checkbox, {
457
+ checked: props.useControlPoints,
458
+ onChange: function onChange(e) {
459
+ return changeChannelSetting({
460
+ useControlPoints: e.target.checked
461
+ });
462
+ },
463
+ style: {
464
+ marginLeft: "auto"
465
+ }
466
+ }, "Advanced")), colorPickerPosition !== null && /*#__PURE__*/React.createElement("div", {
467
+ className: "tf-editor-popover"
468
+ }, /*#__PURE__*/React.createElement("div", {
469
+ className: "tf-editor-cover",
470
+ onClick: function onClick() {
471
+ return setColorPickerPosition(null);
472
+ }
473
+ }), /*#__PURE__*/React.createElement("div", {
474
+ style: colorPickerPositionToStyle(colorPickerPosition)
475
+ }, /*#__PURE__*/React.createElement(SketchPicker, {
476
+ color: colorArrayToObject(lastColorRef.current),
477
+ onChange: handleChangeColor,
478
+ disableAlpha: true
479
+ }))), /*#__PURE__*/React.createElement("svg", {
480
+ className: "tf-editor-svg ".concat(viewerModeString).concat(draggedPointIdx !== null ? " dragging" : ""),
481
+ ref: svgRef,
482
+ width: props.width,
483
+ height: props.height,
484
+ onPointerDown: handlePlotPointerDown,
485
+ onPointerMove: handlePlotPointerMove,
486
+ onPointerUp: handleDragEnd
487
+ }, /*#__PURE__*/React.createElement(ControlPointGradientDef, {
488
+ controlPoints: controlPointsToRender,
489
+ id: "tfGradient-".concat(props.id)
490
+ }), /*#__PURE__*/React.createElement("g", {
491
+ transform: "translate(".concat(TFEDITOR_MARGINS.left, ",").concat(TFEDITOR_MARGINS.top, ")")
492
+ }, /*#__PURE__*/React.createElement("g", {
493
+ ref: histogramRef
494
+ }), /*#__PURE__*/React.createElement("path", {
495
+ className: "line",
496
+ fill: "url(#tfGradient-".concat(props.id, ")"),
497
+ d: areaPath
498
+ }), /*#__PURE__*/React.createElement("g", {
499
+ ref: xAxisRef,
500
+ className: "axis",
501
+ transform: "translate(0,".concat(innerHeight, ")")
502
+ }), /*#__PURE__*/React.createElement("g", {
503
+ ref: yAxisRef,
504
+ className: "axis"
505
+ }), controlPointCircles, !props.useControlPoints && /*#__PURE__*/React.createElement("g", {
506
+ className: "ramp-sliders"
507
+ }, /*#__PURE__*/React.createElement("g", {
508
+ transform: "translate(".concat(xScale(u8ToAbsolute(props.ramp[0], props.channelData)), ")")
509
+ }, /*#__PURE__*/React.createElement("line", {
510
+ y1: innerHeight,
511
+ strokeDasharray: "5,5",
512
+ strokeWidth: 2
513
+ }), /*#__PURE__*/React.createElement("line", {
514
+ className: "ramp-slider-click-target",
515
+ y1: innerHeight,
516
+ strokeWidth: 6,
517
+ onPointerDown: function onPointerDown() {
518
+ return setDraggedPointIdx(TfEditorRampSliderHandle.Min);
519
+ }
520
+ }), /*#__PURE__*/React.createElement("path", {
521
+ d: sliderHandlePath,
522
+ transform: "translate(0,".concat(innerHeight, ") rotate(180)"),
523
+ onPointerDown: function onPointerDown() {
524
+ return setDraggedPointIdx(TfEditorRampSliderHandle.Min);
525
+ }
526
+ })), /*#__PURE__*/React.createElement("g", {
527
+ transform: "translate(".concat(xScale(u8ToAbsolute(props.ramp[1], props.channelData)), ")")
528
+ }, /*#__PURE__*/React.createElement("line", {
529
+ y1: innerHeight,
530
+ strokeDasharray: "5,5",
531
+ strokeWidth: 2
532
+ }), /*#__PURE__*/React.createElement("line", {
533
+ className: "ramp-slider-click-target",
534
+ y1: innerHeight,
535
+ strokeWidth: 6,
536
+ onPointerDown: function onPointerDown() {
537
+ return setDraggedPointIdx(TfEditorRampSliderHandle.Max);
538
+ }
539
+ }), /*#__PURE__*/React.createElement("path", {
540
+ d: sliderHandlePath,
541
+ onPointerDown: function onPointerDown() {
542
+ return setDraggedPointIdx(TfEditorRampSliderHandle.Max);
543
+ }
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),
557
+ onChange: function onChange(v) {
558
+ return v !== null && setRamp([props.ramp[0], absoluteToU8(v, props.channelData)]);
559
+ },
560
+ formatter: numberFormatter,
561
+ min: Math.max(0, u8ToAbsolute(props.ramp[0], props.channelData)),
562
+ max: props.channelData.rawMax,
563
+ size: "small",
564
+ width: 45
565
+ }))), /*#__PURE__*/React.createElement(SliderRow, {
566
+ label: /*#__PURE__*/React.createElement(Checkbox, {
567
+ checked: props.colorizeEnabled,
568
+ onChange: function onChange(e) {
569
+ return changeChannelSetting({
570
+ colorizeEnabled: e.target.checked
571
+ });
572
+ }
573
+ }, "Colorize"),
574
+ max: 1,
575
+ start: props.colorizeAlpha,
576
+ onUpdate: function onUpdate(values) {
577
+ return changeChannelSetting({
578
+ colorizeAlpha: values[0]
579
+ });
580
+ },
581
+ hideSlider: !props.colorizeEnabled
582
+ }));
583
+ };
584
+ export default TfEditor;
@@ -0,0 +1,88 @@
1
+ .channel-row .tf-editor-svg.dragging.basic{
2
+ cursor:ew-resize;
3
+ }
4
+
5
+ .channel-row .tf-editor-svg.dragging.advanced{
6
+ cursor:move;
7
+ }
8
+
9
+ .channel-row .tf-editor-popover{
10
+ position:absolute;
11
+ z-index:9999;
12
+ width:100%;
13
+ }
14
+
15
+ .channel-row .tf-editor-cover{
16
+ position:fixed;
17
+ top:0;
18
+ left:0;
19
+ right:0;
20
+ bottom:0;
21
+ }
22
+
23
+ .channel-row .tf-editor-numeric-input-row{
24
+ display:flex;
25
+ align-items:center;
26
+ justify-content:space-between;
27
+ margin-bottom:20px;
28
+ color:var(--color-controlpanel-text);
29
+ }
30
+
31
+ .channel-row .tf-editor-numeric-input-row .ant-input-number{
32
+ border-color:var(--color-button-tertiary-outline);
33
+ width:60px;
34
+ }
35
+
36
+ .channel-row .tick text{
37
+ fill:var(--color-controlpanel-text);
38
+ user-select:none;
39
+ -o-user-select:none;
40
+ -moz-user-select:none;
41
+ -khtml-user-select:none;
42
+ -webkit-user-select:none;
43
+ }
44
+
45
+ .channel-row rect.bar{
46
+ fill:var(--color-controlpanel-border);
47
+ }
48
+
49
+ .channel-row .axis path,
50
+ .channel-row .axis line{
51
+ stroke:var(--color-controlpanel-border);
52
+ }
53
+
54
+ .channel-row rect{
55
+ fill:none;
56
+ pointer-events:all;
57
+ }
58
+
59
+ .channel-row .line{
60
+ stroke:#aeacae;
61
+ stroke-width:1.5px;
62
+ }
63
+
64
+ .channel-row .ramp-sliders path{
65
+ fill:var(--color-controlpanel-ramp-slider);
66
+ cursor:ew-resize;
67
+ }
68
+
69
+ .channel-row .ramp-sliders line{
70
+ stroke:var(--color-controlpanel-ramp-slider);
71
+ cursor:ew-resize;
72
+ }
73
+
74
+ .channel-row .ramp-sliders line.ramp-slider-click-target{
75
+ stroke:transparent;
76
+ }
77
+
78
+ .channel-row circle{
79
+ fill-opacity:0.6;
80
+ cursor:move;
81
+ stroke:#aeacae;
82
+ stroke-width:1.5px;
83
+ }
84
+
85
+ .channel-row circle.selected{
86
+ fill:white;
87
+ stroke:white;
88
+ }