1package rules23import (4 "strconv"5 "strings"67 "src.rest/cat/012/internal/sheet"8 "src.rest/cat/012/internal/ui/theme"9)1011// The data bar's and icon set's parts of the conditional format form.1213// Kinds of the form's rule, the choices of its Format row.14const (15 formSingle = iota16 formScale17 formBar18 formIcons19)2021var formatTitles = []string{"Single color", "Color scale", "Data bar", "Icon set"}2223// barKinds are the kinds a bar's shortest and longest points offer, and24// iconKinds an icon's threshold's.25var (26 barKinds = [2][]sheet.PointKind{27 {sheet.PointMin, sheet.PointNumber, sheet.PointPercent, sheet.PointPercentile},28 {sheet.PointMax, sheet.PointNumber, sheet.PointPercent, sheet.PointPercentile},29 }30 iconKinds = []sheet.PointKind{sheet.PointPercent, sheet.PointNumber, sheet.PointPercentile}31)3233// barForm is a data bar or icon set being edited.34type barForm struct {35 color int // index in sheet.Colors()[1:]36 pts [2]cfPoint // the bar's shortest and longest; color unused37 barOnly bool3839 set int // index in sheet.IconSets()40 size int // index in the set's sizes41 thr [4]cfPoint // the icons' thresholds, the second icon's first42 sized int // the size thr holds values for43 reverse bool44 iconOnly bool45}4647func newBarForm() barForm {48 b := barForm{color: int(sheet.ColorBlue) - 1}49 b.fitThresholds(3)50 return b51}5253// fitThresholds gives n icons thresholds evenly apart in percent, as54// Excel does, unless they already have values for n.55func (b *barForm) fitThresholds(n int) {56 if b.sized == n {57 return58 }59 for k := range b.thr {60 b.thr[k] = cfPoint{value: strconv.Itoa(100 * (k + 1) / n)}61 }62 b.sized = n63}6465// sizes are the current set's sizes.66func (b *barForm) sizes() []int { return sheet.IconSets()[b.set].Sizes() }6768// icons is how many icons the form's set has.69func (b *barForm) icons() int {70 ss := b.sizes()71 b.size = min(b.size, len(ss)-1)72 return ss[b.size]73}7475// load reads rule r into the form.76func (b *barForm) load(r sheet.CondFormat) {77 switch {78 case r.IsBar():79 b.color, b.barOnly = int(r.Bar)-1, r.BarOnly80 for k, p := range r.Scale {81 b.pts[k] = cfPoint{kind: max(indexOf(barKinds[k], p.Kind), 0), value: p.Value}82 }83 case r.IsIcons():84 b.set = max(indexOf(sheet.IconSets(), r.Icons), 0)85 n := len(r.Scale) + 186 b.size = max(indexOf(b.sizes(), n), 0)87 for k, p := range r.Scale {88 b.thr[k] = cfPoint{kind: max(indexOf(iconKinds, p.Kind), 0), value: p.Value}89 }90 b.sized, b.reverse, b.iconOnly = n, r.Reverse, r.BarOnly91 }92}9394// barRule fills r's data bar from the form.95func (b *barForm) barRule(r *sheet.CondFormat, loc func(string) string) {96 r.Bar, r.BarOnly = sheet.Color(b.color+1), b.barOnly97 for k, p := range b.pts {98 r.Scale = append(r.Scale, sheet.ScalePoint{Kind: barKinds[k][p.kind], Value: loc(strings.TrimSpace(p.value))})99 }100}101102// iconRule fills r's icon set from the form.103func (b *barForm) iconRule(r *sheet.CondFormat, loc func(string) string) {104 n := b.icons()105 r.Icons, r.Reverse, r.BarOnly = sheet.IconSets()[b.set], b.reverse, b.iconOnly106 for _, p := range b.thr[:n-1] {107 r.Scale = append(r.Scale, sheet.ScalePoint{Kind: iconKinds[p.kind], Value: loc(strings.TrimSpace(p.value))})108 }109}110111// barRows are the rows of a data bar: its points, color, whether the112// values show, and a preview.113func (b *barForm) barRows(th *theme.Theme, preview func(w int) string) []row {114 names := [2]string{"Shortest", "Longest"}115 var rs []row116 for k := range b.pts {117 p := &b.pts[k]118 kinds := make([]string, len(barKinds[k]))119 for j, pk := range barKinds[k] {120 kinds[j] = pk.Title()121 }122 kinds[0] = "Automatic"123 rs = append(rs, row{kind: rowChoice, label: names[k], choices: kinds, at: &p.kind,124 hint: "Where the bar is shortest or longest: automatic runs from zero, or the lowest below it, to the highest"})125 if barKinds[k][p.kind].TakesValue() {126 rs = append(rs, row{kind: rowText, label: " Value", text: &p.value, placeholder: "0", hint: "The point's number, percent or percentile"})127 }128 }129 colors := colorTitles()[1:]130 return append(rs,131 row{kind: rowChoice, label: "Bar color", choices: colors, at: &b.color, swatch: func(i int) string {132 return th.RuleText[i+1].Render("██")133 }, hint: "The bar's color, from the terminal's palette"},134 row{kind: rowCheck, label: "Show bar only", on: &b.barOnly, hint: "Hide the values, showing only their bars"},135 row{kind: rowSep},136 row{kind: rowPreview, label: "Preview", preview: preview})137}138139// iconRows are the rows of an icon set: the set and its size, each140// threshold, the order, whether the values show, and a preview.141func (b *barForm) iconRows(preview func(w int) string) []row {142 sets := sheet.IconSets()143 titles := make([]string, len(sets))144 for i, s := range sets {145 titles[i] = s.Title()146 }147 n := b.icons()148 b.fitThresholds(n)149 sizes := make([]string, len(b.sizes()))150 for i, s := range b.sizes() {151 sizes[i] = strconv.Itoa(s) + " icons"152 }153 glyphs := sets[b.set].Glyphs(n)154 rs := []row{155 {kind: rowChoice, label: "Icons", choices: titles, at: &b.set, hint: "The set: arrows, circles filling up, symbols or rising bars"},156 {kind: rowChoice, label: "How many", choices: sizes, at: &b.size, hint: "How many icons the values are sorted into"},157 }158 kinds := make([]string, len(iconKinds))159 for j, k := range iconKinds {160 kinds[j] = k.Title()161 }162 for k := range n - 1 {163 p := &b.thr[k]164 rs = append(rs,165 row{kind: rowChoice, label: glyphs[k+1] + " from", choices: kinds, at: &p.kind, hint: "What the icon's threshold is: a percent of the way from lowest to highest, a number or a percentile"},166 row{kind: rowText, label: " Value", text: &p.value, placeholder: "50", hint: "The lowest value the icon goes to"})167 }168 return append(rs,169 row{kind: rowCheck, label: "Reverse icons", on: &b.reverse, hint: "Give the lowest values the highest icon"},170 row{kind: rowCheck, label: "Show icon only", on: &b.iconOnly, hint: "Hide the values, showing only their icons"},171 row{kind: rowSep},172 row{kind: rowPreview, label: "Preview", preview: preview})173}174175// barSample draws a data bar's look across w columns: bars a quarter,176// half and all of the way, as a sheet's cells would show them.177func barSample(th *theme.Theme, c sheet.Color, w int) string {178 var b strings.Builder179 each := max(w/3, 2)180 for _, frac := range []float64{0.25, 0.6, 1} {181 for _, n := range theme.BarCover(frac, each-1) {182 b.WriteString(th.RuleText[c].Render(theme.Block(n)))183 }184 b.WriteString(" ")185 }186 return b.String()187}188189// iconSample draws an icon set's icons, lowest first, in their colors.190func iconSample(th *theme.Theme, set sheet.IconSet, n int, reverse bool) string {191 var b strings.Builder192 gs := set.Glyphs(n)193 for k := range gs {194 i := k195 if reverse {196 i = len(gs) - 1 - k197 }198 b.WriteString(th.RuleText[set.IconColor(i, n)].Render(gs[i]) + " ")199 }200 return b.String()201}