@genspectrum/dashboard-components 0.11.3 → 0.11.5
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 +16 -0
- package/dist/assets/{mutationOverTimeWorker-Cr-NmYEs.js.map → mutationOverTimeWorker-CWneD7i5.js.map} +1 -1
- package/dist/components.d.ts +44 -44
- package/dist/components.js +193 -162
- package/dist/components.js.map +1 -1
- package/dist/style.css +4 -6
- package/dist/util.d.ts +44 -44
- package/package.json +2 -2
- package/src/preact/aggregatedData/aggregate.tsx +1 -1
- package/src/preact/components/tabs.tsx +19 -39
- package/src/preact/map/__mockData__/aggregatedGermany.json +5 -1
- package/src/preact/map/{useGeoJsonMap.tsx → loadMapSource.tsx} +7 -24
- package/src/preact/map/sequences-by-location-map.tsx +13 -99
- package/src/preact/map/sequences-by-location-table.tsx +28 -5
- package/src/preact/map/sequences-by-location.tsx +32 -13
- package/src/preact/useQuery.ts +1 -1
- package/src/query/computeMapLocationData.spec.ts +103 -0
- package/src/query/computeMapLocationData.ts +136 -0
- package/src/query/querySequencesByLocationData.ts +18 -0
- package/src/utilEntrypoint.ts +1 -1
- package/src/utils/temporal.spec.ts +62 -8
- package/src/utils/temporalClass.ts +1 -8
- package/src/web-components/visualization/gs-sequences-by-location.stories.ts +17 -0
- package/standalone-bundle/assets/{mutationOverTimeWorker-DIQRmxvC.js.map → mutationOverTimeWorker-x1ipPFL0.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +4003 -3962
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
package/dist/style.css
CHANGED
|
@@ -482,7 +482,7 @@ input[type="range"] {
|
|
|
482
482
|
--tw-contain-paint: ;
|
|
483
483
|
--tw-contain-style: ;
|
|
484
484
|
}/*
|
|
485
|
-
! tailwindcss v3.4.
|
|
485
|
+
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
|
|
486
486
|
*//*
|
|
487
487
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
488
488
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
@@ -1154,6 +1154,7 @@ html {
|
|
|
1154
1154
|
display: grid;
|
|
1155
1155
|
width: 100%;
|
|
1156
1156
|
overflow: hidden;
|
|
1157
|
+
direction: ltr;
|
|
1157
1158
|
container-type: inline-size;
|
|
1158
1159
|
grid-template-columns: auto 1fr;
|
|
1159
1160
|
}
|
|
@@ -3154,9 +3155,6 @@ input.tab:checked + .tab-content,
|
|
|
3154
3155
|
.cursor-pointer {
|
|
3155
3156
|
cursor: pointer;
|
|
3156
3157
|
}
|
|
3157
|
-
.resize {
|
|
3158
|
-
resize: both;
|
|
3159
|
-
}
|
|
3160
3158
|
.list-inside {
|
|
3161
3159
|
list-style-position: inside;
|
|
3162
3160
|
}
|
|
@@ -3193,8 +3191,8 @@ input.tab:checked + .tab-content,
|
|
|
3193
3191
|
.gap-y-1 {
|
|
3194
3192
|
row-gap: 0.25rem;
|
|
3195
3193
|
}
|
|
3196
|
-
.overflow-
|
|
3197
|
-
overflow:
|
|
3194
|
+
.overflow-scroll {
|
|
3195
|
+
overflow: scroll;
|
|
3198
3196
|
}
|
|
3199
3197
|
.overflow-x-auto {
|
|
3200
3198
|
overflow-x: auto;
|
package/dist/util.d.ts
CHANGED
|
@@ -787,10 +787,7 @@ declare global {
|
|
|
787
787
|
|
|
788
788
|
declare global {
|
|
789
789
|
interface HTMLElementTagNameMap {
|
|
790
|
-
'gs-
|
|
791
|
-
}
|
|
792
|
-
interface HTMLElementEventMap {
|
|
793
|
-
'gs-location-changed': CustomEvent<Record<string, string>>;
|
|
790
|
+
'gs-mutation-comparison-component': MutationComparisonComponent;
|
|
794
791
|
}
|
|
795
792
|
}
|
|
796
793
|
|
|
@@ -798,7 +795,7 @@ declare global {
|
|
|
798
795
|
declare global {
|
|
799
796
|
namespace JSX {
|
|
800
797
|
interface IntrinsicElements {
|
|
801
|
-
'gs-
|
|
798
|
+
'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
802
799
|
}
|
|
803
800
|
}
|
|
804
801
|
}
|
|
@@ -806,11 +803,7 @@ declare global {
|
|
|
806
803
|
|
|
807
804
|
declare global {
|
|
808
805
|
interface HTMLElementTagNameMap {
|
|
809
|
-
'gs-
|
|
810
|
-
}
|
|
811
|
-
interface HTMLElementEventMap {
|
|
812
|
-
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
813
|
-
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
806
|
+
'gs-mutations-component': MutationsComponent;
|
|
814
807
|
}
|
|
815
808
|
}
|
|
816
809
|
|
|
@@ -818,7 +811,7 @@ declare global {
|
|
|
818
811
|
declare global {
|
|
819
812
|
namespace JSX {
|
|
820
813
|
interface IntrinsicElements {
|
|
821
|
-
'gs-
|
|
814
|
+
'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
822
815
|
}
|
|
823
816
|
}
|
|
824
817
|
}
|
|
@@ -826,10 +819,7 @@ declare global {
|
|
|
826
819
|
|
|
827
820
|
declare global {
|
|
828
821
|
interface HTMLElementTagNameMap {
|
|
829
|
-
'gs-
|
|
830
|
-
}
|
|
831
|
-
interface HTMLElementEventMap {
|
|
832
|
-
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
822
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
833
823
|
}
|
|
834
824
|
}
|
|
835
825
|
|
|
@@ -837,7 +827,7 @@ declare global {
|
|
|
837
827
|
declare global {
|
|
838
828
|
namespace JSX {
|
|
839
829
|
interface IntrinsicElements {
|
|
840
|
-
'gs-
|
|
830
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
841
831
|
}
|
|
842
832
|
}
|
|
843
833
|
}
|
|
@@ -845,10 +835,7 @@ declare global {
|
|
|
845
835
|
|
|
846
836
|
declare global {
|
|
847
837
|
interface HTMLElementTagNameMap {
|
|
848
|
-
'gs-
|
|
849
|
-
}
|
|
850
|
-
interface HTMLElementEventMap {
|
|
851
|
-
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
838
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
852
839
|
}
|
|
853
840
|
}
|
|
854
841
|
|
|
@@ -856,7 +843,7 @@ declare global {
|
|
|
856
843
|
declare global {
|
|
857
844
|
namespace JSX {
|
|
858
845
|
interface IntrinsicElements {
|
|
859
|
-
'gs-
|
|
846
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
860
847
|
}
|
|
861
848
|
}
|
|
862
849
|
}
|
|
@@ -864,10 +851,7 @@ declare global {
|
|
|
864
851
|
|
|
865
852
|
declare global {
|
|
866
853
|
interface HTMLElementTagNameMap {
|
|
867
|
-
'gs-
|
|
868
|
-
}
|
|
869
|
-
interface HTMLElementEventMap {
|
|
870
|
-
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
854
|
+
'gs-aggregate': AggregateComponent;
|
|
871
855
|
}
|
|
872
856
|
}
|
|
873
857
|
|
|
@@ -875,7 +859,7 @@ declare global {
|
|
|
875
859
|
declare global {
|
|
876
860
|
namespace JSX {
|
|
877
861
|
interface IntrinsicElements {
|
|
878
|
-
'gs-
|
|
862
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
879
863
|
}
|
|
880
864
|
}
|
|
881
865
|
}
|
|
@@ -883,7 +867,7 @@ declare global {
|
|
|
883
867
|
|
|
884
868
|
declare global {
|
|
885
869
|
interface HTMLElementTagNameMap {
|
|
886
|
-
'gs-
|
|
870
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
887
871
|
}
|
|
888
872
|
}
|
|
889
873
|
|
|
@@ -891,7 +875,7 @@ declare global {
|
|
|
891
875
|
declare global {
|
|
892
876
|
namespace JSX {
|
|
893
877
|
interface IntrinsicElements {
|
|
894
|
-
'gs-
|
|
878
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
895
879
|
}
|
|
896
880
|
}
|
|
897
881
|
}
|
|
@@ -899,7 +883,7 @@ declare global {
|
|
|
899
883
|
|
|
900
884
|
declare global {
|
|
901
885
|
interface HTMLElementTagNameMap {
|
|
902
|
-
'gs-
|
|
886
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
903
887
|
}
|
|
904
888
|
}
|
|
905
889
|
|
|
@@ -907,7 +891,7 @@ declare global {
|
|
|
907
891
|
declare global {
|
|
908
892
|
namespace JSX {
|
|
909
893
|
interface IntrinsicElements {
|
|
910
|
-
'gs-
|
|
894
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
911
895
|
}
|
|
912
896
|
}
|
|
913
897
|
}
|
|
@@ -915,7 +899,7 @@ declare global {
|
|
|
915
899
|
|
|
916
900
|
declare global {
|
|
917
901
|
interface HTMLElementTagNameMap {
|
|
918
|
-
'gs-
|
|
902
|
+
'gs-sequences-by-location': SequencesByLocationComponent;
|
|
919
903
|
}
|
|
920
904
|
}
|
|
921
905
|
|
|
@@ -923,7 +907,7 @@ declare global {
|
|
|
923
907
|
declare global {
|
|
924
908
|
namespace JSX {
|
|
925
909
|
interface IntrinsicElements {
|
|
926
|
-
'gs-
|
|
910
|
+
'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
927
911
|
}
|
|
928
912
|
}
|
|
929
913
|
}
|
|
@@ -931,7 +915,7 @@ declare global {
|
|
|
931
915
|
|
|
932
916
|
declare global {
|
|
933
917
|
interface HTMLElementTagNameMap {
|
|
934
|
-
'gs-
|
|
918
|
+
'gs-statistics': StatisticsComponent;
|
|
935
919
|
}
|
|
936
920
|
}
|
|
937
921
|
|
|
@@ -939,7 +923,7 @@ declare global {
|
|
|
939
923
|
declare global {
|
|
940
924
|
namespace JSX {
|
|
941
925
|
interface IntrinsicElements {
|
|
942
|
-
'gs-
|
|
926
|
+
'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
943
927
|
}
|
|
944
928
|
}
|
|
945
929
|
}
|
|
@@ -947,7 +931,11 @@ declare global {
|
|
|
947
931
|
|
|
948
932
|
declare global {
|
|
949
933
|
interface HTMLElementTagNameMap {
|
|
950
|
-
'gs-
|
|
934
|
+
'gs-date-range-selector': DateRangeSelectorComponent;
|
|
935
|
+
}
|
|
936
|
+
interface HTMLElementEventMap {
|
|
937
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
938
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
951
939
|
}
|
|
952
940
|
}
|
|
953
941
|
|
|
@@ -955,7 +943,7 @@ declare global {
|
|
|
955
943
|
declare global {
|
|
956
944
|
namespace JSX {
|
|
957
945
|
interface IntrinsicElements {
|
|
958
|
-
'gs-
|
|
946
|
+
'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
959
947
|
}
|
|
960
948
|
}
|
|
961
949
|
}
|
|
@@ -963,7 +951,10 @@ declare global {
|
|
|
963
951
|
|
|
964
952
|
declare global {
|
|
965
953
|
interface HTMLElementTagNameMap {
|
|
966
|
-
'gs-
|
|
954
|
+
'gs-location-filter': LocationFilterComponent;
|
|
955
|
+
}
|
|
956
|
+
interface HTMLElementEventMap {
|
|
957
|
+
'gs-location-changed': CustomEvent<Record<string, string>>;
|
|
967
958
|
}
|
|
968
959
|
}
|
|
969
960
|
|
|
@@ -971,7 +962,7 @@ declare global {
|
|
|
971
962
|
declare global {
|
|
972
963
|
namespace JSX {
|
|
973
964
|
interface IntrinsicElements {
|
|
974
|
-
'gs-
|
|
965
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
975
966
|
}
|
|
976
967
|
}
|
|
977
968
|
}
|
|
@@ -979,7 +970,10 @@ declare global {
|
|
|
979
970
|
|
|
980
971
|
declare global {
|
|
981
972
|
interface HTMLElementTagNameMap {
|
|
982
|
-
'gs-
|
|
973
|
+
'gs-text-input': TextInputComponent;
|
|
974
|
+
}
|
|
975
|
+
interface HTMLElementEventMap {
|
|
976
|
+
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
983
977
|
}
|
|
984
978
|
}
|
|
985
979
|
|
|
@@ -987,7 +981,7 @@ declare global {
|
|
|
987
981
|
declare global {
|
|
988
982
|
namespace JSX {
|
|
989
983
|
interface IntrinsicElements {
|
|
990
|
-
'gs-
|
|
984
|
+
'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
991
985
|
}
|
|
992
986
|
}
|
|
993
987
|
}
|
|
@@ -995,7 +989,10 @@ declare global {
|
|
|
995
989
|
|
|
996
990
|
declare global {
|
|
997
991
|
interface HTMLElementTagNameMap {
|
|
998
|
-
'gs-
|
|
992
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
993
|
+
}
|
|
994
|
+
interface HTMLElementEventMap {
|
|
995
|
+
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
999
996
|
}
|
|
1000
997
|
}
|
|
1001
998
|
|
|
@@ -1003,7 +1000,7 @@ declare global {
|
|
|
1003
1000
|
declare global {
|
|
1004
1001
|
namespace JSX {
|
|
1005
1002
|
interface IntrinsicElements {
|
|
1006
|
-
'gs-
|
|
1003
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1007
1004
|
}
|
|
1008
1005
|
}
|
|
1009
1006
|
}
|
|
@@ -1011,7 +1008,10 @@ declare global {
|
|
|
1011
1008
|
|
|
1012
1009
|
declare global {
|
|
1013
1010
|
interface HTMLElementTagNameMap {
|
|
1014
|
-
'gs-
|
|
1011
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1012
|
+
}
|
|
1013
|
+
interface HTMLElementEventMap {
|
|
1014
|
+
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1015
1015
|
}
|
|
1016
1016
|
}
|
|
1017
1017
|
|
|
@@ -1019,7 +1019,7 @@ declare global {
|
|
|
1019
1019
|
declare global {
|
|
1020
1020
|
namespace JSX {
|
|
1021
1021
|
interface IntrinsicElements {
|
|
1022
|
-
'gs-
|
|
1022
|
+
'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1023
1023
|
}
|
|
1024
1024
|
}
|
|
1025
1025
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
4
4
|
"description": "GenSpectrum web components for building dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@storybook/preact": "^8.0.9",
|
|
110
110
|
"@storybook/preact-vite": "^8.0.9",
|
|
111
111
|
"@storybook/test": "^8.0.0",
|
|
112
|
-
"@storybook/test-runner": "^0.
|
|
112
|
+
"@storybook/test-runner": "^0.21.0",
|
|
113
113
|
"@storybook/types": "^8.0.9",
|
|
114
114
|
"@storybook/web-components": "^8.0.9",
|
|
115
115
|
"@storybook/web-components-vite": "^8.0.9",
|
|
@@ -53,7 +53,7 @@ export const AggregateInner: FunctionComponent<AggregateProps> = (componentProps
|
|
|
53
53
|
field: initialSortField,
|
|
54
54
|
direction: initialSortDirection,
|
|
55
55
|
});
|
|
56
|
-
}, [lapisFilter, fields, lapis]);
|
|
56
|
+
}, [lapisFilter, fields, lapis, initialSortField, initialSortDirection]);
|
|
57
57
|
|
|
58
58
|
if (isLoading) {
|
|
59
59
|
return <LoadingDisplay />;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
import { useState } from 'preact/hooks';
|
|
3
3
|
import { type JSXInternal } from 'preact/src/jsx';
|
|
4
4
|
|
|
5
5
|
type Tab = {
|
|
@@ -14,43 +14,24 @@ interface ComponentTabsProps {
|
|
|
14
14
|
|
|
15
15
|
const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
|
|
16
16
|
const [activeTab, setActiveTab] = useState(tabs[0]?.title);
|
|
17
|
-
const [heightOfTabs, setHeightOfTabs] = useState('3rem');
|
|
18
|
-
const tabRef = useRef<HTMLDivElement>(null);
|
|
19
|
-
|
|
20
|
-
const updateHeightOfTabs = () => {
|
|
21
|
-
if (tabRef.current) {
|
|
22
|
-
const heightOfTabs = tabRef.current.getBoundingClientRect().height;
|
|
23
|
-
setHeightOfTabs(`${heightOfTabs}px`);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
updateHeightOfTabs();
|
|
29
|
-
|
|
30
|
-
window.addEventListener('resize', updateHeightOfTabs);
|
|
31
|
-
return () => {
|
|
32
|
-
window.removeEventListener('resize', updateHeightOfTabs);
|
|
33
|
-
};
|
|
34
|
-
}, []);
|
|
35
17
|
|
|
36
18
|
const tabElements = (
|
|
37
19
|
<div className='flex flex-row'>
|
|
38
20
|
{tabs.map((tab) => {
|
|
39
21
|
return (
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</Fragment>
|
|
22
|
+
<button
|
|
23
|
+
key={tab.title}
|
|
24
|
+
className={`px-4 py-2 text-sm font-medium leading-5 transition-colors duration-150 ${
|
|
25
|
+
activeTab === tab.title
|
|
26
|
+
? 'border-b-2 border-gray-400'
|
|
27
|
+
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-700'
|
|
28
|
+
}`}
|
|
29
|
+
onClick={() => {
|
|
30
|
+
setActiveTab(tab.title);
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
{tab.title}
|
|
34
|
+
</button>
|
|
54
35
|
);
|
|
55
36
|
})}
|
|
56
37
|
</div>
|
|
@@ -59,17 +40,16 @@ const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
|
|
|
59
40
|
const toolbarElement = typeof toolbar === 'function' ? toolbar(activeTab) : toolbar;
|
|
60
41
|
|
|
61
42
|
return (
|
|
62
|
-
<div className='h-full w-full'>
|
|
63
|
-
<div
|
|
43
|
+
<div className='h-full w-full flex flex-col'>
|
|
44
|
+
<div className='flex flex-row justify-between flex-wrap'>
|
|
64
45
|
{tabElements}
|
|
65
46
|
{toolbar && <div className='py-2 flex flex-wrap gap-y-1'>{toolbarElement}</div>}
|
|
66
47
|
</div>
|
|
67
48
|
<div
|
|
68
|
-
className={`p-2 border-2 border-gray-100 rounded-b-md rounded-tr-md ${activeTab === tabs[0]?.title ? '' : 'rounded-tl-md'}`}
|
|
69
|
-
style={{ height: `calc(100% - ${heightOfTabs})` }}
|
|
49
|
+
className={`p-2 flex-grow overflow-scroll border-2 border-gray-100 rounded-b-md rounded-tr-md ${activeTab === tabs[0]?.title ? '' : 'rounded-tl-md'}`}
|
|
70
50
|
>
|
|
71
51
|
{tabs.map((tab) => (
|
|
72
|
-
<div className='h-full
|
|
52
|
+
<div className='h-full' key={tab.title} hidden={activeTab !== tab.title}>
|
|
73
53
|
{tab.content}
|
|
74
54
|
</div>
|
|
75
55
|
))}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"division": "Saxony-Anhalt"
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
|
-
"count":
|
|
48
|
+
"count": 109799,
|
|
49
49
|
"division": null
|
|
50
50
|
},
|
|
51
51
|
{
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
"count": 50650,
|
|
57
57
|
"division": "Baden-Wuerttemberg"
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
"count": 1,
|
|
61
|
+
"division": "Baden-Württemberg"
|
|
62
|
+
},
|
|
59
63
|
{
|
|
60
64
|
"count": 50102,
|
|
61
65
|
"division": "North Rhine Westphalia"
|
|
@@ -4,7 +4,6 @@ import type { GeometryCollection, Topology } from 'topojson-specification';
|
|
|
4
4
|
import z from 'zod';
|
|
5
5
|
|
|
6
6
|
import { UserFacingError } from '../components/error-display';
|
|
7
|
-
import { useQuery } from '../useQuery';
|
|
8
7
|
|
|
9
8
|
export const mapSourceSchema = z.object({
|
|
10
9
|
type: z.literal('topojson'),
|
|
@@ -13,33 +12,17 @@ export const mapSourceSchema = z.object({
|
|
|
13
12
|
});
|
|
14
13
|
export type MapSource = z.infer<typeof mapSourceSchema>;
|
|
15
14
|
|
|
16
|
-
export function useGeoJsonMap(mapSource: MapSource) {
|
|
17
|
-
const {
|
|
18
|
-
data: geojsonData,
|
|
19
|
-
error,
|
|
20
|
-
isLoading,
|
|
21
|
-
} = useQuery(async () => {
|
|
22
|
-
switch (mapSource.type) {
|
|
23
|
-
case 'topojson':
|
|
24
|
-
return await loadTopojsonMap(mapSource);
|
|
25
|
-
}
|
|
26
|
-
}, [mapSource]);
|
|
27
|
-
|
|
28
|
-
if (isLoading) {
|
|
29
|
-
return { isLoading };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (error) {
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { geojsonData, isLoading: false as const };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
15
|
export type GeoJsonFeatureProperties = {
|
|
40
16
|
name: string;
|
|
41
17
|
};
|
|
42
18
|
|
|
19
|
+
export async function loadMapSource(mapSource: MapSource) {
|
|
20
|
+
switch (mapSource.type) {
|
|
21
|
+
case 'topojson':
|
|
22
|
+
return await loadTopojsonMap(mapSource);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
43
26
|
async function loadTopojsonMap(
|
|
44
27
|
mapSource: MapSource,
|
|
45
28
|
): Promise<FeatureCollection<GeometryObject, GeoJsonFeatureProperties>> {
|
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import type { Feature,
|
|
1
|
+
import type { Feature, Geometry, GeometryObject } from 'geojson';
|
|
2
2
|
import Leaflet, { type Layer, type LayerGroup } from 'leaflet';
|
|
3
3
|
import type { FunctionComponent } from 'preact';
|
|
4
|
-
import { useEffect,
|
|
4
|
+
import { useEffect, useRef } from 'preact/hooks';
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import { type AggregateData } from '../../query/queryAggregateData';
|
|
6
|
+
import type { EnhancedGeoJsonFeatureProperties } from '../../query/computeMapLocationData';
|
|
8
7
|
import { InfoHeadline1, InfoParagraph } from '../components/info';
|
|
9
|
-
import { LoadingDisplay } from '../components/loading-display';
|
|
10
8
|
import { Modal, useModalRef } from '../components/modal';
|
|
11
9
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
12
10
|
|
|
13
|
-
type FeatureData = { proportion: number; count: number };
|
|
14
|
-
|
|
15
|
-
type EnhancedGeoJsonFeatureProperties = GeoJsonFeatureProperties & {
|
|
16
|
-
data: FeatureData | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
11
|
type SequencesByLocationMapProps = {
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
locations: Feature<Geometry, EnhancedGeoJsonFeatureProperties>[];
|
|
13
|
+
totalCount: number;
|
|
14
|
+
countOfMatchedLocationData: number;
|
|
15
|
+
nullCount: number;
|
|
16
|
+
unmatchedLocations: string[];
|
|
22
17
|
enableMapNavigation: boolean;
|
|
23
18
|
lapisLocationField: string;
|
|
24
19
|
zoom: number;
|
|
@@ -28,25 +23,11 @@ type SequencesByLocationMapProps = {
|
|
|
28
23
|
};
|
|
29
24
|
|
|
30
25
|
export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapProps> = ({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (isLoadingMap) {
|
|
37
|
-
return <LoadingDisplay />;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return <SequencesByLocationMapInner geojsonData={geojsonData} {...otherProps} />;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
type SequencesByLocationMapInnerProps = Omit<SequencesByLocationMapProps, 'mapSource'> & {
|
|
44
|
-
geojsonData: FeatureCollection<GeometryObject, GeoJsonFeatureProperties>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationMapInnerProps> = ({
|
|
48
|
-
geojsonData,
|
|
49
|
-
locationData,
|
|
26
|
+
locations,
|
|
27
|
+
totalCount,
|
|
28
|
+
countOfMatchedLocationData,
|
|
29
|
+
nullCount,
|
|
30
|
+
unmatchedLocations,
|
|
50
31
|
enableMapNavigation,
|
|
51
32
|
lapisLocationField,
|
|
52
33
|
zoom,
|
|
@@ -56,22 +37,6 @@ export const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationM
|
|
|
56
37
|
}) => {
|
|
57
38
|
const ref = useRef<HTMLDivElement>(null);
|
|
58
39
|
|
|
59
|
-
const { locations, totalCount, countOfMatchedLocationData, unmatchedLocations } = useMemo(() => {
|
|
60
|
-
const countAndProportionByCountry = buildLookupByLocationField(locationData, lapisLocationField);
|
|
61
|
-
const { locations, unmatchedLocations } = matchLocationDataAndGeoJsonFeatures(
|
|
62
|
-
geojsonData,
|
|
63
|
-
countAndProportionByCountry,
|
|
64
|
-
lapisLocationField,
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const totalCount = locationData.map((value) => value.count).reduce((sum, b) => sum + b, 0);
|
|
68
|
-
const countOfMatchedLocationData = locations
|
|
69
|
-
.map((location) => location.properties.data?.count ?? 0)
|
|
70
|
-
.reduce((sum, b) => sum + b, 0);
|
|
71
|
-
|
|
72
|
-
return { locations, totalCount, countOfMatchedLocationData, unmatchedLocations };
|
|
73
|
-
}, [geojsonData, locationData, lapisLocationField]);
|
|
74
|
-
|
|
75
40
|
useEffect(() => {
|
|
76
41
|
if (!ref.current) {
|
|
77
42
|
return;
|
|
@@ -103,8 +68,6 @@ export const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationM
|
|
|
103
68
|
};
|
|
104
69
|
}, [ref, locations, enableMapNavigation, lapisLocationField, zoom, offsetX, offsetY]);
|
|
105
70
|
|
|
106
|
-
const nullCount = locationData.find((row) => row[lapisLocationField] === null)?.count ?? 0;
|
|
107
|
-
|
|
108
71
|
return (
|
|
109
72
|
<div className='h-full'>
|
|
110
73
|
<div ref={ref} className='h-full' />
|
|
@@ -172,55 +135,6 @@ const DataMatchInformation: FunctionComponent<DataMatchInformationProps> = ({
|
|
|
172
135
|
);
|
|
173
136
|
};
|
|
174
137
|
|
|
175
|
-
function buildLookupByLocationField(locationData: AggregateData, lapisLocationField: string) {
|
|
176
|
-
return new Map<string, FeatureData>(
|
|
177
|
-
locationData
|
|
178
|
-
.filter((row) => typeof row[lapisLocationField] === 'string')
|
|
179
|
-
.map((row) => [row[lapisLocationField] as string, row]),
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function matchLocationDataAndGeoJsonFeatures(
|
|
184
|
-
geojsonData: FeatureCollection<GeometryObject, GeoJsonFeatureProperties>,
|
|
185
|
-
countAndProportionByCountry: Map<string, FeatureData>,
|
|
186
|
-
lapisLocationField: string,
|
|
187
|
-
) {
|
|
188
|
-
const matchedLocations: string[] = [];
|
|
189
|
-
|
|
190
|
-
const locations: Feature<GeometryObject, EnhancedGeoJsonFeatureProperties>[] = geojsonData.features.map(
|
|
191
|
-
(feature) => {
|
|
192
|
-
const name = feature?.properties?.name;
|
|
193
|
-
if (typeof name !== 'string') {
|
|
194
|
-
throw new Error(
|
|
195
|
-
`GeoJSON feature with id '${feature.id}' does not have 'properties.name' of type string, was: '${name}'`,
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const data = countAndProportionByCountry.get(name) ?? null;
|
|
200
|
-
if (data !== null) {
|
|
201
|
-
matchedLocations.push(name);
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
...feature,
|
|
205
|
-
properties: {
|
|
206
|
-
...feature.properties,
|
|
207
|
-
data,
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
},
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
const unmatchedLocations = [...countAndProportionByCountry.keys()].filter(
|
|
214
|
-
(name) => !matchedLocations.includes(name),
|
|
215
|
-
);
|
|
216
|
-
if (unmatchedLocations.length > 0) {
|
|
217
|
-
const unmatchedLocationsWarning = `gs-map: Found location data from LAPIS (aggregated by "${lapisLocationField}") that could not be matched on locations on the given map. Unmatched location names are: ${unmatchedLocations.map((it) => `"${it}"`).join(', ')}`;
|
|
218
|
-
console.warn(unmatchedLocationsWarning); // eslint-disable-line no-console -- We should give some feedback about unmatched location data.
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return { locations, unmatchedLocations };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
138
|
function getColor(value: number | undefined): string {
|
|
225
139
|
if (value === undefined) {
|
|
226
140
|
return '#DDDDDD';
|