diff --git a/src/components/ui/aladin.tsx b/src/components/ui/aladin.tsx index be88b98..8d46cc1 100644 --- a/src/components/ui/aladin.tsx +++ b/src/components/ui/aladin.tsx @@ -5,14 +5,14 @@ interface AdditionalSource { ra: number; dec: number; label: string; + description?: string; } interface AladinViewerProps { - ra?: number; - dec?: number; + ra: number; + dec: number; fov?: number; survey?: string; - target?: string; className?: string; additionalSources?: AdditionalSource[]; } @@ -22,7 +22,6 @@ export function AladinViewer({ dec, fov = 0.5, survey = "P/DSS2/color", - target, className = "w-full h-96", additionalSources, }: AladinViewerProps) { @@ -42,23 +41,35 @@ export function AladinViewer({ showCooGridControl: false, }); - if (target) { - aladin.gotoObject(target); - } else if (ra !== undefined && dec !== undefined) { - aladin.gotoRaDec(ra, dec); - } + aladin.gotoRaDec(ra, dec); if (additionalSources && additionalSources.length > 0) { - const catalog = window.A.catalog({ + const nameCatalog = window.A.catalog({ labelColumn: "name", + shape: "cross", + color: "black", displayLabel: true, - labelColor: "#fff", + labelColor: "lightgrey", labelFont: "14px sans-serif", }); - aladin.addCatalog(catalog); + const descrCatalog = window.A.catalog({ + color: "black", + shape: "cross", + }); + aladin.addCatalog(nameCatalog); + aladin.addCatalog(descrCatalog); additionalSources.forEach((source) => { - catalog.addSources( + if (source.description) { + descrCatalog.addSources([ + window.A.marker(source.ra, source.dec, { + name: source.label, + popupTitle: source.label, + popupDesc: source.description, + }), + ]); + } + nameCatalog.addSources( window.A.source(source.ra, source.dec, { name: source.label }), ); }); @@ -66,19 +77,19 @@ export function AladinViewer({ } catch (error) { console.error("Error initializing Aladin:", error); } - }, [ra, dec, fov, survey, target, additionalSources]); + }, [ra, dec, fov, survey, additionalSources]); return
; } interface AladinCatalog { - addSources: (sources: AladinSource) => void; + addSources: (sources: AladinSource | AladinSource[]) => void; } interface AladinSource { ra: number; dec: number; - properties?: { name?: string }; + properties?: { name?: string; popupTitle?: string; popupDesc?: string }; } declare global { @@ -105,12 +116,20 @@ declare global { displayLabel?: boolean; labelColor?: string; labelFont?: string; + sourceSize?: number; + shape?: string; + color?: string; }) => AladinCatalog; source: ( ra: number, dec: number, properties?: { name?: string }, ) => AladinSource; + marker: ( + ra: number, + dec: number, + properties?: { name?: string; popupTitle?: string; popupDesc?: string }, + ) => AladinSource; }; } } diff --git a/src/components/ui/catalog-data.tsx b/src/components/ui/catalog-data.tsx index d8f9876..f66134e 100644 --- a/src/components/ui/catalog-data.tsx +++ b/src/components/ui/catalog-data.tsx @@ -1,139 +1,136 @@ import { ReactElement } from "react"; import { CommonTable } from "./common-table"; -import { PgcObject, Schema } from "../../clients/backend/types.gen"; +import { Catalogs, Schema } from "../../clients/backend/types.gen"; interface CatalogDataProps { - object: PgcObject; - schema: Schema | null; + catalogs: Catalogs; + schema: Schema; +} + +function formatValueWithError( + value: number | undefined, + error: number | undefined, + unit?: string, + decimalPlaces: number = 0, +): string { + if (value === undefined) return "NULL"; + const formattedValue = value.toFixed(decimalPlaces); + const formattedError = + error?.toFixed(decimalPlaces) || "0".padEnd(decimalPlaces + 1, "0"); + + if (!unit) { + return `${formattedValue} ± ${formattedError}`; + } + + return `${formattedValue} ${unit} ± ${formattedError} ${unit}`; } export function CatalogData({ - object, + catalogs, schema, }: CatalogDataProps): ReactElement { - if (!object || !schema) return
; - - const coordinatesColumns = [ - { name: "Parameter" }, - { name: "Value" }, - { name: "Unit" }, - { name: "Error" }, - { name: "Error unit" }, - ]; - - const coordinatesData = [ - { - Parameter: "Right ascension", - Value: object.catalogs?.coordinates?.equatorial?.ra?.toFixed(6) || "NULL", - Unit: schema.units.coordinates?.equatorial?.ra || "NULL", - Error: - object.catalogs?.coordinates?.equatorial?.e_ra?.toFixed(6) || "NULL", - "Error unit": schema.units.coordinates?.equatorial?.e_ra || "NULL", - }, - { - Parameter: "Declination", - Value: - object.catalogs?.coordinates?.equatorial?.dec?.toFixed(6) || "NULL", - Unit: schema.units.coordinates?.equatorial?.dec || "NULL", - Error: - object.catalogs?.coordinates?.equatorial?.e_dec?.toFixed(6) || "NULL", - "Error unit": schema.units.coordinates?.equatorial?.e_dec || "NULL", - }, - { - Parameter: "Galactic longitude", - Value: object.catalogs?.coordinates?.galactic?.lon?.toFixed(6) || "NULL", - Unit: schema.units.coordinates?.galactic?.lon || "NULL", - Error: - object.catalogs?.coordinates?.galactic?.e_lon?.toFixed(6) || "NULL", - "Error unit": schema.units.coordinates?.galactic?.e_lon || "NULL", - }, - { - Parameter: "Galactic latitude", - Value: object.catalogs?.coordinates?.galactic?.lat?.toFixed(6) || "NULL", - Unit: schema.units.coordinates?.galactic?.lat || "NULL", - Error: - object.catalogs?.coordinates?.galactic?.e_lat?.toFixed(6) || "NULL", - "Error unit": schema.units.coordinates?.galactic?.e_lat || "NULL", - }, - ]; + if (!catalogs) return
; - const redshiftColumns = [ - { name: "Parameter" }, - { name: "Value" }, - { name: "Error" }, - ]; + const columns = [{ name: "Parameter" }, { name: "Value" }]; - const redshiftData = [ - { - Parameter: "z", - Value: object.catalogs?.redshift?.z?.toFixed(6) || "NULL", - Error: object.catalogs?.redshift?.e_z?.toFixed(6) || "NULL", - }, - ]; + const data = []; - const velocityColumns = [ - { name: "Parameter" }, - { name: "Value" }, - { name: "Unit" }, - { name: "Error" }, - { name: "Error unit" }, - ]; + if (catalogs?.designation?.name) { + data.push({ + Parameter: "Name", + Value: catalogs.designation.name, + }); + } - const velocityData = [ - { - Parameter: "Heliocentric", - Value: object.catalogs?.velocity?.heliocentric?.v?.toFixed(2) || "NULL", - Unit: schema.units.velocity?.heliocentric?.v || "NULL", - Error: object.catalogs?.velocity?.heliocentric?.e_v?.toFixed(2) || "NULL", - "Error unit": schema.units.velocity?.heliocentric?.e_v || "NULL", - }, - { - Parameter: "Local Group", - Value: object.catalogs?.velocity?.local_group?.v?.toFixed(2) || "NULL", - Unit: schema.units.velocity?.local_group?.v || "NULL", - Error: object.catalogs?.velocity?.local_group?.e_v?.toFixed(2) || "NULL", - "Error unit": schema.units.velocity?.local_group?.e_v || "NULL", - }, - { - Parameter: "CMB (old)", - Value: object.catalogs?.velocity?.cmb_old?.v?.toFixed(2) || "NULL", - Unit: schema.units.velocity?.cmb_old?.v || "NULL", - Error: object.catalogs?.velocity?.cmb_old?.e_v?.toFixed(2) || "NULL", - "Error unit": schema.units.velocity?.cmb_old?.e_v || "NULL", - }, - { - Parameter: "CMB", - Value: object.catalogs?.velocity?.cmb?.v?.toFixed(2) || "NULL", - Unit: schema.units.velocity?.cmb?.v || "NULL", - Error: object.catalogs?.velocity?.cmb?.e_v?.toFixed(2) || "NULL", - "Error unit": schema.units.velocity?.cmb?.e_v || "NULL", - }, - ]; + if (catalogs?.coordinates) { + data.push( + { + Parameter: "Equatorial RA", + Value: formatValueWithError( + catalogs.coordinates.equatorial?.ra, + catalogs.coordinates.equatorial?.e_ra, + schema.units.coordinates?.equatorial?.ra, + 2, + ), + }, + { + Parameter: "Equatorial Dec", + Value: formatValueWithError( + catalogs.coordinates.equatorial?.dec, + catalogs.coordinates.equatorial?.e_dec, + schema.units.coordinates?.equatorial?.dec, + 2, + ), + }, + { + Parameter: "Galactic l", + Value: formatValueWithError( + catalogs.coordinates.galactic?.lon, + catalogs.coordinates.galactic?.e_lon, + schema.units.coordinates?.galactic?.lon, + 2, + ), + }, + { + Parameter: "Galactic b", + Value: formatValueWithError( + catalogs.coordinates.galactic?.lat, + catalogs.coordinates.galactic?.e_lat, + schema.units.coordinates?.galactic?.lat, + 2, + ), + }, + ); + } - return ( -
- {object.catalogs?.coordinates && ( - -

Coordinates

-

Celestial coordinates of the object

-
- )} + if (catalogs?.redshift) { + data.push({ + Parameter: "Redshift z", + Value: formatValueWithError( + catalogs.redshift.z, + catalogs.redshift.e_z, + undefined, + 5, + ), + }); + } - {object.catalogs?.redshift && ( - -

Redshift

-

Redshift measurements

-
- )} + if (catalogs?.velocity) { + data.push( + { + Parameter: "Heliocentric Velocity", + Value: formatValueWithError( + catalogs.velocity.heliocentric?.v, + catalogs.velocity.heliocentric?.e_v, + schema.units.velocity?.heliocentric?.v, + ), + }, + { + Parameter: "Local Group Velocity", + Value: formatValueWithError( + catalogs.velocity.local_group?.v, + catalogs.velocity.local_group?.e_v, + schema.units.velocity?.local_group?.v, + ), + }, + { + Parameter: "CMB (old) Velocity", + Value: formatValueWithError( + catalogs.velocity.cmb_old?.v, + catalogs.velocity.cmb_old?.e_v, + schema.units.velocity?.cmb_old?.v, + ), + }, + { + Parameter: "CMB Velocity", + Value: formatValueWithError( + catalogs.velocity.cmb?.v, + catalogs.velocity.cmb?.e_v, + schema.units.velocity?.cmb?.v, + ), + }, + ); + } - {object.catalogs?.velocity && ( - -

Velocity

-

- Velocity measurements with respect to different apexes -

-
- )} -
- ); + return ; } diff --git a/src/components/ui/common-table.tsx b/src/components/ui/common-table.tsx index b8df151..0061498 100644 --- a/src/components/ui/common-table.tsx +++ b/src/components/ui/common-table.tsx @@ -52,7 +52,7 @@ export function CommonTable({ {children && (
diff --git a/src/components/ui/footer.tsx b/src/components/ui/footer.tsx index fa3947a..e99c5ef 100644 --- a/src/components/ui/footer.tsx +++ b/src/components/ui/footer.tsx @@ -7,10 +7,16 @@ import { MdKeyboardArrowDown, MdKeyboardArrowUp } from "react-icons/md"; const footerContent = (
- Information: + Information:{" "} + + The next generation of the HyperLeda database +
- Original version: + Original version:{" "} + + OHP Mirror +
); diff --git a/src/components/ui/hint.tsx b/src/components/ui/hint.tsx index 513e2e1..879182e 100644 --- a/src/components/ui/hint.tsx +++ b/src/components/ui/hint.tsx @@ -1,10 +1,10 @@ import { Tooltip } from "flowbite-react"; -import { ReactElement } from "react"; +import { ReactElement, ReactNode } from "react"; import { MdHelpOutline } from "react-icons/md"; interface HintProps { children: ReactElement; - hintContent: ReactElement; + hintContent: ReactNode; className?: string; } diff --git a/src/components/ui/link-button.tsx b/src/components/ui/link-button.tsx deleted file mode 100644 index e09d040..0000000 --- a/src/components/ui/link-button.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { ReactElement, ReactNode } from "react"; -import { MdOpenInNew } from "react-icons/md"; -import { Link } from "./link"; - -interface LinkButtonProps { - children?: ReactNode; - to: string; -} - -export function LinkButton(props: LinkButtonProps): ReactElement { - return ( -
-
{props.children}
- - - -
- ); -} diff --git a/src/components/ui/link.tsx b/src/components/ui/link.tsx index 7999d88..a4b37c6 100644 --- a/src/components/ui/link.tsx +++ b/src/components/ui/link.tsx @@ -1,25 +1,35 @@ -import { ReactElement } from "react"; +import { ReactElement, ReactNode } from "react"; +import { MdOpenInNew } from "react-icons/md"; interface LinkProps { - children?: ReactElement | string; + children?: ReactNode; href: string; className?: string; + external?: boolean; } export function Link(props: LinkProps): ReactElement { - const content = props.children ?? props.href; + const content = props.children; const baseClass = "text-green-500 hover:text-green-600 transition-colors"; const className = props.className ? `${baseClass} ${props.className}` : baseClass; + + const linkProps = props.external + ? { + target: "_blank", + rel: "noopener noreferrer", + } + : {}; + return ( {content} + {props.external && } ); } diff --git a/src/pages/CrossmatchResults.tsx b/src/pages/CrossmatchResults.tsx index c232026..0c8b6b3 100644 --- a/src/pages/CrossmatchResults.tsx +++ b/src/pages/CrossmatchResults.tsx @@ -20,7 +20,7 @@ import { getResource } from "../resources/resources"; import { Button } from "../components/ui/button"; import { Loading } from "../components/ui/loading"; import { ErrorPage, ErrorPageHomeButton } from "../components/ui/error-page"; -import { LinkButton } from "../components/ui/link-button"; +import { Link } from "../components/ui/link"; export function CrossmatchResultsPage(): ReactElement { const [searchParams, setSearchParams] = useSearchParams(); @@ -121,9 +121,9 @@ export function CrossmatchResultsPage(): ReactElement { function getRecordName(record: RecordCrossmatch): ReactElement { const displayName = record.catalogs.designation?.name || record.record_id; return ( - + {displayName} - + ); } @@ -224,15 +224,14 @@ export function CrossmatchResultsPage(): ReactElement {

Crossmatch results

- - - + +
- {object.catalogs?.coordinates?.equatorial && ( + {object.catalogs?.coordinates && ( {object.catalogs?.designation?.name || `PGC ${object.pgc}`} -

PGC: {object.pgc}

+

PGC: {object.pgc}

+ + OHP Mirror +
- - +
); } diff --git a/src/pages/RecordCrossmatchDetails.tsx b/src/pages/RecordCrossmatchDetails.tsx index a2ab362..a25d59a 100644 --- a/src/pages/RecordCrossmatchDetails.tsx +++ b/src/pages/RecordCrossmatchDetails.tsx @@ -2,31 +2,18 @@ import { ReactElement, useEffect, useState } from "react"; import { NavigateFunction, useNavigate, useParams } from "react-router-dom"; import { AladinViewer } from "../components/ui/aladin"; import { Loading } from "../components/ui/loading"; -import { - ErrorPage, - ErrorPageBackButton, - ErrorPageHomeButton, -} from "../components/ui/error-page"; +import { ErrorPage, ErrorPageHomeButton } from "../components/ui/error-page"; import { CatalogData } from "../components/ui/catalog-data"; import { getRecordCrossmatchAdminApiV1RecordCrossmatchGet } from "../clients/admin/sdk.gen"; import { GetRecordCrossmatchResponse, RecordCrossmatch, - RecordCrossmatchStatus, PgcCandidate, Schema as AdminSchema, } from "../clients/admin/types.gen"; -import { - PgcObject, - Schema as BackendSchema, -} from "../clients/backend/types.gen"; +import { Schema as BackendSchema } from "../clients/backend/types.gen"; import { getResource } from "../resources/resources"; - -function backToResultsHandler(navigate: NavigateFunction) { - return function f() { - navigate(-1); - }; -} +import { Link } from "../components/ui/link"; function renderNotFound(navigate: NavigateFunction) { return ( @@ -34,16 +21,12 @@ function renderNotFound(navigate: NavigateFunction) { title="Crossmatch Not Found" message="The requested crossmatch record could not be found." > - navigate("/")} /> ); } -function getStatusLabel(status: RecordCrossmatchStatus): string { - return getResource(`crossmatch.status.${status}`).Title; -} - +// TODO: remove when admin api uses the same structures as data api function convertAdminSchemaToBackendSchema( adminSchema: AdminSchema, ): BackendSchema { @@ -93,90 +76,110 @@ function convertAdminSchemaToBackendSchema( }; } -function convertToPgcObject(candidate: PgcCandidate): PgcObject { - return { - pgc: candidate.pgc, - catalogs: candidate.catalogs, - }; -} +function createDescription( + velocity?: { v: number; e_v: number } | null, + redshift?: { z: number; e_z: number } | null, +): string { + const parts = []; -function convertRecordToPgcObject(record: RecordCrossmatch): PgcObject { - return { - pgc: record.metadata.pgc || 0, - catalogs: record.catalogs, - }; + if (velocity) { + parts.push(`v: ${velocity.v.toFixed(1)} ± ${velocity.e_v.toFixed(1)} km/s`); + } + + if (redshift) { + parts.push(`z: ${redshift.z.toFixed(4)} ± ${redshift.e_z.toFixed(4)}`); + } + + return parts.join(", "); } -function convertCandidatesToAdditionalSources(candidates: PgcCandidate[]) { - return candidates +function convertCandidatesToAdditionalSources( + candidates: PgcCandidate[], + mainRecord: RecordCrossmatch, +) { + const candidateSources = candidates .filter((candidate) => candidate.catalogs?.coordinates?.equatorial) .map((candidate) => ({ ra: candidate.catalogs!.coordinates!.equatorial.ra, dec: candidate.catalogs!.coordinates!.equatorial.dec, label: `PGC ${candidate.pgc}`, + description: createDescription( + candidate.catalogs?.velocity?.heliocentric, + candidate.catalogs?.redshift, + ), })); + + const mainRecordSource = mainRecord.catalogs?.coordinates?.equatorial + ? { + ra: mainRecord.catalogs.coordinates.equatorial.ra, + dec: mainRecord.catalogs.coordinates.equatorial.dec, + label: + mainRecord.catalogs?.designation?.name || + `Record ${mainRecord.record_id}`, + } + : null; + + return mainRecordSource + ? [mainRecordSource, ...candidateSources] + : candidateSources; } function renderCrossmatchDetails( data: GetRecordCrossmatchResponse, ): ReactElement { const { crossmatch, candidates, schema } = data; - const recordObject = convertRecordToPgcObject(crossmatch); + const recordCatalogs = crossmatch.catalogs; const backendSchema = convertAdminSchemaToBackendSchema(schema); - const candidateSources = convertCandidatesToAdditionalSources(candidates); + const candidateSources = convertCandidatesToAdditionalSources( + candidates, + crossmatch, + ); return (
{crossmatch.catalogs?.coordinates?.equatorial && ( -
- - {candidateSources.length > 0 && ( -

- White labels show crossmatch candidates -

- )} -
+ )}
-

- Record ID: {crossmatch.record_id} -

-

- Status: {getStatusLabel(crossmatch.status)} -

- {crossmatch.metadata.pgc && ( -

PGC: {crossmatch.metadata.pgc}

+ {crossmatch.catalogs?.designation?.name && ( +

+ {crossmatch.catalogs.designation.name} +

)} +

+ Status:{" "} + {getResource(`crossmatch.status.${crossmatch.status}`).Title} +

-
-

Record Catalog Data

- -
+ {candidates.length > 0 && (

Crossmatch Candidates

- {candidates.map((candidate, index) => { - const candidateObject = convertToPgcObject(candidate); - return ( -
-

- Candidate {index + 1}: PGC {candidate.pgc} -

- -
- ); - })} + {candidates.map((candidate, index) => ( +
+

+ Candidate {index + 1}:{" "} + {`PGC ${candidate.pgc}`} +

+ +
+ ))}
)}
diff --git a/src/pages/TableDetails.tsx b/src/pages/TableDetails.tsx index 75c1b53..4343f2f 100644 --- a/src/pages/TableDetails.tsx +++ b/src/pages/TableDetails.tsx @@ -38,7 +38,10 @@ function renderBibliography(bib: Bibliography): ReactElement { return (
- {bib.bibcode} | {authors}: "{bib.title}" + + {bib.bibcode} + {" "} + | {authors}: "{bib.title}"
); @@ -220,7 +223,7 @@ function ColumnInfo(props: ColumnInfoProps): ReactElement {

Unified Content Descriptor. Describes astronomical quantities in a structured way. For more information see{" "} - + IVOA Recommendation .