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}