react-science 0.25.0 → 0.26.2

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 (166) hide show
  1. package/lib/app/explorer/MeasurementExplorer.js +29 -7
  2. package/lib/app/explorer/MeasurementExplorerWithState.js +2 -2
  3. package/lib/app/helpers/MeasurementPlot.js +28 -19
  4. package/lib/app/helpers/react-plot.js +1 -1
  5. package/lib/app/kinds/mass/MassPlotView.js +2 -2
  6. package/lib/app/kinds/mass/MeasurementMassPlot.js +49 -33
  7. package/lib/app/panels/measurement-info/MeasurementInfoPanel.js +13 -58
  8. package/lib/app-data/state/data/data.helpers.js +32 -8
  9. package/lib/components/forms/Checkbox.js +4 -5
  10. package/lib/components/forms/Input.js +3 -87
  11. package/lib/components/forms/TextArea.js +25 -0
  12. package/lib/components/forms/index.js +2 -0
  13. package/lib/components/forms/radio-group/ButtonRadioItem.js +76 -0
  14. package/lib/components/forms/radio-group/ClassicRadioItem.js +92 -0
  15. package/lib/components/forms/radio-group/RadioGroup.js +69 -0
  16. package/lib/components/forms/radio-group/index.js +17 -0
  17. package/lib/components/forms/styles.js +80 -0
  18. package/lib/components/forms/utils/SubText.js +20 -0
  19. package/lib/components/forms/utils/index.js +17 -0
  20. package/lib/components/header/PanelHeader.js +51 -0
  21. package/lib/components/header/index.js +1 -0
  22. package/lib/components/index.js +1 -0
  23. package/lib/components/info-panel/InfoPanel.js +94 -0
  24. package/lib/components/info-panel/index.js +17 -0
  25. package/lib/components/modal/ConfirmModal.js +3 -2
  26. package/lib/components/modal/Modal.js +3 -1
  27. package/lib/components/table/Table.js +2 -2
  28. package/lib/components/toolbar/PanelPreferencesToolbar.js +26 -0
  29. package/lib/components/toolbar/index.js +1 -0
  30. package/lib-esm/app/explorer/MeasurementExplorer.d.ts.map +1 -1
  31. package/lib-esm/app/explorer/MeasurementExplorer.js +30 -8
  32. package/lib-esm/app/explorer/MeasurementExplorer.js.map +1 -1
  33. package/lib-esm/app/explorer/MeasurementExplorerWithState.d.ts.map +1 -1
  34. package/lib-esm/app/explorer/MeasurementExplorerWithState.js +2 -2
  35. package/lib-esm/app/explorer/MeasurementExplorerWithState.js.map +1 -1
  36. package/lib-esm/app/helpers/MeasurementPlot.d.ts +3 -3
  37. package/lib-esm/app/helpers/MeasurementPlot.d.ts.map +1 -1
  38. package/lib-esm/app/helpers/MeasurementPlot.js +28 -19
  39. package/lib-esm/app/helpers/MeasurementPlot.js.map +1 -1
  40. package/lib-esm/app/helpers/react-plot.d.ts.map +1 -1
  41. package/lib-esm/app/helpers/react-plot.js +1 -1
  42. package/lib-esm/app/helpers/react-plot.js.map +1 -1
  43. package/lib-esm/app/kinds/mass/MassPlotView.d.ts.map +1 -1
  44. package/lib-esm/app/kinds/mass/MassPlotView.js +2 -2
  45. package/lib-esm/app/kinds/mass/MassPlotView.js.map +1 -1
  46. package/lib-esm/app/kinds/mass/MeasurementMassPlot.d.ts.map +1 -1
  47. package/lib-esm/app/kinds/mass/MeasurementMassPlot.js +49 -33
  48. package/lib-esm/app/kinds/mass/MeasurementMassPlot.js.map +1 -1
  49. package/lib-esm/app/panels/measurement-info/MeasurementInfoPanel.d.ts.map +1 -1
  50. package/lib-esm/app/panels/measurement-info/MeasurementInfoPanel.js +15 -60
  51. package/lib-esm/app/panels/measurement-info/MeasurementInfoPanel.js.map +1 -1
  52. package/lib-esm/app-data/state/data/data.helpers.d.ts +20 -12
  53. package/lib-esm/app-data/state/data/data.helpers.d.ts.map +1 -1
  54. package/lib-esm/app-data/state/data/data.helpers.js +30 -7
  55. package/lib-esm/app-data/state/data/data.helpers.js.map +1 -1
  56. package/lib-esm/components/forms/Checkbox.d.ts.map +1 -1
  57. package/lib-esm/components/forms/Checkbox.js +1 -2
  58. package/lib-esm/components/forms/Checkbox.js.map +1 -1
  59. package/lib-esm/components/forms/Input.d.ts +4 -2
  60. package/lib-esm/components/forms/Input.d.ts.map +1 -1
  61. package/lib-esm/components/forms/Input.js +3 -87
  62. package/lib-esm/components/forms/Input.js.map +1 -1
  63. package/lib-esm/components/forms/TextArea.d.ts +8 -0
  64. package/lib-esm/components/forms/TextArea.d.ts.map +1 -0
  65. package/lib-esm/components/forms/TextArea.js +19 -0
  66. package/lib-esm/components/forms/TextArea.js.map +1 -0
  67. package/lib-esm/components/forms/index.d.ts +2 -0
  68. package/lib-esm/components/forms/index.d.ts.map +1 -1
  69. package/lib-esm/components/forms/index.js +2 -0
  70. package/lib-esm/components/forms/index.js.map +1 -1
  71. package/lib-esm/components/forms/radio-group/ButtonRadioItem.d.ts +3 -0
  72. package/lib-esm/components/forms/radio-group/ButtonRadioItem.d.ts.map +1 -0
  73. package/lib-esm/components/forms/radio-group/ButtonRadioItem.js +50 -0
  74. package/lib-esm/components/forms/radio-group/ButtonRadioItem.js.map +1 -0
  75. package/lib-esm/components/forms/radio-group/ClassicRadioItem.d.ts +3 -0
  76. package/lib-esm/components/forms/radio-group/ClassicRadioItem.d.ts.map +1 -0
  77. package/lib-esm/components/forms/radio-group/ClassicRadioItem.js +66 -0
  78. package/lib-esm/components/forms/radio-group/ClassicRadioItem.js.map +1 -0
  79. package/lib-esm/components/forms/radio-group/RadioGroup.d.ts +18 -0
  80. package/lib-esm/components/forms/radio-group/RadioGroup.d.ts.map +1 -0
  81. package/lib-esm/components/forms/radio-group/RadioGroup.js +43 -0
  82. package/lib-esm/components/forms/radio-group/RadioGroup.js.map +1 -0
  83. package/lib-esm/components/forms/radio-group/index.d.ts +2 -0
  84. package/lib-esm/components/forms/radio-group/index.d.ts.map +1 -0
  85. package/lib-esm/components/forms/radio-group/index.js +2 -0
  86. package/lib-esm/components/forms/radio-group/index.js.map +1 -0
  87. package/lib-esm/components/forms/styles.d.ts +26 -0
  88. package/lib-esm/components/forms/styles.d.ts.map +1 -0
  89. package/lib-esm/components/forms/styles.js +75 -0
  90. package/lib-esm/components/forms/styles.js.map +1 -0
  91. package/lib-esm/components/forms/utils/SubText.d.ts +7 -0
  92. package/lib-esm/components/forms/utils/SubText.d.ts.map +1 -0
  93. package/lib-esm/components/forms/utils/SubText.js +17 -0
  94. package/lib-esm/components/forms/utils/SubText.js.map +1 -0
  95. package/lib-esm/components/forms/utils/index.d.ts +2 -0
  96. package/lib-esm/components/forms/utils/index.d.ts.map +1 -0
  97. package/lib-esm/components/forms/utils/index.js +2 -0
  98. package/lib-esm/components/forms/utils/index.js.map +1 -0
  99. package/lib-esm/components/header/PanelHeader.d.ts +10 -0
  100. package/lib-esm/components/header/PanelHeader.d.ts.map +1 -0
  101. package/lib-esm/components/header/PanelHeader.js +48 -0
  102. package/lib-esm/components/header/PanelHeader.js.map +1 -0
  103. package/lib-esm/components/header/index.d.ts +1 -0
  104. package/lib-esm/components/header/index.d.ts.map +1 -1
  105. package/lib-esm/components/header/index.js +1 -0
  106. package/lib-esm/components/header/index.js.map +1 -1
  107. package/lib-esm/components/index.d.ts +1 -0
  108. package/lib-esm/components/index.d.ts.map +1 -1
  109. package/lib-esm/components/index.js +1 -0
  110. package/lib-esm/components/index.js.map +1 -1
  111. package/lib-esm/components/info-panel/InfoPanel.d.ts +15 -0
  112. package/lib-esm/components/info-panel/InfoPanel.d.ts.map +1 -0
  113. package/lib-esm/components/info-panel/InfoPanel.js +91 -0
  114. package/lib-esm/components/info-panel/InfoPanel.js.map +1 -0
  115. package/lib-esm/components/info-panel/index.d.ts +2 -0
  116. package/lib-esm/components/info-panel/index.d.ts.map +1 -0
  117. package/lib-esm/components/info-panel/index.js +2 -0
  118. package/lib-esm/components/info-panel/index.js.map +1 -0
  119. package/lib-esm/components/modal/ConfirmModal.d.ts.map +1 -1
  120. package/lib-esm/components/modal/ConfirmModal.js +3 -2
  121. package/lib-esm/components/modal/ConfirmModal.js.map +1 -1
  122. package/lib-esm/components/modal/Modal.d.ts.map +1 -1
  123. package/lib-esm/components/modal/Modal.js +3 -1
  124. package/lib-esm/components/modal/Modal.js.map +1 -1
  125. package/lib-esm/components/table/Table.d.ts +2 -3
  126. package/lib-esm/components/table/Table.d.ts.map +1 -1
  127. package/lib-esm/components/table/Table.js +2 -2
  128. package/lib-esm/components/table/Table.js.map +1 -1
  129. package/lib-esm/components/toolbar/PanelPreferencesToolbar.d.ts +7 -0
  130. package/lib-esm/components/toolbar/PanelPreferencesToolbar.d.ts.map +1 -0
  131. package/lib-esm/components/toolbar/PanelPreferencesToolbar.js +23 -0
  132. package/lib-esm/components/toolbar/PanelPreferencesToolbar.js.map +1 -0
  133. package/lib-esm/components/toolbar/index.d.ts +1 -0
  134. package/lib-esm/components/toolbar/index.d.ts.map +1 -1
  135. package/lib-esm/components/toolbar/index.js +1 -0
  136. package/lib-esm/components/toolbar/index.js.map +1 -1
  137. package/package.json +16 -14
  138. package/src/app/explorer/MeasurementExplorer.tsx +34 -12
  139. package/src/app/explorer/MeasurementExplorerWithState.tsx +2 -2
  140. package/src/app/helpers/MeasurementPlot.tsx +44 -33
  141. package/src/app/helpers/react-plot.tsx +6 -4
  142. package/src/app/kinds/mass/MassPlotView.tsx +2 -2
  143. package/src/app/kinds/mass/MeasurementMassPlot.tsx +57 -37
  144. package/src/app/panels/measurement-info/MeasurementInfoPanel.tsx +14 -84
  145. package/src/app-data/state/data/data.helpers.ts +49 -16
  146. package/src/components/forms/Checkbox.tsx +2 -3
  147. package/src/components/forms/Input.tsx +14 -125
  148. package/src/components/forms/TextArea.tsx +45 -0
  149. package/src/components/forms/index.ts +2 -0
  150. package/src/components/forms/radio-group/ButtonRadioItem.tsx +77 -0
  151. package/src/components/forms/radio-group/ClassicRadioItem.tsx +95 -0
  152. package/src/components/forms/radio-group/RadioGroup.tsx +83 -0
  153. package/src/components/forms/radio-group/index.ts +1 -0
  154. package/src/components/forms/styles.ts +96 -0
  155. package/src/components/forms/utils/SubText.tsx +31 -0
  156. package/src/components/forms/utils/index.ts +1 -0
  157. package/src/components/header/PanelHeader.tsx +75 -0
  158. package/src/components/header/index.ts +1 -0
  159. package/src/components/index.ts +1 -0
  160. package/src/components/info-panel/InfoPanel.tsx +150 -0
  161. package/src/components/info-panel/index.ts +1 -0
  162. package/src/components/modal/ConfirmModal.tsx +3 -2
  163. package/src/components/modal/Modal.tsx +3 -1
  164. package/src/components/table/Table.tsx +3 -5
  165. package/src/components/toolbar/PanelPreferencesToolbar.tsx +46 -0
  166. package/src/components/toolbar/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-science",
3
- "version": "0.25.0",
3
+ "version": "0.26.2",
4
4
  "description": "React components to build scientific applications UI",
5
5
  "exports": {
6
6
  "./app": {
@@ -50,7 +50,7 @@
50
50
  "test-only": "vitest run --coverage "
51
51
  },
52
52
  "volta": {
53
- "node": "18.17.0"
53
+ "node": "18.17.1"
54
54
  },
55
55
  "overrides": {
56
56
  "react": "^18.2.0",
@@ -67,6 +67,7 @@
67
67
  "@lukeed/uuid": "^2.0.1",
68
68
  "@popperjs/core": "^2.11.8",
69
69
  "@radix-ui/react-checkbox": "^1.0.4",
70
+ "@radix-ui/react-radio-group": "^1.1.3",
70
71
  "@radix-ui/react-select": "^1.2.2",
71
72
  "@tanstack/react-query": "^4.32.6",
72
73
  "@tanstack/react-table": "^8.9.3",
@@ -85,7 +86,7 @@
85
86
  "netcdfjs": "^2.0.2",
86
87
  "react-d3-utils": "^1.0.0",
87
88
  "react-dropzone": "^14.2.3",
88
- "react-error-boundary": "^4.0.10",
89
+ "react-error-boundary": "^4.0.11",
89
90
  "react-icons": "^4.10.1",
90
91
  "react-inspector": "^6.0.2",
91
92
  "react-kbs": "^2.1.1",
@@ -100,31 +101,32 @@
100
101
  "devDependencies": {
101
102
  "@babel/core": "^7.22.10",
102
103
  "@babel/eslint-parser": "^7.22.10",
103
- "@playwright/experimental-ct-react": "^1.36.2",
104
- "@playwright/test": "^1.36.2",
105
- "@storybook/addon-essentials": "7.2.1",
106
- "@storybook/blocks": "7.2.1",
107
- "@storybook/react": "7.2.1",
108
- "@storybook/react-vite": "7.2.1",
104
+ "@playwright/experimental-ct-react": "^1.37.0",
105
+ "@playwright/test": "^1.37.0",
106
+ "@storybook/addon-essentials": "7.3.0",
107
+ "@storybook/addon-storysource": "7.3.0",
108
+ "@storybook/blocks": "7.3.0",
109
+ "@storybook/react": "7.3.0",
110
+ "@storybook/react-vite": "7.3.0",
109
111
  "@types/babel__core": "^7.20.1",
110
112
  "@types/d3-scale-chromatic": "^3.0.0",
111
- "@types/lodash": "^4.14.196",
112
- "@types/react": "^18.2.19",
113
+ "@types/lodash": "^4.14.197",
114
+ "@types/react": "^18.2.20",
113
115
  "@types/react-dom": "^18.2.7",
114
116
  "@types/react-inspector": "^4.0.2",
115
117
  "@vitejs/plugin-react": "^4.0.4",
116
118
  "@vitest/coverage-v8": "^0.34.1",
117
119
  "cheminfo-font": "^1.11.0",
118
120
  "cross-env": "^7.0.3",
119
- "eslint": "^8.46.0",
121
+ "eslint": "^8.47.0",
120
122
  "eslint-config-zakodium": "^8.0.2",
121
123
  "eslint-plugin-storybook": "^0.6.13",
122
- "prettier": "^3.0.1",
124
+ "prettier": "^3.0.2",
123
125
  "react": "^18.2.0",
124
126
  "react-dom": "^18.2.0",
125
127
  "react-ocl": "^6.1.0",
126
128
  "rimraf": "^5.0.1",
127
- "storybook": "7.2.1",
129
+ "storybook": "7.3.0",
128
130
  "typescript": "^5.1.6",
129
131
  "vite": "^4.4.9",
130
132
  "vitest": "^0.34.1"
@@ -1,5 +1,5 @@
1
1
  import styled from '@emotion/styled';
2
- import { useState } from 'react';
2
+ import { useMemo, useState } from 'react';
3
3
  import { FaExchangeAlt, FaArrowsAltH } from 'react-icons/fa';
4
4
 
5
5
  import { MeasurementPlot, MeasurementPlotProps } from '../helpers/index';
@@ -37,18 +37,40 @@ const MeasurementExplorerAction = styled.div`
37
37
  `;
38
38
 
39
39
  export function MeasurementExplorer(props: MeasurementExplorerProps) {
40
- const {
41
- measurement: { data },
42
- width = '100%',
43
- height = '100%',
44
- } = props;
40
+ const { measurement, width = '100%', height = '100%' } = props;
41
+ const measurementsArray = useMemo(
42
+ () => (Array.isArray(measurement) ? measurement : [measurement]),
43
+ [measurement],
44
+ );
45
+ const varNames = useMemo(() => {
46
+ const varNames: string[][] = [];
47
+ for (const [i, { data }] of measurementsArray.entries()) {
48
+ for (const { variables } of data) {
49
+ const names: string[] = [];
50
+ for (const varName in variables) {
51
+ if (i === 0) {
52
+ names.push(varName);
53
+ } else if (!varNames.flat().includes(varName)) {
54
+ throw new Error(
55
+ `Measurements selected does not have the same variables `,
56
+ );
57
+ }
58
+ }
59
+ varNames.push(names);
60
+ }
61
+ }
62
+ return varNames;
63
+ }, [measurementsArray]);
45
64
 
46
65
  function defaultInfo(dataIndex: number) {
47
- const varNames = Object.keys(data[dataIndex].variables);
48
66
  return {
49
67
  dataIndex,
50
- xVariableName: varNames.includes('x') ? 'x' : varNames[0],
51
- yVariableName: varNames.includes('y') ? 'y' : varNames[1],
68
+ xVariableName: varNames[dataIndex].includes('x')
69
+ ? 'x'
70
+ : varNames[dataIndex][0],
71
+ yVariableName: varNames[dataIndex].includes('y')
72
+ ? 'y'
73
+ : varNames[dataIndex][1],
52
74
  };
53
75
  }
54
76
 
@@ -67,9 +89,9 @@ export function MeasurementExplorer(props: MeasurementExplorerProps) {
67
89
  const formatVarKey = `${varKey} - `;
68
90
  return formatVarKey + label + formatUnit;
69
91
  }
70
- const { variables } = data[info.dataIndex];
92
+ const { variables } = measurementsArray[0].data[info.dataIndex];
71
93
  const oppositeAxis = axis === 'x' ? 'yVariableName' : 'xVariableName';
72
- return Object.keys(variables).map((d) => {
94
+ return varNames[info.dataIndex].map((d) => {
73
95
  if (d !== info[oppositeAxis]) {
74
96
  return (
75
97
  <option key={d} value={d}>
@@ -97,7 +119,7 @@ export function MeasurementExplorer(props: MeasurementExplorerProps) {
97
119
  }
98
120
  }}
99
121
  >
100
- {data.map((d, i) => (
122
+ {measurementsArray[0].data.map((d, i) => (
101
123
  // eslint-disable-next-line react/no-array-index-key
102
124
  <option key={i} value={i}>
103
125
  {i}
@@ -1,12 +1,12 @@
1
1
  import { getCurrentMeasurementData, useAppState } from '../../app-data/index';
2
- import { assertNotNull } from '../../components/index';
3
2
 
4
3
  import { MeasurementExplorer } from './MeasurementExplorer';
5
4
 
6
5
  export default function MeasurementExplorerWithState() {
7
6
  const appState = useAppState();
8
7
  const data = getCurrentMeasurementData(appState);
9
- assertNotNull(data);
8
+
9
+ if (!data) return <div>No selected measurement</div>;
10
10
  return (
11
11
  <MeasurementExplorer
12
12
  measurement={data.data}
@@ -6,10 +6,10 @@ import type { MeasurementBase, MeasurementAppView } from '../../app-data/index';
6
6
 
7
7
  import { BasicComponent } from './index';
8
8
 
9
- type Measurement = Pick<MeasurementBase, 'meta' | 'info' | 'data'>;
9
+ type Measurement = Pick<MeasurementBase, 'meta' | 'info' | 'data' | 'id'>;
10
10
  export interface MeasurementPlotProps {
11
- measurement: Measurement;
12
- measurementDisplay: MeasurementAppView;
11
+ measurement: Measurement[] | Measurement;
12
+ measurementDisplay: MeasurementAppView[] | MeasurementAppView;
13
13
  dataIndex?: number;
14
14
  xVariableName?: string;
15
15
  yVariableName?: string;
@@ -35,44 +35,55 @@ export function MeasurementPlot(props: MeasurementPlotProps) {
35
35
  function MeasurementComponent(props: MeasurementPlotProps) {
36
36
  const {
37
37
  measurementDisplay,
38
- measurement: { data },
38
+ measurement,
39
39
  dataIndex = 0,
40
40
  xVariableName = 'x',
41
41
  yVariableName = 'y',
42
42
  } = props;
43
43
 
44
- const { x, y } = useMemo(() => {
45
- const { variables } = data[dataIndex];
46
- const { [xVariableName]: x, [yVariableName]: y } = variables;
47
- if (x === undefined || y === undefined) {
48
- throw new Error(
49
- `Variable "${
50
- x === undefined ? xVariableName : yVariableName
51
- }" is not available in data. Only ${Object.keys(
52
- data[dataIndex].variables,
53
- ).join(', ')} are available`,
54
- );
55
- }
56
-
57
- return { x, y };
58
- }, [data, dataIndex, xVariableName, yVariableName]);
59
-
60
- const { color } = measurementDisplay;
61
- if (color.kind !== 'fixed') {
62
- throw new Error(`unimplemented stroke for kind ${color.kind}`);
63
- }
44
+ const dataXY = useMemo(() => {
45
+ const measurementsArray = Array.isArray(measurement)
46
+ ? measurement
47
+ : [measurement];
48
+ return measurementsArray.map(({ data, id }) => {
49
+ const { variables } = data[dataIndex];
50
+ const { [xVariableName]: x, [yVariableName]: y } = variables;
51
+ if (x === undefined || y === undefined) {
52
+ throw new Error(
53
+ `Variable "${
54
+ x === undefined ? xVariableName : yVariableName
55
+ }" is not available in data. Only ${Object.keys(
56
+ data[dataIndex].variables,
57
+ ).join(', ')} are available`,
58
+ );
59
+ }
60
+ return { x, y, id };
61
+ });
62
+ }, [dataIndex, measurement, xVariableName, yVariableName]);
64
63
 
65
64
  return (
66
65
  <BasicComponent {...props}>
67
- <LineSeries
68
- lineStyle={{
69
- stroke: color.color,
70
- }}
71
- data={xyToXYObject({
72
- x: x.data,
73
- y: y.data,
74
- })}
75
- />
66
+ {dataXY.map(({ x, y, id }, i) => {
67
+ const { color } = Array.isArray(measurementDisplay)
68
+ ? measurementDisplay[i]
69
+ : measurementDisplay;
70
+ if (color.kind !== 'fixed') {
71
+ throw new Error(`unimplemented stroke for kind ${color.kind}`);
72
+ }
73
+
74
+ return (
75
+ <LineSeries
76
+ key={id}
77
+ lineStyle={{
78
+ stroke: color.color,
79
+ }}
80
+ data={xyToXYObject({
81
+ x: x.data,
82
+ y: y.data,
83
+ })}
84
+ />
85
+ );
86
+ })}
76
87
  </BasicComponent>
77
88
  );
78
89
  }
@@ -47,10 +47,12 @@ export function BasicComponent(props: BasicComponentProps) {
47
47
  flipHorizontalAxis = false,
48
48
  } = props;
49
49
 
50
- const {
51
- info: { title },
52
- data,
53
- } = measurement;
50
+ const [
51
+ {
52
+ info: { title },
53
+ data,
54
+ },
55
+ ] = Array.isArray(measurement) ? measurement : [measurement];
54
56
 
55
57
  const { x, y } = useMemo(() => {
56
58
  const { variables } = data[dataIndex];
@@ -2,14 +2,14 @@ import {
2
2
  getCurrentMeasurementData,
3
3
  useAppState,
4
4
  } from '../../../app-data/index';
5
- import { assertNotNull } from '../../../components/index';
6
5
 
7
6
  import { MeasurementMassPlot } from './MeasurementMassPlot';
8
7
 
9
8
  export function MassPlotView() {
10
9
  const appState = useAppState();
11
10
  const data = getCurrentMeasurementData(appState);
12
- assertNotNull(data);
11
+
12
+ if (!data) return <div>No selected measurement</div>;
13
13
  return (
14
14
  <MeasurementMassPlot
15
15
  measurement={data.data}
@@ -21,23 +21,28 @@ interface Peak {
21
21
  }
22
22
 
23
23
  export function MeasurementMassPlot(props: MeasurementPlotProps) {
24
- const { measurement } = props;
25
- if (!measurement.data) {
26
- throw new Error(
27
- 'This is weird, the data property is not available on measurement',
28
- );
29
- }
30
- if (measurement.data.length === 0) {
31
- throw new Error('Data property is empty');
32
- }
33
- if (measurement.data.length > 1) {
34
- throw new Error('Length of data property is larger than 1');
35
- }
36
- if (!measurement.data[0].variables.x) {
37
- throw new Error('x variable in undefined');
38
- }
39
- if (!measurement.data[0].variables.y) {
40
- throw new Error('y variable in undefined');
24
+ const { measurement: measurements } = props;
25
+ const measurementsArray = Array.isArray(measurements)
26
+ ? measurements
27
+ : [measurements];
28
+ for (const measurement of measurementsArray) {
29
+ if (!measurement.data) {
30
+ throw new Error(
31
+ 'This is weird, the data property is not available on measurement',
32
+ );
33
+ }
34
+ if (measurement.data.length === 0) {
35
+ throw new Error('Data property is empty');
36
+ }
37
+ if (measurement.data.length > 1) {
38
+ throw new Error('Length of data property is larger than 1');
39
+ }
40
+ if (!measurement.data[0].variables.x) {
41
+ throw new Error('x variable in undefined');
42
+ }
43
+ if (!measurement.data[0].variables.y) {
44
+ throw new Error('y variable in undefined');
45
+ }
41
46
  }
42
47
 
43
48
  return (
@@ -48,33 +53,43 @@ export function MeasurementMassPlot(props: MeasurementPlotProps) {
48
53
  }
49
54
 
50
55
  function MassComponent(props: MeasurementPlotProps) {
51
- const { measurement } = props;
56
+ const { measurement: measurements } = props;
52
57
 
53
- const { data } = measurement;
54
- const { variables } = data[0];
55
-
56
- const { x, y } = useMemo(() => {
57
- return { x: variables.x, y: variables.y };
58
- }, [variables]);
58
+ const dataXY = useMemo(() => {
59
+ const measurementsArray = Array.isArray(measurements)
60
+ ? measurements
61
+ : [measurements];
62
+ return measurementsArray.map(({ data, id }) => {
63
+ const { variables } = data[0];
64
+ const { x, y } = variables;
65
+ return { x, y, id };
66
+ });
67
+ }, [measurements]);
59
68
 
60
69
  const { x: xDomain } = usePlotControllerAxes();
61
- const { profile, peaks } = useMemo(() => {
62
- const spectrum = new Spectrum({
63
- x: x.data,
64
- y: y.data,
65
- });
66
- const isContinuous = spectrum.isContinuous();
67
- const profile =
68
- isContinuous &&
69
- xyToXYObject({
70
+ const { profiles, peaks } = useMemo(() => {
71
+ const profiles = [];
72
+ const peaks = [];
73
+ for (const { x, y, id } of dataXY) {
74
+ const spectrum = new Spectrum({
70
75
  x: x.data,
71
76
  y: y.data,
72
77
  });
78
+ const isContinuous = spectrum.isContinuous();
79
+ const data =
80
+ isContinuous &&
81
+ xyToXYObject({
82
+ x: x.data,
83
+ y: y.data,
84
+ });
85
+ profiles.push({ data, id });
86
+ peaks.push(...spectrum.getPeaks(data));
87
+ }
73
88
  return {
74
- profile,
75
- peaks: spectrum.getPeaks(profile),
89
+ profiles,
90
+ peaks,
76
91
  };
77
- }, [x.data, y.data]);
92
+ }, [dataXY]);
78
93
  const bestPeaks = useMemo(
79
94
  () =>
80
95
  getBestPeaks(peaks, {
@@ -88,7 +103,12 @@ function MassComponent(props: MeasurementPlotProps) {
88
103
  );
89
104
  return (
90
105
  <BasicComponent {...props}>
91
- {profile && <LineSeries data={profile} lineStyle={{ stroke: 'green' }} />}
106
+ {profiles.map(
107
+ ({ data, id }) =>
108
+ data && (
109
+ <LineSeries key={id} data={data} lineStyle={{ stroke: 'green' }} />
110
+ ),
111
+ )}
92
112
  <BarSeries data={peaks} lineStyle={{ stroke: 'red' }} />
93
113
  <Annotations>
94
114
  {bestPeaks.map(({ x, y, shortLabel }: Peak) => (
@@ -1,97 +1,27 @@
1
- import { useState } from 'react';
2
-
3
1
  import {
4
2
  getCurrentMeasurementData,
5
3
  useAppState,
6
4
  } from '../../../app-data/index';
7
- import { ValueRenderers, Table } from '../../../components/index';
5
+ import { InfoPanelData, InfoPanel } from '../../../components/index';
8
6
 
9
7
  export function MeasurementInfoPanel() {
10
8
  const appState = useAppState();
11
9
  const measurement = getCurrentMeasurementData(appState);
12
- const [search, setSearch] = useState('');
13
10
  if (!measurement) return null;
14
- const { meta, info } = measurement.data;
15
-
16
- function viewData(data: Record<string, any>) {
17
- return Object.keys(data).map((key) => {
18
- const value = data[key];
19
- if (
20
- !key.toLowerCase().includes(search.toLowerCase()) &&
21
- !valueSearch(value, search)
22
- ) {
23
- return null;
24
- }
25
- return (
26
- <Table.Row key={key}>
27
- <ValueRenderers.Text value={key} />
28
- {valueCell(value)}
29
- </Table.Row>
30
- );
31
- });
32
- }
33
11
 
34
- return (
35
- <div>
36
- <input
37
- style={{
38
- border: 'solid 1px black',
39
- width: '300px',
40
- marginBottom: '10px',
41
- padding: '3px',
42
- }}
43
- value={search}
44
- placeholder="search for a parameter ..."
45
- onChange={({ target }) => {
46
- if (target.value !== undefined) setSearch(target.value);
47
- }}
48
- />
49
- <Table>
50
- <Table.Header>
51
- <ValueRenderers.Title value="Parameter" />
52
- <ValueRenderers.Title value="Value" />
53
- </Table.Header>
54
- {viewData(info)}
55
- {viewData(meta)}
56
- </Table>
57
- </div>
58
- );
59
- }
12
+ // TODO: solution for multiple measurements
13
+ const { meta, info } = measurement.data[0];
60
14
 
61
- /**
62
- * Get the value cell depending on the type of the value
63
- * @param value - ValueRenderers value.
64
- * @returns - ValueRenderers component.
65
- */
66
- function valueCell(value: number | string | object) {
67
- switch (typeof value) {
68
- case 'number':
69
- return <ValueRenderers.Number value={value} />;
70
- case 'object':
71
- return <ValueRenderers.Object value={value} />;
72
- case 'string':
73
- return <ValueRenderers.Text value={value} />;
74
- default:
75
- <ValueRenderers.Text value={value} />;
76
- }
77
- }
15
+ const data: InfoPanelData[] = [
16
+ {
17
+ description: 'Information',
18
+ data: info,
19
+ },
20
+ {
21
+ description: 'Metadata',
22
+ data: meta,
23
+ },
24
+ ];
78
25
 
79
- /**
80
- * Search a string in different type of values
81
- *
82
- * @param value - Value to search in.
83
- * @param search - Value to search for.
84
- * @returns - If search exist in value
85
- */
86
- function valueSearch(value: number | string | object, search: string): boolean {
87
- switch (typeof value) {
88
- case 'number':
89
- return String(value).includes(search.toLowerCase());
90
- case 'object':
91
- return JSON.stringify(value).toLowerCase().includes(search.toLowerCase());
92
- case 'string':
93
- return value.toLowerCase().includes(search.toLowerCase());
94
- default:
95
- return false;
96
- }
26
+ return <InfoPanel data={data} title="" />;
97
27
  }
@@ -1,7 +1,12 @@
1
1
  import { assertNotNull } from '../../../components/index';
2
2
  import type { AppState, AppView } from '../index';
3
3
 
4
- import type { AppData, MeasurementKind, Measurements } from './AppData';
4
+ import type {
5
+ AppData,
6
+ MeasurementBase,
7
+ MeasurementKind,
8
+ Measurements,
9
+ } from './AppData';
5
10
  import { measurementKinds } from './kinds';
6
11
 
7
12
  export function getMeasurement(
@@ -46,20 +51,27 @@ export function getFirstMeasurementOrFail<Kind extends MeasurementKind>(
46
51
  export function getCurrentMeasurement(state: AppState) {
47
52
  const selectedMeasurement = getSelectedMeasurement(state.view);
48
53
  if (!selectedMeasurement) return null;
49
-
50
- return getMeasurement(
51
- state.data.measurements,
52
- selectedMeasurement.kind,
53
- selectedMeasurement.id,
54
- );
54
+ const measurements: MeasurementBase[] = [];
55
+ for (const id of selectedMeasurement.ids) {
56
+ const measurement = getMeasurement(
57
+ state.data.measurements,
58
+ selectedMeasurement.kind,
59
+ id,
60
+ );
61
+ if (!measurement) return null;
62
+ measurements.push(measurement);
63
+ }
64
+ return measurements;
55
65
  }
56
66
 
57
67
  export function getCurrentMeasurementData(state: AppState) {
58
68
  const selectedMeasurement = getCurrentMeasurement(state);
59
69
  if (!selectedMeasurement) return null;
60
- const kindAndId = getMeasurementKindAndId(state.data, selectedMeasurement.id);
61
- const display = state.view.measurements[selectedMeasurement.id];
62
- return { data: selectedMeasurement, display, kindAndId };
70
+ const kindAndIds = getMeasurementKindAndIds(state.data, selectedMeasurement);
71
+ const display = selectedMeasurement.map(
72
+ ({ id }) => state.view.measurements[id],
73
+ );
74
+ return { data: selectedMeasurement, display, kindAndIds };
63
75
  }
64
76
 
65
77
  export function getFirstSelectedMeasurementData(state: AppState) {
@@ -91,7 +103,10 @@ export interface MeasurementKindAndId {
91
103
  kind: MeasurementKind;
92
104
  id: string;
93
105
  }
94
-
106
+ export interface MeasurementKindAndIds {
107
+ kind: MeasurementKind;
108
+ ids: string[];
109
+ }
95
110
  export function getMeasurementKindAndId(data: AppData, measurementId: string) {
96
111
  for (const kind of measurementKinds) {
97
112
  const measurement = getMeasurement(data.measurements, kind, measurementId);
@@ -99,17 +114,35 @@ export function getMeasurementKindAndId(data: AppData, measurementId: string) {
99
114
  }
100
115
  throw new Error(`Measurement kind for ${measurementId} not found`);
101
116
  }
102
-
117
+ export function getMeasurementKindAndIds(
118
+ data: AppData,
119
+ measurementId: MeasurementBase[],
120
+ ) {
121
+ let found = false;
122
+ for (const kind of measurementKinds) {
123
+ for (const { id } of measurementId) {
124
+ const measurement = getMeasurement(data.measurements, kind, id);
125
+ if (measurement) found = true;
126
+ if (found && !measurement) {
127
+ throw new Error(
128
+ `Measurement kind for ${measurementId.join(', ')} not found`,
129
+ );
130
+ }
131
+ }
132
+ if (found) return { kind, ids: measurementId };
133
+ }
134
+ throw new Error(`Measurement kind for ${measurementId.join(', ')} not found`);
135
+ }
103
136
  export function getSelectedMeasurement(
104
137
  view: AppView,
105
- ): MeasurementKindAndId | null {
138
+ ): MeasurementKindAndIds | null {
106
139
  const { selectedKind, selectedMeasurements } = view;
107
140
  if (!selectedKind) return null;
108
141
  const kind = selectedKind;
109
142
  const currentMeasurements = selectedMeasurements[kind];
110
- if (!currentMeasurements || currentMeasurements.length !== 1) return null;
111
- const id = currentMeasurements[0];
112
- return { kind, id };
143
+ if (!currentMeasurements || currentMeasurements.length === 0) return null;
144
+ const ids = currentMeasurements;
145
+ return { kind, ids };
113
146
  }
114
147
 
115
148
  export function getSelectedMeasurementOrFail(view: AppView) {
@@ -4,6 +4,8 @@ import * as RadixCheckbox from '@radix-ui/react-checkbox';
4
4
  import { ReactNode } from 'react';
5
5
  import { RxCheck, RxMinus } from 'react-icons/rx/index';
6
6
 
7
+ import { disabledColor, enabledColor } from './styles';
8
+
7
9
  export type CheckedState = boolean | 'indeterminate';
8
10
 
9
11
  interface CheckboxProps {
@@ -13,9 +15,6 @@ interface CheckboxProps {
13
15
  onChange?: (checked: CheckedState) => void;
14
16
  }
15
17
 
16
- const enabledColor = '#1677ff';
17
- const disabledColor = '#b8b8b8';
18
-
19
18
  export function Checkbox(props: CheckboxProps) {
20
19
  const {
21
20
  checked = 'indeterminate',