Skip to content

Commit

Permalink
feat: prediction tables dialogs, alleles table
Browse files Browse the repository at this point in the history
  • Loading branch information
trevorpfiz committed Oct 15, 2024
1 parent 690533a commit e96d6d3
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 5 deletions.
32 changes: 32 additions & 0 deletions apps/nextjs/src/components/predictions/tables/alleles-columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";

import type { ColumnDef } from "@tanstack/react-table";

import { DataTableColumnHeader } from "~/components/predictions/tables/prediction-column-header";

interface AlleleData {
HLA_Allele: string;
MHC_Binding_Affinity: string;
pMHC_Stability: string;
}

export const allelesColumns: ColumnDef<AlleleData>[] = [
{
accessorKey: "HLA_Allele",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="HLA Allele" />
),
},
{
accessorKey: "MHC_Binding_Affinity",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="MHC Binding Affinity" />
),
},
{
accessorKey: "pMHC_Stability",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="pMHC Stability" />
),
},
];
120 changes: 120 additions & 0 deletions apps/nextjs/src/components/predictions/tables/alleles-data-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"use client";

import type {
ColumnDef,
SortingState,
VisibilityState,
} from "@tanstack/react-table";
import { useState } from "react";
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";

import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@epi/ui/table";

import { DataTablePagination } from "~/components/peptides/tables/similarity-pagination";

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}

function AllelesDataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});

const table = useReactTable({
data,
columns,
state: {
sorting,
columnVisibility,
},
onSortingChange: setSorting,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
debugTable: false,
});

return (
<div className="space-y-4">
{/* Data Table */}
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder ? null : (
<div
className={
header.column.getCanSort()
? "flex items-center gap-2"
: ""
}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</div>
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length > 0 ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>

{/* Pagination Controls */}
<DataTablePagination table={table} />
</div>
);
}

export { AllelesDataTable };
15 changes: 13 additions & 2 deletions apps/nextjs/src/components/predictions/tables/mhc-i-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@

import type { ColumnDef } from "@tanstack/react-table";

import type { MhcIResult } from "@epi/validators/epitopes";
import type { MhcIResult as BaseMhcIResult } from "@epi/validators/epitopes";

import type { MhcIResult } from "~/components/predictions/tables/mhc-peptide-dialog-cell";
import { MhcPeptideDialogCell } from "~/components/predictions/tables/mhc-peptide-dialog-cell";
import { DataTableColumnHeader } from "~/components/predictions/tables/prediction-column-header";

export const mhcIColumns: ColumnDef<MhcIResult>[] = [
export const mhcIColumns: ColumnDef<BaseMhcIResult>[] = [
{
accessorKey: "Peptide_Sequence",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Peptide Sequence" />
),
cell: ({ row }) => {
const rowData = row.original;
// add type to rowData
const rowDataWithType: MhcIResult = {
...rowData,
type: "MHC-I",
};
return <MhcPeptideDialogCell rowData={rowDataWithType} />;
},
},
{
accessorKey: "ClassI_TCR_Recognition",
Expand Down
17 changes: 14 additions & 3 deletions apps/nextjs/src/components/predictions/tables/mhc-ii-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,30 @@

import type { ColumnDef } from "@tanstack/react-table";

import type { MhcIIResult } from "@epi/validators/epitopes";
import type { MhcIIResult as BaseMhcIIResult } from "@epi/validators/epitopes";

import type { MhcIIResult } from "~/components/predictions/tables/mhc-peptide-dialog-cell";
import { MhcPeptideDialogCell } from "~/components/predictions/tables/mhc-peptide-dialog-cell";
import { DataTableColumnHeader } from "~/components/predictions/tables/prediction-column-header";

export const mhcIIColumns: ColumnDef<MhcIIResult>[] = [
export const mhcIIColumns: ColumnDef<BaseMhcIIResult>[] = [
{
accessorKey: "Peptide_Sequence",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Peptide Sequence" />
),
cell: ({ row }) => {
const rowData = row.original;
// add type to rowData
const rowDataWithType: MhcIIResult = {
...rowData,
type: "MHC-II",
};
return <MhcPeptideDialogCell rowData={rowDataWithType} />;
},
},
{
accessorKey: "ClassI_TCR_Recognition",
accessorKey: "ClassII_TCR_Recognition",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="TCR Recognition" />
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useMemo, useState } from "react";

import type {
MhcIIResult as BaseMhcIIResult,
MhcIResult as BaseMhcIResult,
} from "@epi/validators/epitopes";
import { Button } from "@epi/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@epi/ui/dialog";

import { allelesColumns } from "~/components/predictions/tables/alleles-columns";
import { AllelesDataTable } from "~/components/predictions/tables/alleles-data-table";
import { parseAlleleData } from "~/lib/utils";

export enum PeptideFields {
TCR_RECOGNITION_I = "ClassI_TCR_Recognition",
MHC_BINDING_AFFINITY_I = "ClassI_MHC_Binding_Affinity",
PMHC_STABILITY_I = "ClassI_pMHC_Stability",

TCR_RECOGNITION_II = "ClassII_TCR_Recognition",
MHC_BINDING_AFFINITY_II = "ClassII_MHC_Binding_Affinity",
PMHC_STABILITY_II = "ClassII_pMHC_Stability",
}

// Extend the existing MhcIResult with a discriminator
export type MhcIResult = BaseMhcIResult & {
type: "MHC-I";
};

// Extend the existing MhcIIResult with a discriminator
export type MhcIIResult = BaseMhcIIResult & {
type: "MHC-II";
};

interface PeptideDialogProps {
rowData: MhcIResult | MhcIIResult;
}

export function MhcPeptideDialogCell({ rowData }: PeptideDialogProps) {
const { Peptide_Sequence, Best_Binding_Affinity, Best_pMHC_Stability } =
rowData;

const [open, setOpen] = useState(false);

// Extract values based on whether the rowData is MHC-I or MHC-II
let tcrRecognition: number;
let mhcBindingAffinity: string;
let pmhcStability: string;

if (rowData.type === "MHC-I") {
tcrRecognition = rowData.ClassI_TCR_Recognition;
mhcBindingAffinity = rowData.ClassI_MHC_Binding_Affinity;
pmhcStability = rowData.ClassI_pMHC_Stability;
} else {
tcrRecognition = rowData.ClassII_TCR_Recognition;
mhcBindingAffinity = rowData.ClassII_MHC_Binding_Affinity;
pmhcStability = rowData.ClassII_pMHC_Stability;
}

const alleleData = useMemo(
() => parseAlleleData(mhcBindingAffinity, pmhcStability),
[mhcBindingAffinity, pmhcStability],
);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
variant="link"
className="items-center justify-start p-0 text-blue-500"
>
{Peptide_Sequence}
</Button>
</DialogTrigger>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>{Peptide_Sequence}</DialogTitle>
<DialogDescription>
<dl>
<div className="flex gap-1">
<dt>TCR Recognition:</dt>
<dd>{tcrRecognition}</dd>
</div>
<div className="flex gap-1">
<dt>Best Binding Affinity:</dt>
<dd>{Best_Binding_Affinity}</dd>
</div>
<div className="flex gap-1">
<dt>Best pMHC Stability:</dt>
<dd>{Best_pMHC_Stability}</dd>
</div>
</dl>
</DialogDescription>
</DialogHeader>

<div className="gap-4 pt-4">
{/* Alleles Table */}
<div>
<AllelesDataTable columns={allelesColumns} data={alleleData} />
</div>
</div>
</DialogContent>
</Dialog>
);
}
30 changes: 30 additions & 0 deletions apps/nextjs/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,33 @@ export const getColorFromScore = (
return rgbToHex(interpolatedColor);
}
};

export function parseAlleleData(
bindingAffinity: string,
stability: string,
): {
HLA_Allele: string;
MHC_Binding_Affinity: string;
pMHC_Stability: string;
}[] {
const bindingAffinityEntries = bindingAffinity.split("|").map((entry) => {
const [allele, value] = entry.split("=");
return { HLA_Allele: allele ?? "", MHC_Binding_Affinity: value ?? "" };
});

const stabilityEntries = stability.split("|").map((entry) => {
const [allele, value] = entry.split("=");
return { HLA_Allele: allele, pMHC_Stability: value };
});

// Merge by allele
return bindingAffinityEntries.map((bindingEntry) => {
const matchingStability = stabilityEntries.find(
(stabilityEntry) => stabilityEntry.HLA_Allele === bindingEntry.HLA_Allele,
);
return {
...bindingEntry,
pMHC_Stability: matchingStability?.pMHC_Stability ?? "N/A",
};
});
}

0 comments on commit e96d6d3

Please sign in to comment.