@genspectrum/dashboard-components 0.11.0 → 0.11.2
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/custom-elements.json +3 -3
- package/dist/assets/mutationOverTimeWorker-Cr-NmYEs.js.map +1 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +112 -14
- package/dist/components.js.map +1 -1
- package/dist/style.css +19 -0
- package/dist/util.d.ts +20 -4
- package/package.json +1 -1
- package/src/preact/map/sequences-by-location-map.tsx +98 -10
- package/src/preact/map/sequences-by-location.tsx +1 -0
- package/src/utilEntrypoint.ts +1 -0
- package/src/web-components/visualization/gs-sequences-by-location.stories.ts +4 -4
- package/src/web-components/visualization/gs-sequences-by-location.tsx +1 -1
- package/standalone-bundle/assets/mutationOverTimeWorker-DIQRmxvC.js.map +1 -0
- package/standalone-bundle/dashboard-components.js +2234 -2153
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/assets/mutationOverTimeWorker-BjjkMGzd.js.map +0 -1
- package/standalone-bundle/assets/mutationOverTimeWorker-DoUBht2e.js.map +0 -1
package/dist/style.css
CHANGED
|
@@ -2684,6 +2684,9 @@ input.tab:checked + .tab-content,
|
|
|
2684
2684
|
border-end-end-radius: inherit;
|
|
2685
2685
|
border-start-end-radius: inherit;
|
|
2686
2686
|
}
|
|
2687
|
+
.modal-middle {
|
|
2688
|
+
place-items: center;
|
|
2689
|
+
}
|
|
2687
2690
|
.modal-bottom {
|
|
2688
2691
|
place-items: end;
|
|
2689
2692
|
}
|
|
@@ -2957,6 +2960,9 @@ input.tab:checked + .tab-content,
|
|
|
2957
2960
|
.-top-3 {
|
|
2958
2961
|
top: -0.75rem;
|
|
2959
2962
|
}
|
|
2963
|
+
.bottom-0 {
|
|
2964
|
+
bottom: 0px;
|
|
2965
|
+
}
|
|
2960
2966
|
.bottom-full {
|
|
2961
2967
|
bottom: 100%;
|
|
2962
2968
|
}
|
|
@@ -2993,6 +2999,9 @@ input.tab:checked + .tab-content,
|
|
|
2993
2999
|
.z-10 {
|
|
2994
3000
|
z-index: 10;
|
|
2995
3001
|
}
|
|
3002
|
+
.z-\[1001\] {
|
|
3003
|
+
z-index: 1001;
|
|
3004
|
+
}
|
|
2996
3005
|
.float-right {
|
|
2997
3006
|
float: right;
|
|
2998
3007
|
}
|
|
@@ -3122,6 +3131,9 @@ input.tab:checked + .tab-content,
|
|
|
3122
3131
|
.min-w-\[7\.5rem\] {
|
|
3123
3132
|
min-width: 7.5rem;
|
|
3124
3133
|
}
|
|
3134
|
+
.max-w-3xl {
|
|
3135
|
+
max-width: 48rem;
|
|
3136
|
+
}
|
|
3125
3137
|
.max-w-screen-lg {
|
|
3126
3138
|
max-width: 1024px;
|
|
3127
3139
|
}
|
|
@@ -3199,6 +3211,9 @@ input.tab:checked + .tab-content,
|
|
|
3199
3211
|
.break-words {
|
|
3200
3212
|
overflow-wrap: break-word;
|
|
3201
3213
|
}
|
|
3214
|
+
.rounded {
|
|
3215
|
+
border-radius: 0.25rem;
|
|
3216
|
+
}
|
|
3202
3217
|
.rounded-lg {
|
|
3203
3218
|
border-radius: 0.5rem;
|
|
3204
3219
|
}
|
|
@@ -3272,6 +3287,10 @@ input.tab:checked + .tab-content,
|
|
|
3272
3287
|
.p-4 {
|
|
3273
3288
|
padding: 1rem;
|
|
3274
3289
|
}
|
|
3290
|
+
.px-1 {
|
|
3291
|
+
padding-left: 0.25rem;
|
|
3292
|
+
padding-right: 0.25rem;
|
|
3293
|
+
}
|
|
3275
3294
|
.px-4 {
|
|
3276
3295
|
padding-left: 1rem;
|
|
3277
3296
|
padding-right: 1rem;
|
package/dist/util.d.ts
CHANGED
|
@@ -157,6 +157,22 @@ declare const lapisFilterSchema: default_2.ZodIntersection<default_2.ZodRecord<d
|
|
|
157
157
|
aminoAcidInsertions?: string[] | undefined;
|
|
158
158
|
}>>;
|
|
159
159
|
|
|
160
|
+
export declare type MapSource = default_2.infer<typeof mapSourceSchema>;
|
|
161
|
+
|
|
162
|
+
declare const mapSourceSchema: default_2.ZodObject<{
|
|
163
|
+
type: default_2.ZodLiteral<"topojson">;
|
|
164
|
+
url: default_2.ZodString;
|
|
165
|
+
topologyObjectsKey: default_2.ZodString;
|
|
166
|
+
}, "strip", default_2.ZodTypeAny, {
|
|
167
|
+
type: "topojson";
|
|
168
|
+
url: string;
|
|
169
|
+
topologyObjectsKey: string;
|
|
170
|
+
}, {
|
|
171
|
+
type: "topojson";
|
|
172
|
+
url: string;
|
|
173
|
+
topologyObjectsKey: string;
|
|
174
|
+
}>;
|
|
175
|
+
|
|
160
176
|
export declare type MutationComparisonProps = default_2.infer<typeof mutationComparisonPropsSchema>;
|
|
161
177
|
|
|
162
178
|
declare const mutationComparisonPropsSchema: default_2.ZodObject<{
|
|
@@ -835,7 +851,7 @@ declare global {
|
|
|
835
851
|
|
|
836
852
|
declare global {
|
|
837
853
|
interface HTMLElementTagNameMap {
|
|
838
|
-
'gs-
|
|
854
|
+
'gs-aggregate': AggregateComponent;
|
|
839
855
|
}
|
|
840
856
|
}
|
|
841
857
|
|
|
@@ -843,7 +859,7 @@ declare global {
|
|
|
843
859
|
declare global {
|
|
844
860
|
namespace JSX {
|
|
845
861
|
interface IntrinsicElements {
|
|
846
|
-
'gs-
|
|
862
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
847
863
|
}
|
|
848
864
|
}
|
|
849
865
|
}
|
|
@@ -851,7 +867,7 @@ declare global {
|
|
|
851
867
|
|
|
852
868
|
declare global {
|
|
853
869
|
interface HTMLElementTagNameMap {
|
|
854
|
-
'gs-
|
|
870
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
855
871
|
}
|
|
856
872
|
}
|
|
857
873
|
|
|
@@ -859,7 +875,7 @@ declare global {
|
|
|
859
875
|
declare global {
|
|
860
876
|
namespace JSX {
|
|
861
877
|
interface IntrinsicElements {
|
|
862
|
-
'gs-
|
|
878
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
863
879
|
}
|
|
864
880
|
}
|
|
865
881
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Feature, FeatureCollection, GeometryObject } from 'geojson';
|
|
2
2
|
import Leaflet, { type Layer, type LayerGroup } from 'leaflet';
|
|
3
3
|
import type { FunctionComponent } from 'preact';
|
|
4
|
-
import { useEffect, useRef } from 'preact/hooks';
|
|
4
|
+
import { useEffect, useMemo, useRef } from 'preact/hooks';
|
|
5
5
|
|
|
6
6
|
import { type GeoJsonFeatureProperties, type MapSource, useGeoJsonMap } from './useGeoJsonMap';
|
|
7
7
|
import { type AggregateData } from '../../query/queryAggregateData';
|
|
8
|
+
import { InfoHeadline1, InfoParagraph } from '../components/info';
|
|
8
9
|
import { LoadingDisplay } from '../components/loading-display';
|
|
9
10
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
10
11
|
|
|
@@ -22,6 +23,7 @@ type SequencesByLocationMapProps = {
|
|
|
22
23
|
zoom: number;
|
|
23
24
|
offsetX: number;
|
|
24
25
|
offsetY: number;
|
|
26
|
+
hasTableView: boolean;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapProps> = ({
|
|
@@ -49,21 +51,31 @@ export const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationM
|
|
|
49
51
|
zoom,
|
|
50
52
|
offsetX,
|
|
51
53
|
offsetY,
|
|
54
|
+
hasTableView,
|
|
52
55
|
}) => {
|
|
53
56
|
const ref = useRef<HTMLDivElement>(null);
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
if (!ref.current || geojsonData === undefined || locationData === undefined) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
58
|
+
const { locations, totalCount, countOfMatchedLocationData, unmatchedLocations } = useMemo(() => {
|
|
60
59
|
const countAndProportionByCountry = buildLookupByLocationField(locationData, lapisLocationField);
|
|
61
|
-
const locations = matchLocationDataAndGeoJsonFeatures(
|
|
60
|
+
const { locations, unmatchedLocations } = matchLocationDataAndGeoJsonFeatures(
|
|
62
61
|
geojsonData,
|
|
63
62
|
countAndProportionByCountry,
|
|
64
63
|
lapisLocationField,
|
|
65
64
|
);
|
|
66
65
|
|
|
66
|
+
const totalCount = locationData.map((value) => value.count).reduce((sum, b) => sum + b, 0);
|
|
67
|
+
const countOfMatchedLocationData = locations
|
|
68
|
+
.map((location) => location.properties.data?.count ?? 0)
|
|
69
|
+
.reduce((sum, b) => sum + b, 0);
|
|
70
|
+
|
|
71
|
+
return { locations, totalCount, countOfMatchedLocationData, unmatchedLocations };
|
|
72
|
+
}, [geojsonData, locationData, lapisLocationField]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!ref.current) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
const leafletMap = Leaflet.map(ref.current, {
|
|
68
80
|
scrollWheelZoom: enableMapNavigation,
|
|
69
81
|
zoomControl: enableMapNavigation,
|
|
@@ -88,9 +100,85 @@ export const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationM
|
|
|
88
100
|
return () => {
|
|
89
101
|
leafletMap.remove();
|
|
90
102
|
};
|
|
91
|
-
}, [ref,
|
|
103
|
+
}, [ref, locations, enableMapNavigation, lapisLocationField, zoom, offsetX, offsetY]);
|
|
92
104
|
|
|
93
|
-
|
|
105
|
+
const nullCount = locationData.find((row) => row[lapisLocationField] === null)?.count ?? 0;
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div className='h-full'>
|
|
109
|
+
<div ref={ref} className='h-full' />
|
|
110
|
+
<div className='relative'>
|
|
111
|
+
<DataMatchInformation
|
|
112
|
+
totalCount={totalCount}
|
|
113
|
+
countOfMatchedLocationData={countOfMatchedLocationData}
|
|
114
|
+
unmatchedLocations={unmatchedLocations}
|
|
115
|
+
nullCount={nullCount}
|
|
116
|
+
hasTableView={hasTableView}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
type DataMatchInformationProps = {
|
|
124
|
+
totalCount: number;
|
|
125
|
+
countOfMatchedLocationData: number;
|
|
126
|
+
unmatchedLocations: string[];
|
|
127
|
+
nullCount: number;
|
|
128
|
+
hasTableView: boolean;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const DataMatchInformation: FunctionComponent<DataMatchInformationProps> = ({
|
|
132
|
+
totalCount,
|
|
133
|
+
countOfMatchedLocationData,
|
|
134
|
+
unmatchedLocations,
|
|
135
|
+
nullCount,
|
|
136
|
+
hasTableView,
|
|
137
|
+
}) => {
|
|
138
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
139
|
+
|
|
140
|
+
const proportion = formatProportion(countOfMatchedLocationData / totalCount);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
<button
|
|
145
|
+
onClick={() => dialogRef.current?.showModal()}
|
|
146
|
+
className='text-sm absolute bottom-0 px-1 z-[1001] bg-white rounded border cursor-pointer tooltip'
|
|
147
|
+
data-tip='Click for detailed information'
|
|
148
|
+
>
|
|
149
|
+
This map shows {proportion} of the data.
|
|
150
|
+
</button>
|
|
151
|
+
<dialog ref={dialogRef} className={'modal modal-middle'}>
|
|
152
|
+
<div className='modal-box max-w-3xl'>
|
|
153
|
+
<InfoHeadline1>Sequences By Location - Map View</InfoHeadline1>
|
|
154
|
+
<InfoParagraph>
|
|
155
|
+
The current filter has matched {totalCount.toLocaleString('en-us')} sequences. From these
|
|
156
|
+
sequences, we were able to match {countOfMatchedLocationData.toLocaleString('en-us')} (
|
|
157
|
+
{proportion}) on locations on the map.
|
|
158
|
+
</InfoParagraph>
|
|
159
|
+
<InfoParagraph>
|
|
160
|
+
{unmatchedLocations.length > 0 && (
|
|
161
|
+
<>
|
|
162
|
+
The following locations from the data could not be matched on the map:{' '}
|
|
163
|
+
{unmatchedLocations.map((it) => `"${it}"`).join(', ')}.{' '}
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
166
|
+
{nullCount > 0 &&
|
|
167
|
+
`${nullCount.toLocaleString('en-us')} matching sequences have no location information. `}
|
|
168
|
+
{hasTableView && 'You can check the table view for more detailed information.'}
|
|
169
|
+
</InfoParagraph>
|
|
170
|
+
<div className='modal-action'>
|
|
171
|
+
<form method='dialog'>
|
|
172
|
+
<button className={'float-right underline text-sm hover:text-blue-700 mr-2'}>Close</button>
|
|
173
|
+
</form>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<form method='dialog' className='modal-backdrop'>
|
|
177
|
+
<button>Helper to close when clicked outside</button>
|
|
178
|
+
</form>
|
|
179
|
+
</dialog>
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
94
182
|
};
|
|
95
183
|
|
|
96
184
|
function buildLookupByLocationField(locationData: AggregateData, lapisLocationField: string) {
|
|
@@ -139,7 +227,7 @@ function matchLocationDataAndGeoJsonFeatures(
|
|
|
139
227
|
console.warn(unmatchedLocationsWarning); // eslint-disable-line no-console -- We should give some feedback about unmatched location data.
|
|
140
228
|
}
|
|
141
229
|
|
|
142
|
-
return locations;
|
|
230
|
+
return { locations, unmatchedLocations };
|
|
143
231
|
}
|
|
144
232
|
|
|
145
233
|
function getColor(value: number | undefined): string {
|
|
@@ -98,6 +98,7 @@ const SequencesByLocationMapTabs: FunctionComponent<SequencesByLocationMapTabsPr
|
|
|
98
98
|
zoom={originalComponentProps.zoom}
|
|
99
99
|
offsetX={originalComponentProps.offsetX}
|
|
100
100
|
offsetY={originalComponentProps.offsetY}
|
|
101
|
+
hasTableView={originalComponentProps.views.includes(views.table)}
|
|
101
102
|
/>
|
|
102
103
|
),
|
|
103
104
|
};
|
package/src/utilEntrypoint.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type {
|
|
|
27
27
|
RelativeGrowthAdvantageProps,
|
|
28
28
|
} from './preact/relativeGrowthAdvantage/relative-growth-advantage';
|
|
29
29
|
export type { StatisticsProps } from './preact/statistic/statistics';
|
|
30
|
+
export type { MapSource } from './preact/map/useGeoJsonMap';
|
|
30
31
|
|
|
31
32
|
export type { ConfidenceIntervalMethod } from './preact/shared/charts/confideceInterval';
|
|
32
33
|
|
|
@@ -17,7 +17,7 @@ const codeExample = `<gs-sequences-by-location
|
|
|
17
17
|
lapisFilter='{"dateFrom":"2022-01-01","dateTo":"2022-04-01"}'
|
|
18
18
|
lapisLocationField='country'
|
|
19
19
|
mapSource='{"type":"topojson","url":"https://mock.map.data/topo.json","topologyObjectsKey":"countries"}'
|
|
20
|
-
enableMapNavigation
|
|
20
|
+
enableMapNavigation
|
|
21
21
|
width='1100px'
|
|
22
22
|
height='800px'
|
|
23
23
|
views='["map"]'
|
|
@@ -25,7 +25,7 @@ const codeExample = `<gs-sequences-by-location
|
|
|
25
25
|
offsetX='0'
|
|
26
26
|
offsetY='10'
|
|
27
27
|
pageSize='5'
|
|
28
|
-
|
|
28
|
+
></gs-sequences-by-location>`;
|
|
29
29
|
|
|
30
30
|
const meta: Meta<Required<SequencesByLocationProps>> = {
|
|
31
31
|
title: 'Visualization/Sequences by location',
|
|
@@ -84,7 +84,7 @@ const Template: StoryObj<SequencesByLocationProps> = {
|
|
|
84
84
|
args: {
|
|
85
85
|
enableMapNavigation: false,
|
|
86
86
|
width: '1100px',
|
|
87
|
-
height: '
|
|
87
|
+
height: '700px',
|
|
88
88
|
views: ['map', 'table'],
|
|
89
89
|
pageSize: 10,
|
|
90
90
|
},
|
|
@@ -150,7 +150,7 @@ export const Germany: StoryObj<SequencesByLocationProps> = {
|
|
|
150
150
|
topologyObjectsKey: 'deu',
|
|
151
151
|
},
|
|
152
152
|
views: ['map', 'table'],
|
|
153
|
-
zoom: 6
|
|
153
|
+
zoom: 6,
|
|
154
154
|
offsetX: 10,
|
|
155
155
|
offsetY: 51.4,
|
|
156
156
|
},
|
|
@@ -130,7 +130,7 @@ export class SequencesByLocationComponent extends PreactLitAdapterWithGridJsStyl
|
|
|
130
130
|
* Enable map navigation (dragging, keyboard navigation, zooming).
|
|
131
131
|
*/
|
|
132
132
|
@property({ type: Boolean })
|
|
133
|
-
enableMapNavigation: boolean =
|
|
133
|
+
enableMapNavigation: boolean = false;
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
136
|
* The width of the component.
|