@genspectrum/dashboard-components 0.6.3 → 0.6.4
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 +1 -1
- package/dist/dashboard-components.js +84 -11
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +7 -2
- package/dist/style.css +3 -0
- package/package.json +1 -1
- package/src/preact/components/tooltip.stories.tsx +54 -0
- package/src/preact/components/tooltip.tsx +31 -0
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +23 -11
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +6 -2
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +45 -10
- package/src/query/queryMutationsOverTime.spec.ts +50 -24
- package/src/query/queryMutationsOverTime.ts +20 -5
- package/src/utils/temporal.spec.ts +5 -0
- package/src/utils/temporal.ts +29 -5
- package/src/web-components/visualization/gs-mutations-over-time.tsx +5 -0
package/custom-elements.json
CHANGED
|
@@ -1823,7 +1823,7 @@
|
|
|
1823
1823
|
"declarations": [
|
|
1824
1824
|
{
|
|
1825
1825
|
"kind": "class",
|
|
1826
|
-
"description": "## Context\n\nThis component displays mutations (substitutions and deletions) over time for a dataset selected by a LAPIS filter.\nThe shown date range is determined by the date field in the LAPIS filter.\nIf the date field is not set, the date range is determined by all available dates in the dataset.\n\n## Views\n\n### Grid View\n\nThe grid view shows the proportion for each mutation over date ranges.",
|
|
1826
|
+
"description": "## Context\n\nThis component displays mutations (substitutions and deletions) over time for a dataset selected by a LAPIS filter.\nThe shown date range is determined by the date field in the LAPIS filter.\nIf the date field is not set, the date range is determined by all available dates in the dataset.\n\n## Views\n\n### Grid View\n\nThe grid view shows the proportion for each mutation over date ranges.\n\nThe grid will show at max 100 rows and 200 columns for browser performance reasons.\nMore data might make the browser unresponsive.\nIf the numbers are exceeded, an error message will be shown.\nIn both cases, the `lapisFilter` should be narrowed down to reduce the number of mutations or date ranges.\nThe number of date ranges can also be reduced by selecting a larger granularity (months instead of weeks).",
|
|
1827
1827
|
"name": "MutationsOverTimeComponent",
|
|
1828
1828
|
"members": [
|
|
1829
1829
|
{
|
|
@@ -4585,6 +4585,9 @@ input.tab:checked + .tab-content,
|
|
|
4585
4585
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
4586
4586
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
4587
4587
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
4588
|
+
}
|
|
4589
|
+
.peer:hover ~ .peer-hover\\:block {
|
|
4590
|
+
display: block;
|
|
4588
4591
|
}`;
|
|
4589
4592
|
var __defProp$c = Object.defineProperty;
|
|
4590
4593
|
var __decorateClass$c = (decorators, target, key, kind) => {
|
|
@@ -5938,6 +5941,9 @@ class YearMonthDay {
|
|
|
5938
5941
|
toString() {
|
|
5939
5942
|
return this.text;
|
|
5940
5943
|
}
|
|
5944
|
+
englishName() {
|
|
5945
|
+
return this.dayjs.format("dddd, MMMM D, YYYY");
|
|
5946
|
+
}
|
|
5941
5947
|
get firstDay() {
|
|
5942
5948
|
return this;
|
|
5943
5949
|
}
|
|
@@ -5978,12 +5984,16 @@ class YearWeek {
|
|
|
5978
5984
|
toString() {
|
|
5979
5985
|
return this.text;
|
|
5980
5986
|
}
|
|
5987
|
+
englishName() {
|
|
5988
|
+
return `Week ${this.isoWeekNumber}, ${this.isoYearNumber}`;
|
|
5989
|
+
}
|
|
5981
5990
|
get firstDay() {
|
|
5982
5991
|
const firstDay = dayjs().year(this.isoYearNumber).month(1).date(4).isoWeek(this.isoWeekNumber).startOf("isoWeek");
|
|
5983
5992
|
return this.cache.getYearMonthDay(firstDay.format("YYYY-MM-DD"));
|
|
5984
5993
|
}
|
|
5985
5994
|
get lastDay() {
|
|
5986
|
-
const
|
|
5995
|
+
const firstDay = dayjs().year(this.isoYearNumber).startOf("year").add((this.isoWeekNumber - 1) * 7, "day").startOf("week").add(1, "day");
|
|
5996
|
+
const lastDay = firstDay.add(6, "day");
|
|
5987
5997
|
return this.cache.getYearMonthDay(lastDay.format("YYYY-MM-DD"));
|
|
5988
5998
|
}
|
|
5989
5999
|
get year() {
|
|
@@ -6014,6 +6024,9 @@ class YearMonth {
|
|
|
6014
6024
|
toString() {
|
|
6015
6025
|
return this.text;
|
|
6016
6026
|
}
|
|
6027
|
+
englishName() {
|
|
6028
|
+
return `${monthName(this.monthNumber)} ${this.yearNumber}`;
|
|
6029
|
+
}
|
|
6017
6030
|
get firstDay() {
|
|
6018
6031
|
return this.cache.getYearMonthDay(dayjs(`${this.yearNumber}-${this.monthNumber}-01`).format("YYYY-MM-DD"));
|
|
6019
6032
|
}
|
|
@@ -6049,6 +6062,9 @@ class Year {
|
|
|
6049
6062
|
toString() {
|
|
6050
6063
|
return this.text;
|
|
6051
6064
|
}
|
|
6065
|
+
englishName() {
|
|
6066
|
+
return this.year.toString();
|
|
6067
|
+
}
|
|
6052
6068
|
get firstMonth() {
|
|
6053
6069
|
return this.cache.getYearMonth(`${this.year}-01`);
|
|
6054
6070
|
}
|
|
@@ -6074,6 +6090,9 @@ class Year {
|
|
|
6074
6090
|
return new Year(year, cache);
|
|
6075
6091
|
}
|
|
6076
6092
|
}
|
|
6093
|
+
function monthName(month) {
|
|
6094
|
+
return dayjs().month(month - 1).format("MMMM");
|
|
6095
|
+
}
|
|
6077
6096
|
function generateAllDaysInRange(start, end) {
|
|
6078
6097
|
const days = [];
|
|
6079
6098
|
const daysInBetween = end.minus(start);
|
|
@@ -7887,14 +7906,32 @@ function filterMutationTypes(displayedMutationTypes, data) {
|
|
|
7887
7906
|
}
|
|
7888
7907
|
function filterProportion(data, proportionInterval) {
|
|
7889
7908
|
data.getFirstAxisKeys().forEach((mutation) => {
|
|
7890
|
-
const row = data.getRow(mutation, 0);
|
|
7891
|
-
if (!row.some(
|
|
7909
|
+
const row = data.getRow(mutation, { count: 0, proportion: 0 });
|
|
7910
|
+
if (!row.some(
|
|
7911
|
+
(value) => value.proportion >= proportionInterval.min && value.proportion <= proportionInterval.max
|
|
7912
|
+
)) {
|
|
7892
7913
|
data.deleteRow(mutation);
|
|
7893
7914
|
}
|
|
7894
7915
|
});
|
|
7895
7916
|
}
|
|
7917
|
+
const Tooltip = ({ children, content }) => {
|
|
7918
|
+
const referenceRef = A(null);
|
|
7919
|
+
const floatingRef = A(null);
|
|
7920
|
+
useFloatingUi(referenceRef, floatingRef, [offset(5), shift(), flip()]);
|
|
7921
|
+
return /* @__PURE__ */ u$1("div", { className: "relative", children: [
|
|
7922
|
+
/* @__PURE__ */ u$1("div", { className: "peer", ref: referenceRef, children }),
|
|
7923
|
+
/* @__PURE__ */ u$1("div", { ref: floatingRef, className: `${dropdownClass} hidden peer-hover:block`, children: content })
|
|
7924
|
+
] });
|
|
7925
|
+
};
|
|
7926
|
+
const MAX_NUMBER_OF_GRID_ROWS = 100;
|
|
7896
7927
|
const MutationsOverTimeGrid = ({ data }) => {
|
|
7897
7928
|
const mutations = data.getFirstAxisKeys();
|
|
7929
|
+
if (mutations.length > MAX_NUMBER_OF_GRID_ROWS) {
|
|
7930
|
+
throw new UserFacingError(
|
|
7931
|
+
"Too many mutations",
|
|
7932
|
+
`The dataset contains ${mutations.length} mutations. Please adapt the filters to reduce the number to below ${MAX_NUMBER_OF_GRID_ROWS}.`
|
|
7933
|
+
);
|
|
7934
|
+
}
|
|
7898
7935
|
const dates = data.getSecondAxisKeys().sort((a2, b3) => compareTemporal(a2, b3));
|
|
7899
7936
|
return /* @__PURE__ */ u$1(
|
|
7900
7937
|
"div",
|
|
@@ -7915,7 +7952,7 @@ const MutationsOverTimeGrid = ({ data }) => {
|
|
|
7915
7952
|
`mutation-${mutation.toString()}`
|
|
7916
7953
|
),
|
|
7917
7954
|
dates.map((date, j2) => {
|
|
7918
|
-
const value = data.get(mutation, date) ?? 0;
|
|
7955
|
+
const value = data.get(mutation, date) ?? { proportion: 0, count: 0 };
|
|
7919
7956
|
return /* @__PURE__ */ u$1(
|
|
7920
7957
|
"div",
|
|
7921
7958
|
{
|
|
@@ -7930,15 +7967,41 @@ const MutationsOverTimeGrid = ({ data }) => {
|
|
|
7930
7967
|
}
|
|
7931
7968
|
);
|
|
7932
7969
|
};
|
|
7933
|
-
const ProportionCell = ({ value }) => {
|
|
7934
|
-
|
|
7970
|
+
const ProportionCell = ({ value, mutation, date }) => {
|
|
7971
|
+
const tooltipContent = /* @__PURE__ */ u$1("div", { children: [
|
|
7972
|
+
/* @__PURE__ */ u$1("p", { children: [
|
|
7973
|
+
/* @__PURE__ */ u$1("span", { className: "font-bold", children: date.englishName() }),
|
|
7974
|
+
" (",
|
|
7975
|
+
timeIntervalDisplay(date),
|
|
7976
|
+
")"
|
|
7977
|
+
] }),
|
|
7978
|
+
/* @__PURE__ */ u$1("p", { children: mutation.code }),
|
|
7979
|
+
/* @__PURE__ */ u$1("p", { children: [
|
|
7980
|
+
"Proportion: ",
|
|
7981
|
+
formatProportion(value.proportion)
|
|
7982
|
+
] }),
|
|
7983
|
+
/* @__PURE__ */ u$1("p", { children: [
|
|
7984
|
+
"Count: ",
|
|
7985
|
+
value.count
|
|
7986
|
+
] })
|
|
7987
|
+
] });
|
|
7988
|
+
return /* @__PURE__ */ u$1(Fragment, { children: /* @__PURE__ */ u$1("div", { className: "py-1", children: /* @__PURE__ */ u$1(Tooltip, { content: tooltipContent, children: /* @__PURE__ */ u$1(
|
|
7935
7989
|
"div",
|
|
7936
7990
|
{
|
|
7937
|
-
style: {
|
|
7991
|
+
style: {
|
|
7992
|
+
backgroundColor: backgroundColor(value.proportion),
|
|
7993
|
+
color: textColor(value.proportion)
|
|
7994
|
+
},
|
|
7938
7995
|
className: "text-center hover:font-bold text-xs",
|
|
7939
|
-
children: formatProportion(value, 0)
|
|
7996
|
+
children: formatProportion(value.proportion, 0)
|
|
7940
7997
|
}
|
|
7941
|
-
) }) });
|
|
7998
|
+
) }) }) });
|
|
7999
|
+
};
|
|
8000
|
+
const timeIntervalDisplay = (date) => {
|
|
8001
|
+
if (date instanceof YearMonthDay) {
|
|
8002
|
+
return date.toString();
|
|
8003
|
+
}
|
|
8004
|
+
return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
|
|
7942
8005
|
};
|
|
7943
8006
|
const backgroundColor = (proportion) => {
|
|
7944
8007
|
const minAlpha = 0;
|
|
@@ -8014,8 +8077,15 @@ class Map2d {
|
|
|
8014
8077
|
return copy;
|
|
8015
8078
|
}
|
|
8016
8079
|
}
|
|
8080
|
+
const MAX_NUMBER_OF_GRID_COLUMNS = 200;
|
|
8017
8081
|
async function queryMutationsOverTimeData(lapisFilter, sequenceType, lapis, lapisDateField, granularity, signal) {
|
|
8018
8082
|
const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
|
|
8083
|
+
if (allDates.length > MAX_NUMBER_OF_GRID_COLUMNS) {
|
|
8084
|
+
throw new UserFacingError(
|
|
8085
|
+
"Too many dates",
|
|
8086
|
+
`The dataset would contain ${allDates.length} date intervals. Please reduce the number to below ${MAX_NUMBER_OF_GRID_COLUMNS} to display the data. You can achieve this by either narrowing the date range in the provided LAPIS filter or by selecting a larger granularity.`
|
|
8087
|
+
);
|
|
8088
|
+
}
|
|
8019
8089
|
const subQueries = allDates.map(async (date) => {
|
|
8020
8090
|
const dateFrom = date.firstDay.toString();
|
|
8021
8091
|
const dateTo = date.lastDay.toString();
|
|
@@ -8081,7 +8151,10 @@ function groupByMutation(data) {
|
|
|
8081
8151
|
);
|
|
8082
8152
|
data.forEach((mutationData) => {
|
|
8083
8153
|
mutationData.mutations.forEach((mutationEntry) => {
|
|
8084
|
-
dataArray.set(mutationEntry.mutation, mutationData.date,
|
|
8154
|
+
dataArray.set(mutationEntry.mutation, mutationData.date, {
|
|
8155
|
+
count: mutationEntry.count,
|
|
8156
|
+
proportion: mutationEntry.proportion
|
|
8157
|
+
});
|
|
8085
8158
|
});
|
|
8086
8159
|
});
|
|
8087
8160
|
addZeroValuesForDatesWithNoMutationData(dataArray, data);
|
|
@@ -8092,7 +8165,7 @@ function addZeroValuesForDatesWithNoMutationData(dataArray, data) {
|
|
|
8092
8165
|
const someMutation = dataArray.getFirstAxisKeys()[0];
|
|
8093
8166
|
data.forEach((mutationData) => {
|
|
8094
8167
|
if (mutationData.mutations.length === 0) {
|
|
8095
|
-
dataArray.set(someMutation, mutationData.date, 0);
|
|
8168
|
+
dataArray.set(someMutation, mutationData.date, { count: 0, proportion: 0 });
|
|
8096
8169
|
}
|
|
8097
8170
|
});
|
|
8098
8171
|
}
|