Search Apps Documentation Source Content File Folder Download Copy Actions Download

charts.gno

3.51 Kb ยท 123 lines
  1// Charts is a package that provides functions to generate ASCII charts in markdown format.
  2// It includes functions for line charts, bar charts, and column charts.
  3//
  4// Both line and column charts are implemented in a way that offers normalization of data;
  5// In short terms precision of the chart can be adjusted by changing the number of points or columns.
  6// Line chart offers a way to adjust the spacing between points as well, which allows for more control over the chart.
  7//
  8// e.g. Let's say there is a data set of a value of dollar per day in the year of 2025 in a slice of floats.
  9// If those values are passed to the `GenerateLineChart` in this manner:
 10// `GenerateLineChart(values, 12, 5, "Dollar per day", "Days", "Dollars")`
 11// The chart will be normalized to 12 points, with 5 characters of spacing between each point, but more importantly,
 12// these points will represent a rough monthly average of the dollar value (months are not of equal length, so this is not an exact science).
 13
 14package charts
 15
 16import (
 17	"strings"
 18
 19	"gno.land/p/nt/ufmt"
 20)
 21
 22const height = 15
 23
 24// normalizeData reduces the number of data points to maxColumns by averaging
 25// Returns nil if values is empty or maxColumns is zero or negative
 26func normalizeData(values []float64, maxColumns int) []float64 {
 27	if len(values) == 0 || maxColumns <= 0 {
 28		return nil
 29	}
 30
 31	if maxColumns >= len(values) {
 32		result := make([]float64, len(values))
 33		copy(result, values)
 34		return result
 35	}
 36
 37	result := make([]float64, maxColumns)
 38	groupSize := float64(len(values)) / float64(maxColumns)
 39
 40	for i := 0; i < maxColumns; i++ {
 41		start := int(float64(i) * groupSize)
 42		end := int(float64(i+1) * groupSize)
 43		if end > len(values) {
 44			end = len(values)
 45		}
 46
 47		sum := 0.0
 48		count := end - start
 49		for j := start; j < end; j++ {
 50			sum += values[j]
 51		}
 52		if count > 0 {
 53			result[i] = sum / float64(count)
 54		}
 55	}
 56
 57	return result
 58}
 59
 60// findMaxValue returns the maximum value in a slice of float64
 61func findMaxValue(values []float64) float64 {
 62	if len(values) == 0 {
 63		return 0
 64	}
 65
 66	maxVal := values[0]
 67	for _, v := range values[1:] {
 68		if v > maxVal {
 69			maxVal = v
 70		}
 71	}
 72	return maxVal
 73}
 74
 75// calculateMaxValueStringLength returns the maximum string length when formatting values
 76func calculateMaxValueStringLength(values []float64) int {
 77	maxValStrLen := 0
 78	for _, v := range values {
 79		valStr := ufmt.Sprintf("%.2f", v)
 80		if len(valStr) > maxValStrLen {
 81			maxValStrLen = len(valStr)
 82		}
 83	}
 84	return maxValStrLen
 85}
 86
 87// formatChartHeader formats the chart header with title and markdown code block
 88func formatChartHeader(title string) string {
 89	output := ""
 90	if title != "" {
 91		output += "## " + title + "\n"
 92	}
 93	return output
 94}
 95
 96// formatYAxisTitle adds the y-axis title to the chart
 97func formatYAxisTitle(yAxisTitle string) string {
 98	if yAxisTitle != "" {
 99		return yAxisTitle + "\n\n"
100	}
101	return ""
102}
103
104// formatXAxisTitle adds the x-axis title to the chart
105func formatXAxisTitle(xAxisTitle string, width int) string {
106	if xAxisTitle != "" {
107		padding := strings.Repeat(" ", width/2-len(xAxisTitle)/2)
108		return "       " + padding + xAxisTitle + "\n"
109	}
110	return ""
111}
112
113// formatYAxisLabel formats a y-axis label with proper padding
114func formatYAxisLabel(value float64, scale float64, yAxisWidth int) string {
115	yValue := ufmt.Sprintf("%.2f", value/scale)
116	padding := strings.Repeat(" ", yAxisWidth-len(yValue))
117	return ufmt.Sprintf("%s%s | ", padding, yValue)
118}
119
120// formatYAxisSpace returns spacing for y-axis when no label is needed
121func formatYAxisSpace(yAxisWidth int) string {
122	return strings.Repeat(" ", yAxisWidth) + " | "
123}