diff --git a/CHANGELOG.md b/CHANGELOG.md
index e25dcd7b3..8fae87e87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,8 @@ You can also check the
## Unreleased
-Nothing yet.
+- Features
+ - Added a way to make table columns responsive
### 6.2.5 - 2025-12-02
diff --git a/app/charts/index.ts b/app/charts/index.ts
index 2f18b8078..913afba99 100644
--- a/app/charts/index.ts
+++ b/app/charts/index.ts
@@ -616,6 +616,7 @@ export const getInitialConfig = (
settings: {
showSearch: true,
showAllRows: false,
+ limitColumnWidths: false,
},
links: {
enabled: false,
diff --git a/app/charts/table/cell-desktop.tsx b/app/charts/table/cell-desktop.tsx
index da9eb11e4..ef0afadde 100644
--- a/app/charts/table/cell-desktop.tsx
+++ b/app/charts/table/cell-desktop.tsx
@@ -1,4 +1,4 @@
-import { Box, Theme } from "@mui/material";
+import { Box, Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { hcl } from "d3-color";
import { ScaleLinear } from "d3-scale";
@@ -10,6 +10,7 @@ import { LinkedCellWrapper } from "@/charts/table/linked-cell-wrapper";
import { ColumnMeta, TableChartState } from "@/charts/table/table-state";
import { Tag } from "@/charts/table/tag";
import { Flex } from "@/components/flex";
+import { OverflowTooltip } from "@/components/overflow-tooltip";
import { Observation } from "@/domain/data";
const useStyles = makeStyles((theme: Theme) => ({
@@ -61,12 +62,15 @@ export const CellDesktop = ({
barShowBackground,
} = columnMeta;
const classes = useStyles();
- const { links } = useChartState() as TableChartState;
+ const { links, shouldApplyWidthLimits } = useChartState() as TableChartState;
switch (columnMeta.type) {
case "text":
+ const textContent = columnMeta.formatter(cell);
+
return (
- {columnMeta.formatter(cell)}
+ {shouldApplyWidthLimits ? (
+
+
+ {textContent}
+
+
+ ) : (
+
+ {textContent}
+
+ )}
);
@@ -89,8 +107,8 @@ export const CellDesktop = ({
const { colorScale: cColorScale } = columnMeta;
return (
@@ -128,6 +146,7 @@ export const CellDesktop = ({
sx={{
flexDirection: "column",
justifyContent: "center",
+ alignItems: "flex-end",
// Padding is a constant accounted for in the
// widthScale domain (see table state).
px: `${BAR_CELL_PADDING}px`,
diff --git a/app/charts/table/constants.ts b/app/charts/table/constants.ts
index 53d534c22..07e9c5aa4 100644
--- a/app/charts/table/constants.ts
+++ b/app/charts/table/constants.ts
@@ -1,3 +1,4 @@
export const TABLE_HEIGHT = 600;
export const BAR_CELL_PADDING = 12;
export const SORTING_ARROW_WIDTH = 24;
+export const LIMITED_COLUMN_WIDTH = 120;
diff --git a/app/charts/table/linked-cell-wrapper.tsx b/app/charts/table/linked-cell-wrapper.tsx
index dee466d23..98ee85e23 100644
--- a/app/charts/table/linked-cell-wrapper.tsx
+++ b/app/charts/table/linked-cell-wrapper.tsx
@@ -11,9 +11,11 @@ import { Icon } from "@/icons";
const useStyles = makeStyles((theme: Theme) => ({
link: {
+ overflow: "hidden",
display: "inline-flex",
alignItems: "center",
gap: theme.spacing(1),
+ minWidth: 0,
color: "inherit",
fontWeight: "inherit",
textDecoration: "none",
diff --git a/app/charts/table/table-content.tsx b/app/charts/table/table-content.tsx
index 864a75d84..f61359971 100644
--- a/app/charts/table/table-content.tsx
+++ b/app/charts/table/table-content.tsx
@@ -1,4 +1,4 @@
-import { Box, TableSortLabel, Theme } from "@mui/material";
+import { Box, TableSortLabel, Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import clsx from "clsx";
import { createContext, ReactNode, useContext, useMemo } from "react";
@@ -6,8 +6,10 @@ import { HeaderGroup } from "react-table";
import { SORTING_ARROW_WIDTH } from "@/charts/table/constants";
import { ColumnMeta } from "@/charts/table/table-state";
+import { columnCanBeWidthLimited } from "@/charts/table/width-limit";
import { Flex } from "@/components/flex";
import { OpenMetadataPanelWrapper } from "@/components/metadata-panel";
+import { OverflowTooltip } from "@/components/overflow-tooltip";
import { Observation } from "@/domain/data";
/** Workaround because react-window can't pass props to inner element */
@@ -16,6 +18,7 @@ type TableContentProps = {
tableColumnsMeta: Record;
customSortCount: number;
totalColumnsWidth: number;
+ shouldApplyWidthLimits: boolean;
};
const TableContentContext = createContext(
@@ -27,6 +30,7 @@ export const TableContentProvider = ({
tableColumnsMeta,
customSortCount,
totalColumnsWidth,
+ shouldApplyWidthLimits,
children,
}: TableContentProps & { children: ReactNode }) => {
const value = useMemo(() => {
@@ -35,8 +39,15 @@ export const TableContentProvider = ({
tableColumnsMeta,
customSortCount,
totalColumnsWidth,
+ shouldApplyWidthLimits,
};
- }, [headerGroups, tableColumnsMeta, customSortCount, totalColumnsWidth]);
+ }, [
+ headerGroups,
+ tableColumnsMeta,
+ customSortCount,
+ totalColumnsWidth,
+ shouldApplyWidthLimits,
+ ]);
return (
@@ -75,8 +86,13 @@ export const TableContent = ({ children }: { children: ReactNode }) => {
throw Error("Please wrap TableContent in TableContentProvider");
}
- const { headerGroups, tableColumnsMeta, customSortCount, totalColumnsWidth } =
- ctx;
+ const {
+ headerGroups,
+ tableColumnsMeta,
+ customSortCount,
+ totalColumnsWidth,
+ shouldApplyWidthLimits,
+ } = ctx;
return (
<>
@@ -87,10 +103,13 @@ export const TableContent = ({ children }: { children: ReactNode }) => {
// eslint-disable-next-line react/jsx-key
{headerGroup.headers.map((column) => {
- const { dim, columnComponentType } =
+ const { type, dim, columnComponentType } =
tableColumnsMeta[column.id];
// We assume that the customSortCount items are at the beginning of the sorted array, so any item with a lower index must be a custom sorted one
const isCustomSorted = column.sortedIndex < customSortCount;
+ const hasWidthLimit =
+ shouldApplyWidthLimits && columnCanBeWidthLimited(type);
+ const headerText = `${column.Header}`;
return (
// eslint-disable-next-line react/jsx-key
@@ -102,6 +121,7 @@ export const TableContent = ({ children }: { children: ReactNode }) => {
: undefined
)}
{...column.getHeaderProps(column.getSortByToggleProps())}
+ title={headerText}
>
{
"& svg": {
opacity: isCustomSorted ? 1 : 0.5,
},
+ ...(hasWidthLimit && {
+ minWidth: 0,
+ }),
}}
>
-
- {column.render("Header")}
-
+ {hasWidthLimit ? (
+
+
+ {column.render("Header")}
+
+
+ ) : (
+
+ {column.render("Header")}
+
+ )}
diff --git a/app/charts/table/table-state.tsx b/app/charts/table/table-state.tsx
index ef797170a..9c88fb5ef 100644
--- a/app/charts/table/table-state.tsx
+++ b/app/charts/table/table-state.tsx
@@ -26,13 +26,18 @@ import {
CommonChartState,
} from "@/charts/shared/chart-state";
import { useSize } from "@/charts/shared/use-size";
-import { BAR_CELL_PADDING, TABLE_HEIGHT } from "@/charts/table/constants";
+import {
+ BAR_CELL_PADDING,
+ LIMITED_COLUMN_WIDTH,
+ TABLE_HEIGHT,
+} from "@/charts/table/constants";
import { getTableUIElementsOffset } from "@/charts/table/table";
import {
TableStateVariables,
useTableStateData,
useTableStateVariables,
} from "@/charts/table/table-state-props";
+import { columnCanBeWidthLimited } from "@/charts/table/width-limit";
import {
ColumnStyleCategory,
ColumnStyleHeatmap,
@@ -118,6 +123,7 @@ export type TableChartState = CommonChartState &
groupingIds: string[];
hiddenIds: string[];
sortingIds: { id: string; desc: boolean }[];
+ shouldApplyWidthLimits: boolean;
links: TableLinks;
};
@@ -210,11 +216,10 @@ const useTableState = (
[chartData, types]
);
- // Columns used by react-table
- const tableColumns = useMemo(() => {
+ const tableColumnsData = useMemo(() => {
const allComponents = [...dimensions, ...measures];
- const columns = orderedTableColumns.map((c) => {
+ const columnData = orderedTableColumns.map((c) => {
const headerComponent = allComponents.find((d) => d.id === c.componentId);
if (!headerComponent) {
@@ -228,7 +233,7 @@ const useTableState = (
const headerLabel = getLabelWithUnit(headerComponent);
// The column width depends on the estimated width of the
- // longest value in the column, with a minimum of 150px.
+ // longest value in the column, with a minimum of 50px.
const columnItems = [...new Set(chartData.map((d) => d[c.componentId]))];
const columnItemSizes = [
...columnItems.map((item) => {
@@ -241,40 +246,81 @@ const useTableState = (
}),
];
- const width = Math.max(
+ const naturalWidth = Math.max(
50,
getTextWidth(headerLabel, { fontSize: 16 }) + 44,
...columnItemSizes
);
return {
- Header: headerLabel,
- // Slugify accessor to avoid id's "." to be parsed as JS object notation.
- accessor: getSlugifiedId(c.componentId),
- width,
- sortType: (
- rowA: Row,
- rowB: Row,
- colId: string
- ) => {
- for (const d of sorters) {
- const result = ascending(
- d(rowA.values[colId]),
- d(rowB.values[colId])
- );
-
- if (result) {
- return result;
- }
- }
-
- return 0;
- },
+ c,
+ headerComponent,
+ headerLabel,
+ sorters,
+ naturalWidth,
+ canBeWidthLimited: columnCanBeWidthLimited(
+ fields[c.componentId].columnStyle.type
+ ),
};
});
- return columns;
- }, [dimensions, measures, orderedTableColumns, chartData, formatNumber]);
+ const totalNaturalWidth = columnData.reduce(
+ (sum, col) => sum + col.naturalWidth,
+ 0
+ );
+
+ const shouldApplyLimits =
+ settings.limitColumnWidths && totalNaturalWidth > chartWidth;
+
+ const columns = columnData.map(
+ ({ c, headerLabel, sorters, naturalWidth, canBeWidthLimited }) => {
+ const width =
+ shouldApplyLimits && canBeWidthLimited
+ ? Math.min(naturalWidth, LIMITED_COLUMN_WIDTH)
+ : naturalWidth;
+
+ return {
+ Header: headerLabel,
+ // Slugify accessor to avoid id's "." to be parsed as JS object notation.
+ accessor: getSlugifiedId(c.componentId),
+ width,
+ sortType: (
+ rowA: Row,
+ rowB: Row,
+ colId: string
+ ) => {
+ for (const d of sorters) {
+ const result = ascending(
+ d(rowA.values[colId]),
+ d(rowB.values[colId])
+ );
+
+ if (result) {
+ return result;
+ }
+ }
+
+ return 0;
+ },
+ };
+ }
+ );
+
+ return { columns, shouldApplyLimits };
+ }, [
+ dimensions,
+ measures,
+ orderedTableColumns,
+ chartData,
+ formatNumber,
+ fields,
+ chartWidth,
+ settings.limitColumnWidths,
+ ]);
+
+ // Columns used by react-table
+ const tableColumns = tableColumnsData.columns;
+ const shouldApplyWidthLimits = tableColumnsData.shouldApplyLimits;
// Groupings used by react-table
const groupingIds = useMemo(
@@ -436,6 +482,7 @@ const useTableState = (
groupingIds,
hiddenIds,
sortingIds,
+ shouldApplyWidthLimits,
xScaleTimeRange,
links,
...variables,
diff --git a/app/charts/table/table.tsx b/app/charts/table/table.tsx
index 8241b6044..302563e4a 100644
--- a/app/charts/table/table.tsx
+++ b/app/charts/table/table.tsx
@@ -100,6 +100,7 @@ export const Table = () => {
groupingIds,
hiddenIds,
sortingIds,
+ shouldApplyWidthLimits,
} = useChartState() as TableChartState;
const classes = useStyles();
@@ -370,6 +371,7 @@ export const Table = () => {
tableColumnsMeta={tableColumnsMeta}
customSortCount={customSortCount}
totalColumnsWidth={totalColumnsWidth}
+ shouldApplyWidthLimits={shouldApplyWidthLimits}
>
{({ height }: { height: number }) => (
diff --git a/app/charts/table/width-limit.ts b/app/charts/table/width-limit.ts
new file mode 100644
index 000000000..e88ed3da5
--- /dev/null
+++ b/app/charts/table/width-limit.ts
@@ -0,0 +1,5 @@
+import { ColumnMeta } from "@/charts/table/table-state";
+
+export const columnCanBeWidthLimited = (columnType: ColumnMeta["type"]) => {
+ return columnType === "text" || columnType === "category";
+};
diff --git a/app/config-types.ts b/app/config-types.ts
index 9029bae39..3cda82f49 100644
--- a/app/config-types.ts
+++ b/app/config-types.ts
@@ -821,6 +821,7 @@ export type TableColumn = t.TypeOf;
const TableSettings = t.type({
showSearch: t.boolean,
showAllRows: t.boolean,
+ limitColumnWidths: t.boolean,
});
export type TableSettings = t.TypeOf;
diff --git a/app/configurator/configurator-state/mocks.ts b/app/configurator/configurator-state/mocks.ts
index 75ea6d444..02372cbd7 100644
--- a/app/configurator/configurator-state/mocks.ts
+++ b/app/configurator/configurator-state/mocks.ts
@@ -1298,7 +1298,11 @@ export const configJoinedCubes: Partial<
limits: {},
conversionUnitsByComponentId: {},
chartType: "table",
- settings: { showSearch: true, showAllRows: false },
+ settings: {
+ showSearch: true,
+ showAllRows: false,
+ limitColumnWidths: false,
+ },
links: {
enabled: false,
baseUrl: "",
diff --git a/app/configurator/configurator-state/reducer.spec.tsx b/app/configurator/configurator-state/reducer.spec.tsx
index c282d894e..22fdd6c06 100644
--- a/app/configurator/configurator-state/reducer.spec.tsx
+++ b/app/configurator/configurator-state/reducer.spec.tsx
@@ -606,7 +606,7 @@ describe("deriveFiltersFromFields", () => {
"it": "",
},
},
- "version": "5.1.0",
+ "version": "5.2.0",
}
`);
});
diff --git a/app/configurator/table/table-chart-configurator.tsx b/app/configurator/table/table-chart-configurator.tsx
index 3770ab1b1..5b134750f 100644
--- a/app/configurator/table/table-chart-configurator.tsx
+++ b/app/configurator/table/table-chart-configurator.tsx
@@ -110,6 +110,16 @@ export const ChartConfiguratorTable = ({
mainLabel={Sorting}
/>
+
+
+
diff --git a/app/docs/fixtures.ts b/app/docs/fixtures.ts
index 8925d874e..643258b31 100644
--- a/app/docs/fixtures.ts
+++ b/app/docs/fixtures.ts
@@ -1142,7 +1142,7 @@ export const tableConfig: TableConfig = {
type: "identity",
},
},
- settings: { showSearch: true, showAllRows: true },
+ settings: { showSearch: true, showAllRows: true, limitColumnWidths: false },
links: {
enabled: false,
baseUrl: "",
diff --git a/app/locales/de/messages.po b/app/locales/de/messages.po
index 0638914ae..72fa8e8a3 100644
--- a/app/locales/de/messages.po
+++ b/app/locales/de/messages.po
@@ -1124,6 +1124,10 @@ msgstr "Messung der linken Achse"
msgid "wmts.legend-title"
msgstr "Legende"
+#: app/configurator/table/table-chart-configurator.tsx
+msgid "controls.tableSettings.limitColumnWidths"
+msgstr "Responsive Spaltenbreiten"
+
#: app/components/footer.tsx
msgid "footer.button.lindas"
msgstr "LINDAS Linked Data Dienste"
diff --git a/app/locales/en/messages.po b/app/locales/en/messages.po
index 578ac7d92..ac7ca4c99 100644
--- a/app/locales/en/messages.po
+++ b/app/locales/en/messages.po
@@ -1124,6 +1124,10 @@ msgstr "Left axis measure"
msgid "wmts.legend-title"
msgstr "Legend"
+#: app/configurator/table/table-chart-configurator.tsx
+msgid "controls.tableSettings.limitColumnWidths"
+msgstr "Responsive column widths"
+
#: app/components/footer.tsx
msgid "footer.button.lindas"
msgstr "LINDAS Linked Data Services"
diff --git a/app/locales/fr/messages.po b/app/locales/fr/messages.po
index 67a99212f..24b7ef5d9 100644
--- a/app/locales/fr/messages.po
+++ b/app/locales/fr/messages.po
@@ -1124,6 +1124,10 @@ msgstr "Mesure de l'axe gauche"
msgid "wmts.legend-title"
msgstr "Légende"
+#: app/configurator/table/table-chart-configurator.tsx
+msgid "controls.tableSettings.limitColumnWidths"
+msgstr "Largeurs de colonnes adaptatives"
+
#: app/components/footer.tsx
msgid "footer.button.lindas"
msgstr "Services de données liées LINDAS"
diff --git a/app/locales/it/messages.po b/app/locales/it/messages.po
index 672b1c5f1..92ee616e2 100644
--- a/app/locales/it/messages.po
+++ b/app/locales/it/messages.po
@@ -1124,6 +1124,10 @@ msgstr "Misura dell'asse sinistro"
msgid "wmts.legend-title"
msgstr "Legenda"
+#: app/configurator/table/table-chart-configurator.tsx
+msgid "controls.tableSettings.limitColumnWidths"
+msgstr "Larghezze delle colonne reattive"
+
#: app/components/footer.tsx
msgid "footer.button.lindas"
msgstr "Servizi di dati collegati LINDAS"
diff --git a/app/utils/chart-config/constants.ts b/app/utils/chart-config/constants.ts
index 7837e2a31..7efd679ac 100644
--- a/app/utils/chart-config/constants.ts
+++ b/app/utils/chart-config/constants.ts
@@ -1,3 +1,3 @@
-export const CONFIGURATOR_STATE_VERSION = "5.1.0";
+export const CONFIGURATOR_STATE_VERSION = "5.2.0";
-export const CHART_CONFIG_VERSION = "5.1.0";
+export const CHART_CONFIG_VERSION = "5.2.0";
diff --git a/app/utils/chart-config/versioning.ts b/app/utils/chart-config/versioning.ts
index 077555217..9cb1fc717 100644
--- a/app/utils/chart-config/versioning.ts
+++ b/app/utils/chart-config/versioning.ts
@@ -1672,6 +1672,33 @@ export const chartConfigMigrations: Migration[] = [
delete newConfig.links;
}
+ return newConfig;
+ },
+ },
+ {
+ from: "5.1.0",
+ to: "5.2.0",
+ description: `table chart {
+ settings {
+ + limitColumnWidths
+ }
+ }`,
+ up: (config) => {
+ const newConfig = { ...config, version: "5.2.0" };
+
+ if (newConfig.chartType === "table") {
+ newConfig.settings.limitColumnWidths = false;
+ }
+
+ return newConfig;
+ },
+ down: (config) => {
+ const newConfig = { ...config, version: "5.1.0" };
+
+ if (newConfig.chartType === "table") {
+ delete newConfig.settings.limitColumnWidths;
+ }
+
return newConfig;
},
},
@@ -2312,6 +2339,12 @@ export const configuratorStateMigrations: Migration[] = [
fromChartConfigVersion: "5.0.0",
toChartConfigVersion: "5.1.0",
}),
+ makeBumpChartConfigVersionMigration({
+ fromVersion: "5.1.0",
+ toVersion: "5.2.0",
+ fromChartConfigVersion: "5.1.0",
+ toChartConfigVersion: "5.2.0",
+ }),
];
export const migrateConfiguratorState = makeMigrate(
diff --git a/e2e/limits.spec.ts b/e2e/limits.spec.ts
index 086b56bfb..197b6003f 100644
--- a/e2e/limits.spec.ts
+++ b/e2e/limits.spec.ts
@@ -3,7 +3,8 @@ import { setup } from "./common";
const { test, expect } = setup();
-test("future, time-range limits should be displayed in the chart", async ({
+// Skipping the test, as cube is broken now.
+test.skip("future, time-range limits should be displayed in the chart", async ({
page,
selectors,
}) => {