package renderer import ( "fmt" "strings" "github.com/picoclaw/chart/internal/types" ) type TextRenderer struct { width int height int } func NewTextRenderer() *TextRenderer { return &TextRenderer{ width: 60, height: 15, } } func (r *TextRenderer) Render(chart *types.Chart) ([]byte, error) { var sb strings.Builder if chart.Title != "" { sb.WriteString(fmt.Sprintf("[ Chart: %s ]\n", chart.Title)) } if len(chart.Data.Datasets) == 0 { sb.WriteString("No data available.\n") return []byte(sb.String()), nil } switch chart.Type { case types.ChartTypePie, types.ChartTypeDonut: r.renderPieChart(&sb, &chart.Data) case types.ChartTypeBubble: r.renderBubbleChart(&sb, &chart.Data) case types.ChartTypeMixed: r.renderMixedChart(&sb, &chart.Data) case types.ChartTypePolar, types.ChartTypeRadar: r.renderRadarLikeChart(&sb, &chart.Data) default: r.renderBarLikeChart(&sb, &chart.Data) } return []byte(sb.String()), nil } func (r *TextRenderer) renderPieChart(sb *strings.Builder, data *types.ChartData) { dataset := data.Datasets[0] values := dataset.Values labels := data.Labels total := 0.0 for _, v := range values { total += v } for i, v := range values { label := fmt.Sprintf("Item %d", i+1) if i < len(labels) { label = labels[i] } percentage := v / total * 100 barLen := int(percentage / 4) if barLen > 12 { barLen = 12 } bar := strings.Repeat("#", barLen) sb.WriteString(fmt.Sprintf("%-12s |%s [%.1f%%]\n", label, bar, percentage)) } } func (r *TextRenderer) renderBubbleChart(sb *strings.Builder, data *types.ChartData) { dataset := data.Datasets[0] labels := data.Labels if len(labels) == 0 { labels = make([]string, len(dataset.Values)) for i := range labels { labels[i] = fmt.Sprintf("X%d", i+1) } } maxValue := dataset.Values[0] for _, v := range dataset.Values { if v > maxValue { maxValue = v } } for i, value := range dataset.Values { label := labels[i] bubbleSize := 2 + int((value/maxValue)*5) bubble := strings.Repeat("o", bubbleSize) sb.WriteString(fmt.Sprintf("%-8s |%s %.2f\n", label, bubble, value)) } } func (r *TextRenderer) renderMixedChart(sb *strings.Builder, data *types.ChartData) { dataset := data.Datasets[0] labels := data.Labels if len(labels) == 0 { labels = make([]string, len(dataset.Values)) for i := range labels { labels[i] = fmt.Sprintf("X%d", i+1) } } maxValue := dataset.Values[0] for _, v := range dataset.Values { if v > maxValue { maxValue = v } } for i, value := range dataset.Values { label := labels[i] barLen := int((value / maxValue) * float64(r.width-20)) if barLen < 1 { barLen = 1 } bar := strings.Repeat("#", barLen) sb.WriteString(fmt.Sprintf("%-8s |%s %.2f [mixed]\n", label, bar, value)) } } func (r *TextRenderer) renderRadarLikeChart(sb *strings.Builder, data *types.ChartData) { dataset := data.Datasets[0] values := dataset.Values labels := data.Labels maxValue := values[0] for _, v := range values { if v > maxValue { maxValue = v } } sb.WriteString(fmt.Sprintf("[ Radar/Polar Chart: %d dimensions ]\n", len(values))) for i, v := range values { label := fmt.Sprintf("Dim %d", i+1) if i < len(labels) { label = labels[i] } barLen := int((v / maxValue) * float64(r.width-25)) if barLen < 1 { barLen = 1 } bar := strings.Repeat("#", barLen) sb.WriteString(fmt.Sprintf("%-12s |%s %.2f\n", label, bar, v)) } } func (r *TextRenderer) renderBarLikeChart(sb *strings.Builder, data *types.ChartData) { dataset := data.Datasets[0] labels := data.Labels if len(labels) == 0 { labels = make([]string, len(dataset.Values)) for i := range labels { labels[i] = fmt.Sprintf("X%d", i+1) } } maxValue := dataset.Values[0] for _, v := range dataset.Values { if v > maxValue { maxValue = v } } for i, value := range dataset.Values { label := labels[i] if i >= len(labels) { label = fmt.Sprintf("X%d", i+1) } barLen := int((value / maxValue) * float64(r.width-20)) if barLen < 1 { barLen = 1 } bar := strings.Repeat("#", barLen) sb.WriteString(fmt.Sprintf("%-8s |%s %.2f\n", label, bar, value)) } }