diff --git a/packages/@ourworldindata/grapher/src/dataTable/DataTable.scss b/packages/@ourworldindata/grapher/src/dataTable/DataTable.scss
index 849fbe49273..29998f2a91a 100644
--- a/packages/@ourworldindata/grapher/src/dataTable/DataTable.scss
+++ b/packages/@ourworldindata/grapher/src/dataTable/DataTable.scss
@@ -142,21 +142,21 @@
border-color: $header-hover;
}
- > div {
+ .content {
display: flex;
align-items: start;
justify-content: flex-end;
gap: 6px;
}
- &.entity > div {
+ &.entity .content {
justify-content: flex-start;
}
.sort-icon {
color: $sort-icon;
font-size: 13px;
- width: 19px;
+ width: 19px; // keeps column widths fixed when sorting
text-align: right;
&.active {
diff --git a/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx b/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx
index 94b7faaac25..36b0ca5a571 100644
--- a/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx
+++ b/packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx
@@ -37,6 +37,9 @@ import {
Tippy,
isCountryName,
range,
+ maxBy,
+ minBy,
+ excludeUndefined,
} from "@ourworldindata/utils"
import { makeSelectionArray } from "../chart/ChartUtils"
import { SelectionArray } from "../selection/SelectionArray"
@@ -78,6 +81,7 @@ export interface DataTableManager {
endTime?: Time
startTime?: Time
dataTableSlugs?: ColumnSlug[]
+ isNarrow?: boolean
}
@observer
@@ -222,6 +226,63 @@ export class DataTable extends React.Component<{
return this.displayDimensions.length > 1
}
+ // If the table has a single data column, we move the data column
+ // closer to the entity column to make it easier to read the table
+ @computed private get singleDataColumnStyle():
+ | {
+ minWidth: number
+ contentMaxWidth: number
+ }
+ | undefined {
+ // no need to do this on mobile
+ if (this.manager.isNarrow) return
+
+ const hasSingleDataColumn =
+ this.displayDimensions.length === 1 &&
+ this.displayDimensions[0].columns.length === 1
+
+ if (!hasSingleDataColumn) return
+
+ // header text
+ const dimension = this.displayDimensions[0]
+ const column = this.displayDimensions[0].columns[0]
+ const headerText = this.subheaderText(column, dimension)
+
+ // display values
+ const values = excludeUndefined(
+ this.displayRows.map(
+ (row) => (row?.dimensionValues[0] as SingleValue).single
+ )
+ )
+
+ const accessor = (v: Value): number | undefined =>
+ typeof v.value === "string" ? v.value.length : v.value
+ const maxValue = maxBy(values, accessor)
+ const minValue = minBy(values, accessor)
+
+ const measureWidth = (text: string): number =>
+ Bounds.forText(text, { fontSize: 14 }).width
+
+ // in theory, we should be measuring the length of all values
+ // but we might have a lot of values, so we just measure the length
+ // of the min and max values as a proxy
+ const contentMaxWidth = Math.ceil(
+ Math.max(
+ measureWidth(maxValue?.displayValue ?? "") + 20, // 20px accounts for a possible info icon
+ measureWidth(minValue?.displayValue ?? "") + 20, // 20px accounts for a possible info icon
+ measureWidth(headerText) + 26 // 26px accounts for the sort icon
+ )
+ )
+
+ // minimum width of the column
+ const minWidth = 0.66 * this.bounds.width
+
+ // only do this if there is an actual need
+ if (minWidth - contentMaxWidth < 320) return
+
+ return { minWidth, contentMaxWidth }
+ }
+
private get dimensionHeaders(): JSX.Element[] | null {
const { sort } = this.tableState
return this.displayDimensions.map((dim, dimIndex) => {
@@ -263,13 +324,20 @@ export class DataTable extends React.Component<{
})
}
+ private subheaderText(
+ column: DataTableColumn,
+ dimension: DataTableDimension
+ ): string {
+ return isDeltaColumn(column.key)
+ ? columnNameByType[column.key]
+ : dimension.coreTableColumn.formatTime(column.targetTime!)
+ }
+
private get dimensionSubheaders(): JSX.Element[][] {
const { sort } = this.tableState
return this.displayDimensions.map((dim, dimIndex) =>
dim.columns.map((column, i) => {
- const headerText = isDeltaColumn(column.key)
- ? columnNameByType[column.key]
- : dim.coreTableColumn.formatTime(column.targetTime!)
+ const headerText = this.subheaderText(column, dim)
return (