cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / sheet / pivotcols.go
pivotcols.go 3.6K
1package sheet23import "slices"45// Column groups of a pivot: each combination of the column fields' values6// is a group, and so is each combination of the first few fields' values,7// an outer group. Outer groups order the columns when a field is sorted8// by a value's total, and show as subtotal columns after their last9// combination when column totals are on, as Sheets' totals of an outer10// column field.1112// colGroup is a combination of the first len(labels) column fields'13// values: all of them for a column of results, fewer for an outer group.14type colGroup struct {15 id int16 labels []Value17 formats []Format18}1920// colNode finds column groups by each column field's value in turn.21type colNode struct {22 kids map[groupKey]*colNode23 group *colGroup24}2526// colGroupsOf returns the ids of the column groups row counts in, outer27// groups first, or none without column fields.28func (c *pivotCalc) colGroupsOf(row int) []int {29 if len(c.p.Columns) == 0 || len(c.p.Values) == 0 {30 return nil31 }32 c.colIDs = c.colIDs[:0]33 n := &c.colRoot34 for k, g := range c.p.Columns {35 key := keyOf(c.d.value(Addr{Col: g.Col, Row: row}), true)36 kid := n.kids[key]37 if kid == nil {38 if n.kids == nil {39 n.kids = map[groupKey]*colNode{}40 }41 kid = &colNode{group: c.newColGroup(row, k+1)}42 n.kids[key] = kid43 if k == len(c.p.Columns)-1 {44 c.cols = append(c.cols, kid.group)45 }46 }47 n = kid48 c.colIDs = append(c.colIDs, n.group.id)49 }50 return c.colIDs51}5253// newColGroup is the group of row's values in the first depth column54// fields.55func (c *pivotCalc) newColGroup(row, depth int) *colGroup {56 cg := &colGroup{id: c.nextCol}57 c.nextCol++58 for _, g := range c.p.Columns[:depth] {59 a := Addr{Col: g.Col, Row: row}60 cg.labels = append(cg.labels, c.d.value(a))61 cg.formats = append(cg.formats, c.d.format(a))62 }63 return cg64}6566// outer returns the group of the first len(labels) column fields' values.67func (c *pivotCalc) outer(labels []Value) *colGroup {68 n := &c.colRoot69 for _, v := range labels {70 n = n.kids[keyOf(v, true)]71 }72 return n.group73}7475// sortCols orders the column groups by each column field in turn; a field76// sorted by a value's total compares the totals of its outer groups, so77// each outer group's columns stay together.78func (c *pivotCalc) sortCols() {79 slices.SortStableFunc(c.cols, func(a, b *colGroup) int {80 for k, g := range c.p.Columns {81 d := c.order(g, a.labels[k:k+1], b.labels[k:k+1], func(i int) (Value, Value) {82 va, _ := c.result(c.root, c.outer(a.labels[:k+1]).id, i)83 vb, _ := c.result(c.root, c.outer(b.labels[:k+1]).id, i)84 return va, vb85 })86 if d != 0 {87 return d88 }89 }90 return 091 })92}9394// subtotals reports whether outer column groups show as subtotal columns:95// with column totals on and several column fields.96func (c *pivotCalc) subtotals() bool {97 return c.p.ColumnTotals && len(c.p.Columns) > 198}99100// columnOrder lists the column groups as the results show them: each101// combination in order, after each outer group's last one its subtotal102// when subtotals show, innermost first.103func (c *pivotCalc) columnOrder() []*colGroup {104 if !c.subtotals() {105 return slices.Clone(c.cols)106 }107 var out []*colGroup108 for i, cg := range c.cols {109 out = append(out, cg)110 for d := len(c.p.Columns) - 1; d >= 1; d-- {111 if i+1 < len(c.cols) && sameLabels(cg.labels[:d], c.cols[i+1].labels[:d]) {112 break113 }114 out = append(out, c.outer(cg.labels[:d]))115 }116 }117 return out118}119120// totalName labels the total of a group, "East Total", as Sheets does.121func totalName(v Value, f Format) string {122 if v.Kind == Empty {123 return "Total"124 }125 return FormatText(v, f) + " Total"126}