Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/components/HighTable/HighTable.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,21 @@ export const NonSortableColunns: Story = {
data: sortableDataFrame(createUnsortableData(), { sortableColumns: new Set(['ID', 'Count', 'Constant', 'Value1', 'Value2', 'Value3', 'Undefined']) }),
},
}
export const ExclusiveSort: Story = {
render: (args) => {
const [orderBy, setOrderBy] = useState<OrderBy>([])
return (
<HighTable
{...args}
orderBy={orderBy}
onOrderByChange={setOrderBy}
/>
)
},
args: {
data: sortableDataFrame(createUnsortableData(), { exclusiveSort: true }),
},
}
export const LongStrings: Story = {
args: {
data: sortableDataFrame(createLongStringsData()),
Expand Down
10 changes: 7 additions & 3 deletions src/components/TableHeader/TableHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useMemo } from 'react'
import { OrderBy, toggleColumn } from '../../helpers/sort.js'
import { OrderBy, toggleColumn, toggleColumnExclusive } from '../../helpers/sort.js'
import { useData } from '../../hooks/useData.js'
import { ColumnParameters } from '../../hooks/useTableConfig.js'
import ColumnHeader from '../ColumnHeader/ColumnHeader.js'

Expand All @@ -18,12 +19,15 @@ interface TableHeaderProps {
export default function TableHeader({
columnsParameters, orderBy, onOrderByChange, canMeasureWidth, ariaRowIndex, columnClassNames = [],
}: TableHeaderProps) {
const { data } = useData()
const exclusiveSort = data.exclusiveSort === true
// Function to handle click for changing orderBy
const getToggleOrderBy = useCallback((columnHeader: string) => {
if (!onOrderByChange || !orderBy) return undefined
return () => {
onOrderByChange(toggleColumn(columnHeader, orderBy))
}}, [orderBy, onOrderByChange]
const next = exclusiveSort ? toggleColumnExclusive(columnHeader, orderBy) : toggleColumn(columnHeader, orderBy)
onOrderByChange(next)
}}, [orderBy, onOrderByChange, exclusiveSort]
)

const orderByColumn = useMemo(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/dataframe/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export function validateColumn({ column, data: { columnDescriptors } }: { column
}
}

export function validateOrderBy({ orderBy, data: { columnDescriptors } }: { orderBy?: OrderBy, data: Pick<DataFrame, 'columnDescriptors'> }): void {
export function validateOrderBy({ orderBy, data: { columnDescriptors, exclusiveSort } }: { orderBy?: OrderBy, data: Pick<DataFrame, 'columnDescriptors' | 'exclusiveSort'> }): void {
const sortableColumns = new Set(columnDescriptors.filter(c => c.sortable).map(c => c.name))
validateOrderByAgainstSortableColumns({ orderBy, sortableColumns })
validateOrderByAgainstSortableColumns({ orderBy, sortableColumns, exclusiveSort })
}

export function checkSignal(signal?: AbortSignal): void {
Expand Down
10 changes: 7 additions & 3 deletions src/helpers/dataframe/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { checkSignal, validateColumn, validateFetchParams, validateRow } from '.
import { DataFrame, DataFrameEvents, Obj, ResolvedValue } from './types.js'

export function sortableDataFrame<M extends Obj, C extends Obj>(
data: DataFrame<M, C>, options?: { sortableColumns?: Set<string> }
data: DataFrame<M, C>, options?: { sortableColumns?: Set<string>, exclusiveSort?: boolean }
): DataFrame<M, C> {
// If sortableColumns is not provided, make all columns sortable.
const sortableColumns = options?.sortableColumns ?? new Set(data.columnDescriptors.map(c => c.name))
const exclusiveSort = options?.exclusiveSort ?? data.exclusiveSort
Comment thread
severo marked this conversation as resolved.
// Validate that all sortable columns are present in the header.
for (const column of sortableColumns) {
validateColumn({ column, data: { columnDescriptors: data.columnDescriptors } })
Expand All @@ -20,6 +21,9 @@ export function sortableDataFrame<M extends Obj, C extends Obj>(
return sortable === false || sortable === undefined // If the column is not in sortableColumns, it should not be sortable
})) {
// TODO(SL): we should return a clone of the data frame (and we should provide a helper function to clone a dataframe).
Comment thread
bleakley marked this conversation as resolved.
if (options && 'exclusiveSort' in options && data.exclusiveSort !== options.exclusiveSort) {
return { ...data, exclusiveSort: options.exclusiveSort }
}
return data
}

Expand All @@ -39,7 +43,7 @@ export function sortableDataFrame<M extends Obj, C extends Obj>(

const getUpstreamRow: ({ row, orderBy }: { row: number, orderBy?: OrderBy }) => ResolvedValue<number> | undefined = function({ row, orderBy }) {
validateRow({ row, data: { numRows } })
validateOrderByAgainstSortableColumns({ orderBy, sortableColumns })
validateOrderByAgainstSortableColumns({ orderBy, sortableColumns, exclusiveSort })
if (!orderBy || orderBy.length === 0) {
// If no orderBy is provided, we can return the upstream row number.
return { value: row }
Expand Down Expand Up @@ -116,7 +120,7 @@ export function sortableDataFrame<M extends Obj, C extends Obj>(
}
}

return { metadata, numRows, columnDescriptors, getRowNumber, getCell, fetch, eventTarget }
return { metadata, numRows, columnDescriptors, getRowNumber, getCell, fetch, eventTarget, exclusiveSort }
}

async function fetchFromIndexes({ columns, indexes, signal, fetch }: { columns?: string[], indexes: number[], signal?: AbortSignal, fetch: Exclude<DataFrame['fetch'], undefined> }): Promise<void> {
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/dataframe/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export interface DataFrame<M extends Obj = Obj, C extends Obj = Obj> {
columnDescriptors: readonly ColumnDescriptor<C>[]
metadata?: M

// If true, only one column can be sorted at a time, and any update to orderBy will replace the previous one.
Comment thread
bleakley marked this conversation as resolved.
// Defaults to false.
exclusiveSort?: boolean

// Returns the cell value.
// undefined means pending, ResolvedValue is a boxed value type (so we can distinguish undefined from pending)
// getCell does NOT initiate a fetch, it just returns resolved data
Expand Down
16 changes: 15 additions & 1 deletion src/helpers/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ export function serializeOrderBy(orderBy: OrderBy): string {
return JSON.stringify(orderBy)
}

export function validateOrderByAgainstSortableColumns({ sortableColumns, orderBy }: { sortableColumns?: Set<string>, orderBy?: OrderBy }): void {
export function validateOrderByAgainstSortableColumns({ sortableColumns, orderBy, exclusiveSort }: { sortableColumns?: Set<string>, orderBy?: OrderBy, exclusiveSort?: boolean }): void {
if (!orderBy) return
const unsortableColumns = orderBy.map(({ column }) => column).filter(column => !sortableColumns?.has(column))
if (unsortableColumns.length > 0) {
throw new Error(`Unsortable columns in orderBy field: ${unsortableColumns.join(', ')}`)
}
if (exclusiveSort && orderBy.length > 1) {
throw new Error('DataFrame is exclusiveSort, but orderBy contains multiple columns')
}
}

export function areEqualOrderBy(a?: OrderBy, b?: OrderBy): boolean {
Expand Down Expand Up @@ -62,6 +65,17 @@ export function toggleColumn(column: string, orderBy: OrderBy): OrderBy {
return [{ column, direction: 'ascending' }, ...prefix, ...suffix]
}

export function toggleColumnExclusive(column: string, orderBy: OrderBy): OrderBy {
const { item } = partitionOrderBy(orderBy, column)
if (item) {
if (item.direction === 'ascending') {
return [{ column, direction: 'descending' }]
}
return []
}
return [{ column, direction: 'ascending' }]
}

// TODO(SL): test
export function computeRanks(values: any[]): number[] {
const valuesWithIndex = values.map((value, index) => ({ value, index }))
Expand Down