@genspectrum/dashboard-components 0.6.3 → 0.6.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 +1 -1
- package/dist/dashboard-components.js +121 -41
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +11 -0
- package/dist/style.css +6 -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/__mockData__/aggregated_tooManyMutations.json +1470 -0
- package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_tooManyMutations.json +16453 -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 +77 -41
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +62 -8
- 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 +11 -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 limits the number of rows columns for browser performance reasons.\nToo much data might make the browser unresponsive.\n\nThe number of columns is limited to 200.\nIf this number are exceeded, an error message will be shown.\nIt is your responsibility to make sure that this does not happen.\nDepending on the selected date range in the `lapisFilter`, you can adapt the granularity accordingly\n(e.g. use months instead of days).\n\nThe number of rows is limited to 100.\nIf there are more, the component will only show 100 mutations and notify the user.",
|
|
1827
1827
|
"name": "MutationsOverTimeComponent",
|
|
1828
1828
|
"members": [
|
|
1829
1829
|
{
|
|
@@ -4464,6 +4464,9 @@ input.tab:checked + .tab-content,
|
|
|
4464
4464
|
padding-top: 1rem;
|
|
4465
4465
|
padding-bottom: 1rem;
|
|
4466
4466
|
}
|
|
4467
|
+
.pl-2 {
|
|
4468
|
+
padding-left: 0.5rem;
|
|
4469
|
+
}
|
|
4467
4470
|
.text-center {
|
|
4468
4471
|
text-align: center;
|
|
4469
4472
|
}
|
|
@@ -4585,6 +4588,9 @@ input.tab:checked + .tab-content,
|
|
|
4585
4588
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
4586
4589
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
4587
4590
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
4591
|
+
}
|
|
4592
|
+
.peer:hover ~ .peer-hover\\:block {
|
|
4593
|
+
display: block;
|
|
4588
4594
|
}`;
|
|
4589
4595
|
var __defProp$c = Object.defineProperty;
|
|
4590
4596
|
var __decorateClass$c = (decorators, target, key, kind) => {
|
|
@@ -5938,6 +5944,9 @@ class YearMonthDay {
|
|
|
5938
5944
|
toString() {
|
|
5939
5945
|
return this.text;
|
|
5940
5946
|
}
|
|
5947
|
+
englishName() {
|
|
5948
|
+
return this.dayjs.format("dddd, MMMM D, YYYY");
|
|
5949
|
+
}
|
|
5941
5950
|
get firstDay() {
|
|
5942
5951
|
return this;
|
|
5943
5952
|
}
|
|
@@ -5978,12 +5987,16 @@ class YearWeek {
|
|
|
5978
5987
|
toString() {
|
|
5979
5988
|
return this.text;
|
|
5980
5989
|
}
|
|
5990
|
+
englishName() {
|
|
5991
|
+
return `Week ${this.isoWeekNumber}, ${this.isoYearNumber}`;
|
|
5992
|
+
}
|
|
5981
5993
|
get firstDay() {
|
|
5982
5994
|
const firstDay = dayjs().year(this.isoYearNumber).month(1).date(4).isoWeek(this.isoWeekNumber).startOf("isoWeek");
|
|
5983
5995
|
return this.cache.getYearMonthDay(firstDay.format("YYYY-MM-DD"));
|
|
5984
5996
|
}
|
|
5985
5997
|
get lastDay() {
|
|
5986
|
-
const
|
|
5998
|
+
const firstDay = dayjs().year(this.isoYearNumber).startOf("year").add((this.isoWeekNumber - 1) * 7, "day").startOf("week").add(1, "day");
|
|
5999
|
+
const lastDay = firstDay.add(6, "day");
|
|
5987
6000
|
return this.cache.getYearMonthDay(lastDay.format("YYYY-MM-DD"));
|
|
5988
6001
|
}
|
|
5989
6002
|
get year() {
|
|
@@ -6014,6 +6027,9 @@ class YearMonth {
|
|
|
6014
6027
|
toString() {
|
|
6015
6028
|
return this.text;
|
|
6016
6029
|
}
|
|
6030
|
+
englishName() {
|
|
6031
|
+
return `${monthName(this.monthNumber)} ${this.yearNumber}`;
|
|
6032
|
+
}
|
|
6017
6033
|
get firstDay() {
|
|
6018
6034
|
return this.cache.getYearMonthDay(dayjs(`${this.yearNumber}-${this.monthNumber}-01`).format("YYYY-MM-DD"));
|
|
6019
6035
|
}
|
|
@@ -6049,6 +6065,9 @@ class Year {
|
|
|
6049
6065
|
toString() {
|
|
6050
6066
|
return this.text;
|
|
6051
6067
|
}
|
|
6068
|
+
englishName() {
|
|
6069
|
+
return this.year.toString();
|
|
6070
|
+
}
|
|
6052
6071
|
get firstMonth() {
|
|
6053
6072
|
return this.cache.getYearMonth(`${this.year}-01`);
|
|
6054
6073
|
}
|
|
@@ -6074,6 +6093,9 @@ class Year {
|
|
|
6074
6093
|
return new Year(year, cache);
|
|
6075
6094
|
}
|
|
6076
6095
|
}
|
|
6096
|
+
function monthName(month) {
|
|
6097
|
+
return dayjs().month(month - 1).format("MMMM");
|
|
6098
|
+
}
|
|
6077
6099
|
function generateAllDaysInRange(start, end) {
|
|
6078
6100
|
const days = [];
|
|
6079
6101
|
const daysInBetween = end.minus(start);
|
|
@@ -7887,58 +7909,106 @@ function filterMutationTypes(displayedMutationTypes, data) {
|
|
|
7887
7909
|
}
|
|
7888
7910
|
function filterProportion(data, proportionInterval) {
|
|
7889
7911
|
data.getFirstAxisKeys().forEach((mutation) => {
|
|
7890
|
-
const row = data.getRow(mutation, 0);
|
|
7891
|
-
if (!row.some(
|
|
7912
|
+
const row = data.getRow(mutation, { count: 0, proportion: 0 });
|
|
7913
|
+
if (!row.some(
|
|
7914
|
+
(value) => value.proportion >= proportionInterval.min && value.proportion <= proportionInterval.max
|
|
7915
|
+
)) {
|
|
7892
7916
|
data.deleteRow(mutation);
|
|
7893
7917
|
}
|
|
7894
7918
|
});
|
|
7895
7919
|
}
|
|
7920
|
+
const Tooltip = ({ children, content }) => {
|
|
7921
|
+
const referenceRef = A(null);
|
|
7922
|
+
const floatingRef = A(null);
|
|
7923
|
+
useFloatingUi(referenceRef, floatingRef, [offset(5), shift(), flip()]);
|
|
7924
|
+
return /* @__PURE__ */ u$1("div", { className: "relative", children: [
|
|
7925
|
+
/* @__PURE__ */ u$1("div", { className: "peer", ref: referenceRef, children }),
|
|
7926
|
+
/* @__PURE__ */ u$1("div", { ref: floatingRef, className: `${dropdownClass} hidden peer-hover:block`, children: content })
|
|
7927
|
+
] });
|
|
7928
|
+
};
|
|
7929
|
+
const MAX_NUMBER_OF_GRID_ROWS = 100;
|
|
7896
7930
|
const MutationsOverTimeGrid = ({ data }) => {
|
|
7897
|
-
const
|
|
7931
|
+
const allMutations = data.getFirstAxisKeys();
|
|
7932
|
+
const shownMutations = allMutations.slice(0, MAX_NUMBER_OF_GRID_ROWS);
|
|
7898
7933
|
const dates = data.getSecondAxisKeys().sort((a2, b3) => compareTemporal(a2, b3));
|
|
7899
|
-
return /* @__PURE__ */ u$1(
|
|
7900
|
-
"div",
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
const value = data.get(mutation, date) ?? 0;
|
|
7919
|
-
return /* @__PURE__ */ u$1(
|
|
7934
|
+
return /* @__PURE__ */ u$1(Fragment, { children: [
|
|
7935
|
+
allMutations.length > MAX_NUMBER_OF_GRID_ROWS && /* @__PURE__ */ u$1("div", { className: "pl-2", children: [
|
|
7936
|
+
"Showing ",
|
|
7937
|
+
MAX_NUMBER_OF_GRID_ROWS,
|
|
7938
|
+
" of ",
|
|
7939
|
+
allMutations.length,
|
|
7940
|
+
" mutations. You can narrow the filter to reduce the number of mutations."
|
|
7941
|
+
] }),
|
|
7942
|
+
/* @__PURE__ */ u$1(
|
|
7943
|
+
"div",
|
|
7944
|
+
{
|
|
7945
|
+
style: {
|
|
7946
|
+
display: "grid",
|
|
7947
|
+
gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
|
|
7948
|
+
gridTemplateColumns: `8rem repeat(${dates.length}, minmax(1.5rem, 1fr))`
|
|
7949
|
+
},
|
|
7950
|
+
children: shownMutations.map((mutation, i2) => {
|
|
7951
|
+
return /* @__PURE__ */ u$1(Fragment, { children: [
|
|
7952
|
+
/* @__PURE__ */ u$1(
|
|
7920
7953
|
"div",
|
|
7921
7954
|
{
|
|
7922
|
-
style: { gridRowStart: i2 + 1, gridColumnStart:
|
|
7923
|
-
children: /* @__PURE__ */ u$1(
|
|
7955
|
+
style: { gridRowStart: i2 + 1, gridColumnStart: 1 },
|
|
7956
|
+
children: /* @__PURE__ */ u$1(MutationCell, { mutation })
|
|
7924
7957
|
},
|
|
7925
|
-
|
|
7926
|
-
)
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7958
|
+
`mutation-${mutation.toString()}`
|
|
7959
|
+
),
|
|
7960
|
+
dates.map((date, j2) => {
|
|
7961
|
+
const value = data.get(mutation, date) ?? { proportion: 0, count: 0 };
|
|
7962
|
+
return /* @__PURE__ */ u$1(
|
|
7963
|
+
"div",
|
|
7964
|
+
{
|
|
7965
|
+
style: { gridRowStart: i2 + 1, gridColumnStart: j2 + 2 },
|
|
7966
|
+
children: /* @__PURE__ */ u$1(ProportionCell, { value, date, mutation })
|
|
7967
|
+
},
|
|
7968
|
+
`${mutation.toString()}-${date.toString()}`
|
|
7969
|
+
);
|
|
7970
|
+
})
|
|
7971
|
+
] }, `fragment-${mutation.toString()}`);
|
|
7972
|
+
})
|
|
7973
|
+
}
|
|
7974
|
+
)
|
|
7975
|
+
] });
|
|
7932
7976
|
};
|
|
7933
|
-
const ProportionCell = ({ value }) => {
|
|
7934
|
-
|
|
7977
|
+
const ProportionCell = ({ value, mutation, date }) => {
|
|
7978
|
+
const tooltipContent = /* @__PURE__ */ u$1("div", { children: [
|
|
7979
|
+
/* @__PURE__ */ u$1("p", { children: [
|
|
7980
|
+
/* @__PURE__ */ u$1("span", { className: "font-bold", children: date.englishName() }),
|
|
7981
|
+
" (",
|
|
7982
|
+
timeIntervalDisplay(date),
|
|
7983
|
+
")"
|
|
7984
|
+
] }),
|
|
7985
|
+
/* @__PURE__ */ u$1("p", { children: mutation.code }),
|
|
7986
|
+
/* @__PURE__ */ u$1("p", { children: [
|
|
7987
|
+
"Proportion: ",
|
|
7988
|
+
formatProportion(value.proportion)
|
|
7989
|
+
] }),
|
|
7990
|
+
/* @__PURE__ */ u$1("p", { children: [
|
|
7991
|
+
"Count: ",
|
|
7992
|
+
value.count
|
|
7993
|
+
] })
|
|
7994
|
+
] });
|
|
7995
|
+
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
7996
|
"div",
|
|
7936
7997
|
{
|
|
7937
|
-
style: {
|
|
7998
|
+
style: {
|
|
7999
|
+
backgroundColor: backgroundColor(value.proportion),
|
|
8000
|
+
color: textColor(value.proportion)
|
|
8001
|
+
},
|
|
7938
8002
|
className: "text-center hover:font-bold text-xs",
|
|
7939
|
-
children: formatProportion(value, 0)
|
|
8003
|
+
children: formatProportion(value.proportion, 0)
|
|
7940
8004
|
}
|
|
7941
|
-
) }) });
|
|
8005
|
+
) }) }) });
|
|
8006
|
+
};
|
|
8007
|
+
const timeIntervalDisplay = (date) => {
|
|
8008
|
+
if (date instanceof YearMonthDay) {
|
|
8009
|
+
return date.toString();
|
|
8010
|
+
}
|
|
8011
|
+
return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
|
|
7942
8012
|
};
|
|
7943
8013
|
const backgroundColor = (proportion) => {
|
|
7944
8014
|
const minAlpha = 0;
|
|
@@ -8014,8 +8084,15 @@ class Map2d {
|
|
|
8014
8084
|
return copy;
|
|
8015
8085
|
}
|
|
8016
8086
|
}
|
|
8087
|
+
const MAX_NUMBER_OF_GRID_COLUMNS = 200;
|
|
8017
8088
|
async function queryMutationsOverTimeData(lapisFilter, sequenceType, lapis, lapisDateField, granularity, signal) {
|
|
8018
8089
|
const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
|
|
8090
|
+
if (allDates.length > MAX_NUMBER_OF_GRID_COLUMNS) {
|
|
8091
|
+
throw new UserFacingError(
|
|
8092
|
+
"Too many dates",
|
|
8093
|
+
`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.`
|
|
8094
|
+
);
|
|
8095
|
+
}
|
|
8019
8096
|
const subQueries = allDates.map(async (date) => {
|
|
8020
8097
|
const dateFrom = date.firstDay.toString();
|
|
8021
8098
|
const dateTo = date.lastDay.toString();
|
|
@@ -8081,7 +8158,10 @@ function groupByMutation(data) {
|
|
|
8081
8158
|
);
|
|
8082
8159
|
data.forEach((mutationData) => {
|
|
8083
8160
|
mutationData.mutations.forEach((mutationEntry) => {
|
|
8084
|
-
dataArray.set(mutationEntry.mutation, mutationData.date,
|
|
8161
|
+
dataArray.set(mutationEntry.mutation, mutationData.date, {
|
|
8162
|
+
count: mutationEntry.count,
|
|
8163
|
+
proportion: mutationEntry.proportion
|
|
8164
|
+
});
|
|
8085
8165
|
});
|
|
8086
8166
|
});
|
|
8087
8167
|
addZeroValuesForDatesWithNoMutationData(dataArray, data);
|
|
@@ -8092,7 +8172,7 @@ function addZeroValuesForDatesWithNoMutationData(dataArray, data) {
|
|
|
8092
8172
|
const someMutation = dataArray.getFirstAxisKeys()[0];
|
|
8093
8173
|
data.forEach((mutationData) => {
|
|
8094
8174
|
if (mutationData.mutations.length === 0) {
|
|
8095
|
-
dataArray.set(someMutation, mutationData.date, 0);
|
|
8175
|
+
dataArray.set(someMutation, mutationData.date, { count: 0, proportion: 0 });
|
|
8096
8176
|
}
|
|
8097
8177
|
});
|
|
8098
8178
|
}
|