line_chart.gno
3.59 Kb ยท 142 lines
1package charts
2
3import (
4 "strings"
5
6 "gno.land/p/nt/ufmt"
7)
8
9// GenerateLineChart creates an ASCII line chart in markdown format
10// values: slice of float values to chart
11// maxPoints: maximum number of points to display (will normalize if exceeded)
12// pointSpacing: horizontal spacing between points (in characters)
13// title: chart title
14// xAxisTitle: title for the x-axis
15// yAxisTitle: title for the y-axis
16// Returns a markdown string representing the chart
17func GenerateLineChart(values []float64, maxPoints int, pointSpacing int, title, xAxisTitle, yAxisTitle string) string {
18 if len(values) == 0 {
19 return "no data to display"
20 }
21
22 if maxPoints <= 0 {
23 return "maxPoints must be greater than 0"
24 }
25
26 maxVal := findMaxValue(values)
27 maxValStrLen := calculateMaxValueStringLength(values)
28
29 minSpacing := maxValStrLen + 2
30 if pointSpacing < minSpacing {
31 return ufmt.Sprintf("pointSpacing must be at least %d to fit value labels", minSpacing)
32 }
33 if pointSpacing > 25 {
34 return ufmt.Sprintf("pointSpacing must be between %d and 25", minSpacing)
35 }
36
37 displayValues := values
38 if len(values) > maxPoints {
39 displayValues = normalizeData(values, maxPoints)
40 }
41
42 scale := float64(height) / maxVal
43
44 gridWidth := (len(displayValues)-1)*pointSpacing + len(displayValues)
45 grid := make([][]rune, height+1)
46 for i := range grid {
47 grid[i] = make([]rune, gridWidth)
48 for j := range grid[i] {
49 grid[i][j] = ' '
50 }
51 }
52
53 spacing := pointSpacing + 1
54 for i := 0; i < len(displayValues); i++ {
55 x := i * spacing
56 y := int(displayValues[i] * scale)
57 if y > height {
58 y = height
59 }
60 grid[height-y][x] = '*'
61
62 if i < len(displayValues)-1 {
63 nextX := (i + 1) * spacing
64 nextY := int(displayValues[i+1] * scale)
65 if nextY > height {
66 nextY = height
67 }
68 dx := nextX - x
69 dy := nextY - y
70 prevInterpY := y
71 for step := 1; step < dx; step++ {
72 interpY := y + (dy*step)/dx
73 startY := prevInterpY
74 endY := interpY
75 if startY > endY {
76 startY, endY = endY, startY
77 }
78 for fillY := startY; fillY <= endY; fillY++ {
79 grid[height-fillY][x+step] = '*'
80 }
81 prevInterpY = interpY
82 }
83 }
84 }
85
86 output := "\n"
87 output += formatChartHeader(title)
88 output += "\n```\n"
89 output += formatYAxisTitle(yAxisTitle)
90
91 maxYValue := ufmt.Sprintf("%.2f", maxVal)
92 yAxisWidth := len(maxYValue)
93
94 for row := 0; row <= height; row++ {
95 gridRow := grid[row]
96 gridY := height - row
97 if gridY%3 == 0 {
98 output += formatYAxisLabel(float64(gridY), scale, yAxisWidth)
99 } else {
100 output += formatYAxisSpace(yAxisWidth)
101 }
102 output += string(gridRow)
103 output += "\n"
104 }
105
106 output += strings.Repeat(" ", yAxisWidth) + " +"
107 output += strings.Repeat("-", gridWidth+3)
108 output += "\n"
109
110 output += formatValueLabelsLineChart(displayValues, pointSpacing, yAxisWidth)
111
112 if xAxisTitle != "" {
113 output += formatXAxisTitle(xAxisTitle, gridWidth)
114 }
115
116 output += "```"
117 return output
118}
119
120// formatValueLabelsLineChart formats the value labels for the x-axis for line charts
121func formatValueLabelsLineChart(displayValues []float64, pointSpacing int, yAxisWidth int) string {
122 output := strings.Repeat(" ", yAxisWidth+2)
123 spacing := pointSpacing + 1
124 for i := 0; i < len(displayValues); i++ {
125 x := i * spacing
126 valStr := ufmt.Sprintf("%.2f", displayValues[i])
127 labelPos := x - len(valStr)/2
128 if labelPos < 0 {
129 labelPos = 0
130 }
131 if i == 0 {
132 output += strings.Repeat(" ", labelPos)
133 } else {
134 prevX := (i - 1) * spacing
135 prevValStr := ufmt.Sprintf("%.2f", displayValues[i-1])
136 prevLabelEnd := prevX - len(prevValStr)/2 + len(prevValStr)
137 output += strings.Repeat(" ", labelPos-prevLabelEnd)
138 }
139 output += valStr
140 }
141 return output + "\n"
142}