nitro-bio / nitro-ui

https://storybook.nitro.bio/
MIT License
17 stars 3 forks source link

Plate Annotations #54

Closed ninjha01 closed 1 month ago

ninjha01 commented 3 months ago

Plate Annotations

We want to add support for annotations on the plate. There are three types of annotations we want to support:

  1. Column Annotations
  2. Row Annotations
  3. Well Annotations
export type PlateAnnotation = {
  label: string;
  id: string;
};
export type RowAnnotation = {
        rows: number[];
        annotation: string;
            className?: string;
    }
export type ColumnAnnotation =  {
        columns: number[];
        annotation: string;
                className?: string;

    }

export type WellAnnotation = {
        wells: number;
        annotation: string;
                className?: string;
    }

export const Plate = ({
  ...
  columnAnnotations,
  rowAnnotations,
  wellAnnotations,
  ...
}: {
    ...
  columnAnnotations: ColumnAnnotation[];
  rowAnnotations: RowAnnotation[];
  wellAnnotations: WellAnnotation[];
    ...

}) => {

We will need to update the rendering logic to support these annotations. ColumnAnnotations and RowAnnotations should be rendered on the plate, in a gutter on the left and top respectively. WellAnnotations should be rendered on the well itself. It should be a dashed border around the well with the annotation text inside. If multiple annotations are present, they should be rendered in a stack. Eventually, we will want to add a UI for adding and removing annotations, but that can be done in a future PR.

After opening a PR, create a new story in the Plate.stories.tsx file to demonstrate the new functionality. Then, add the new functionality to the Plate component.

rmcl commented 1 month ago

What do you think about consolidating these into one prop- "annotations" and have a union type? It feels like it will be a bit annoying for the consuming developer to have to wrangle three lists.

I think we might also want to add the "id" and "label" props

so something like:

export type RowAnnotation = {
    rows: number[];
    id: string;
    label: string;
    className?: string;
}

export type ColumnAnnotation = {
    columns: number[];
    id: string;
    label: string;
    className?: string;
}

export type WellAnnotation = {
    wells: number;
    id: string;
    label: string;
    className?: string;
}

export type PlateAnnotation = RowAnnotation | ColumnAnnotation | WellAnnotation;

export const Plate = ({
  ...
  annotations,
  ...
}: {
    ...
  annotations: PlateAnnotation[];
    ...

}) => {

We can then have utility functions to differentiate like this:

function isRowAnnotation(annotation: PlateAnnotation): annotation is RowAnnotation {
    return (annotation as RowAnnotation).rows !== undefined;
}

function isColumnAnnotation(annotation: PlateAnnotation): annotation is ColumnAnnotation {
    return (annotation as ColumnAnnotation).columns !== undefined;
}

function isWellAnnotation(annotation: PlateAnnotation): annotation is WellAnnotation {
    return (annotation as WellAnnotation).wells !== undefined;
}

// Example usage
const exampleAnnotation: PlateAnnotation = {
    wells: 10,
    id: "well-1",
    label: "Well 1"
};

if (isRowAnnotation(exampleAnnotation)) {
    console.log("It's a RowAnnotation", exampleAnnotation.rows);
} else if (isColumnAnnotation(exampleAnnotation)) {
    console.log("It's a ColumnAnnotation", exampleAnnotation.columns);
} else if (isWellAnnotation(exampleAnnotation)) {
    console.log("It's a WellAnnotation", exampleAnnotation.wells);
}

Just a thought. Let me know if I misunderstood

ninjha01 commented 1 month ago

I like this idea - but for the first pass I'd like to keep the api a bit more rigid. I'm still not entirely sure that this is the right data model for annotations, and my hunch is it'll be easier to change the component if we have three separate concepts for annotations to start with. Does that make sense?