-
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.
feat(components): add mutation over time plot for wastewater / WISE
- Loading branch information
1 parent
4eec1f5
commit 45d8674
Showing
11 changed files
with
4,561 additions
and
3,274 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
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 |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { type Dataset } from './Dataset'; | ||
import { type Operator } from './Operator'; | ||
import { fetchAggregated, fetchDetails } from '../lapisApi/lapisApi'; | ||
import { type AggregatedItem } from '../lapisApi/lapisTypes'; | ||
import { type LapisFilter } from '../types'; | ||
|
||
export class FetchDetailsOperator<Fields extends Record<string, unknown>> implements Operator<Fields> { | ||
constructor( | ||
private filter: LapisFilter, | ||
private fields: string[] = [], | ||
) {} | ||
|
||
async evaluate(lapisUrl: string, signal?: AbortSignal): Promise<Dataset<Fields>> { | ||
const detailsResponse = ( | ||
await fetchDetails( | ||
lapisUrl, | ||
{ | ||
...this.filter, | ||
fields: this.fields, | ||
}, | ||
signal, | ||
) | ||
).data; | ||
|
||
return { content: detailsResponse as Fields[] }; | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
...onents/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.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 |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { type Meta, type StoryObj } from '@storybook/preact'; | ||
|
||
import { WastewaterMutationsOverTime, type WastewaterMutationsOverTimeProps } from './wastewater-mutations-over-time'; | ||
import { WISE_LAPIS_URL } from '../../../constants'; | ||
import referenceGenome from '../../../lapisApi/__mockData__/referenceGenome.json'; | ||
import { LapisUrlContext } from '../../LapisUrlContext'; | ||
import { ReferenceGenomeContext } from '../../ReferenceGenomeContext'; | ||
|
||
const meta: Meta<WastewaterMutationsOverTimeProps> = { | ||
title: 'Visualization/Wastewater Mutation over time', | ||
component: WastewaterMutationsOverTime, | ||
argTypes: { | ||
width: { control: 'text' }, | ||
height: { control: 'text' }, | ||
locations: { control: 'object' }, | ||
}, | ||
parameters: { | ||
fetchMock: {}, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
|
||
const Template = { | ||
render: (args: WastewaterMutationsOverTimeProps) => ( | ||
<LapisUrlContext.Provider value={WISE_LAPIS_URL}> | ||
<ReferenceGenomeContext.Provider value={referenceGenome}> | ||
<WastewaterMutationsOverTime width={args.width} height={args.height} locations={args.locations} /> | ||
</ReferenceGenomeContext.Provider> | ||
</LapisUrlContext.Provider> | ||
), | ||
}; | ||
|
||
// This test uses mock data: defaultMockData.ts (through mutationOverTimeWorker.mock.ts) | ||
export const Default: StoryObj<WastewaterMutationsOverTimeProps> = { | ||
...Template, | ||
args: { | ||
width: '100%', | ||
height: '700px', | ||
locations: [], | ||
}, | ||
}; |
170 changes: 170 additions & 0 deletions
170
components/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.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 |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { type FunctionComponent } from 'preact'; | ||
import { type Dispatch, type StateUpdater, useContext, useState } from 'preact/hooks'; | ||
|
||
import { queryWastewaterData } from '../../../query/queryWastewaterData'; | ||
import { LapisUrlContext } from '../../LapisUrlContext'; | ||
import { type ColorScale } from '../../components/color-scale-selector'; | ||
import { ColorScaleSelectorDropdown } from '../../components/color-scale-selector-dropdown'; | ||
import { CsvDownloadButton } from '../../components/csv-download-button'; | ||
import { ErrorBoundary } from '../../components/error-boundary'; | ||
import { ErrorDisplay } from '../../components/error-display'; | ||
import { Fullscreen } from '../../components/fullscreen'; | ||
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../../components/info'; | ||
import { LoadingDisplay } from '../../components/loading-display'; | ||
import { NoDataDisplay } from '../../components/no-data-display'; | ||
import { ResizeContainer } from '../../components/resize-container'; | ||
import Tabs from '../../components/tabs'; | ||
import { | ||
BaseMutationOverTimeDataMap, | ||
type MutationOverTimeDataMap, | ||
} from '../../mutationsOverTime/MutationOverTimeData'; | ||
import MutationsOverTimeGrid from '../../mutationsOverTime/mutations-over-time-grid'; | ||
import { useQuery } from '../../useQuery'; | ||
|
||
export interface WastewaterMutationsOverTimeProps { | ||
width: string; | ||
height: string; | ||
locations: string[]; | ||
} | ||
|
||
export const WastewaterMutationsOverTime: FunctionComponent<WastewaterMutationsOverTimeProps> = (componentProps) => { | ||
const { width, height } = componentProps; | ||
const size = { height, width }; | ||
|
||
return ( | ||
<ErrorBoundary size={size}> | ||
<ResizeContainer size={size}> | ||
<WastewaterMutationsOverTimeInner {...componentProps} /> | ||
</ResizeContainer> | ||
</ErrorBoundary> | ||
); | ||
}; | ||
|
||
export const WastewaterMutationsOverTimeInner: FunctionComponent<WastewaterMutationsOverTimeProps> = ( | ||
componentProps, | ||
) => { | ||
const lapis = useContext(LapisUrlContext); | ||
|
||
const { data, error, isLoading } = useQuery(() => queryWastewaterData(lapis), []); // TODO fetch and transform data, filter by locations | ||
|
||
if (isLoading) { | ||
return <LoadingDisplay />; | ||
} | ||
|
||
if (error !== null) { | ||
return <ErrorDisplay error={error} />; | ||
} | ||
|
||
if (data === null || data === undefined) { | ||
return <NoDataDisplay />; | ||
} | ||
|
||
const locationMap = new Map<string, MutationOverTimeDataMap>(); | ||
for (const row of data) { | ||
if (componentProps.locations.length > 0 && !componentProps.locations.includes(row.location)) { | ||
continue; | ||
} | ||
if (!locationMap.has(row.location)) { | ||
locationMap.set(row.location, new BaseMutationOverTimeDataMap()); | ||
} | ||
const map = locationMap.get(row.location)!; | ||
for (const mutation of row.mutations) { | ||
map.set(mutation.mutation, row.date, { proportion: mutation.proportion, count: NaN, totalCount: NaN }); | ||
} | ||
} | ||
const mutationOverTimeDataPerLocation = [...locationMap.entries()].map(([location, data]) => ({ location, data })); | ||
|
||
return ( | ||
<MutationsOverTimeTabs | ||
mutationOverTimeDataPerLocation={mutationOverTimeDataPerLocation} | ||
originalComponentProps={componentProps} | ||
/> | ||
); | ||
}; | ||
|
||
type MutationOverTimeDataPerLocation = { | ||
location: string; | ||
data: MutationOverTimeDataMap; | ||
}[]; | ||
|
||
type MutationOverTimeTabsProps = { | ||
mutationOverTimeDataPerLocation: MutationOverTimeDataPerLocation; | ||
originalComponentProps: WastewaterMutationsOverTimeProps; | ||
}; | ||
|
||
const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({ | ||
mutationOverTimeDataPerLocation, | ||
originalComponentProps, | ||
}) => { | ||
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' }); | ||
|
||
const tabs = mutationOverTimeDataPerLocation.map(({ location, data }) => ({ | ||
title: location, | ||
content: <MutationsOverTimeGrid data={data} colorScale={colorScale} />, | ||
})); | ||
|
||
const toolbar = (activeTab: string) => ( | ||
<Toolbar | ||
activeTab={activeTab} | ||
colorScale={colorScale} | ||
setColorScale={setColorScale} | ||
originalComponentProps={originalComponentProps} | ||
data={mutationOverTimeDataPerLocation} | ||
/> | ||
); | ||
|
||
return <Tabs tabs={tabs} toolbar={toolbar} />; | ||
}; | ||
|
||
type ToolbarProps = { | ||
activeTab: string; | ||
colorScale: ColorScale; | ||
setColorScale: Dispatch<StateUpdater<ColorScale>>; | ||
originalComponentProps: WastewaterMutationsOverTimeProps; | ||
data: MutationOverTimeDataPerLocation; | ||
}; | ||
|
||
const Toolbar: FunctionComponent<ToolbarProps> = ({ | ||
activeTab, | ||
colorScale, | ||
setColorScale, | ||
originalComponentProps, | ||
data, | ||
}) => { | ||
return ( | ||
<> | ||
{activeTab === 'Grid' && ( | ||
<ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} /> | ||
)} | ||
<CsvDownloadButton | ||
className='mx-1 btn btn-xs' | ||
getData={() => getDownloadData(data)} | ||
filename='wastewater_mutations_over_time.csv' | ||
/> | ||
<WastewaterMutationsOverTimeInfo originalComponentProps={originalComponentProps} /> | ||
<Fullscreen /> | ||
</> | ||
); | ||
}; | ||
|
||
type WastewaterMutationsOverTimeInfoProps = { | ||
originalComponentProps: WastewaterMutationsOverTimeProps; | ||
}; | ||
|
||
const WastewaterMutationsOverTimeInfo: FunctionComponent<WastewaterMutationsOverTimeInfoProps> = ({ | ||
originalComponentProps, | ||
}) => { | ||
const lapis = useContext(LapisUrlContext); | ||
return ( | ||
<Info> | ||
<InfoHeadline1>Info for mutations over time</InfoHeadline1> | ||
<InfoParagraph>TODO: Ask for text</InfoParagraph> | ||
<InfoComponentCode componentName='mutations-over-time' params={originalComponentProps} lapisUrl={lapis} /> | ||
</Info> | ||
); | ||
}; | ||
|
||
function getDownloadData(data: MutationOverTimeDataPerLocation) { | ||
// TODO | ||
return []; | ||
} |
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 |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { parseDateStringToTemporal, TemporalClass, toTemporalClass } from '../utils/temporalClass'; | ||
import { FetchDetailsOperator } from '../operator/FetchDetailsOperator'; | ||
import { Substitution, SubstitutionClass } from '../utils/mutations'; | ||
|
||
export type WastewaterData = { | ||
location: string; | ||
date: TemporalClass; | ||
mutations: { mutation: Substitution; proportion: number }[]; | ||
}[]; | ||
|
||
export async function queryWastewaterData(lapis: string, signal?: AbortSignal): Promise<WastewaterData> { | ||
const fetchData = new FetchDetailsOperator<Record<string, string | null | number>>({}, [ | ||
'date', | ||
'location', | ||
'mutations', | ||
]); | ||
const data = (await fetchData.evaluate(lapis, signal)).content; | ||
|
||
return data.map((row) => ({ | ||
location: row.location as string, | ||
date: toTemporalClass(parseDateStringToTemporal(row.date as string, 'day')), | ||
mutations: transformMutations(JSON.parse(row.mutations as string)), | ||
})); | ||
} | ||
|
||
function transformMutations(input: Record<string, number>): { mutation: Substitution; proportion: number }[] { | ||
return Object.entries(input).map(([key, value]) => ({ | ||
mutation: SubstitutionClass.parse(key)!, | ||
proportion: value, | ||
})); | ||
} |
53 changes: 53 additions & 0 deletions
53
components/src/web-components/visualization/gs-wastewater-mutations-over-time.stories.ts
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 |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import type { Meta, StoryObj } from '@storybook/web-components'; | ||
import { html } from 'lit'; | ||
|
||
import './gs-wastewater-mutations-over-time'; | ||
import '../app'; | ||
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock'; | ||
import { WISE_LAPIS_URL } from '../../constants'; | ||
import { type WastewaterMutationsOverTimeProps } from '../../preact/wastewater/mutationsOverTime/wastewater-mutations-over-time'; | ||
|
||
const codeExample = String.raw` | ||
<gs-wastewater-mutations-over-time | ||
locations='[]' | ||
width='100%' | ||
height='700px' | ||
></gs-wastewater-mutations-over-time>`; | ||
|
||
const meta: Meta<Required<WastewaterMutationsOverTimeProps>> = { | ||
title: 'Visualization/Wastewater mutations over time', | ||
component: 'gs-wastewater-mutations-over-time', | ||
argTypes: { | ||
locations: { control: 'object' }, | ||
width: { control: 'text' }, | ||
height: { control: 'text' }, | ||
}, | ||
args: { | ||
locations: [], | ||
width: '100%', | ||
height: '700px', | ||
}, | ||
parameters: withComponentDocs({ | ||
componentDocs: { | ||
opensShadowDom: true, | ||
expectsChildren: false, | ||
codeExample, | ||
}, | ||
fetchMock: {}, | ||
}), | ||
tags: ['autodocs'], | ||
}; | ||
|
||
export default meta; | ||
|
||
export const Default: StoryObj<Required<WastewaterMutationsOverTimeProps>> = { | ||
render: (args) => html` | ||
<gs-app lapis="${WISE_LAPIS_URL}"> | ||
<gs-wastewater-mutations-over-time | ||
.views=${args.views} | ||
.width=${args.width} | ||
.height=${args.height} | ||
></gs-wastewater-mutations-over-time> | ||
</gs-app> | ||
`, | ||
}; |
Oops, something went wrong.