react-cheminfo 0.70.0 → 0.71.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.
- package/README.md +57 -2
- package/lib/about/core/providers.d.ts +6 -0
- package/lib/about/core/providers.d.ts.map +1 -1
- package/lib/about/core/providers.js +7 -0
- package/lib/about/core/providers.js.map +1 -1
- package/lib/about/ui/AboutProvidedBy.js +34 -0
- package/lib/about/ui/AboutProvidedBy.js.map +1 -1
- package/lib/download/core/figurePng.d.ts +14 -0
- package/lib/download/core/figurePng.d.ts.map +1 -1
- package/lib/download/core/figurePng.js +21 -6
- package/lib/download/core/figurePng.js.map +1 -1
- package/lib/download/core/svgToPng.d.ts.map +1 -1
- package/lib/download/core/svgToPng.js +4 -2
- package/lib/download/core/svgToPng.js.map +1 -1
- package/lib/format/core/relativeTime.d.ts +8 -3
- package/lib/format/core/relativeTime.d.ts.map +1 -1
- package/lib/format/core/relativeTime.js +19 -7
- package/lib/format/core/relativeTime.js.map +1 -1
- package/lib/host/core/connectHost.d.ts +25 -34
- package/lib/host/core/connectHost.d.ts.map +1 -1
- package/lib/host/core/connectHost.js +43 -89
- package/lib/host/core/connectHost.js.map +1 -1
- package/lib/host/core/hostOutputs.d.ts +58 -0
- package/lib/host/core/hostOutputs.d.ts.map +1 -0
- package/lib/host/core/hostOutputs.js +151 -0
- package/lib/host/core/hostOutputs.js.map +1 -0
- package/lib/host/core/index.d.ts +9 -3
- package/lib/host/core/index.d.ts.map +1 -1
- package/lib/host/core/index.js +4 -1
- package/lib/host/core/index.js.map +1 -1
- package/lib/host/core/irKinds.d.ts +124 -0
- package/lib/host/core/irKinds.d.ts.map +1 -0
- package/lib/host/core/irKinds.js +46 -0
- package/lib/host/core/irKinds.js.map +1 -0
- package/lib/host/core/kinds.d.ts +13 -5
- package/lib/host/core/kinds.d.ts.map +1 -1
- package/lib/host/core/kinds.js +11 -5
- package/lib/host/core/kinds.js.map +1 -1
- package/lib/host/core/protocol.d.ts +50 -7
- package/lib/host/core/protocol.d.ts.map +1 -1
- package/lib/host/core/protocol.js +10 -6
- package/lib/host/core/protocol.js.map +1 -1
- package/lib/host/core/selectInputs.d.ts +37 -0
- package/lib/host/core/selectInputs.d.ts.map +1 -0
- package/lib/host/core/selectInputs.js +110 -0
- package/lib/host/core/selectInputs.js.map +1 -0
- package/lib/host/testing/sciPeaks.d.ts +31 -0
- package/lib/host/testing/sciPeaks.d.ts.map +1 -1
- package/lib/host/testing/sciPeaks.js +73 -0
- package/lib/host/testing/sciPeaks.js.map +1 -1
- package/lib/host/testing/standInHost.d.ts +9 -1
- package/lib/host/testing/standInHost.d.ts.map +1 -1
- package/lib/host/testing/standInHost.js +19 -1
- package/lib/host/testing/standInHost.js.map +1 -1
- package/lib/host/ui/useHostConnection.d.ts +13 -5
- package/lib/host/ui/useHostConnection.d.ts.map +1 -1
- package/lib/host/ui/useHostConnection.js +4 -3
- package/lib/host/ui/useHostConnection.js.map +1 -1
- package/lib/host-testing.d.ts +2 -2
- package/lib/host-testing.d.ts.map +1 -1
- package/lib/host-testing.js +2 -2
- package/lib/host-testing.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/ir/core/handPicking.d.ts +58 -0
- package/lib/ir/core/handPicking.d.ts.map +1 -0
- package/lib/ir/core/handPicking.js +130 -0
- package/lib/ir/core/handPicking.js.map +1 -0
- package/lib/ir/core/index.d.ts +3 -0
- package/lib/ir/core/index.d.ts.map +1 -1
- package/lib/ir/core/index.js +3 -0
- package/lib/ir/core/index.js.map +1 -1
- package/lib/ir/core/irBandState.d.ts +57 -0
- package/lib/ir/core/irBandState.d.ts.map +1 -0
- package/lib/ir/core/irBandState.js +119 -0
- package/lib/ir/core/irBandState.js.map +1 -0
- package/lib/ir/core/irCommands.d.ts +11 -0
- package/lib/ir/core/irCommands.d.ts.map +1 -1
- package/lib/ir/core/irCommands.js +6 -0
- package/lib/ir/core/irCommands.js.map +1 -1
- package/lib/ir/core/irPeaks.d.ts +28 -0
- package/lib/ir/core/irPeaks.d.ts.map +1 -0
- package/lib/ir/core/irPeaks.js +66 -0
- package/lib/ir/core/irPeaks.js.map +1 -0
- package/lib/ir/core/irReducer.d.ts +8 -0
- package/lib/ir/core/irReducer.d.ts.map +1 -1
- package/lib/ir/core/irReducer.js +15 -32
- package/lib/ir/core/irReducer.js.map +1 -1
- package/lib/ir/core/irSpectrum.d.ts +15 -0
- package/lib/ir/core/irSpectrum.d.ts.map +1 -1
- package/lib/ir/core/irSpectrum.js.map +1 -1
- package/lib/ir/core/irState.d.ts +9 -6
- package/lib/ir/core/irState.d.ts.map +1 -1
- package/lib/ir/core/irState.js +10 -6
- package/lib/ir/core/irState.js.map +1 -1
- package/lib/ir/core/pickBands.d.ts +14 -0
- package/lib/ir/core/pickBands.d.ts.map +1 -1
- package/lib/ir/core/pickBands.js +39 -33
- package/lib/ir/core/pickBands.js.map +1 -1
- package/lib/ir/core/ramanBands.d.ts +9 -1
- package/lib/ir/core/ramanBands.d.ts.map +1 -1
- package/lib/ir/core/ramanBands.js +2 -2
- package/lib/ir/core/ramanBands.js.map +1 -1
- package/lib/ir/core/spectrumBands.d.ts +46 -0
- package/lib/ir/core/spectrumBands.d.ts.map +1 -0
- package/lib/ir/core/spectrumBands.js +98 -0
- package/lib/ir/core/spectrumBands.js.map +1 -0
- package/lib/ir/ui/BandRow.d.ts +5 -0
- package/lib/ir/ui/BandRow.d.ts.map +1 -1
- package/lib/ir/ui/BandRow.js +4 -3
- package/lib/ir/ui/BandRow.js.map +1 -1
- package/lib/ir/ui/BandsPanel.d.ts +5 -0
- package/lib/ir/ui/BandsPanel.d.ts.map +1 -1
- package/lib/ir/ui/BandsPanel.js +36 -16
- package/lib/ir/ui/BandsPanel.js.map +1 -1
- package/lib/ir/ui/BandsToolbar.d.ts +9 -0
- package/lib/ir/ui/BandsToolbar.d.ts.map +1 -0
- package/lib/ir/ui/BandsToolbar.js +61 -0
- package/lib/ir/ui/BandsToolbar.js.map +1 -0
- package/lib/ir/ui/IrCanvas.d.ts +2 -1
- package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
- package/lib/ir/ui/IrCanvas.js +19 -5
- package/lib/ir/ui/IrCanvas.js.map +1 -1
- package/lib/ir/ui/IrChart.d.ts.map +1 -1
- package/lib/ir/ui/IrChart.js +8 -1
- package/lib/ir/ui/IrChart.js.map +1 -1
- package/lib/ir/ui/IrDocumentationDialog.js +1 -1
- package/lib/ir/ui/IrDocumentationDialog.js.map +1 -1
- package/lib/ir/ui/IrEditor.d.ts +4 -2
- package/lib/ir/ui/IrEditor.d.ts.map +1 -1
- package/lib/ir/ui/IrEditor.js.map +1 -1
- package/lib/ir/ui/IrShell.d.ts.map +1 -1
- package/lib/ir/ui/IrShell.js +6 -0
- package/lib/ir/ui/IrShell.js.map +1 -1
- package/lib/ir/ui/IrStatusBar.d.ts.map +1 -1
- package/lib/ir/ui/IrStatusBar.js +1 -1
- package/lib/ir/ui/IrStatusBar.js.map +1 -1
- package/lib/ir/ui/IrToolbar.d.ts +4 -3
- package/lib/ir/ui/IrToolbar.d.ts.map +1 -1
- package/lib/ir/ui/IrToolbar.js +5 -4
- package/lib/ir/ui/IrToolbar.js.map +1 -1
- package/lib/ir/ui/VibrationalEditor.d.ts +2 -1
- package/lib/ir/ui/VibrationalEditor.d.ts.map +1 -1
- package/lib/ir/ui/VibrationalEditor.js.map +1 -1
- package/lib/ir/ui/irActions.d.ts +5 -0
- package/lib/ir/ui/irActions.d.ts.map +1 -1
- package/lib/ir/ui/irActions.js +6 -0
- package/lib/ir/ui/irActions.js.map +1 -1
- package/lib/ir/ui/irChartProps.d.ts +4 -1
- package/lib/ir/ui/irChartProps.d.ts.map +1 -1
- package/lib/ir/ui/useIrEditor.d.ts +5 -11
- package/lib/ir/ui/useIrEditor.d.ts.map +1 -1
- package/lib/ir/ui/useIrEditor.js +17 -18
- package/lib/ir/ui/useIrEditor.js.map +1 -1
- package/lib/locales/de.json +63 -0
- package/lib/locales/en.json +63 -0
- package/lib/locales/es.json +63 -0
- package/lib/locales/fr.json +63 -0
- package/lib/locales/it.json +63 -0
- package/lib/slides/ui/slideSanitizeSchema.d.ts +3 -2
- package/lib/slides/ui/slideSanitizeSchema.d.ts.map +1 -1
- package/lib/slides/ui/slideSanitizeSchema.js +82 -2
- package/lib/slides/ui/slideSanitizeSchema.js.map +1 -1
- package/package.json +1 -1
- package/src/about/core/providers.ts +7 -0
- package/src/about/ui/AboutProvidedBy.tsx +49 -0
- package/src/download/core/figurePng.ts +21 -8
- package/src/download/core/svgToPng.ts +3 -4
- package/src/format/core/relativeTime.ts +29 -7
- package/src/host/core/connectHost.ts +77 -120
- package/src/host/core/hostOutputs.ts +197 -0
- package/src/host/core/index.ts +13 -7
- package/src/host/core/irKinds.ts +92 -0
- package/src/host/core/kinds.ts +16 -6
- package/src/host/core/protocol.ts +50 -8
- package/src/host/core/selectInputs.ts +145 -0
- package/src/host/testing/sciPeaks.ts +140 -0
- package/src/host/testing/standInHost.ts +30 -5
- package/src/host/ui/useHostConnection.ts +17 -8
- package/src/host-testing.ts +4 -0
- package/src/ir/core/handPicking.ts +154 -0
- package/src/ir/core/index.ts +14 -0
- package/src/ir/core/irBandState.ts +155 -0
- package/src/ir/core/irCommands.ts +7 -0
- package/src/ir/core/irPeaks.ts +79 -0
- package/src/ir/core/irReducer.ts +27 -34
- package/src/ir/core/irSpectrum.ts +16 -0
- package/src/ir/core/irState.ts +15 -8
- package/src/ir/core/pickBands.ts +46 -39
- package/src/ir/core/ramanBands.ts +2 -2
- package/src/ir/core/spectrumBands.ts +113 -0
- package/src/ir/ui/BandRow.tsx +20 -2
- package/src/ir/ui/BandsPanel.tsx +46 -88
- package/src/ir/ui/BandsToolbar.tsx +152 -0
- package/src/ir/ui/IrCanvas.tsx +22 -11
- package/src/ir/ui/IrChart.tsx +10 -1
- package/src/ir/ui/IrDocumentationDialog.tsx +7 -0
- package/src/ir/ui/IrEditor.tsx +4 -2
- package/src/ir/ui/IrShell.tsx +5 -0
- package/src/ir/ui/IrStatusBar.tsx +4 -1
- package/src/ir/ui/IrToolbar.tsx +14 -5
- package/src/ir/ui/VibrationalEditor.tsx +2 -1
- package/src/ir/ui/irActions.ts +11 -0
- package/src/ir/ui/irChartProps.ts +4 -1
- package/src/ir/ui/useIrEditor.ts +21 -21
- package/src/locales/de.json +63 -0
- package/src/locales/en.json +63 -0
- package/src/locales/es.json +63 -0
- package/src/locales/fr.json +63 -0
- package/src/locales/it.json +63 -0
- package/src/slides/ui/slideSanitizeSchema.ts +86 -2
- package/styles/chrome.css +7 -0
- package/styles/slides.css +47 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking bands by hand.
|
|
3
|
+
*
|
|
4
|
+
* Where a click lands is not where the band is: it is taken to the top of the
|
|
5
|
+
* band it was aimed at, and the band is read off the spectrum there exactly as
|
|
6
|
+
* automatic picking reads one — the same values, and the same strength letter.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { peakPicking } from 'ir-spectrum';
|
|
10
|
+
import {
|
|
11
|
+
xMinMaxValues,
|
|
12
|
+
xyMaxClosestYPoint,
|
|
13
|
+
xyMaxYPoint,
|
|
14
|
+
xyMinYPoint,
|
|
15
|
+
} from 'ml-spectra-processing';
|
|
16
|
+
|
|
17
|
+
import type { IrBand, IrBandStrength } from './irBand.ts';
|
|
18
|
+
import type { IrSpectrum } from './irSpectrum.ts';
|
|
19
|
+
import { infraredMeasurement } from './pickBands.ts';
|
|
20
|
+
import { intensityStrength } from './ramanBands.ts';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* What a click while picking by hand makes of a spectrum's bands: the band it
|
|
24
|
+
* landed on taken away, or else the band under it added.
|
|
25
|
+
* @param spectrum - The spectrum picked on.
|
|
26
|
+
* @param wavenumber - Where the click landed, in cm⁻¹.
|
|
27
|
+
* @param reach - How close to a band counts as on it, and how far either side
|
|
28
|
+
* to look for one, in cm⁻¹: a few pixels' worth at the zoom of the chart.
|
|
29
|
+
* @returns The new bands, or the spectrum's own list when nothing changed.
|
|
30
|
+
*/
|
|
31
|
+
export function editBandsAt(
|
|
32
|
+
spectrum: IrSpectrum,
|
|
33
|
+
wavenumber: number,
|
|
34
|
+
reach: number,
|
|
35
|
+
): IrBand[] {
|
|
36
|
+
const bands = spectrum.bands ?? [];
|
|
37
|
+
let hit: IrBand | null = null;
|
|
38
|
+
let nearest = Math.abs(reach);
|
|
39
|
+
for (const band of bands) {
|
|
40
|
+
const distance = Math.abs(band.wavenumber - wavenumber);
|
|
41
|
+
if (distance <= nearest) {
|
|
42
|
+
nearest = distance;
|
|
43
|
+
hit = band;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (hit !== null) return removeBand(bands, hit.wavenumber);
|
|
47
|
+
const band = pickBandAt(spectrum, wavenumber, reach);
|
|
48
|
+
return band === null ? bands : addBand(bands, band);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The band a click asks for: the top of the band under it.
|
|
53
|
+
*
|
|
54
|
+
* The highest point within reach of the click first, then uphill from there to
|
|
55
|
+
* the top: a click lands on a band's flank as often as on its top, and a flank
|
|
56
|
+
* carries bumps of noise that a climb from the click alone would stop at. A
|
|
57
|
+
* band is a maximum of absorbance, or of intensity for a Raman spectrum,
|
|
58
|
+
* whichever way up the chart is drawn.
|
|
59
|
+
* @param spectrum - The spectrum picked on.
|
|
60
|
+
* @param wavenumber - Where the click landed, in cm⁻¹.
|
|
61
|
+
* @param reach - How far either side of it to look, in cm⁻¹.
|
|
62
|
+
* @returns The band, or `null` when the spectrum carries no axis a band is a
|
|
63
|
+
* maximum of, or is flat within reach.
|
|
64
|
+
*/
|
|
65
|
+
export function pickBandAt(
|
|
66
|
+
spectrum: IrSpectrum,
|
|
67
|
+
wavenumber: number,
|
|
68
|
+
reach: number,
|
|
69
|
+
): IrBand | null {
|
|
70
|
+
const { wavenumber: x, intensity, absorbance } = spectrum;
|
|
71
|
+
const y = intensity ?? absorbance;
|
|
72
|
+
if (y === undefined || x.length < 2 || !Number.isFinite(wavenumber)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const around = Number.isFinite(reach) ? Math.abs(reach) : 0;
|
|
76
|
+
const within = { from: wavenumber - around, to: wavenumber + around };
|
|
77
|
+
const near = xyMaxYPoint({ x, y }, within);
|
|
78
|
+
// A stretch that does not rise at all has no band to climb to.
|
|
79
|
+
if (around > 0 && near.y <= xyMinYPoint({ x, y }, within).y) return null;
|
|
80
|
+
const top = xyMaxClosestYPoint({ x, y }, { targetIndex: near.index });
|
|
81
|
+
return bandAtIndex(spectrum, top.index);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The band at one sample of a spectrum, read the way automatic picking reads
|
|
86
|
+
* one.
|
|
87
|
+
* @param spectrum - The spectrum.
|
|
88
|
+
* @param index - The sample.
|
|
89
|
+
* @returns The band, or `null` when there is no such sample or no axis to read
|
|
90
|
+
* it on.
|
|
91
|
+
*/
|
|
92
|
+
export function bandAtIndex(
|
|
93
|
+
spectrum: IrSpectrum,
|
|
94
|
+
index: number,
|
|
95
|
+
): IrBand | null {
|
|
96
|
+
const { id, wavenumber, absorbance, transmittance, intensity } = spectrum;
|
|
97
|
+
const at = wavenumber[index];
|
|
98
|
+
if (at === undefined) return null;
|
|
99
|
+
if (intensity !== undefined) {
|
|
100
|
+
const value = intensity[index] ?? Number.NaN;
|
|
101
|
+
const { min, max } = xMinMaxValues(intensity);
|
|
102
|
+
return {
|
|
103
|
+
spectrumId: id,
|
|
104
|
+
wavenumber: at,
|
|
105
|
+
intensity: value,
|
|
106
|
+
strength: intensityStrength(value, min, max),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (absorbance === undefined || transmittance === undefined) return null;
|
|
110
|
+
// `ir-spectrum` reads the sample and works out the letter, as it does for
|
|
111
|
+
// every band it picks itself.
|
|
112
|
+
const peak = peakPicking(
|
|
113
|
+
infraredMeasurement(wavenumber, absorbance, transmittance),
|
|
114
|
+
at,
|
|
115
|
+
{ yVariable: 'a' },
|
|
116
|
+
);
|
|
117
|
+
if (peak === undefined) return null;
|
|
118
|
+
return {
|
|
119
|
+
spectrumId: id,
|
|
120
|
+
wavenumber: at,
|
|
121
|
+
absorbance: peak.absorbance,
|
|
122
|
+
transmittance: peak.transmittance * 100,
|
|
123
|
+
strength: peak.kind as IrBandStrength,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The bands with one more, in its place.
|
|
129
|
+
* @param bands - The bands, ascending.
|
|
130
|
+
* @param band - The band to add.
|
|
131
|
+
* @returns A new list, or the same one when a band already sits there.
|
|
132
|
+
*/
|
|
133
|
+
export function addBand(bands: IrBand[], band: IrBand): IrBand[] {
|
|
134
|
+
let index = 0;
|
|
135
|
+
while (
|
|
136
|
+
index < bands.length &&
|
|
137
|
+
(bands[index]?.wavenumber ?? 0) < band.wavenumber
|
|
138
|
+
) {
|
|
139
|
+
index++;
|
|
140
|
+
}
|
|
141
|
+
if (bands[index]?.wavenumber === band.wavenumber) return bands;
|
|
142
|
+
return bands.toSpliced(index, 0, band);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The bands without the one at a wavenumber.
|
|
147
|
+
* @param bands - The bands.
|
|
148
|
+
* @param wavenumber - Where the band to take away sits, exactly.
|
|
149
|
+
* @returns A new list, or the same one when no band sits there.
|
|
150
|
+
*/
|
|
151
|
+
export function removeBand(bands: IrBand[], wavenumber: number): IrBand[] {
|
|
152
|
+
const index = bands.findIndex((band) => band.wavenumber === wavenumber);
|
|
153
|
+
return index === -1 ? bands : bands.toSpliced(index, 1);
|
|
154
|
+
}
|
package/src/ir/core/index.ts
CHANGED
|
@@ -24,6 +24,14 @@ export type { BandLabelPlan, BandLabelPlanOptions } from './bandLabelPlan.ts';
|
|
|
24
24
|
export { planBandLabels } from './bandLabelPlan.ts';
|
|
25
25
|
export { detectIrFormat } from './detectIrFormat.ts';
|
|
26
26
|
export { downloadIrSpectra, downloadIrSpectrum } from './downloadSpectra.ts';
|
|
27
|
+
export {
|
|
28
|
+
addBand,
|
|
29
|
+
bandAtIndex,
|
|
30
|
+
editBandsAt,
|
|
31
|
+
pickBandAt,
|
|
32
|
+
removeBand,
|
|
33
|
+
} from './handPicking.ts';
|
|
34
|
+
export { bandsFromPeaks, peaksFromBands } from './irPeaks.ts';
|
|
27
35
|
export type { IrJcampReadOptions } from './fromJcamp.ts';
|
|
28
36
|
export type { IrSpcReadOptions } from './fromSpc.ts';
|
|
29
37
|
export type { IrBand, IrBandStrength } from './irBand.ts';
|
|
@@ -124,6 +132,12 @@ export { loadIrSpectra } from './loadIrSpectra.ts';
|
|
|
124
132
|
export { BAND_HIT_RADIUS, nearestBandAt } from './nearestBand.ts';
|
|
125
133
|
export type { PickBandsOptions } from './pickBands.ts';
|
|
126
134
|
export { pickBands } from './pickBands.ts';
|
|
135
|
+
export {
|
|
136
|
+
orderedBands,
|
|
137
|
+
repickBands,
|
|
138
|
+
samePicking,
|
|
139
|
+
withBands,
|
|
140
|
+
} from './spectrumBands.ts';
|
|
127
141
|
export {
|
|
128
142
|
DEFAULT_DATA_TYPE,
|
|
129
143
|
JCAMP_EXTENSION,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The changes of the viewer's state that are about bands: spectra arriving
|
|
3
|
+
* with theirs or without, a list picked by hand, a list found automatically
|
|
4
|
+
* again, and new picking settings, which pick again every list that was found
|
|
5
|
+
* automatically.
|
|
6
|
+
*
|
|
7
|
+
* Apart from the reducer, which routes these actions here; like it, each
|
|
8
|
+
* returns the state itself when it has nothing to do.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { IrBand } from './irBand.ts';
|
|
12
|
+
import type { IrSpectrum } from './irSpectrum.ts';
|
|
13
|
+
import type { IrSettings, IrState, PartialIrState } from './irState.ts';
|
|
14
|
+
import { mergeIrSettings } from './irState.ts';
|
|
15
|
+
import type { PickBandsOptions } from './pickBands.ts';
|
|
16
|
+
import {
|
|
17
|
+
orderedBands,
|
|
18
|
+
repickBands,
|
|
19
|
+
samePicking,
|
|
20
|
+
withBands,
|
|
21
|
+
} from './spectrumBands.ts';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Spectra arriving, each carrying its bands: its own, or picked now.
|
|
25
|
+
* @param state - The state they arrive in.
|
|
26
|
+
* @param spectra - The spectra.
|
|
27
|
+
* @returns A new list.
|
|
28
|
+
*/
|
|
29
|
+
export function arriving(
|
|
30
|
+
state: IrState,
|
|
31
|
+
spectra: readonly IrSpectrum[],
|
|
32
|
+
): IrSpectrum[] {
|
|
33
|
+
const { picking } = state.settings;
|
|
34
|
+
return spectra.map((spectrum) => withBands(spectrum, picking));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Give one spectrum bands picked by hand.
|
|
39
|
+
* @param state - The state in force.
|
|
40
|
+
* @param id - Which spectrum.
|
|
41
|
+
* @param bands - Its bands, in any order.
|
|
42
|
+
* @returns The new state, or the old one when they were its own already.
|
|
43
|
+
*/
|
|
44
|
+
export function setSpectrumBands(
|
|
45
|
+
state: IrState,
|
|
46
|
+
id: string,
|
|
47
|
+
bands: IrBand[],
|
|
48
|
+
): IrState {
|
|
49
|
+
return mapSpectrum(state, id, (spectrum) => {
|
|
50
|
+
const ordered = orderedBands(spectrum.id, bands);
|
|
51
|
+
return ordered === spectrum.bands && spectrum.bandsByHand === true
|
|
52
|
+
? spectrum
|
|
53
|
+
: { ...spectrum, bands: ordered, bandsByHand: true };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Have one spectrum's bands found automatically again.
|
|
59
|
+
* @param state - The state in force.
|
|
60
|
+
* @param id - Which spectrum.
|
|
61
|
+
* @returns The new state, or the old one when they already were.
|
|
62
|
+
*/
|
|
63
|
+
export function resetSpectrumBands(state: IrState, id: string): IrState {
|
|
64
|
+
const { picking } = state.settings;
|
|
65
|
+
return mapSpectrum(state, id, (spectrum) =>
|
|
66
|
+
spectrum.bandsByHand
|
|
67
|
+
? repickBands({ ...spectrum, bandsByHand: false }, picking)
|
|
68
|
+
: spectrum,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Fold a change into the workspace, and pick again what it changes.
|
|
74
|
+
* @param state - The state in force.
|
|
75
|
+
* @param change - What to change about the workspace.
|
|
76
|
+
* @returns The new state.
|
|
77
|
+
*/
|
|
78
|
+
export function changeSettings(
|
|
79
|
+
state: IrState,
|
|
80
|
+
change: Partial<IrSettings>,
|
|
81
|
+
): IrState {
|
|
82
|
+
const settings = mergeIrSettings(state.settings, change);
|
|
83
|
+
const { spectra } = state.data;
|
|
84
|
+
const picked = pickedAgain(spectra, state.settings.picking, settings.picking);
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
data: picked === spectra ? state.data : { spectra: picked },
|
|
88
|
+
settings,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Take a whole state, as a host restoring one supplies it.
|
|
94
|
+
* @param state - The state in force.
|
|
95
|
+
* @param restored - The parts the host supplies.
|
|
96
|
+
* @returns The new state.
|
|
97
|
+
*/
|
|
98
|
+
export function restoreState(
|
|
99
|
+
state: IrState,
|
|
100
|
+
restored: PartialIrState,
|
|
101
|
+
): IrState {
|
|
102
|
+
const { data, view, settings } = restored;
|
|
103
|
+
const merged = mergeIrSettings(state.settings, settings);
|
|
104
|
+
const spectra =
|
|
105
|
+
data === undefined
|
|
106
|
+
? state.data.spectra
|
|
107
|
+
: arriving({ ...state, settings: merged }, data.spectra);
|
|
108
|
+
return {
|
|
109
|
+
data: {
|
|
110
|
+
spectra: pickedAgain(spectra, state.settings.picking, merged.picking),
|
|
111
|
+
},
|
|
112
|
+
view: { ...state.view, ...view },
|
|
113
|
+
settings: merged,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Change one spectrum, leaving the state alone when it did not change.
|
|
119
|
+
* @param state - The state in force.
|
|
120
|
+
* @param id - Which spectrum.
|
|
121
|
+
* @param change - What to make of it; return it unchanged for no change.
|
|
122
|
+
* @returns The new state, or the old one.
|
|
123
|
+
*/
|
|
124
|
+
export function mapSpectrum(
|
|
125
|
+
state: IrState,
|
|
126
|
+
id: string,
|
|
127
|
+
change: (spectrum: IrSpectrum) => IrSpectrum,
|
|
128
|
+
): IrState {
|
|
129
|
+
let changed = false;
|
|
130
|
+
const spectra = state.data.spectra.map((spectrum) => {
|
|
131
|
+
if (spectrum.id !== id) return spectrum;
|
|
132
|
+
const next = change(spectrum);
|
|
133
|
+
if (next !== spectrum) changed = true;
|
|
134
|
+
return next;
|
|
135
|
+
});
|
|
136
|
+
if (!changed) return state;
|
|
137
|
+
return { ...state, data: { spectra }, view: { ...state.view, error: null } };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The spectra under new picking settings: those found automatically picked
|
|
142
|
+
* again.
|
|
143
|
+
* @param spectra - The spectra.
|
|
144
|
+
* @param before - The settings they were picked with.
|
|
145
|
+
* @param after - The settings now in force.
|
|
146
|
+
* @returns The same list when the settings pick as before.
|
|
147
|
+
*/
|
|
148
|
+
function pickedAgain(
|
|
149
|
+
spectra: IrSpectrum[],
|
|
150
|
+
before: PickBandsOptions,
|
|
151
|
+
after: PickBandsOptions,
|
|
152
|
+
): IrSpectrum[] {
|
|
153
|
+
if (samePicking(before, after)) return spectra;
|
|
154
|
+
return spectra.map((spectrum) => repickBands(spectrum, after));
|
|
155
|
+
}
|
|
@@ -86,6 +86,13 @@ export const irCommands = {
|
|
|
86
86
|
'A drag zooms to exactly the rectangle it sweeps out, both axes at once — for looking into a baseline or a shoulder rather than across the spectrum. It is the only drag on percent transmittance.',
|
|
87
87
|
gestures: [{ title: 'Over the chart', shortcuts: ['Drag a rectangle'] }],
|
|
88
88
|
},
|
|
89
|
+
pickTool: {
|
|
90
|
+
title: 'Pick bands by hand',
|
|
91
|
+
shortcut: { keys: ['k'] },
|
|
92
|
+
description:
|
|
93
|
+
'A click adds the band under it to the selected spectrum, at its top; a click on a marked band takes it away. A drag still zooms, and bands picked by hand stay put when the threshold moves.',
|
|
94
|
+
gestures: [{ title: 'Over the chart', shortcuts: ['Click'] }],
|
|
95
|
+
},
|
|
89
96
|
zoomIn: {
|
|
90
97
|
title: 'Zoom in',
|
|
91
98
|
shortcut: { keys: ['+', '='] },
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bands of an infrared spectrum as a notebook writes them, and back: the
|
|
3
|
+
* peaks of the host protocol's `spectra.ir.jdx` and `spectra.ir.peaks`.
|
|
4
|
+
*
|
|
5
|
+
* The two differ in two ways only: a notebook's transmittance is a fraction
|
|
6
|
+
* where the viewer holds a percentage, and it calls a band's strength `kind`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { xFindClosestIndex } from 'ml-spectra-processing';
|
|
10
|
+
|
|
11
|
+
import type { IrPeakValue } from '../../host/core/irKinds.ts';
|
|
12
|
+
|
|
13
|
+
import { bandAtIndex } from './handPicking.ts';
|
|
14
|
+
import type { IrBand, IrBandStrength } from './irBand.ts';
|
|
15
|
+
import type { IrSpectrum } from './irSpectrum.ts';
|
|
16
|
+
import { orderedBands } from './spectrumBands.ts';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The bands a notebook handed over with a spectrum.
|
|
20
|
+
*
|
|
21
|
+
* Each stays where the notebook put it, with the values it gave; a value it
|
|
22
|
+
* left out is read off the spectrum at the nearest sample, and a strength
|
|
23
|
+
* letter that is not one of `w`, `m` and `S` is worked out again.
|
|
24
|
+
* @param spectrum - The spectrum they were picked on.
|
|
25
|
+
* @param peaks - The peaks, in any order.
|
|
26
|
+
* @returns The bands, ascending, one per wavenumber.
|
|
27
|
+
*/
|
|
28
|
+
export function bandsFromPeaks(
|
|
29
|
+
spectrum: IrSpectrum,
|
|
30
|
+
peaks: readonly IrPeakValue[],
|
|
31
|
+
): IrBand[] {
|
|
32
|
+
const bands: IrBand[] = [];
|
|
33
|
+
for (const peak of peaks) {
|
|
34
|
+
const { wavenumber, absorbance, transmittance, kind } = peak;
|
|
35
|
+
if (!Number.isFinite(wavenumber)) continue;
|
|
36
|
+
const read = bandAtIndex(
|
|
37
|
+
spectrum,
|
|
38
|
+
xFindClosestIndex(spectrum.wavenumber, wavenumber),
|
|
39
|
+
);
|
|
40
|
+
if (read === null) continue;
|
|
41
|
+
bands.push({
|
|
42
|
+
...read,
|
|
43
|
+
wavenumber,
|
|
44
|
+
...(absorbance === undefined ? {} : { absorbance }),
|
|
45
|
+
...(transmittance === undefined
|
|
46
|
+
? {}
|
|
47
|
+
: { transmittance: transmittance * 100 }),
|
|
48
|
+
strength: isStrength(kind) ? kind : read.strength,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return orderedBands(spectrum.id, bands);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Bands as a notebook keeps them.
|
|
56
|
+
* @param bands - The bands, as the viewer holds them.
|
|
57
|
+
* @returns The peaks, in the same order.
|
|
58
|
+
*/
|
|
59
|
+
export function peaksFromBands(bands: readonly IrBand[]): IrPeakValue[] {
|
|
60
|
+
const peaks: IrPeakValue[] = [];
|
|
61
|
+
for (const band of bands) {
|
|
62
|
+
const { wavenumber, absorbance, transmittance, strength } = band;
|
|
63
|
+
peaks.push({
|
|
64
|
+
wavenumber,
|
|
65
|
+
...(absorbance === undefined ? {} : { absorbance }),
|
|
66
|
+
...(transmittance === undefined
|
|
67
|
+
? {}
|
|
68
|
+
: { transmittance: transmittance / 100 }),
|
|
69
|
+
kind: strength,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return peaks;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isStrength(kind: string | undefined): kind is IrBandStrength {
|
|
76
|
+
return kind !== undefined && STRENGTHS.includes(kind);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const STRENGTHS: readonly string[] = ['w', 'm', 'S'] satisfies IrBandStrength[];
|
package/src/ir/core/irReducer.ts
CHANGED
|
@@ -13,10 +13,19 @@
|
|
|
13
13
|
* closed.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import type { IrBand } from './irBand.ts';
|
|
17
|
+
import {
|
|
18
|
+
arriving,
|
|
19
|
+
changeSettings,
|
|
20
|
+
mapSpectrum,
|
|
21
|
+
resetSpectrumBands,
|
|
22
|
+
restoreState,
|
|
23
|
+
setSpectrumBands,
|
|
24
|
+
} from './irBandState.ts';
|
|
16
25
|
import type { IrPanelId } from './irSidePanels.ts';
|
|
17
26
|
import type { IrDomain, IrSpectrum } from './irSpectrum.ts';
|
|
18
27
|
import type { IrSettings, IrState, IrTool, PartialIrState } from './irState.ts';
|
|
19
|
-
import { isIrWindow
|
|
28
|
+
import { isIrWindow } from './irState.ts';
|
|
20
29
|
|
|
21
30
|
/** Everything that can be asked of the viewer. */
|
|
22
31
|
export type IrAction =
|
|
@@ -26,6 +35,8 @@ export type IrAction =
|
|
|
26
35
|
| { type: 'renameSpectrum'; id: string; name: string }
|
|
27
36
|
| { type: 'setSpectrumColor'; id: string; color: string }
|
|
28
37
|
| { type: 'setSpectrumVisible'; id: string; visible: boolean }
|
|
38
|
+
| { type: 'setBands'; id: string; bands: IrBand[] }
|
|
39
|
+
| { type: 'resetBands'; id: string }
|
|
29
40
|
| { type: 'selectSpectrum'; id: string | null }
|
|
30
41
|
| { type: 'setDomain'; domain: IrDomain }
|
|
31
42
|
| { type: 'resetDomain' }
|
|
@@ -46,7 +57,10 @@ export function irReducer(state: IrState, action: IrAction): IrState {
|
|
|
46
57
|
switch (action.type) {
|
|
47
58
|
case 'addSpectra': {
|
|
48
59
|
if (action.spectra.length === 0) return state;
|
|
49
|
-
const spectra = [
|
|
60
|
+
const spectra = [
|
|
61
|
+
...state.data.spectra,
|
|
62
|
+
...arriving(state, action.spectra),
|
|
63
|
+
];
|
|
50
64
|
return {
|
|
51
65
|
...state,
|
|
52
66
|
data: { spectra },
|
|
@@ -65,7 +79,7 @@ export function irReducer(state: IrState, action: IrAction): IrState {
|
|
|
65
79
|
case 'setSpectra': {
|
|
66
80
|
return {
|
|
67
81
|
...state,
|
|
68
|
-
data: { spectra:
|
|
82
|
+
data: { spectra: arriving(state, action.spectra) },
|
|
69
83
|
view: {
|
|
70
84
|
...state.view,
|
|
71
85
|
selectedId: action.spectra[0]?.id ?? null,
|
|
@@ -122,6 +136,14 @@ export function irReducer(state: IrState, action: IrAction): IrState {
|
|
|
122
136
|
);
|
|
123
137
|
}
|
|
124
138
|
|
|
139
|
+
case 'setBands': {
|
|
140
|
+
return setSpectrumBands(state, action.id, action.bands);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case 'resetBands': {
|
|
144
|
+
return resetSpectrumBands(state, action.id);
|
|
145
|
+
}
|
|
146
|
+
|
|
125
147
|
case 'selectSpectrum': {
|
|
126
148
|
if (state.view.selectedId === action.id) return state;
|
|
127
149
|
return { ...state, view: { ...state.view, selectedId: action.id } };
|
|
@@ -162,17 +184,11 @@ export function irReducer(state: IrState, action: IrAction): IrState {
|
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
case 'setSettings': {
|
|
165
|
-
|
|
166
|
-
return { ...state, settings };
|
|
187
|
+
return changeSettings(state, action.settings);
|
|
167
188
|
}
|
|
168
189
|
|
|
169
190
|
case 'loadState': {
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
data: data === undefined ? state.data : { spectra: [...data.spectra] },
|
|
173
|
-
view: { ...state.view, ...view },
|
|
174
|
-
settings: mergeIrSettings(state.settings, settings),
|
|
175
|
-
};
|
|
191
|
+
return restoreState(state, action.state);
|
|
176
192
|
}
|
|
177
193
|
|
|
178
194
|
case 'clear': {
|
|
@@ -191,29 +207,6 @@ export function irReducer(state: IrState, action: IrAction): IrState {
|
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
|
|
194
|
-
/**
|
|
195
|
-
* Change one spectrum, leaving the state alone when it did not change.
|
|
196
|
-
* @param state - The state in force.
|
|
197
|
-
* @param id - Which spectrum.
|
|
198
|
-
* @param change - What to make of it; return it unchanged for no change.
|
|
199
|
-
* @returns The new state, or the old one.
|
|
200
|
-
*/
|
|
201
|
-
function mapSpectrum(
|
|
202
|
-
state: IrState,
|
|
203
|
-
id: string,
|
|
204
|
-
change: (spectrum: IrSpectrum) => IrSpectrum,
|
|
205
|
-
): IrState {
|
|
206
|
-
let changed = false;
|
|
207
|
-
const spectra = state.data.spectra.map((spectrum) => {
|
|
208
|
-
if (spectrum.id !== id) return spectrum;
|
|
209
|
-
const next = change(spectrum);
|
|
210
|
-
if (next !== spectrum) changed = true;
|
|
211
|
-
return next;
|
|
212
|
-
});
|
|
213
|
-
if (!changed) return state;
|
|
214
|
-
return { ...state, data: { spectra }, view: { ...state.view, error: null } };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
210
|
/**
|
|
218
211
|
* Refuse an action, saying why.
|
|
219
212
|
* @param state - The state in force.
|
|
@@ -25,6 +25,8 @@ import type { DataXY } from 'cheminfo-types';
|
|
|
25
25
|
|
|
26
26
|
import type { ChartDomain } from '../../chart/core/chartDomain.ts';
|
|
27
27
|
|
|
28
|
+
import type { IrBand } from './irBand.ts';
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
31
|
* One trace of a measurement: wavenumber against whichever value axis is drawn.
|
|
30
32
|
*
|
|
@@ -150,6 +152,20 @@ export interface IrSpectrum {
|
|
|
150
152
|
* @default false
|
|
151
153
|
*/
|
|
152
154
|
excludeFromDomain?: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* The bands picked on it, ascending by wavenumber. The viewer picks them
|
|
157
|
+
* when a spectrum arrives without any; a host may hand them over with it, as
|
|
158
|
+
* a notebook that keeps them does.
|
|
159
|
+
* @default undefined
|
|
160
|
+
*/
|
|
161
|
+
bands?: IrBand[];
|
|
162
|
+
/**
|
|
163
|
+
* Whether `bands` were picked by hand — edited in the viewer, or handed over
|
|
164
|
+
* — rather than found with the picking settings, which re-pick only a list
|
|
165
|
+
* that was not.
|
|
166
|
+
* @default false
|
|
167
|
+
*/
|
|
168
|
+
bandsByHand?: boolean;
|
|
153
169
|
}
|
|
154
170
|
|
|
155
171
|
/**
|
package/src/ir/core/irState.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* `settings` is the workspace, which is how every spectrum is drawn whichever
|
|
8
8
|
* ones happen to be open.
|
|
9
9
|
*
|
|
10
|
-
* The bands are
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* a
|
|
10
|
+
* The bands are in `data`, each spectrum carrying its own: picked when it
|
|
11
|
+
* arrives, picked again by the reducer when the settings change while they were
|
|
12
|
+
* found automatically, and left alone once picked by hand. That is what lets a
|
|
13
|
+
* band be added or taken away, and a list be handed to a host and back.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { IrPanelId, IrSidePanelId } from './irSidePanels.ts';
|
|
@@ -18,6 +18,7 @@ import type { IrDomain, IrMode, IrSpectrum } from './irSpectrum.ts';
|
|
|
18
18
|
import type { IrTechnique } from './irTechnique.ts';
|
|
19
19
|
import { defaultIrMode, irModeFor, offersIrMode } from './irTechnique.ts';
|
|
20
20
|
import type { PickBandsOptions } from './pickBands.ts';
|
|
21
|
+
import { withBands } from './spectrumBands.ts';
|
|
21
22
|
|
|
22
23
|
/** The document: what a file holds. */
|
|
23
24
|
export interface IrData {
|
|
@@ -25,8 +26,11 @@ export interface IrData {
|
|
|
25
26
|
spectra: IrSpectrum[];
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* What a drag across the chart is for while it is chosen, and for `pick`, what
|
|
31
|
+
* a click is: a band added where it lands, or taken away.
|
|
32
|
+
*/
|
|
33
|
+
export type IrTool = 'read' | 'box' | 'pick';
|
|
30
34
|
|
|
31
35
|
/** Where you are in the viewer. */
|
|
32
36
|
export interface IrView {
|
|
@@ -165,8 +169,11 @@ export function createIrState(options: CreateIrStateOptions = {}): IrState {
|
|
|
165
169
|
technique,
|
|
166
170
|
mode: defaultIrMode(technique),
|
|
167
171
|
};
|
|
172
|
+
const merged = mergeIrSettings(opened, settings);
|
|
168
173
|
return {
|
|
169
|
-
data: {
|
|
174
|
+
data: {
|
|
175
|
+
spectra: spectra.map((spectrum) => withBands(spectrum, merged.picking)),
|
|
176
|
+
},
|
|
170
177
|
view: {
|
|
171
178
|
selectedId: spectra[0]?.id ?? null,
|
|
172
179
|
domain: null,
|
|
@@ -174,7 +181,7 @@ export function createIrState(options: CreateIrStateOptions = {}): IrState {
|
|
|
174
181
|
openPanelIds: [...(panelIds ?? DEFAULT_SIDE_PANELS)],
|
|
175
182
|
error: null,
|
|
176
183
|
},
|
|
177
|
-
settings:
|
|
184
|
+
settings: merged,
|
|
178
185
|
};
|
|
179
186
|
}
|
|
180
187
|
|