@complat/react-spectra-editor 1.7.1 → 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 +75 -26
  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
@@ -1,11 +1,9 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
6
  exports.default = void 0;
8
- var _jcampconverter = _interopRequireDefault(require("jcampconverter"));
9
7
  var _converter = require("./converter");
10
8
  var _list_layout = require("../constants/list_layout");
11
9
  var _multiplicity_calc = require("./multiplicity_calc");
@@ -25,6 +23,7 @@ const spectraDigit = layout => {
25
23
  case _list_layout.LIST_LAYOUT.CDS:
26
24
  case _list_layout.LIST_LAYOUT.SEC:
27
25
  case _list_layout.LIST_LAYOUT.GC:
26
+ case _list_layout.LIST_LAYOUT.LC_MS:
28
27
  case _list_layout.LIST_LAYOUT.MS:
29
28
  return 0;
30
29
  case _list_layout.LIST_LAYOUT.C13:
@@ -44,6 +43,16 @@ const fixDigit = (input, precision) => {
44
43
  const output = input || 0.0;
45
44
  return output.toFixed(precision);
46
45
  };
46
+
47
+ /**
48
+ * Integer 0–20 for toFixed; invalid input uses fallback.
49
+ * Default 3 preserves legacy cursor precision.
50
+ */
51
+ const clampDecimalPlaces = (value, fallback = 3) => {
52
+ const n = Number(value);
53
+ if (!Number.isFinite(n)) return fallback;
54
+ return Math.max(0, Math.min(20, Math.trunc(n)));
55
+ };
47
56
  const buildData = entity => {
48
57
  if (!entity) return {
49
58
  isExist: false
@@ -63,6 +72,28 @@ const toPeakStr = peaks => {
63
72
  const str = arr.join('#');
64
73
  return str;
65
74
  };
75
+ const extractUvvisLcmsPeaks = hplcMsSt => {
76
+ if (!hplcMsSt?.uvvis) {
77
+ return '{}';
78
+ }
79
+ const {
80
+ listWaveLength = [],
81
+ spectraList = []
82
+ } = hplcMsSt.uvvis;
83
+ const byWavelength = {};
84
+ spectraList.forEach((spectrum, idx) => {
85
+ const wl = listWaveLength[idx];
86
+ const wlKey = String(Math.round(wl));
87
+ (spectrum.peaks || []).forEach((p, n) => {
88
+ if (!byWavelength[wlKey]) byWavelength[wlKey] = [];
89
+ byWavelength[wlKey].push({
90
+ x: p.x,
91
+ y: p.y
92
+ });
93
+ });
94
+ });
95
+ return JSON.stringify(byWavelength);
96
+ };
66
97
  const spectraOps = {
67
98
  [_list_layout.LIST_LAYOUT.PLAIN]: {
68
99
  head: '',
@@ -147,6 +178,10 @@ const spectraOps = {
147
178
  [_list_layout.LIST_LAYOUT.DLS_INTENSITY]: {
148
179
  head: 'DLS',
149
180
  tail: '.'
181
+ },
182
+ [_list_layout.LIST_LAYOUT.LC_MS]: {
183
+ head: 'HPLC UV/VIS',
184
+ tail: ''
150
185
  }
151
186
  };
152
187
  const rmRef = (peaks, shift, atIndex = 0) => {
@@ -158,6 +193,147 @@ const rmRef = (peaks, shift, atIndex = 0) => {
158
193
  const refValue = selectedShift.ref.value || selectedShift.peak.x;
159
194
  return peaks.map(p => (0, _converter.IsSame)(p.x, refValue) ? null : p).filter(r => r != null);
160
195
  };
196
+ const isValidLcmsWavelengthLabel = wl => Number.isFinite(wl) && wl > 0;
197
+ const LCMS_INTEGRATION_EXPORT_MODES = ['percent', 'area', 'both'];
198
+ const isLcmsIntegrationExportMode = v => LCMS_INTEGRATION_EXPORT_MODES.includes(v);
199
+ const formatedLCMS = (hplcMsSt, isAscend, decimal, options = {}) => {
200
+ if (!hplcMsSt?.uvvis) {
201
+ return '';
202
+ }
203
+ const {
204
+ lcmsIntegrationsExport: modeOpt,
205
+ includeIntegrationArea = false
206
+ } = options;
207
+ let lcmsIntegrationsExport = modeOpt;
208
+ if (!isLcmsIntegrationExportMode(lcmsIntegrationsExport)) {
209
+ lcmsIntegrationsExport = hplcMsSt?.lcmsIntegrationsExport;
210
+ }
211
+ if (!isLcmsIntegrationExportMode(lcmsIntegrationsExport)) {
212
+ lcmsIntegrationsExport = includeIntegrationArea ? 'both' : 'percent';
213
+ }
214
+ let result = '\n';
215
+ const sections = [];
216
+ const parsedDecimal = Number.isFinite(decimal) ? decimal : Number.parseInt(decimal, 10);
217
+ const resolvedDecimal = Number.isFinite(parsedDecimal) ? parsedDecimal : 3;
218
+ const {
219
+ listWaveLength = [],
220
+ spectraList = []
221
+ } = hplcMsSt.uvvis || {};
222
+ const ms = hplcMsSt.ms || {};
223
+ const tic = hplcMsSt.tic;
224
+ const threshold = hplcMsSt.threshold;
225
+ listWaveLength.forEach((wavelength, idx) => {
226
+ const spectrum = spectraList[idx];
227
+ if (!spectrum) {
228
+ return;
229
+ }
230
+ const peaks = spectrum.peaks || [];
231
+ const integrations = spectrum.integrations || [];
232
+ const stack = integrations?.[0]?.stack;
233
+ const hasStack = Array.isArray(stack) && stack.length > 0;
234
+ const hasList = !hasStack && Array.isArray(integrations) && integrations.length > 0;
235
+ const hasIntegrations = hasStack || hasList;
236
+ const hasPeaks = peaks.length > 0;
237
+ if (!hasPeaks && !hasIntegrations) {
238
+ return;
239
+ }
240
+ const lines = [];
241
+ if (isValidLcmsWavelengthLabel(wavelength)) {
242
+ lines.push(`Wavelength ${wavelength} nm:`);
243
+ }
244
+ if (hasPeaks) {
245
+ const sortedPeaks = [...peaks].sort((a, b) => b.y - a.y);
246
+ const maxIntensity = sortedPeaks[0].y || 1;
247
+ const peakLines = sortedPeaks.map(peak => {
248
+ const rt = peak.x.toFixed(resolvedDecimal);
249
+ const percent = (peak.y / maxIntensity * 100).toFixed(1);
250
+ return `${rt} min (${percent}%)`;
251
+ });
252
+ lines.push(`Peaks: ${peakLines.join(', ')}`);
253
+ }
254
+ if (hasIntegrations) {
255
+ const entries = hasStack ? stack : integrations;
256
+ const refAreaCandidate = hasStack ? integrations?.[0]?.refArea : integrations?.[0]?.refArea ?? integrations?.[0]?.area;
257
+ const refArea = refAreaCandidate && refAreaCandidate > 0 ? refAreaCandidate : 1;
258
+ const sortedIntegrations = [...entries].sort((a, b) => a.xL - b.xL);
259
+ const integrationLines = sortedIntegrations.map(integ => {
260
+ const rt = integ.xL.toFixed(resolvedDecimal);
261
+ const area = integ.area || integ.absoluteArea || 0;
262
+ const percent = (area / refArea * 100).toFixed(1);
263
+ const areaStr = Number.isFinite(area) ? area.toExponential(3) : String(area);
264
+ if (lcmsIntegrationsExport === 'area') {
265
+ return `${rt} min (A=${areaStr})`;
266
+ }
267
+ if (lcmsIntegrationsExport === 'both') {
268
+ return `${rt} min (${percent}%, A=${areaStr})`;
269
+ }
270
+ return `${rt} min (${percent}%)`;
271
+ });
272
+ lines.push(`Integrations: ${integrationLines.join(', ')}`);
273
+ }
274
+ sections.push(lines.join('\n'));
275
+ });
276
+ if (sections.length > 0) {
277
+ result = `\n${sections.join('\n\n')}`;
278
+ }
279
+ const polarity = tic?.polarity || 'positive';
280
+ let polarityKey = 'neutral';
281
+ if (polarity === 'negative') {
282
+ polarityKey = 'negative';
283
+ } else if (polarity === 'positive') {
284
+ polarityKey = 'positive';
285
+ }
286
+ let polarityLabel = '';
287
+ if (polarity === 'negative') {
288
+ polarityLabel = '−';
289
+ } else if (polarity === 'positive') {
290
+ polarityLabel = '+';
291
+ }
292
+ if (tic && ms[polarityKey]) {
293
+ const pageValues = Array.isArray(ms[polarityKey].pageValues) ? ms[polarityKey].pageValues : [];
294
+ const peaksByPage = Array.isArray(ms[polarityKey].peaks) ? ms[polarityKey].peaks : [];
295
+ let currentIndex = -1;
296
+ if (Number.isFinite(tic.currentPageValue) && pageValues.length > 0) {
297
+ currentIndex = pageValues.findIndex(value => Number.isFinite(value) && Math.abs(value - tic.currentPageValue) < 1e-6);
298
+ }
299
+ if (Array.isArray(tic[polarityKey]?.data?.x)) {
300
+ const fallbackIndex = tic[polarityKey].data.x.findIndex(x => Math.abs(x - tic.currentPageValue) < 1e-6);
301
+ if (currentIndex < 0) currentIndex = fallbackIndex;
302
+ }
303
+ if (currentIndex >= 0 && peaksByPage[currentIndex]) {
304
+ const peaks = peaksByPage[currentIndex];
305
+ const maxIntensity = Math.max(...peaks.map(p => p.y)) || 1;
306
+ const thresholdValue = threshold?.value != null ? threshold.value : 5;
307
+ const filtered = peaks.filter(peak => peak.y / maxIntensity * 100 >= thresholdValue);
308
+ const sortedPeaks = [...filtered].sort((a, b) => {
309
+ if (isAscend) {
310
+ return parseFloat(a.x) - parseFloat(b.x);
311
+ }
312
+ return parseFloat(b.x) - parseFloat(a.x);
313
+ });
314
+ const label = polarityLabel ? `${polarityLabel}ESI` : 'ESI';
315
+ if (result) {
316
+ result += '\n\n';
317
+ }
318
+ const rtPage = tic?.currentPageValue;
319
+ const rtDecimal = Math.max(resolvedDecimal, 3);
320
+ const rtPart = Number.isFinite(rtPage) ? `RT ${fixDigit(rtPage, rtDecimal)} min, ` : '';
321
+ result += `MS (${label}), ${rtPart}m/z (≥${thresholdValue}%):\n`;
322
+ const lines = sortedPeaks.map(peak => {
323
+ const mass = fixDigit(peak.x, resolvedDecimal);
324
+ const percent = Math.round(peak.y / maxIntensity * 100);
325
+ return `${mass} (${percent}%)`;
326
+ });
327
+ result += ` ${lines.join(', ')}`;
328
+ } else {
329
+ if (result) {
330
+ result += '\n\n';
331
+ }
332
+ result += 'MS: No data for current retention time.\n';
333
+ }
334
+ }
335
+ return result;
336
+ };
161
337
  const formatedMS = (peaks, maxY, decimal = 2, isAscend = true) => {
162
338
  const ascendFunc = (a, b) => parseFloat(a) - parseFloat(b);
163
339
  const descendFunc = (a, b) => parseFloat(b) - parseFloat(a);
@@ -365,7 +541,8 @@ const peaksBody = ({
365
541
  integration,
366
542
  atIndex = 0,
367
543
  waveLength,
368
- temperature
544
+ temperature,
545
+ hplcMsSt = null
369
546
  }) => {
370
547
  const result = rmShiftFromPeaks(peaks, shift, atIndex);
371
548
  const ascendFunc = (a, b) => parseFloat(a.x) - parseFloat(b.x);
@@ -373,6 +550,9 @@ const peaksBody = ({
373
550
  const sortFunc = isAscend ? ascendFunc : descendFunc;
374
551
  const ordered = result.sort(sortFunc);
375
552
  const maxY = Math.max(...ordered.map(o => o.y));
553
+ if (layout === _list_layout.LIST_LAYOUT.LC_MS) {
554
+ return formatedLCMS(hplcMsSt, isAscend, decimal);
555
+ }
376
556
  if (layout === _list_layout.LIST_LAYOUT.MS) {
377
557
  return formatedMS(ordered, maxY, decimal, isAscend);
378
558
  }
@@ -445,6 +625,7 @@ const isCyclicVoltaLayout = layoutSt => _list_layout.LIST_LAYOUT.CYCLIC_VOLTAMME
445
625
  const isCDSLayout = layoutSt => _list_layout.LIST_LAYOUT.CDS === layoutSt;
446
626
  const isSECLayout = layoutSt => _list_layout.LIST_LAYOUT.SEC === layoutSt;
447
627
  const isGCLayout = layoutSt => _list_layout.LIST_LAYOUT.GC === layoutSt;
628
+ const isLCMsLayout = layoutSt => _list_layout.LIST_LAYOUT.LC_MS === layoutSt;
448
629
  const isEmWaveLayout = layoutSt => [_list_layout.LIST_LAYOUT.IR, _list_layout.LIST_LAYOUT.RAMAN, _list_layout.LIST_LAYOUT.UVVIS, _list_layout.LIST_LAYOUT.HPLC_UVVIS].indexOf(layoutSt) >= 0;
449
630
  const hasMultiCurves = layoutSt => [_list_layout.LIST_LAYOUT.CYCLIC_VOLTAMMETRY, _list_layout.LIST_LAYOUT.SEC, _list_layout.LIST_LAYOUT.GC, _list_layout.LIST_LAYOUT.AIF].indexOf(layoutSt) >= 0;
450
631
  const isAIFLayout = layoutSt => _list_layout.LIST_LAYOUT.AIF === layoutSt;
@@ -609,6 +790,7 @@ const inlineNotation = (layout, data, sampleName = '') => {
609
790
  };
610
791
  const Format = {
611
792
  toPeakStr,
793
+ extractUvvisLcmsPeaks,
612
794
  buildData,
613
795
  spectraDigit,
614
796
  spectraOps,
@@ -638,7 +820,9 @@ const Format = {
638
820
  isDLSIntensityLayout,
639
821
  isEmWaveLayout,
640
822
  isGCLayout,
823
+ isLCMsLayout,
641
824
  fixDigit,
825
+ clampDecimalPlaces,
642
826
  formatPeaksByPrediction,
643
827
  formatedMS,
644
828
  formatedEm,
@@ -651,6 +835,7 @@ const Format = {
651
835
  strNumberFixedDecimal,
652
836
  formatedXRD,
653
837
  strNumberFixedLength,
654
- inlineNotation
838
+ inlineNotation,
839
+ formatedLCMS
655
840
  };
656
841
  var _default = exports.default = Format;
@@ -55,13 +55,33 @@ const tpDiv = (d, digits, yFactor = 1) => `
55
55
  <span> y: ${d3.format('.2~e')(d.y * (yFactor || 1))}</span>
56
56
  <div>
57
57
  `;
58
+ const msPeakTpDiv = (d, relInt, digits) => `
59
+ <div
60
+ class="peak-tp"
61
+ style="${tpStyle()}"
62
+ >
63
+ <span>${_format.default.fixDigit(d.x, digits)} (${relInt})</span>
64
+ <div>
65
+ `;
66
+ const resolveDigits = (layout, xDigits) => xDigits != null && xDigits !== '' ? _format.default.clampDecimalPlaces(xDigits, _format.default.spectraDigit(layout)) : _format.default.spectraDigit(layout);
67
+ const peakTipHtml = ({
68
+ d,
69
+ layout,
70
+ yFactor,
71
+ xDigits,
72
+ msMaxY
73
+ }) => {
74
+ const digits = resolveDigits(layout, xDigits);
75
+ if (_format.default.isMsLayout(layout) && msMaxY > 0) {
76
+ const relPct = 100 * d.y / msMaxY;
77
+ const rel = parseInt(relPct, 10);
78
+ return msPeakTpDiv(d, rel, digits);
79
+ }
80
+ return tpDiv(d, digits, yFactor || 1);
81
+ };
58
82
  const InitTip = () => {
59
83
  d3.select('.peak-tp').remove();
60
- const tip = (0, _d3Tip.default)().attr('class', 'd3-tip').html(({
61
- d,
62
- layout,
63
- yFactor
64
- }) => tpDiv(d, _format.default.spectraDigit(layout), yFactor || 1));
84
+ const tip = (0, _d3Tip.default)().attr('class', 'd3-tip').html(peakTipHtml);
65
85
  return tip;
66
86
  };
67
87
  exports.InitTip = InitTip;
@@ -3,11 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getArea = exports.getAbsoluteArea = exports.calcArea = void 0;
6
+ exports.splitAreaProportionally = exports.normalizeSplitLines = exports.getVisualSplitGroups = exports.getVisualSplitGroupBoundaries = exports.getSplitAreas = exports.getLinearBaseline = exports.getIntegrationPoints = exports.getArea = exports.getAbsoluteAreaWithBaseline = exports.getAbsoluteArea = exports.generateVisualSplitGroupId = exports.calcArea = exports.buildSplitIntervals = void 0;
7
7
  var _calc = require("./calc");
8
8
  /* eslint-disable no-mixed-operators */
9
9
 
10
10
  const getArea = (xL, xU, data) => {
11
+ if (!Array.isArray(data) || data.length === 0) return NaN;
11
12
  let [iL, iU] = [data.length - 1, 0];
12
13
  for (let i = 0; i < data.length; i += 1) {
13
14
  const pt = data[i];
@@ -23,27 +24,68 @@ const getArea = (xL, xU, data) => {
23
24
  return Math.abs(data[iU].k - data[iL].k);
24
25
  };
25
26
  exports.getArea = getArea;
26
- const getAbsoluteArea = (xL, xU, data) => {
27
- const ps = data.filter(d => d.x > xL && d.x < xU);
28
- if (!ps[0]) return 0;
29
- let area = 0;
30
- const point1 = ps[0];
31
- const point2 = ps[ps.length - 1];
27
+ const getIntegrationPoints = (xL, xU, data) => {
28
+ if (!Array.isArray(data)) return [];
29
+ return data.filter(d => d && Number.isFinite(d.x) && Number.isFinite(d.y) && d.x > xL && d.x < xU);
30
+ };
31
+ exports.getIntegrationPoints = getIntegrationPoints;
32
+ const getLinearBaseline = points => {
33
+ if (!points || !points[0]) return () => 0;
34
+ const point1 = points[0];
35
+ const point2 = points[points.length - 1];
32
36
  const slope = (0, _calc.calcSlope)(point1.x, point1.y, point2.x, point2.y);
33
- let lastDY = point1.y;
34
- if (ps.length > 1) {
35
- for (let i = 1; i < ps.length; i += 1) {
36
- const pt = ps[i];
37
- const lastD = ps[i - 1];
38
- const y = slope * (pt.x - lastD.x) + lastDY;
39
- lastDY = y;
40
- const delta = Math.abs(pt.y - y);
41
- area += delta;
37
+ return point => point1.y + slope * (point.x - point1.x);
38
+ };
39
+ exports.getLinearBaseline = getLinearBaseline;
40
+ const normalizeSplitLines = splitLines => {
41
+ if (!Array.isArray(splitLines)) return [];
42
+ const finiteValues = splitLines.map(value => parseFloat(value)).filter(value => Number.isFinite(value));
43
+ return [...new Set(finiteValues)].sort((a, b) => a - b);
44
+ };
45
+ exports.normalizeSplitLines = normalizeSplitLines;
46
+ const buildSplitIntervals = (xL, xU, splitLines = []) => {
47
+ if (!Number.isFinite(xL) || !Number.isFinite(xU)) return [];
48
+ const [lower, upper] = [xL, xU].sort((a, b) => a - b);
49
+ if (lower === upper) return [];
50
+ const sortedSplitLines = normalizeSplitLines(splitLines).filter(splitX => splitX > lower && splitX < upper);
51
+ return [lower, ...sortedSplitLines, upper].slice(1).map((right, index, bounds) => ({
52
+ xL: index === 0 ? lower : bounds[index - 1],
53
+ xU: right
54
+ }));
55
+ };
56
+ exports.buildSplitIntervals = buildSplitIntervals;
57
+ const getAbsoluteAreaWithBaseline = (xL, xU, data, baselineY) => {
58
+ const ps = getIntegrationPoints(xL, xU, data);
59
+ if (ps.length < 2 || typeof baselineY !== 'function') return 0;
60
+ let area = 0;
61
+ for (let i = 1; i < ps.length; i += 1) {
62
+ const pt = ps[i];
63
+ const baselineValue = baselineY(pt);
64
+ if (Number.isFinite(baselineValue)) {
65
+ area += Math.abs(pt.y - baselineValue);
42
66
  }
43
67
  }
44
68
  return area;
45
69
  };
70
+ exports.getAbsoluteAreaWithBaseline = getAbsoluteAreaWithBaseline;
71
+ const getAbsoluteArea = (xL, xU, data) => {
72
+ const points = getIntegrationPoints(xL, xU, data);
73
+ const baselineY = getLinearBaseline(points);
74
+ return getAbsoluteAreaWithBaseline(xL, xU, data, baselineY);
75
+ };
46
76
  exports.getAbsoluteArea = getAbsoluteArea;
77
+ const getSplitAreas = (xL, xU, splitLines, data) => {
78
+ const intervals = buildSplitIntervals(xL, xU, splitLines);
79
+ const mainPoints = getIntegrationPoints(xL, xU, data);
80
+ const baselineY = getLinearBaseline(mainPoints);
81
+ return intervals.map(interval => ({
82
+ xL: interval.xL,
83
+ xU: interval.xU,
84
+ area: getArea(interval.xL, interval.xU, data),
85
+ absoluteArea: getAbsoluteAreaWithBaseline(interval.xL, interval.xU, data, baselineY)
86
+ }));
87
+ };
88
+ exports.getSplitAreas = getSplitAreas;
47
89
  const calcArea = (d, refArea, refFactor, ignoreRef = false) => {
48
90
  if (ignoreRef) {
49
91
  const {
@@ -53,6 +95,77 @@ const calcArea = (d, refArea, refFactor, ignoreRef = false) => {
53
95
  }
54
96
  return (d.area * refFactor / refArea).toFixed(2);
55
97
  };
98
+ exports.calcArea = calcArea;
99
+ const splitAreaProportionally = (totalArea, leftRaw, rightRaw) => {
100
+ const safeTotal = Number.isFinite(totalArea) ? totalArea : 0;
101
+ if (safeTotal === 0) return {
102
+ left: 0,
103
+ right: 0
104
+ };
105
+ const safeLeft = Number.isFinite(leftRaw) ? Math.max(leftRaw, 0) : 0;
106
+ const safeRight = Number.isFinite(rightRaw) ? Math.max(rightRaw, 0) : 0;
107
+ const rawSum = safeLeft + safeRight;
108
+ if (rawSum <= 0) {
109
+ const half = safeTotal / 2;
110
+ return {
111
+ left: half,
112
+ right: half
113
+ };
114
+ }
115
+ const ratio = safeTotal / rawSum;
116
+ return {
117
+ left: safeLeft * ratio,
118
+ right: safeRight * ratio
119
+ };
120
+ };
121
+ exports.splitAreaProportionally = splitAreaProportionally;
122
+ let visualSplitGroupCounter = 0;
123
+ const generateVisualSplitGroupId = () => {
124
+ visualSplitGroupCounter += 1;
125
+ const seed = typeof Date !== 'undefined' && Date.now ? Date.now().toString(36) : Math.random().toString(36).slice(2);
126
+ return `vsg-${seed}-${visualSplitGroupCounter}`;
127
+ };
128
+ exports.generateVisualSplitGroupId = generateVisualSplitGroupId;
129
+ const getVisualSplitGroups = (stack = []) => {
130
+ if (!Array.isArray(stack) || stack.length === 0) return [];
131
+ const groups = [];
132
+ let current = null;
133
+ stack.forEach(item => {
134
+ const groupId = item && item.visualSplitGroupId;
135
+ if (groupId && current && current.groupId === groupId) {
136
+ current.items.push(item);
137
+ current.xL = Math.min(current.xL, item.xL);
138
+ current.xU = Math.max(current.xU, item.xU);
139
+ } else {
140
+ current = {
141
+ groupId: groupId || null,
142
+ items: [item],
143
+ xL: item.xL,
144
+ xU: item.xU,
145
+ isMerged: false
146
+ };
147
+ groups.push(current);
148
+ }
149
+ });
150
+ groups.forEach(group => {
151
+ // eslint-disable-next-line no-param-reassign
152
+ group.isMerged = !!group.groupId && group.items.length > 1;
153
+ });
154
+ return groups;
155
+ };
156
+ exports.getVisualSplitGroups = getVisualSplitGroups;
157
+ const getVisualSplitGroupBoundaries = group => {
158
+ if (!group || !group.isMerged) return [];
159
+ const sortedItems = [...group.items].sort((a, b) => a.xL - b.xL);
160
+ const boundaries = [];
161
+ for (let i = 0; i < sortedItems.length - 1; i += 1) {
162
+ const current = sortedItems[i];
163
+ const next = sortedItems[i + 1];
164
+ const boundary = (current.xU + next.xL) / 2;
165
+ if (Number.isFinite(boundary)) boundaries.push(boundary);
166
+ }
167
+ return boundaries;
168
+ };
56
169
 
57
170
  // eslint-disable-line
58
- exports.calcArea = calcArea;
171
+ exports.getVisualSplitGroupBoundaries = getVisualSplitGroupBoundaries;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.setPendingIntegrationDraft = exports.isDraftForCurve = exports.hasPendingIntegrationDraft = exports.forgetPendingIntegrationDraft = exports.confirmCancelPendingIntegration = exports.clearPendingIntegrationDraft = void 0;
7
+ let pendingIntegrationDraft = null;
8
+ const cancelMessage = 'You are currently creating an integration. Are you sure you want to cancel it?';
9
+ const hasPendingIntegrationDraft = () => !!pendingIntegrationDraft;
10
+ exports.hasPendingIntegrationDraft = hasPendingIntegrationDraft;
11
+ const setPendingIntegrationDraft = draft => {
12
+ pendingIntegrationDraft = draft;
13
+ };
14
+ exports.setPendingIntegrationDraft = setPendingIntegrationDraft;
15
+ const forgetPendingIntegrationDraft = () => {
16
+ pendingIntegrationDraft = null;
17
+ };
18
+ exports.forgetPendingIntegrationDraft = forgetPendingIntegrationDraft;
19
+ const clearPendingIntegrationDraft = () => {
20
+ const draft = pendingIntegrationDraft;
21
+ pendingIntegrationDraft = null;
22
+ if (draft && typeof draft.cancel === 'function') {
23
+ draft.cancel();
24
+ }
25
+ };
26
+ exports.clearPendingIntegrationDraft = clearPendingIntegrationDraft;
27
+ const confirmCancelPendingIntegration = () => {
28
+ if (!hasPendingIntegrationDraft()) return true;
29
+ const shouldCancel = typeof window === 'undefined' || typeof window.confirm !== 'function'
30
+ // This confirmation intentionally protects an in-progress two-click integration.
31
+ // eslint-disable-next-line no-alert
32
+ || window.confirm(cancelMessage);
33
+ if (shouldCancel) {
34
+ clearPendingIntegrationDraft();
35
+ }
36
+ return shouldCancel;
37
+ };
38
+ exports.confirmCancelPendingIntegration = confirmCancelPendingIntegration;
39
+ const isDraftForCurve = (jcampIdx, dataLength) => {
40
+ if (!pendingIntegrationDraft) return false;
41
+ return pendingIntegrationDraft.jcampIdx === jcampIdx && pendingIntegrationDraft.dataLength === dataLength;
42
+ };
43
+ exports.isDraftForCurve = isDraftForCurve;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.handleVisualSplitLineClick = exports.handleIntegrationMouseMove = exports.handleIntegrationClick = exports.drawVisualSplitLinesForFocus = exports.drawIntegrationAUC = exports.buildIntegrationGroups = exports.buildIntegrationBarData = exports.bindSplitMouseHandlers = void 0;
8
+ var _integration = require("./integration");
9
+ var _focus = require("./focus");
10
+ var _compass = require("./compass");
11
+ var _cfg = _interopRequireDefault(require("./cfg"));
12
+ var _integration_split = require("./integration_split");
13
+ const d3 = require('d3');
14
+ const buildIntegrationGroups = (stack = []) => (0, _integration.getVisualSplitGroups)(stack).map(group => ({
15
+ xL: group.xL,
16
+ xU: group.xU,
17
+ isMerged: group.isMerged,
18
+ groupId: group.groupId,
19
+ target: group.items[0]
20
+ }));
21
+ exports.buildIntegrationGroups = buildIntegrationGroups;
22
+ const buildIntegrationBarData = (layout, stack = []) => {
23
+ const showIntegSplit = _cfg.default.showIntegSplitTools(layout);
24
+ const igGroups = buildIntegrationGroups(stack);
25
+ return {
26
+ showIntegSplit,
27
+ igGroups,
28
+ igBarData: showIntegSplit ? igGroups : stack
29
+ };
30
+ };
31
+ exports.buildIntegrationBarData = buildIntegrationBarData;
32
+ const handleIntegrationMouseMove = (focus, event, data, shift, ignoreRef) => {
33
+ if (!focus.isUiSplitIntgSt && !focus.isUiVisualSplitIntgSt) return;
34
+ if (focus.isUiVisualSplitIntgSt && (0, _integration_split.isAlreadyVisuallySplit)(data)) {
35
+ (0, _integration_split.clearIntegrationSplitPreview)(focus);
36
+ return;
37
+ }
38
+ if (focus.isUiSplitIntgSt && (0, _integration_split.isMergedVisualSplitGroup)(data)) {
39
+ (0, _integration_split.clearIntegrationSplitPreview)(focus);
40
+ return;
41
+ }
42
+ const splitX = (0, _integration_split.getSplitXFromEvent)(event, focus);
43
+ const previewTarget = (0, _integration_split.resolveSplitTarget)(focus, data, splitX) || data;
44
+ (0, _integration_split.drawIntegrationSplitPreview)(focus, previewTarget, splitX, shift, ignoreRef);
45
+ };
46
+ exports.handleIntegrationMouseMove = handleIntegrationMouseMove;
47
+ const handleIntegrationClick = (focus, event, data, fallbackClick) => {
48
+ if (!focus.isUiSplitIntgSt && !focus.isUiVisualSplitIntgSt) {
49
+ if (typeof fallbackClick === 'function') fallbackClick(event, data);
50
+ return;
51
+ }
52
+ event.stopPropagation();
53
+ event.preventDefault();
54
+ const splitX = (0, _integration_split.getSplitXFromEvent)(event, focus);
55
+ (0, _integration_split.clearIntegrationSplitPreview)(focus);
56
+ const target = (0, _integration_split.resolveSplitTarget)(focus, data, splitX);
57
+ if (!target) return;
58
+ if (focus.isUiVisualSplitIntgSt) {
59
+ const {
60
+ stack = [],
61
+ shift = 0
62
+ } = focus.integrationSplitTargets || {};
63
+ const existingSplitX = (0, _integration_split.getVisualSplitLineAtX)(focus, stack, splitX, shift);
64
+ if (Number.isFinite(existingSplitX)) {
65
+ if (typeof focus.removeVisualSplitLineAct !== 'function') return;
66
+ focus.removeVisualSplitLineAct({
67
+ curveIdx: focus.jcampIdx,
68
+ splitX: existingSplitX,
69
+ data: focus.data
70
+ });
71
+ return;
72
+ }
73
+ if ((0, _integration_split.isAlreadyVisuallySplit)(target)) return;
74
+ if (typeof focus.addVisualSplitLineAct !== 'function') return;
75
+ focus.addVisualSplitLineAct({
76
+ curveIdx: focus.jcampIdx,
77
+ target,
78
+ splitX,
79
+ data: focus.data
80
+ });
81
+ return;
82
+ }
83
+ if ((0, _integration_split.isMergedVisualSplitGroup)(data)) return;
84
+ if (typeof focus.splitIntegrationAct !== 'function') return;
85
+ focus.splitIntegrationAct({
86
+ curveIdx: focus.jcampIdx,
87
+ target,
88
+ splitX,
89
+ data: focus.data
90
+ });
91
+ };
92
+ exports.handleIntegrationClick = handleIntegrationClick;
93
+ const handleVisualSplitLineClick = (focus, event, splitX) => {
94
+ event.stopPropagation();
95
+ event.preventDefault();
96
+ (0, _integration_split.clearIntegrationSplitPreview)(focus);
97
+ if (typeof focus.removeVisualSplitLineAct !== 'function') return;
98
+ focus.removeVisualSplitLineAct({
99
+ curveIdx: focus.jcampIdx,
100
+ splitX,
101
+ data: focus.data
102
+ });
103
+ };
104
+ exports.handleVisualSplitLineClick = handleVisualSplitLineClick;
105
+ const drawVisualSplitLinesForFocus = (focus, stack, shift, ignoreRef) => {
106
+ (0, _integration_split.drawIntegrationVisualSplitLines)(focus, stack, shift, ignoreRef, focus.isUiVisualSplitIntgSt, (event, splitX) => handleVisualSplitLineClick(focus, event, splitX));
107
+ };
108
+ exports.drawVisualSplitLinesForFocus = drawVisualSplitLinesForFocus;
109
+ const drawIntegrationAUC = (focus, stack, shift = 0) => {
110
+ const {
111
+ xt,
112
+ yt
113
+ } = (0, _compass.TfRescale)(focus);
114
+ const groups = buildIntegrationGroups(stack);
115
+ const auc = focus.tags.aucPath.selectAll('path').data(groups);
116
+ auc.exit().attr('class', 'exit').remove();
117
+ const integCurve = border => {
118
+ const {
119
+ xL,
120
+ xU
121
+ } = border;
122
+ const ps = (0, _integration.getIntegrationPoints)(xL - shift, xU - shift, focus.data);
123
+ if (!ps[0]) return null;
124
+ const baselineY = (0, _integration.getLinearBaseline)(ps);
125
+ return d3.area().x(d => xt(d.x)).y0(d => yt(baselineY(d))).y1(d => yt(d.y))(ps);
126
+ };
127
+ const showIntegSplit = _cfg.default.showIntegSplitTools(focus.layout);
128
+ auc.enter().append('path').attr('class', 'auc').attr('fill', 'red').attr('stroke', 'none').attr('fill-opacity', 0.2).attr('stroke-width', 2).merge(auc).attr('d', d => integCurve(d)).attr('id', d => `auc${(0, _focus.itgIdTag)(d)}`).on('mouseover', (event, d) => {
129
+ d3.select(`#auc${(0, _focus.itgIdTag)(d)}`).attr('stroke', 'blue');
130
+ d3.select(`#auc${(0, _focus.itgIdTag)(d)}`).style('fill', 'blue');
131
+ }).on('mouseout', (event, d) => {
132
+ d3.select(`#auc${(0, _focus.itgIdTag)(d)}`).attr('stroke', 'none');
133
+ d3.select(`#auc${(0, _focus.itgIdTag)(d)}`).style('fill', 'red');
134
+ d3.select(`#auc${(0, _focus.itgIdTag)(d)}`).style('fill-opacity', 0.2);
135
+ if (showIntegSplit) (0, _integration_split.clearIntegrationSplitPreview)(focus);
136
+ }).on('mousemove', showIntegSplit ? (event, d) => handleIntegrationMouseMove(focus, event, d, shift, true) : null).on('click', (event, d) => handleIntegrationClick(focus, event, d, (clickEvent, clickData) => focus.onClickTarget(clickEvent, clickData)));
137
+ };
138
+ exports.drawIntegrationAUC = drawIntegrationAUC;
139
+ const bindSplitMouseHandlers = ({
140
+ focus,
141
+ shift,
142
+ ignoreRef,
143
+ showIntegSplit,
144
+ fallbackClick
145
+ }) => ({
146
+ mouseout: (event, d) => {
147
+ d3.select(`#igbp${(0, _focus.itgIdTag)(d)}`).attr('stroke', '#228B22');
148
+ d3.select(`#igbc${(0, _focus.itgIdTag)(d)}`).attr('stroke', '#228B22');
149
+ d3.select(`#igtp${(0, _focus.itgIdTag)(d)}`).style('fill', '#228B22');
150
+ if (showIntegSplit) (0, _integration_split.clearIntegrationSplitPreview)(focus);
151
+ },
152
+ mousemove: showIntegSplit ? (event, d) => handleIntegrationMouseMove(focus, event, d, shift, ignoreRef) : null,
153
+ click: (event, d) => handleIntegrationClick(focus, event, d, fallbackClick)
154
+ });
155
+ exports.bindSplitMouseHandlers = bindSplitMouseHandlers;