react-cheminfo 0.76.0 → 0.77.0
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.
- package/README.md +141 -31
- package/lib/card/core/cardAddress.d.ts +19 -2
- package/lib/card/core/cardAddress.d.ts.map +1 -1
- package/lib/card/core/cardAddress.js +35 -2
- package/lib/card/core/cardAddress.js.map +1 -1
- package/lib/card/core/cardPageUrl.d.ts +7 -1
- package/lib/card/core/cardPageUrl.d.ts.map +1 -1
- package/lib/card/core/cardPageUrl.js +4 -2
- package/lib/card/core/cardPageUrl.js.map +1 -1
- package/lib/card/core/index.d.ts +1 -1
- package/lib/card/core/index.d.ts.map +1 -1
- package/lib/card/core/index.js +1 -1
- package/lib/card/core/index.js.map +1 -1
- package/lib/credits/core/credits.d.ts +30 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +35 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/download/core/downloadFigure.d.ts +12 -1
- package/lib/download/core/downloadFigure.d.ts.map +1 -1
- package/lib/download/core/downloadFigure.js +21 -0
- package/lib/download/core/downloadFigure.js.map +1 -1
- package/lib/download/core/figureDpi.d.ts +59 -0
- package/lib/download/core/figureDpi.d.ts.map +1 -0
- package/lib/download/core/figureDpi.js +75 -0
- package/lib/download/core/figureDpi.js.map +1 -0
- package/lib/download/core/figurePng.d.ts +4 -1
- package/lib/download/core/figurePng.d.ts.map +1 -1
- package/lib/download/core/figurePng.js +11 -2
- package/lib/download/core/figurePng.js.map +1 -1
- package/lib/download/core/index.d.ts +4 -1
- package/lib/download/core/index.d.ts.map +1 -1
- package/lib/download/core/index.js +3 -1
- package/lib/download/core/index.js.map +1 -1
- package/lib/download/core/pngDpi.d.ts +22 -0
- package/lib/download/core/pngDpi.d.ts.map +1 -0
- package/lib/download/core/pngDpi.js +79 -0
- package/lib/download/core/pngDpi.js.map +1 -0
- package/lib/download/ui/FigureDownloadPanel.d.ts +13 -0
- package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.js +18 -34
- package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
- package/lib/download/ui/figureDownloadHint.d.ts +5 -0
- package/lib/download/ui/figureDownloadHint.d.ts.map +1 -1
- package/lib/download/ui/figureDownloadHint.js +8 -0
- package/lib/download/ui/figureDownloadHint.js.map +1 -1
- package/lib/download/ui/figureDownloadOptions.d.ts +84 -0
- package/lib/download/ui/figureDownloadOptions.d.ts.map +1 -0
- package/lib/download/ui/figureDownloadOptions.js +2 -0
- package/lib/download/ui/figureDownloadOptions.js.map +1 -0
- package/lib/download/ui/resolutionChoices.d.ts +27 -0
- package/lib/download/ui/resolutionChoices.d.ts.map +1 -0
- package/lib/download/ui/resolutionChoices.js +48 -0
- package/lib/download/ui/resolutionChoices.js.map +1 -0
- package/lib/download/ui/useFigureActions.d.ts +4 -1
- package/lib/download/ui/useFigureActions.d.ts.map +1 -1
- package/lib/download/ui/useFigureActions.js +17 -8
- package/lib/download/ui/useFigureActions.js.map +1 -1
- package/lib/download/ui/useFigureDownload.d.ts +2 -55
- package/lib/download/ui/useFigureDownload.d.ts.map +1 -1
- package/lib/download/ui/useFigureDownload.js +45 -13
- package/lib/download/ui/useFigureDownload.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +14 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +1 -1
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +3 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/locales/de.json +30 -1
- package/lib/locales/en.json +30 -1
- package/lib/locales/es.json +30 -1
- package/lib/locales/fr.json +30 -1
- package/lib/locales/it.json +30 -1
- package/lib/molecule3d/ui/Molecule3DStage.d.ts +4 -33
- package/lib/molecule3d/ui/Molecule3DStage.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DStage.js +3 -37
- package/lib/molecule3d/ui/Molecule3DStage.js.map +1 -1
- package/lib/osiris/core/barGeometry.d.ts +42 -0
- package/lib/osiris/core/barGeometry.d.ts.map +1 -0
- package/lib/osiris/core/barGeometry.js +46 -0
- package/lib/osiris/core/barGeometry.js.map +1 -0
- package/lib/osiris/core/computeProperties.d.ts +42 -0
- package/lib/osiris/core/computeProperties.d.ts.map +1 -0
- package/lib/osiris/core/computeProperties.js +115 -0
- package/lib/osiris/core/computeProperties.js.map +1 -0
- package/lib/osiris/core/constants.d.ts +70 -0
- package/lib/osiris/core/constants.d.ts.map +1 -0
- package/lib/osiris/core/constants.js +99 -0
- package/lib/osiris/core/constants.js.map +1 -0
- package/lib/osiris/core/fitness.d.ts +32 -0
- package/lib/osiris/core/fitness.d.ts.map +1 -0
- package/lib/osiris/core/fitness.js +48 -0
- package/lib/osiris/core/fitness.js.map +1 -0
- package/lib/osiris/core/index.d.ts +29 -0
- package/lib/osiris/core/index.d.ts.map +1 -0
- package/lib/osiris/core/index.js +19 -0
- package/lib/osiris/core/index.js.map +1 -0
- package/lib/osiris/core/poolSize.d.ts +17 -0
- package/lib/osiris/core/poolSize.d.ts.map +1 -0
- package/lib/osiris/core/poolSize.js +22 -0
- package/lib/osiris/core/poolSize.js.map +1 -0
- package/lib/osiris/core/poolTypes.d.ts +140 -0
- package/lib/osiris/core/poolTypes.d.ts.map +1 -0
- package/lib/osiris/core/poolTypes.js +12 -0
- package/lib/osiris/core/poolTypes.js.map +1 -0
- package/lib/osiris/core/predictIdCode.d.ts +39 -0
- package/lib/osiris/core/predictIdCode.d.ts.map +1 -0
- package/lib/osiris/core/predictIdCode.js +54 -0
- package/lib/osiris/core/predictIdCode.js.map +1 -0
- package/lib/osiris/core/predictProtocol.d.ts +67 -0
- package/lib/osiris/core/predictProtocol.d.ts.map +1 -0
- package/lib/osiris/core/predictProtocol.js +29 -0
- package/lib/osiris/core/predictProtocol.js.map +1 -0
- package/lib/osiris/core/predictionPool.d.ts +23 -0
- package/lib/osiris/core/predictionPool.d.ts.map +1 -0
- package/lib/osiris/core/predictionPool.js +186 -0
- package/lib/osiris/core/predictionPool.js.map +1 -0
- package/lib/osiris/core/predictionQueue.d.ts +46 -0
- package/lib/osiris/core/predictionQueue.d.ts.map +1 -0
- package/lib/osiris/core/predictionQueue.js +57 -0
- package/lib/osiris/core/predictionQueue.js.map +1 -0
- package/lib/osiris/core/predictors.d.ts +62 -0
- package/lib/osiris/core/predictors.d.ts.map +1 -0
- package/lib/osiris/core/predictors.js +100 -0
- package/lib/osiris/core/predictors.js.map +1 -0
- package/lib/osiris/core/propertyKeys.d.ts +32 -0
- package/lib/osiris/core/propertyKeys.d.ts.map +1 -0
- package/lib/osiris/core/propertyKeys.js +44 -0
- package/lib/osiris/core/propertyKeys.js.map +1 -0
- package/lib/osiris/core/riskAppearance.d.ts +60 -0
- package/lib/osiris/core/riskAppearance.d.ts.map +1 -0
- package/lib/osiris/core/riskAppearance.js +59 -0
- package/lib/osiris/core/riskAppearance.js.map +1 -0
- package/lib/osiris/core/riskDetail.d.ts +22 -0
- package/lib/osiris/core/riskDetail.d.ts.map +1 -0
- package/lib/osiris/core/riskDetail.js +86 -0
- package/lib/osiris/core/riskDetail.js.map +1 -0
- package/lib/osiris/core/riskDetailRequest.d.ts +31 -0
- package/lib/osiris/core/riskDetailRequest.d.ts.map +1 -0
- package/lib/osiris/core/riskDetailRequest.js +49 -0
- package/lib/osiris/core/riskDetailRequest.js.map +1 -0
- package/lib/osiris/core/riskFindings.d.ts +26 -0
- package/lib/osiris/core/riskFindings.d.ts.map +1 -0
- package/lib/osiris/core/riskFindings.js +79 -0
- package/lib/osiris/core/riskFindings.js.map +1 -0
- package/lib/osiris/core/runPredictionJob.d.ts +20 -0
- package/lib/osiris/core/runPredictionJob.d.ts.map +1 -0
- package/lib/osiris/core/runPredictionJob.js +43 -0
- package/lib/osiris/core/runPredictionJob.js.map +1 -0
- package/lib/osiris/core/scales.d.ts +70 -0
- package/lib/osiris/core/scales.d.ts.map +1 -0
- package/lib/osiris/core/scales.js +185 -0
- package/lib/osiris/core/scales.js.map +1 -0
- package/lib/osiris/core/types.d.ts +91 -0
- package/lib/osiris/core/types.d.ts.map +1 -0
- package/lib/osiris/core/types.js +19 -0
- package/lib/osiris/core/types.js.map +1 -0
- package/lib/osiris/core/valuation.d.ts +28 -0
- package/lib/osiris/core/valuation.d.ts.map +1 -0
- package/lib/osiris/core/valuation.js +39 -0
- package/lib/osiris/core/valuation.js.map +1 -0
- package/lib/osiris/ui/PropertyBar.d.ts +27 -0
- package/lib/osiris/ui/PropertyBar.d.ts.map +1 -0
- package/lib/osiris/ui/PropertyBar.js +27 -0
- package/lib/osiris/ui/PropertyBar.js.map +1 -0
- package/lib/osiris/ui/PropertyPanel.d.ts +91 -0
- package/lib/osiris/ui/PropertyPanel.d.ts.map +1 -0
- package/lib/osiris/ui/PropertyPanel.js +77 -0
- package/lib/osiris/ui/PropertyPanel.js.map +1 -0
- package/lib/osiris/ui/PropertyRows.d.ts +40 -0
- package/lib/osiris/ui/PropertyRows.d.ts.map +1 -0
- package/lib/osiris/ui/PropertyRows.js +23 -0
- package/lib/osiris/ui/PropertyRows.js.map +1 -0
- package/lib/osiris/ui/RiskFindings.d.ts +43 -0
- package/lib/osiris/ui/RiskFindings.d.ts.map +1 -0
- package/lib/osiris/ui/RiskFindings.js +51 -0
- package/lib/osiris/ui/RiskFindings.js.map +1 -0
- package/lib/osiris/ui/RiskPanel.d.ts +46 -0
- package/lib/osiris/ui/RiskPanel.d.ts.map +1 -0
- package/lib/osiris/ui/RiskPanel.js +32 -0
- package/lib/osiris/ui/RiskPanel.js.map +1 -0
- package/lib/osiris/ui/RiskSquare.d.ts +51 -0
- package/lib/osiris/ui/RiskSquare.d.ts.map +1 -0
- package/lib/osiris/ui/RiskSquare.js +23 -0
- package/lib/osiris/ui/RiskSquare.js.map +1 -0
- package/lib/osiris/ui/index.d.ts +15 -0
- package/lib/osiris/ui/index.d.ts.map +1 -0
- package/lib/osiris/ui/index.js +8 -0
- package/lib/osiris/ui/index.js.map +1 -0
- package/lib/osiris/ui/usePredictions.d.ts +81 -0
- package/lib/osiris/ui/usePredictions.d.ts.map +1 -0
- package/lib/osiris/ui/usePredictions.js +102 -0
- package/lib/osiris/ui/usePredictions.js.map +1 -0
- package/lib/osiris-ui.d.ts +11 -0
- package/lib/osiris-ui.d.ts.map +1 -0
- package/lib/osiris-ui.js +11 -0
- package/lib/osiris-ui.js.map +1 -0
- package/lib/osiris.d.ts +17 -0
- package/lib/osiris.d.ts.map +1 -0
- package/lib/osiris.js +17 -0
- package/lib/osiris.js.map +1 -0
- package/lib/panel/ui/FilterInput.d.ts.map +1 -1
- package/lib/panel/ui/FilterInput.js +3 -1
- package/lib/panel/ui/FilterInput.js.map +1 -1
- package/lib/panel/ui/SidePanelItem.d.ts +39 -0
- package/lib/panel/ui/SidePanelItem.d.ts.map +1 -0
- package/lib/panel/ui/SidePanelItem.js +63 -0
- package/lib/panel/ui/SidePanelItem.js.map +1 -0
- package/lib/panel/ui/SidePanelStack.d.ts +11 -10
- package/lib/panel/ui/SidePanelStack.d.ts.map +1 -1
- package/lib/panel/ui/SidePanelStack.js +22 -15
- package/lib/panel/ui/SidePanelStack.js.map +1 -1
- package/lib/panel/ui/SpectrumColorSwatch.d.ts.map +1 -1
- package/lib/panel/ui/SpectrumColorSwatch.js +4 -2
- package/lib/panel/ui/SpectrumColorSwatch.js.map +1 -1
- package/lib/panel/ui/usePanelFolds.d.ts +35 -0
- package/lib/panel/ui/usePanelFolds.d.ts.map +1 -0
- package/lib/panel/ui/usePanelFolds.js +73 -0
- package/lib/panel/ui/usePanelFolds.js.map +1 -0
- package/lib/shared/ui/FullscreenStage.d.ts +43 -0
- package/lib/shared/ui/FullscreenStage.d.ts.map +1 -0
- package/lib/shared/ui/FullscreenStage.js +38 -0
- package/lib/shared/ui/FullscreenStage.js.map +1 -0
- package/lib/shared/ui/canFillScreen.d.ts +7 -0
- package/lib/shared/ui/canFillScreen.d.ts.map +1 -0
- package/lib/shared/ui/canFillScreen.js +9 -0
- package/lib/shared/ui/canFillScreen.js.map +1 -0
- package/lib/shared/ui/index.d.ts +3 -0
- package/lib/shared/ui/index.d.ts.map +1 -1
- package/lib/shared/ui/index.js +2 -0
- package/lib/shared/ui/index.js.map +1 -1
- package/lib/split/ui/splitPanes.d.ts +3 -3
- package/lib/split/ui/splitPanes.js +1 -1
- package/lib/structure/core/structureNotations.d.ts +8 -1
- package/lib/structure/core/structureNotations.d.ts.map +1 -1
- package/lib/structure/core/structureNotations.js +14 -8
- package/lib/structure/core/structureNotations.js.map +1 -1
- package/lib/structure/ui/StructureExportPanel.d.ts +8 -0
- package/lib/structure/ui/StructureExportPanel.d.ts.map +1 -1
- package/lib/structure/ui/StructureExportPanel.js +9 -4
- package/lib/structure/ui/StructureExportPanel.js.map +1 -1
- package/lib/structure/ui/index.d.ts +1 -0
- package/lib/structure/ui/index.d.ts.map +1 -1
- package/lib/structure/ui/index.js.map +1 -1
- package/package.json +4 -1
- package/src/card/core/cardAddress.ts +39 -3
- package/src/card/core/cardPageUrl.ts +10 -3
- package/src/card/core/index.ts +7 -1
- package/src/credits/core/credits.ts +40 -0
- package/src/download/core/downloadFigure.ts +26 -1
- package/src/download/core/figureDpi.ts +94 -0
- package/src/download/core/figurePng.ts +11 -2
- package/src/download/core/index.ts +13 -1
- package/src/download/core/pngDpi.ts +84 -0
- package/src/download/ui/FigureDownloadPanel.tsx +41 -51
- package/src/download/ui/figureDownloadHint.ts +13 -0
- package/src/download/ui/figureDownloadOptions.ts +85 -0
- package/src/download/ui/resolutionChoices.ts +67 -0
- package/src/download/ui/useFigureActions.ts +24 -9
- package/src/download/ui/useFigureDownload.tsx +57 -71
- package/src/ecosystem/core/sites.ts +14 -0
- package/src/ecosystem/core/types.ts +2 -1
- package/src/ecosystem/ui/glyphs.tsx +20 -0
- package/src/locales/de.json +30 -1
- package/src/locales/en.json +30 -1
- package/src/locales/es.json +30 -1
- package/src/locales/fr.json +30 -1
- package/src/locales/it.json +30 -1
- package/src/molecule3d/ui/Molecule3DStage.tsx +4 -88
- package/src/osiris/core/barGeometry.ts +74 -0
- package/src/osiris/core/computeProperties.ts +175 -0
- package/src/osiris/core/constants.ts +110 -0
- package/src/osiris/core/fitness.ts +59 -0
- package/src/osiris/core/index.ts +78 -0
- package/src/osiris/core/poolSize.ts +24 -0
- package/src/osiris/core/poolTypes.ts +140 -0
- package/src/osiris/core/predictIdCode.ts +69 -0
- package/src/osiris/core/predictProtocol.ts +78 -0
- package/src/osiris/core/predictionPool.ts +237 -0
- package/src/osiris/core/predictionQueue.ts +89 -0
- package/src/osiris/core/predictors.ts +142 -0
- package/src/osiris/core/propertyKeys.ts +54 -0
- package/src/osiris/core/riskAppearance.ts +86 -0
- package/src/osiris/core/riskDetail.ts +120 -0
- package/src/osiris/core/riskDetailRequest.ts +64 -0
- package/src/osiris/core/riskFindings.ts +92 -0
- package/src/osiris/core/runPredictionJob.ts +49 -0
- package/src/osiris/core/scales.ts +236 -0
- package/src/osiris/core/types.ts +103 -0
- package/src/osiris/core/valuation.ts +44 -0
- package/src/osiris/ui/PropertyBar.tsx +77 -0
- package/src/osiris/ui/PropertyPanel.tsx +227 -0
- package/src/osiris/ui/PropertyRows.tsx +92 -0
- package/src/osiris/ui/RiskFindings.tsx +137 -0
- package/src/osiris/ui/RiskPanel.tsx +81 -0
- package/src/osiris/ui/RiskSquare.tsx +108 -0
- package/src/osiris/ui/index.ts +17 -0
- package/src/osiris/ui/usePredictions.ts +194 -0
- package/src/osiris-ui.ts +11 -0
- package/src/osiris.ts +17 -0
- package/src/panel/ui/FilterInput.tsx +4 -1
- package/src/panel/ui/SidePanelItem.tsx +162 -0
- package/src/panel/ui/SidePanelStack.tsx +29 -36
- package/src/panel/ui/SpectrumColorSwatch.tsx +5 -3
- package/src/panel/ui/usePanelFolds.ts +103 -0
- package/src/shared/ui/FullscreenStage.tsx +96 -0
- package/src/shared/ui/canFillScreen.ts +8 -0
- package/src/shared/ui/index.ts +6 -0
- package/src/split/ui/splitPanes.tsx +4 -4
- package/src/structure/core/structureNotations.ts +25 -8
- package/src/structure/ui/StructureExportPanel.tsx +23 -4
- package/src/structure/ui/index.ts +4 -0
- package/styles/osiris.css +339 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the tool predicts about one molecule, and how a risk is explained.
|
|
3
|
+
*
|
|
4
|
+
* Every quantity OpenChemLib can fail to compute is `number | null` here, never
|
|
5
|
+
* the library's own sentinel: cLogP and solubility answer `-999`, the polar
|
|
6
|
+
* surface area `-1`, drug-likeness `-999` when the tables did not load and `-1`
|
|
7
|
+
* when no fragment matched, and a risk `0`. None of them is `NaN`, and
|
|
8
|
+
* `DrugScoreCalculator` consumes every one of them and returns a plausible
|
|
9
|
+
* number, so a sentinel that leaves this module is a wrong value on screen with
|
|
10
|
+
* nothing to say it is wrong.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The four risks OSIRIS assesses, in the order the library numbers them. */
|
|
14
|
+
export const RISK_TYPES = [
|
|
15
|
+
'mutagenic',
|
|
16
|
+
'tumorigenic',
|
|
17
|
+
'irritant',
|
|
18
|
+
'reproductive',
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
/** One of the four risks. */
|
|
22
|
+
export type RiskType = (typeof RISK_TYPES)[number];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* How risky one type came back.
|
|
26
|
+
*
|
|
27
|
+
* `unknown` is the library's `RISK_UNKNOWN`, which it returns when its fragment
|
|
28
|
+
* tables did not load — never "nothing was found", which is `none`.
|
|
29
|
+
*/
|
|
30
|
+
export type RiskLevel = 'unknown' | 'none' | 'low' | 'high';
|
|
31
|
+
|
|
32
|
+
/** Everything the tool shows for one molecule. */
|
|
33
|
+
export interface OsirisProperties {
|
|
34
|
+
/** The canonical idCode, read before anything touched the molecule. */
|
|
35
|
+
idCode: string;
|
|
36
|
+
/** The canonical SMILES, for a link and for a download. */
|
|
37
|
+
smiles: string;
|
|
38
|
+
/** A name from the source, or the SMILES when it carried none. */
|
|
39
|
+
label: string;
|
|
40
|
+
/** e.g. `C6H6`. */
|
|
41
|
+
molecularFormula: string;
|
|
42
|
+
/** The relative molecular weight in g/mol — 78.11 for benzene, never the monoisotopic mass. */
|
|
43
|
+
molecularWeight: number;
|
|
44
|
+
/** cLogP, the octanol/water partition coefficient. */
|
|
45
|
+
logP: number | null;
|
|
46
|
+
/** Aqueous solubility, log10 of mol/L at 25 °C and pH 7.5. */
|
|
47
|
+
logS: number | null;
|
|
48
|
+
/** Topological polar surface area, in Ų. */
|
|
49
|
+
polarSurfaceArea: number | null;
|
|
50
|
+
/** Drug-likeness: positive is drug-like, negative is not. */
|
|
51
|
+
druglikeness: number | null;
|
|
52
|
+
/** The drug score, 0 to 1. `null` whenever any of its inputs was unknown. */
|
|
53
|
+
drugScore: number | null;
|
|
54
|
+
/** Every nitrogen and oxygen — the OSIRIS definition, not Lipinski's. */
|
|
55
|
+
acceptorCount: number;
|
|
56
|
+
/** Every nitrogen and oxygen carrying at least one hydrogen. */
|
|
57
|
+
donorCount: number;
|
|
58
|
+
/** Atoms with a real, non-pseudo parity. */
|
|
59
|
+
stereoCenterCount: number;
|
|
60
|
+
/** Single, non-ring, non-terminal, non-amide bonds. */
|
|
61
|
+
rotatableBondCount: number;
|
|
62
|
+
/** How the four risks came back. */
|
|
63
|
+
risks: Record<RiskType, RiskLevel>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Which branch of the predictor produced one line of a risk explanation. */
|
|
67
|
+
export type RiskFindingKind = 'known' | 'fragment' | 'none' | 'note';
|
|
68
|
+
|
|
69
|
+
/** One line of why a risk came back as it did. */
|
|
70
|
+
export interface RiskFinding {
|
|
71
|
+
/**
|
|
72
|
+
* `known` — the molecule's own idCode is in the RTECS-derived list, so there
|
|
73
|
+
* is nothing to highlight. `fragment` — an alerting substructure matched.
|
|
74
|
+
* `none` — nothing was found. `note` — anything else the predictor printed.
|
|
75
|
+
*/
|
|
76
|
+
kind: RiskFindingKind;
|
|
77
|
+
/** The sentence the predictor printed, verbatim. */
|
|
78
|
+
description: string;
|
|
79
|
+
/** How alerting the matched fragment is, or `null` when this is not a fragment. */
|
|
80
|
+
severity: 'high' | 'medium' | null;
|
|
81
|
+
/** The fragment's idCode, or `null` — a known molecule carries no fragment. */
|
|
82
|
+
idCode: string | null;
|
|
83
|
+
/** The molecule's atoms the fragment matched, so the page can highlight them. */
|
|
84
|
+
atoms: readonly number[];
|
|
85
|
+
/** The molecule's bonds the fragment matched. */
|
|
86
|
+
bonds: readonly number[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Why one risk came back as it did. */
|
|
90
|
+
export interface RiskDetail {
|
|
91
|
+
/** Which of the four risks this explains. */
|
|
92
|
+
risk: RiskType;
|
|
93
|
+
/** What the predictor printed, one entry per sentence or matched fragment. */
|
|
94
|
+
findings: readonly RiskFinding[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** One entry of a `ParameterizedString[]`, as OpenChemLib hands it over. */
|
|
98
|
+
export interface DetailEntry {
|
|
99
|
+
/** 1 is an idCode, 2 is text, 3 is a number written out. */
|
|
100
|
+
type: number;
|
|
101
|
+
/** The entry itself. */
|
|
102
|
+
value: string;
|
|
103
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataWarrior's own fuzzy fitness, ported from
|
|
3
|
+
* `com.actelion.research.chem.prediction.MolecularPropertyHelper`.
|
|
4
|
+
*
|
|
5
|
+
* It is what turns a predicted number into the green-to-red ramp behind it,
|
|
6
|
+
* and it is the library's arithmetic rather than a cut-off invented here: a
|
|
7
|
+
* property inside its preferred band scores 1, and the score falls away
|
|
8
|
+
* smoothly outside it over `halfWidth`. There is no cliff anywhere, so a cLogP
|
|
9
|
+
* of 4.01 does not look categorically worse than one of 3.99.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How well one value sits in a preferred band, from 0 to 1.
|
|
14
|
+
*
|
|
15
|
+
* A bound given as `null` is simply not applied, which is how a property
|
|
16
|
+
* bounded on one side only — a solubility that must be high enough, a weight
|
|
17
|
+
* that must be low enough — scores against the one bound it has. The original
|
|
18
|
+
* passes `Double.NaN` for an absent bound and `NaN` is accepted here too.
|
|
19
|
+
*
|
|
20
|
+
* The Java comment claims the fitness at `max + halfWidth` is `1/e`; the
|
|
21
|
+
* arithmetic gives `1/(1+e)`, and the arithmetic is what was ported.
|
|
22
|
+
* @param value - The predicted number.
|
|
23
|
+
* @param min - The bottom of the preferred band, or `null` when there is none.
|
|
24
|
+
* @param max - The top of the preferred band, or `null` when there is none.
|
|
25
|
+
* @param halfWidth - How far outside the band the score takes to fall away.
|
|
26
|
+
* @returns The fitness, or `NaN` when the value is `NaN`.
|
|
27
|
+
*/
|
|
28
|
+
export function getValuation(
|
|
29
|
+
value: number,
|
|
30
|
+
min: number | null,
|
|
31
|
+
max: number | null,
|
|
32
|
+
halfWidth: number,
|
|
33
|
+
): number {
|
|
34
|
+
if (Number.isNaN(value)) return Number.NaN;
|
|
35
|
+
|
|
36
|
+
let v = 1;
|
|
37
|
+
if (min !== null && !Number.isNaN(min)) {
|
|
38
|
+
v *= 1 / (1 + Math.exp((min - value) / halfWidth));
|
|
39
|
+
}
|
|
40
|
+
if (max !== null && !Number.isNaN(max)) {
|
|
41
|
+
v *= 1 / (1 + Math.exp((value - max) / halfWidth));
|
|
42
|
+
}
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One property's bar: a track, a fill whose colour is how good the value is,
|
|
3
|
+
* and the published rule of thumb marked on it.
|
|
4
|
+
*
|
|
5
|
+
* The colour is handed to the stylesheet as a number rather than mixed here.
|
|
6
|
+
* A ramp computed in JavaScript would be a hex code in a component, which is
|
|
7
|
+
* the one thing a site of this family may not write: the green, the amber and
|
|
8
|
+
* the red are the shared status tokens, and `properties.css` mixes them.
|
|
9
|
+
*
|
|
10
|
+
* The bar carries no accessible text of its own. It draws the same number the
|
|
11
|
+
* row already prints beside it, so announcing it twice would only make the
|
|
12
|
+
* panel longer to listen to.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
16
|
+
|
|
17
|
+
import type { BarGeometry } from '../core/barGeometry.ts';
|
|
18
|
+
|
|
19
|
+
/** What {@link PropertyBar} needs. */
|
|
20
|
+
export interface PropertyBarProps {
|
|
21
|
+
/** Where the fill and the mark go, from `barGeometry`. */
|
|
22
|
+
geometry: BarGeometry;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Draw one property's bar.
|
|
27
|
+
* @param props - See {@link PropertyBarProps}.
|
|
28
|
+
* @returns The track, its fill and its labelled mark.
|
|
29
|
+
*/
|
|
30
|
+
export function PropertyBar(props: PropertyBarProps): ReactElement {
|
|
31
|
+
const { geometry } = props;
|
|
32
|
+
// `--fitness` is a custom property, which React sets verbatim and
|
|
33
|
+
// `CSSProperties` has no name for; the stylesheet reads it in the `calc()`
|
|
34
|
+
// that mixes the ramp.
|
|
35
|
+
const fill = {
|
|
36
|
+
left: percent(geometry.start),
|
|
37
|
+
width: percent(geometry.length),
|
|
38
|
+
'--fitness': geometry.fitness.toFixed(4),
|
|
39
|
+
} as CSSProperties;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="property-bar" aria-hidden="true">
|
|
43
|
+
<div className="property-bar__track">
|
|
44
|
+
{geometry.origin === 0 ? null : (
|
|
45
|
+
<div
|
|
46
|
+
className="property-bar__origin"
|
|
47
|
+
style={{ left: percent(geometry.origin) }}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
<div className="property-bar__fill" style={fill} />
|
|
51
|
+
{geometry.mark === null ? null : (
|
|
52
|
+
<div
|
|
53
|
+
className="property-bar__mark"
|
|
54
|
+
style={{ left: percent(geometry.mark) }}
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
{geometry.mark === null || geometry.markLabel === null ? null : (
|
|
59
|
+
<span
|
|
60
|
+
className="property-bar__threshold"
|
|
61
|
+
style={{ left: percent(geometry.mark) }}
|
|
62
|
+
>
|
|
63
|
+
{geometry.markLabel}
|
|
64
|
+
</span>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A fraction of the track, as a CSS length.
|
|
72
|
+
* @param fraction - 0 at the left end, 1 at the right.
|
|
73
|
+
* @returns The percentage, to two decimals.
|
|
74
|
+
*/
|
|
75
|
+
function percent(fraction: number): string {
|
|
76
|
+
return `${(fraction * 100).toFixed(2)}%`;
|
|
77
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything OSIRIS predicts about one molecule: the four toxicity risks, then
|
|
3
|
+
* the ten numbers, in the order the original panel showed them.
|
|
4
|
+
*
|
|
5
|
+
* One panel wherever a molecule is predicted — beside an editor's canvas, for
|
|
6
|
+
* the row of a table the reader focused, beside a drawing — because a second
|
|
7
|
+
* panel would be a second place for the same ten rows to drift apart.
|
|
8
|
+
*
|
|
9
|
+
* The sections stand whether or not there is anything in them. A prediction
|
|
10
|
+
* costs a fifth of a second, and a panel that appeared only once it was ready
|
|
11
|
+
* would move the page under the reader's eye every time they drew a bond.
|
|
12
|
+
*
|
|
13
|
+
* Which explanation is open is the caller's state — it belongs to the page, and
|
|
14
|
+
* on the explorer it is what a click on a square changes — but *fetching* it is
|
|
15
|
+
* the panel's own business, so no page repeats the dance.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Callout, H5 } from '@blueprintjs/core';
|
|
19
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
20
|
+
import { useEffect, useState } from 'react';
|
|
21
|
+
import { MF } from 'react-mf';
|
|
22
|
+
|
|
23
|
+
import { errorMessage } from '../../error/core/toError.ts';
|
|
24
|
+
import { PagePart } from '../../share/ui/PagePart.tsx';
|
|
25
|
+
import type { PropertyKey } from '../core/propertyKeys.ts';
|
|
26
|
+
import { PROPERTY_KEYS } from '../core/propertyKeys.ts';
|
|
27
|
+
import type { OsirisProperties, RiskDetail, RiskType } from '../core/types.ts';
|
|
28
|
+
|
|
29
|
+
import { PropertyRows } from './PropertyRows.tsx';
|
|
30
|
+
import { RiskFindings } from './RiskFindings.tsx';
|
|
31
|
+
import { RiskPanel } from './RiskPanel.tsx';
|
|
32
|
+
|
|
33
|
+
/** What {@link PropertyPanel} needs. */
|
|
34
|
+
export interface PropertyPanelProps {
|
|
35
|
+
/**
|
|
36
|
+
* What was predicted, or `null` when there is nothing to show yet.
|
|
37
|
+
* @default null
|
|
38
|
+
*/
|
|
39
|
+
properties?: OsirisProperties | null;
|
|
40
|
+
/**
|
|
41
|
+
* The atom layout to draw the molecule with, in an explanation. Empty leaves
|
|
42
|
+
* openchemlib to invent one.
|
|
43
|
+
* @default ''
|
|
44
|
+
*/
|
|
45
|
+
coordinates?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Whether a prediction is running for the molecule the panel is showing.
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
pending?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* What went wrong with this structure, in one sentence, or `null`.
|
|
53
|
+
* @default null
|
|
54
|
+
*/
|
|
55
|
+
problem?: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Whether to open on the molecule's formula and name. Off for a host that
|
|
58
|
+
* already shows the formula beside the panel, such as a structure editor's
|
|
59
|
+
* side panel.
|
|
60
|
+
* @default true
|
|
61
|
+
*/
|
|
62
|
+
head?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether to draw the four risks, and the explanation a square opens. Off
|
|
65
|
+
* for a page answering a question about the numbers alone.
|
|
66
|
+
* @default true
|
|
67
|
+
*/
|
|
68
|
+
risks?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The property rows to draw, in the panel's order whatever order they are
|
|
71
|
+
* given in. Empty leaves the section out, for a page answering a question
|
|
72
|
+
* about the risks alone.
|
|
73
|
+
* @default PROPERTY_KEYS
|
|
74
|
+
*/
|
|
75
|
+
rows?: readonly PropertyKey[];
|
|
76
|
+
/**
|
|
77
|
+
* What is said when there is nothing predicted and nothing running.
|
|
78
|
+
* @default 'Nothing to predict yet.'
|
|
79
|
+
*/
|
|
80
|
+
emptyMessage?: ReactNode;
|
|
81
|
+
/**
|
|
82
|
+
* Which risk has its explanation open.
|
|
83
|
+
* @default null
|
|
84
|
+
*/
|
|
85
|
+
openRisk?: RiskType | null;
|
|
86
|
+
/**
|
|
87
|
+
* Called with the risk to open, or `null` to close it. Without it the squares
|
|
88
|
+
* are not clickable.
|
|
89
|
+
* @default undefined
|
|
90
|
+
*/
|
|
91
|
+
onOpenRisk?: (risk: RiskType | null) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Why a risk came back as it did. Without it no explanation is offered.
|
|
94
|
+
* @default undefined
|
|
95
|
+
*/
|
|
96
|
+
loadDetail?: (structure: string, risk: RiskType) => Promise<RiskDetail>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** One fetched explanation, and the request it answers. */
|
|
100
|
+
interface DetailAnswer {
|
|
101
|
+
key: string;
|
|
102
|
+
detail: RiskDetail | null;
|
|
103
|
+
error: string | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The risks and the properties of one molecule.
|
|
108
|
+
* @param props - See {@link PropertyPanelProps}.
|
|
109
|
+
* @returns The two panels, and the open explanation between them.
|
|
110
|
+
*/
|
|
111
|
+
export function PropertyPanel(props: PropertyPanelProps): ReactElement {
|
|
112
|
+
const {
|
|
113
|
+
properties = null,
|
|
114
|
+
coordinates = '',
|
|
115
|
+
pending = false,
|
|
116
|
+
problem = null,
|
|
117
|
+
head = true,
|
|
118
|
+
risks = true,
|
|
119
|
+
rows = PROPERTY_KEYS,
|
|
120
|
+
emptyMessage = 'Nothing to predict yet.',
|
|
121
|
+
openRisk = null,
|
|
122
|
+
onOpenRisk,
|
|
123
|
+
loadDetail,
|
|
124
|
+
} = props;
|
|
125
|
+
|
|
126
|
+
const structure =
|
|
127
|
+
properties === null
|
|
128
|
+
? ''
|
|
129
|
+
: coordinates === ''
|
|
130
|
+
? properties.idCode
|
|
131
|
+
: `${properties.idCode} ${coordinates}`;
|
|
132
|
+
const answer = useRiskDetail(structure, openRisk, loadDetail);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="property-panel">
|
|
136
|
+
{problem === null ? null : (
|
|
137
|
+
<Callout intent="warning" compact>
|
|
138
|
+
{problem}
|
|
139
|
+
</Callout>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{properties === null ? (
|
|
143
|
+
<p className="osiris-note">{pending ? 'Predicting…' : emptyMessage}</p>
|
|
144
|
+
) : head ? (
|
|
145
|
+
<header className="property-panel__head">
|
|
146
|
+
<MF mf={properties.molecularFormula} />
|
|
147
|
+
<span className="property-panel__label">{properties.label}</span>
|
|
148
|
+
</header>
|
|
149
|
+
) : null}
|
|
150
|
+
|
|
151
|
+
{risks ? (
|
|
152
|
+
<PagePart part="risks">
|
|
153
|
+
<RiskPanel
|
|
154
|
+
risks={properties?.risks ?? null}
|
|
155
|
+
pending={pending}
|
|
156
|
+
openRisk={openRisk}
|
|
157
|
+
onOpenRisk={onOpenRisk}
|
|
158
|
+
/>
|
|
159
|
+
</PagePart>
|
|
160
|
+
) : null}
|
|
161
|
+
|
|
162
|
+
{!risks ||
|
|
163
|
+
openRisk === null ||
|
|
164
|
+
structure === '' ||
|
|
165
|
+
loadDetail === undefined ? null : (
|
|
166
|
+
<PagePart part="detail">
|
|
167
|
+
<RiskFindings
|
|
168
|
+
risk={openRisk}
|
|
169
|
+
structure={structure}
|
|
170
|
+
detail={answer?.detail ?? null}
|
|
171
|
+
error={answer?.error ?? null}
|
|
172
|
+
/>
|
|
173
|
+
</PagePart>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{rows.length === 0 ? null : (
|
|
177
|
+
<PagePart part="properties">
|
|
178
|
+
<section className="property-section">
|
|
179
|
+
<H5>Predicted properties</H5>
|
|
180
|
+
<PropertyRows properties={properties} keys={rows} />
|
|
181
|
+
</section>
|
|
182
|
+
</PagePart>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Fetch the open risk's explanation, and answer only about the request that is
|
|
190
|
+
* open now.
|
|
191
|
+
*
|
|
192
|
+
* The answer carries the request it belongs to rather than being cleared when a
|
|
193
|
+
* new one starts: clearing would mean writing state from inside the effect, and
|
|
194
|
+
* comparing keys says the same thing without the extra render.
|
|
195
|
+
* @param structure - The molecule: an idCode, with its coordinates after a space.
|
|
196
|
+
* @param openRisk - Which risk is open, or `null`.
|
|
197
|
+
* @param loadDetail - What answers the question, or `undefined` when nothing does.
|
|
198
|
+
* @returns The answer to the open request, or `null` while it is being worked out.
|
|
199
|
+
*/
|
|
200
|
+
function useRiskDetail(
|
|
201
|
+
structure: string,
|
|
202
|
+
openRisk: RiskType | null,
|
|
203
|
+
loadDetail:
|
|
204
|
+
((structure: string, risk: RiskType) => Promise<RiskDetail>) | undefined,
|
|
205
|
+
): DetailAnswer | null {
|
|
206
|
+
const key =
|
|
207
|
+
openRisk === null || structure === '' ? '' : `${structure} ${openRisk}`;
|
|
208
|
+
const [answer, setAnswer] = useState<DetailAnswer | null>(null);
|
|
209
|
+
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (key === '' || openRisk === null || loadDetail === undefined) return;
|
|
212
|
+
let live = true;
|
|
213
|
+
loadDetail(structure, openRisk).then(
|
|
214
|
+
(detail) => {
|
|
215
|
+
if (live) setAnswer({ key, detail, error: null });
|
|
216
|
+
},
|
|
217
|
+
(error: unknown) => {
|
|
218
|
+
if (live) setAnswer({ key, detail: null, error: errorMessage(error) });
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
return () => {
|
|
222
|
+
live = false;
|
|
223
|
+
};
|
|
224
|
+
}, [key, structure, openRisk, loadDetail]);
|
|
225
|
+
|
|
226
|
+
return answer !== null && answer.key === key ? answer : null;
|
|
227
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ten predicted numbers, one row each: a name, a bar and a value.
|
|
3
|
+
*
|
|
4
|
+
* The order is the legacy panel's — cLogP, solubility, molweight, TPSA,
|
|
5
|
+
* drug-likeness, the two hydrogen-bond counts, stereocentres, rotatable bonds,
|
|
6
|
+
* drug score — and it is `PROPERTY_KEYS`, which the comparison table's columns
|
|
7
|
+
* and the plot's default axes also read. A reader who learned the panel reads
|
|
8
|
+
* the table without relearning it.
|
|
9
|
+
*
|
|
10
|
+
* A value the predictor could not answer prints the missing marker and draws no
|
|
11
|
+
* bar. It is never zero: `DrugScoreCalculator` consumes every sentinel and
|
|
12
|
+
* hands back a plausible number, so a bar drawn from one would be a wrong
|
|
13
|
+
* answer with nothing saying it is wrong.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ReactElement } from 'react';
|
|
17
|
+
|
|
18
|
+
import { MISSING_VALUE } from '../../format/core/missing.ts';
|
|
19
|
+
import { formatDecimal } from '../../format/core/numbers.ts';
|
|
20
|
+
import { barGeometry } from '../core/barGeometry.ts';
|
|
21
|
+
import type { PropertyKey } from '../core/propertyKeys.ts';
|
|
22
|
+
import { PROPERTY_KEYS, propertyValue } from '../core/propertyKeys.ts';
|
|
23
|
+
import { propertyScale } from '../core/scales.ts';
|
|
24
|
+
import type { OsirisProperties } from '../core/types.ts';
|
|
25
|
+
|
|
26
|
+
import { PropertyBar } from './PropertyBar.tsx';
|
|
27
|
+
|
|
28
|
+
/** What {@link PropertyRows} needs. */
|
|
29
|
+
export interface PropertyRowsProps {
|
|
30
|
+
/**
|
|
31
|
+
* What was predicted, or `null` while the molecule is still being worked on —
|
|
32
|
+
* in which case every row stands with its name and no value, so nothing moves
|
|
33
|
+
* when the answer arrives.
|
|
34
|
+
* @default null
|
|
35
|
+
*/
|
|
36
|
+
properties?: OsirisProperties | null;
|
|
37
|
+
/**
|
|
38
|
+
* The rows to draw. They are drawn in the panel's order whatever order they
|
|
39
|
+
* are given in, so a narrowed panel still reads as the whole one does.
|
|
40
|
+
* @default PROPERTY_KEYS
|
|
41
|
+
*/
|
|
42
|
+
keys?: readonly PropertyKey[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The property rows, all ten unless fewer are asked, in the panel's order.
|
|
47
|
+
* @param props - See {@link PropertyRowsProps}.
|
|
48
|
+
* @returns One row per property.
|
|
49
|
+
*/
|
|
50
|
+
export function PropertyRows(props: PropertyRowsProps): ReactElement {
|
|
51
|
+
const { properties = null, keys = PROPERTY_KEYS } = props;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="property-rows">
|
|
55
|
+
{PROPERTY_KEYS.filter((key) => keys.includes(key)).map((key) => (
|
|
56
|
+
<PropertyRow
|
|
57
|
+
key={key}
|
|
58
|
+
propertyKey={key}
|
|
59
|
+
value={properties === null ? null : propertyValue(properties, key)}
|
|
60
|
+
/>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface PropertyRowProps {
|
|
67
|
+
propertyKey: PropertyKey;
|
|
68
|
+
value: number | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function PropertyRow(props: PropertyRowProps): ReactElement {
|
|
72
|
+
const { propertyKey, value } = props;
|
|
73
|
+
const scale = propertyScale(propertyKey);
|
|
74
|
+
const geometry = barGeometry(propertyKey, value);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className="property-row">
|
|
78
|
+
<span className="property-row__label">
|
|
79
|
+
{scale.label}
|
|
80
|
+
{scale.unit === '' ? null : (
|
|
81
|
+
<span className="property-row__unit">{scale.unit}</span>
|
|
82
|
+
)}
|
|
83
|
+
</span>
|
|
84
|
+
<span className="property-row__bar">
|
|
85
|
+
{geometry === null ? null : <PropertyBar geometry={geometry} />}
|
|
86
|
+
</span>
|
|
87
|
+
<span className="property-row__value">
|
|
88
|
+
{value === null ? MISSING_VALUE : formatDecimal(value, scale.decimals)}
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why one risk came back as it did.
|
|
3
|
+
*
|
|
4
|
+
* Two branches of the predictor reach the same red square and mean different
|
|
5
|
+
* things. *This molecule is known to be mutagenic* means the compound's own
|
|
6
|
+
* idCode is in a list derived from RTECS — data on this exact compound, and
|
|
7
|
+
* nothing on the drawing to point at. *High-risk fragments indicating
|
|
8
|
+
* Mutagenicity* means a substructure matched, and that substructure is drawn
|
|
9
|
+
* beside the molecule with its atoms painted on it.
|
|
10
|
+
*
|
|
11
|
+
* Telling them apart is the whole point of opening a square: aspirin, caffeine
|
|
12
|
+
* and paracetamol are all red through the first branch, and highlighting the
|
|
13
|
+
* whole molecule there would call the compound its own toxicophore.
|
|
14
|
+
*
|
|
15
|
+
* A fragment heading is repeated on each of the fragments under it, so it is
|
|
16
|
+
* printed once and the matches follow.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Callout, Spinner } from '@blueprintjs/core';
|
|
20
|
+
import type { ReactElement } from 'react';
|
|
21
|
+
|
|
22
|
+
import { Structure } from '../../structure/ui/Structure.tsx';
|
|
23
|
+
import { RISK_LABELS } from '../core/constants.ts';
|
|
24
|
+
import { KNOWN_MOLECULE_NOTE } from '../core/riskAppearance.ts';
|
|
25
|
+
import type { RiskDetail, RiskFinding, RiskType } from '../core/types.ts';
|
|
26
|
+
|
|
27
|
+
/** What {@link RiskFindings} needs. */
|
|
28
|
+
export interface RiskFindingsProps {
|
|
29
|
+
/** Which risk is open. */
|
|
30
|
+
risk: RiskType;
|
|
31
|
+
/** The molecule it was assessed on: the idCode, with its coordinates after a space. */
|
|
32
|
+
structure: string;
|
|
33
|
+
/**
|
|
34
|
+
* What the predictor found, or `null` while it is being worked out.
|
|
35
|
+
* @default null
|
|
36
|
+
*/
|
|
37
|
+
detail?: RiskDetail | null;
|
|
38
|
+
/**
|
|
39
|
+
* Why the explanation could not be fetched, or `null`.
|
|
40
|
+
* @default null
|
|
41
|
+
*/
|
|
42
|
+
error?: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** How big a drawing in the explanation is, in pixels. */
|
|
46
|
+
const DRAWING = { width: 190, height: 130 };
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The explanation of one risk.
|
|
50
|
+
* @param props - See {@link RiskFindingsProps}.
|
|
51
|
+
* @returns The predictor's own sentences, with every matched fragment drawn.
|
|
52
|
+
*/
|
|
53
|
+
export function RiskFindings(props: RiskFindingsProps): ReactElement {
|
|
54
|
+
const { risk, structure, detail = null, error = null } = props;
|
|
55
|
+
|
|
56
|
+
if (error !== null) {
|
|
57
|
+
return <Callout intent="warning">{error}</Callout>;
|
|
58
|
+
}
|
|
59
|
+
if (detail === null) {
|
|
60
|
+
return (
|
|
61
|
+
// A div, not a paragraph: the spinner draws a div of its own.
|
|
62
|
+
<div className="osiris-note osiris-note--working">
|
|
63
|
+
<Spinner size={14} /> Working out why {RISK_LABELS[risk]} came back as
|
|
64
|
+
it did.
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="risk-findings">
|
|
71
|
+
{detail.findings.map((finding, index) => (
|
|
72
|
+
<Finding
|
|
73
|
+
// The predictor prints each heading once and each matching fragment
|
|
74
|
+
// once under it, so the pair names the line without an index.
|
|
75
|
+
key={`${finding.kind}:${finding.description}:${finding.idCode ?? ''}`}
|
|
76
|
+
finding={finding}
|
|
77
|
+
structure={structure}
|
|
78
|
+
heading={
|
|
79
|
+
detail.findings[index - 1]?.description === finding.description
|
|
80
|
+
? null
|
|
81
|
+
: finding.description
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface FindingProps {
|
|
90
|
+
finding: RiskFinding;
|
|
91
|
+
structure: string;
|
|
92
|
+
heading: string | null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Finding(props: FindingProps): ReactElement {
|
|
96
|
+
const { finding, structure, heading } = props;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className="risk-finding">
|
|
100
|
+
{heading === null ? null : (
|
|
101
|
+
<p className="risk-finding__heading">{heading}</p>
|
|
102
|
+
)}
|
|
103
|
+
{finding.kind === 'known' ? (
|
|
104
|
+
<p className="osiris-note">{KNOWN_MOLECULE_NOTE}</p>
|
|
105
|
+
) : null}
|
|
106
|
+
{finding.idCode === null ? null : (
|
|
107
|
+
<div className="risk-finding__drawings">
|
|
108
|
+
<figure className="risk-finding__figure">
|
|
109
|
+
<Structure
|
|
110
|
+
idCode={finding.idCode}
|
|
111
|
+
width={DRAWING.width}
|
|
112
|
+
height={DRAWING.height}
|
|
113
|
+
/>
|
|
114
|
+
<figcaption>
|
|
115
|
+
{finding.severity === 'medium' ? 'Medium-risk' : 'High-risk'}{' '}
|
|
116
|
+
fragment
|
|
117
|
+
</figcaption>
|
|
118
|
+
</figure>
|
|
119
|
+
<figure className="risk-finding__figure">
|
|
120
|
+
<Structure
|
|
121
|
+
idCode={structure}
|
|
122
|
+
width={DRAWING.width}
|
|
123
|
+
height={DRAWING.height}
|
|
124
|
+
atomHighlight={[...finding.atoms]}
|
|
125
|
+
bondHighlight={[...finding.bonds]}
|
|
126
|
+
/>
|
|
127
|
+
<figcaption>
|
|
128
|
+
{finding.atoms.length === 0
|
|
129
|
+
? 'Not located on this drawing'
|
|
130
|
+
: 'Where it sits on the molecule'}
|
|
131
|
+
</figcaption>
|
|
132
|
+
</figure>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|