github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/control/controlexecute/dimension_color_map.go (about)

     1  package controlexecute
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  type DimensionColorGenerator struct {
     8  	Map            map[string]map[string]uint8
     9  	startingRow    uint8
    10  	startingColumn uint8
    11  
    12  	// state
    13  	allocatedColorCodes []uint8
    14  	forbiddenColumns    map[uint8]bool
    15  	currentRow          uint8
    16  	currentColumn       uint8
    17  }
    18  
    19  const minColumn = 16
    20  const maxColumn = 51
    21  const minRow = 0
    22  const maxRow = 5
    23  
    24  // NewDimensionColorGenerator creates a new NewDimensionColorGenerator
    25  func NewDimensionColorGenerator(startingRow, startingColumn uint8) (*DimensionColorGenerator, error) {
    26  	forbiddenColumns := map[uint8]bool{
    27  		16: true, 17: true, 18: true, 19: true, 20: true, // red
    28  		22: true, 23: true, 27: true, 28: true, 29: true, //orange
    29  		34: true, 35: true, 36: true, 40: true, 41: true, 42: true, //green/orange
    30  		46: true, 47: true, 48: true, 49: true, // green
    31  	}
    32  	if startingColumn < minColumn || startingColumn > maxColumn {
    33  		return nil, fmt.Errorf("starting column must be between 16 and 51")
    34  	}
    35  	if startingRow < minRow || startingRow > maxRow {
    36  		return nil, fmt.Errorf("starting row must be between 0 and 5")
    37  	}
    38  
    39  	g := &DimensionColorGenerator{
    40  		Map:              make(map[string]map[string]uint8),
    41  		startingRow:      startingRow,
    42  		startingColumn:   startingColumn,
    43  		forbiddenColumns: forbiddenColumns,
    44  	}
    45  	g.reset()
    46  	return g, nil
    47  }
    48  
    49  func (g *DimensionColorGenerator) GetDimensionProperties() []string {
    50  	var res []string
    51  	for d := range g.Map {
    52  		res = append(res, d)
    53  	}
    54  	return res
    55  }
    56  
    57  func (g *DimensionColorGenerator) reset() {
    58  	// create the state map
    59  	g.currentRow = g.startingRow
    60  	g.currentColumn = g.startingColumn
    61  	// clear allocated colors
    62  	g.allocatedColorCodes = nil
    63  }
    64  
    65  func (g *DimensionColorGenerator) populate(e *ExecutionTree) {
    66  	for _, run := range e.ControlRuns {
    67  		for _, r := range run.Rows {
    68  			for _, d := range r.Dimensions {
    69  				if !g.hasDimensionValue(d) {
    70  					g.addDimensionValue(d)
    71  				}
    72  			}
    73  		}
    74  	}
    75  }
    76  
    77  func (g *DimensionColorGenerator) hasDimensionValue(dimension Dimension) bool {
    78  	dimensionMap, ok := g.Map[dimension.Key]
    79  	var gotValue bool
    80  	if ok {
    81  		// so we have a dimension map for this dimension
    82  		// - do we have a dimension color for this property value?
    83  		_, gotValue = dimensionMap[dimension.Value]
    84  	}
    85  	return gotValue
    86  }
    87  
    88  func (g *DimensionColorGenerator) addDimensionValue(d Dimension) {
    89  	// do we have a dimension map for this dimension property?
    90  	if g.Map[d.Key] == nil {
    91  		g.Map[d.Key] = make(map[string]uint8)
    92  	}
    93  
    94  	// store the color keyed by property VALUE
    95  	color := g.getNextColor()
    96  
    97  	g.Map[d.Key][d.Value] = color
    98  }
    99  
   100  func (g *DimensionColorGenerator) getNextColor() uint8 {
   101  	g.incrementCurrentColumn(2)
   102  	g.incrementCurrentRow(2)
   103  
   104  	// does this color clash, or is it forbidden
   105  	color := g.getCurrentColor()
   106  	origColor := color
   107  	for g.colorClashes(color) {
   108  		g.incrementCurrentColumn(1)
   109  		g.incrementCurrentRow(1)
   110  		color = g.getCurrentColor()
   111  		if color == origColor {
   112  			// we have tried them all reset and start from the first color
   113  			g.reset()
   114  			return g.getNextColor()
   115  		}
   116  	}
   117  
   118  	// store this color code
   119  	g.allocatedColorCodes = append(g.allocatedColorCodes, color)
   120  	return color
   121  }
   122  
   123  func (g *DimensionColorGenerator) getCurrentColor() uint8 {
   124  	return g.currentColumn + g.currentRow*36
   125  }
   126  
   127  func (g *DimensionColorGenerator) incrementCurrentRow(increment uint8) {
   128  	g.currentRow += increment
   129  	if g.currentRow > maxRow {
   130  		g.currentRow -= maxRow
   131  	}
   132  }
   133  
   134  func (g *DimensionColorGenerator) incrementCurrentColumn(increment uint8) {
   135  	g.currentColumn += increment
   136  	if g.currentColumn > maxColumn {
   137  		// reset to 16
   138  		g.currentColumn -= maxColumn - minColumn + 1
   139  	}
   140  	for ; g.forbiddenColumns[g.currentColumn]; g.currentColumn++ {
   141  	}
   142  }
   143  
   144  // check map our map of color indexes - if we are within 5 of any other element, skip this color
   145  func (g *DimensionColorGenerator) colorClashes(color uint8) bool {
   146  	for _, a := range g.allocatedColorCodes {
   147  		if a == color {
   148  			return true
   149  		}
   150  	}
   151  
   152  	return false
   153  }