module powerbi . extensibility . visual { import Selection = d3 . Selection ; import UpdateSelection = d3 . selection . Update ; import ClassAndSelector = powerbi . extensibility . utils . svg . CssConstants . ClassAndSelector ; import createClassAndSelector = powerbi . extensibility . utils . svg . CssConstants . createClassAndSelector ; export interface ITableView { data ( data , dataIdFunction , dataAppended ) : ITableView ; rowHeight ( rowHeight ) : ITableView ; columnWidth ( columnWidth ) : ITableView ; orientation ( orientation ) : ITableView ; rows ( rows ) : ITableView ; columns ( columns ) : ITableView ; viewport ( viewport ) : ITableView ; render ( ) : void ; empty ( ) : void ; computedColumns : number ; computedRows : number ; } export module TableViewFactory { export function createTableView ( options ) { return new TableView ( options ) ; } } export interface TableViewViewOptions { enter : ( selection : Selection < any > ) => void ; exit : ( selection : Selection < any > ) => void ; update : ( selection : Selection < any > ) => void ; baseContainer : Selection < any > ; rowHeight : number ; columnWidth : number ; orientation : string ; rows : number ; columns : number ; viewport : IViewport ; scrollEnabled : boolean ; } export interface TableViewGroupedData { data : any [ ] ; totalColumns : number ; totalRows : number ; } export interface TableViewComputedOptions { columns : number ; rows : number ; } /**
     * A UI Virtualized List, that uses the D3 Enter, Update & Exit pattern to update rows.
     * It can create lists containing either HTML or SVG elements.
     */ export class TableView implements ITableView { public static RowSelector : ClassAndSelector = createClassAndSelector ( 'row' ) ; public static CellSelector : ClassAndSelector = createClassAndSelector ( 'cell' ) ; private static defaultRowHeight = 0 ; private static defaultColumns = 1 ; private getDatumIndex : ( d : any ) => { } ; private _data : any [ ] ; private _totalRows : number ; private _totalColumns : number ; private options : TableViewViewOptions ; private visibleGroupContainer : Selection < any > ; private scrollContainer : Selection < any > ; private computedOptions : TableViewComputedOptions ; public constructor ( options ) { this . options = $ . extend ( true , { } , options ) ; this . options . baseContainer . style ( 'overflow-y' , 'auto' ) . attr ( 'drag-resize-disabled' , true ) ; this . scrollContainer = options . baseContainer . append ( 'div' ) . attr ( 'class' , 'scrollRegion' ) ; this . visibleGroupContainer = this . scrollContainer . append ( 'div' ) . attr ( 'class' , 'visibleGroup' ) ; TableView . SetDefaultOptions ( options ) ; } private static SetDefaultOptions ( options ) { options . rowHeight = options . rowHeight || TableView . defaultRowHeight ; } public get computedColumns ( ) : number { return this . computedOptions ? this . computedOptions . columns : 0 ; } public get computedRows ( ) : number { return this . computedOptions ? this . computedOptions . rows : 0 ; } public rowHeight ( rowHeight ) { this . options . rowHeight = Math . ceil ( rowHeight ) ; return this ; } public columnWidth ( columnWidth ) { this . options . columnWidth = Math . ceil ( columnWidth ) ; return this ; } public orientation ( orientation ) { this . options . orientation = orientation ; return this ; } public rows ( rows ) { this . options . rows = Math . ceil ( rows ) ; return this ; } public columns ( columns ) { this . options . columns = Math . ceil ( columns ) ; return this ; } public data ( data , getDatumIndex , dataReset = false ) { this . _data = data ; this . getDatumIndex = getDatumIndex ; this . setTotalRows ( ) ; if ( dataReset ) { $ ( this . options . baseContainer . node ( ) ) . scrollTop ( 0 ) ; } return this ; } public viewport ( viewport ) { this . options . viewport = viewport ; return this ; } public empty ( ) { this . _data = [ ] ; this . render ( ) ; return this ; } private setTotalRows ( ) { let count = this . _data . length , rows = Math . min ( this . options . rows , count ) , columns = Math . min ( this . options . columns , count ) ; if ( ( columns > 0 ) && ( rows > 0 ) ) { this . _totalColumns = columns ; this . _totalRows = rows ; } else if ( rows > 0 ) { this . _totalRows = rows ; this . _totalColumns = Math . ceil ( count / rows ) ; } else if ( columns > 0 ) { this . _totalColumns = columns ; this . _totalRows = Math . ceil ( count / columns ) ; } else { this . _totalColumns = TableView . defaultColumns ; this . _totalRows = Math . ceil ( count / TableView . defaultColumns ) ; } } private getGroupedData ( ) { let options = this . options , groupedData = [ ] , totalItems = this . _data . length , totalRows = options . rows > totalItems ? totalItems : options . rows , totalColumns = options . columns > totalItems ? totalItems : options . columns ; if ( totalColumns === 0 && totalRows === 0 ) { if ( options . orientation === Orientation . HORIZONTAL ) { totalColumns = totalItems ; totalRows = 1 ; } else { totalColumns = 1 ; totalRows = totalItems ; } } else if ( totalColumns === 0 && totalRows > 0 ) { totalColumns = Math . ceil ( totalItems / totalRows ) ; } else if ( totalColumns > 0 && totalRows === 0 ) { totalRows = Math . ceil ( totalItems / totalColumns ) ; } if ( this . options . orientation === Orientation . VERTICAL ) { let n = totalRows ; totalRows = totalColumns ; totalColumns = n ; } else if ( this . options . orientation === Orientation . HORIZONTAL ) { if ( totalRows === 0 ) { totalRows = this . _totalRows ; } if ( totalColumns === 0 ) { totalColumns = this . _totalColumns ; } } let m = 0 , k = 0 ; for ( let i = 0 ; i < totalRows ; i ++ ) { if ( this . options . orientation === Orientation . VERTICAL && options . rows === 0 && totalItems % options . columns > 0 && options . columns <= totalItems ) { if ( totalItems % options . columns > i ) { m = i * Math . ceil ( totalItems / options . columns ) ; k = m + Math . ceil ( totalItems / options . columns ) ; this . addDataToArray ( groupedData , this . _data , m , k ) ; } else { this . addDataToArray ( groupedData , this . _data , k , k + Math . floor ( totalItems / options . columns ) ) ; k = k + Math . floor ( totalItems / options . columns ) ; } } else if ( this . options . orientation === Orientation . HORIZONTAL && options . columns === 0 && totalItems % options . rows > 0 && options . rows <= totalItems ) { if ( totalItems % options . rows > i ) { m = i * Math . ceil ( totalItems / options . rows ) ; k = m + Math . ceil ( totalItems / options . rows ) ; this . addDataToArray ( groupedData , this . _data , m , k ) ; } else { this . addDataToArray ( groupedData , this . _data , k , k + Math . floor ( totalItems / options . rows ) ) ; k = k + Math . floor ( totalItems / options . rows ) ; } } else { let k = i * totalColumns ; this . addDataToArray ( groupedData , this . _data , k , k + totalColumns ) ; } } this . computedOptions = this . getComputedOptions ( groupedData , this . options . orientation ) ; return { data : groupedData , totalColumns : totalColumns , totalRows : totalRows } ; } private addDataToArray ( array , data , start , end ) { if ( ! array || ! data ) { return ; } let elements = data . slice ( start , end ) ; if ( elements && elements . length > 0 ) { array . push ( elements ) ; } } private getComputedOptions ( data , orientation ) { let rows , columns = 0 ; rows = data ? data . length : 0 ; for ( let i = 0 ; i < rows ; i ++ ) { let currentRow = data [ i ] ; if ( currentRow && currentRow . length > columns ) { columns = currentRow . length ; } } if ( orientation === Orientation . HORIZONTAL ) { return { columns : columns , rows : rows } ; } else { return { columns : rows , rows : columns } ; } } public render ( ) { let options = this . options , visibleGroupContainer = this . visibleGroupContainer , rowHeight = options . rowHeight || TableView . defaultRowHeight , groupedData = this . getGroupedData ( ) , rowSelection , cellSelection ; rowSelection = visibleGroupContainer . selectAll ( TableView . RowSelector . selector ) . data ( < ChicletSlicerDataPoint [ ] > groupedData . data ) ; rowSelection . enter ( ) . append ( "div" ) . classed ( TableView . RowSelector . class , true ) ; cellSelection = rowSelection . selectAll ( TableView . CellSelector . selector ) . data ( ( dataPoints ) => { return dataPoints ; } ) ; cellSelection . enter ( ) . append ( 'div' ) . classed ( TableView . CellSelector . class , true ) ; cellSelection . call ( ( selection ) => { options . enter ( selection ) ; } ) ; cellSelection . call ( ( selection ) => { options . update ( selection ) ; } ) ; cellSelection . style ( { 'height' : ( rowHeight > 0 ) ? rowHeight + 'px' : 'auto' } ) ; if ( this . options . orientation === Orientation . VERTICAL ) { let realColumnNumber = 0 ; for ( let i = 0 ; i < groupedData . data . length ; i ++ ) { if ( groupedData . data [ i ] . length !== 0 ) realColumnNumber = i + 1 ; } cellSelection . style ( { 'width' : '100%' } ) ; rowSelection . style ( { 'width' : ( options . columnWidth > 0 ) ? options . columnWidth + 'px' : ( 100 / realColumnNumber ) + '%' } ) ; } else { cellSelection . style ( { 'width' : ( options . columnWidth > 0 ) ? options . columnWidth + 'px' : ( 100 / groupedData . totalColumns ) + '%' } ) ; rowSelection . style ( { 'width' : null } ) ; } cellSelection . exit ( ) . remove ( ) ; rowSelection . exit ( ) . call ( d => options . exit ( d ) ) . remove ( ) ; } } }