@complat/react-spectra-editor 1.7.0 → 1.8.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 (111) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -662
  3. package/README.md +5 -0
  4. package/dist/__tests__/fixtures/lc_ms_jcamp.js +2329 -0
  5. package/dist/__tests__/fixtures/lc_ms_jcamp_2.js +2874 -0
  6. package/dist/__tests__/fixtures/lc_ms_jcamp_mz_chemstation.js +3242 -0
  7. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_chemstation.js +854 -0
  8. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_neg.js +1233 -0
  9. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_pos.js +1233 -0
  10. package/dist/__tests__/fixtures/lc_ms_jcamp_uvvis.js +48908 -0
  11. package/dist/__tests__/fixtures/lc_ms_jcamp_uvvis_chemstation.js +15424 -0
  12. package/dist/__tests__/units/components/cmd_bar/r05_submit_btn.test.js +158 -3
  13. package/dist/__tests__/units/components/d3_line_rect.test.js +85 -0
  14. package/dist/__tests__/units/components/d3_multi/multi_focus.test.js +43 -0
  15. package/dist/__tests__/units/components/hplc_viewer.test.js +123 -0
  16. package/dist/__tests__/units/components/panel/info.test.js +55 -15
  17. package/dist/__tests__/units/features/lc-ms/parsing/lcmsCategory.test.js +73 -0
  18. package/dist/__tests__/units/features/lc-ms/submit/peaks.test.js +310 -0
  19. package/dist/actions/curve.js +5 -2
  20. package/dist/actions/hplc_ms.js +66 -0
  21. package/dist/actions/integration.js +17 -2
  22. package/dist/actions/ui.js +39 -11
  23. package/dist/app.js +21 -8
  24. package/dist/components/cmd_bar/01_viewer.js +15 -11
  25. package/dist/components/cmd_bar/03_peak.js +25 -16
  26. package/dist/components/cmd_bar/04_integration.js +116 -17
  27. package/dist/components/cmd_bar/08_peak_group.js +83 -0
  28. package/dist/components/cmd_bar/collapsible_toolbar_group.js +101 -0
  29. package/dist/components/cmd_bar/common.js +4 -3
  30. package/dist/components/cmd_bar/index.js +76 -18
  31. package/dist/components/cmd_bar/r01_layout.js +6 -0
  32. package/dist/components/cmd_bar/r02_scan.js +15 -11
  33. package/dist/components/cmd_bar/r03_threshold.js +19 -3
  34. package/dist/components/cmd_bar/r04_submit.js +61 -6
  35. package/dist/components/cmd_bar/r05_submit_btn.js +60 -13
  36. package/dist/components/cmd_bar/r06_predict_btn.js +4 -4
  37. package/dist/components/cmd_bar/r07_wavelength_btn.js +10 -9
  38. package/dist/components/cmd_bar/r09_detector.js +2 -2
  39. package/dist/components/cmd_bar/tri_btn.js +4 -1
  40. package/dist/components/common/draw.js +6 -5
  41. package/dist/components/d3_line/index.js +70 -15
  42. package/dist/components/d3_line/line_focus.js +104 -67
  43. package/dist/components/d3_line_rect/index.js +836 -0
  44. package/dist/components/d3_line_rect/line_focus.js +526 -0
  45. package/dist/components/d3_line_rect/multi_focus.js +433 -0
  46. package/dist/components/d3_line_rect/rect_focus.js +247 -0
  47. package/dist/components/d3_multi/index.js +154 -163
  48. package/dist/components/d3_multi/multi_focus.js +117 -68
  49. package/dist/components/d3_rect/index.js +6 -0
  50. package/dist/components/d3_rect/rect_focus.js +17 -5
  51. package/dist/components/hplc_viewer.js +169 -0
  52. package/dist/components/lc_ms_uv_tools_bar.js +175 -0
  53. package/dist/components/multi_jcamps_viewer.js +17 -5
  54. package/dist/components/panel/compare.js +84 -62
  55. package/dist/components/panel/cyclic_voltamery_data.js +15 -14
  56. package/dist/components/panel/graph_selection.js +32 -30
  57. package/dist/components/panel/index.js +28 -22
  58. package/dist/components/panel/info.js +90 -31
  59. package/dist/constants/action_type.js +23 -2
  60. package/dist/constants/list_graph.js +16 -0
  61. package/dist/constants/list_layout.js +2 -1
  62. package/dist/constants/list_ui.js +5 -2
  63. package/dist/features/lc-ms/entities/extractEntityLCMS.js +187 -0
  64. package/dist/features/lc-ms/parsing/chemstation.js +108 -0
  65. package/dist/features/lc-ms/parsing/index.js +46 -0
  66. package/dist/features/lc-ms/parsing/lcmsCategory.js +44 -0
  67. package/dist/features/lc-ms/parsing/lcmsJcampInfo.js +18 -0
  68. package/dist/features/lc-ms/parsing/lcmsMsPage.js +241 -0
  69. package/dist/features/lc-ms/parsing/pageValue.js +27 -0
  70. package/dist/features/lc-ms/submit/index.js +24 -0
  71. package/dist/features/lc-ms/submit/peaks.js +66 -0
  72. package/dist/features/lc-ms/ui/wavelengthSelect.js +54 -0
  73. package/dist/fn.js +4 -1
  74. package/dist/helpers/brush.js +74 -19
  75. package/dist/helpers/calc.js +4 -2
  76. package/dist/helpers/cfg.js +5 -4
  77. package/dist/helpers/chem.js +449 -71
  78. package/dist/helpers/compass.js +181 -17
  79. package/dist/helpers/extractEntityLCMS.js +24 -0
  80. package/dist/helpers/extractParams.js +118 -33
  81. package/dist/helpers/extractPeaksEdit.js +29 -9
  82. package/dist/helpers/format.js +189 -4
  83. package/dist/helpers/init.js +25 -5
  84. package/dist/helpers/integration.js +130 -17
  85. package/dist/helpers/integration_draft.js +43 -0
  86. package/dist/helpers/integration_focus.js +155 -0
  87. package/dist/helpers/integration_jcamp.js +68 -0
  88. package/dist/helpers/integration_split.js +202 -0
  89. package/dist/helpers/mount.js +8 -1
  90. package/dist/helpers/sweep.js +39 -0
  91. package/dist/index.js +284 -26
  92. package/dist/layer_content.js +17 -3
  93. package/dist/layer_init.js +124 -14
  94. package/dist/layer_prism.js +31 -5
  95. package/dist/reducers/index.js +3 -1
  96. package/dist/reducers/reducer_curve.js +54 -36
  97. package/dist/reducers/reducer_hplc_ms/hydrate.js +289 -0
  98. package/dist/reducers/reducer_hplc_ms/index.js +122 -0
  99. package/dist/reducers/reducer_hplc_ms/persistence.js +74 -0
  100. package/dist/reducers/reducer_hplc_ms/tic.js +70 -0
  101. package/dist/reducers/reducer_hplc_ms/utils.js +70 -0
  102. package/dist/reducers/reducer_hplc_ms/uvvis.js +377 -0
  103. package/dist/reducers/reducer_integration.js +265 -7
  104. package/dist/reducers/reducer_layout.js +1 -1
  105. package/dist/reducers/reducer_submit.js +2 -1
  106. package/dist/reducers/reducer_ui.js +149 -13
  107. package/dist/reducers/undo_redo_config.js +2 -2
  108. package/dist/sagas/saga_lcms_ui.js +85 -0
  109. package/dist/sagas/saga_ui.js +177 -74
  110. package/dist/setupTests.js +5 -5
  111. package/package.json +10 -7
@@ -0,0 +1,377 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.uvvisUndo = exports.uvvisRedo = exports.updateWavelength = exports.updateHplcMsPeaks = exports.updateHplcMsIntegrations = exports.removeHplcMsPeak = exports.recordUvvisEditBefore = exports.emptyUvvisHistory = exports.clearIntegrationAllHplcMs = exports.clearAllPeaksHplcMs = void 0;
7
+ var _integration = require("../../helpers/integration");
8
+ var _utils = require("./utils");
9
+ var _persistence = require("./persistence");
10
+ /* eslint-disable prefer-object-spread, default-param-last */
11
+
12
+ const MAX_UVVIS_UNDO = 50;
13
+ const emptyUvvisHistory = () => ({
14
+ past: [],
15
+ future: []
16
+ });
17
+ exports.emptyUvvisHistory = emptyUvvisHistory;
18
+ const captureUvvisEditSnapshot = state => {
19
+ const {
20
+ uvvis
21
+ } = state;
22
+ const {
23
+ spectraList = [],
24
+ selectedWaveLength,
25
+ wavelengthIdx
26
+ } = uvvis;
27
+ return {
28
+ selectedWaveLength,
29
+ wavelengthIdx,
30
+ spectraList: spectraList.map(sp => ({
31
+ peaks: (sp.peaks || []).map(p => ({
32
+ ...p
33
+ })),
34
+ integrations: JSON.parse(JSON.stringify(sp.integrations || []))
35
+ }))
36
+ };
37
+ };
38
+ const restoreUvvisEditSnapshot = (state, snap) => {
39
+ const {
40
+ uvvis
41
+ } = state;
42
+ const newSpectraList = uvvis.spectraList.map((sp, i) => {
43
+ const p = snap.spectraList[i];
44
+ if (!p) return sp;
45
+ return {
46
+ ...sp,
47
+ peaks: (p.peaks || []).map(x => ({
48
+ ...x
49
+ })),
50
+ integrations: JSON.parse(JSON.stringify(p.integrations || []))
51
+ };
52
+ });
53
+ const wlIdx = Number.isInteger(snap.wavelengthIdx) ? snap.wavelengthIdx : 0;
54
+ const safeIdx = wlIdx >= 0 && wlIdx < newSpectraList.length ? wlIdx : 0;
55
+ const currentSpectrum = newSpectraList[safeIdx] || null;
56
+ return {
57
+ ...state,
58
+ uvvis: {
59
+ ...uvvis,
60
+ selectedWaveLength: snap.selectedWaveLength,
61
+ wavelengthIdx: safeIdx,
62
+ spectraList: newSpectraList,
63
+ currentSpectrum
64
+ }
65
+ };
66
+ };
67
+ const recordUvvisEditBefore = state => {
68
+ const hist = state.uvvisEditHistory || emptyUvvisHistory();
69
+ const snap = captureUvvisEditSnapshot(state);
70
+ const past = [...hist.past, snap].slice(-MAX_UVVIS_UNDO);
71
+ return {
72
+ ...state,
73
+ uvvisEditHistory: {
74
+ past,
75
+ future: []
76
+ }
77
+ };
78
+ };
79
+ exports.recordUvvisEditBefore = recordUvvisEditBefore;
80
+ const uvvisUndo = state => {
81
+ const hist = state.uvvisEditHistory || emptyUvvisHistory();
82
+ if (!hist.past.length) return state;
83
+ const past = [...hist.past];
84
+ const prior = past.pop();
85
+ const currentSnap = captureUvvisEditSnapshot(state);
86
+ const future = [currentSnap, ...hist.future];
87
+ const restored = restoreUvvisEditSnapshot(state, prior);
88
+ return {
89
+ ...restored,
90
+ uvvisEditHistory: {
91
+ past,
92
+ future
93
+ }
94
+ };
95
+ };
96
+ exports.uvvisUndo = uvvisUndo;
97
+ const uvvisRedo = state => {
98
+ const hist = state.uvvisEditHistory || emptyUvvisHistory();
99
+ if (!hist.future.length) return state;
100
+ const [next, ...restFuture] = hist.future;
101
+ const past = [...hist.past, captureUvvisEditSnapshot(state)].slice(-MAX_UVVIS_UNDO);
102
+ const restored = restoreUvvisEditSnapshot(state, next);
103
+ return {
104
+ ...restored,
105
+ uvvisEditHistory: {
106
+ past,
107
+ future: restFuture
108
+ }
109
+ };
110
+ };
111
+ exports.uvvisRedo = uvvisRedo;
112
+ const updateHplcMsPeaks = (state, action) => {
113
+ const {
114
+ spectrumId,
115
+ peaks
116
+ } = action.payload || {};
117
+ const {
118
+ uvvis
119
+ } = state;
120
+ const {
121
+ spectraList = [],
122
+ listWaveLength = []
123
+ } = uvvis;
124
+ const normalizedSpectrumId = (0, _utils.normalizeSpectrumId)(spectrumId);
125
+ const spectrumIndex = listWaveLength.map(waveLength => (0, _utils.normalizeSpectrumId)(waveLength)).indexOf(normalizedSpectrumId);
126
+ if (spectrumIndex === -1) return state;
127
+ const st = action.meta?.skipUvvisHistory ? state : recordUvvisEditBefore(state);
128
+ const newSpectraList = [...spectraList];
129
+ const updatedSpectrum = {
130
+ ...newSpectraList[spectrumIndex],
131
+ peaks
132
+ };
133
+ newSpectraList[spectrumIndex] = updatedSpectrum;
134
+ const newCurrentSpectrum = (0, _utils.normalizeSpectrumId)(uvvis.selectedWaveLength) === normalizedSpectrumId ? updatedSpectrum : uvvis.currentSpectrum;
135
+ return {
136
+ ...st,
137
+ uvvis: {
138
+ ...uvvis,
139
+ spectraList: newSpectraList,
140
+ currentSpectrum: newCurrentSpectrum
141
+ }
142
+ };
143
+ };
144
+ exports.updateHplcMsPeaks = updateHplcMsPeaks;
145
+ const updateWavelength = (state, action) => {
146
+ const {
147
+ payload
148
+ } = action;
149
+ if (payload?.target) {
150
+ const {
151
+ value
152
+ } = payload.target;
153
+ const {
154
+ uvvis
155
+ } = state;
156
+ const {
157
+ listWaveLength = [],
158
+ spectraList = []
159
+ } = uvvis;
160
+ const normalizedValue = (0, _utils.normalizeSpectrumId)(value);
161
+ const normalizedWaveLengths = listWaveLength.map(waveLength => (0, _utils.normalizeSpectrumId)(waveLength));
162
+ const wavelengthIdx = normalizedWaveLengths.indexOf(normalizedValue);
163
+ const currentSpectrum = spectraList.find((spectrum, index) => normalizedWaveLengths[index] === normalizedValue);
164
+ const next = {
165
+ ...state,
166
+ uvvis: {
167
+ ...uvvis,
168
+ selectedWaveLength: normalizedValue,
169
+ wavelengthIdx,
170
+ spectraList,
171
+ currentSpectrum
172
+ }
173
+ };
174
+ (0, _persistence.persistLcmsUvvisWavelength)(state.lcmsDatasetKey, normalizedValue);
175
+ return next;
176
+ }
177
+ return state;
178
+ };
179
+ exports.updateWavelength = updateWavelength;
180
+ const updateHplcMsIntegrations = (state, action) => {
181
+ const {
182
+ spectrumId,
183
+ integration,
184
+ remove,
185
+ shift = 0
186
+ } = action.payload || {};
187
+ const {
188
+ uvvis
189
+ } = state;
190
+ const {
191
+ spectraList = [],
192
+ listWaveLength = []
193
+ } = uvvis;
194
+ const normalizedSpectrumId = (0, _utils.normalizeSpectrumId)(spectrumId);
195
+ const curveIdx = listWaveLength.map(waveLength => (0, _utils.normalizeSpectrumId)(waveLength)).indexOf(normalizedSpectrumId);
196
+ if (curveIdx === -1) {
197
+ return state;
198
+ }
199
+ const selectedSpectrum = spectraList[curveIdx];
200
+ if (!selectedSpectrum) {
201
+ return state;
202
+ }
203
+ const getRange = intg => {
204
+ const xL = intg?.xExtent?.xL ?? intg?.xL;
205
+ const xU = intg?.xExtent?.xU ?? intg?.xU;
206
+ return {
207
+ xL,
208
+ xU
209
+ };
210
+ };
211
+ if (remove) {
212
+ const {
213
+ xL: rmXL,
214
+ xU: rmXU
215
+ } = getRange(integration);
216
+ if (rmXL == null || rmXU == null) return state;
217
+ const stRm = action.meta?.skipUvvisHistory ? state : recordUvvisEditBefore(state);
218
+ const uvRm = stRm.uvvis;
219
+ const spListRm = uvRm.spectraList;
220
+ const specRm = spListRm[curveIdx];
221
+ const intRm = specRm.integrations || [];
222
+ const newIntegrations = intRm.filter(intg => {
223
+ const {
224
+ xL,
225
+ xU
226
+ } = getRange(intg);
227
+ if (xL == null || xU == null) return true;
228
+ return !(Math.abs(xL - rmXL) < 1e-6 && Math.abs(xU - rmXU) < 1e-6);
229
+ });
230
+ const newSpectrum = {
231
+ ...specRm,
232
+ integrations: newIntegrations
233
+ };
234
+ const newSpectraList = [...spListRm];
235
+ newSpectraList[curveIdx] = newSpectrum;
236
+ return {
237
+ ...stRm,
238
+ uvvis: {
239
+ ...uvRm,
240
+ spectraList: newSpectraList,
241
+ currentSpectrum: (0, _utils.normalizeSpectrumId)(uvRm.selectedWaveLength) === normalizedSpectrumId ? newSpectrum : uvRm.currentSpectrum
242
+ }
243
+ };
244
+ }
245
+ if (!integration) {
246
+ return state;
247
+ }
248
+ const {
249
+ xExtent,
250
+ data
251
+ } = integration;
252
+ if (!xExtent || !data || xExtent.xL == null || xExtent.xU == null || xExtent.xU === xExtent.xL) {
253
+ return state;
254
+ }
255
+ const stAdd = action.meta?.skipUvvisHistory ? state : recordUvvisEditBefore(state);
256
+ const uvAdd = stAdd.uvvis;
257
+ const spListAdd = uvAdd.spectraList;
258
+ const specAdd = spListAdd[curveIdx];
259
+ const integrationsAdd = specAdd.integrations || [];
260
+ const {
261
+ xL,
262
+ xU
263
+ } = xExtent;
264
+ const area = (0, _integration.getArea)(xL, xU, data);
265
+ const absoluteArea = (0, _integration.getAbsoluteArea)(xL, xU, data);
266
+ const isFirst = integrationsAdd.length === 0;
267
+ const newIntegration = {
268
+ xL: xL + shift,
269
+ xU: xU + shift,
270
+ area,
271
+ absoluteArea,
272
+ refArea: isFirst ? area : integrationsAdd[0]?.refArea ?? area,
273
+ xExtent,
274
+ data
275
+ };
276
+ const newIntegrations = [...integrationsAdd, newIntegration];
277
+ const newSpectrum = {
278
+ ...specAdd,
279
+ integrations: newIntegrations
280
+ };
281
+ const newSpectraList = [...spListAdd];
282
+ newSpectraList[curveIdx] = newSpectrum;
283
+ return {
284
+ ...stAdd,
285
+ uvvis: {
286
+ ...uvAdd,
287
+ spectraList: newSpectraList,
288
+ currentSpectrum: (0, _utils.normalizeSpectrumId)(uvAdd.selectedWaveLength) === normalizedSpectrumId ? newSpectrum : uvAdd.currentSpectrum
289
+ }
290
+ };
291
+ };
292
+ exports.updateHplcMsIntegrations = updateHplcMsIntegrations;
293
+ const removeHplcMsPeak = (state, action) => {
294
+ const {
295
+ spectrumId,
296
+ peak
297
+ } = action.payload || {};
298
+ const {
299
+ uvvis
300
+ } = state;
301
+ const {
302
+ spectraList = [],
303
+ listWaveLength = []
304
+ } = uvvis;
305
+ const normalizedSpectrumId = (0, _utils.normalizeSpectrumId)(spectrumId);
306
+ const index = listWaveLength.map(waveLength => (0, _utils.normalizeSpectrumId)(waveLength)).indexOf(normalizedSpectrumId);
307
+ if (index === -1) return state;
308
+ const spectrum = spectraList[index];
309
+ if (!spectrum || !Array.isArray(spectrum.peaks) || !peak) return state;
310
+ const stPk = action.meta?.skipUvvisHistory ? state : recordUvvisEditBefore(state);
311
+ const uvPk = stPk.uvvis;
312
+ const spListPk = uvPk.spectraList;
313
+ const spectrumPk = spListPk[index];
314
+ const filteredPeaks = spectrumPk.peaks.filter(p => !(Math.abs(p.x - peak.x) < 1e-6 && Math.abs(p.y - peak.y) < 1e-6));
315
+ const updatedSpectrum = {
316
+ ...spectrumPk,
317
+ peaks: filteredPeaks
318
+ };
319
+ const updatedSpectraList = [...spListPk];
320
+ updatedSpectraList[index] = updatedSpectrum;
321
+ return {
322
+ ...stPk,
323
+ uvvis: {
324
+ ...uvPk,
325
+ spectraList: updatedSpectraList,
326
+ currentSpectrum: (0, _utils.normalizeSpectrumId)(uvPk.selectedWaveLength) === normalizedSpectrumId ? updatedSpectrum : uvPk.currentSpectrum
327
+ }
328
+ };
329
+ };
330
+ exports.removeHplcMsPeak = removeHplcMsPeak;
331
+ const clearIntegrationAllHplcMs = (state, action = {}) => {
332
+ const {
333
+ uvvis
334
+ } = state;
335
+ if (!uvvis || !Array.isArray(uvvis.spectraList)) {
336
+ return state;
337
+ }
338
+ const stCl = action.meta?.skipUvvisHistory ? state : recordUvvisEditBefore(state);
339
+ const uvCl = stCl.uvvis;
340
+ const newSpectraList = uvCl.spectraList.map(spectrum => ({
341
+ ...spectrum,
342
+ integrations: []
343
+ }));
344
+ const newUvvis = {
345
+ ...uvCl,
346
+ spectraList: newSpectraList,
347
+ currentSpectrum: newSpectraList[uvCl.wavelengthIdx] || null
348
+ };
349
+ return {
350
+ ...stCl,
351
+ uvvis: newUvvis
352
+ };
353
+ };
354
+ exports.clearIntegrationAllHplcMs = clearIntegrationAllHplcMs;
355
+ const clearAllPeaksHplcMs = (state, action = {}) => {
356
+ const {
357
+ uvvis
358
+ } = state;
359
+ if (!uvvis || !Array.isArray(uvvis.spectraList)) {
360
+ return state;
361
+ }
362
+ const stPkAll = action.meta?.skipUvvisHistory ? state : recordUvvisEditBefore(state);
363
+ const uvPkAll = stPkAll.uvvis;
364
+ const newSpectraList = uvPkAll.spectraList.map(spectrum => ({
365
+ ...spectrum,
366
+ peaks: []
367
+ }));
368
+ return {
369
+ ...stPkAll,
370
+ uvvis: {
371
+ ...uvPkAll,
372
+ spectraList: newSpectraList,
373
+ currentSpectrum: newSpectraList[uvPkAll.wavelengthIdx] || null
374
+ }
375
+ };
376
+ };
377
+ exports.clearAllPeaksHplcMs = clearAllPeaksHplcMs;
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.default = void 0;
7
+ exports.integrationReducer = exports.default = void 0;
8
8
  var _reduxUndo = _interopRequireDefault(require("redux-undo"));
9
9
  var _action_type = require("../constants/action_type");
10
10
  var _integration = require("../helpers/integration");
@@ -37,7 +37,7 @@ const addToStack = (state, action) => {
37
37
  integrations
38
38
  } = state;
39
39
  let selectedIntegration = integrations[curveIdx];
40
- if (selectedIntegration === false || selectedIntegration === undefined) {
40
+ if (!selectedIntegration) {
41
41
  selectedIntegration = defaultEmptyIntegration;
42
42
  }
43
43
  const {
@@ -53,7 +53,7 @@ const addToStack = (state, action) => {
53
53
  xL,
54
54
  xU
55
55
  } = xExtent;
56
- if (!xL || !xU || xU - xL === 0) {
56
+ if (!Number.isFinite(xL) || !Number.isFinite(xU) || xU - xL === 0) {
57
57
  return state;
58
58
  }
59
59
  const area = (0, _integration.getArea)(xL, xU, data);
@@ -76,6 +76,22 @@ const addToStack = (state, action) => {
76
76
  selectedIdx: curveIdx
77
77
  });
78
78
  };
79
+ const dropOrphanVisualSplitGroupIds = stack => {
80
+ const groupCounts = stack.reduce((acc, item) => {
81
+ const groupId = item && item.visualSplitGroupId;
82
+ if (groupId) acc[groupId] = (acc[groupId] || 0) + 1;
83
+ return acc;
84
+ }, {});
85
+ return stack.map(item => {
86
+ const groupId = item && item.visualSplitGroupId;
87
+ if (!groupId || groupCounts[groupId] > 1) return item;
88
+ const {
89
+ visualSplitGroupId,
90
+ ...rest
91
+ } = item;
92
+ return rest;
93
+ });
94
+ };
79
95
  const rmFromStack = (state, action) => {
80
96
  const {
81
97
  dataToRemove,
@@ -94,16 +110,251 @@ const rmFromStack = (state, action) => {
94
110
  stack
95
111
  } = selectedIntegration;
96
112
  let [txL, txU] = [0, 0];
97
- if (xL && xU) {
98
- // rm click integration
113
+ if (Number.isFinite(xL) && Number.isFinite(xU)) {
99
114
  [txL, txU] = [xL, xU];
100
115
  } else if (xExtent) {
101
- // rm click multiplicity
102
116
  [txL, txU] = [xExtent.xL, xExtent.xU];
103
117
  } else {
104
118
  return state;
105
119
  }
106
- const newStack = stack.filter(k => k.xL !== txL && k.xU !== txU);
120
+ const filteredStack = stack.filter(k => k.xL !== txL && k.xU !== txU);
121
+ const newStack = dropOrphanVisualSplitGroupIds(filteredStack);
122
+ const newIntegration = Object.assign({}, selectedIntegration, {
123
+ stack: newStack
124
+ });
125
+ const newArrIntegration = [...integrations];
126
+ newArrIntegration[curveIdx] = newIntegration;
127
+ return Object.assign({}, state, {
128
+ integrations: newArrIntegration,
129
+ selectedIdx: curveIdx
130
+ });
131
+ };
132
+ const hasEnoughDataResolution = (xL, xU, data) => {
133
+ const [lower, upper] = [xL, xU].sort((a, b) => a - b);
134
+ const points = data.filter(pt => pt && Number.isFinite(pt.x) && pt.x >= lower && pt.x <= upper);
135
+ if (points.length < 2) return false;
136
+ return points.some(pt => pt.x !== points[0].x);
137
+ };
138
+ const computeProportionalSplitAreas = (xL, splitX, xU, data, original) => {
139
+ const areaParts = (0, _integration.splitAreaProportionally)(original.area, (0, _integration.getArea)(xL, splitX, data), (0, _integration.getArea)(splitX, xU, data));
140
+ const absParts = (0, _integration.splitAreaProportionally)(original.absoluteArea, (0, _integration.getAbsoluteArea)(xL, splitX, data), (0, _integration.getAbsoluteArea)(splitX, xU, data));
141
+ return {
142
+ leftArea: areaParts.left,
143
+ rightArea: areaParts.right,
144
+ leftAbs: absParts.left,
145
+ rightAbs: absParts.right
146
+ };
147
+ };
148
+ const buildSplitStackItem = (xL, xU, shift, area, absoluteArea) => {
149
+ const [lower, upper] = [xL, xU].sort((a, b) => a - b);
150
+ return {
151
+ xL: lower + shift,
152
+ xU: upper + shift,
153
+ area,
154
+ absoluteArea
155
+ };
156
+ };
157
+ const getVisualSplitTolerance = (xL, xU) => Math.max(Math.abs(xU - xL) * 1e-6, Number.EPSILON);
158
+ const findTargetIntegrationIndex = (stack, target) => stack.findIndex(item => item.xL === target.xL && item.xU === target.xU);
159
+ const buildVisualSplitItem = (xL, xU, shift, area, absoluteArea, groupId) => {
160
+ const [lower, upper] = [xL, xU].sort((a, b) => a - b);
161
+ const item = {
162
+ xL: lower + shift,
163
+ xU: upper + shift,
164
+ area,
165
+ absoluteArea
166
+ };
167
+ if (groupId) item.visualSplitGroupId = groupId;
168
+ return item;
169
+ };
170
+ const isVisuallySplit = (stack, item) => {
171
+ if (!item || !item.visualSplitGroupId) return false;
172
+ return stack.some(other => other !== item && other.visualSplitGroupId === item.visualSplitGroupId);
173
+ };
174
+ const buildRawSplitPart = (xL, xU, shift, data) => buildSplitStackItem(xL, xU, shift, (0, _integration.getArea)(xL, xU, data), (0, _integration.getAbsoluteArea)(xL, xU, data));
175
+ const findVisualSplitNeighborhood = (stack, original, shift, xL, xU) => {
176
+ const groupId = original && original.visualSplitGroupId;
177
+ if (!groupId) return {
178
+ hasLeft: false,
179
+ hasRight: false
180
+ };
181
+ const tolerance = getVisualSplitTolerance(xL, xU);
182
+ const isGroupSibling = item => item !== original && item.visualSplitGroupId === groupId;
183
+ return {
184
+ hasLeft: stack.some(item => isGroupSibling(item) && Math.abs(item.xU - shift - xL) <= tolerance),
185
+ hasRight: stack.some(item => isGroupSibling(item) && Math.abs(item.xL - shift - xU) <= tolerance)
186
+ };
187
+ };
188
+ const buildSplitParts = (original, xL, splitX, xU, shift, data, stack) => {
189
+ if (!original.visualSplitGroupId) {
190
+ return [buildRawSplitPart(xL, splitX, shift, data), buildRawSplitPart(splitX, xU, shift, data)];
191
+ }
192
+ const {
193
+ hasLeft,
194
+ hasRight
195
+ } = findVisualSplitNeighborhood(stack, original, shift, xL, xU);
196
+ if (!hasLeft && !hasRight) {
197
+ return [buildRawSplitPart(xL, splitX, shift, data), buildRawSplitPart(splitX, xU, shift, data)];
198
+ }
199
+ const groupId = original.visualSplitGroupId;
200
+ const {
201
+ leftArea,
202
+ rightArea,
203
+ leftAbs,
204
+ rightAbs
205
+ } = computeProportionalSplitAreas(xL, splitX, xU, data, original);
206
+ const leftStaysInGroup = hasLeft;
207
+ const rightStaysInGroup = hasRight;
208
+ const leftPart = leftStaysInGroup ? buildVisualSplitItem(xL, splitX, shift, leftArea, leftAbs, groupId) : buildSplitStackItem(xL, splitX, shift, leftArea, leftAbs);
209
+ const rightPart = rightStaysInGroup ? buildVisualSplitItem(splitX, xU, shift, rightArea, rightAbs, groupId) : buildSplitStackItem(splitX, xU, shift, rightArea, rightAbs);
210
+ return [leftPart, rightPart];
211
+ };
212
+ const splitStack = (state, action) => {
213
+ const {
214
+ curveIdx,
215
+ target,
216
+ splitX,
217
+ data
218
+ } = action.payload;
219
+ if (!Number.isFinite(curveIdx) || !target || !Array.isArray(data)) {
220
+ return state;
221
+ }
222
+ const {
223
+ integrations
224
+ } = state;
225
+ const selectedIntegration = integrations[curveIdx];
226
+ if (!selectedIntegration) {
227
+ return state;
228
+ }
229
+ const {
230
+ stack,
231
+ shift
232
+ } = selectedIntegration;
233
+ const targetIndex = findTargetIntegrationIndex(stack, target);
234
+ if (targetIndex < 0 || !Number.isFinite(splitX)) {
235
+ return state;
236
+ }
237
+ const original = stack[targetIndex];
238
+ const [xL, xU] = [original.xL - shift, original.xU - shift].sort((a, b) => a - b);
239
+ if (!Number.isFinite(xL) || !Number.isFinite(xU) || splitX <= xL || splitX >= xU) {
240
+ return state;
241
+ }
242
+ if (!hasEnoughDataResolution(xL, splitX, data) || !hasEnoughDataResolution(splitX, xU, data)) {
243
+ return state;
244
+ }
245
+ const splitParts = buildSplitParts(original, xL, splitX, xU, shift, data, stack);
246
+ const [leftIntegration, rightIntegration] = splitParts;
247
+ const newStack = dropOrphanVisualSplitGroupIds([...stack.slice(0, targetIndex), leftIntegration, rightIntegration, ...stack.slice(targetIndex + 1)]);
248
+ const newIntegration = Object.assign({}, selectedIntegration, {
249
+ stack: newStack
250
+ });
251
+ const newArrIntegration = [...integrations];
252
+ newArrIntegration[curveIdx] = newIntegration;
253
+ return Object.assign({}, state, {
254
+ integrations: newArrIntegration,
255
+ selectedIdx: curveIdx
256
+ });
257
+ };
258
+ const addVisualSplitLine = (state, action) => {
259
+ const {
260
+ curveIdx,
261
+ target,
262
+ splitX,
263
+ data
264
+ } = action.payload;
265
+ if (!Number.isFinite(curveIdx) || !target || !Number.isFinite(splitX) || !Array.isArray(data)) {
266
+ return state;
267
+ }
268
+ const {
269
+ integrations
270
+ } = state;
271
+ const selectedIntegration = integrations[curveIdx];
272
+ if (!selectedIntegration) {
273
+ return state;
274
+ }
275
+ const {
276
+ stack,
277
+ shift
278
+ } = selectedIntegration;
279
+ const targetIndex = findTargetIntegrationIndex(stack, target);
280
+ if (targetIndex < 0) return state;
281
+ const original = stack[targetIndex];
282
+ if (original.visualSplitGroupId || isVisuallySplit(stack, original)) {
283
+ return state;
284
+ }
285
+ const [xL, xU] = [original.xL - shift, original.xU - shift].sort((a, b) => a - b);
286
+ const tolerance = getVisualSplitTolerance(xL, xU);
287
+ if (splitX <= xL + tolerance || splitX >= xU - tolerance) {
288
+ return state;
289
+ }
290
+ if (!hasEnoughDataResolution(xL, splitX, data) || !hasEnoughDataResolution(splitX, xU, data)) {
291
+ return state;
292
+ }
293
+ const groupId = (0, _integration.generateVisualSplitGroupId)();
294
+ const {
295
+ leftArea,
296
+ rightArea,
297
+ leftAbs,
298
+ rightAbs
299
+ } = computeProportionalSplitAreas(xL, splitX, xU, data, original);
300
+ const leftItem = buildVisualSplitItem(xL, splitX, shift, leftArea, leftAbs, groupId);
301
+ const rightItem = buildVisualSplitItem(splitX, xU, shift, rightArea, rightAbs, groupId);
302
+ if (leftItem.xL >= leftItem.xU || rightItem.xL >= rightItem.xU || leftItem.xU !== rightItem.xL) {
303
+ return state;
304
+ }
305
+ const newStack = [...stack.slice(0, targetIndex), leftItem, rightItem, ...stack.slice(targetIndex + 1)];
306
+ const newIntegration = Object.assign({}, selectedIntegration, {
307
+ stack: newStack
308
+ });
309
+ const newArrIntegration = [...integrations];
310
+ newArrIntegration[curveIdx] = newIntegration;
311
+ return Object.assign({}, state, {
312
+ integrations: newArrIntegration,
313
+ selectedIdx: curveIdx
314
+ });
315
+ };
316
+ const removeVisualSplitLine = (state, action) => {
317
+ const {
318
+ curveIdx,
319
+ splitX,
320
+ data
321
+ } = action.payload;
322
+ if (!Number.isFinite(curveIdx) || !Number.isFinite(splitX) || !Array.isArray(data)) {
323
+ return state;
324
+ }
325
+ const {
326
+ integrations
327
+ } = state;
328
+ const selectedIntegration = integrations[curveIdx];
329
+ if (!selectedIntegration) {
330
+ return state;
331
+ }
332
+ const {
333
+ stack,
334
+ shift
335
+ } = selectedIntegration;
336
+ if (!Array.isArray(stack) || stack.length < 2) return state;
337
+ const tolerance = getVisualSplitTolerance(Math.min(...stack.map(s => s.xL - shift)), Math.max(...stack.map(s => s.xU - shift)));
338
+ let mergeStartIdx = -1;
339
+ for (let i = 0; i < stack.length - 1; i += 1) {
340
+ const left = stack[i];
341
+ const right = stack[i + 1];
342
+ const gapTolerance = Math.max(tolerance, Math.abs(left.xU - right.xL));
343
+ if (left.visualSplitGroupId && left.visualSplitGroupId === right.visualSplitGroupId && Math.abs(left.xU - shift - splitX) <= gapTolerance && Math.abs(right.xL - shift - splitX) <= gapTolerance) {
344
+ mergeStartIdx = i;
345
+ break;
346
+ }
347
+ }
348
+ if (mergeStartIdx < 0) return state;
349
+ const leftItem = stack[mergeStartIdx];
350
+ const rightItem = stack[mergeStartIdx + 1];
351
+ const groupId = leftItem.visualSplitGroupId;
352
+ const mergedXL = Math.min(leftItem.xL, rightItem.xL) - shift;
353
+ const mergedXU = Math.max(leftItem.xU, rightItem.xU) - shift;
354
+ const remainingInGroup = stack.filter((item, idx) => idx !== mergeStartIdx && idx !== mergeStartIdx + 1 && item.visualSplitGroupId === groupId);
355
+ const keepGroupId = remainingInGroup.length > 0;
356
+ const mergedItem = buildVisualSplitItem(mergedXL, mergedXU, shift, (leftItem.area || 0) + (rightItem.area || 0), (leftItem.absoluteArea || 0) + (rightItem.absoluteArea || 0), keepGroupId ? groupId : null);
357
+ const newStack = [...stack.slice(0, mergeStartIdx), mergedItem, ...stack.slice(mergeStartIdx + 2)];
107
358
  const newIntegration = Object.assign({}, selectedIntegration, {
108
359
  stack: newStack
109
360
  });
@@ -214,6 +465,12 @@ const integrationReducer = (state = initialState, action) => {
214
465
  return addToStack(state, action);
215
466
  case _action_type.INTEGRATION.RM_ONE:
216
467
  return rmFromStack(state, action);
468
+ case _action_type.INTEGRATION.SPLIT:
469
+ return splitStack(state, action);
470
+ case _action_type.INTEGRATION.ADD_VISUAL_SPLIT:
471
+ return addVisualSplitLine(state, action);
472
+ case _action_type.INTEGRATION.RM_VISUAL_SPLIT:
473
+ return removeVisualSplitLine(state, action);
217
474
  case _action_type.INTEGRATION.SET_REF:
218
475
  return setRef(state, action);
219
476
  case _action_type.INTEGRATION.SET_FKR:
@@ -230,5 +487,6 @@ const integrationReducer = (state = initialState, action) => {
230
487
  return _undo_redo_config.undoRedoActions.indexOf(action.type) >= 0 ? Object.assign({}, state) : state;
231
488
  }
232
489
  };
490
+ exports.integrationReducer = integrationReducer;
233
491
  const undoableIntegrationReducer = (0, _reduxUndo.default)(integrationReducer, _undo_redo_config.undoRedoConfig);
234
492
  var _default = exports.default = undoableIntegrationReducer;
@@ -14,7 +14,7 @@ const layoutReducer = (state = initialState, action) => {
14
14
  case _action_type.LAYOUT.UPDATE:
15
15
  return action.payload;
16
16
  case _action_type.MANAGER.RESETALL:
17
- return action.payload.operation.layout;
17
+ return action.payload?.operation?.layout || state;
18
18
  default:
19
19
  return state;
20
20
  }