forked from Sage-Bionetworks/synapse-web-monorepo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move TimelinePlot to TimelinePlotWithSpecies, and wrap it with the sp…
…ecies selector component
- Loading branch information
1 parent
bd0b27e
commit c9881da
Showing
7 changed files
with
306 additions
and
184 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 18 additions & 5 deletions
23
packages/synapse-react-client/src/components/TimelinePlot/TimelinePlot.integration.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
233 changes: 58 additions & 175 deletions
233
packages/synapse-react-client/src/components/TimelinePlot/TimelinePlot.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,202 +1,85 @@ | ||
import React from 'react' | ||
import React, { useState, useEffect } from 'react' | ||
import { useGetFullTableQueryResults } from '../../synapse-queries' | ||
import { BUNDLE_MASK_QUERY_RESULTS } from '../../utils/SynapseConstants' | ||
import hardcodedPhasesQueryResponseData, { | ||
phaseObservationIndex, | ||
phaseSpeciesIndex, | ||
} from './phasesQueryResponseData' | ||
import TimelinePhase from './TimelinePhase' | ||
import getColorPalette from '../ColorGradient/ColorGradient' | ||
import { Box } from '@mui/system' | ||
import { ColumnSingleValueFilterOperator } from '@sage-bionetworks/synapse-types' | ||
import { ObservationCardSchema } from '../row_renderers/ObservationCard' | ||
import { parseEntityIdFromSqlStatement } from '../../utils/functions' | ||
import { SizeMe } from 'react-sizeme' | ||
import TimelineLegendItem from './TimelineLegendItem' | ||
import { Skeleton } from '@mui/material' | ||
|
||
const OBSERVATION_PHASE_COLUMN_NAME = 'phase' | ||
const OBSERVATION_TIME_COLUMN_NAME = 'time' | ||
const OBSERVATION_TIME_UNITS_COLUMN_NAME = 'timeunits' | ||
const OBSERVATION_SUBMITTER_NAME_COLUMN_NAME = 'submittername' | ||
const OBSERVATION_TEXT_COLUMN_NAME = 'text' | ||
const OBSERVATION_TYPE_COLUMN_NAME = 'tag' | ||
const OBSERVATION_SUBMITTER_USER_ID_COLUMN_NAME = 'submitteruserid' | ||
import { | ||
SQLOperator, | ||
getAdditionalFilters, | ||
parseEntityIdFromSqlStatement, | ||
} from '../../utils/functions' | ||
import { InputLabel, Select, MenuItem } from '@mui/material' | ||
import { StyledFormControl } from '../styled' | ||
import TimelinePlotWithSpecies from './TimelinePlotWithSpecies' | ||
|
||
export type TimelinePlotProps = { | ||
species: string | ||
resourceId: string | ||
observationsSql: string | ||
sql: string | ||
searchParams?: Record<string, string> | ||
sqlOperator?: SQLOperator | ||
} | ||
export const TimelinePlot = ({ | ||
observationsSql, | ||
species, | ||
resourceId, | ||
sql, | ||
searchParams, | ||
sqlOperator, | ||
}: TimelinePlotProps) => { | ||
// Fetch the table data | ||
const eventsTableId = parseEntityIdFromSqlStatement(observationsSql) | ||
const eventsTableId = parseEntityIdFromSqlStatement(sql) | ||
const [species, setSpecies] = useState<string | undefined | null>() | ||
const queryFilters = getAdditionalFilters( | ||
eventsTableId, | ||
searchParams, | ||
sqlOperator, | ||
) | ||
// Fetch the species | ||
const eventTableQuery = useGetFullTableQueryResults({ | ||
entityId: eventsTableId, | ||
query: { | ||
sql: `${observationsSql} WHERE observationTime IS NOT NULL`, | ||
sort: [ | ||
{ | ||
column: 'observationTime', | ||
direction: 'ASC', | ||
}, | ||
], | ||
additionalFilters: [ | ||
{ | ||
columnName: 'resourceId', | ||
concreteType: | ||
'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter', | ||
values: [resourceId], | ||
operator: ColumnSingleValueFilterOperator.EQUAL, | ||
}, | ||
{ | ||
columnName: 'species', | ||
concreteType: | ||
'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter', | ||
values: [species], | ||
operator: ColumnSingleValueFilterOperator.EQUAL, | ||
}, | ||
], | ||
sql: `SELECT species FROM ${eventsTableId} WHERE species IS NOT null GROUP BY species`, | ||
additionalFilters: queryFilters, | ||
}, | ||
|
||
partMask: BUNDLE_MASK_QUERY_RESULTS, | ||
concreteType: 'org.sagebionetworks.repo.model.table.QueryBundleRequest', | ||
}) | ||
|
||
const { data: eventsData, isLoading } = eventTableQuery | ||
|
||
if (isLoading) { | ||
return <LoadingTimelinePlot /> | ||
} | ||
const observationPhaseIndex = | ||
eventsData?.queryResult?.queryResults.headers.findIndex( | ||
header => header.name.toLowerCase() === OBSERVATION_PHASE_COLUMN_NAME, | ||
)! | ||
const observationTimeIndex = | ||
eventsData?.queryResult?.queryResults.headers.findIndex( | ||
header => header.name.toLowerCase() === OBSERVATION_TIME_COLUMN_NAME, | ||
)! | ||
const observationTimeUnitIndex = | ||
eventsData?.queryResult?.queryResults.headers.findIndex( | ||
header => | ||
header.name.toLowerCase() === OBSERVATION_TIME_UNITS_COLUMN_NAME, | ||
)! | ||
const observationSubmitterNameIndex = | ||
eventsData?.queryResult?.queryResults.headers.findIndex( | ||
header => | ||
header.name.toLowerCase() === OBSERVATION_SUBMITTER_NAME_COLUMN_NAME, | ||
)! | ||
const observationTextIndex = | ||
eventsData?.queryResult?.queryResults.headers.findIndex( | ||
header => header.name.toLowerCase() === OBSERVATION_TEXT_COLUMN_NAME, | ||
)! | ||
const observationTypeIndex = | ||
eventsData?.queryResult?.queryResults.headers.findIndex( | ||
header => header.name.toLowerCase() === OBSERVATION_TYPE_COLUMN_NAME, | ||
)! | ||
const submitterUserIdIndex = | ||
eventsData?.queryResult?.queryResults.headers.findIndex( | ||
header => | ||
header.name.toLowerCase() === OBSERVATION_SUBMITTER_USER_ID_COLUMN_NAME, | ||
)! | ||
|
||
const schema: ObservationCardSchema = { | ||
submitterName: observationSubmitterNameIndex, | ||
submitterUserId: submitterUserIdIndex, | ||
tag: observationTypeIndex, | ||
text: observationTextIndex, | ||
time: observationTimeIndex, | ||
timeUnits: observationTimeUnitIndex, | ||
} | ||
const { data: speciesData, isLoading } = eventTableQuery | ||
const rows = speciesData?.queryResult?.queryResults?.rows | ||
|
||
// filter the phases query response data to the specific species | ||
const phasesForTargetSpecies = | ||
hardcodedPhasesQueryResponseData.queryResult?.queryResults.rows.filter( | ||
row => { | ||
return row.values[phaseSpeciesIndex] == species | ||
}, | ||
) | ||
// then walk through the phases and create a plot for each (iff event data exists for that phase!) | ||
useEffect(() => { | ||
if (rows) { | ||
setSpecies(rows[0].values[0]) | ||
} | ||
}, [rows]) | ||
|
||
if (!phasesForTargetSpecies || phasesForTargetSpecies.length == 0) { | ||
if (isLoading || !rows || rows.length == 0) { | ||
return <></> | ||
} | ||
|
||
const phaseRowsWithData = phasesForTargetSpecies.filter(phaseRow => { | ||
const phaseEventRows = eventsData?.queryResult?.queryResults.rows.filter( | ||
row => { | ||
return ( | ||
row.values[observationPhaseIndex] == | ||
phaseRow.values[phaseObservationIndex] | ||
) | ||
}, | ||
) | ||
return phaseEventRows?.length && phaseEventRows?.length > 0 | ||
}) | ||
return ( | ||
<Box> | ||
{/* Legend */} | ||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: '25px' }}> | ||
{phaseRowsWithData.map((phaseRow, index) => { | ||
const { colorPalette } = getColorPalette(index, 1) | ||
return ( | ||
<TimelineLegendItem | ||
key={phaseRow.rowId} | ||
color={colorPalette[0]} | ||
phaseName={phaseRow.values[phaseObservationIndex]} | ||
/> | ||
) | ||
})} | ||
</Box> | ||
{/* Phase plots */} | ||
<SizeMe refreshMode="debounce" noPlaceholder={true}> | ||
{({ size }) => ( | ||
<Box sx={{ display: 'flex' }} className="forcePlotlyDefaultCursor"> | ||
{phaseRowsWithData.map((phaseRow, index) => { | ||
const { colorPalette } = getColorPalette(index, 1) | ||
const phaseEventRows = | ||
eventsData?.queryResult?.queryResults.rows.filter(row => { | ||
return ( | ||
row.values[observationPhaseIndex] == | ||
phaseRow.values[phaseObservationIndex] | ||
) | ||
}) | ||
return ( | ||
<TimelinePhase | ||
key={phaseRow.rowId} | ||
name={phaseRow.values[phaseObservationIndex]!} | ||
color={colorPalette[0]} | ||
rowData={phaseEventRows!} | ||
schema={schema} | ||
widthPx={ | ||
size.width ? size.width / phaseRowsWithData.length : 0 | ||
} | ||
/> | ||
) | ||
})} | ||
</Box> | ||
)} | ||
</SizeMe> | ||
<StyledFormControl> | ||
<InputLabel>Species</InputLabel> | ||
<Select | ||
value={species} | ||
defaultValue={rows[0].values[0]} | ||
label="Species" | ||
onChange={event => setSpecies(event.target.value)} | ||
> | ||
{rows?.map(row => { | ||
const species = row.values[0]! | ||
return ( | ||
<MenuItem key={species} value={species}> | ||
{species} | ||
</MenuItem> | ||
) | ||
})} | ||
</Select> | ||
</StyledFormControl> | ||
{species && ( | ||
<TimelinePlotWithSpecies | ||
observationsSql={sql} | ||
species={species} | ||
additionalFilters={queryFilters} | ||
/> | ||
)} | ||
</Box> | ||
) | ||
} | ||
|
||
const LoadingTimelinePlot = () => { | ||
return ( | ||
<Box> | ||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}> | ||
<Skeleton height="45px" width="80px" /> | ||
<Skeleton height="45px" width="80px" /> | ||
</Box> | ||
<Box sx={{ display: 'flex' }}> | ||
<Skeleton height="150px" width="100%" /> | ||
</Box> | ||
</Box> | ||
) | ||
} | ||
|
||
export default TimelinePlot |
Oops, something went wrong.