Search Apps Documentation Source Content File Folder Download Copy Actions Download

piechart.gno

2.92 Kb ยท 115 lines
  1// Package piechart provides functionality to render a pie chart as an SVG image.
  2// It takes a list of PieSlice objects, each representing a slice of the pie with a value,
  3// color, and label, and generates an SVG representation of the pie chart.
  4package piechart
  5
  6import (
  7	"math"
  8
  9	"gno.land/p/demo/svg"
 10	"gno.land/p/nt/ufmt"
 11	"gno.land/p/sunspirit/md"
 12)
 13
 14type PieSlice struct {
 15	Value float64
 16	Color string
 17	Label string
 18}
 19
 20// Render creates an SVG pie chart from given slices (value, color and label).
 21// It returns an img svg markup as a string, including a markdown header if a non-empty title is provided.
 22func Render(slices []PieSlice, title string) string {
 23	// Validate input slices length
 24	if len(slices) == 0 {
 25		return "\npiechart fails: no data provided"
 26	}
 27
 28	const (
 29		canvasWidth  = 500
 30		canvasHeight = 200
 31		centerX      = 100.0
 32		centerY      = 100.0
 33		radius       = 80.0
 34		legendX      = 210
 35		legendStartY = 30
 36		lineHeight   = 26
 37		squareSize   = 16
 38		fontSize     = 16
 39	)
 40
 41	canvas := svg.NewCanvas(canvasWidth, canvasHeight)
 42
 43	// Sum all values to compute slices proportions
 44	var total float64
 45	for _, s := range slices {
 46		total += s.Value
 47	}
 48
 49	// Draw pie slices and legend in one pass
 50	startAngle := -math.Pi / 2
 51	for i, s := range slices {
 52		if s.Value > 0 {
 53			// --- PIE SLICE ---
 54			// Calculate angle span for current slice
 55			angle := (s.Value / total) * 2 * math.Pi
 56			endAngle := startAngle + angle
 57
 58			// Compute start and end points on the circle circumference
 59			cosStart, sinStart := math.Cos(startAngle), math.Sin(startAngle)
 60			cosEnd, sinEnd := math.Cos(endAngle), math.Sin(endAngle)
 61			x1 := centerX + radius*cosStart
 62			y1 := centerY + radius*sinStart
 63			x2 := centerX + radius*cosEnd
 64			y2 := centerY + radius*sinEnd
 65
 66			// Determine if the arc should be a large arc (> 180 degrees) (Arc direction)
 67			largeArcFlag := 0
 68			if angle > math.Pi {
 69				largeArcFlag = 1
 70			}
 71
 72			// Build the SVG path for the pie slice
 73			path := ufmt.Sprintf(
 74				"M%.2f,%.2f L%.2f,%.2f A%.2f,%.2f 0 %d 1 %.2f,%.2f Z",
 75				centerX, centerY, x1, y1, radius, radius, largeArcFlag, x2, y2,
 76			)
 77
 78			// Colored slice
 79			canvas.Append(svg.Path{
 80				D:    path,
 81				Fill: s.Color,
 82			})
 83
 84			startAngle = endAngle
 85		}
 86
 87		// --- LEGEND ---
 88		y := legendStartY + i*lineHeight
 89		// Colored square representing slice color
 90		canvas.Append(svg.Rectangle{
 91			X:      legendX,
 92			Y:      y - squareSize/2,
 93			Width:  squareSize,
 94			Height: squareSize,
 95			Fill:   s.Color,
 96		})
 97
 98		// Legend text showing label, value and percentage
 99		text := ufmt.Sprintf("%s: %.0f (%.1f%%)", s.Label, s.Value, s.Value*100/total)
100		canvas.Append(svg.Text{
101			X:    legendX + squareSize + 8,
102			Y:    y + fontSize/3,
103			Text: text,
104			Fill: "#54595D",
105			Attr: svg.BaseAttrs{
106				Style: ufmt.Sprintf("font-family:'Inter var',sans-serif;font-size:%dpx;", fontSize),
107			},
108		})
109	}
110
111	if title == "" {
112		return canvas.Render("Pie Chart")
113	}
114	return md.H2(title) + canvas.Render("Pie Chart "+title)
115}