@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
@@ -4,19 +4,27 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.convertTopic = exports.Topic2Seed = exports.ToThresEndPts = exports.ToShiftPeaks = exports.ToFrequency = exports.GetCyclicVoltaShiftOffset = exports.GetCyclicVoltaRatio = exports.GetCyclicVoltaPreviousShift = exports.GetCyclicVoltaPeakSeparate = exports.GetComparisons = exports.Feature2Peak = exports.Feature2MaxMinPeak = exports.ExtractJcamp = exports.Convert2Thres = exports.Convert2Scan = exports.Convert2Peak = exports.Convert2MaxMinPeak = exports.Convert2DValue = void 0;
7
+ exports.buildIntegFeature = exports.Topic2Seed = exports.ToThresEndPts = exports.ToShiftPeaks = exports.ToFrequency = exports.GetCyclicVoltaShiftOffset = exports.GetCyclicVoltaRatio = exports.GetCyclicVoltaPreviousShift = exports.GetCyclicVoltaPeakSeparate = exports.GetComparisons = exports.Feature2Peak = exports.Feature2MaxMinPeak = exports.ExtractJcamp = exports.Convert2Thres = exports.Convert2Scan = exports.Convert2Peak = exports.Convert2MaxMinPeak = exports.Convert2DValue = void 0;
8
+ Object.defineProperty(exports, "buildLcmsMsPageJcamp", {
9
+ enumerable: true,
10
+ get: function get() {
11
+ return _parsing.buildLcmsMsPageJcamp;
12
+ }
13
+ });
14
+ exports.convertTopic = exports.convertThresEndPts = void 0;
8
15
  var _jcampconverter = _interopRequireDefault(require("jcampconverter"));
9
16
  var _reselect = require("reselect");
10
17
  var _shift = require("./shift");
11
- var _cfg = _interopRequireDefault(require("./cfg"));
12
18
  var _format = _interopRequireDefault(require("./format"));
13
19
  var _list_layout = require("../constants/list_layout");
14
20
  var _integration = require("./integration");
21
+ var _parsing = require("../features/lc-ms/parsing");
15
22
  /* eslint-disable
16
23
  no-mixed-operators, react/function-component-definition,
17
24
  prefer-object-spread, camelcase, no-plusplus, prefer-destructuring,
18
25
  max-len */
19
26
 
27
+ const canIntegrate = layoutSt => !(_format.default.isNmrLayout(layoutSt) || _format.default.isHplcUvVisLayout(layoutSt) || _format.default.isLCMsLayout(layoutSt));
20
28
  const getTopic = (_, props) => props.topic;
21
29
  const getFeature = (_, props) => props.feature;
22
30
  const getLayout = (state, _) => state.layout; // eslint-disable-line
@@ -104,12 +112,13 @@ const calcXY = (xs, ys, maxY, offset) => {
104
112
  return sp;
105
113
  };
106
114
  const convertTopic = (topic, layout, feature, offset) => {
115
+ if (!feature || !topic) return [];
107
116
  const {
108
117
  maxY
109
- } = feature;
110
- const xs = topic.x;
111
- const ys = topic.y;
112
- const isItgDisable = _cfg.default.btnCmdIntg(layout);
118
+ } = feature || {};
119
+ const xs = topic.x || [];
120
+ const ys = topic.y || [];
121
+ const isItgDisable = canIntegrate(layout);
113
122
  if (!isItgDisable) return calcXYK(xs, ys, maxY, offset);
114
123
  return calcXY(xs, ys, maxY, offset);
115
124
  };
@@ -154,7 +163,7 @@ const convertComparisons = (layout, comparisons, feature) => {
154
163
  };
155
164
  const GetComparisons = exports.GetComparisons = (0, _reselect.createSelector)(getLayout, getOthers, getFeature, convertComparisons);
156
165
  const convertFrequency = (layout, feature) => {
157
- if (['1H', '13C', '19F', '31P', '15N', '29Si'].indexOf(layout) < 0) return false;
166
+ if (!_format.default.isNmrLayout(layout)) return false;
158
167
  const {
159
168
  observeFrequency
160
169
  } = feature;
@@ -164,6 +173,33 @@ const convertFrequency = (layout, feature) => {
164
173
  const ToFrequency = exports.ToFrequency = (0, _reselect.createSelector)(getLayout, getFeature, convertFrequency);
165
174
  const getThreshold = state => state.threshold ? state.threshold.list[state.curve.curveIdx].value * 1.0 : false;
166
175
  const Convert2Peak = (feature, threshold, offset, upThreshold = false, lowThreshold = false) => {
176
+ if (feature?.operation?.layout === 'LC/MS') {
177
+ if (feature.peaks?.length) {
178
+ return feature.peaks.map(p => ({
179
+ x: p.x - (offset || 0),
180
+ y: p.y
181
+ }));
182
+ }
183
+ const data = feature.data[0];
184
+ if (!data) return [];
185
+ const {
186
+ x,
187
+ y
188
+ } = data;
189
+ const peaks = [];
190
+ const maxIntensity = Math.max(...y);
191
+ const thresholdValue = threshold || 0;
192
+ for (let i = 1; i < y.length - 1; i++) {
193
+ const intensity = y[i] / maxIntensity * 100;
194
+ if (intensity >= thresholdValue && y[i] > y[i - 1] && y[i] > y[i + 1]) {
195
+ peaks.push({
196
+ x: x[i],
197
+ y: y[i]
198
+ });
199
+ }
200
+ }
201
+ return peaks;
202
+ }
167
203
  const peak = [];
168
204
  if (!feature || !feature.data) return peak;
169
205
  const data = feature.data[0];
@@ -179,8 +215,6 @@ const Convert2Peak = (feature, threshold, offset, upThreshold = false, lowThresh
179
215
  const {
180
216
  layout
181
217
  } = operation;
182
-
183
- // if (!Format.isSECLayout(layout) && (upperThres || lowerThres)) {
184
218
  if ((_format.default.isCyclicVoltaLayout(layout) || _format.default.isCDSLayout(layout)) && (upperThres || lowerThres)) {
185
219
  let upperThresVal = upThreshold || upperThres;
186
220
  if (!upperThresVal) {
@@ -291,6 +325,7 @@ const convertThresEndPts = (feature, threshold) => {
291
325
  }];
292
326
  return endPts;
293
327
  };
328
+ exports.convertThresEndPts = convertThresEndPts;
294
329
  const ToThresEndPts = exports.ToThresEndPts = (0, _reselect.createSelector)(getFeature, getThreshold, convertThresEndPts);
295
330
  const getShiftPeak = state => {
296
331
  const {
@@ -322,14 +357,20 @@ const ToShiftPeaks = exports.ToShiftPeaks = (0, _reselect.createSelector)(getShi
322
357
  // ExtractJcamp
323
358
  // - - - - - - - - - - - - - - - - - - - - - -
324
359
  const readLayout = jcamp => {
360
+ if (jcamp.dataType?.toUpperCase?.() === 'LC/MS') {
361
+ return _list_layout.LIST_LAYOUT.LC_MS;
362
+ }
325
363
  const {
326
364
  xType,
327
365
  spectra
328
366
  } = jcamp;
329
367
  if (xType && _format.default.isNmrLayout(xType)) return xType;
368
+ if (!spectra || !Array.isArray(spectra) || spectra.length === 0) {
369
+ return false;
370
+ }
330
371
  const {
331
372
  dataType
332
- } = spectra[0];
373
+ } = spectra[0] || {};
333
374
  if (dataType) {
334
375
  if (dataType.includes('INFRARED SPECTRUM')) {
335
376
  return _list_layout.LIST_LAYOUT.IR;
@@ -379,6 +420,9 @@ const readLayout = jcamp => {
379
420
  if (dataType.includes('DLS intensity')) {
380
421
  return _list_layout.LIST_LAYOUT.DLS_INTENSITY;
381
422
  }
423
+ if (dataType.includes('LC/MS')) {
424
+ return _list_layout.LIST_LAYOUT.LC_MS;
425
+ }
382
426
  }
383
427
  return false;
384
428
  };
@@ -386,30 +430,217 @@ const extrSpectraShare = (spectra, layout) => spectra.map(s => Object.assign({
386
430
  layout
387
431
  }, s)).filter(r => r != null);
388
432
  const extrSpectraMs = (jcamp, layout) => {
389
- const scanCount = jcamp.info.$CSSCANCOUNT || 1;
390
- const spc = extrSpectraShare(jcamp.spectra.slice(0, scanCount), layout);
391
- let spectra = spc || [];
392
- if (jcamp.info.UNITS && jcamp.info.SYMBOL) {
393
- const units = jcamp.info.UNITS.split(',');
394
- const symbol = jcamp.info.SYMBOL.split(',');
433
+ const csCategories = [].concat(jcamp?.info?.$CSCATEGORY || []).map(c => String(c).toUpperCase());
434
+ const hasUvvisCategory = csCategories.some(c => c.includes('UVVIS'));
435
+ const hasUvvisDataType = jcamp?.spectra?.some(s => {
436
+ const dt = String(s?.dataType || '').toUpperCase();
437
+ return dt.includes('UV-VIS') || dt.includes('UVVIS') || dt.includes('HPLC UV');
438
+ });
439
+ const jcampDataType = String(jcamp?.dataType || '').toUpperCase();
440
+ const hasUvvisJcampDataType = jcampDataType.includes('UV-VIS') || jcampDataType.includes('UVVIS') || jcampDataType.includes('HPLC UV');
441
+ const isUvvisData = hasUvvisCategory || hasUvvisDataType || hasUvvisJcampDataType;
442
+ const hasTicCategory = csCategories.some(c => c.includes('TIC'));
443
+ const hasTicDataType = jcamp?.spectra?.some(s => {
444
+ const dt = String(s?.dataType || '').toUpperCase();
445
+ return dt.includes('MASS TIC') || dt.includes('TIC');
446
+ });
447
+ const hasTicJcampDataType = jcampDataType.includes('MASS TIC') || jcampDataType.includes('TIC');
448
+ const isTicData = hasTicCategory || hasTicDataType || hasTicJcampDataType;
449
+ const getCategory = idx => csCategories[idx] || '';
450
+ const finalSpectra = [];
451
+ const parseIntegralsString = raw => {
452
+ if (raw == null) return [];
453
+ let text = raw;
454
+ if (Array.isArray(text)) text = text.join(' ');
455
+ text = String(text).trim();
456
+ const groups = text.match(/\(([^)]+)\)/g) || [];
457
+ const out = [];
458
+ groups.forEach(g => {
459
+ const nums = g.replace(/[()]/g, '').split(/[,\s;]+/).map(s => Number(s)).filter(Number.isFinite);
460
+ if (nums.length >= 3) {
461
+ const [xL, xU, area, absMaybe] = nums;
462
+ out.push({
463
+ xL,
464
+ xU,
465
+ area,
466
+ absoluteArea: Number.isFinite(absMaybe) ? absMaybe : Math.abs(area),
467
+ xExtent: {
468
+ xL,
469
+ xU
470
+ }
471
+ });
472
+ }
473
+ });
474
+ return out;
475
+ };
476
+ const pickIntegralsForPair = (raw, idx) => {
477
+ if (raw == null) return '';
478
+ if (Array.isArray(raw)) return raw[idx] ?? '';
479
+ if (typeof raw === 'string') return idx === 0 ? raw : '';
480
+ return '';
481
+ };
482
+ if (isUvvisData) {
483
+ const spectraList = jcamp.spectra || [];
484
+ const uvvisSpectra = [];
485
+ const peakTablesByPage = new Map();
486
+ const hasPeakData = table => {
487
+ const data = table?.data?.[0];
488
+ if (!data) return false;
489
+ if (Array.isArray(data)) return data.length >= 2;
490
+ if (data?.x && data?.y) return data.x.length > 0 && data.y.length > 0;
491
+ return false;
492
+ };
493
+ const buildPeaks = source => {
494
+ if (!source) return [];
495
+ if (Array.isArray(source)) {
496
+ const peaks = [];
497
+ for (let i = 0; i < source.length - 1; i += 2) {
498
+ const x = Number(source[i]);
499
+ const y = Number(source[i + 1]);
500
+ if (Number.isFinite(x) && Number.isFinite(y)) peaks.push({
501
+ x,
502
+ y
503
+ });
504
+ }
505
+ return peaks;
506
+ }
507
+ if (source?.x && source?.y) {
508
+ const len = Math.min(source.x.length, source.y.length);
509
+ const peaks = new Array(len);
510
+ for (let j = 0; j < len; j++) peaks[j] = {
511
+ x: source.x[j],
512
+ y: source.y[j]
513
+ };
514
+ return peaks;
515
+ }
516
+ return [];
517
+ };
518
+ spectraList.forEach((s, idx) => {
519
+ if (!s) return;
520
+ const sDataType = String(s.dataType || '').toUpperCase();
521
+ const isUvvisSpectrum = s.dataType === 'LC/MS' && getCategory(idx).includes('UVVIS') || sDataType.includes('UV-VIS') || sDataType.includes('UVVIS') || sDataType.includes('HPLC UV');
522
+ if (isUvvisSpectrum) {
523
+ uvvisSpectra.push({
524
+ spectrum: s,
525
+ idx
526
+ });
527
+ }
528
+ if (s.dataType?.includes('PEAKTABLE')) {
529
+ const pageKey = s.pageValue ?? s.page;
530
+ if (pageKey == null) return;
531
+ const entry = peakTablesByPage.get(pageKey) || {};
532
+ const cat = getCategory(idx);
533
+ if (cat.includes('AUTO_PEAK')) entry.auto = s;else if (cat.includes('EDIT_PEAK')) entry.edit = s;else entry.other = s;
534
+ peakTablesByPage.set(pageKey, entry);
535
+ }
536
+ });
537
+ const container = jcamp?.info?.$OBSERVEDINTEGRALS ?? null;
538
+ const jcampUnitsField = String(jcamp?.info?.UNITS || '').toUpperCase();
539
+ const jcampUnitsIndicatesMinutes = jcampUnitsField.includes('MINUTE');
540
+ const jcampUnitsIndicatesSeconds = jcampUnitsField.includes('SECOND');
541
+ const getMaxAbsX = data => {
542
+ const xs = data?.[0]?.x;
543
+ if (!Array.isArray(xs) || xs.length === 0) return 0;
544
+ return xs.reduce((max, value) => {
545
+ const abs = Math.abs(Number(value));
546
+ return Number.isFinite(abs) && abs > max ? abs : max;
547
+ }, 0);
548
+ };
549
+ uvvisSpectra.forEach(({
550
+ spectrum
551
+ }, pairIdx) => {
552
+ const xUnitUpper = String(spectrum?.xUnit || '').toUpperCase();
553
+ const isExplicitMinutes = xUnitUpper.includes('MINUTE') || jcampUnitsIndicatesMinutes;
554
+ const isExplicitSeconds = xUnitUpper.includes('SECOND') || jcampUnitsIndicatesSeconds;
555
+ const isTimeAxis = xUnitUpper.includes('TIME') || xUnitUpper.includes('SECOND');
556
+ const dataLooksLikeSeconds = getMaxAbsX(spectrum?.data) > 60;
557
+ const needsSecToMin = isTimeAxis && !isExplicitMinutes && (isExplicitSeconds || dataLooksLikeSeconds);
558
+ const scaleX = value => needsSecToMin ? value / 60 : value;
559
+ const pageKey = spectrum.pageValue ?? spectrum.page;
560
+ const peakTable = peakTablesByPage.get(pageKey);
561
+ let selectedPeakTable = null;
562
+ if (hasPeakData(peakTable?.edit)) {
563
+ selectedPeakTable = peakTable.edit;
564
+ } else if (hasPeakData(peakTable?.auto)) {
565
+ selectedPeakTable = peakTable.auto;
566
+ } else if (hasPeakData(peakTable?.other)) {
567
+ selectedPeakTable = peakTable.other;
568
+ } else {
569
+ selectedPeakTable = peakTable?.edit || peakTable?.auto || peakTable?.other || null;
570
+ }
571
+ const originalData = spectrum?.data?.[0];
572
+ let normalizedData = spectrum.data;
573
+ if (needsSecToMin && originalData?.x) {
574
+ normalizedData = [{
575
+ ...originalData,
576
+ x: originalData.x.map(scaleX)
577
+ }];
578
+ }
579
+ const mainSpectrum = {
580
+ ...spectrum,
581
+ data: normalizedData,
582
+ peaks: [],
583
+ integrations: [],
584
+ csCategory: 'UVVIS PEAK TABLE'
585
+ };
586
+ const peakSource = selectedPeakTable?.data?.[0] || spectrum?.data?.[0];
587
+ const peaks = buildPeaks(peakSource).map(p => ({
588
+ ...p,
589
+ x: scaleX(p.x)
590
+ }));
591
+ if (peaks.length) mainSpectrum.peaks = peaks;
592
+ const rawText = pickIntegralsForPair(container, pairIdx);
593
+ const integrals = parseIntegralsString(rawText).map(integ => ({
594
+ ...integ,
595
+ xL: scaleX(integ.xL),
596
+ xU: scaleX(integ.xU),
597
+ xExtent: {
598
+ xL: scaleX(integ.xL),
599
+ xU: scaleX(integ.xU)
600
+ }
601
+ }));
602
+ if (integrals.length) mainSpectrum.integrations = integrals;
603
+ finalSpectra.push(mainSpectrum);
604
+ });
605
+ } else if (isTicData) {
606
+ (jcamp.spectra || []).forEach(s => {
607
+ const hasPoints = s?.data?.[0]?.x?.length > 0;
608
+ if (hasPoints) {
609
+ finalSpectra.push({
610
+ ...s,
611
+ csCategory: (0, _parsing.inferLcMsCategory)(s, jcamp)
612
+ });
613
+ }
614
+ });
615
+ } else {
616
+ (jcamp.spectra || []).forEach(s => {
617
+ const hasPoints = s?.data?.[0]?.x?.length > 0;
618
+ if (hasPoints) {
619
+ finalSpectra.push({
620
+ ...s,
621
+ csCategory: (0, _parsing.inferLcMsCategory)(s, jcamp)
622
+ });
623
+ }
624
+ });
625
+ }
626
+ let spectra = extrSpectraShare(finalSpectra, layout) || [];
627
+ const info = jcamp?.info || {};
628
+ if (info.UNITS && info.SYMBOL) {
629
+ const unitsString = Array.isArray(info.UNITS) ? info.UNITS[0] : info.UNITS;
630
+ const symbolString = Array.isArray(info.SYMBOL) ? info.SYMBOL[0] : info.SYMBOL;
631
+ const units = String(unitsString).split(',');
632
+ const symbols = String(symbolString).split(',');
395
633
  let xUnit = null;
396
634
  let yUnit = null;
397
- symbol.forEach((sym, idx) => {
398
- const currSymbol = sym.replace(' ', '').toLowerCase();
399
- if (currSymbol === 'x') {
400
- xUnit = units[idx].trim();
401
- } else if (currSymbol === 'y') {
402
- yUnit = units[idx].trim();
403
- }
635
+ symbols.forEach((sym, idx) => {
636
+ const curr = String(sym).replace(' ', '').toLowerCase();
637
+ if (curr === 'x') xUnit = units[idx]?.trim?.() || null;
638
+ if (curr === 'y') yUnit = units[idx]?.trim?.() || null;
404
639
  });
405
640
  spectra = spectra.map(sp => {
406
641
  const spectrum = sp;
407
- if (xUnit) {
408
- spectrum.xUnit = xUnit;
409
- }
410
- if (yUnit) {
411
- spectrum.yUnit = yUnit;
412
- }
642
+ if (xUnit) spectrum.xUnit = xUnit;
643
+ if (yUnit) spectrum.yUnit = yUnit;
413
644
  return spectrum;
414
645
  });
415
646
  }
@@ -422,21 +653,33 @@ const extrSpectraNi = (jcamp, layout) => {
422
653
  return [spectrum] || [jcamp.spectra[0]];
423
654
  };
424
655
  const calcThresRef = (s, peakUp) => {
425
- const ys = s && s.data[0].y;
426
- if (!ys) return null;
656
+ if (!s || !s.data || !Array.isArray(s.data) || !s.data[0] || !s.data[0].y) {
657
+ return null;
658
+ }
659
+ const ys = s.data[0].y;
660
+ if (!ys || !Array.isArray(ys) || ys.length === 0) return null;
427
661
  const ref = peakUp ? Math.min(...ys.map(a => Math.abs(a))) : Math.max(...ys);
662
+ if (!s.maxY || s.maxY === 0) return null;
428
663
  return peakUp ? Math.floor(ref * 100 * 100 / s.maxY) / 100 : Math.ceil(ref * 100 * 100 / s.maxY) / 100;
429
664
  };
430
665
  const calcUpperThres = s => {
431
- const ys = s && s.data[0].y;
432
- if (!ys) return null;
666
+ if (!s || !s.data || !Array.isArray(s.data) || !s.data[0] || !s.data[0].y) {
667
+ return null;
668
+ }
669
+ const ys = s.data[0].y;
670
+ if (!ys || !Array.isArray(ys) || ys.length === 0) return null;
433
671
  const ref = Math.max(...ys);
672
+ if (!s.maxY || s.maxY === 0) return null;
434
673
  return Math.floor(ref * 100 * 100 / s.maxY) / 100;
435
674
  };
436
675
  const calcLowerThres = s => {
437
- const ys = s && s.data[0].y;
438
- if (!ys) return null;
676
+ if (!s || !s.data || !Array.isArray(s.data) || !s.data[0] || !s.data[0].y) {
677
+ return null;
678
+ }
679
+ const ys = s.data[0].y;
680
+ if (!ys || !Array.isArray(ys) || ys.length === 0) return null;
439
681
  const ref = Math.min(...ys);
682
+ if (!s.minY || s.minY === 0) return null;
440
683
  return Math.ceil(ref * 100 * 100 / s.minY) / 100;
441
684
  };
442
685
  const extractShift = (s, jcamp) => {
@@ -492,13 +735,12 @@ const extractVoltammetryData = jcamp => {
492
735
  return peakStack;
493
736
  };
494
737
  const buildPeakFeature = (jcamp, layout, peakUp, s, thresRef, upperThres = false, lowerThres = false) => {
495
- // eslint-disable-line
496
738
  const {
497
739
  xType,
498
740
  info
499
741
  } = jcamp;
500
742
  const subTyp = xType ? ` - ${xType}` : '';
501
- return Object.assign({
743
+ const baseFeature = {
502
744
  typ: s.dataType + subTyp,
503
745
  peakUp,
504
746
  thresRef,
@@ -518,8 +760,14 @@ const buildPeakFeature = (jcamp, layout, peakUp, s, thresRef, upperThres = false
518
760
  scanRate: +info.$CSSCANRATE || 0.1,
519
761
  weAreaValue: info.$CSWEAREAVALUE || '',
520
762
  weAreaUnit: info.$CSWEAREAUNIT || '',
521
- currentMode: info.$CSCURRENTMODE || ''
522
- }, s);
763
+ currentMode: info.$CSCURRENTMODE || '',
764
+ csCategory: info.$CSCATEGORY || s.csCategory
765
+ };
766
+ if (layout === 'LC/MS') {
767
+ if (s.peaks) baseFeature.peaks = s.peaks;
768
+ if (s.integrations) baseFeature.integrations = s.integrations;
769
+ }
770
+ return Object.assign({}, baseFeature, s);
523
771
  };
524
772
  const maxArray = arr => {
525
773
  let len = arr.length;
@@ -547,23 +795,43 @@ const calcIntgRefArea = (spectra, stack) => {
547
795
  raw2realRatio
548
796
  };
549
797
  };
798
+ const parseObservedIntegralGroups = rawValue => {
799
+ if (!rawValue) return {};
800
+ const tokenRegx = /[^A-Za-z0-9._-]/g;
801
+ const groupsByIdx = {};
802
+ rawValue.split('\n').forEach(line => {
803
+ const cells = line.split(',').map(c => c.replace(tokenRegx, ''));
804
+ if (cells.length < 2) return;
805
+ const idx = parseInt(cells[0], 10);
806
+ const groupId = cells[1];
807
+ if (!Number.isInteger(idx) || idx < 0 || !groupId) return;
808
+ groupsByIdx[idx] = groupId;
809
+ });
810
+ return groupsByIdx;
811
+ };
550
812
  const buildIntegFeature = (jcamp, spectra) => {
551
813
  const {
552
814
  $OBSERVEDINTEGRALS,
553
- $OBSERVEDMULTIPLETS
815
+ $OBSERVEDMULTIPLETS,
816
+ $OBSERVEDINTEGRALSGROUPS
554
817
  } = jcamp.info;
555
818
  const regx = /[^0-9.,-]/g;
556
819
  let stack = [];
557
820
  if ($OBSERVEDINTEGRALS) {
558
821
  const its = $OBSERVEDINTEGRALS.split('\n').slice(1);
559
- const itStack = its.map(t => {
822
+ const groupsByIdx = parseObservedIntegralGroups($OBSERVEDINTEGRALSGROUPS);
823
+ const itStack = its.map((t, idx) => {
560
824
  const ts = t.replace(regx, '').split(',');
561
- return {
825
+ const item = {
562
826
  xL: parseFloat(ts[0]),
563
827
  xU: parseFloat(ts[1]),
564
828
  area: parseFloat(ts[2]),
565
829
  absoluteArea: parseFloat(ts[3])
566
830
  };
831
+ const groupId = groupsByIdx[idx];
832
+ return groupId ? Object.assign({}, item, {
833
+ visualSplitGroupId: groupId
834
+ }) : item;
567
835
  });
568
836
  stack = [...stack, ...itStack];
569
837
  }
@@ -602,7 +870,7 @@ const range = (head, tail, length) => {
602
870
  );
603
871
  };
604
872
  */
605
-
873
+ exports.buildIntegFeature = buildIntegFeature;
606
874
  const buildSimFeature = jcamp => {
607
875
  const {
608
876
  $CSSIMULATIONPEAKS
@@ -780,32 +1048,15 @@ const extrFeaturesCylicVolta = (jcamp, layout, peakUp) => {
780
1048
  }).filter(r => r != null);
781
1049
  return features;
782
1050
  };
783
- const extrFeaturesMs = (jcamp, layout, peakUp) => {
784
- // const nfs = {};
785
- // const category = jcamp.info.$CSCATEGORY;
786
- // const scanCount = parseInt(jcamp.info.$CSSCANCOUNT, 10) - 1;
787
- // if (category) {
788
- // const idxEditPeak = category.indexOf('EDIT_PEAK');
789
- // if (idxEditPeak >= 0) {
790
- // const sEP = jcamp.spectra[idxEditPeak + scanCount];
791
- // const thresRef = calcThresRef(sEP, peakUp);
792
- // nfs.editPeak = buildPeakFeature(jcamp, layout, peakUp, sEP, thresRef);
793
- // }
794
- // const idxAutoPeak = category.indexOf('AUTO_PEAK');
795
- // if (idxAutoPeak >= 0) {
796
- // const sAP = jcamp.spectra[idxAutoPeak + scanCount];
797
- // const thresRef = calcThresRef(sAP, peakUp);
798
- // nfs.autoPeak = buildPeakFeature(jcamp, layout, peakUp, sAP, thresRef);
799
- // }
800
- // return nfs;
801
- // }
802
- // // workaround for legacy design
1051
+ const extrFeaturesMs = (jcamp, layout, peakUp, spectra) => {
803
1052
  const thresRef = jcamp.info && jcamp.info.$CSTHRESHOLD * 100 || 5;
804
- const base = jcamp.spectra[0];
805
- const features = jcamp.spectra.map(s => {
1053
+ const features = spectra.map(s => {
1054
+ if (!s.data || !s.data[0] || !s.data[0].x || !s.data[0].y) {
1055
+ return null;
1056
+ }
806
1057
  const cpo = buildPeakFeature(jcamp, layout, peakUp, s, +thresRef.toFixed(4));
807
1058
  const bnd = getBoundary(s);
808
- return Object.assign({}, base, cpo, bnd);
1059
+ return Object.assign({}, cpo, bnd);
809
1060
  }).filter(r => r != null);
810
1061
  return features;
811
1062
  };
@@ -819,17 +1070,135 @@ const extractTemperature = jcamp => {
819
1070
  }
820
1071
  return 'xxx';
821
1072
  };
1073
+ const normalizeXyData = raw => {
1074
+ if (!raw) return null;
1075
+ if (Array.isArray(raw)) {
1076
+ if (raw.length === 0) return null;
1077
+ const first = raw[0];
1078
+ if (first && typeof first === 'object' && !Array.isArray(first)) {
1079
+ const x = Array.isArray(first.x) ? first.x : [];
1080
+ const y = Array.isArray(first.y) ? first.y : [];
1081
+ if (x.length || y.length) return {
1082
+ x,
1083
+ y
1084
+ };
1085
+ }
1086
+ if (Array.isArray(first)) {
1087
+ if (raw.length === 2 && Array.isArray(raw[0]) && Array.isArray(raw[1])) {
1088
+ return {
1089
+ x: raw[0],
1090
+ y: raw[1]
1091
+ };
1092
+ }
1093
+ const x = [];
1094
+ const y = [];
1095
+ raw.forEach(pair => {
1096
+ if (!Array.isArray(pair) || pair.length < 2) return;
1097
+ const xVal = Number(pair[0]);
1098
+ const yVal = Number(pair[1]);
1099
+ if (Number.isFinite(xVal) && Number.isFinite(yVal)) {
1100
+ x.push(xVal);
1101
+ y.push(yVal);
1102
+ }
1103
+ });
1104
+ if (x.length || y.length) return {
1105
+ x,
1106
+ y
1107
+ };
1108
+ }
1109
+ } else if (typeof raw === 'object') {
1110
+ const x = Array.isArray(raw.x) ? raw.x : [];
1111
+ const y = Array.isArray(raw.y) ? raw.y : [];
1112
+ if (x.length || y.length) return {
1113
+ x,
1114
+ y
1115
+ };
1116
+ }
1117
+ return null;
1118
+ };
1119
+ const ensureSpectrumData = (spectrum, source) => {
1120
+ if (!spectrum) return spectrum;
1121
+ const result = {
1122
+ ...spectrum
1123
+ };
1124
+ let normalized = normalizeXyData(result.data);
1125
+ if (!normalized && source) {
1126
+ normalized = normalizeXyData({
1127
+ x: source.x,
1128
+ y: source.y
1129
+ }) || normalizeXyData(source.data);
1130
+ }
1131
+ if (normalized) {
1132
+ result.data = [{
1133
+ x: normalized.x || [],
1134
+ y: normalized.y || []
1135
+ }];
1136
+ }
1137
+ return result;
1138
+ };
822
1139
  const ExtractJcamp = source => {
823
1140
  const jcamp = _jcampconverter.default.convert(source, {
824
1141
  xy: true,
825
- keepRecordsRegExp: /(\$CSTHRESHOLD|\$CSSCANAUTOTARGET|\$CSSCANEDITTARGET|\$CSSCANCOUNT|\$CSSOLVENTNAME|\$CSSOLVENTVALUE|\$CSSOLVENTX|\$CSCATEGORY|\$CSITAREA|\$CSITFACTOR|\$OBSERVEDINTEGRALS|\$OBSERVEDMULTIPLETS|\$OBSERVEDMULTIPLETSPEAKS|\.SOLVENTNAME|\.OBSERVEFREQUENCY|\$CSSIMULATIONPEAKS|\$CSUPPERTHRESHOLD|\$CSLOWERTHRESHOLD|\$CSCYCLICVOLTAMMETRYDATA|UNITS|SYMBOL|\$CSAUTOMETADATA|\$DETECTOR|MN|MW|D|MP|MELTINGPOINT|TG|\$CSSCANRATE|\$CSSPECTRUMDIRECTION|\$CSWEAREAVALUE|\$CSWEAREAUNIT|\$CSCURRENTMODE)/ // eslint-disable-line
1142
+ keepRecordsRegExp: /(\$CSTHRESHOLD|\$CSSCANAUTOTARGET|\$CSSCANEDITTARGET|\$CSSCANCOUNT|\$CSSOLVENTNAME|\$CSSOLVENTVALUE|\$CSSOLVENTX|\$CSCATEGORY|\$CSITAREA|\$CSITFACTOR|\$OBSERVEDINTEGRALS|\$OBSERVEDINTEGRALSGROUPS|\$OBSERVEDMULTIPLETS|\$OBSERVEDMULTIPLETSPEAKS|\.SOLVENTNAME|\.OBSERVEFREQUENCY|\$CSSIMULATIONPEAKS|\$CSUPPERTHRESHOLD|\$CSLOWERTHRESHOLD|\$CSCYCLICVOLTAMMETRYDATA|UNITS|SYMBOL|\$CSAUTOMETADATA|\$DETECTOR|MN|MW|D|MP|MELTINGPOINT|TG|\$CSSCANRATE|\$CSSPECTRUMDIRECTION|\$CSWEAREAVALUE|\$CSWEAREAUNIT|\$CSCURRENTMODE|\$CSLCMSMZPAGE|SCAN_MODE|SCANMODE|TYPE|SOFTWARE|DATATYPE)/ // eslint-disable-line
826
1143
  });
827
- const layout = readLayout(jcamp);
1144
+ const isChemstation = (0, _parsing.isChemstationLcms)(source, jcamp);
1145
+ const parsedPages = (0, _parsing.parseChemstationPages)(source, jcamp);
1146
+ const spectraCount = Array.isArray(jcamp.spectra) ? jcamp.spectra.length : 0;
1147
+ if (parsedPages.length > 1 && spectraCount < parsedPages.length) {
1148
+ jcamp.spectra = parsedPages;
1149
+ }
1150
+ const hasNtuples = jcamp.ntuples && Array.isArray(jcamp.ntuples) && jcamp.ntuples.length > 0;
1151
+ const hasSpectra = jcamp.spectra && Array.isArray(jcamp.spectra) && jcamp.spectra.length > 0;
1152
+ const hasNtupleData = hasNtuples && jcamp.ntuples.some(ntuple => !!normalizeXyData(ntuple?.data) || !!normalizeXyData({
1153
+ x: ntuple?.x,
1154
+ y: ntuple?.y
1155
+ }));
1156
+ const hasSpectraData = hasSpectra && jcamp.spectra.some(spectrum => !!normalizeXyData(spectrum?.data) || !!normalizeXyData({
1157
+ x: spectrum?.x,
1158
+ y: spectrum?.y
1159
+ }));
1160
+ if (hasNtuples && hasNtupleData) {
1161
+ if (hasSpectra && jcamp.spectra.length === 1 && jcamp.ntuples.length > 1) {
1162
+ const singleSpectrum = jcamp.spectra[0];
1163
+ jcamp.spectra = jcamp.ntuples.map(ntuple => {
1164
+ const spectrum = {
1165
+ ...singleSpectrum,
1166
+ ...ntuple,
1167
+ dataType: singleSpectrum.dataType || jcamp.dataType || ntuple.dataType,
1168
+ xUnit: ntuple.xUnit || singleSpectrum.xUnit || jcamp.info?.XUNITS,
1169
+ yUnit: ntuple.yUnit || singleSpectrum.yUnit || jcamp.info?.YUNITS,
1170
+ pageValue: ntuple.pageValue || ntuple.page,
1171
+ page: ntuple.page || ntuple.pageValue,
1172
+ pageSymbol: ntuple.pageSymbol || ntuple.pageValue || ntuple.page
1173
+ };
1174
+ ensureSpectrumData(spectrum, ntuple);
1175
+ return spectrum;
1176
+ });
1177
+ } else if (!hasSpectra || !hasSpectraData || jcamp.spectra.length < jcamp.ntuples.length) {
1178
+ jcamp.spectra = jcamp.ntuples.map(ntuple => {
1179
+ const spectrum = {
1180
+ ...ntuple,
1181
+ dataType: jcamp.dataType || ntuple.dataType,
1182
+ xUnit: ntuple.xUnit || jcamp.info?.XUNITS,
1183
+ yUnit: ntuple.yUnit || jcamp.info?.YUNITS,
1184
+ pageValue: ntuple.pageValue || ntuple.page,
1185
+ page: ntuple.page || ntuple.pageValue,
1186
+ pageSymbol: ntuple.pageSymbol || ntuple.pageValue || ntuple.page
1187
+ };
1188
+ ensureSpectrumData(spectrum, ntuple);
1189
+ return spectrum;
1190
+ });
1191
+ }
1192
+ }
1193
+ let layout = readLayout(jcamp);
1194
+ if (isChemstation) {
1195
+ layout = _list_layout.LIST_LAYOUT.LC_MS;
1196
+ }
828
1197
  const peakUp = !_format.default.isIrLayout(layout);
829
- const spectra = _format.default.isMsLayout(layout) ? extrSpectraMs(jcamp, layout) : extrSpectraNi(jcamp, layout);
1198
+ const spectra = _format.default.isMsLayout(layout) || _format.default.isLCMsLayout(layout) ? extrSpectraMs(jcamp, layout) : extrSpectraNi(jcamp, layout);
830
1199
  let features = {};
831
- if (_format.default.isMsLayout(layout)) {
832
- features = extrFeaturesMs(jcamp, layout, peakUp);
1200
+ if (_format.default.isMsLayout(layout) || _format.default.isLCMsLayout(layout)) {
1201
+ features = extrFeaturesMs(jcamp, layout, peakUp, spectra);
833
1202
  } else if (_format.default.isXRDLayout(layout)) {
834
1203
  features = extrFeaturesXrd(jcamp, layout, peakUp);
835
1204
  const temperature = extractTemperature(jcamp);
@@ -865,6 +1234,15 @@ const ExtractJcamp = source => {
865
1234
  // : ((Format.isXRDLayout(layout) || Format.isCyclicVoltaLayout(layout))
866
1235
  // ? extrFeaturesXrd(jcamp, layout, peakUp) : extrFeaturesNi(jcamp, layout, peakUp, spectra));
867
1236
 
1237
+ const lcmsMzPageFromInfo = _format.default.isLCMsLayout(layout) ? (0, _parsing.readLcmsMzPageFromJcampInfo)(jcamp.info) : null;
1238
+ if (lcmsMzPageFromInfo != null) {
1239
+ return {
1240
+ spectra,
1241
+ features,
1242
+ layout,
1243
+ lcms_mz_page: lcmsMzPageFromInfo
1244
+ };
1245
+ }
868
1246
  return {
869
1247
  spectra,
870
1248
  features,