react-cheminfo 0.65.0 → 0.66.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 (237) hide show
  1. package/README.md +11 -6
  2. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  3. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  4. package/lib/ir/core/analysisSpectra.d.ts +23 -6
  5. package/lib/ir/core/analysisSpectra.d.ts.map +1 -1
  6. package/lib/ir/core/analysisSpectra.js.map +1 -1
  7. package/lib/ir/core/bandLabelPlan.d.ts.map +1 -1
  8. package/lib/ir/core/bandLabelPlan.js +11 -6
  9. package/lib/ir/core/bandLabelPlan.js.map +1 -1
  10. package/lib/ir/core/index.d.ts +13 -9
  11. package/lib/ir/core/index.d.ts.map +1 -1
  12. package/lib/ir/core/index.js +9 -6
  13. package/lib/ir/core/index.js.map +1 -1
  14. package/lib/ir/core/irBand.d.ts +45 -14
  15. package/lib/ir/core/irBand.d.ts.map +1 -1
  16. package/lib/ir/core/irBand.js +28 -6
  17. package/lib/ir/core/irBand.js.map +1 -1
  18. package/lib/ir/core/irCommands.d.ts +11 -0
  19. package/lib/ir/core/irCommands.d.ts.map +1 -1
  20. package/lib/ir/core/irCommands.js +11 -1
  21. package/lib/ir/core/irCommands.js.map +1 -1
  22. package/lib/ir/core/irDomain.d.ts +4 -3
  23. package/lib/ir/core/irDomain.d.ts.map +1 -1
  24. package/lib/ir/core/irDomain.js +10 -7
  25. package/lib/ir/core/irDomain.js.map +1 -1
  26. package/lib/ir/core/irFormat.d.ts +14 -2
  27. package/lib/ir/core/irFormat.d.ts.map +1 -1
  28. package/lib/ir/core/irFormat.js +33 -4
  29. package/lib/ir/core/irFormat.js.map +1 -1
  30. package/lib/ir/core/irMode.d.ts +35 -8
  31. package/lib/ir/core/irMode.d.ts.map +1 -1
  32. package/lib/ir/core/irMode.js +38 -10
  33. package/lib/ir/core/irMode.js.map +1 -1
  34. package/lib/ir/core/irReducer.d.ts +0 -28
  35. package/lib/ir/core/irReducer.d.ts.map +1 -1
  36. package/lib/ir/core/irReducer.js +1 -24
  37. package/lib/ir/core/irReducer.js.map +1 -1
  38. package/lib/ir/core/irSpectrum.d.ts +52 -26
  39. package/lib/ir/core/irSpectrum.d.ts.map +1 -1
  40. package/lib/ir/core/irSpectrum.js +21 -15
  41. package/lib/ir/core/irSpectrum.js.map +1 -1
  42. package/lib/ir/core/irState.d.ts +40 -2
  43. package/lib/ir/core/irState.d.ts.map +1 -1
  44. package/lib/ir/core/irState.js +38 -1
  45. package/lib/ir/core/irState.js.map +1 -1
  46. package/lib/ir/core/irTechnique.d.ts +94 -0
  47. package/lib/ir/core/irTechnique.d.ts.map +1 -0
  48. package/lib/ir/core/irTechnique.js +87 -0
  49. package/lib/ir/core/irTechnique.js.map +1 -0
  50. package/lib/ir/core/irTechniqueCommands.d.ts +26 -0
  51. package/lib/ir/core/irTechniqueCommands.d.ts.map +1 -0
  52. package/lib/ir/core/irTechniqueCommands.js +68 -0
  53. package/lib/ir/core/irTechniqueCommands.js.map +1 -0
  54. package/lib/ir/core/pickBands.d.ts +6 -5
  55. package/lib/ir/core/pickBands.d.ts.map +1 -1
  56. package/lib/ir/core/pickBands.js +49 -26
  57. package/lib/ir/core/pickBands.js.map +1 -1
  58. package/lib/ir/core/ramanBands.d.ts +30 -0
  59. package/lib/ir/core/ramanBands.d.ts.map +1 -0
  60. package/lib/ir/core/ramanBands.js +83 -0
  61. package/lib/ir/core/ramanBands.js.map +1 -0
  62. package/lib/ir/core/spectrumJcamp.d.ts +10 -5
  63. package/lib/ir/core/spectrumJcamp.d.ts.map +1 -1
  64. package/lib/ir/core/spectrumJcamp.js +63 -15
  65. package/lib/ir/core/spectrumJcamp.js.map +1 -1
  66. package/lib/ir/core/toIrSpectrum.d.ts.map +1 -1
  67. package/lib/ir/core/toIrSpectrum.js +17 -2
  68. package/lib/ir/core/toIrSpectrum.js.map +1 -1
  69. package/lib/ir/ui/BandRow.d.ts +30 -0
  70. package/lib/ir/ui/BandRow.d.ts.map +1 -0
  71. package/lib/ir/ui/BandRow.js +42 -0
  72. package/lib/ir/ui/BandRow.js.map +1 -0
  73. package/lib/ir/ui/BandsPanel.d.ts +4 -0
  74. package/lib/ir/ui/BandsPanel.d.ts.map +1 -1
  75. package/lib/ir/ui/BandsPanel.js +14 -59
  76. package/lib/ir/ui/BandsPanel.js.map +1 -1
  77. package/lib/ir/ui/IrAboutDialog.d.ts +7 -0
  78. package/lib/ir/ui/IrAboutDialog.d.ts.map +1 -1
  79. package/lib/ir/ui/IrAboutDialog.js +16 -3
  80. package/lib/ir/ui/IrAboutDialog.js.map +1 -1
  81. package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
  82. package/lib/ir/ui/IrCanvas.js +2 -2
  83. package/lib/ir/ui/IrCanvas.js.map +1 -1
  84. package/lib/ir/ui/IrChart.d.ts +2 -1
  85. package/lib/ir/ui/IrChart.d.ts.map +1 -1
  86. package/lib/ir/ui/IrChart.js +18 -21
  87. package/lib/ir/ui/IrChart.js.map +1 -1
  88. package/lib/ir/ui/IrCommandProvider.d.ts +7 -0
  89. package/lib/ir/ui/IrCommandProvider.d.ts.map +1 -1
  90. package/lib/ir/ui/IrCommandProvider.js +2 -2
  91. package/lib/ir/ui/IrCommandProvider.js.map +1 -1
  92. package/lib/ir/ui/IrCommandTooltip.d.ts +7 -0
  93. package/lib/ir/ui/IrCommandTooltip.d.ts.map +1 -1
  94. package/lib/ir/ui/IrCommandTooltip.js +3 -2
  95. package/lib/ir/ui/IrCommandTooltip.js.map +1 -1
  96. package/lib/ir/ui/IrDocumentationDialog.d.ts +8 -1
  97. package/lib/ir/ui/IrDocumentationDialog.d.ts.map +1 -1
  98. package/lib/ir/ui/IrDocumentationDialog.js +30 -7
  99. package/lib/ir/ui/IrDocumentationDialog.js.map +1 -1
  100. package/lib/ir/ui/IrEditor.d.ts +3 -1
  101. package/lib/ir/ui/IrEditor.d.ts.map +1 -1
  102. package/lib/ir/ui/IrEditor.js +3 -20
  103. package/lib/ir/ui/IrEditor.js.map +1 -1
  104. package/lib/ir/ui/IrHeader.d.ts.map +1 -1
  105. package/lib/ir/ui/IrHeader.js +5 -1
  106. package/lib/ir/ui/IrHeader.js.map +1 -1
  107. package/lib/ir/ui/IrLogo.d.ts +11 -3
  108. package/lib/ir/ui/IrLogo.d.ts.map +1 -1
  109. package/lib/ir/ui/IrLogo.js +14 -5
  110. package/lib/ir/ui/IrLogo.js.map +1 -1
  111. package/lib/ir/ui/IrShell.d.ts.map +1 -1
  112. package/lib/ir/ui/IrShell.js +23 -56
  113. package/lib/ir/ui/IrShell.js.map +1 -1
  114. package/lib/ir/ui/IrStatusBar.d.ts +3 -2
  115. package/lib/ir/ui/IrStatusBar.d.ts.map +1 -1
  116. package/lib/ir/ui/IrStatusBar.js +14 -9
  117. package/lib/ir/ui/IrStatusBar.js.map +1 -1
  118. package/lib/ir/ui/IrToolbar.d.ts +3 -0
  119. package/lib/ir/ui/IrToolbar.d.ts.map +1 -1
  120. package/lib/ir/ui/IrToolbar.js +10 -3
  121. package/lib/ir/ui/IrToolbar.js.map +1 -1
  122. package/lib/ir/ui/IrTraces.d.ts +4 -1
  123. package/lib/ir/ui/IrTraces.d.ts.map +1 -1
  124. package/lib/ir/ui/IrTraces.js +2 -0
  125. package/lib/ir/ui/IrTraces.js.map +1 -1
  126. package/lib/ir/ui/RamanEditor.d.ts +46 -0
  127. package/lib/ir/ui/RamanEditor.d.ts.map +1 -0
  128. package/lib/ir/ui/RamanEditor.js +17 -0
  129. package/lib/ir/ui/RamanEditor.js.map +1 -0
  130. package/lib/ir/ui/SpectraPanel.d.ts.map +1 -1
  131. package/lib/ir/ui/SpectraPanel.js +2 -2
  132. package/lib/ir/ui/SpectraPanel.js.map +1 -1
  133. package/lib/ir/ui/SpectrumRow.d.ts +7 -0
  134. package/lib/ir/ui/SpectrumRow.d.ts.map +1 -1
  135. package/lib/ir/ui/SpectrumRow.js +4 -5
  136. package/lib/ir/ui/SpectrumRow.js.map +1 -1
  137. package/lib/ir/ui/VibrationalEditor.d.ts +48 -0
  138. package/lib/ir/ui/VibrationalEditor.d.ts.map +1 -0
  139. package/lib/ir/ui/VibrationalEditor.js +31 -0
  140. package/lib/ir/ui/VibrationalEditor.js.map +1 -0
  141. package/lib/ir/ui/bandsTsv.d.ts +19 -0
  142. package/lib/ir/ui/bandsTsv.d.ts.map +1 -0
  143. package/lib/ir/ui/bandsTsv.js +38 -0
  144. package/lib/ir/ui/bandsTsv.js.map +1 -0
  145. package/lib/ir/ui/irChartProps.d.ts +14 -4
  146. package/lib/ir/ui/irChartProps.d.ts.map +1 -1
  147. package/lib/ir/ui/irChartProps.js +3 -2
  148. package/lib/ir/ui/irChartProps.js.map +1 -1
  149. package/lib/ir/ui/irCommandContext.d.ts +8 -0
  150. package/lib/ir/ui/irCommandContext.d.ts.map +1 -1
  151. package/lib/ir/ui/irCommandContext.js +14 -3
  152. package/lib/ir/ui/irCommandContext.js.map +1 -1
  153. package/lib/ir/ui/useIrEditor.d.ts +3 -3
  154. package/lib/ir/ui/useIrEditor.d.ts.map +1 -1
  155. package/lib/ir/ui/useIrEditor.js +13 -4
  156. package/lib/ir/ui/useIrEditor.js.map +1 -1
  157. package/lib/ir/ui/useIrHandle.d.ts +14 -0
  158. package/lib/ir/ui/useIrHandle.d.ts.map +1 -0
  159. package/lib/ir/ui/useIrHandle.js +39 -0
  160. package/lib/ir/ui/useIrHandle.js.map +1 -0
  161. package/lib/ir/ui/useIrText.d.ts +40 -0
  162. package/lib/ir/ui/useIrText.d.ts.map +1 -0
  163. package/lib/ir/ui/useIrText.js +118 -0
  164. package/lib/ir/ui/useIrText.js.map +1 -0
  165. package/lib/ir/ui/useIrZoom.d.ts +7 -0
  166. package/lib/ir/ui/useIrZoom.d.ts.map +1 -1
  167. package/lib/ir/ui/useIrZoom.js +5 -4
  168. package/lib/ir/ui/useIrZoom.js.map +1 -1
  169. package/lib/ir/ui/useValueAxisZoom.d.ts +20 -0
  170. package/lib/ir/ui/useValueAxisZoom.d.ts.map +1 -0
  171. package/lib/ir/ui/useValueAxisZoom.js +54 -0
  172. package/lib/ir/ui/useValueAxisZoom.js.map +1 -0
  173. package/lib/ir.d.ts +3 -0
  174. package/lib/ir.d.ts.map +1 -1
  175. package/lib/ir.js +3 -0
  176. package/lib/ir.js.map +1 -1
  177. package/lib/locales/de.json +20 -1
  178. package/lib/locales/en.json +20 -1
  179. package/lib/locales/es.json +20 -1
  180. package/lib/locales/fr.json +20 -1
  181. package/lib/locales/it.json +20 -1
  182. package/lib/raman.d.ts +16 -0
  183. package/lib/raman.d.ts.map +1 -0
  184. package/lib/raman.js +12 -0
  185. package/lib/raman.js.map +1 -0
  186. package/package.json +2 -1
  187. package/src/ir/core/analysisSpectra.ts +26 -10
  188. package/src/ir/core/bandLabelPlan.ts +21 -10
  189. package/src/ir/core/index.ts +25 -5
  190. package/src/ir/core/irBand.ts +52 -14
  191. package/src/ir/core/irCommands.ts +15 -1
  192. package/src/ir/core/irDomain.ts +9 -7
  193. package/src/ir/core/irFormat.ts +39 -6
  194. package/src/ir/core/irMode.ts +42 -10
  195. package/src/ir/core/irReducer.ts +1 -49
  196. package/src/ir/core/irSpectrum.ts +59 -30
  197. package/src/ir/core/irState.ts +67 -2
  198. package/src/ir/core/irTechnique.ts +115 -0
  199. package/src/ir/core/irTechniqueCommands.ts +82 -0
  200. package/src/ir/core/pickBands.ts +63 -28
  201. package/src/ir/core/ramanBands.ts +105 -0
  202. package/src/ir/core/spectrumJcamp.ts +65 -16
  203. package/src/ir/core/toIrSpectrum.ts +20 -2
  204. package/src/ir/ui/BandRow.tsx +107 -0
  205. package/src/ir/ui/BandsPanel.tsx +32 -125
  206. package/src/ir/ui/IrAboutDialog.tsx +69 -28
  207. package/src/ir/ui/IrCanvas.tsx +2 -1
  208. package/src/ir/ui/IrChart.tsx +20 -24
  209. package/src/ir/ui/IrCommandProvider.tsx +10 -3
  210. package/src/ir/ui/IrCommandTooltip.tsx +11 -2
  211. package/src/ir/ui/IrDocumentationDialog.tsx +99 -55
  212. package/src/ir/ui/IrEditor.tsx +5 -36
  213. package/src/ir/ui/IrHeader.tsx +10 -3
  214. package/src/ir/ui/IrLogo.tsx +28 -9
  215. package/src/ir/ui/IrShell.tsx +27 -75
  216. package/src/ir/ui/IrStatusBar.tsx +17 -12
  217. package/src/ir/ui/IrToolbar.tsx +26 -15
  218. package/src/ir/ui/IrTraces.tsx +5 -1
  219. package/src/ir/ui/RamanEditor.tsx +53 -0
  220. package/src/ir/ui/SpectraPanel.tsx +2 -1
  221. package/src/ir/ui/SpectrumRow.tsx +14 -5
  222. package/src/ir/ui/VibrationalEditor.tsx +94 -0
  223. package/src/ir/ui/bandsTsv.ts +46 -0
  224. package/src/ir/ui/irChartProps.ts +14 -4
  225. package/src/ir/ui/irCommandContext.tsx +18 -3
  226. package/src/ir/ui/useIrEditor.ts +20 -8
  227. package/src/ir/ui/useIrHandle.ts +51 -0
  228. package/src/ir/ui/useIrText.ts +170 -0
  229. package/src/ir/ui/useIrZoom.ts +12 -3
  230. package/src/ir/ui/useValueAxisZoom.ts +67 -0
  231. package/src/ir.ts +3 -0
  232. package/src/locales/de.json +20 -1
  233. package/src/locales/en.json +20 -1
  234. package/src/locales/es.json +20 -1
  235. package/src/locales/fr.json +20 -1
  236. package/src/locales/it.json +20 -1
  237. package/src/raman.ts +19 -0
@@ -14,14 +14,31 @@ import { xySortX } from 'ml-spectra-processing';
14
14
 
15
15
  import type { IrMeta, IrMode, IrOrigin } from './irSpectrum.ts';
16
16
 
17
- /** One spectrum as a reader produced it, before it is named and coloured. */
17
+ /**
18
+ * One spectrum as a reader produced it, before it is named and coloured.
19
+ *
20
+ * The readers of this package produce infrared spectra, which carry absorbance
21
+ * and transmittance; a host reading a Raman file builds one carrying its
22
+ * intensity instead.
23
+ */
18
24
  export interface LoadedIrSpectrum {
19
- /** The wavenumbers, ascending. */
25
+ /** The wavenumbers — or, for a Raman spectrum, the Raman shifts — ascending. */
20
26
  wavenumber: Float64Array;
21
- /** Absorbance at each. */
22
- absorbance: Float64Array;
23
- /** Percent transmittance at each. */
24
- transmittance: Float64Array;
27
+ /**
28
+ * Absorbance at each, for an infrared spectrum.
29
+ * @default undefined
30
+ */
31
+ absorbance?: Float64Array;
32
+ /**
33
+ * Percent transmittance at each, for an infrared spectrum.
34
+ * @default undefined
35
+ */
36
+ transmittance?: Float64Array;
37
+ /**
38
+ * Intensity at each, for a Raman spectrum.
39
+ * @default undefined
40
+ */
41
+ intensity?: Float64Array;
25
42
  /** What the file said about the acquisition, `null` when it said nothing. */
26
43
  meta: IrMeta | null;
27
44
  /** Where it came from. */
@@ -96,10 +113,9 @@ export function analysisSpectra(
96
113
  return spectra;
97
114
  }
98
115
 
99
- /** The three arrays of one spectrum, in ascending wavenumber. */
100
- type SortedTraces = Pick<
101
- LoadedIrSpectrum,
102
- 'wavenumber' | 'absorbance' | 'transmittance'
116
+ /** The three arrays of one infrared spectrum, in ascending wavenumber. */
117
+ type SortedTraces = Required<
118
+ Pick<LoadedIrSpectrum, 'wavenumber' | 'absorbance' | 'transmittance'>
103
119
  >;
104
120
 
105
121
  /**
@@ -18,6 +18,7 @@ import { labelStackBaselines } from '../../chart/core/labelStack.ts';
18
18
  import type { AssignedBand } from './assignBands.ts';
19
19
  import { bandLabel } from './assignBands.ts';
20
20
  import type { IrBand } from './irBand.ts';
21
+ import { bandHeight, bandValue } from './irBand.ts';
21
22
  import { formatWavenumber } from './irFormat.ts';
22
23
  import { bandsPointDown } from './irMode.ts';
23
24
  import type { IrMode } from './irSpectrum.ts';
@@ -99,12 +100,15 @@ export function planBandLabels(options: BandLabelPlanOptions): BandLabelPlan[] {
99
100
  limit = 12,
100
101
  } = options;
101
102
 
102
- const inside: AssignedBand[] = [];
103
+ const inside: NamedBand[] = [];
103
104
  for (const entry of assigned) {
105
+ // A band with no value on the axis being read has no tip to hang from.
106
+ const value = bandValue(entry.band, mode);
107
+ if (value === undefined) continue;
104
108
  const pixelX = chartPixel(xScale, entry.band.wavenumber);
105
109
  if (!Number.isFinite(pixelX)) continue;
106
110
  if (pixelX < plot.left || pixelX > plot.right) continue;
107
- inside.push(entry);
111
+ inside.push({ entry, value });
108
112
  }
109
113
 
110
114
  const named =
@@ -112,18 +116,20 @@ export function planBandLabels(options: BandLabelPlanOptions): BandLabelPlan[] {
112
116
  ? inside
113
117
  : inside
114
118
  .toSorted(
115
- (first, second) => second.band.absorbance - first.band.absorbance,
119
+ (first, second) =>
120
+ bandHeight(second.entry.band) - bandHeight(first.entry.band),
116
121
  )
117
122
  .slice(0, limit)
118
123
  .toSorted(
119
- (first, second) => first.band.wavenumber - second.band.wavenumber,
124
+ (first, second) =>
125
+ first.entry.band.wavenumber - second.entry.band.wavenumber,
120
126
  );
121
127
 
122
128
  const centres = new Float64Array(named.length);
123
129
  for (let index = 0; index < named.length; index++) {
124
130
  centres[index] = chartPixel(
125
131
  xScale,
126
- (named[index] as AssignedBand).band.wavenumber,
132
+ (named[index] as NamedBand).entry.band.wavenumber,
127
133
  );
128
134
  }
129
135
  const placed = layoutAnnotationBoxes(
@@ -136,12 +142,9 @@ export function planBandLabels(options: BandLabelPlanOptions): BandLabelPlan[] {
136
142
  const down = bandsPointDown(mode);
137
143
  const plans: BandLabelPlan[] = new Array<BandLabelPlan>(named.length);
138
144
  for (let index = 0; index < named.length; index++) {
139
- const entry = named[index] as AssignedBand;
145
+ const { entry, value } = named[index] as NamedBand;
140
146
  const lines = labelLines(entry, showAssignments);
141
- const tipY = chartPixel(
142
- yScale,
143
- mode === 'absorbance' ? entry.band.absorbance : entry.band.transmittance,
144
- );
147
+ const tipY = chartPixel(yScale, value);
145
148
  plans[index] = {
146
149
  band: entry.band,
147
150
  tipX: centres[index] as number,
@@ -161,6 +164,14 @@ export function planBandLabels(options: BandLabelPlanOptions): BandLabelPlan[] {
161
164
  return plans;
162
165
  }
163
166
 
167
+ /** A band inside the window, with its value on the axis being read. */
168
+ interface NamedBand {
169
+ /** The band and whatever the table said about it. */
170
+ entry: AssignedBand;
171
+ /** Where its tip is, on the value axis. */
172
+ value: number;
173
+ }
174
+
164
175
  /**
165
176
  * How wide a label is taken to be while it is being spaced out.
166
177
  *
@@ -10,7 +10,8 @@
10
10
  * transmittance and a band points the other way in each, and that a wavenumber
11
11
  * maps to a range of vibrations rather than to one. Nothing here knows what a
12
12
  * glycan is, and nothing should: an infrared spectrum answers for whatever was
13
- * put in the beam.
13
+ * put in the beam. A Raman spectrum is the one other technique held, and what
14
+ * differs for it is in `IR_TECHNIQUE_RULES`.
14
15
  */
15
16
 
16
17
  export type { LoadedIrSpectrum } from './analysisSpectra.ts';
@@ -26,16 +27,22 @@ export { downloadIrSpectra, downloadIrSpectrum } from './downloadSpectra.ts';
26
27
  export type { IrJcampReadOptions } from './fromJcamp.ts';
27
28
  export type { IrSpcReadOptions } from './fromSpc.ts';
28
29
  export type { IrBand, IrBandStrength } from './irBand.ts';
29
- export { sameIrBand } from './irBand.ts';
30
+ export { bandHeight, bandValue, sameIrBand } from './irBand.ts';
30
31
  export type {
31
32
  IrCommand,
32
33
  IrCommandHandlers,
33
34
  IrCommandId,
35
+ IrCommandTable,
34
36
  IrGesture,
35
37
  IrShortcut,
36
38
  RunIrCommand,
37
39
  } from './irCommands.ts';
38
- export { irCommandLabel, irCommands } from './irCommands.ts';
40
+ export {
41
+ irCommandEntryLabel,
42
+ irCommandLabel,
43
+ irCommands,
44
+ } from './irCommands.ts';
45
+ export { irCommandOffered, irCommandsFor } from './irTechniqueCommands.ts';
39
46
  export {
40
47
  LABEL_HEADROOM,
41
48
  X_DOMAIN_PADDING,
@@ -46,9 +53,11 @@ export {
46
53
  export type { IrEditorHandle, ZoomToBandOptions } from './irEditorApi.ts';
47
54
  export {
48
55
  ABSORBANCE_DECIMALS,
56
+ INTENSITY_DIGITS,
49
57
  TRANSMITTANCE_DECIMALS,
50
58
  formatIrValue,
51
59
  formatWavenumber,
60
+ formatWavenumberRange,
52
61
  shortModeLabel,
53
62
  } from './irFormat.ts';
54
63
  export {
@@ -61,8 +70,8 @@ export {
61
70
  yAxisTitle,
62
71
  zoomGestures,
63
72
  } from './irMode.ts';
64
- export type { CreateIrStateOptions, IrAction } from './irReducer.ts';
65
- export { createIrState, irReducer } from './irReducer.ts';
73
+ export type { IrAction } from './irReducer.ts';
74
+ export { irReducer } from './irReducer.ts';
66
75
  export type {
67
76
  IrDomain,
68
77
  IrFormat,
@@ -75,6 +84,7 @@ export type {
75
84
  } from './irSpectrum.ts';
76
85
  export { traceOf } from './irSpectrum.ts';
77
86
  export type {
87
+ CreateIrStateOptions,
78
88
  IrData,
79
89
  IrSettings,
80
90
  IrState,
@@ -89,9 +99,19 @@ export {
89
99
  DEFAULT_SIDE_PANEL,
90
100
  DEFAULT_SIDE_PANELS,
91
101
  MAXIMUM_LABEL_LIMIT,
102
+ createIrState,
92
103
  isIrWindow,
93
104
  mergeIrSettings,
94
105
  } from './irState.ts';
106
+ export type { IrTechnique, IrTechniqueRules } from './irTechnique.ts';
107
+ export {
108
+ IR_TECHNIQUES,
109
+ IR_TECHNIQUE_RULES,
110
+ defaultIrMode,
111
+ irModeFor,
112
+ irModeTechnique,
113
+ offersIrMode,
114
+ } from './irTechnique.ts';
95
115
  export type {
96
116
  LoadIrSpectraOptions,
97
117
  LoadIrSpectraResult,
@@ -1,21 +1,24 @@
1
1
  /**
2
2
  * One band picked out of a spectrum.
3
3
  *
4
- * A band is the infrared analogue of a mass spectrum's peak: the thing a chemist
5
- * points at, names, and reads a functional group off. It carries both value axes
6
- * for the same reason the spectrum does — the band a reader sees at 30 percent
7
- * transmittance and the one a table calls strongly absorbing are one band, and
8
- * making the caller convert to find out would be the conversion this package
9
- * does not do.
4
+ * A band is the vibrational analogue of a mass spectrum's peak: the thing a
5
+ * chemist points at, names, and reads a functional group off. An infrared band
6
+ * carries both value axes for the same reason its spectrum does — the band a
7
+ * reader sees at 30 percent transmittance and the one a table calls strongly
8
+ * absorbing are one band, and making the caller convert to find out would be the
9
+ * conversion this package does not do. A Raman band carries its intensity.
10
10
  */
11
11
 
12
+ import type { IrMode } from './irSpectrum.ts';
13
+
12
14
  /**
13
15
  * How strong a band is, in the letters a correlation table uses.
14
16
  *
15
- * `w` weak, `m` medium, `S` strong — `ir-spectrum` decides which from where the
16
- * band sits between the spectrum's own extremes, so the letters are relative to
17
- * the spectrum in hand rather than to an absolute absorbance. That is what a
18
- * printed table means by them too.
17
+ * `w` weak, `m` medium, `S` strong — decided from where the band sits between
18
+ * the spectrum's own extremes, so the letters are relative to the spectrum in
19
+ * hand rather than to an absolute value. That is what a printed table means by
20
+ * them too. `ir-spectrum` decides it for an infrared band from its
21
+ * transmittance, and the same rule is applied to a Raman band's intensity.
19
22
  */
20
23
  export type IrBandStrength = 'w' | 'm' | 'S';
21
24
 
@@ -25,10 +28,21 @@ export interface IrBand {
25
28
  spectrumId: string;
26
29
  /** Where it sits, in cm⁻¹. */
27
30
  wavenumber: number;
28
- /** Its absorbance, which is what it was picked as a maximum of. */
29
- absorbance: number;
30
- /** Its percent transmittance, the same band read the other way up. */
31
- transmittance: number;
31
+ /**
32
+ * Its absorbance, which an infrared band is picked as a maximum of.
33
+ * @default undefined
34
+ */
35
+ absorbance?: number;
36
+ /**
37
+ * Its percent transmittance, the same infrared band read the other way up.
38
+ * @default undefined
39
+ */
40
+ transmittance?: number;
41
+ /**
42
+ * Its intensity, which a Raman band is picked as a maximum of.
43
+ * @default undefined
44
+ */
45
+ intensity?: number;
32
46
  /** How strong it is, relative to the spectrum it came from. */
33
47
  strength: IrBandStrength;
34
48
  }
@@ -55,3 +69,27 @@ export function sameIrBand(
55
69
  current.wavenumber === next.wavenumber
56
70
  );
57
71
  }
72
+
73
+ /**
74
+ * A band's value on the axis being read.
75
+ * @param band - The band.
76
+ * @param mode - Which value axis is being read.
77
+ * @returns The value, or `undefined` when its spectrum carries no such axis.
78
+ */
79
+ export function bandValue(band: IrBand, mode: IrMode): number | undefined {
80
+ return band[mode];
81
+ }
82
+
83
+ /**
84
+ * How tall a band stands, which is what a cut to the strongest bands ranks by.
85
+ *
86
+ * The value it was picked as a maximum of: its absorbance for an infrared band,
87
+ * its intensity for a Raman one. Never its transmittance, in which a stronger
88
+ * band is a smaller number.
89
+ * @param band - The band.
90
+ * @returns The height, or negative infinity for a band carrying neither, so it
91
+ * ranks last.
92
+ */
93
+ export function bandHeight(band: IrBand): number {
94
+ return band.absorbance ?? band.intensity ?? Number.NEGATIVE_INFINITY;
95
+ }
@@ -10,6 +10,9 @@
10
10
  * this one, because a module that exports a component and a constant together
11
11
  * cannot be hot reloaded — `react-refresh/only-export-components` is the rule
12
12
  * that says so, and the split is the fix rather than the exception.
13
+ *
14
+ * This is the infrared table; `irTechniqueCommands.ts` derives the Raman one
15
+ * from it.
13
16
  */
14
17
 
15
18
  /** The key a command answers to. */
@@ -134,6 +137,9 @@ export const irCommands = {
134
137
  /** Identity of one of the commands the viewer offers. */
135
138
  export type IrCommandId = keyof typeof irCommands;
136
139
 
140
+ /** The commands a viewer offers, in the order they are reached for. */
141
+ export type IrCommandTable = Readonly<Partial<Record<IrCommandId, IrCommand>>>;
142
+
137
143
  /** What to do when each command is run, as the shell wires them. */
138
144
  export type IrCommandHandlers = Partial<
139
145
  Record<IrCommandId, (() => void) | undefined>
@@ -148,7 +154,15 @@ export type RunIrCommand = (id: IrCommandId) => void;
148
154
  * @returns The title, and the key in parentheses when it has one.
149
155
  */
150
156
  export function irCommandLabel(id: IrCommandId): string {
151
- const entry = irCommands[id] as IrCommand;
157
+ return irCommandEntryLabel(irCommands[id]);
158
+ }
159
+
160
+ /**
161
+ * A command's title, with its key in parentheses when it has one.
162
+ * @param entry - The command, as a table words it.
163
+ * @returns The label.
164
+ */
165
+ export function irCommandEntryLabel(entry: IrCommand): string {
152
166
  const key = entry.shortcut?.label?.[0] ?? entry.shortcut?.keys[0];
153
167
  return key === undefined ? entry.title : `${entry.title} (${key})`;
154
168
  }
@@ -5,9 +5,10 @@
5
5
  * something usable when it is handed nothing at all — a chart with no data is
6
6
  * still mounted, still measured, and still builds its scales.
7
7
  *
8
- * The x window is in ascending wavenumbers even though the axis is drawn from
9
- * 4000 down to 400: which way round it is read is the chart's business, and
10
- * every piece of arithmetic that moves a window expects its ends in order.
8
+ * The x window is in ascending wavenumbers even though the infrared axis is
9
+ * drawn from 4000 down to 400: which way round it is read is the chart's
10
+ * business, and every piece of arithmetic that moves a window expects its ends
11
+ * in order.
11
12
  */
12
13
 
13
14
  import { xMaxValue, xMinValue } from 'ml-spectra-processing';
@@ -57,9 +58,8 @@ export function fullDomain(
57
58
  const extent =
58
59
  traceExtent(spectra, mode, false) ?? traceExtent(spectra, mode, true);
59
60
  if (extent === null) {
60
- return mode === 'absorbance'
61
- ? { x: [400, 4000], y: [0, 1] }
62
- : { x: [400, 4000], y: [0, 100] };
61
+ // A percentage runs to 100; absorbance and intensity are open at the top.
62
+ return { x: [400, 4000], y: mode === 'transmittance' ? [0, 100] : [0, 1] };
63
63
  }
64
64
 
65
65
  const { xMinimum, xMaximum, yMinimum, yMaximum } = extent;
@@ -135,7 +135,9 @@ function traceExtent(
135
135
  for (const spectrum of spectra) {
136
136
  if (!spectrum.visible) continue;
137
137
  if (!includeExcluded && spectrum.excludeFromDomain) continue;
138
- const { x, y } = traceOf(spectrum, mode);
138
+ const trace = traceOf(spectrum, mode);
139
+ if (trace === undefined) continue;
140
+ const { x, y } = trace;
139
141
  if (x.length === 0 || y.length === 0) continue;
140
142
  // The wavenumber ends are read off the first and last sample rather than
141
143
  // searched for: the array is ascending by construction, so a scan would walk
@@ -6,6 +6,8 @@
6
6
  * row that calls it 1714.6 read as two bands.
7
7
  */
8
8
 
9
+ import { formatTrimmed } from '../../format/core/numbers.ts';
10
+
9
11
  import type { IrMode } from './irSpectrum.ts';
10
12
 
11
13
  /** How many decimals an absorbance is written to. */
@@ -14,6 +16,9 @@ export const ABSORBANCE_DECIMALS = 3;
14
16
  /** How many a percent transmittance is written to. */
15
17
  export const TRANSMITTANCE_DECIMALS = 1;
16
18
 
19
+ /** How many significant digits an intensity is written to. */
20
+ export const INTENSITY_DIGITS = 4;
21
+
17
22
  /**
18
23
  * Write a wavenumber the way a chemist quotes one.
19
24
  *
@@ -35,23 +40,51 @@ export function formatWavenumber(wavenumber: number): string {
35
40
  * Three decimals for an absorbance and one for a percent transmittance, which is
36
41
  * roughly the same precision expressed two ways — an absorbance of 0.001 and a
37
42
  * transmittance of 0.2 percent are both about where a real spectrum's noise
38
- * sits.
43
+ * sits. An intensity is counts from one instrument and a fraction of the
44
+ * strongest band from another, so no count of decimals suits it: it keeps
45
+ * `INTENSITY_DIGITS` significant digits instead.
39
46
  * @param value - The value.
40
47
  * @param mode - Which axis it is in.
41
48
  * @returns The number as a string, without its unit.
42
49
  */
43
50
  export function formatIrValue(value: number, mode: IrMode): string {
44
51
  if (!Number.isFinite(value)) return '';
45
- return value.toFixed(
46
- mode === 'absorbance' ? ABSORBANCE_DECIMALS : TRANSMITTANCE_DECIMALS,
47
- );
52
+ const { decimals } = VALUE_FORMATS[mode];
53
+ return decimals === undefined
54
+ ? formatTrimmed(value, { significant: INTENSITY_DIGITS })
55
+ : value.toFixed(decimals);
48
56
  }
49
57
 
50
58
  /**
51
59
  * What the value axis is called where there is room for one word.
52
60
  * @param mode - Which axis is being drawn.
53
- * @returns `A` or `%T`, as a spectrum is annotated by hand.
61
+ * @returns `A`, `%T` or `I`, as a spectrum is annotated by hand.
54
62
  */
55
63
  export function shortModeLabel(mode: IrMode): string {
56
- return mode === 'absorbance' ? 'A' : '%T';
64
+ return VALUE_FORMATS[mode].symbol;
57
65
  }
66
+
67
+ /**
68
+ * The range a spectrum covers, written in the order its axis is read.
69
+ * @param wavenumbers - Its wavenumbers, ascending.
70
+ * @param reversed - Whether the axis runs from high to low, as infrared's does.
71
+ * @returns `4000–400 cm⁻¹` on a reversed axis, `100–3200 cm⁻¹` on one that is
72
+ * not, and an empty string for a spectrum with no point.
73
+ */
74
+ export function formatWavenumberRange(
75
+ wavenumbers: Float64Array,
76
+ reversed: boolean,
77
+ ): string {
78
+ const lowest = wavenumbers[0];
79
+ const highest = wavenumbers.at(-1);
80
+ if (lowest === undefined || highest === undefined) return '';
81
+ const [first, last] = reversed ? [highest, lowest] : [lowest, highest];
82
+ return `${formatWavenumber(first)}–${formatWavenumber(last)} cm⁻¹`;
83
+ }
84
+
85
+ /** How each value axis is written: its symbol, and its count of decimals. */
86
+ const VALUE_FORMATS: Record<IrMode, { symbol: string; decimals?: number }> = {
87
+ transmittance: { symbol: '%T', decimals: TRANSMITTANCE_DECIMALS },
88
+ absorbance: { symbol: 'A', decimals: ABSORBANCE_DECIMALS },
89
+ intensity: { symbol: 'I' },
90
+ };
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Everything that differs between reading a spectrum as absorbance and reading
3
- * it as percent transmittance.
2
+ * Everything that differs between reading a spectrum as absorbance, as percent
3
+ * transmittance, or — for a Raman spectrum — as intensity.
4
4
  *
5
- * The two are the same measurement drawn upside down, and nearly every
5
+ * The first two are the same measurement drawn upside down, and nearly every
6
6
  * difference between them is one of a handful of facts: what the axis is called,
7
7
  * where its baseline is, whether that baseline is held in the window, which way
8
8
  * a band points, and which zoom gestures are worth answering. Gathering them
9
9
  * here is what keeps the switch from being an `if (mode === …)` in the chart,
10
10
  * the tracker, the band marks, the zoom and the fitting separately, where they
11
- * would eventually disagree.
11
+ * would eventually disagree. Intensity is one more entry of the same facts.
12
12
  *
13
13
  * **No conversion happens here, or anywhere in this package.** `ir-spectrum`
14
14
  * adds both an absorbance and a transmittance variable to every spectrum it
@@ -24,9 +24,14 @@ import type {
24
24
  } from '../../chart/core/chartDomain.ts';
25
25
 
26
26
  import type { IrMode } from './irSpectrum.ts';
27
+ import type { IrTechnique } from './irTechnique.ts';
28
+ import { IR_TECHNIQUE_RULES, irModeTechnique } from './irTechnique.ts';
27
29
 
28
- /** The two ways a spectrum is read, in the order a control offers them. */
29
- export const IR_MODES: readonly IrMode[] = ['transmittance', 'absorbance'];
30
+ /**
31
+ * The two ways an infrared spectrum is read, in the order a control offers
32
+ * them.
33
+ */
34
+ export const IR_MODES: readonly IrMode[] = IR_TECHNIQUE_RULES.infrared.modes;
30
35
 
31
36
  /**
32
37
  * How each mode behaves, every fact in one place.
@@ -115,6 +120,22 @@ export const IR_MODE_RULES = {
115
120
  */
116
121
  wheelZoom: true,
117
122
  },
123
+ intensity: {
124
+ title: 'intensity',
125
+ /** No light scattered is a real floor, as no light absorbed is. */
126
+ baseline: 0,
127
+ /** Not held in a dragged window, for the reason absorbance gives. */
128
+ keepBaseline: false,
129
+ /** A Raman band stands up out of the baseline under it. */
130
+ bandsPointDown: false,
131
+ /**
132
+ * Both drags are worth having: intensity is open at the top and its weak
133
+ * bands lie against zero, as absorbance's do.
134
+ */
135
+ boxZoomOnly: false,
136
+ /** The wheel scales the axis about zero, bringing a weak band up. */
137
+ wheelZoom: true,
138
+ },
118
139
  } as const satisfies Record<
119
140
  IrMode,
120
141
  {
@@ -199,10 +220,21 @@ export function bandsPointDown(mode: IrMode): boolean {
199
220
  }
200
221
 
201
222
  /**
202
- * The other mode, for a control that toggles between the two.
223
+ * The next mode, for a control that toggles between them.
224
+ *
225
+ * It never leaves the technique: an infrared viewer goes back and forth
226
+ * between its two axes, and a Raman one, reading intensity alone, stays where
227
+ * it is.
203
228
  * @param mode - The mode in force.
204
- * @returns The one to switch to.
229
+ * @param technique - The technique whose modes are toggled through. Defaults to
230
+ * the one that reads `mode`.
231
+ * @returns The one to switch to: the technique's first when `mode` is not one
232
+ * of its own.
205
233
  */
206
- export function otherIrMode(mode: IrMode): IrMode {
207
- return mode === 'absorbance' ? 'transmittance' : 'absorbance';
234
+ export function otherIrMode(
235
+ mode: IrMode,
236
+ technique: IrTechnique = irModeTechnique(mode),
237
+ ): IrMode {
238
+ const modes: readonly IrMode[] = IR_TECHNIQUE_RULES[technique].modes;
239
+ return modes[(modes.indexOf(mode) + 1) % modes.length] ?? mode;
208
240
  }
@@ -16,12 +16,7 @@
16
16
  import type { IrSidePanelId } from './irSidePanels.ts';
17
17
  import type { IrDomain, IrSpectrum } from './irSpectrum.ts';
18
18
  import type { IrSettings, IrState, IrTool, PartialIrState } from './irState.ts';
19
- import {
20
- DEFAULT_SETTINGS,
21
- DEFAULT_SIDE_PANELS,
22
- isIrWindow,
23
- mergeIrSettings,
24
- } from './irState.ts';
19
+ import { isIrWindow, mergeIrSettings } from './irState.ts';
25
20
 
26
21
  /** Everything that can be asked of the viewer. */
27
22
  export type IrAction =
@@ -41,49 +36,6 @@ export type IrAction =
41
36
  | { type: 'loadState'; state: PartialIrState }
42
37
  | { type: 'clear' };
43
38
 
44
- /** What a viewer opens on. */
45
- export interface CreateIrStateOptions {
46
- /**
47
- * The spectra to open on.
48
- * @default []
49
- */
50
- spectra?: IrSpectrum[];
51
- /**
52
- * The workspace to open in.
53
- * @default DEFAULT_SETTINGS
54
- */
55
- settings?: Partial<IrSettings>;
56
- /**
57
- * The panels open to begin with.
58
- * @default DEFAULT_SIDE_PANELS
59
- */
60
- panelIds?: IrSidePanelId[];
61
- }
62
-
63
- /**
64
- * The state a viewer opens with.
65
- *
66
- * The panel list is spread rather than stored by reference: a caller's array is
67
- * the caller's to mutate, and a viewer holding it would find its open panels
68
- * changing under it.
69
- * @param options - What it opens on.
70
- * @returns The state.
71
- */
72
- export function createIrState(options: CreateIrStateOptions = {}): IrState {
73
- const { spectra = [], settings, panelIds } = options;
74
- return {
75
- data: { spectra: [...spectra] },
76
- view: {
77
- selectedId: spectra[0]?.id ?? null,
78
- domain: null,
79
- tool: 'read',
80
- openPanelIds: [...(panelIds ?? DEFAULT_SIDE_PANELS)],
81
- error: null,
82
- },
83
- settings: mergeIrSettings(DEFAULT_SETTINGS, settings),
84
- };
85
- }
86
-
87
39
  /**
88
40
  * Fold one action into the state.
89
41
  * @param state - The state in force.