@datawheel/data-explorer 1.3.3 → 1.4.1

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/dist/main.d.mts CHANGED
@@ -451,6 +451,7 @@ interface FilterItem extends QueryParamsItem {
451
451
  conditionOne: [`${Comparison}`, string, number];
452
452
  conditionTwo?: [`${Comparison}`, string, number];
453
453
  joint: "and" | "or";
454
+ type?: "greaterThan" | "lessThan" | "between";
454
455
  }
455
456
  interface MeasureItem extends QueryParamsItem {
456
457
  name: string;
@@ -462,6 +463,7 @@ interface PropertyItem extends QueryParamsItem {
462
463
  }
463
464
 
464
465
  type Formatter = (value: number | null, locale?: string) => string;
466
+ type DrilldownFormatter = (value: string | null, locale?: string) => string;
465
467
  interface PanelDescriptor {
466
468
  key: string;
467
469
  label: string;
@@ -1145,6 +1147,10 @@ declare function ExplorerComponent<Locale extends string>(props: {
1145
1147
  * that are displayed next to the value.
1146
1148
  * */
1147
1149
  idFormatters?: Record<string, Formatter>;
1150
+ /**
1151
+ * Defines an index of formatter functions available for drilldowns.
1152
+ * */
1153
+ drilldownFormatters?: Record<string, DrilldownFormatter>;
1148
1154
  /**
1149
1155
  * Defines an alternative height for the component structure.
1150
1156
  * @default "100vh"
@@ -1332,6 +1338,7 @@ interface SettingsContextProps {
1332
1338
  defaultMembersFilter: "id" | "name" | "any";
1333
1339
  formatters: Record<string, Formatter>;
1334
1340
  idFormatters: Record<string, Formatter>;
1341
+ drilldownFormatters: Record<string, DrilldownFormatter>;
1335
1342
  previewLimit: number;
1336
1343
  panels: PanelDescriptor[];
1337
1344
  paginationConfig: Pagination;
package/dist/main.mjs CHANGED
@@ -1,13 +1,13 @@
1
- import { keyframes, createStyles, Select, Center, Text, rem, Input, Box, Stack, Group, Button, SimpleGrid, Flex, ScrollArea, LoadingOverlay, Table, MultiSelect, NumberInput, Menu, ActionIcon, UnstyledButton, Loader, Container, Title, useMantineTheme, TextInput, CopyButton, MantineProvider, Alert, Space, Modal, Tooltip, useComponentDefaultProps, Skeleton, Anchor, Paper, packSx, CloseButton, Tabs, ThemeIcon, Drawer, Switch, Divider, Accordion, Popover, Checkbox } from '@mantine/core';
1
+ import { keyframes, createStyles, Select, Center, Text, rem, Input, Box, Stack, Group, Button, SimpleGrid, Flex, ScrollArea, LoadingOverlay, Table, MultiSelect, NumberInput, Menu, ActionIcon, UnstyledButton, Loader, Container, Title, useMantineTheme, TextInput, CopyButton, MantineProvider, Alert, Space, Pagination, Modal, Tooltip, useComponentDefaultProps, Skeleton, Anchor, Paper, packSx, CloseButton, Tabs, ThemeIcon, Drawer, Switch, Divider, Accordion, Popover, Checkbox } from '@mantine/core';
2
2
  import { useClipboard, useDebouncedState, useMediaQuery, useFullscreen, useDisclosure } from '@mantine/hooks';
3
3
  import { IconWorld, IconExternalLink, IconClipboard, IconSettings, IconMathGreater, IconMathLower, IconArrowsLeftRight, IconWorldWww, IconClipboardCheck, IconAlertCircle, IconAlertTriangle, IconLanguage, IconCopy, IconArrowLeft, IconArrowRight, IconX, IconChevronLeft, IconChevronRight, IconVectorTriangle, IconPhotoDown, IconDownload, IconArrowsMinimize, IconArrowsMaximize, IconCheck, IconShare, IconSearch, IconDotsVertical, IconTrash, IconPlus, IconStack3, IconInfoCircleFilled, IconArrowsSort, IconSortDescendingNumbers, IconSortDescendingLetters, IconSortAscendingNumbers, IconSortAscendingLetters, IconHelpCircle, IconFilterOff, IconFilter, IconBox, IconClock, IconAdjustments } from '@tabler/icons-react';
4
4
  import * as React15 from 'react';
5
- import React15__default, { createContext, forwardRef, useMemo, useCallback, useContext, useRef, useEffect, useState, Suspense, useLayoutEffect } from 'react';
5
+ import React15__default, { createContext, forwardRef, useMemo, memo, useCallback, useContext, useRef, useEffect, useState, Suspense, useLayoutEffect } from 'react';
6
6
  import { translationFactory } from '@datawheel/use-translation';
7
7
  import { createSlice, createSelector, combineReducers, configureStore, bindActionCreators } from '@reduxjs/toolkit';
8
8
  import { useSelector as useSelector$1, useStore, Provider as Provider$1 } from 'react-redux';
9
9
  import { useNavigate, BrowserRouter, useLocation } from 'react-router-dom';
10
- import { VizbuilderProvider, useVizbuilderContext, ErrorBoundary, useD3plusConfig } from '@datawheel/vizbuilder/react';
10
+ import { VizbuilderProvider, useVizbuilderContext, d3plusConfigBuilder, ErrorBoundary, useD3plusConfig } from '@datawheel/vizbuilder/react';
11
11
  import { QueryClient, useQuery, QueryClientProvider, keepPreviousData, useMutation } from '@tanstack/react-query';
12
12
  import { assign } from 'd3plus-common';
13
13
  import identity2 from 'lodash-es/identity';
@@ -369,7 +369,8 @@ function getAnnotation(item, key, locale = "xx") {
369
369
  return ann[`${key}_${locale}`] || ann[`${key}_${locale.slice(0, 2)}`] || ann[key];
370
370
  }
371
371
  function parseNumeric(value, elseValue) {
372
- return value && Number.isFinite(value) && !Number.isNaN(value) ? Number.parseFloat(value) : elseValue;
372
+ const result = Number.parseFloat(String(value));
373
+ return Number.isFinite(result) ? result : elseValue;
373
374
  }
374
375
 
375
376
  // src/utils/array.ts
@@ -599,6 +600,7 @@ function SettingsProvider(props) {
599
600
  defaultMembersFilter: props.defaultMembersFilter || "id",
600
601
  formatters: props.formatters || {},
601
602
  idFormatters: props.idFormatters || {},
603
+ drilldownFormatters: props.drilldownFormatters || {},
602
604
  panels: props.panels,
603
605
  previewLimit: props.previewLimit || 50,
604
606
  paginationConfig: (_a = props.pagination) != null ? _a : { rowsLimits: [100, 300, 500, 1e3], defaultLimit: 100 },
@@ -712,6 +714,10 @@ function useidFormatters() {
712
714
  const { idFormatters } = useSettings();
713
715
  return { idFormatters };
714
716
  }
717
+ function useDrilldownFormatters() {
718
+ const { drilldownFormatters } = useSettings();
719
+ return { drilldownFormatters };
720
+ }
715
721
 
716
722
  // src/utils/structs.ts
717
723
  function buildQuery(props) {
@@ -786,15 +792,16 @@ function buildFilter(props) {
786
792
  measure: props.measure || `${props.name}`,
787
793
  conditionOne: props.conditionOne || [
788
794
  props.const1 ? `${props.const1[0]}` : `${"gt" /* GT */}`,
789
- props.const1 ? props.const1[1].toString() : props.inputtedValue || "0",
790
- props.const1 ? props.const1[1] : parseNumeric(props.interpretedValue, 0)
791
- ],
792
- conditionTwo: props.conditionTwo || [
793
- props.const2 ? `${props.const2[0]}` : `${"gt" /* GT */}`,
794
- props.const2 ? props.const2[1].toString() : props.inputtedValue || "0",
795
- props.const2 ? props.const2[1] : parseNumeric(props.interpretedValue, 0)
795
+ props.const1 ? props.const1[1].toString() : props.inputtedValue || "",
796
+ props.const1 ? props.const1[1] : parseNumeric(props.interpretedValue, NaN)
796
797
  ],
797
- joint: props.joint === "or" ? "or" : "and"
798
+ conditionTwo: props.conditionTwo || (props.const2 ? [
799
+ `${props.const2[0]}`,
800
+ props.const2[1].toString(),
801
+ parseNumeric(props.const2[1].toString(), NaN)
802
+ ] : void 0),
803
+ joint: props.joint === "or" ? "or" : "and",
804
+ type: props.type
798
805
  };
799
806
  }
800
807
  function buildMeasure(props) {
@@ -978,13 +985,28 @@ function filterParse(item) {
978
985
  const condition = item.slice(indexName + 1);
979
986
  const joint = condition.includes(".and.") ? "and" : "or";
980
987
  const [cond1, cond2] = condition.split(`.${joint}.`);
988
+ const conditionOne = filterParseCondition(cond1);
989
+ const conditionTwo = cond2 ? filterParseCondition(cond2) : void 0;
990
+ let type;
991
+ if (conditionOne && conditionTwo) {
992
+ if (conditionOne[0] === "gte" /* GTE */ && conditionTwo[0] === "lte" /* LTE */) {
993
+ type = "between";
994
+ }
995
+ } else if (conditionOne) {
996
+ if (conditionOne[0] === "gte" /* GTE */ || conditionOne[0] === "gt" /* GT */) {
997
+ type = "greaterThan";
998
+ } else if (conditionOne[0] === "lte" /* LTE */ || conditionOne[0] === "lt" /* LT */) {
999
+ type = "lessThan";
1000
+ }
1001
+ }
981
1002
  return {
982
1003
  key: Math.random().toString(16).slice(2),
983
1004
  active: true,
984
1005
  measure: item.slice(0, indexName),
985
1006
  joint,
986
- conditionOne: filterParseCondition(cond1),
987
- conditionTwo: cond2 ? filterParseCondition(cond2) : void 0
1007
+ conditionOne,
1008
+ conditionTwo,
1009
+ type
988
1010
  };
989
1011
  }
990
1012
 
@@ -2103,6 +2125,14 @@ function QueryProvider({ children, defaultCube }) {
2103
2125
  });
2104
2126
  });
2105
2127
  if (newQuery) {
2128
+ Object.values(newQuery.params.filters).forEach((filter) => {
2129
+ const currentFilter = Object.values(queryItem.params.filters).find(
2130
+ (f) => f.measure === filter.measure
2131
+ );
2132
+ if (currentFilter) {
2133
+ filter.type = currentFilter.type;
2134
+ }
2135
+ });
2106
2136
  const promises = [...restDrilldowns, ...Object.values(newQuery.params.drilldowns)].map(
2107
2137
  (dd) => {
2108
2138
  const currentDrilldown = queryItem.params.drilldowns[dd.key];
@@ -3117,26 +3147,60 @@ function getFiltersConditions(fn, value) {
3117
3147
  const comparisonMap = /* @__PURE__ */ new Map([
3118
3148
  [
3119
3149
  "greaterThan",
3120
- (value2) => ({
3121
- conditionOne: ["gte" /* GTE */, String(value2[0]), Number(value2[0])],
3122
- conditionTwo: ["gt" /* GT */, "0", 0]
3123
- })
3150
+ (value2) => {
3151
+ const val = value2[0] === "" ? "" : String(value2[0]);
3152
+ const num = value2[0] === "" ? NaN : Number(value2[0]);
3153
+ return {
3154
+ conditionOne: ["gte" /* GTE */, val, num],
3155
+ conditionTwo: void 0,
3156
+ type: "greaterThan"
3157
+ };
3158
+ }
3124
3159
  ],
3125
3160
  [
3126
3161
  "lessThan",
3127
- (value2) => ({
3128
- conditionOne: ["lte" /* LTE */, String(value2[0]), Number(value2[0])],
3129
- conditionTwo: ["gt" /* GT */, "0", 0]
3130
- })
3162
+ (value2) => {
3163
+ const val = value2[0] === "" ? "" : String(value2[0]);
3164
+ const num = value2[0] === "" ? NaN : Number(value2[0]);
3165
+ return {
3166
+ conditionOne: ["lte" /* LTE */, val, num],
3167
+ conditionTwo: void 0,
3168
+ type: "lessThan"
3169
+ };
3170
+ }
3131
3171
  ],
3132
3172
  [
3133
3173
  "between",
3134
3174
  (values) => {
3135
3175
  const [min, max] = values;
3176
+ const isMinValid = min !== "" && min !== null && min !== void 0;
3177
+ const isMaxValid = max !== "" && max !== null && max !== void 0;
3178
+ if (isMinValid && isMaxValid) {
3179
+ return {
3180
+ conditionOne: ["gte" /* GTE */, String(min), Number(min)],
3181
+ conditionTwo: ["lte" /* LTE */, String(max), Number(max)],
3182
+ joint: "and",
3183
+ type: "between"
3184
+ };
3185
+ }
3186
+ if (isMinValid) {
3187
+ return {
3188
+ conditionOne: ["gte" /* GTE */, String(min), Number(min)],
3189
+ conditionTwo: void 0,
3190
+ type: "between"
3191
+ };
3192
+ }
3193
+ if (isMaxValid) {
3194
+ return {
3195
+ conditionOne: ["lte" /* LTE */, String(max), Number(max)],
3196
+ conditionTwo: void 0,
3197
+ type: "between"
3198
+ };
3199
+ }
3136
3200
  return {
3137
- conditionOne: ["gte" /* GTE */, String(min), Number(min)],
3138
- conditionTwo: ["lte" /* LTE */, String(max), Number(max)],
3139
- joint: "and"
3201
+ conditionOne: ["gte" /* GTE */, "", NaN],
3202
+ conditionTwo: void 0,
3203
+ type: "between"
3140
3204
  };
3141
3205
  }
3142
3206
  ]
@@ -3259,6 +3323,7 @@ function useTable({
3259
3323
  const { translate: t } = useTranslation();
3260
3324
  const { getFormat, getFormatter } = useFormatter();
3261
3325
  const { idFormatters } = useidFormatters();
3326
+ const { drilldownFormatters } = useDrilldownFormatters();
3262
3327
  const { sortKey, sortDir } = useSelector$1(selectSortingParams);
3263
3328
  const theme = useMantineTheme();
3264
3329
  const isSmallerThanMd = useMediaQuery(
@@ -3412,6 +3477,8 @@ function useTable({
3412
3477
  const row = cell.row;
3413
3478
  const cellId = row.original[`${cell.column.id} ID`];
3414
3479
  const idFormatter = idFormatters[`${keyCol.localeLabel} ID`];
3480
+ const drilldownFormatter = drilldownFormatters[`${keyCol.localeLabel}`] || drilldownFormatters[`${entity.name}`];
3481
+ const formattedValue = drilldownFormatter ? drilldownFormatter(cellValue, locale) : cellValue;
3415
3482
  return /* @__PURE__ */ React15__default.createElement(Flex, { justify: "space-between", sx: { width: "100%", maxWidth: 400 }, gap: "sm" }, /* @__PURE__ */ React15__default.createElement(
3416
3483
  Text,
3417
3484
  {
@@ -3422,7 +3489,7 @@ function useTable({
3422
3489
  textOverflow: "ellipsis"
3423
3490
  }
3424
3491
  },
3425
- cellValue
3492
+ formattedValue
3426
3493
  ), /* @__PURE__ */ React15__default.createElement(Box, null, cellId && /* @__PURE__ */ React15__default.createElement(Text, { color: "dimmed" }, idFormatter ? idFormatter(cellId) : cellId)));
3427
3494
  }
3428
3495
  };
@@ -3723,12 +3790,13 @@ var MultiFilter = ({ header }) => {
3723
3790
  const { translate: t } = useTranslation();
3724
3791
  const cutItems = useSelector$1(selectCutItems);
3725
3792
  const drilldownItems = useSelector$1(selectDrilldownItems);
3726
- useSelector$1(selectLocale);
3727
- header.column.id;
3793
+ const locale = useSelector$1(selectLocale);
3794
+ const label = header.column.id;
3728
3795
  const localeLabel = header.column.columnDef.header;
3729
3796
  const drilldown = drilldownItems.find((d) => d.level === header.column.id);
3730
3797
  const actions2 = useActions();
3731
3798
  const { idFormatters } = useidFormatters();
3799
+ const { drilldownFormatters } = useDrilldownFormatters();
3732
3800
  const navigate = useNavigate();
3733
3801
  const debouncedUpdateUrl = useMemo(
3734
3802
  () => debounce((query2) => {
@@ -3772,11 +3840,13 @@ var MultiFilter = ({ header }) => {
3772
3840
  value: cut.members || [],
3773
3841
  data: drilldown.members.map((m) => {
3774
3842
  const idFormatter = idFormatters[`${localeLabel} ID`];
3843
+ const drilldownFormatter = drilldownFormatters[`${localeLabel}`] || drilldownFormatters[`${label}`];
3775
3844
  const formattedKey = idFormatter ? idFormatter(m.key) : m.key;
3776
3845
  const key = formattedKey ? `(${formattedKey})` : formattedKey;
3846
+ const formattedCaption = drilldownFormatter ? drilldownFormatter(m.caption || m.key, locale.code) : m.caption;
3777
3847
  return {
3778
3848
  value: `${m.key}`,
3779
- label: m.caption ? `${m.caption} ${key}` : `${key}`
3849
+ label: formattedCaption ? `${formattedCaption} ${key}` : `${key}`
3780
3850
  };
3781
3851
  }),
3782
3852
  clearButtonProps: { "aria-label": "Clear selection" },
@@ -3949,6 +4019,7 @@ function LevelItem({
3949
4019
  const drilldowns = useSelector$1(selectDrilldownMap);
3950
4020
  useSelector$1(selectDrilldownItems);
3951
4021
  const { idFormatters } = useidFormatters();
4022
+ const { drilldownFormatters } = useDrilldownFormatters();
3952
4023
  const label = useMemo(() => {
3953
4024
  const captions = [
3954
4025
  getCaption(dimension, locale),
@@ -4057,11 +4128,13 @@ function LevelItem({
4057
4128
  value: (cut == null ? void 0 : cut.members) || [],
4058
4129
  data: currentDrilldown.members.map((m) => {
4059
4130
  const idFormatter = idFormatters[`${label} ID`];
4131
+ const drilldownFormatter = drilldownFormatters[`${label}`];
4060
4132
  const formattedKey = idFormatter ? idFormatter(m.key) : m.key;
4061
4133
  const key = formattedKey ? `(${formattedKey})` : formattedKey;
4134
+ const formattedCaption = drilldownFormatter ? drilldownFormatter(m.caption || m.key, locale) : m.caption;
4062
4135
  return {
4063
4136
  value: `${m.key}`,
4064
- label: m.caption ? `${m.caption} ${key}` : `${key}`
4137
+ label: formattedCaption ? `${formattedCaption} ${key}` : `${key}`
4065
4138
  };
4066
4139
  }),
4067
4140
  clearable: true,
@@ -4132,6 +4205,9 @@ function getFilterfnKey(type) {
4132
4205
  }
4133
4206
  }
4134
4207
  function getFilterFn(filter) {
4208
+ if (filter.type) {
4209
+ return filter.type;
4210
+ }
4135
4211
  if (filter.conditionOne && filter.conditionTwo) {
4136
4212
  if (filter.conditionOne[0] === "gte" /* GTE */ && filter.conditionTwo[0] === "lte" /* LTE */) {
4137
4213
  return "between";
@@ -4163,13 +4239,14 @@ function NumberInputComponent({ text, filter }) {
4163
4239
  };
4164
4240
  }, [debouncedUpdateUrl]);
4165
4241
  function getFilterValue(filter2) {
4166
- return isNotValid(filter2.conditionOne[2]) || filter2.conditionOne[2] === 0 ? "" : filter2.conditionOne[2];
4242
+ return isNotValid(filter2.conditionOne[2]) ? "" : filter2.conditionOne[2];
4167
4243
  }
4168
4244
  function onInputChange({ filter: filter2, value }) {
4169
4245
  const isEmpty = value === "";
4170
- const conditions = getFiltersConditions(getFilterFn(filter2) || "greaterThan", [Number(value)]) || {};
4246
+ const currentType = getFilterFn(filter2) || "greaterThan";
4247
+ const conditions = getFiltersConditions(currentType, [value]) || {};
4171
4248
  const active = !isEmpty;
4172
- const newFilter = buildFilter({ ...filter2, active, ...conditions });
4249
+ const newFilter = buildFilter({ ...filter2, active, ...conditions, type: currentType });
4173
4250
  actions2.updateFilter(newFilter);
4174
4251
  const newQuery = buildQuery(cloneDeep(queryItem));
4175
4252
  newQuery.params.filters[filter2.key] = newFilter;
@@ -4204,9 +4281,10 @@ var MinMax = ({ filter, hideControls, ...rest }) => {
4204
4281
  }, [debouncedUpdateUrl]);
4205
4282
  function onInputChangeMinMax(props) {
4206
4283
  const { filter: filter2, min: min2, max: max2 } = props;
4207
- const conditions = getFiltersConditions(getFilterFn(filter2) || "greaterThan", [Number(min2), Number(max2)]) || {};
4208
- const active = Boolean(min2) && Boolean(max2);
4209
- const newFilter = buildFilter({ ...filter2, active, ...conditions });
4284
+ const currentType = getFilterFn(filter2) || "between";
4285
+ const conditions = getFiltersConditions(currentType, [min2, max2]) || {};
4286
+ const active = min2 !== "" || max2 !== "";
4287
+ const newFilter = buildFilter({ ...filter2, active, ...conditions, type: currentType });
4210
4288
  actions2.updateFilter(newFilter);
4211
4289
  const newQuery = buildQuery(cloneDeep(queryItem));
4212
4290
  newQuery.params.filters[filter2.key] = newFilter;
@@ -4214,7 +4292,7 @@ var MinMax = ({ filter, hideControls, ...rest }) => {
4214
4292
  }
4215
4293
  function getFilterValue(condition) {
4216
4294
  if (condition) {
4217
- return isNotValid(condition[2]) || condition[2] === 0 ? "" : condition[2];
4295
+ return isNotValid(condition[2]) ? "" : condition[2];
4218
4296
  }
4219
4297
  return "";
4220
4298
  }
@@ -4244,14 +4322,26 @@ var MinMax = ({ filter, hideControls, ...rest }) => {
4244
4322
  };
4245
4323
  function FilterFnsMenu({ filter }) {
4246
4324
  const actions2 = useActions();
4325
+ const updateUrl = useUpdateUrl();
4326
+ const queryItem = useSelector$1(selectCurrentQueryItem);
4247
4327
  const { translate: t } = useTranslation();
4248
4328
  return /* @__PURE__ */ React15__default.createElement(React15__default.Fragment, null, /* @__PURE__ */ React15__default.createElement(Menu, { shadow: "md", width: 200 }, /* @__PURE__ */ React15__default.createElement(Menu.Target, null, /* @__PURE__ */ React15__default.createElement(ActionIcon, { size: "xs" }, /* @__PURE__ */ React15__default.createElement(IconSettings, null))), /* @__PURE__ */ React15__default.createElement(Menu.Dropdown, null, /* @__PURE__ */ React15__default.createElement(Menu.Label, null, t("params.filter_mode")), /* @__PURE__ */ React15__default.createElement(
4249
4329
  Menu.Item,
4250
4330
  {
4251
4331
  icon: /* @__PURE__ */ React15__default.createElement(IconMathGreater, { size: 14 }),
4252
4332
  onClick: () => {
4253
- const conditions = getFiltersConditions("greaterThan", [0]) || {};
4254
- actions2.updateFilter(buildFilter({ ...filter, ...conditions, active: false }));
4333
+ const currentType = "greaterThan";
4334
+ const conditions = getFiltersConditions(currentType, [""]) || {};
4335
+ const newFilter = buildFilter({
4336
+ ...filter,
4337
+ ...conditions,
4338
+ active: false,
4339
+ type: currentType
4340
+ });
4341
+ actions2.updateFilter(newFilter);
4342
+ const newQuery = buildQuery(cloneDeep(queryItem));
4343
+ newQuery.params.filters[filter.key] = newFilter;
4344
+ updateUrl(newQuery);
4255
4345
  }
4256
4346
  },
4257
4347
  t("comparison.GT")
@@ -4260,8 +4350,18 @@ function FilterFnsMenu({ filter }) {
4260
4350
  {
4261
4351
  icon: /* @__PURE__ */ React15__default.createElement(IconMathLower, { size: 14 }),
4262
4352
  onClick: () => {
4263
- const conditions = getFiltersConditions("lessThan", [0]) || {};
4264
- actions2.updateFilter(buildFilter({ ...filter, ...conditions, active: false }));
4353
+ const currentType = "lessThan";
4354
+ const conditions = getFiltersConditions(currentType, [""]) || {};
4355
+ const newFilter = buildFilter({
4356
+ ...filter,
4357
+ ...conditions,
4358
+ active: false,
4359
+ type: currentType
4360
+ });
4361
+ actions2.updateFilter(newFilter);
4362
+ const newQuery = buildQuery(cloneDeep(queryItem));
4363
+ newQuery.params.filters[filter.key] = newFilter;
4364
+ updateUrl(newQuery);
4265
4365
  }
4266
4366
  },
4267
4367
  t("comparison.LT")
@@ -4270,8 +4370,18 @@ function FilterFnsMenu({ filter }) {
4270
4370
  {
4271
4371
  icon: /* @__PURE__ */ React15__default.createElement(IconArrowsLeftRight, { size: 14 }),
4272
4372
  onClick: () => {
4273
- const conditions = getFiltersConditions("between", [0, 0]) || {};
4274
- actions2.updateFilter(buildFilter({ ...filter, ...conditions, active: false }));
4373
+ const currentType = "between";
4374
+ const conditions = getFiltersConditions(currentType, ["", ""]) || {};
4375
+ const newFilter = buildFilter({
4376
+ ...filter,
4377
+ ...conditions,
4378
+ active: false,
4379
+ type: currentType
4380
+ });
4381
+ actions2.updateFilter(newFilter);
4382
+ const newQuery = buildQuery(cloneDeep(queryItem));
4383
+ newQuery.params.filters[filter.key] = newFilter;
4384
+ updateUrl(newQuery);
4275
4385
  }
4276
4386
  },
4277
4387
  t("comparison.BT")
@@ -6528,6 +6638,7 @@ function ExplorerComponent(props) {
6528
6638
  defaultMembersFilter: props.defaultMembersFilter,
6529
6639
  formatters: props.formatters,
6530
6640
  idFormatters: props.idFormatters,
6641
+ drilldownFormatters: props.drilldownFormatters,
6531
6642
  withPermalink: props.withPermalink,
6532
6643
  panels,
6533
6644
  pagination,
@@ -6649,6 +6760,31 @@ var iconByFormat = {
6649
6760
  png: IconPhotoDown,
6650
6761
  svg: IconVectorTriangle
6651
6762
  };
6763
+ var ChartRenderer = memo((props) => {
6764
+ const { ChartComponent, config, containerRef, overlayRef, onReady } = props;
6765
+ React15__default.useEffect(() => {
6766
+ const box = containerRef.current;
6767
+ if (!box) return;
6768
+ const checkSvg = () => {
6769
+ const svg = box.querySelector("svg");
6770
+ if (svg) {
6771
+ if (overlayRef.current) {
6772
+ overlayRef.current.style.display = "none";
6773
+ }
6774
+ onReady();
6775
+ return true;
6776
+ }
6777
+ return false;
6778
+ };
6779
+ if (checkSvg()) return;
6780
+ const observer = new MutationObserver(() => {
6781
+ if (checkSvg()) observer.disconnect();
6782
+ });
6783
+ observer.observe(box, { childList: true, subtree: true });
6784
+ return () => observer.disconnect();
6785
+ }, [containerRef, overlayRef, onReady]);
6786
+ return /* @__PURE__ */ React15__default.createElement(ChartComponent, { config });
6787
+ });
6652
6788
  function ChartCard(props) {
6653
6789
  const { chart, isFullMode, onFocus, height } = props;
6654
6790
  const { dataset } = chart.datagroup;
@@ -6723,8 +6859,46 @@ function ChartCard(props) {
6723
6859
  copied ? translate("share_copied") : translate("action_share")
6724
6860
  ));
6725
6861
  }, [translate]);
6862
+ const [isLoaded, setIsLoaded] = useState(false);
6863
+ const [shouldRenderChart, setShouldRenderChart] = useState(false);
6864
+ const overlayRef = useRef(null);
6865
+ React15__default.useEffect(() => {
6866
+ setIsLoaded(false);
6867
+ setShouldRenderChart(false);
6868
+ if (overlayRef.current) {
6869
+ overlayRef.current.style.display = "block";
6870
+ }
6871
+ }, [chart.key]);
6872
+ React15__default.useEffect(() => {
6873
+ if (ChartComponent && (inView || hasBeenInView) && !shouldRenderChart) {
6874
+ const timer = setTimeout(() => {
6875
+ window.requestAnimationFrame(() => {
6876
+ window.requestAnimationFrame(() => {
6877
+ setShouldRenderChart(true);
6878
+ });
6879
+ });
6880
+ }, 500);
6881
+ return () => clearTimeout(timer);
6882
+ }
6883
+ }, [ChartComponent, inView, hasBeenInView, shouldRenderChart]);
6884
+ React15__default.useEffect(() => {
6885
+ const box = nodeRef.current;
6886
+ if (!box || !shouldRenderChart || isLoaded) return;
6887
+ const observer = new MutationObserver(() => {
6888
+ const svg = box.querySelector("svg");
6889
+ if (svg) {
6890
+ window.requestAnimationFrame(() => setIsLoaded(true));
6891
+ observer.disconnect();
6892
+ }
6893
+ });
6894
+ observer.observe(box, { childList: true, subtree: true });
6895
+ if (box.querySelector("svg")) {
6896
+ setIsLoaded(true);
6897
+ }
6898
+ return () => observer.disconnect();
6899
+ }, [shouldRenderChart, isLoaded]);
6726
6900
  if (!ChartComponent || !config) return null;
6727
- const resolvedHeight = height ? height : isFullMode ? "calc(100vh - 3rem)" : 400;
6901
+ const resolvedHeight = height ? height : isFullMode ? "calc(100vh - 3rem)" : "calc((80vh - 4rem) / 2)";
6728
6902
  return /* @__PURE__ */ React15__default.createElement(ErrorBoundary, { ErrorContent: CardErrorComponent }, /* @__PURE__ */ React15__default.createElement(
6729
6903
  Paper,
6730
6904
  {
@@ -6735,11 +6909,42 @@ function ChartCard(props) {
6735
6909
  /* @__PURE__ */ React15__default.createElement(Stack, { spacing: "xs", p: "xs", style: { position: "relative" }, h: "100%", w: "100%" }, /* @__PURE__ */ React15__default.createElement(Group, { position: "center", spacing: "xs", align: "center" }, isFullMode && shareButton, downloadButtons, onFocus && focusButton), /* @__PURE__ */ React15__default.createElement(
6736
6910
  Box,
6737
6911
  {
6738
- style: { flex: "1 1 auto" },
6912
+ style: { flex: "1 1 auto", position: "relative" },
6739
6913
  ref: setRefs,
6740
6914
  sx: { "& > .viz": { height: "100%" } }
6741
6915
  },
6742
- ChartComponent && (inView || hasBeenInView) ? /* @__PURE__ */ React15__default.createElement(ChartComponent, { config }) : /* @__PURE__ */ React15__default.createElement("div", { style: { height: "100%", width: "100%" } })
6916
+ /* @__PURE__ */ React15__default.createElement(
6917
+ Box,
6918
+ {
6919
+ ref: overlayRef,
6920
+ style: {
6921
+ position: "absolute",
6922
+ top: 0,
6923
+ left: 0,
6924
+ right: 0,
6925
+ bottom: 0,
6926
+ zIndex: 100
6927
+ }
6928
+ },
6929
+ /* @__PURE__ */ React15__default.createElement(
6930
+ LoadingOverlay,
6931
+ {
6932
+ visible: !isLoaded,
6933
+ overlayBlur: 2,
6934
+ transitionDuration: 0
6935
+ }
6936
+ )
6937
+ ),
6938
+ ChartComponent && (inView || hasBeenInView) && shouldRenderChart ? /* @__PURE__ */ React15__default.createElement(
6939
+ ChartRenderer,
6940
+ {
6941
+ ChartComponent,
6942
+ config,
6943
+ containerRef: nodeRef,
6944
+ overlayRef,
6945
+ onReady: () => setIsLoaded(true)
6946
+ }
6947
+ ) : /* @__PURE__ */ React15__default.createElement("div", { style: { height: "100%", width: "100%" } })
6743
6948
  ))
6744
6949
  ));
6745
6950
  }
@@ -6795,8 +7000,12 @@ function Vizbuilder(props) {
6795
7000
  chartLimits,
6796
7001
  chartTypes,
6797
7002
  datacap,
7003
+ getFormatter,
6798
7004
  getTopojsonConfig,
6799
7005
  NonIdealState: NonIdealState2,
7006
+ postprocessConfig,
7007
+ showConfidenceInt,
7008
+ translate,
6800
7009
  ViewErrorComponent
6801
7010
  } = useVizbuilderContext();
6802
7011
  const { actions: actions2 } = useSettings();
@@ -6812,21 +7021,46 @@ function Vizbuilder(props) {
6812
7021
  });
6813
7022
  return Object.fromEntries(charts2.map((chart2) => [chart2.key, chart2]));
6814
7023
  }, [chartLimits, chartTypes, datacap, datasets, getTopojsonConfig]);
7024
+ const filteredCharts = useMemo(() => {
7025
+ const list = Object.values(charts);
7026
+ const builderParams = {
7027
+ fullMode: false,
7028
+ getFormatter,
7029
+ showConfidenceInt,
7030
+ t: translate
7031
+ };
7032
+ return list.filter((chart2) => {
7033
+ const builder = d3plusConfigBuilder[chart2.type];
7034
+ if (!builder) return false;
7035
+ const config = builder(chart2, builderParams);
7036
+ const finalConfig = postprocessConfig(config, chart2, builderParams);
7037
+ return finalConfig !== false;
7038
+ });
7039
+ }, [charts, getFormatter, postprocessConfig, showConfidenceInt, translate]);
7040
+ const [currentPage, setCurrentPage] = useState(1);
7041
+ const pageSize = 4;
7042
+ useEffect(() => {
7043
+ setCurrentPage(1);
7044
+ }, [datasets, charts]);
6815
7045
  const content = useMemo(() => {
6816
7046
  const isLoading = datasets.some((dataset) => Object.keys(dataset.columns).length === 0);
6817
7047
  if (isLoading) {
6818
7048
  console.debug("Loading datasets...", datasets);
6819
7049
  return /* @__PURE__ */ React15__default.createElement(NonIdealState2, { status: "loading" });
6820
7050
  }
6821
- const chartList = Object.values(charts).slice(0, 10);
6822
- if (chartList.length === 0) {
7051
+ const chartList = filteredCharts.slice(
7052
+ (currentPage - 1) * pageSize,
7053
+ currentPage * pageSize
7054
+ );
7055
+ if (filteredCharts.length === 0) {
6823
7056
  if (datasets.length === 1 && datasets[0].data.length === 1) {
6824
7057
  return /* @__PURE__ */ React15__default.createElement(NonIdealState2, { status: "one-row" });
6825
7058
  }
6826
7059
  return /* @__PURE__ */ React15__default.createElement(NonIdealState2, { status: "empty" });
6827
7060
  }
6828
7061
  const isSingleChart = chartList.length === 1;
6829
- return /* @__PURE__ */ React15__default.createElement(
7062
+ const totalPages = Math.ceil(filteredCharts.length / pageSize);
7063
+ return /* @__PURE__ */ React15__default.createElement(Stack, { spacing: "xl" }, /* @__PURE__ */ React15__default.createElement(
6830
7064
  "div",
6831
7065
  {
6832
7066
  className: cx("vb-scrollcontainer", classes.grid, {
@@ -6854,8 +7088,17 @@ function Vizbuilder(props) {
6854
7088
  }
6855
7089
  );
6856
7090
  })
6857
- );
6858
- }, [charts, classes, cx, datasets, NonIdealState2]);
7091
+ ), totalPages > 1 && /* @__PURE__ */ React15__default.createElement(
7092
+ Pagination,
7093
+ {
7094
+ total: totalPages,
7095
+ value: currentPage,
7096
+ onChange: setCurrentPage,
7097
+ position: "center",
7098
+ mb: "xl"
7099
+ }
7100
+ ));
7101
+ }, [filteredCharts, currentPage, classes, cx, datasets, NonIdealState2, actions2]);
6859
7102
  const currentChart = (queryItem == null ? void 0 : queryItem.chart) || "";
6860
7103
  const chart = charts[currentChart];
6861
7104
  return /* @__PURE__ */ React15__default.createElement("div", { className: cls("vb-wrapper", classes.wrapper, props.className) }, props.customHeader, /* @__PURE__ */ React15__default.createElement(ErrorBoundary, { ErrorContent: ViewErrorComponent }, content), props.customFooter, /* @__PURE__ */ React15__default.createElement(
@@ -6883,7 +7126,7 @@ function useVizbuilderData() {
6883
7126
  });
6884
7127
  return query;
6885
7128
  }
6886
- var LoadingOverlay3 = () => {
7129
+ var LoadingOverlay4 = () => {
6887
7130
  const { translate: t } = useTranslation();
6888
7131
  const { loading: isLoading, message } = useSelector$1(selectLoadingState);
6889
7132
  const description = !message ? void 0 : message.type === "HEAVY_QUERY" ? t("loading.message_heavyquery", message) : (
@@ -6910,7 +7153,7 @@ function VizbuilderView(props) {
6910
7153
  const { cube, params } = props;
6911
7154
  const query = useVizbuilderData();
6912
7155
  if (query.isLoading) {
6913
- return /* @__PURE__ */ React15__default.createElement(LoadingOverlay3, { visible: true });
7156
+ return /* @__PURE__ */ React15__default.createElement(LoadingOverlay4, { visible: true });
6914
7157
  }
6915
7158
  const data = query.data;
6916
7159
  const types = (_a = query.data) == null ? void 0 : _a.types;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/data-explorer",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "main": "./dist/main.mjs",
5
5
  "types": "./dist/main.d.mts",
6
6
  "files": [