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
@@ -1,24 +1,37 @@
1
+ import type { IrTechnique } from '../core/irTechnique.ts';
2
+
3
+ import { useIrText } from './useIrText.ts';
4
+
1
5
  export interface IrLogoProps {
2
6
  /**
3
7
  * How big to draw it, in pixels.
4
8
  * @default 16
5
9
  */
6
10
  size?: number;
11
+ /**
12
+ * What the spectra were measured with, which decides which way the bands
13
+ * point.
14
+ * @default 'infrared'
15
+ */
16
+ technique?: IrTechnique;
7
17
  }
8
18
 
9
19
  /**
10
- * The viewer's mark: three bands hanging from a baseline.
20
+ * The viewer's mark: three bands meeting a baseline.
11
21
  *
12
22
  * Drawn from the domain's own primitives rather than being a picture of a chart —
13
23
  * a transmittance baseline near the top with a strong band, a medium one and a
14
24
  * weak one dropping out of it, which is what an infrared spectrum looks like at a
15
- * glance and what nothing else looks like. Three elements, because that is what
16
- * survives 16 pixels.
25
+ * glance and what nothing else looks like. A Raman spectrum's bands stand up from
26
+ * a baseline near the foot instead, so its mark is the same three bands rising.
27
+ * Three elements, because that is what survives 16 pixels.
17
28
  * @param props - Component props.
18
29
  * @returns The mark, as SVG.
19
30
  */
20
31
  export function IrLogo(props: IrLogoProps) {
21
- const { size = 16 } = props;
32
+ const { size = 16, technique = 'infrared' } = props;
33
+ const { mark } = useIrText(technique);
34
+ const { baseline, bands } = MARKS[technique];
22
35
 
23
36
  return (
24
37
  <svg
@@ -26,19 +39,19 @@ export function IrLogo(props: IrLogoProps) {
26
39
  height={size}
27
40
  viewBox="0 0 16 16"
28
41
  role="img"
29
- aria-label="Infrared viewer"
42
+ aria-label={mark}
30
43
  >
31
- {/* The baseline, high up, where 100 % transmittance runs. */}
44
+ {/* The baseline: high up where 100 % transmittance runs, low for Raman. */}
32
45
  <path
33
- d="M1 3H15"
46
+ d={baseline}
34
47
  fill="none"
35
48
  stroke="#94a3b8"
36
49
  strokeWidth={1}
37
50
  strokeLinecap="round"
38
51
  />
39
- {/* Three bands dropping out of it, strong, medium, weak. */}
52
+ {/* Three bands meeting it, strong, medium, weak. */}
40
53
  <path
41
- d="M3 3V13M8 3V8.5M12.5 3V6"
54
+ d={bands}
42
55
  fill="none"
43
56
  stroke="#0072b2"
44
57
  strokeWidth={2}
@@ -47,3 +60,9 @@ export function IrLogo(props: IrLogoProps) {
47
60
  </svg>
48
61
  );
49
62
  }
63
+
64
+ /** The two strokes of each technique's mark. */
65
+ const MARKS: Record<IrTechnique, { baseline: string; bands: string }> = {
66
+ infrared: { baseline: 'M1 3H15', bands: 'M3 3V13M8 3V8.5M12.5 3V6' },
67
+ raman: { baseline: 'M1 13H15', bands: 'M3 13V3M8 13V7.5M12.5 13V10' },
68
+ };
@@ -1,11 +1,5 @@
1
1
  import type { Ref } from 'react';
2
- import {
3
- useCallback,
4
- useId,
5
- useImperativeHandle,
6
- useMemo,
7
- useState,
8
- } from 'react';
2
+ import { useId, useMemo, useState } from 'react';
9
3
  import { SplitPane, useFullscreen } from 'react-science/ui';
10
4
 
11
5
  import { sanitizeFileName } from '../../download/core/sanitizeFileName.ts';
@@ -13,12 +7,12 @@ import { useFigureDownload } from '../../download/ui/useFigureDownload.tsx';
13
7
  import { PanelRail } from '../../panel/ui/PanelRail.tsx';
14
8
  import type { IrBand } from '../core/irBand.ts';
15
9
  import type { IrCommandHandlers } from '../core/irCommands.ts';
16
- import { fullDomain } from '../core/irDomain.ts';
17
- import type { IrEditorHandle, ZoomToBandOptions } from '../core/irEditorApi.ts';
18
- import { otherIrMode, yAxisRules } from '../core/irMode.ts';
10
+ import type { IrEditorHandle } from '../core/irEditorApi.ts';
11
+ import { otherIrMode } from '../core/irMode.ts';
19
12
  import { irSidePanels } from '../core/irSidePanels.ts';
20
- import type { IrDomain, IrSpectrum } from '../core/irSpectrum.ts';
13
+ import type { IrSpectrum } from '../core/irSpectrum.ts';
21
14
  import { DEFAULT_SIDE_PANEL } from '../core/irState.ts';
15
+ import { irCommandOffered } from '../core/irTechniqueCommands.ts';
22
16
 
23
17
  import { IrAboutDialog } from './IrAboutDialog.tsx';
24
18
  import { IrCanvas } from './IrCanvas.tsx';
@@ -28,7 +22,9 @@ import { IrHeader } from './IrHeader.tsx';
28
22
  import { IrSidePanel } from './IrSidePanel.tsx';
29
23
  import { IrToolbar } from './IrToolbar.tsx';
30
24
  import { useIrActions, useIrEditorState } from './irStateContext.ts';
25
+ import { useIrHandle } from './useIrHandle.ts';
31
26
  import { useIrShortcuts } from './useIrShortcuts.ts';
27
+ import { useValueAxisZoom } from './useValueAxisZoom.ts';
32
28
 
33
29
  export interface IrShellProps {
34
30
  /**
@@ -58,8 +54,8 @@ export function IrShell(props: IrShellProps) {
58
54
  const { state, visibleSpectra, selectedSpectrum } = useIrEditorState();
59
55
  const actions = useIrActions();
60
56
  const { toggle: toggleFullScreen } = useFullscreen();
61
- const { openPanelIds, tool, domain } = state.view;
62
- const { mode, pickBands, showAssignments } = state.settings;
57
+ const { openPanelIds, tool } = state.view;
58
+ const { mode, pickBands, showAssignments, technique } = state.settings;
63
59
 
64
60
  const [isAboutOpen, setIsAboutOpen] = useState(false);
65
61
  const [isDocumentationOpen, setIsDocumentationOpen] = useState(false);
@@ -79,27 +75,7 @@ export function IrShell(props: IrShellProps) {
79
75
  // times a second.
80
76
  const [highlight, setHighlight] = useState<IrBand | null>(null);
81
77
 
82
- /**
83
- * ChartScale the value axis by a factor, about the baseline the mode hangs from.
84
- * @param factor - Above one to zoom out.
85
- */
86
- const scaleValueAxis = useCallback(
87
- (factor: number) => {
88
- const shown = domain ?? fullDomain(visibleSpectra, mode);
89
- // Asked of the mode rather than written out again here: the baseline a
90
- // value axis is scaled about is `irMode`'s to know, and a second copy of
91
- // it is a second thing to get wrong.
92
- const { baseline = 0 } = yAxisRules(mode);
93
- actions.setDomain({
94
- x: shown.x,
95
- y: [
96
- baseline + (shown.y[0] - baseline) * factor,
97
- baseline + (shown.y[1] - baseline) * factor,
98
- ],
99
- });
100
- },
101
- [actions, domain, visibleSpectra, mode],
102
- );
78
+ const { zoomIn, zoomOut } = useValueAxisZoom();
103
79
 
104
80
  const commands = useMemo<IrCommandHandlers>(
105
81
  () => ({
@@ -108,13 +84,18 @@ export function IrShell(props: IrShellProps) {
108
84
  fullScreen: toggleFullScreen,
109
85
  readTool: () => actions.setTool('read'),
110
86
  boxTool: () => actions.setTool(tool === 'box' ? 'read' : 'box'),
111
- zoomIn: () => scaleValueAxis(1 / ZOOM_STEP),
112
- zoomOut: () => scaleValueAxis(ZOOM_STEP),
87
+ zoomIn,
88
+ zoomOut,
113
89
  resetZoom: () => actions.resetDomain(),
114
- toggleMode: () => actions.setSettings({ mode: otherIrMode(mode) }),
90
+ // A command the technique does not offer has no handler, so its key
91
+ // stays with the page.
92
+ toggleMode: irCommandOffered(technique, 'toggleMode')
93
+ ? () => actions.setSettings({ mode: otherIrMode(mode, technique) })
94
+ : undefined,
115
95
  togglePicking: () => actions.setSettings({ pickBands: !pickBands }),
116
- toggleAssignments: () =>
117
- actions.setSettings({ showAssignments: !showAssignments }),
96
+ toggleAssignments: irCommandOffered(technique, 'toggleAssignments')
97
+ ? () => actions.setSettings({ showAssignments: !showAssignments })
98
+ : undefined,
118
99
  exportImage: () => setFigureOpen(!isFigureOpen),
119
100
  clear: () => actions.clear(),
120
101
  }),
@@ -122,9 +103,11 @@ export function IrShell(props: IrShellProps) {
122
103
  actions,
123
104
  tool,
124
105
  mode,
106
+ technique,
125
107
  pickBands,
126
108
  showAssignments,
127
- scaleValueAxis,
109
+ zoomIn,
110
+ zoomOut,
128
111
  toggleFullScreen,
129
112
  isFigureOpen,
130
113
  setFigureOpen,
@@ -137,28 +120,7 @@ export function IrShell(props: IrShellProps) {
137
120
  // again.
138
121
  const [root, setRoot] = useState<HTMLElement | null>(null);
139
122
  useIrShortcuts(root, commands);
140
-
141
- useImperativeHandle(
142
- handle,
143
- (): IrEditorHandle => ({
144
- getSpectra: () => state.data.spectra,
145
- setSpectra: (spectra) => actions.setSpectra(spectra),
146
- addSpectra: (spectra) => actions.addSpectra(spectra),
147
- clear: () => actions.clear(),
148
- setDomain: (next) => actions.setDomain(next),
149
- resetDomain: () => actions.resetDomain(),
150
- zoomToWavenumber: (wavenumber, options: ZoomToBandOptions = {}) => {
151
- const { width = DEFAULT_FRAME_WIDTH } = options;
152
- const shown: IrDomain = domain ?? fullDomain(visibleSpectra, mode);
153
- actions.setDomain({
154
- x: [wavenumber - width / 2, wavenumber + width / 2],
155
- y: shown.y,
156
- });
157
- },
158
- selectSpectrum: (id) => actions.selectSpectrum(id),
159
- }),
160
- [state.data.spectra, actions, domain, visibleSpectra, mode],
161
- );
123
+ useIrHandle(handle);
162
124
 
163
125
  /**
164
126
  * Open or close the side, as the splitter is dragged shut and back open.
@@ -175,7 +137,7 @@ export function IrShell(props: IrShellProps) {
175
137
  }
176
138
 
177
139
  return (
178
- <IrCommandProvider handlers={commands}>
140
+ <IrCommandProvider handlers={commands} technique={technique}>
179
141
  {/* `tabIndex` so a click anywhere in the viewer makes this the target the
180
142
  shortcuts are heard on: keys are delivered to whatever holds the caret,
181
143
  and an SVG never does. */}
@@ -221,27 +183,17 @@ export function IrShell(props: IrShellProps) {
221
183
  <IrAboutDialog
222
184
  isOpen={isAboutOpen}
223
185
  onClose={() => setIsAboutOpen(false)}
186
+ technique={technique}
224
187
  />
225
188
  <IrDocumentationDialog
226
189
  isOpen={isDocumentationOpen}
227
190
  onClose={() => setIsDocumentationOpen(false)}
191
+ technique={technique}
228
192
  />
229
193
  </IrCommandProvider>
230
194
  );
231
195
  }
232
196
 
233
- /**
234
- * What one press of zoom in or out is worth.
235
- *
236
- * A fifth, which is about what one notch of a mouse wheel does — so the button
237
- * and the wheel feel like the same gesture at two speeds rather than two
238
- * different ones.
239
- */
240
- const ZOOM_STEP = 1.2;
241
-
242
- /** How wide a window a host gets when it asks to frame a band without saying. */
243
- const DEFAULT_FRAME_WIDTH = 200;
244
-
245
197
  /**
246
198
  * What a picture of the chart is called.
247
199
  *
@@ -8,18 +8,21 @@ import {
8
8
  statusSpacerStyle as spacerStyle,
9
9
  } from '../../panel/core/statusBar.ts';
10
10
  import { countAssigned } from '../core/assignBands.ts';
11
- import { formatWavenumber } from '../core/irFormat.ts';
12
- import { boxZoomOnly, yAxisTitle } from '../core/irMode.ts';
11
+ import { formatWavenumberRange } from '../core/irFormat.ts';
12
+ import { boxZoomOnly } from '../core/irMode.ts';
13
+ import { IR_TECHNIQUE_RULES } from '../core/irTechnique.ts';
13
14
 
14
15
  import { useIrEditorState } from './irStateContext.ts';
16
+ import { useIrText } from './useIrText.ts';
15
17
 
16
18
  /**
17
19
  * The one line under the chart: what is drawn, what was found, what went wrong.
18
20
  *
19
21
  * Everything in it is derived, so it is the cheapest place in the viewer to
20
22
  * answer "did that work" — how many spectra are drawn of those open, how many
21
- * bands the picking found and how many the table had something to say about, and
22
- * the range the selected spectrum covers.
23
+ * bands the picking found and how many the table had something to say about
24
+ * (where the table covers the technique), and the range the selected spectrum
25
+ * covers, in the order its axis is read.
23
26
  *
24
27
  * The refusal is last and is the only red in the shell, so the eye that has just
25
28
  * pressed something and seen nothing happen lands on the reason.
@@ -30,12 +33,14 @@ export function IrStatusBar() {
30
33
  useIrEditorState();
31
34
  const { spectra } = state.data;
32
35
  const { error, tool } = state.view;
33
- const { mode, pickBands } = state.settings;
36
+ const { mode, pickBands, technique } = state.settings;
37
+ const { reverseX, assignments } = IR_TECHNIQUE_RULES[technique];
38
+ const text = useIrText(technique);
34
39
 
35
40
  const range =
36
- selectedSpectrum === null || selectedSpectrum.wavenumber.length === 0
37
- ? null
38
- : `${formatWavenumber(selectedSpectrum.wavenumber.at(-1) as number)}–${formatWavenumber(selectedSpectrum.wavenumber[0] as number)} cm⁻¹`;
41
+ selectedSpectrum === null
42
+ ? ''
43
+ : formatWavenumberRange(selectedSpectrum.wavenumber, reverseX);
39
44
 
40
45
  return (
41
46
  <div style={barStyle}>
@@ -45,7 +50,7 @@ export function IrStatusBar() {
45
50
  : `${countLabel(visibleSpectra.length, 'spectrum', 'spectra')} drawn of ${spectra.length}`}
46
51
  </span>
47
52
 
48
- <span style={itemStyle}>{yAxisTitle(mode)}</span>
53
+ <span style={itemStyle}>{text.yTitle(mode)}</span>
49
54
 
50
55
  {selectedSpectrum === null ? null : (
51
56
  <span style={nameStyle} title={selectedSpectrum.name}>
@@ -53,12 +58,12 @@ export function IrStatusBar() {
53
58
  </span>
54
59
  )}
55
60
 
56
- {range === null ? null : <span style={mutedStyle}>{range}</span>}
61
+ {range === '' ? null : <span style={mutedStyle}>{range}</span>}
57
62
 
58
63
  {pickBands && assigned.length > 0 ? (
59
64
  <span style={itemStyle}>
60
- {countLabel(assigned.length, 'band', 'bands')},{' '}
61
- {countAssigned(assigned)} assigned
65
+ {countLabel(assigned.length, 'band', 'bands')}
66
+ {assignments ? `, ${countAssigned(assigned)} assigned` : null}
62
67
  </span>
63
68
  ) : null}
64
69
 
@@ -1,10 +1,13 @@
1
1
  import { Toolbar } from 'react-science/ui';
2
2
 
3
3
  import type { FigureDownloadState } from '../../download/ui/useFigureDownload.tsx';
4
- import { boxZoomOnly } from '../core/irMode.ts';
4
+ import { bandsPointDown, boxZoomOnly } from '../core/irMode.ts';
5
+ import { defaultIrMode } from '../core/irTechnique.ts';
6
+ import { irCommandOffered } from '../core/irTechniqueCommands.ts';
5
7
 
6
8
  import { useIrCommandProps } from './irCommandContext.tsx';
7
9
  import { useIrEditorState } from './irStateContext.ts';
10
+ import { useIrText } from './useIrText.ts';
8
11
 
9
12
  /** What {@link IrToolbar} needs. */
10
13
  export interface IrToolbarProps {
@@ -29,6 +32,9 @@ export interface IrToolbarProps {
29
32
  * drag on that axis is a rectangle, so both buttons go dead with the square zoom
30
33
  * lit. Showing `read` as available there would offer a gesture the chart does not
31
34
  * make, and showing it as *active* would name the wrong one.
35
+ *
36
+ * A button whose command the technique does not offer is not drawn: a Raman
37
+ * spectrum has a single value axis and no assignment to name.
32
38
  * @param props - Component props.
33
39
  * @returns The toolbar.
34
40
  */
@@ -38,7 +44,8 @@ export function IrToolbar(props: IrToolbarProps) {
38
44
  const command = useIrCommandProps();
39
45
  const { spectra } = state.data;
40
46
  const { domain, tool } = state.view;
41
- const { mode, pickBands, showAssignments } = state.settings;
47
+ const { mode, pickBands, showAssignments, technique } = state.settings;
48
+ const text = useIrText(technique);
42
49
 
43
50
  const isEmpty = spectra.length === 0;
44
51
  // The popover opens the panel itself, so the command's own click is left out.
@@ -49,7 +56,7 @@ export function IrToolbar(props: IrToolbarProps) {
49
56
  // `collapse`, not the default `wrap`: a wrapping vertical toolbar measures
50
57
  // itself and writes the result back as an inline width, so in a row short of
51
58
  // space it settles at half an icon and stays there.
52
- <Toolbar vertical overflow="collapse" aria-label="Infrared spectrum tools">
59
+ <Toolbar vertical overflow="collapse" aria-label={text.tools}>
53
60
  <Toolbar.Item
54
61
  icon="trash"
55
62
  intent="danger"
@@ -83,24 +90,28 @@ export function IrToolbar(props: IrToolbarProps) {
83
90
  disabled={domain === null}
84
91
  />
85
92
 
86
- <Toolbar.Item
87
- icon={mode === 'absorbance' ? 'arrow-up' : 'arrow-down'}
88
- active={mode === 'absorbance'}
89
- {...command('toggleMode')}
90
- disabled={isEmpty}
91
- />
93
+ {irCommandOffered(technique, 'toggleMode') ? (
94
+ <Toolbar.Item
95
+ icon={bandsPointDown(mode) ? 'arrow-down' : 'arrow-up'}
96
+ active={mode !== defaultIrMode(technique)}
97
+ {...command('toggleMode')}
98
+ disabled={isEmpty}
99
+ />
100
+ ) : null}
92
101
  <Toolbar.Item
93
102
  icon="locate"
94
103
  active={pickBands}
95
104
  {...command('togglePicking')}
96
105
  disabled={selectedSpectrum === null}
97
106
  />
98
- <Toolbar.Item
99
- icon="label"
100
- active={showAssignments}
101
- {...command('toggleAssignments')}
102
- disabled={!pickBands || selectedSpectrum === null}
103
- />
107
+ {irCommandOffered(technique, 'toggleAssignments') ? (
108
+ <Toolbar.Item
109
+ icon="label"
110
+ active={showAssignments}
111
+ {...command('toggleAssignments')}
112
+ disabled={!pickBands || selectedSpectrum === null}
113
+ />
114
+ ) : null}
104
115
 
105
116
  {/* What the chart looks like right now is what a figure is of — the
106
117
  window it was dragged to, the way up it is being read, the
@@ -26,7 +26,10 @@ export interface IrTracesProps {
26
26
  * the *high* end, the opposite of what every other chart's does.
27
27
  */
28
28
  xDomain: readonly [number, number];
29
- /** The wavenumber axis as it is currently zoomed, running right to left. */
29
+ /**
30
+ * The wavenumber axis as it is currently zoomed, running right to left for
31
+ * infrared.
32
+ */
30
33
  xScale: ChartScale;
31
34
  /** The value axis as it is currently zoomed. */
32
35
  yScale: ChartScale;
@@ -85,6 +88,7 @@ export function IrTraces(props: IrTracesProps): ReactElement {
85
88
  for (const spectrum of spectra) {
86
89
  if (!spectrum.visible) continue;
87
90
  const trace = traceOf(spectrum, mode);
91
+ if (trace === undefined) continue;
88
92
  const { x, y } = xyReduce(trace, { from, to, nbPoints: wanted });
89
93
  const d = profilePath(x, y, xScale, yScale);
90
94
  if (d !== '') drawn.push({ id: spectrum.id, color: spectrum.color, d });
@@ -0,0 +1,53 @@
1
+ import type { Ref } from 'react';
2
+
3
+ import type { IrEditorHandle } from '../core/irEditorApi.ts';
4
+ import type { IrSidePanelId } from '../core/irSidePanels.ts';
5
+ import type { IrSpectrum } from '../core/irSpectrum.ts';
6
+
7
+ import { VibrationalEditor } from './VibrationalEditor.tsx';
8
+
9
+ /** The handle a host drives a Raman viewer through: the infrared one's. */
10
+ export type RamanEditorHandle = IrEditorHandle;
11
+
12
+ export interface RamanEditorProps {
13
+ /**
14
+ * The spectra to open on, each carrying its `intensity`. Read when the viewer
15
+ * opens and never again: what is on the chart belongs to the viewer from then
16
+ * on. The `ref` handle is how spectra arrive afterwards.
17
+ * @default []
18
+ */
19
+ data?: IrSpectrum[];
20
+ /**
21
+ * Called whenever the spectra open change — one loaded, one closed, the chart
22
+ * cleared — with the spectra now open. Not called for a zoom, a selection, a
23
+ * rename or a colour.
24
+ * @default undefined
25
+ */
26
+ onChange?: (spectra: IrSpectrum[]) => void;
27
+ /**
28
+ * The side panels open to begin with.
29
+ * @default DEFAULT_SIDE_PANELS
30
+ */
31
+ defaultPanelIds?: IrSidePanelId[];
32
+ /**
33
+ * The handle a host drives the viewer through: take these spectra, frame this
34
+ * Raman shift, select that one.
35
+ * @default undefined
36
+ */
37
+ ref?: Ref<RamanEditorHandle>;
38
+ }
39
+
40
+ /**
41
+ * A Raman spectrum viewer: the infrared viewer's shell, panels, gestures and
42
+ * export, changed only where Raman is physically different.
43
+ *
44
+ * The spectra are read as intensity, their one value axis, so there is no mode
45
+ * to choose; the Raman shift runs from low at the left to high at the right;
46
+ * the bands are picked as maxima of intensity; and the infrared correlation
47
+ * table is not asked what a band might be.
48
+ * @param props - Component props.
49
+ * @returns The viewer.
50
+ */
51
+ export function RamanEditor(props: RamanEditorProps) {
52
+ return <VibrationalEditor technique="raman" {...props} />;
53
+ }
@@ -37,7 +37,7 @@ export function SpectraPanel() {
37
37
  const actions = useIrActions();
38
38
  const { spectra } = state.data;
39
39
  const { selectedId } = state.view;
40
- const { mode } = state.settings;
40
+ const { mode, technique } = state.settings;
41
41
 
42
42
  const selected = spectra.find((spectrum) => spectrum.id === selectedId);
43
43
  const allDrawn = spectra.every((spectrum) => spectrum.visible);
@@ -96,6 +96,7 @@ export function SpectraPanel() {
96
96
  <SpectrumRow
97
97
  key={spectrum.id}
98
98
  spectrum={spectrum}
99
+ technique={technique}
99
100
  selected={spectrum.id === selectedId}
100
101
  onSelect={() => actions.selectSpectrum(spectrum.id)}
101
102
  onToggle={() =>
@@ -1,12 +1,20 @@
1
1
  import { Button } from 'react-science/ui';
2
2
 
3
3
  import { SpectrumColorSwatch } from '../../panel/ui/SpectrumColorSwatch.tsx';
4
- import { formatWavenumber } from '../core/irFormat.ts';
4
+ import { formatWavenumberRange } from '../core/irFormat.ts';
5
5
  import type { IrSpectrum } from '../core/irSpectrum.ts';
6
+ import type { IrTechnique } from '../core/irTechnique.ts';
7
+ import { IR_TECHNIQUE_RULES } from '../core/irTechnique.ts';
6
8
 
7
9
  export interface SpectrumRowProps {
8
10
  /** The spectrum the row is about. */
9
11
  spectrum: IrSpectrum;
12
+ /**
13
+ * What it was measured with, which decides the order its range is written
14
+ * in: high to low for infrared, low to high for Raman.
15
+ * @default 'infrared'
16
+ */
17
+ technique?: IrTechnique;
10
18
  /** Whether the panels are about it. */
11
19
  selected: boolean;
12
20
  /** Point the panels at it. */
@@ -31,6 +39,7 @@ export interface SpectrumRowProps {
31
39
  export function SpectrumRow(props: SpectrumRowProps) {
32
40
  const {
33
41
  spectrum,
42
+ technique = 'infrared',
34
43
  selected,
35
44
  onSelect,
36
45
  onToggle,
@@ -40,10 +49,10 @@ export function SpectrumRow(props: SpectrumRowProps) {
40
49
  onRename,
41
50
  } = props;
42
51
 
43
- const range =
44
- spectrum.wavenumber.length === 0
45
- ? ''
46
- : `${formatWavenumber(spectrum.wavenumber.at(-1) as number)}–${formatWavenumber(spectrum.wavenumber[0] as number)} cm⁻¹`;
52
+ const range = formatWavenumberRange(
53
+ spectrum.wavenumber,
54
+ IR_TECHNIQUE_RULES[technique].reverseX,
55
+ );
47
56
 
48
57
  return (
49
58
  <div
@@ -0,0 +1,94 @@
1
+ import type { Ref } from 'react';
2
+ import { RootLayout } from 'react-science/ui';
3
+
4
+ import type { IrEditorHandle } from '../core/irEditorApi.ts';
5
+ import type { IrSidePanelId } from '../core/irSidePanels.ts';
6
+ import type { IrMode, IrSpectrum } from '../core/irSpectrum.ts';
7
+ import type { IrTechnique } from '../core/irTechnique.ts';
8
+
9
+ import { IrShell } from './IrShell.tsx';
10
+ import { IrStateProvider } from './IrStateProvider.tsx';
11
+ import { useIrEditor } from './useIrEditor.ts';
12
+
13
+ export interface VibrationalEditorProps {
14
+ /**
15
+ * What the spectra were measured with. Read when the viewer opens and fixed
16
+ * from then on: it decides the value axes, the direction and name of the
17
+ * wavenumber axis, and whether the correlation table is asked about a band.
18
+ */
19
+ technique: IrTechnique;
20
+ /**
21
+ * The spectra to open on. Read when the viewer opens and never again.
22
+ * @default []
23
+ */
24
+ data?: IrSpectrum[];
25
+ /**
26
+ * Called whenever the spectra open change, with the spectra now open.
27
+ * @default undefined
28
+ */
29
+ onChange?: (spectra: IrSpectrum[]) => void;
30
+ /**
31
+ * Which value axis to open in; one the technique does not offer opens its
32
+ * first instead.
33
+ * @default the technique's first
34
+ */
35
+ mode?: IrMode;
36
+ /**
37
+ * The side panels open to begin with.
38
+ * @default DEFAULT_SIDE_PANELS
39
+ */
40
+ defaultPanelIds?: IrSidePanelId[];
41
+ /**
42
+ * The handle a host drives the viewer through.
43
+ * @default undefined
44
+ */
45
+ ref?: Ref<IrEditorHandle>;
46
+ }
47
+
48
+ /**
49
+ * The viewer `IrEditor` and `RamanEditor` both are: one shell, one set of
50
+ * panels and gestures, with what differs between the techniques read from
51
+ * `IR_TECHNIQUE_RULES` rather than written into either.
52
+ * @param props - Component props.
53
+ * @returns The viewer.
54
+ */
55
+ export function VibrationalEditor(props: VibrationalEditorProps) {
56
+ const {
57
+ technique,
58
+ data = NO_SPECTRA,
59
+ onChange,
60
+ mode,
61
+ defaultPanelIds,
62
+ ref,
63
+ } = props;
64
+
65
+ const editor = useIrEditor({
66
+ spectra: data,
67
+ settings: { technique, ...(mode === undefined ? {} : { mode }) },
68
+ panelIds: defaultPanelIds,
69
+ onChange,
70
+ });
71
+
72
+ return (
73
+ <IrStateProvider editor={editor}>
74
+ {/* `react-science`'s own root: it holds the element the browser is asked
75
+ to show full screen, points Blueprint's portals at that element so
76
+ dialogs and tooltips are not left behind in `document.body`, and paints
77
+ the backdrop white. Above the shell rather than inside it, so the shell
78
+ can ask whether it is full screen and offer the way out. */}
79
+ <RootLayout style={rootLayoutStyle}>
80
+ <IrShell handle={ref} />
81
+ </RootLayout>
82
+ </IrStateProvider>
83
+ );
84
+ }
85
+
86
+ /**
87
+ * The viewer fills what it is given, and its own children scroll rather than
88
+ * push: `react-science` lays its root out as a column of full width and height,
89
+ * and a flex column with no `minHeight` grows past its parent instead.
90
+ */
91
+ const rootLayoutStyle = { minHeight: 0 } as const;
92
+
93
+ /** What a viewer opens on when it is handed nothing, kept stable for the memo. */
94
+ const NO_SPECTRA: IrSpectrum[] = [];