1package chart23import (4 "strconv"56 "github.com/charmbracelet/x/ansi"78 "src.rest/cat/012/internal/sheet"9)1011// legendBox is where a chart's legend of series goes: a row under the12// plot, a column right of it, or nowhere. Draw lays the chart out in the13// room the legend leaves.14type legendBox struct {15 names []string16 pos sheet.ChartLegend17 row int // the bottom legend's row18 x, w int // the right legend's first column and width19}2021// Legend sizes.22const (23 legendNameW = 16 // longest name in a right legend24 minRightPlot = 16 // narrowest chart a right legend leaves25)2627// placeLegend makes room for a legend of names in a w by h chart and28// returns the room left for the chart. A legend needs two series or more,29// and space: a bottom one seven rows, a right one a chart still30// minRightPlot wide beside it.31func placeLegend(names []string, pos sheet.ChartLegend, w, h int) (legendBox, int, int) {32 lg := legendBox{pos: sheet.LegendNone}33 if len(names) < 2 {34 return lg, w, h35 }36 switch pos {37 case sheet.LegendBottom:38 if h >= 7 {39 return legendBox{names: names, pos: pos, row: h - 1}, w, h - 140 }41 case sheet.LegendRight:42 nameW := 143 for _, n := range names {44 nameW = max(nameW, ansi.StringWidth(n))45 }46 lw := 2 + min(nameW, legendNameW)47 if w-lw-1 >= minRightPlot {48 return legendBox{names: names, pos: pos, x: w - lw, w: lw}, w - lw - 1, h49 }50 }51 return lg, w, h52}5354func (lg legendBox) draw(g *Grid) {55 switch lg.pos {56 case sheet.LegendBottom:57 g.legend(lg.row, lg.names)58 case sheet.LegendRight:59 g.legendColumn(lg.x, lg.w, lg.names)60 }61}6263// legend draws "■ Rent ■ Food" centered on row y, shortening names to64// fit.65func (g *Grid) legend(y int, names []string) {66 const gap = " "67 for limit := 24; limit >= 1; limit-- {68 items := make([]string, len(names))69 width := 070 for i, n := range names {71 items[i] = "■ " + ansi.Truncate(n, limit, "…")72 width += ansi.StringWidth(items[i])73 }74 width += len(gap) * (len(items) - 1)75 if width > g.W && limit > 1 {76 continue77 }78 x := max((g.W-width)/2, 0)79 for i, it := range items {80 if x+ansi.StringWidth(it) > g.W {81 g.text(max(x, 0), y, "…", Label)82 return83 }84 g.set(x, y, "■", SeriesRole(i))85 g.text(x+2, y, it[len("■ "):], Label)86 x += ansi.StringWidth(it) + len(gap)87 }88 return89 }90}9192// legendColumn draws the legend one series a row from column x, w wide,93// centered down the chart, ending in "… 3 more" when they don't all fit.94func (g *Grid) legendColumn(x, w int, names []string) {95 n := min(len(names), g.H)96 y := (g.H - n) / 297 for i, name := range names {98 if y == g.H-1 && i < len(names)-1 {99 g.text(x, y, ansi.Truncate("… "+strconv.Itoa(len(names)-i)+" more", w, "…"), Label)100 return101 }102 g.set(x, y, "■", SeriesRole(i))103 g.text(x+2, y, ansi.Truncate(name, w-2, "…"), Label)104 y++105 }106}107108func seriesNames(series []sheet.ChartSeries) []string {109 names := make([]string, len(series))110 for i, s := range series {111 names[i] = s.Name112 }113 return names114}