Skip to content

Commit

Permalink
Merge pull request #2623 from owid/fix-grapher-redesign-enhance-table
Browse files Browse the repository at this point in the history
Grapher redesign: Move single data column closer to the entity column
  • Loading branch information
sophiamersmann authored Sep 26, 2023
2 parents f4e367f + 645520d commit 52153be
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 21 deletions.
6 changes: 3 additions & 3 deletions packages/@ourworldindata/grapher/src/dataTable/DataTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
122 changes: 104 additions & 18 deletions packages/@ourworldindata/grapher/src/dataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ import {
Tippy,
isCountryName,
range,
maxBy,
minBy,
excludeUndefined,
} from "@ourworldindata/utils"
import { makeSelectionArray } from "../chart/ChartUtils"
import { SelectionArray } from "../selection/SelectionArray"
Expand Down Expand Up @@ -81,6 +84,7 @@ export interface DataTableManager {
showSelectionOnlyInDataTable?: boolean
isSmall?: boolean
isMedium?: boolean
isNarrow?: boolean
}

@observer
Expand Down Expand Up @@ -238,6 +242,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) => {
Expand Down Expand Up @@ -279,13 +340,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 (
<ColumnHeader
key={column.key}
Expand All @@ -302,6 +370,10 @@ export class DataTable extends React.Component<{
colType="subdimension"
subdimensionType={column.key}
lastSubdimension={i === dim.columns.length - 1}
minWidth={this.singleDataColumnStyle?.minWidth}
contentMaxWidth={
this.singleDataColumnStyle?.contentMaxWidth
}
/>
)
})
Expand Down Expand Up @@ -355,6 +427,8 @@ export class DataTable extends React.Component<{
column.targetTime !== undefined &&
column.targetTime !== value.time

const { contentMaxWidth } = this.singleDataColumnStyle ?? {}

return (
<td
key={key}
Expand All @@ -366,12 +440,14 @@ export class DataTable extends React.Component<{
},
])}
>
{shouldShowClosestTimeNotice &&
makeClosestTimeNotice(
actualColumn.formatTime(column.targetTime!),
actualColumn.formatTime(value.time!) // todo: add back format: "MMM D",
)}
<span>{value.displayValue}</span>
<CellContent maxWidth={contentMaxWidth}>
{shouldShowClosestTimeNotice &&
makeClosestTimeNotice(
actualColumn.formatTime(column.targetTime!),
actualColumn.formatTime(value.time!) // todo: add back format: "MMM D",
)}
<span>{value.displayValue}</span>
</CellContent>
</td>
)
}
Expand Down Expand Up @@ -844,6 +920,8 @@ function ColumnHeader(props: {
colType: "entity" | "dimension" | "subdimension"
subdimensionType?: ColumnKey
lastSubdimension?: boolean
minWidth?: number
contentMaxWidth?: number
}): JSX.Element {
const { sortable, sortedCol, colType, subdimensionType, lastSubdimension } =
props
Expand All @@ -869,24 +947,32 @@ function ColumnHeader(props: {
firstSubdimension: subdimensionType === "start",
endSubdimension: subdimensionType === "end",
lastSubdimension,
deltaColumn: isDeltaColumn(subdimensionType),
})}
rowSpan={props.rowSpan ?? 1}
colSpan={props.colSpan ?? 1}
onClick={props.onClick}
style={{ minWidth: props.minWidth }}
>
<div
className={classnames({
deltaColumn: isDeltaColumn(subdimensionType),
})}
>
{!isEntityColumn && sortIcon}
<span>{props.headerText}</span>
{isEntityColumn && sortIcon}
</div>
<CellContent maxWidth={props.contentMaxWidth}>
<div className="content">
{!isEntityColumn && sortIcon}
<span>{props.headerText}</span>
{isEntityColumn && sortIcon}
</div>
</CellContent>
</th>
)
}

function CellContent(props: {
maxWidth?: number
children?: React.ReactNode
}): JSX.Element {
if (!props.maxWidth) return <>{props.children}</>
return <div style={{ maxWidth: props.maxWidth }}>{props.children}</div>
}

function SortIcon(props: {
isActiveIcon?: boolean
order: SortOrder
Expand Down

0 comments on commit 52153be

Please sign in to comment.