import { $Values as Values } from 'utility-types' ; import { GOLDEN_RATIO } from '../../../../common/constants' ; import { Pixels } from '../../../../common/geometry' ; import { Part } from '../../../../common/text_utils' ; import { ArrayEntry , CHILDREN_KEY , DEPTH_KEY , entryValue , HierarchyOfArrays } from './group_by_rollup' ; const MAX_U_PADDING_RATIO = 0.0256197 ; const MAX_TOP_PADDING_RATIO = 0.33 ; interface LayoutElement { nodes : HierarchyOfArrays ; dependentSize : number ; sectionSizes : number [ ] ; sectionOffsets : number [ ] ; } function layVector ( nodes , independentSize , areaAccessor ) { const area = nodes . reduce ( ( p , n ) => p + areaAccessor ( n ) , 0 ) ; const dependentSize = area / independentSize ; let currentOffset = 0 ; const sectionOffsets = [ currentOffset ] ; const sectionSizes = nodes . map ( ( e , i ) => { const sectionSize = areaAccessor ( e ) / dependentSize ; if ( i < nodes . length - 1 ) sectionOffsets . push ( ( currentOffset += sectionSize ) ) ; return sectionSize ; } ) ; return { nodes , dependentSize , sectionSizes , sectionOffsets } ; } internal export function leastSquarishAspectRatio ( { sectionSizes , dependentSize } ) { return sectionSizes . reduce ( ( p , n ) => Math . min ( p , n / dependentSize , dependentSize / n ) , 1 ) ; } const NullLayoutElement = { nodes : [ ] , dependentSize : NaN , sectionSizes : [ ] , sectionOffsets : [ ] , } ; public export const LayerLayout = Object . freeze ( { horizontal : 'horizontal' as const , vertical : 'vertical' as const , squarifying : 'squarifying' as const , } ) ; public export type LayerLayout = Values < typeof LayerLayout > ; function bestVector ( nodes , height , areaAccessor , layout ) { let previousWorstAspectRatio = - 1 ; let currentWorstAspectRatio = 0 ; let previousVectorLayout = NullLayoutElement ; let currentVectorLayout = NullLayoutElement ; let currentCount = 1 ; do { previousVectorLayout = currentVectorLayout ; previousWorstAspectRatio = currentWorstAspectRatio ; currentVectorLayout = layVector ( nodes . slice ( 0 , currentCount ) , height , areaAccessor ) ; currentWorstAspectRatio = leastSquarishAspectRatio ( currentVectorLayout ) ; } while ( currentCount ++ < nodes . length && ( layout || currentWorstAspectRatio > previousWorstAspectRatio ) ) ; return layout || currentWorstAspectRatio >= previousWorstAspectRatio ? currentVectorLayout : previousVectorLayout ; } function vectorNodeCoordinates ( vectorLayout , x0Base , y0Base , vertical ) { const { nodes , dependentSize , sectionSizes , sectionOffsets } = vectorLayout ; return nodes . map ( ( e , i ) => { const x0 = vertical ? x0Base + sectionOffsets [ i ] : x0Base ; const y0 = vertical ? y0Base : y0Base + sectionOffsets [ i ] ; const x1 = vertical ? x0 + sectionSizes [ i ] : x0 + dependentSize ; const y1 = vertical ? y0 + dependentSize : y0 + sectionSizes [ i ] ; return { node : e , x0 , y0 , x1 , y1 } ; } ) ; } internal export const getTopPadding = ( requestedTopPadding , fullHeight ) => Math . min ( requestedTopPadding , fullHeight * MAX_TOP_PADDING_RATIO ) ; internal export function treemap ( nodes , areaAccessor , topPaddingAccessor , paddingAccessor , { x0 : outerX0 , y0 : outerY0 , width : outerWidth , height : outerHeight , } , layouts ) { if ( nodes . length === 0 ) return [ ] ; const depth = nodes [ 0 ] [ 1 ] [ DEPTH_KEY ] - 1 ; const layerLayout = layouts [ depth ] ?? null ; const vertical = layerLayout === LayerLayout . vertical || ( ! layerLayout && outerWidth / GOLDEN_RATIO <= outerHeight ) ; const independentSize = vertical ? outerWidth : outerHeight ; const vectorElements = bestVector ( nodes , independentSize , areaAccessor , layerLayout ) ; const vector = vectorNodeCoordinates ( vectorElements , outerX0 , outerY0 , vertical ) ; const { dependentSize } = vectorElements ; return vector . concat ( ... vector . map ( ( { node , x0 , y0 , x1 , y1 } ) => { const childrenNodes = entryValue ( node ) [ CHILDREN_KEY ] ; if ( childrenNodes . length === 0 ) { return [ ] ; } const fullWidth = x1 - x0 ; const fullHeight = y1 - y0 ; const uPadding = Math . min ( paddingAccessor ( node ) , fullWidth * MAX_U_PADDING_RATIO * 2 , fullHeight * MAX_U_PADDING_RATIO * 2 ) ; const topPadding = getTopPadding ( topPaddingAccessor ( node ) , fullHeight ) ; const width = fullWidth - 2 * uPadding ; const height = fullHeight - uPadding - topPadding ; return treemap ( childrenNodes , ( d ) => ( ( width * height ) / ( fullWidth * fullHeight ) ) * areaAccessor ( d ) , topPaddingAccessor , paddingAccessor , { x0 : x0 + uPadding , y0 : y0 + topPadding , width , height , } , layouts ) ; } ) ) . concat ( treemap ( nodes . slice ( vector . length ) , areaAccessor , topPaddingAccessor , paddingAccessor , vertical ? { x0 : outerX0 , y0 : outerY0 + dependentSize , width : outerWidth , height : outerHeight - dependentSize } : { x0 : outerX0 + dependentSize , y0 : outerY0 , width : outerWidth - dependentSize , height : outerHeight } , layouts ) ) ; }