github.com/wrgl/wrgl@v0.14.0/pkg/widgets/prof/stat_table.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright © 2022 Wrangle Ltd
     3  
     4  package widgetsprof
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/gdamore/tcell/v2"
    11  	"github.com/rivo/tview"
    12  	"github.com/wrgl/wrgl/pkg/objects"
    13  	"github.com/wrgl/wrgl/pkg/widgets"
    14  )
    15  
    16  func floatString(f float64) string {
    17  	return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", f), "0"), ".")
    18  }
    19  
    20  var (
    21  	cellStyle      = tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorBlack)
    22  	columnStyle    = tcell.StyleDefault.Background(tcell.ColorBlack).Bold(true)
    23  	pctStyle       = tcell.StyleDefault.Foreground(tcell.ColorLightGray).Background(tcell.ColorBlack)
    24  	statNameStyle  = tcell.StyleDefault.Foreground(tcell.ColorAqua).Background(tcell.ColorBlack).Bold(true)
    25  	statValueStyle = tcell.StyleDefault.Foreground(tcell.ColorYellow).Background(tcell.ColorBlack)
    26  	addedStyle     = cellStyle.Foreground(tcell.ColorGreen)
    27  	removedStyle   = cellStyle.Foreground(tcell.ColorRed)
    28  
    29  	statCells = []StatCells{
    30  		newSingleStatCells("NA count", func(colProf *objects.ColumnProfile) string {
    31  			return fmt.Sprintf("%d", colProf.NACount)
    32  		}),
    33  		newSingleStatCells("Min", func(colProf *objects.ColumnProfile) string {
    34  			if colProf.Min == nil {
    35  				return ""
    36  			}
    37  			return floatString(*colProf.Min)
    38  		}),
    39  		newSingleStatCells("Max", func(colProf *objects.ColumnProfile) string {
    40  			if colProf.Max == nil {
    41  				return ""
    42  			}
    43  			return floatString(*colProf.Max)
    44  		}),
    45  		newSingleStatCells("Mean", func(colProf *objects.ColumnProfile) string {
    46  			if colProf.Mean == nil {
    47  				return ""
    48  			}
    49  			return floatString(*colProf.Mean)
    50  		}),
    51  		newSingleStatCells("Median", func(colProf *objects.ColumnProfile) string {
    52  			if colProf.Median == nil {
    53  				return ""
    54  			}
    55  			return floatString(*colProf.Median)
    56  		}),
    57  		newSingleStatCells("Std. Deviation", func(colProf *objects.ColumnProfile) string {
    58  			if colProf.StdDeviation == nil {
    59  				return ""
    60  			}
    61  			return floatString(*colProf.StdDeviation)
    62  		}),
    63  		newSingleStatCells("Min length", func(colProf *objects.ColumnProfile) string {
    64  			if colProf.MinStrLen == 0 {
    65  				return ""
    66  			}
    67  			return fmt.Sprintf("%d", colProf.MinStrLen)
    68  		}),
    69  		newSingleStatCells("Max length", func(colProf *objects.ColumnProfile) string {
    70  			if colProf.MaxStrLen == 0 {
    71  				return ""
    72  			}
    73  			return fmt.Sprintf("%d", colProf.MaxStrLen)
    74  		}),
    75  		newSingleStatCells("Avg length", func(colProf *objects.ColumnProfile) string {
    76  			if colProf.AvgStrLen == 0 {
    77  				return ""
    78  			}
    79  			return fmt.Sprintf("%d", colProf.AvgStrLen)
    80  		}),
    81  		newTopValuesCells("Top values", func(colProf *objects.ColumnProfile) objects.ValueCounts { return colProf.TopValues }),
    82  		newPercentilesCells("Percentiles", func(colProf *objects.ColumnProfile) []float64 { return colProf.Percentiles }),
    83  	}
    84  )
    85  
    86  type StatTable struct {
    87  	*widgets.SelectableTable
    88  	pool        *widgets.CellsPool
    89  	tblProf     *objects.TableProfile
    90  	rowsPerStat []int
    91  	statCells   []StatCells
    92  }
    93  
    94  func NewStatTable(tblProf *objects.TableProfile) *StatTable {
    95  	t := &StatTable{
    96  		SelectableTable: widgets.NewSelectableTable(),
    97  		tblProf:         tblProf,
    98  	}
    99  	t.SelectableTable.SetGetCellsFunc(t.getCells).
   100  		SetMinSelection(1, 1).
   101  		Select(1, 1, 0)
   102  	totalRows := t.calculateRowsCount()
   103  	t.VirtualTable.SetFixed(1, 1).
   104  		SetShape(totalRows+1, len(tblProf.Columns)+1).
   105  		SetSeparator('│')
   106  	t.pool = widgets.NewCellsPool(t.VirtualTable)
   107  	return t
   108  }
   109  
   110  func (t *StatTable) calculateRowsCount() int {
   111  	totalRows := 0
   112  	for _, sc := range statCells {
   113  		c := 0
   114  		for _, col := range t.tblProf.Columns {
   115  			if v := sc.NumRows(col); v > c {
   116  				c = v
   117  			}
   118  		}
   119  		if c > 0 {
   120  			t.rowsPerStat = append(t.rowsPerStat, c)
   121  			totalRows += c
   122  			t.statCells = append(t.statCells, sc)
   123  		}
   124  	}
   125  	return totalRows
   126  }
   127  
   128  func (t *StatTable) getCells(row, column int) []*widgets.TableCell {
   129  	if row == 0 {
   130  		if column == 0 {
   131  			return nil
   132  		}
   133  		cells, ok := t.pool.Get(row, column, 1)
   134  		if !ok {
   135  			cells[0].SetText(t.tblProf.Columns[column-1].Name).
   136  				SetStyle(columnStyle).
   137  				SetAlign(tview.AlignCenter)
   138  		}
   139  		return cells
   140  	}
   141  	sum := 0
   142  	var sc StatCells
   143  	var statRow int
   144  	for i, rowsCount := range t.rowsPerStat {
   145  		sum += rowsCount
   146  		if sum > row-1 {
   147  			sc = t.statCells[i]
   148  			statRow = row - 1 - sum + rowsCount
   149  			break
   150  		}
   151  	}
   152  	if column == 0 {
   153  		cells, ok := t.pool.Get(row, column, 1)
   154  		if !ok && statRow == 0 {
   155  			cells[0].SetText(sc.Name()).SetStyle(statNameStyle)
   156  		}
   157  		return cells
   158  	}
   159  	cells, ok := t.pool.Get(row, column, sc.NumColumns())
   160  	if !ok {
   161  		cp := t.tblProf.Columns[column-1]
   162  		if statRow < sc.NumRows(cp) {
   163  			sc.DecorateCells(statRow, t.tblProf, cp, cells)
   164  		}
   165  	}
   166  	return cells
   167  }