@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.
@@ -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 lastDay = dayjs().year(this.isoYearNumber).month(12).date(31).isoWeek(this.isoWeekNumber).endOf("isoWeek");
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((value) => value >= proportionInterval.min && value <= proportionInterval.max)) {
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 mutations = data.getFirstAxisKeys();
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
- style: {
7903
- display: "grid",
7904
- gridTemplateRows: `repeat(${mutations.length}, 24px)`,
7905
- gridTemplateColumns: `8rem repeat(${dates.length}, minmax(1.5rem, 1fr))`
7906
- },
7907
- children: mutations.map((mutation, i2) => {
7908
- return /* @__PURE__ */ u$1(Fragment, { children: [
7909
- /* @__PURE__ */ u$1(
7910
- "div",
7911
- {
7912
- style: { gridRowStart: i2 + 1, gridColumnStart: 1 },
7913
- children: /* @__PURE__ */ u$1(MutationCell, { mutation })
7914
- },
7915
- `mutation-${mutation.toString()}`
7916
- ),
7917
- dates.map((date, j2) => {
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: j2 + 2 },
7923
- children: /* @__PURE__ */ u$1(ProportionCell, { value, date, mutation })
7955
+ style: { gridRowStart: i2 + 1, gridColumnStart: 1 },
7956
+ children: /* @__PURE__ */ u$1(MutationCell, { mutation })
7924
7957
  },
7925
- `${mutation.toString()}-${date.toString()}`
7926
- );
7927
- })
7928
- ] }, `fragment-${mutation.toString()}`);
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
- return /* @__PURE__ */ u$1(Fragment, { children: /* @__PURE__ */ u$1("div", { className: "py-1", children: /* @__PURE__ */ u$1(
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: { backgroundColor: backgroundColor(value), color: textColor(value) },
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, mutationEntry.proportion);
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
  }