diff --git a/package-lock.json b/package-lock.json index 2eac202..8c0d8ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "three": "^0.155.0", + "ts-enum-util": "^4.0.2", "zustand": "^4.4.1" }, "devDependencies": { @@ -5459,6 +5460,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-enum-util": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/ts-enum-util/-/ts-enum-util-4.0.2.tgz", + "integrity": "sha512-BB5qjvHYgYgOB/CaoA1Cy/B2QNnZ+nVBrJ15VV/AXGWx+AO83k5wgeLOJvkSLoKKavvH/M8Wj4ZbgROjsuYwzw==" + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", diff --git a/package.json b/package.json index be8ba91..1f644d7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "three": "^0.155.0", + "ts-enum-util": "^4.0.2", "zustand": "^4.4.1" }, "devDependencies": { diff --git a/src/calculations/qvalue.test.ts b/src/calculations/qvalue.test.ts index d6c542d..0839b23 100644 --- a/src/calculations/qvalue.test.ts +++ b/src/calculations/qvalue.test.ts @@ -1,5 +1,5 @@ -import { expect, test } from "vitest"; +import { test } from "vitest"; test("Test getting q from pixel position ", () => { - console.log("write some tests here"); + console.log("write some tests here"); }); diff --git a/src/plot/centrePlot.tsx b/src/plot/centrePlot.tsx index b9fd8b8..e3ee3da 100644 --- a/src/plot/centrePlot.tsx +++ b/src/plot/centrePlot.tsx @@ -22,7 +22,6 @@ import { useBeamlineConfigStore } from "../data-entry/beamlineconfigStore"; import LegendBar from "./legendBar"; import ResultsBar from "../results/resultsBar"; import NumericRange from "../calculations/numericRange"; -import { useResultStore } from "../results/resultsStore"; import { getPointForQ } from "../calculations/qvalue"; export default function CentrePlot(): JSX.Element { @@ -128,16 +127,15 @@ export default function CentrePlot(): JSX.Element { const domains = getDomains(ajustedDetector, ajustedCameraTube); // requested range on diagram - const resultStore = useResultStore(); const requestedMax = getPointForQ( - resultStore.requestedRange.max * 1e9, + plotConfig.requestedRange.max * 1e9, bealineConfig.angle ?? 0, bealineConfig.cameraLength ?? 0, (bealineConfig.wavelength ?? 0) * 1e-9, ajustedBeamstop, ); const requestedMin = getPointForQ( - resultStore.requestedRange.min * 1e9, + plotConfig.requestedRange.min * 1e9, bealineConfig.angle ?? 0, bealineConfig.cameraLength ?? 0, (bealineConfig.wavelength ?? 0) * 1e-9, @@ -233,7 +231,7 @@ export default function CentrePlot(): JSX.Element { strokeWidth={2} /> )} - {resultStore.requestedRange.min && ( + {plotConfig.requestedRange.min && plotConfig.requestedRange.max && ( number; +export enum ScatteringOptions { + q = "q", + s = "s", + d = "d", + twoTheta = `2${'\u03B8'}` } export interface PlotConfig { @@ -27,6 +23,9 @@ export interface PlotConfig { calibrantInPlot: boolean; calibrant: string; plotAxes: PlotAxes; + requestedRange: NumericRange; + selected: ScatteringOptions; + units: string; update: (newConfig: Partial) => void; } @@ -40,6 +39,9 @@ export const usePlotStore = create((set) => ({ calibrantInPlot: false, calibrant: "something", plotAxes: PlotAxes.milimeter, + requestedRange: new NumericRange(0, 1), + selected: ScatteringOptions.q, + units: "r-nanometres", update: (newConfig) => { set({ ...newConfig }); }, diff --git a/src/results/rangeDiagram.tsx b/src/results/rangeDiagram.tsx index 2088ce5..83e513f 100644 --- a/src/results/rangeDiagram.tsx +++ b/src/results/rangeDiagram.tsx @@ -1,62 +1,60 @@ import NumericRange from "../calculations/numericRange"; -import { useResultStore } from "./resultsStore"; -// in here to put the range diagram export default function RangeDiagram(props: { - visibleQRange: NumericRange; - fullQRange: NumericRange; + visibleQRange: NumericRange; + fullQRange: NumericRange; + requestedRange: NumericRange; }): JSX.Element { - const svgRange = props.fullQRange.max - props.fullQRange.min; - const visableStart = (props.visibleQRange.min / svgRange) * 100; - const visbleWidth = - ((props.visibleQRange.max - props.visibleQRange.min) / svgRange) * 100; - const resultStore = useResultStore(); + const svgRange = props.fullQRange.max - props.fullQRange.min; + const visableStart = (props.visibleQRange.min / svgRange) * 100; + const visbleWidth = + ((props.visibleQRange.max - props.visibleQRange.min) / svgRange) * 100; - const requestedMax = (resultStore.requestedRange.max / svgRange) * 100; - const requestedMin = (resultStore.requestedRange.min / svgRange) * 100; - const rectColour = props.visibleQRange.containsRange( - resultStore.requestedRange, - ) - ? "green" - : "red"; + const requestedMax = (props.requestedRange.max / svgRange) * 100; + const requestedMin = (props.requestedRange.min / svgRange) * 100; + const rectColour = props.visibleQRange.containsRange( + props.requestedRange, + ) + ? "green" + : "red"; - return ( - - - - - - {" "} - Requested min - - - Requested max - - - ); + return ( + + + + + + {" "} + Requested min + + + Requested max + + + ); } diff --git a/src/results/resultsBar.tsx b/src/results/resultsBar.tsx index 62cc155..2cca902 100644 --- a/src/results/resultsBar.tsx +++ b/src/results/resultsBar.tsx @@ -12,33 +12,72 @@ import { TextField, Typography, } from "@mui/material"; -import { AngleUnits } from "../utils/units"; -import React from "react"; -import { useResultStore } from "./resultsStore"; +import React, { ChangeEvent, useState } from "react"; import NumericRange from "../calculations/numericRange"; import RangeDiagram from "./rangeDiagram"; -import { ScatteringQuantity } from "./scatteringQuantities"; +import { D, Q, S, ScatteringQuantity, TwoTheta, ReciprocalWavelengthUnitsOptions, AngleUnitsOptions, WavelengthUnitsOptions } from "./scatteringQuantities"; +import { useBeamlineConfigStore } from "../data-entry/beamlineconfigStore"; +import { usePlotStore, ScatteringOptions } from "../plot/plotStore"; + export default function ResultsBar(props: { visableQRange: NumericRange; fullQrange: NumericRange; }): JSX.Element { - const [angleUnits, setAngleUnits] = React.useState( - AngleUnits.radians, - ); - const handleAngleUnits = (event: SelectChangeEvent) => { - setAngleUnits(event.target.value as AngleUnits); - }; - const [quantity, setQuantity] = React.useState( - ScatteringQuantity.q, - ); + const requestedQRange = usePlotStore((state) => state.requestedRange); + const selected = usePlotStore((state) => state.selected) + const units = usePlotStore((state) => state.units) + const update = usePlotStore((state) => state.update) + const wavelength = useBeamlineConfigStore((state) => state.wavelength); + let quantity: ScatteringQuantity; + + switch (selected) { + case ScatteringOptions.d: + quantity = new D("nanometres", WavelengthUnitsOptions); + break; + case ScatteringOptions.twoTheta: + quantity = new TwoTheta("radians", wavelength ?? 0, AngleUnitsOptions); + break; + case ScatteringOptions.s: + quantity = new S("r-nanometres", ReciprocalWavelengthUnitsOptions); + break; + default: + quantity = new Q("r-nanometres", ReciprocalWavelengthUnitsOptions); + } + + const handleQuantityChange = (event: SelectChangeEvent): void => { + update({ selected: event.target.value as ScatteringOptions, units: }) + } + + const visibleRange = new NumericRange( + quantity.fromQ(props.visableQRange.min), + quantity.fromQ(props.visableQRange.max)) - const handleQuantity = (event: SelectChangeEvent) => { - setQuantity(event.target.value as ScatteringQuantity); - }; + const fullRange = new NumericRange( + quantity.fromQ(props.visableQRange.min), + quantity.fromQ(props.visableQRange.max)) + + const requestedRange = new NumericRange( + quantity.fromQ(requestedQRange.min), + quantity.fromQ(requestedQRange.min), + ) + + const handleUnitChange = (event: SelectChangeEvent) => { + update({ units: event.target.value }) + } + console.log(Object.values(typeof quantity.units)) + + const handleRequestedMaxRange = (event: ChangeEvent): void => { + const newRequestedMax = quantity.tooQ(parseFloat(event.target.value) ?? 0) + update({ requestedRange: new NumericRange(requestedQRange.min, newRequestedMax) }); + } + + const handleRequestedMinRange = (event: ChangeEvent): void => { + const newRequestedMin = quantity.tooQ(parseFloat(event.target.value) ?? 0) + update({ requestedRange: new NumericRange(newRequestedMin, requestedQRange.max,) }); + } - const resultStore = useResultStore(); return ( @@ -52,21 +91,21 @@ export default function ResultsBar(props: { quantity @@ -74,59 +113,50 @@ export default function ResultsBar(props: { units - Min {quantity} value: {props.visableQRange.min.toFixed(4)}{" "} + Min {selected} value: {visibleRange.min.toFixed(4)}{" "} - Max {quantity} value: {props.visableQRange.max.toFixed(4)} + Max {selected} value: {visibleRange.max.toFixed(4)} - Requested min {quantity} value: - Requested max {quantity} value: + Requested min {selected} value: + Requested max {selected} value: { - resultStore.update({ - requestedRange: new NumericRange( - parseFloat(event.target.value) ?? 0, - resultStore.requestedRange.max, - ), - }); - }} + value={requestedRange.min} + onChange={handleRequestedMinRange} /> { - resultStore.update({ - requestedRange: new NumericRange( - resultStore.requestedRange.min, - parseFloat(event.target.value) ?? 0, - ), - }); - }} + value={requestedRange.max} + onChange={handleRequestedMaxRange} /> diff --git a/src/results/resultsStore.ts b/src/results/resultsStore.ts index 6578aff..62a124b 100644 --- a/src/results/resultsStore.ts +++ b/src/results/resultsStore.ts @@ -1,18 +1,28 @@ import { create } from "zustand"; import NumericRange from "../calculations/numericRange"; -import { quantityQ, ScatteringQuantity } from "./scatteringQuantities"; +import { AngleUnits, WavelengthUnits, ReciprocalWavelengthUnits } from "../utils/units"; +import { Q, ReciprocalWavelengthUnitsOptions } from "./scatteringQuantities"; + +export interface ScatteringQuantity { + units: WavelengthUnits | AngleUnits | ReciprocalWavelengthUnits, + unitOptions: { value: string, label: string }[], + fromQ: (quantity: number) => number, + tooQ: (quantity: number) => number, +}; export interface ResultStore { - selected: ScatteringQuantity; + quantity: ScatteringQuantity; + units: String; requestedRange: NumericRange; - update: (results: Partial) => void; + update: (newConfig: Partial) => void; } export const useResultStore = create((set) => ({ - selected: quantityQ, requestedRange: new NumericRange(0, 1), - update: (results: Partial) => { - set({ ...results }); + quantity: new Q("r-nanometres", ReciprocalWavelengthUnitsOptions); + units: "r-nanometres", + update: (newConfig) => { + set({ ...newConfig }); }, - })); + diff --git a/src/results/scatteringQuantities.ts b/src/results/scatteringQuantities.ts index 5ecde4a..097b641 100644 --- a/src/results/scatteringQuantities.ts +++ b/src/results/scatteringQuantities.ts @@ -1,80 +1,108 @@ -import { AngleUnits, ReciprocalWavelengthUnits, WavelengthUnits } from "../utils/units"; -const theta = "\u03B8"; +const angstrum = "\u212B"; -export type ScatteringUnits = WavelengthUnits | AngleUnits | ReciprocalWavelengthUnits; +// export type WavelengthUnits = "nanometres" | "angstroms"; +// export const WavelengthUnitsOptions = [ +// { value: "nanometres", label: "nm" }, +// { value: "angstroms", label: angstrum }, +// ]; -export interface ScatteringQuantity { - name: string, - units: ScatteringUnits, - fromQ: (quantity: number, units: ScatteringUnits) => number, - tooQ: (quantity: number, units: ScatteringUnits) => number, -}; +// export type ReciprocalWavelengthUnits = "r-nanometres" | "r-angstroms" +// export const ReciprocalWavelengthUnitsOptions = [ +// { value: "r-nanometres", label: "1/nm" }, +// { value: "r-angstroms", label: `1/${angstrum}` }]; -export const quantityQ = { - name: "q", - units: ReciprocalWavelengthUnits.nanmometres, - fromQ: (quantity: number, units: ScatteringUnits) => { - if (units === ReciprocalWavelengthUnits.angstroms) { +// export type AngleUnits = "radians" | "degrees"; +// export const AngleUnitsOptions = [ +// { value: "radians", label: "rad" }, +// { value: "degrees", label: "deg" }, +// ]; + +math.unit + + +export class Q implements ScatteringQuantity { + units: ReciprocalWavelengthUnits; + unitOptions: Array<{ value: string, label: string }>; + constructor(units: ReciprocalWavelengthUnits, unitOptions: Array<{ value: string, label: string }>) { + this.units = units + this.unitOptions = unitOptions + } + fromQ(quantity: number): number { + if (this.units == "r-angstroms") { return quantity / 10; } return quantity - }, - tooQ: (quantity: number, units: ScatteringUnits) => { - if (units === ReciprocalWavelengthUnits.angstroms) { + } + tooQ(quantity: number): number { + if (this.units === "r-angstroms") { return quantity * 10; } return quantity - }, + } } -export const quantityS = { - name: "s", - units: ReciprocalWavelengthUnits.nanmometres, - fromQ: (quantity: number, units: ScatteringUnits) => { - if (units === ReciprocalWavelengthUnits.angstroms) { - return quantity / 10; +export class S implements ScatteringQuantity { + units: ReciprocalWavelengthUnits; + unitOptions: Array<{ value: string, label: string }>; + constructor(units: ReciprocalWavelengthUnits, unitOptions: Array<{ value: string, label: string }>) { + this.units = units + this.unitOptions = unitOptions + } + fromQ(quantity: number): number { + if (this.units === "r-angstroms") { + return (1 / quantity) * 10; } return quantity - }, - tooQ: (quantity: number, units: ScatteringUnits) => { - if (units === ReciprocalWavelengthUnits.angstroms) { - return quantity * 10; + } + tooQ(quantity: number): number { + if (this.units === "r-angstroms") { + return 1 / (quantity / 10); } return quantity - }, + } } -export const quantityD = { - name: "d", - units: ReciprocalWavelengthUnits.nanmometres, - fromQ: (quantity: number, units: ScatteringUnits) => { - if (units === ReciprocalWavelengthUnits.angstroms) { - return quantity / 10; +export class D implements ScatteringQuantity { + units: WavelengthUnits; + unitOptions: Array<{ value: string, label: string }>; + constructor(units: WavelengthUnits, unitOptions: Array<{ value: string, label: string }>) { + this.units = units; + this.unitOptions = unitOptions + } + fromQ(quantity: number): number { + if (this.units === "angstroms") { + return 2 * Math.PI / (quantity) * 10; } - return quantity - }, - tooQ: (quantity: number, units: ScatteringUnits) => { - if (units === ReciprocalWavelengthUnits.angstroms) { - return quantity * 10; + return 2 * Math.PI / (quantity) + } + tooQ(quantity: number): number { + if (this.units === "angstroms") { + return 2 * Math.PI / (quantity / 10); } - return quantity - }, + return 2 * Math.PI / (quantity) + } } -export const quantity2Theta = { - name: `2${theta}`, - units: AngleUnits.radians, - fromQ: (quantity: number, units: ScatteringUnits) => { - if (units === WavelengthUnits.angstroms) { - return quantity / 10; +export class TwoTheta implements ScatteringQuantity { + units: AngleUnits; + wavelength: number; + unitOptions: Array<{ value: string, label: string }>; + constructor(units: AngleUnits, wavelength: number, unitOptions: Array<{ value: string, label: string }>) { + this.units = units + this.wavelength = wavelength + this.unitOptions = unitOptions + } + fromQ(quantity: number): number { + if (this.units === "degrees") { + return (2 * Math.asin((quantity * this.wavelength) / (4 * Math.PI))); } return quantity - }, - tooQ: (quantity: number, units: ScatteringUnits) => { - if (units === WavelengthUnits.angstroms) { - return quantity * 10; + } + tooQ(quantity: number): number { + if (this.units === "degrees") { + return quantity; } - return quantity - }, -} \ No newline at end of file + return 4 * Math.PI * Math.sin(quantity) / this.wavelength + } +} diff --git a/src/utils/types.ts b/src/utils/types.ts index 0a5fadb..13c1f22 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -26,3 +26,5 @@ export interface BeamlineConfig { maxCameraLength: number; wavelength: number | null; } + +export type Enum = { [s: number]: string };