@genspectrum/dashboard-components 0.19.2 → 0.19.3
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 +160 -10
- package/dist/{LineageFilterChangedEvent-ixHQkq8y.js → LineageFilterChangedEvent-b0iuroUL.js} +15 -5
- package/dist/LineageFilterChangedEvent-b0iuroUL.js.map +1 -0
- package/dist/assets/mutationOverTimeWorker-ChQTFL68.js.map +1 -1
- package/dist/components.d.ts +71 -25
- package/dist/components.js +9047 -8699
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +51 -25
- package/dist/util.js +2 -1
- package/package.json +1 -1
- package/src/componentsEntrypoint.ts +3 -1
- package/src/preact/components/error-display.stories.tsx +2 -1
- package/src/preact/components/error-display.tsx +2 -3
- package/src/preact/components/resize-container.tsx +7 -10
- package/src/preact/components/tooltip.tsx +7 -4
- package/src/preact/dateRangeFilter/date-range-filter.stories.tsx +5 -4
- package/src/preact/dateRangeFilter/date-range-filter.tsx +2 -1
- package/src/preact/dateRangeFilter/dateRangeOption.ts +2 -1
- package/src/preact/genomeViewer/CDSPlot.tsx +219 -0
- package/src/preact/genomeViewer/genome-data-viewer.stories.tsx +113 -0
- package/src/preact/genomeViewer/genome-data-viewer.tsx +69 -0
- package/src/preact/genomeViewer/loadGff3.spec.ts +61 -0
- package/src/preact/genomeViewer/loadGff3.ts +174 -0
- package/src/preact/lineageFilter/LineageFilterChangedEvent.ts +3 -1
- package/src/preact/lineageFilter/lineage-filter.stories.tsx +3 -2
- package/src/preact/locationFilter/LocationChangedEvent.ts +2 -1
- package/src/preact/locationFilter/location-filter.stories.tsx +3 -2
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +3 -2
- package/src/preact/mutationFilter/mutation-filter.tsx +2 -1
- package/src/preact/shared/charts/colors.ts +1 -1
- package/src/preact/textFilter/TextFilterChangedEvent.ts +3 -1
- package/src/preact/textFilter/text-filter.stories.tsx +4 -3
- package/src/utilEntrypoint.ts +2 -0
- package/src/utils/gsEventNames.ts +9 -0
- package/src/web-components/input/gs-date-range-filter.stories.ts +4 -3
- package/src/web-components/input/gs-date-range-filter.tsx +3 -2
- package/src/web-components/input/gs-lineage-filter.stories.ts +3 -2
- package/src/web-components/input/gs-lineage-filter.tsx +2 -1
- package/src/web-components/input/gs-location-filter.stories.ts +3 -2
- package/src/web-components/input/gs-location-filter.tsx +2 -1
- package/src/web-components/input/gs-mutation-filter.stories.ts +3 -2
- package/src/web-components/input/gs-mutation-filter.tsx +2 -1
- package/src/web-components/input/gs-text-filter.stories.ts +3 -2
- package/src/web-components/input/gs-text-filter.tsx +2 -1
- package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +18 -0
- package/src/web-components/visualization/gs-genome-data-viewer.stories.ts +108 -0
- package/src/web-components/visualization/gs-genome-data-viewer.tsx +59 -0
- package/src/web-components/visualization/index.ts +1 -0
- package/standalone-bundle/assets/mutationOverTimeWorker-jChgWnwp.js.map +1 -1
- package/standalone-bundle/dashboard-components.js +8275 -8002
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/LineageFilterChangedEvent-ixHQkq8y.js.map +0 -1
package/dist/util.d.ts
CHANGED
|
@@ -149,6 +149,16 @@ declare const dateRangeValueSchema: default_2.ZodNullable<default_2.ZodUnion<[de
|
|
|
149
149
|
dateTo?: string | undefined;
|
|
150
150
|
}>]>>;
|
|
151
151
|
|
|
152
|
+
export declare const gsEventNames: {
|
|
153
|
+
readonly error: "gs-error";
|
|
154
|
+
readonly dateRangeFilterChanged: "gs-date-range-filter-changed";
|
|
155
|
+
readonly dateRangeOptionChanged: "gs-date-range-option-changed";
|
|
156
|
+
readonly mutationFilterChanged: "gs-mutation-filter-changed";
|
|
157
|
+
readonly lineageFilterChanged: "gs-lineage-filter-changed";
|
|
158
|
+
readonly locationChanged: "gs-location-changed";
|
|
159
|
+
readonly textFilterChanged: "gs-text-filter-changed";
|
|
160
|
+
};
|
|
161
|
+
|
|
152
162
|
export declare type LapisFilter = default_2.infer<typeof lapisFilterSchema>;
|
|
153
163
|
|
|
154
164
|
declare const lapisFilterSchema: default_2.ZodIntersection<default_2.ZodRecord<default_2.ZodString, default_2.ZodUnion<[default_2.ZodString, default_2.ZodArray<default_2.ZodString, "many">, default_2.ZodNumber, default_2.ZodNull, default_2.ZodBoolean, default_2.ZodUndefined]>>, default_2.ZodObject<{
|
|
@@ -879,7 +889,7 @@ export { }
|
|
|
879
889
|
|
|
880
890
|
declare global {
|
|
881
891
|
interface HTMLElementEventMap {
|
|
882
|
-
|
|
892
|
+
[gsEventNames.error]: ErrorEvent;
|
|
883
893
|
}
|
|
884
894
|
}
|
|
885
895
|
|
|
@@ -900,6 +910,22 @@ declare global {
|
|
|
900
910
|
}
|
|
901
911
|
|
|
902
912
|
|
|
913
|
+
declare global {
|
|
914
|
+
interface HTMLElementTagNameMap {
|
|
915
|
+
'gs-genome-data-viewer': GenomeDataViewerComponent;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
declare global {
|
|
921
|
+
namespace JSX {
|
|
922
|
+
interface IntrinsicElements {
|
|
923
|
+
'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
|
|
903
929
|
declare global {
|
|
904
930
|
interface HTMLElementTagNameMap {
|
|
905
931
|
'gs-mutation-comparison-component': MutationComparisonComponent;
|
|
@@ -934,7 +960,7 @@ declare global {
|
|
|
934
960
|
|
|
935
961
|
declare global {
|
|
936
962
|
interface HTMLElementTagNameMap {
|
|
937
|
-
'gs-
|
|
963
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
938
964
|
}
|
|
939
965
|
}
|
|
940
966
|
|
|
@@ -942,7 +968,7 @@ declare global {
|
|
|
942
968
|
declare global {
|
|
943
969
|
namespace JSX {
|
|
944
970
|
interface IntrinsicElements {
|
|
945
|
-
'gs-
|
|
971
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
946
972
|
}
|
|
947
973
|
}
|
|
948
974
|
}
|
|
@@ -950,7 +976,7 @@ declare global {
|
|
|
950
976
|
|
|
951
977
|
declare global {
|
|
952
978
|
interface HTMLElementTagNameMap {
|
|
953
|
-
'gs-
|
|
979
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
954
980
|
}
|
|
955
981
|
}
|
|
956
982
|
|
|
@@ -958,7 +984,7 @@ declare global {
|
|
|
958
984
|
declare global {
|
|
959
985
|
namespace JSX {
|
|
960
986
|
interface IntrinsicElements {
|
|
961
|
-
'gs-
|
|
987
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
962
988
|
}
|
|
963
989
|
}
|
|
964
990
|
}
|
|
@@ -966,7 +992,7 @@ declare global {
|
|
|
966
992
|
|
|
967
993
|
declare global {
|
|
968
994
|
interface HTMLElementTagNameMap {
|
|
969
|
-
'gs-
|
|
995
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
970
996
|
}
|
|
971
997
|
}
|
|
972
998
|
|
|
@@ -974,7 +1000,7 @@ declare global {
|
|
|
974
1000
|
declare global {
|
|
975
1001
|
namespace JSX {
|
|
976
1002
|
interface IntrinsicElements {
|
|
977
|
-
'gs-
|
|
1003
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
978
1004
|
}
|
|
979
1005
|
}
|
|
980
1006
|
}
|
|
@@ -982,7 +1008,7 @@ declare global {
|
|
|
982
1008
|
|
|
983
1009
|
declare global {
|
|
984
1010
|
interface HTMLElementTagNameMap {
|
|
985
|
-
'gs-
|
|
1011
|
+
'gs-aggregate': AggregateComponent;
|
|
986
1012
|
}
|
|
987
1013
|
}
|
|
988
1014
|
|
|
@@ -990,7 +1016,7 @@ declare global {
|
|
|
990
1016
|
declare global {
|
|
991
1017
|
namespace JSX {
|
|
992
1018
|
interface IntrinsicElements {
|
|
993
|
-
'gs-
|
|
1019
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
994
1020
|
}
|
|
995
1021
|
}
|
|
996
1022
|
}
|
|
@@ -1014,7 +1040,7 @@ declare global {
|
|
|
1014
1040
|
|
|
1015
1041
|
declare global {
|
|
1016
1042
|
interface HTMLElementTagNameMap {
|
|
1017
|
-
'gs-sequences-
|
|
1043
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
1018
1044
|
}
|
|
1019
1045
|
}
|
|
1020
1046
|
|
|
@@ -1022,7 +1048,7 @@ declare global {
|
|
|
1022
1048
|
declare global {
|
|
1023
1049
|
namespace JSX {
|
|
1024
1050
|
interface IntrinsicElements {
|
|
1025
|
-
'gs-sequences-
|
|
1051
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1026
1052
|
}
|
|
1027
1053
|
}
|
|
1028
1054
|
}
|
|
@@ -1030,7 +1056,7 @@ declare global {
|
|
|
1030
1056
|
|
|
1031
1057
|
declare global {
|
|
1032
1058
|
interface HTMLElementTagNameMap {
|
|
1033
|
-
'gs-
|
|
1059
|
+
'gs-sequences-by-location': SequencesByLocationComponent;
|
|
1034
1060
|
}
|
|
1035
1061
|
}
|
|
1036
1062
|
|
|
@@ -1038,7 +1064,7 @@ declare global {
|
|
|
1038
1064
|
declare global {
|
|
1039
1065
|
namespace JSX {
|
|
1040
1066
|
interface IntrinsicElements {
|
|
1041
|
-
'gs-
|
|
1067
|
+
'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1042
1068
|
}
|
|
1043
1069
|
}
|
|
1044
1070
|
}
|
|
@@ -1046,7 +1072,7 @@ declare global {
|
|
|
1046
1072
|
|
|
1047
1073
|
declare global {
|
|
1048
1074
|
interface HTMLElementTagNameMap {
|
|
1049
|
-
'gs-
|
|
1075
|
+
'gs-statistics': StatisticsComponent;
|
|
1050
1076
|
}
|
|
1051
1077
|
}
|
|
1052
1078
|
|
|
@@ -1054,7 +1080,7 @@ declare global {
|
|
|
1054
1080
|
declare global {
|
|
1055
1081
|
namespace JSX {
|
|
1056
1082
|
interface IntrinsicElements {
|
|
1057
|
-
'gs-
|
|
1083
|
+
'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1058
1084
|
}
|
|
1059
1085
|
}
|
|
1060
1086
|
}
|
|
@@ -1062,10 +1088,11 @@ declare global {
|
|
|
1062
1088
|
|
|
1063
1089
|
declare global {
|
|
1064
1090
|
interface HTMLElementTagNameMap {
|
|
1065
|
-
'gs-
|
|
1091
|
+
'gs-date-range-filter': DateRangeFilterComponent;
|
|
1066
1092
|
}
|
|
1067
1093
|
interface HTMLElementEventMap {
|
|
1068
|
-
|
|
1094
|
+
[gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
|
|
1095
|
+
[gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
|
|
1069
1096
|
}
|
|
1070
1097
|
}
|
|
1071
1098
|
|
|
@@ -1073,7 +1100,7 @@ declare global {
|
|
|
1073
1100
|
declare global {
|
|
1074
1101
|
namespace JSX {
|
|
1075
1102
|
interface IntrinsicElements {
|
|
1076
|
-
'gs-
|
|
1103
|
+
'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1077
1104
|
}
|
|
1078
1105
|
}
|
|
1079
1106
|
}
|
|
@@ -1081,11 +1108,10 @@ declare global {
|
|
|
1081
1108
|
|
|
1082
1109
|
declare global {
|
|
1083
1110
|
interface HTMLElementTagNameMap {
|
|
1084
|
-
'gs-
|
|
1111
|
+
'gs-location-filter': LocationFilterComponent;
|
|
1085
1112
|
}
|
|
1086
1113
|
interface HTMLElementEventMap {
|
|
1087
|
-
|
|
1088
|
-
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1114
|
+
[gsEventNames.locationChanged]: LocationChangedEvent;
|
|
1089
1115
|
}
|
|
1090
1116
|
}
|
|
1091
1117
|
|
|
@@ -1093,7 +1119,7 @@ declare global {
|
|
|
1093
1119
|
declare global {
|
|
1094
1120
|
namespace JSX {
|
|
1095
1121
|
interface IntrinsicElements {
|
|
1096
|
-
'gs-
|
|
1122
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1097
1123
|
}
|
|
1098
1124
|
}
|
|
1099
1125
|
}
|
|
@@ -1104,7 +1130,7 @@ declare global {
|
|
|
1104
1130
|
'gs-text-filter': TextFilterComponent;
|
|
1105
1131
|
}
|
|
1106
1132
|
interface HTMLElementEventMap {
|
|
1107
|
-
|
|
1133
|
+
[gsEventNames.textFilterChanged]: TextFilterChangedEvent;
|
|
1108
1134
|
}
|
|
1109
1135
|
}
|
|
1110
1136
|
|
|
@@ -1123,7 +1149,7 @@ declare global {
|
|
|
1123
1149
|
'gs-mutation-filter': MutationFilterComponent;
|
|
1124
1150
|
}
|
|
1125
1151
|
interface HTMLElementEventMap {
|
|
1126
|
-
|
|
1152
|
+
[gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
|
|
1127
1153
|
}
|
|
1128
1154
|
}
|
|
1129
1155
|
|
|
@@ -1142,7 +1168,7 @@ declare global {
|
|
|
1142
1168
|
'gs-lineage-filter': LineageFilterComponent;
|
|
1143
1169
|
}
|
|
1144
1170
|
interface HTMLElementEventMap {
|
|
1145
|
-
|
|
1171
|
+
[gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
|
|
1146
1172
|
}
|
|
1147
1173
|
}
|
|
1148
1174
|
|
package/dist/util.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { D, a, L, T, d, v } from "./LineageFilterChangedEvent-
|
|
1
|
+
import { D, a, L, T, d, g, v } from "./LineageFilterChangedEvent-b0iuroUL.js";
|
|
2
2
|
export {
|
|
3
3
|
D as DateRangeOptionChangedEvent,
|
|
4
4
|
a as LineageFilterChangedEvent,
|
|
5
5
|
L as LocationChangedEvent,
|
|
6
6
|
T as TextFilterChangedEvent,
|
|
7
7
|
d as dateRangeOptionPresets,
|
|
8
|
+
g as gsEventNames,
|
|
8
9
|
v as views
|
|
9
10
|
};
|
|
10
11
|
//# sourceMappingURL=util.js.map
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { type gsEventNames } from './utils/gsEventNames';
|
|
2
|
+
|
|
1
3
|
export * from './web-components';
|
|
2
4
|
|
|
3
5
|
export { type ErrorEvent, UserFacingError } from './preact/components/error-display';
|
|
4
6
|
|
|
5
7
|
declare global {
|
|
6
8
|
interface HTMLElementEventMap {
|
|
7
|
-
|
|
9
|
+
[gsEventNames.error]: ErrorEvent;
|
|
8
10
|
}
|
|
9
11
|
}
|
|
@@ -3,6 +3,7 @@ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
|
3
3
|
|
|
4
4
|
import { ErrorDisplay, UserFacingError } from './error-display';
|
|
5
5
|
import { ResizeContainer } from './resize-container';
|
|
6
|
+
import { gsEventNames } from '../../utils/gsEventNames';
|
|
6
7
|
|
|
7
8
|
const meta: Meta = {
|
|
8
9
|
title: 'Component/Error',
|
|
@@ -58,7 +59,7 @@ export const FiresEvent: StoryObj = {
|
|
|
58
59
|
|
|
59
60
|
play: async ({ canvasElement }) => {
|
|
60
61
|
const listenerMock = fn();
|
|
61
|
-
canvasElement.addEventListener(
|
|
62
|
+
canvasElement.addEventListener(gsEventNames.error, listenerMock);
|
|
62
63
|
|
|
63
64
|
await waitFor(async () => {
|
|
64
65
|
await expect(listenerMock.mock.calls.at(-1)![0].error.name).toStrictEqual('UserFacingError');
|
|
@@ -5,12 +5,11 @@ import { type ZodError } from 'zod';
|
|
|
5
5
|
import { InfoHeadline1, InfoParagraph } from './info';
|
|
6
6
|
import { Modal } from './modal';
|
|
7
7
|
import { LapisError, UnknownLapisError } from '../../lapisApi/lapisApi';
|
|
8
|
-
|
|
9
|
-
export const GS_ERROR_EVENT_TYPE = 'gs-error';
|
|
8
|
+
import { gsEventNames } from '../../utils/gsEventNames';
|
|
10
9
|
|
|
11
10
|
export class ErrorEvent extends Event {
|
|
12
11
|
constructor(public readonly error: Error) {
|
|
13
|
-
super(
|
|
12
|
+
super(gsEventNames.error, {
|
|
14
13
|
bubbles: true,
|
|
15
14
|
composed: true,
|
|
16
15
|
});
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ComponentChildren } from 'preact';
|
|
2
|
+
import { forwardRef } from 'preact/compat';
|
|
2
3
|
|
|
3
4
|
export type Size = {
|
|
4
5
|
width: string;
|
|
5
6
|
height?: string;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
-
size
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const ResizeContainer: FunctionComponent<ResizeContainerProps> = ({ children, size }) => {
|
|
13
|
-
return (
|
|
14
|
-
<div style={size} className='bg-white'>
|
|
9
|
+
export const ResizeContainer = forwardRef<HTMLDivElement, { size: Size; children: ComponentChildren }>(
|
|
10
|
+
({ size, children }, ref) => (
|
|
11
|
+
<div ref={ref} style={{ width: size.width, height: size.height, position: 'relative' }}>
|
|
15
12
|
{children}
|
|
16
13
|
</div>
|
|
17
|
-
)
|
|
18
|
-
|
|
14
|
+
),
|
|
15
|
+
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
|
+
import { type CSSProperties } from 'preact/compat';
|
|
2
3
|
import { type JSXInternal } from 'preact/src/jsx';
|
|
3
4
|
|
|
4
5
|
export type TooltipPosition =
|
|
@@ -14,6 +15,7 @@ export type TooltipPosition =
|
|
|
14
15
|
export type TooltipProps = {
|
|
15
16
|
content: string | JSXInternal.Element;
|
|
16
17
|
position?: TooltipPosition;
|
|
18
|
+
tooltipStyle?: CSSProperties;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
function getPositionCss(position?: TooltipPosition) {
|
|
@@ -39,12 +41,13 @@ function getPositionCss(position?: TooltipPosition) {
|
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
const Tooltip: FunctionComponent<TooltipProps> = ({ children, content, position = 'bottom' }) => {
|
|
44
|
+
const Tooltip: FunctionComponent<TooltipProps> = ({ children, content, position = 'bottom', tooltipStyle }) => {
|
|
43
45
|
return (
|
|
44
|
-
<div className=
|
|
45
|
-
<div
|
|
46
|
+
<div className={`relative group`}>
|
|
47
|
+
<div>{children}</div>
|
|
46
48
|
<div
|
|
47
|
-
className={`absolute z-10 w-max bg-white p-4 border border-gray-200 rounded-md invisible
|
|
49
|
+
className={`absolute z-10 w-max bg-white p-4 border border-gray-200 rounded-md invisible group-hover:visible ${getPositionCss(position)}`}
|
|
50
|
+
style={{ ...tooltipStyle }}
|
|
48
51
|
>
|
|
49
52
|
{content}
|
|
50
53
|
</div>
|
|
@@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
|
|
7
7
|
import { DateRangeFilter, type DateRangeFilterProps } from './date-range-filter';
|
|
8
8
|
import { previewHandles } from '../../../.storybook/preview';
|
|
9
9
|
import { LAPIS_URL } from '../../constants';
|
|
10
|
+
import { gsEventNames } from '../../utils/gsEventNames';
|
|
10
11
|
import { LapisUrlContextProvider } from '../LapisUrlContext';
|
|
11
12
|
import { dateRangeOptionPresets, type DateRangeValue } from './dateRangeOption';
|
|
12
13
|
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectErrorMessage';
|
|
@@ -27,7 +28,7 @@ const meta: Meta<DateRangeFilterProps> = {
|
|
|
27
28
|
component: DateRangeFilter,
|
|
28
29
|
parameters: {
|
|
29
30
|
actions: {
|
|
30
|
-
handles: [
|
|
31
|
+
handles: [gsEventNames.dateRangeFilterChanged, gsEventNames.dateRangeOptionChanged, ...previewHandles],
|
|
31
32
|
},
|
|
32
33
|
fetchMock: {},
|
|
33
34
|
},
|
|
@@ -198,7 +199,7 @@ export const ChangingTheValueProgrammatically: StoryObj<DateRangeFilterProps> =
|
|
|
198
199
|
const ref = useRef<HTMLDivElement>(null);
|
|
199
200
|
|
|
200
201
|
useEffect(() => {
|
|
201
|
-
ref.current?.addEventListener(
|
|
202
|
+
ref.current?.addEventListener(gsEventNames.dateRangeOptionChanged, (event) => {
|
|
202
203
|
setValue(event.detail);
|
|
203
204
|
});
|
|
204
205
|
}, []);
|
|
@@ -333,8 +334,8 @@ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRend
|
|
|
333
334
|
const filterChangedListenerMock = fn();
|
|
334
335
|
const optionChangedListenerMock = fn();
|
|
335
336
|
await step('Setup event listener mock', () => {
|
|
336
|
-
canvasElement.addEventListener(
|
|
337
|
-
canvasElement.addEventListener(
|
|
337
|
+
canvasElement.addEventListener(gsEventNames.dateRangeFilterChanged, filterChangedListenerMock);
|
|
338
|
+
canvasElement.addEventListener(gsEventNames.dateRangeOptionChanged, optionChangedListenerMock);
|
|
338
339
|
});
|
|
339
340
|
|
|
340
341
|
return { canvas, filterChangedListenerMock, optionChangedListenerMock };
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
dateRangeOptionSchema,
|
|
11
11
|
dateRangeValueSchema,
|
|
12
12
|
} from './dateRangeOption';
|
|
13
|
+
import { gsEventNames } from '../../utils/gsEventNames';
|
|
13
14
|
import { ClearableSelect } from '../components/clearable-select';
|
|
14
15
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
15
16
|
|
|
@@ -170,7 +171,7 @@ export const DateRangeFilterInner = ({
|
|
|
170
171
|
};
|
|
171
172
|
|
|
172
173
|
divRef.current?.dispatchEvent(
|
|
173
|
-
new CustomEvent(
|
|
174
|
+
new CustomEvent(gsEventNames.dateRangeFilterChanged, {
|
|
174
175
|
detail,
|
|
175
176
|
bubbles: true,
|
|
176
177
|
composed: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
|
|
3
3
|
import { toYYYYMMDD } from './dateConversion';
|
|
4
|
+
import { gsEventNames } from '../../utils/gsEventNames';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A date range option that can be used in the `gs-date-range-filter` component.
|
|
@@ -36,7 +37,7 @@ export type DateRangeValue = z.infer<typeof dateRangeValueSchema>;
|
|
|
36
37
|
|
|
37
38
|
export class DateRangeOptionChangedEvent extends CustomEvent<DateRangeValue> {
|
|
38
39
|
constructor(detail: DateRangeValue) {
|
|
39
|
-
super(
|
|
40
|
+
super(gsEventNames.dateRangeOptionChanged, {
|
|
40
41
|
detail,
|
|
41
42
|
bubbles: true,
|
|
42
43
|
composed: true,
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Fragment, type FunctionComponent } from 'preact';
|
|
2
|
+
import { useMemo, useState } from 'preact/hooks';
|
|
3
|
+
|
|
4
|
+
import { type CDSFeature } from './loadGff3';
|
|
5
|
+
import { MinMaxRangeSlider } from '../components/min-max-range-slider';
|
|
6
|
+
import Tooltip from '../components/tooltip';
|
|
7
|
+
import { singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
8
|
+
|
|
9
|
+
const MAX_TICK_NUMBER = 20;
|
|
10
|
+
const MIN_TICK_NUMBER = 2;
|
|
11
|
+
|
|
12
|
+
function getMaxTickNumber(fullWidth: number): number {
|
|
13
|
+
const baseValue = 1000;
|
|
14
|
+
|
|
15
|
+
const normalizedRatio = fullWidth / baseValue;
|
|
16
|
+
|
|
17
|
+
const ticks = Math.round(MAX_TICK_NUMBER * normalizedRatio);
|
|
18
|
+
return Math.max(MIN_TICK_NUMBER, Math.min(MAX_TICK_NUMBER, ticks));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getTicks(zoomStart: number, zoomEnd: number, fullWidth: number) {
|
|
22
|
+
const maxTickNumber = getMaxTickNumber(fullWidth);
|
|
23
|
+
const length = zoomEnd - zoomStart;
|
|
24
|
+
const minTickSize = length / maxTickNumber;
|
|
25
|
+
let maxTickSize = 10 ** Math.round(Math.log(minTickSize) / Math.log(10));
|
|
26
|
+
const numTicks = Math.round(length / maxTickSize);
|
|
27
|
+
if (numTicks > maxTickNumber) {
|
|
28
|
+
if (numTicks > 2 * maxTickNumber) {
|
|
29
|
+
maxTickSize = maxTickSize * 5;
|
|
30
|
+
} else {
|
|
31
|
+
maxTickSize = maxTickSize * 2;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const ticks = [];
|
|
35
|
+
|
|
36
|
+
const offset = Math.ceil(zoomStart / maxTickSize);
|
|
37
|
+
ticks.push({ start: zoomStart, end: zoomStart + maxTickSize - (zoomStart % maxTickSize) });
|
|
38
|
+
for (let i = 0; i <= numTicks; i++) {
|
|
39
|
+
const start = i * maxTickSize + offset * maxTickSize;
|
|
40
|
+
if (start >= zoomEnd) {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
const end = (i + 1) * maxTickSize + offset * maxTickSize;
|
|
44
|
+
if (end > zoomEnd) {
|
|
45
|
+
ticks.push({ start, end: zoomEnd });
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
ticks.push({ start, end });
|
|
49
|
+
}
|
|
50
|
+
return ticks;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface XAxisProps {
|
|
54
|
+
zoomStart: number;
|
|
55
|
+
zoomEnd: number;
|
|
56
|
+
fullWidth: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const XAxis: FunctionComponent<XAxisProps> = (componentProps) => {
|
|
60
|
+
const { zoomStart, zoomEnd, fullWidth } = componentProps;
|
|
61
|
+
|
|
62
|
+
const ticks = useMemo(() => getTicks(zoomStart, zoomEnd, fullWidth), [zoomStart, zoomEnd, fullWidth]);
|
|
63
|
+
const visibleRegionLength = zoomEnd - zoomStart;
|
|
64
|
+
const averageWidth = visibleRegionLength / ticks.length;
|
|
65
|
+
return (
|
|
66
|
+
<div className={'h-6 relative'}>
|
|
67
|
+
{ticks.map((tick, idx) => {
|
|
68
|
+
const width = tick.end - tick.start;
|
|
69
|
+
const widthPercent = (width / visibleRegionLength) * 100;
|
|
70
|
+
const leftPercent = ((tick.start - zoomStart) / visibleRegionLength) * 100;
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
key={idx}
|
|
74
|
+
class='absolute text-xs text-black px-1 hover:opacity-80 border-l border-r border-gray-400 border-t'
|
|
75
|
+
style={{
|
|
76
|
+
left: `calc(${leftPercent}% - 1px)`,
|
|
77
|
+
width: `calc(${widthPercent}% - 1px)`,
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{width >= averageWidth ? tick.start : ''}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function getTooltipPosition(cds_start: number, cds_end: number, genomeLength: number) {
|
|
89
|
+
const tooltipY = cds_start + (cds_end - cds_start) / 2 < genomeLength / 2 ? 'start' : 'end';
|
|
90
|
+
return `bottom-${tooltipY}` as const;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface TooltipContentProps {
|
|
94
|
+
cds: CDSFeature;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const TooltipContent: FunctionComponent<TooltipContentProps> = (componentProps) => {
|
|
98
|
+
const { cds } = componentProps;
|
|
99
|
+
const cdsLength = cds.positions.reduce((acc, pos) => acc + pos.end - pos.start, 0);
|
|
100
|
+
const cdsCoordinates = cds.positions.map((pos) => `${pos.start}-${pos.end}`).join(', ');
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
<p>
|
|
104
|
+
<span className='font-bold'>{cds.label}</span>
|
|
105
|
+
</p>
|
|
106
|
+
<table>
|
|
107
|
+
<tbody>
|
|
108
|
+
<tr>
|
|
109
|
+
<th className='font-medium px-2 text-right'>CDS Length</th>
|
|
110
|
+
<td>{cdsLength}</td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<th className='font-medium px-2 text-right'>CDS Coordinates</th>
|
|
114
|
+
<td>{cdsCoordinates}</td>
|
|
115
|
+
</tr>
|
|
116
|
+
</tbody>
|
|
117
|
+
</table>
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
interface CDSBarsProps {
|
|
123
|
+
gffData: CDSFeature[][];
|
|
124
|
+
zoomStart: number;
|
|
125
|
+
zoomEnd: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const CDSBars: FunctionComponent<CDSBarsProps> = (componentProps) => {
|
|
129
|
+
const { gffData, zoomStart, zoomEnd } = componentProps;
|
|
130
|
+
const visibleRegionLength = zoomEnd - zoomStart;
|
|
131
|
+
return (
|
|
132
|
+
<>
|
|
133
|
+
{gffData.map((data, listId) => (
|
|
134
|
+
<div className={'w-full h-6 relative'} key={listId}>
|
|
135
|
+
{data.map((cds, idx) => (
|
|
136
|
+
<Fragment key={idx}>
|
|
137
|
+
{cds.positions.map((position, posIdx) => {
|
|
138
|
+
const start = Math.max(position.start, zoomStart);
|
|
139
|
+
const end = Math.min(position.end, zoomEnd);
|
|
140
|
+
|
|
141
|
+
if (start >= end) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const widthPercent = ((end - start) / visibleRegionLength) * 100;
|
|
146
|
+
const leftPercent = ((start - zoomStart) / visibleRegionLength) * 100;
|
|
147
|
+
const tooltipPosition = getTooltipPosition(start, end, visibleRegionLength);
|
|
148
|
+
return (
|
|
149
|
+
<Tooltip
|
|
150
|
+
content={<TooltipContent cds={cds} />}
|
|
151
|
+
position={tooltipPosition}
|
|
152
|
+
key={`${idx}-${posIdx}`}
|
|
153
|
+
tooltipStyle={{ left: `${leftPercent}%` }}
|
|
154
|
+
>
|
|
155
|
+
<div
|
|
156
|
+
className='absolute text-xs text-white rounded px-1 py-0.5 cursor-pointer hover:opacity-80 shadow-md'
|
|
157
|
+
style={{
|
|
158
|
+
left: `${leftPercent}%`,
|
|
159
|
+
width: `${widthPercent}%`,
|
|
160
|
+
backgroundColor: singleGraphColorRGBByName(cds.color),
|
|
161
|
+
whiteSpace: 'nowrap',
|
|
162
|
+
overflow: 'hidden',
|
|
163
|
+
textOverflow: 'ellipsis',
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
{cds.label}
|
|
167
|
+
</div>
|
|
168
|
+
</Tooltip>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</Fragment>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
))}
|
|
175
|
+
</>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
interface CDSProps {
|
|
180
|
+
gffData: CDSFeature[][];
|
|
181
|
+
genomeLength: number;
|
|
182
|
+
width: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const CDSPlot: FunctionComponent<CDSProps> = (componentProps) => {
|
|
186
|
+
const { gffData, genomeLength, width } = componentProps;
|
|
187
|
+
|
|
188
|
+
const [zoomStart, setZoomStart] = useState(0);
|
|
189
|
+
const [zoomEnd, setZoomEnd] = useState(genomeLength);
|
|
190
|
+
|
|
191
|
+
const updateZoomStart = (newStart: number) => {
|
|
192
|
+
setZoomStart(newStart);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const updateZoomEnd = (newEnd: number) => {
|
|
196
|
+
setZoomEnd(newEnd);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div class='p-4'>
|
|
201
|
+
<CDSBars gffData={gffData} zoomStart={zoomStart} zoomEnd={zoomEnd} />
|
|
202
|
+
<XAxis zoomStart={zoomStart} zoomEnd={zoomEnd} fullWidth={width} />
|
|
203
|
+
<div class='relative w-full h-5'>
|
|
204
|
+
<MinMaxRangeSlider
|
|
205
|
+
min={zoomStart}
|
|
206
|
+
max={zoomEnd}
|
|
207
|
+
setMin={updateZoomStart}
|
|
208
|
+
setMax={updateZoomEnd}
|
|
209
|
+
rangeMin={0}
|
|
210
|
+
rangeMax={genomeLength}
|
|
211
|
+
step={1}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<XAxis zoomStart={0} zoomEnd={genomeLength} fullWidth={width} />
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export default CDSPlot;
|