Skip to content

Commit 485b3a7

Browse files
committed
ENH: add attribution to molecular drawings
1 parent 01c9b94 commit 485b3a7

4 files changed

Lines changed: 50 additions & 6 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react";
2+
import Typography from "@mui/material/Typography";
3+
import type { SxProps, Theme } from "@mui/material/styles";
4+
5+
// Neither version is importable at runtime -- smiles-drawer's package.json is blocked
6+
// by its own "exports" field, and RDKit drawings are produced server-side (see
7+
// gui/src/server/routes/rules.py). Both are kept in sync by hand with the resolved
8+
// version in package-lock.json and pyproject.toml's `rdkit==` pin, respectively.
9+
const RDKIT_VERSION = "2025.9.1";
10+
const SMILES_DRAWER_VERSION = "2.4.1";
11+
12+
type DrawingLibrary = "smiles-drawer" | "rdkit";
13+
14+
const LIBRARY_LABEL: Record<DrawingLibrary, string> = {
15+
"smiles-drawer": `SmilesDrawer v${SMILES_DRAWER_VERSION}`,
16+
rdkit: `RDKit v${RDKIT_VERSION}`,
17+
};
18+
19+
// A single caption attributing one or more structure/reaction drawings above it to the
20+
// library that rendered them. Place once per diagram -- if a diagram contains multiple
21+
// drawings from the same library (e.g. a compound plus its reconstructions), attribute
22+
// the whole group once rather than repeating this per drawing.
23+
export const DrawingAttribution: React.FC<{ library: DrawingLibrary; sx?: SxProps<Theme> }> = ({ library, sx }) => {
24+
return (
25+
<Typography
26+
variant="caption"
27+
color="text.secondary"
28+
sx={{ display: "block", fontStyle: "italic", ...sx }}
29+
>
30+
Drawn with {LIBRARY_LABEL[library]}
31+
</Typography>
32+
);
33+
};

gui/src/client/src/components/MotifHoverCard.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useQuery } from "@tanstack/react-query";
77
import { fetchMotifStructures } from "../features/motifs/api";
88
import { MotifName } from "./MotifName";
99
import SmilesDrawerContainer from "./SmilesDrawerContainer.js";
10+
import { DrawingAttribution } from "./DrawingAttribution";
1011

1112
const DRAWING_SIZE = 100;
1213

@@ -38,7 +39,10 @@ function MotifHoverContent({ name, hint }: { name: string; hint?: string }) {
3839
return (
3940
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 0.5, maxWidth: 200, py: 0.5 }}>
4041
{smiles ? (
41-
<SmilesDrawerContainer identifier={`motif-hover-${reactId}`} smiles={smiles} size={DRAWING_SIZE} />
42+
<>
43+
<SmilesDrawerContainer identifier={`motif-hover-${reactId}`} smiles={smiles} size={DRAWING_SIZE} />
44+
<DrawingAttribution library="smiles-drawer" sx={{ fontSize: "0.65rem" }} />
45+
</>
4246
) : (
4347
<Box
4448
sx={{

gui/src/client/src/components/workspace/DialogViewItem.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { DialogWindow } from "../DialogWindow";
1414
import { ErrorBoundary } from "../ErrorBoundary";
1515
import { ExportImageButton } from "../ExportImageButton";
1616
import SmilesDrawerContainer from "../SmilesDrawerContainer.js";
17+
import { DrawingAttribution } from "../DrawingAttribution";
1718
import { PrimarySequenceRows, usePrimarySequenceEditor } from "./PrimarySequenceEditor";
1819
import { ClusterReadoutRows } from "./ClusterReadoutRows";
1920

@@ -440,6 +441,7 @@ export const DialogViewItem: React.FC<DialogViewItemProps> = ({
440441
</>
441442
)}
442443
</Box>
444+
<DrawingAttribution library="smiles-drawer" sx={{ textAlign: "center" }} />
443445
{hasReconstructions && (
444446
<DescriptionBox
445447
title={'Explanation'}

gui/src/client/src/components/workspace/WorkspaceRules.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { MinimalIconButton } from "../MinimalIconButton";
2222
import { MotifName } from "../MotifName";
2323
import { horizontalScrollSx } from "../../theme/scrollbarSx";
2424
import SmilesDrawerContainer from "../SmilesDrawerContainer.js";
25+
import { DrawingAttribution } from "../DrawingAttribution";
2526
import { ReactionScheme } from "./ReactionScheme";
2627

2728
const STRUCTURE_SIZE = 130;
@@ -95,11 +96,14 @@ function MatchingRuleRow({ rule }: { rule: MatchingRule }) {
9596

9697
<Collapse in={expanded} unmountOnExit>
9798
<Stack direction="row" spacing={2} sx={{ mt: 1, pl: 1 }}>
98-
<SmilesDrawerContainer
99-
identifier={`matching-rule-${rule.id}`}
100-
smiles={rule.displaySmiles || rule.smiles}
101-
size={STRUCTURE_SIZE}
102-
/>
99+
<Box>
100+
<SmilesDrawerContainer
101+
identifier={`matching-rule-${rule.id}`}
102+
smiles={rule.displaySmiles || rule.smiles}
103+
size={STRUCTURE_SIZE}
104+
/>
105+
<DrawingAttribution library="smiles-drawer" />
106+
</Box>
103107

104108
<Stack spacing={1} sx={{ flex: 1, minWidth: 0 }}>
105109
<Box>
@@ -195,6 +199,7 @@ function ReactionRuleRow({ rule }: { rule: ReactionRule }) {
195199
<Box sx={{ ...horizontalScrollSx, pb: 1 }}>
196200
<ReactionScheme id={rule.id} smarts={rule.smarts} />
197201
</Box>
202+
<DrawingAttribution library="rdkit" />
198203
</Box>
199204

200205
<PropsList props={rule.props} />

0 commit comments

Comments
 (0)