@datawheel/data-explorer 1.3.3 → 1.4.0

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;
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
@@ -786,15 +787,16 @@ function buildFilter(props) {
786
787
  measure: props.measure || `${props.name}`,
787
788
  conditionOne: props.conditionOne || [
788
789
  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)
790
+ props.const1 ? props.const1[1].toString() : props.inputtedValue || "",
791
+ props.const1 ? props.const1[1] : parseNumeric(props.interpretedValue, NaN)
791
792
  ],
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)
796
- ],
797
- joint: props.joint === "or" ? "or" : "and"
793
+ conditionTwo: props.conditionTwo || (props.const2 ? [
794
+ `${props.const2[0]}`,
795
+ props.const2[1].toString(),
796
+ parseNumeric(props.const2[1].toString(), NaN)
797
+ ] : void 0),
798
+ joint: props.joint === "or" ? "or" : "and",
799
+ type: props.type
798
800
  };
799
801
  }
800
802
  function buildMeasure(props) {
@@ -978,13 +980,28 @@ function filterParse(item) {
978
980
  const condition = item.slice(indexName + 1);
979
981
  const joint = condition.includes(".and.") ? "and" : "or";
980
982
  const [cond1, cond2] = condition.split(`.${joint}.`);
983
+ const conditionOne = filterParseCondition(cond1);
984
+ const conditionTwo = cond2 ? filterParseCondition(cond2) : void 0;
985
+ let type;
986
+ if (conditionOne && conditionTwo) {
987
+ if (conditionOne[0] === "gte" /* GTE */ && conditionTwo[0] === "lte" /* LTE */) {
988
+ type = "between";
989
+ }
990
+ } else if (conditionOne) {
991
+ if (conditionOne[0] === "gte" /* GTE */ || conditionOne[0] === "gt" /* GT */) {
992
+ type = "greaterThan";
993
+ } else if (conditionOne[0] === "lte" /* LTE */ || conditionOne[0] === "lt" /* LT */) {
994
+ type = "lessThan";
995
+ }
996
+ }
981
997
  return {
982
998
  key: Math.random().toString(16).slice(2),
983
999
  active: true,
984
1000
  measure: item.slice(0, indexName),
985
1001
  joint,
986
- conditionOne: filterParseCondition(cond1),
987
- conditionTwo: cond2 ? filterParseCondition(cond2) : void 0
1002
+ conditionOne,
1003
+ conditionTwo,
1004
+ type
988
1005
  };
989
1006
  }
990
1007
 
@@ -2103,6 +2120,14 @@ function QueryProvider({ children, defaultCube }) {
2103
2120
  });
2104
2121
  });
2105
2122
  if (newQuery) {
2123
+ Object.values(newQuery.params.filters).forEach((filter) => {
2124
+ const currentFilter = Object.values(queryItem.params.filters).find(
2125
+ (f) => f.measure === filter.measure
2126
+ );
2127
+ if (currentFilter) {
2128
+ filter.type = currentFilter.type;
2129
+ }
2130
+ });
2106
2131
  const promises = [...restDrilldowns, ...Object.values(newQuery.params.drilldowns)].map(
2107
2132
  (dd) => {
2108
2133
  const currentDrilldown = queryItem.params.drilldowns[dd.key];
@@ -3117,26 +3142,60 @@ function getFiltersConditions(fn, value) {
3117
3142
  const comparisonMap = /* @__PURE__ */ new Map([
3118
3143
  [
3119
3144
  "greaterThan",
3120
- (value2) => ({
3121
- conditionOne: ["gte" /* GTE */, String(value2[0]), Number(value2[0])],
3122
- conditionTwo: ["gt" /* GT */, "0", 0]
3123
- })
3145
+ (value2) => {
3146
+ const val = value2[0] === "" ? "" : String(value2[0]);
3147
+ const num = value2[0] === "" ? NaN : Number(value2[0]);
3148
+ return {
3149
+ conditionOne: ["gte" /* GTE */, val, num],
3150
+ conditionTwo: void 0,
3151
+ type: "greaterThan"
3152
+ };
3153
+ }
3124
3154
  ],
3125
3155
  [
3126
3156
  "lessThan",
3127
- (value2) => ({
3128
- conditionOne: ["lte" /* LTE */, String(value2[0]), Number(value2[0])],
3129
- conditionTwo: ["gt" /* GT */, "0", 0]
3130
- })
3157
+ (value2) => {
3158
+ const val = value2[0] === "" ? "" : String(value2[0]);
3159
+ const num = value2[0] === "" ? NaN : Number(value2[0]);
3160
+ return {
3161
+ conditionOne: ["lte" /* LTE */, val, num],
3162
+ conditionTwo: void 0,
3163
+ type: "lessThan"
3164
+ };
3165
+ }
3131
3166
  ],
3132
3167
  [
3133
3168
  "between",
3134
3169
  (values) => {
3135
3170
  const [min, max] = values;
3171
+ const isMinValid = min !== "" && min !== null && min !== void 0;
3172
+ const isMaxValid = max !== "" && max !== null && max !== void 0;
3173
+ if (isMinValid && isMaxValid) {
3174
+ return {
3175
+ conditionOne: ["gte" /* GTE */, String(min), Number(min)],
3176
+ conditionTwo: ["lte" /* LTE */, String(max), Number(max)],
3177
+ joint: "and",
3178
+ type: "between"
3179
+ };
3180
+ }
3181
+ if (isMinValid) {
3182
+ return {
3183
+ conditionOne: ["gte" /* GTE */, String(min), Number(min)],
3184
+ conditionTwo: void 0,
3185
+ type: "between"
3186
+ };
3187
+ }
3188
+ if (isMaxValid) {
3189
+ return {
3190
+ conditionOne: ["lte" /* LTE */, String(max), Number(max)],
3191
+ conditionTwo: void 0,
3192
+ type: "between"
3193
+ };
3194
+ }
3136
3195
  return {
3137
- conditionOne: ["gte" /* GTE */, String(min), Number(min)],
3138
- conditionTwo: ["lte" /* LTE */, String(max), Number(max)],
3139
- joint: "and"
3196
+ conditionOne: ["gte" /* GTE */, "", NaN],
3197
+ conditionTwo: void 0,
3198
+ type: "between"
3140
3199
  };
3141
3200
  }
3142
3201
  ]
@@ -4132,6 +4191,9 @@ function getFilterfnKey(type) {
4132
4191
  }
4133
4192
  }
4134
4193
  function getFilterFn(filter) {
4194
+ if (filter.type) {
4195
+ return filter.type;
4196
+ }
4135
4197
  if (filter.conditionOne && filter.conditionTwo) {
4136
4198
  if (filter.conditionOne[0] === "gte" /* GTE */ && filter.conditionTwo[0] === "lte" /* LTE */) {
4137
4199
  return "between";
@@ -4163,13 +4225,14 @@ function NumberInputComponent({ text, filter }) {
4163
4225
  };
4164
4226
  }, [debouncedUpdateUrl]);
4165
4227
  function getFilterValue(filter2) {
4166
- return isNotValid(filter2.conditionOne[2]) || filter2.conditionOne[2] === 0 ? "" : filter2.conditionOne[2];
4228
+ return isNotValid(filter2.conditionOne[2]) ? "" : filter2.conditionOne[2];
4167
4229
  }
4168
4230
  function onInputChange({ filter: filter2, value }) {
4169
4231
  const isEmpty = value === "";
4170
- const conditions = getFiltersConditions(getFilterFn(filter2) || "greaterThan", [Number(value)]) || {};
4232
+ const currentType = getFilterFn(filter2) || "greaterThan";
4233
+ const conditions = getFiltersConditions(currentType, [value]) || {};
4171
4234
  const active = !isEmpty;
4172
- const newFilter = buildFilter({ ...filter2, active, ...conditions });
4235
+ const newFilter = buildFilter({ ...filter2, active, ...conditions, type: currentType });
4173
4236
  actions2.updateFilter(newFilter);
4174
4237
  const newQuery = buildQuery(cloneDeep(queryItem));
4175
4238
  newQuery.params.filters[filter2.key] = newFilter;
@@ -4204,9 +4267,10 @@ var MinMax = ({ filter, hideControls, ...rest }) => {
4204
4267
  }, [debouncedUpdateUrl]);
4205
4268
  function onInputChangeMinMax(props) {
4206
4269
  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 });
4270
+ const currentType = getFilterFn(filter2) || "between";
4271
+ const conditions = getFiltersConditions(currentType, [min2, max2]) || {};
4272
+ const active = min2 !== "" || max2 !== "";
4273
+ const newFilter = buildFilter({ ...filter2, active, ...conditions, type: currentType });
4210
4274
  actions2.updateFilter(newFilter);
4211
4275
  const newQuery = buildQuery(cloneDeep(queryItem));
4212
4276
  newQuery.params.filters[filter2.key] = newFilter;
@@ -4214,7 +4278,7 @@ var MinMax = ({ filter, hideControls, ...rest }) => {
4214
4278
  }
4215
4279
  function getFilterValue(condition) {
4216
4280
  if (condition) {
4217
- return isNotValid(condition[2]) || condition[2] === 0 ? "" : condition[2];
4281
+ return isNotValid(condition[2]) ? "" : condition[2];
4218
4282
  }
4219
4283
  return "";
4220
4284
  }
@@ -4244,14 +4308,26 @@ var MinMax = ({ filter, hideControls, ...rest }) => {
4244
4308
  };
4245
4309
  function FilterFnsMenu({ filter }) {
4246
4310
  const actions2 = useActions();
4311
+ const updateUrl = useUpdateUrl();
4312
+ const queryItem = useSelector$1(selectCurrentQueryItem);
4247
4313
  const { translate: t } = useTranslation();
4248
4314
  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
4315
  Menu.Item,
4250
4316
  {
4251
4317
  icon: /* @__PURE__ */ React15__default.createElement(IconMathGreater, { size: 14 }),
4252
4318
  onClick: () => {
4253
- const conditions = getFiltersConditions("greaterThan", [0]) || {};
4254
- actions2.updateFilter(buildFilter({ ...filter, ...conditions, active: false }));
4319
+ const currentType = "greaterThan";
4320
+ const conditions = getFiltersConditions(currentType, [""]) || {};
4321
+ const newFilter = buildFilter({
4322
+ ...filter,
4323
+ ...conditions,
4324
+ active: false,
4325
+ type: currentType
4326
+ });
4327
+ actions2.updateFilter(newFilter);
4328
+ const newQuery = buildQuery(cloneDeep(queryItem));
4329
+ newQuery.params.filters[filter.key] = newFilter;
4330
+ updateUrl(newQuery);
4255
4331
  }
4256
4332
  },
4257
4333
  t("comparison.GT")
@@ -4260,8 +4336,18 @@ function FilterFnsMenu({ filter }) {
4260
4336
  {
4261
4337
  icon: /* @__PURE__ */ React15__default.createElement(IconMathLower, { size: 14 }),
4262
4338
  onClick: () => {
4263
- const conditions = getFiltersConditions("lessThan", [0]) || {};
4264
- actions2.updateFilter(buildFilter({ ...filter, ...conditions, active: false }));
4339
+ const currentType = "lessThan";
4340
+ const conditions = getFiltersConditions(currentType, [""]) || {};
4341
+ const newFilter = buildFilter({
4342
+ ...filter,
4343
+ ...conditions,
4344
+ active: false,
4345
+ type: currentType
4346
+ });
4347
+ actions2.updateFilter(newFilter);
4348
+ const newQuery = buildQuery(cloneDeep(queryItem));
4349
+ newQuery.params.filters[filter.key] = newFilter;
4350
+ updateUrl(newQuery);
4265
4351
  }
4266
4352
  },
4267
4353
  t("comparison.LT")
@@ -4270,8 +4356,18 @@ function FilterFnsMenu({ filter }) {
4270
4356
  {
4271
4357
  icon: /* @__PURE__ */ React15__default.createElement(IconArrowsLeftRight, { size: 14 }),
4272
4358
  onClick: () => {
4273
- const conditions = getFiltersConditions("between", [0, 0]) || {};
4274
- actions2.updateFilter(buildFilter({ ...filter, ...conditions, active: false }));
4359
+ const currentType = "between";
4360
+ const conditions = getFiltersConditions(currentType, ["", ""]) || {};
4361
+ const newFilter = buildFilter({
4362
+ ...filter,
4363
+ ...conditions,
4364
+ active: false,
4365
+ type: currentType
4366
+ });
4367
+ actions2.updateFilter(newFilter);
4368
+ const newQuery = buildQuery(cloneDeep(queryItem));
4369
+ newQuery.params.filters[filter.key] = newFilter;
4370
+ updateUrl(newQuery);
4275
4371
  }
4276
4372
  },
4277
4373
  t("comparison.BT")
@@ -6649,6 +6745,31 @@ var iconByFormat = {
6649
6745
  png: IconPhotoDown,
6650
6746
  svg: IconVectorTriangle
6651
6747
  };
6748
+ var ChartRenderer = memo((props) => {
6749
+ const { ChartComponent, config, containerRef, overlayRef, onReady } = props;
6750
+ React15__default.useEffect(() => {
6751
+ const box = containerRef.current;
6752
+ if (!box) return;
6753
+ const checkSvg = () => {
6754
+ const svg = box.querySelector("svg");
6755
+ if (svg) {
6756
+ if (overlayRef.current) {
6757
+ overlayRef.current.style.display = "none";
6758
+ }
6759
+ onReady();
6760
+ return true;
6761
+ }
6762
+ return false;
6763
+ };
6764
+ if (checkSvg()) return;
6765
+ const observer = new MutationObserver(() => {
6766
+ if (checkSvg()) observer.disconnect();
6767
+ });
6768
+ observer.observe(box, { childList: true, subtree: true });
6769
+ return () => observer.disconnect();
6770
+ }, [containerRef, overlayRef, onReady]);
6771
+ return /* @__PURE__ */ React15__default.createElement(ChartComponent, { config });
6772
+ });
6652
6773
  function ChartCard(props) {
6653
6774
  const { chart, isFullMode, onFocus, height } = props;
6654
6775
  const { dataset } = chart.datagroup;
@@ -6723,8 +6844,46 @@ function ChartCard(props) {
6723
6844
  copied ? translate("share_copied") : translate("action_share")
6724
6845
  ));
6725
6846
  }, [translate]);
6847
+ const [isLoaded, setIsLoaded] = useState(false);
6848
+ const [shouldRenderChart, setShouldRenderChart] = useState(false);
6849
+ const overlayRef = useRef(null);
6850
+ React15__default.useEffect(() => {
6851
+ setIsLoaded(false);
6852
+ setShouldRenderChart(false);
6853
+ if (overlayRef.current) {
6854
+ overlayRef.current.style.display = "block";
6855
+ }
6856
+ }, [chart.key]);
6857
+ React15__default.useEffect(() => {
6858
+ if (ChartComponent && (inView || hasBeenInView) && !shouldRenderChart) {
6859
+ const timer = setTimeout(() => {
6860
+ window.requestAnimationFrame(() => {
6861
+ window.requestAnimationFrame(() => {
6862
+ setShouldRenderChart(true);
6863
+ });
6864
+ });
6865
+ }, 500);
6866
+ return () => clearTimeout(timer);
6867
+ }
6868
+ }, [ChartComponent, inView, hasBeenInView, shouldRenderChart]);
6869
+ React15__default.useEffect(() => {
6870
+ const box = nodeRef.current;
6871
+ if (!box || !shouldRenderChart || isLoaded) return;
6872
+ const observer = new MutationObserver(() => {
6873
+ const svg = box.querySelector("svg");
6874
+ if (svg) {
6875
+ window.requestAnimationFrame(() => setIsLoaded(true));
6876
+ observer.disconnect();
6877
+ }
6878
+ });
6879
+ observer.observe(box, { childList: true, subtree: true });
6880
+ if (box.querySelector("svg")) {
6881
+ setIsLoaded(true);
6882
+ }
6883
+ return () => observer.disconnect();
6884
+ }, [shouldRenderChart, isLoaded]);
6726
6885
  if (!ChartComponent || !config) return null;
6727
- const resolvedHeight = height ? height : isFullMode ? "calc(100vh - 3rem)" : 400;
6886
+ const resolvedHeight = height ? height : isFullMode ? "calc(100vh - 3rem)" : "calc((80vh - 4rem) / 2)";
6728
6887
  return /* @__PURE__ */ React15__default.createElement(ErrorBoundary, { ErrorContent: CardErrorComponent }, /* @__PURE__ */ React15__default.createElement(
6729
6888
  Paper,
6730
6889
  {
@@ -6735,11 +6894,42 @@ function ChartCard(props) {
6735
6894
  /* @__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
6895
  Box,
6737
6896
  {
6738
- style: { flex: "1 1 auto" },
6897
+ style: { flex: "1 1 auto", position: "relative" },
6739
6898
  ref: setRefs,
6740
6899
  sx: { "& > .viz": { height: "100%" } }
6741
6900
  },
6742
- ChartComponent && (inView || hasBeenInView) ? /* @__PURE__ */ React15__default.createElement(ChartComponent, { config }) : /* @__PURE__ */ React15__default.createElement("div", { style: { height: "100%", width: "100%" } })
6901
+ /* @__PURE__ */ React15__default.createElement(
6902
+ Box,
6903
+ {
6904
+ ref: overlayRef,
6905
+ style: {
6906
+ position: "absolute",
6907
+ top: 0,
6908
+ left: 0,
6909
+ right: 0,
6910
+ bottom: 0,
6911
+ zIndex: 100
6912
+ }
6913
+ },
6914
+ /* @__PURE__ */ React15__default.createElement(
6915
+ LoadingOverlay,
6916
+ {
6917
+ visible: !isLoaded,
6918
+ overlayBlur: 2,
6919
+ transitionDuration: 0
6920
+ }
6921
+ )
6922
+ ),
6923
+ ChartComponent && (inView || hasBeenInView) && shouldRenderChart ? /* @__PURE__ */ React15__default.createElement(
6924
+ ChartRenderer,
6925
+ {
6926
+ ChartComponent,
6927
+ config,
6928
+ containerRef: nodeRef,
6929
+ overlayRef,
6930
+ onReady: () => setIsLoaded(true)
6931
+ }
6932
+ ) : /* @__PURE__ */ React15__default.createElement("div", { style: { height: "100%", width: "100%" } })
6743
6933
  ))
6744
6934
  ));
6745
6935
  }
@@ -6795,8 +6985,12 @@ function Vizbuilder(props) {
6795
6985
  chartLimits,
6796
6986
  chartTypes,
6797
6987
  datacap,
6988
+ getFormatter,
6798
6989
  getTopojsonConfig,
6799
6990
  NonIdealState: NonIdealState2,
6991
+ postprocessConfig,
6992
+ showConfidenceInt,
6993
+ translate,
6800
6994
  ViewErrorComponent
6801
6995
  } = useVizbuilderContext();
6802
6996
  const { actions: actions2 } = useSettings();
@@ -6812,21 +7006,46 @@ function Vizbuilder(props) {
6812
7006
  });
6813
7007
  return Object.fromEntries(charts2.map((chart2) => [chart2.key, chart2]));
6814
7008
  }, [chartLimits, chartTypes, datacap, datasets, getTopojsonConfig]);
7009
+ const filteredCharts = useMemo(() => {
7010
+ const list = Object.values(charts);
7011
+ const builderParams = {
7012
+ fullMode: false,
7013
+ getFormatter,
7014
+ showConfidenceInt,
7015
+ t: translate
7016
+ };
7017
+ return list.filter((chart2) => {
7018
+ const builder = d3plusConfigBuilder[chart2.type];
7019
+ if (!builder) return false;
7020
+ const config = builder(chart2, builderParams);
7021
+ const finalConfig = postprocessConfig(config, chart2, builderParams);
7022
+ return finalConfig !== false;
7023
+ });
7024
+ }, [charts, getFormatter, postprocessConfig, showConfidenceInt, translate]);
7025
+ const [currentPage, setCurrentPage] = useState(1);
7026
+ const pageSize = 4;
7027
+ useEffect(() => {
7028
+ setCurrentPage(1);
7029
+ }, [datasets, charts]);
6815
7030
  const content = useMemo(() => {
6816
7031
  const isLoading = datasets.some((dataset) => Object.keys(dataset.columns).length === 0);
6817
7032
  if (isLoading) {
6818
7033
  console.debug("Loading datasets...", datasets);
6819
7034
  return /* @__PURE__ */ React15__default.createElement(NonIdealState2, { status: "loading" });
6820
7035
  }
6821
- const chartList = Object.values(charts).slice(0, 10);
6822
- if (chartList.length === 0) {
7036
+ const chartList = filteredCharts.slice(
7037
+ (currentPage - 1) * pageSize,
7038
+ currentPage * pageSize
7039
+ );
7040
+ if (filteredCharts.length === 0) {
6823
7041
  if (datasets.length === 1 && datasets[0].data.length === 1) {
6824
7042
  return /* @__PURE__ */ React15__default.createElement(NonIdealState2, { status: "one-row" });
6825
7043
  }
6826
7044
  return /* @__PURE__ */ React15__default.createElement(NonIdealState2, { status: "empty" });
6827
7045
  }
6828
7046
  const isSingleChart = chartList.length === 1;
6829
- return /* @__PURE__ */ React15__default.createElement(
7047
+ const totalPages = Math.ceil(filteredCharts.length / pageSize);
7048
+ return /* @__PURE__ */ React15__default.createElement(Stack, { spacing: "xl" }, /* @__PURE__ */ React15__default.createElement(
6830
7049
  "div",
6831
7050
  {
6832
7051
  className: cx("vb-scrollcontainer", classes.grid, {
@@ -6854,8 +7073,17 @@ function Vizbuilder(props) {
6854
7073
  }
6855
7074
  );
6856
7075
  })
6857
- );
6858
- }, [charts, classes, cx, datasets, NonIdealState2]);
7076
+ ), totalPages > 1 && /* @__PURE__ */ React15__default.createElement(
7077
+ Pagination,
7078
+ {
7079
+ total: totalPages,
7080
+ value: currentPage,
7081
+ onChange: setCurrentPage,
7082
+ position: "center",
7083
+ mb: "xl"
7084
+ }
7085
+ ));
7086
+ }, [filteredCharts, currentPage, classes, cx, datasets, NonIdealState2, actions2]);
6859
7087
  const currentChart = (queryItem == null ? void 0 : queryItem.chart) || "";
6860
7088
  const chart = charts[currentChart];
6861
7089
  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 +7111,7 @@ function useVizbuilderData() {
6883
7111
  });
6884
7112
  return query;
6885
7113
  }
6886
- var LoadingOverlay3 = () => {
7114
+ var LoadingOverlay4 = () => {
6887
7115
  const { translate: t } = useTranslation();
6888
7116
  const { loading: isLoading, message } = useSelector$1(selectLoadingState);
6889
7117
  const description = !message ? void 0 : message.type === "HEAVY_QUERY" ? t("loading.message_heavyquery", message) : (
@@ -6910,7 +7138,7 @@ function VizbuilderView(props) {
6910
7138
  const { cube, params } = props;
6911
7139
  const query = useVizbuilderData();
6912
7140
  if (query.isLoading) {
6913
- return /* @__PURE__ */ React15__default.createElement(LoadingOverlay3, { visible: true });
7141
+ return /* @__PURE__ */ React15__default.createElement(LoadingOverlay4, { visible: true });
6914
7142
  }
6915
7143
  const data = query.data;
6916
7144
  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.0",
4
4
  "main": "./dist/main.mjs",
5
5
  "types": "./dist/main.d.mts",
6
6
  "files": [