/* * Copyright (c) Jupyter Development Team. * Distributed under the terms of the Modified BSD License. */ import { DataModel } from './datamodel'; /** * An interface describing a merged cell group. * r1: start row * r2: end row * c1: start column * c2: end column */ export interface CellGroup { r1: number; r2: number; c1: number; c2: number; } /** * A collection of helper functions relating to merged cell groups */ export namespace CellGroup { /** * Checks if two cell-groups are intersecting * in the given axis. * @param group1 * @param group2 * @param axis */ export function areCellGroupsIntersectingAtAxis( group1: CellGroup, group2: CellGroup, axis: 'row' | 'column' ): boolean { if (axis === 'row') { return ( (group1.r1 >= group2.r1 && group1.r1 <= group2.r2) || (group1.r2 >= group2.r1 && group1.r2 <= group2.r2) || (group2.r1 >= group1.r1 && group2.r1 <= group1.r2) || (group2.r2 >= group1.r1 && group2.r2 <= group1.r2) ); } return ( (group1.c1 >= group2.c1 && group1.c1 <= group2.c2) || (group1.c2 >= group2.c1 && group1.c2 <= group2.c2) || (group2.c1 >= group1.c1 && group2.c1 <= group1.c2) || (group2.c2 >= group1.c1 && group2.c2 <= group1.c2) ); } /** * Checks if cell-groups are intersecting. * @param group1 * @param group2 */ export function areCellGroupsIntersecting( group1: CellGroup, group2: CellGroup ): boolean { return ( ((group1.r1 >= group2.r1 && group1.r1 <= group2.r2) || (group1.r2 >= group2.r1 && group1.r2 <= group2.r2) || (group2.r1 >= group1.r1 && group2.r1 <= group1.r2) || (group2.r2 >= group1.r1 && group2.r2 <= group1.r2)) && ((group1.c1 >= group2.c1 && group1.c1 <= group2.c2) || (group1.c2 >= group2.c1 && group1.c2 <= group2.c2) || (group2.c1 >= group1.c1 && group2.c1 <= group1.c2) || (group2.c2 >= group1.c1 && group2.c2 <= group1.c2)) ); } /** * Retrieves the index of the cell-group to which * the cell at the given row, column belongs. * @param dataModel * @param rgn * @param row * @param column */ export function getGroupIndex( dataModel: DataModel, rgn: DataModel.CellRegion, row: number, column: number ): number { const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; if ( row >= group.r1 && row <= group.r2 && column >= group.c1 && column <= group.c2 ) { return i; } } return -1; } /** * Returns a cell-group for the given row/index coordinates. * @param dataModel * @param rgn * @param row * @param column */ export function getGroup( dataModel: DataModel, rgn: DataModel.CellRegion, row: number, column: number ): CellGroup | null { const groupIndex = getGroupIndex(dataModel, rgn, row, column); if (groupIndex === -1) { return null; } return dataModel.group(rgn, groupIndex); } /** * Returns all cell groups which belong to * a given cell cell region. * @param dataModel * @param rgn */ export function getCellGroupsAtRegion( dataModel: DataModel, rgn: DataModel.CellRegion ): CellGroup[] { let groupsAtRegion: CellGroup[] = []; const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; groupsAtRegion.push(group); } return groupsAtRegion; } /** * Calculates and returns a merged cell-group from * two cell-group objects. * @param groups */ export function joinCellGroups(groups: CellGroup[]): CellGroup { let startRow = Number.MAX_VALUE; let endRow = Number.MIN_VALUE; let startColumn = Number.MAX_VALUE; let endColumn = Number.MIN_VALUE; for (const group of groups) { startRow = Math.min(startRow, group.r1); endRow = Math.max(endRow, group.r2); startColumn = Math.min(startColumn, group.c1); endColumn = Math.max(endColumn, group.c2); } return { r1: startRow, r2: endRow, c1: startColumn, c2: endColumn }; } /** * Merges a cell group with other cells groups in the * same region if they intersect. * @param dataModel the data model of the grid. * @param group the target cell group. * @param region the region of the cell group. * @returns a new cell group after merging has happened. */ export function joinCellGroupWithMergedCellGroups( dataModel: DataModel, group: CellGroup, region: DataModel.CellRegion ): CellGroup { let joinedGroup: CellGroup = { ...group }; const mergedCellGroups: CellGroup[] = getCellGroupsAtRegion( dataModel, region ); for (let g = 0; g < mergedCellGroups.length; g++) { const mergedGroup = mergedCellGroups[g]; if (areCellGroupsIntersecting(joinedGroup, mergedGroup)) { joinedGroup = joinCellGroups([joinedGroup, mergedGroup]); } } return joinedGroup; } /** * Retrieves a list of cell groups intersecting at * a given row. * @param dataModel data model of the grid. * @param rgn the cell region. * @param row the target row to look for intersections at. * @returns all cell groups intersecting with the row. */ export function getCellGroupsAtRow( dataModel: DataModel, rgn: DataModel.CellRegion, row: number ): CellGroup[] { let groupsAtRow = []; const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; if (row >= group.r1 && row <= group.r2) { groupsAtRow.push(group); } } return groupsAtRow; } /** * Retrieves a list of cell groups intersecting at * a given column. * @param dataModel data model of the grid. * @param rgn the cell region. * @param column the target column to look for intersections at. * @returns all cell groups intersecting with the column. */ export function getCellGroupsAtColumn( dataModel: DataModel, rgn: DataModel.CellRegion, column: number ): CellGroup[] { let groupsAtColumn = []; const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; if (column >= group.c1 && column <= group.c2) { groupsAtColumn.push(group); } } return groupsAtColumn; } /** * Merges a target cell group with any cell groups * it intersects with at a given row or column. * @param dataModel data model of the grid. * @param regions list of cell regions. * @param axis row or column. * @param group the target cell group. * @returns a new merged cell group. */ export function joinCellGroupsIntersectingAtAxis( dataModel: DataModel, regions: DataModel.CellRegion[], axis: 'row' | 'column', group: CellGroup ): CellGroup { let groupsAtAxis: CellGroup[] = []; if (axis === 'row') { for (const region of regions) { for (let r = group.r1; r <= group.r2; r++) { groupsAtAxis = groupsAtAxis.concat( CellGroup.getCellGroupsAtRow(dataModel, region, r) ); } } } else { for (const region of regions) { for (let c = group.c1; c <= group.c2; c++) { groupsAtAxis = groupsAtAxis.concat( CellGroup.getCellGroupsAtColumn(dataModel, region, c) ); } } } let mergedGroupAtAxis: CellGroup = CellGroup.joinCellGroups(groupsAtAxis); if (groupsAtAxis.length > 0) { let mergedCellGroups: CellGroup[] = []; for (const region of regions) { mergedCellGroups = mergedCellGroups.concat( CellGroup.getCellGroupsAtRegion(dataModel, region) ); } for (let g = 0; g < mergedCellGroups.length; g++) { const group = mergedCellGroups[g]; if ( CellGroup.areCellGroupsIntersectingAtAxis( mergedGroupAtAxis, group, axis ) ) { mergedGroupAtAxis = CellGroup.joinCellGroups([ group, mergedGroupAtAxis ]); mergedCellGroups.splice(g, 1); g = 0; } } } return mergedGroupAtAxis; } }