Skip to content

Commit b6eaf5a

Browse files
authored
feat: support Plates with other coordinate systems then 96 wells (#281)
BREAKING CHANGE: require explicit coordinate system value and type instead of implicit 96 well
1 parent dc7e6be commit b6eaf5a

13 files changed

+327
-164
lines changed

src/ImageMap/index.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Story } from '@storybook/react';
22
import React from 'react';
33

44
import { Plate } from '../Plate';
5+
import { COORDINATE_SYSTEM_96_WELL } from '../Plate/coordinateSystem96Well';
56
import { TecanLayout } from '../index';
67

78
import { HiddenArea } from './HiddenArea';
@@ -22,6 +23,7 @@ export const TecanLayoutExample: Story = function Default() {
2223
content: (
2324
<Plate
2425
data={[{ coordinates: { row: 'A', column: 3 }, content: 'test' }]}
26+
coordinateSystem={COORDINATE_SYSTEM_96_WELL}
2527
/>
2628
),
2729
},
@@ -54,6 +56,7 @@ export const Default: Story<ImageMapProps> = function Default(args) {
5456
content={
5557
<Plate
5658
data={[{ coordinates: { row: 'A', column: 3 }, content: 'test' }]}
59+
coordinateSystem={COORDINATE_SYSTEM_96_WELL}
5760
/>
5861
}
5962
/>

src/Plate/EmptyWell.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,31 @@ import React from 'react';
44
import { PALETTE } from '../theme';
55

66
import { PLATE_FLOW } from './constants';
7+
import { CoordinateSystem } from './types';
78
import { columnForPosition, rowForPosition } from './utils';
89
import { GENERAL_WELL_STYLE } from './wellUtils';
910

10-
export function EmptyWell(props: { position: number }) {
11+
export function EmptyWell<TCoordinateSystem extends CoordinateSystem>(props: {
12+
position: number;
13+
coordinateSystem: TCoordinateSystem;
14+
}) {
15+
const row = rowForPosition(
16+
props.position,
17+
PLATE_FLOW,
18+
props.coordinateSystem,
19+
);
20+
const column = columnForPosition(
21+
props.position,
22+
PLATE_FLOW,
23+
props.coordinateSystem,
24+
);
25+
1126
const { setNodeRef, isOver } = useDroppable({
1227
id: props.position,
1328
data: {
1429
coordinates: {
15-
row: rowForPosition(props.position, PLATE_FLOW),
16-
column: columnForPosition(props.position, PLATE_FLOW),
30+
row,
31+
column,
1732
},
1833
},
1934
});
@@ -30,10 +45,7 @@ export function EmptyWell(props: { position: number }) {
3045
alignItems: 'center',
3146
}}
3247
>
33-
<small>
34-
{rowForPosition(props.position, PLATE_FLOW) +
35-
columnForPosition(props.position, PLATE_FLOW)}
36-
</small>
48+
<small>{row + column}</small>
3749
</div>
3850
);
3951
}

src/Plate/FilledWell.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ import React from 'react';
55
import { PALETTE } from '../theme';
66

77
import { PLATE_FLOW } from './constants';
8-
import { PlateWell } from './types';
8+
import { CoordinateSystem, PlateWell } from './types';
99
import { columnForPosition, rowForPosition } from './utils';
1010
import { GENERAL_WELL_STYLE } from './wellUtils';
1111

12-
export function FilledWell(props: {
13-
well: PlateWell;
12+
export function FilledWell<TCoordinateSystem extends CoordinateSystem>(props: {
13+
well: PlateWell<TCoordinateSystem>;
14+
coordinateSystem: TCoordinateSystem;
1415
position: number;
1516
isDraggable: boolean;
1617
}) {
1718
const data = {
1819
coordinates: {
19-
row: rowForPosition(props.position, PLATE_FLOW),
20-
column: columnForPosition(props.position, PLATE_FLOW),
20+
row: rowForPosition(props.position, PLATE_FLOW, props.coordinateSystem),
21+
column: columnForPosition(
22+
props.position,
23+
PLATE_FLOW,
24+
props.coordinateSystem,
25+
),
2126
},
2227
};
2328

src/Plate/RowLabel.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React from 'react';
22

33
import { PLATE_FLOW } from './constants';
4+
import { CoordinateSystem } from './types';
45
import { rowForPosition } from './utils';
56

6-
export function RowLabel(props: { position: number }) {
7+
export function RowLabel(props: {
8+
position: number;
9+
coordinateSystem: CoordinateSystem;
10+
}) {
711
return (
812
<span
913
style={{
@@ -12,7 +16,9 @@ export function RowLabel(props: { position: number }) {
1216
alignItems: 'center',
1317
}}
1418
>
15-
<strong>{rowForPosition(props.position, PLATE_FLOW)}</strong>
19+
<strong>
20+
{rowForPosition(props.position, PLATE_FLOW, props.coordinateSystem)}
21+
</strong>
1622
</span>
1723
);
1824
}

src/Plate/Well.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1+
import { Maybe } from '@mll-lab/js-utils';
12
import React from 'react';
23

34
import { EmptyWell } from './EmptyWell';
45
import { FilledWell } from './FilledWell';
5-
import { PlateWell } from './types';
6+
import { CoordinateSystem, PlateWell } from './types';
67

7-
export function Well(props: {
8+
export function Well<TCoordinateSystem extends CoordinateSystem>(props: {
89
position: number;
9-
well?: PlateWell;
10+
well: Maybe<PlateWell<TCoordinateSystem>>;
11+
coordinateSystem: TCoordinateSystem;
1012
isDraggable: boolean;
1113
}) {
1214
return props.well?.content ? (
1315
<FilledWell
1416
well={props.well}
17+
coordinateSystem={props.coordinateSystem}
1518
position={props.position}
1619
isDraggable={props.isDraggable}
1720
/>
1821
) : (
19-
<EmptyWell position={props.position} />
22+
<EmptyWell
23+
position={props.position}
24+
coordinateSystem={props.coordinateSystem}
25+
/>
2026
);
2127
}

src/Plate/constants.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
1-
import { range } from 'lodash';
1+
import { FlowDirection } from './types';
22

3-
import { Coordinates, FlowDirection } from './types';
4-
5-
const TUBE_COUNT = 96;
6-
export const WELLS = range(1, TUBE_COUNT + 1);
7-
export const COORDINATES_COLUMNS = [
8-
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
9-
] as const satisfies ReadonlyArray<Coordinates['column']>;
10-
export const COORDINATES_ROWS = [
11-
'A',
12-
'B',
13-
'C',
14-
'D',
15-
'E',
16-
'F',
17-
'G',
18-
'H',
19-
] as const satisfies ReadonlyArray<Coordinates['row']>;
3+
/** Used internally when rendering the Plate component. */
204
export const PLATE_FLOW = 'row' as const satisfies FlowDirection;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { CoordinateSystem } from './types';
2+
3+
export const COORDINATE_SYSTEM_96_WELL = {
4+
rows: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
5+
columns: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
6+
} as const satisfies CoordinateSystem;
7+
8+
export type CoordinateSystem96Well = typeof COORDINATE_SYSTEM_96_WELL;

src/Plate/index.stories.tsx

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import React from 'react';
44

55
import { PALETTE } from '../theme';
66

7-
import { COORDINATES_COLUMNS, COORDINATES_ROWS, WELLS } from './constants';
7+
import {
8+
COORDINATE_SYSTEM_96_WELL,
9+
CoordinateSystem96Well,
10+
} from './coordinateSystem96Well';
811
import { PlateProps, PlateWell } from './types';
9-
import { coordinatesForPosition } from './utils';
12+
import { allCoordinateSystemPositions, coordinatesForPosition } from './utils';
1013

1114
import { Plate } from './index';
1215

@@ -18,22 +21,34 @@ export default {
1821
},
1922
};
2023

21-
const data: Array<PlateWell> = [
24+
const data: Array<PlateWell<CoordinateSystem96Well>> = [
2225
{
23-
coordinates: { row: COORDINATES_ROWS[0], column: COORDINATES_COLUMNS[6] },
26+
coordinates: {
27+
row: COORDINATE_SYSTEM_96_WELL.rows[0],
28+
column: COORDINATE_SYSTEM_96_WELL.columns[6],
29+
},
2430
content: <i>It renders any ReactNode</i>,
2531
},
2632
{
27-
coordinates: { row: COORDINATES_ROWS[0], column: COORDINATES_COLUMNS[7] },
33+
coordinates: {
34+
row: COORDINATE_SYSTEM_96_WELL.rows[0],
35+
column: COORDINATE_SYSTEM_96_WELL.columns[7],
36+
},
2837
content: 'Test',
2938
color: PALETTE.red,
3039
},
3140
{
32-
coordinates: { row: COORDINATES_ROWS[1], column: COORDINATES_COLUMNS[2] },
41+
coordinates: {
42+
row: COORDINATE_SYSTEM_96_WELL.rows[1],
43+
column: COORDINATE_SYSTEM_96_WELL.columns[2],
44+
},
3345
content: 'Some text',
3446
},
3547
{
36-
coordinates: { row: COORDINATES_ROWS[2], column: COORDINATES_COLUMNS[2] },
48+
coordinates: {
49+
row: COORDINATE_SYSTEM_96_WELL.rows[2],
50+
column: COORDINATE_SYSTEM_96_WELL.columns[2],
51+
},
3752
content: (
3853
<>
3954
<p>Kontrolle</p>
@@ -45,27 +60,39 @@ const data: Array<PlateWell> = [
4560
},
4661
];
4762

48-
const rowFlowData: Array<PlateWell> = WELLS.map((well) => ({
49-
coordinates: coordinatesForPosition(well, 'row'),
50-
content: well,
51-
}));
63+
const COORDINATE_SYSTEM_96_WELL_POSITIONS = allCoordinateSystemPositions(
64+
COORDINATE_SYSTEM_96_WELL,
65+
);
5266

53-
const columnFlowData: Array<PlateWell> = WELLS.map((well) => ({
54-
coordinates: coordinatesForPosition(well, 'column'),
55-
content: well,
56-
}));
67+
const rowFlowData: Array<PlateWell<CoordinateSystem96Well>> =
68+
COORDINATE_SYSTEM_96_WELL_POSITIONS.map((well) => ({
69+
coordinates: coordinatesForPosition(well, 'row', COORDINATE_SYSTEM_96_WELL),
70+
content: well,
71+
}));
5772

58-
const Template: Story<Partial<PlateProps>> = function Template(args) {
59-
return (
60-
<Plate
61-
data={null}
62-
dndContextProps={{
63-
onDragEnd: action('onDragEnd'), // dataLocation: `const sourceData = e.active.data.current; const targetData = e.over?.data.current;`
64-
}}
65-
{...args}
66-
/>
67-
);
68-
};
73+
const columnFlowData: Array<PlateWell<CoordinateSystem96Well>> =
74+
COORDINATE_SYSTEM_96_WELL_POSITIONS.map((well) => ({
75+
coordinates: coordinatesForPosition(
76+
well,
77+
'column',
78+
COORDINATE_SYSTEM_96_WELL,
79+
),
80+
content: well,
81+
}));
82+
83+
const Template: Story<Partial<PlateProps<CoordinateSystem96Well>>> =
84+
function Template(args) {
85+
return (
86+
<Plate
87+
data={null}
88+
coordinateSystem={COORDINATE_SYSTEM_96_WELL}
89+
dndContextProps={{
90+
onDragEnd: action('onDragEnd'), // dataLocation: `const sourceData = e.active.data.current; const targetData = e.over?.data.current;`
91+
}}
92+
{...args}
93+
/>
94+
);
95+
};
6996

7097
export const Default = Template.bind({});
7198
Default.args = {

src/Plate/index.test.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { render } from '@testing-library/react';
22
import React from 'react';
33

4+
import { COORDINATE_SYSTEM_96_WELL } from './coordinateSystem96Well';
5+
46
import { Plate } from './index';
57

68
describe('Plate', () => {
79
it('renders without data', () => {
8-
render(<Plate data={null} />);
10+
render(<Plate data={null} coordinateSystem={COORDINATE_SYSTEM_96_WELL} />);
911
});
1012
});

0 commit comments

Comments
 (0)