apps/cellar/svg.go 4.2 K raw
1
package main
2
3
import (
4
	"fmt"
5
	"math"
6
	"strings"
7
)
8
9
func buildPentagonSVG(sweetness, acidity, tannin, alcohol, body int, size float64, showLabels bool) string {
10
	cx, cy := size/2.0, size/2.0
11
	margin := 5.0
12
	if showLabels {
13
		margin = 30.0
14
	}
15
	r := size/2.0 - margin
16
	scores := []int{sweetness, acidity, tannin, alcohol, body}
17
	labels := []string{"Sweetness", "Acidity", "Tannin", "Alcohol", "Body"}
18
	angles := make([]float64, 5)
19
	for i := range angles {
20
		angles[i] = (-90.0 + 72.0*float64(i)) * math.Pi / 180.0
21
	}
22
23
	var b strings.Builder
24
	fmt.Fprintf(&b, `<svg viewBox="0 0 %g %g" width="100%%" xmlns="http://www.w3.org/2000/svg">`, size, size)
25
26
	for _, pct := range []float64{0.2, 0.4, 0.6, 0.8} {
27
		parts := make([]string, 5)
28
		for i, a := range angles {
29
			parts[i] = fmt.Sprintf("%.1f,%.1f", cx+r*pct*math.Cos(a), cy+r*pct*math.Sin(a))
30
		}
31
		fmt.Fprintf(&b, `<polygon points="%s" fill="none" stroke="white" stroke-opacity="0.12" stroke-width="0.75"/>`, strings.Join(parts, " "))
32
	}
33
34
	outline := make([]string, 5)
35
	for i, a := range angles {
36
		outline[i] = fmt.Sprintf("%.1f,%.1f", cx+r*math.Cos(a), cy+r*math.Sin(a))
37
	}
38
	fmt.Fprintf(&b, `<polygon points="%s" fill="none" stroke="white" stroke-opacity="0.25" stroke-width="1"/>`, strings.Join(outline, " "))
39
40
	for _, a := range angles {
41
		fmt.Fprintf(&b, `<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="white" stroke-opacity="0.12" stroke-width="0.75"/>`,
42
			cx, cy, cx+r*math.Cos(a), cy+r*math.Sin(a))
43
	}
44
45
	dataPoints := make([][2]float64, 5)
46
	for i, s := range scores {
47
		d := float64(s) / 5.0 * r
48
		dataPoints[i] = [2]float64{cx + d*math.Cos(angles[i]), cy + d*math.Sin(angles[i])}
49
	}
50
	parts := make([]string, 5)
51
	for i, p := range dataPoints {
52
		parts[i] = fmt.Sprintf("%.1f,%.1f", p[0], p[1])
53
	}
54
	fmt.Fprintf(&b, `<polygon points="%s" fill="white" fill-opacity="0.08" stroke="white" stroke-width="1.5"/>`, strings.Join(parts, " "))
55
56
	for _, p := range dataPoints {
57
		fmt.Fprintf(&b, `<circle cx="%.1f" cy="%.1f" r="2.5" fill="white"/>`, p[0], p[1])
58
	}
59
60
	if showLabels {
61
		for i, label := range labels {
62
			a := angles[i]
63
			labelDist := r + 18.0
64
			lx := cx + labelDist*math.Cos(a)
65
			ly := cy + labelDist*math.Sin(a) + 3.5
66
			fmt.Fprintf(&b, `<text x="%.1f" y="%.1f" fill="white" fill-opacity="0.5" font-size="9" font-family="Commit Mono, monospace" text-anchor="middle">%s</text>`, lx, ly, label)
67
		}
68
	}
69
	b.WriteString("</svg>")
70
	return b.String()
71
}
72
73
func buildBarsSVG(clarity, colorIntensity, aromaIntensity, noseComplexity int, width float64) string {
74
	barHeight := 4.0
75
	rowHeight := 22.0
76
	sectionGap := 14.0
77
	labelWidth := 100.0
78
	trackLeft := labelWidth + 4.0
79
	trackWidth := width - trackLeft - 10.0
80
	headerSize := 9.0
81
82
	type attr struct {
83
		Label string
84
		Score int
85
	}
86
	sections := []struct {
87
		Name  string
88
		Attrs []attr
89
	}{
90
		{"Appearance", []attr{{"Clarity", clarity}, {"Intensity", colorIntensity}}},
91
		{"Nose", []attr{{"Aroma", aromaIntensity}, {"Complexity", noseComplexity}}},
92
	}
93
	totalRows := 0
94
	for _, s := range sections {
95
		totalRows += len(s.Attrs)
96
	}
97
	totalHeight := float64(len(sections))*(headerSize+8.0) + float64(totalRows)*rowHeight + sectionGap
98
99
	var b strings.Builder
100
	fmt.Fprintf(&b, `<svg viewBox="0 0 %g %g" width="100%%" xmlns="http://www.w3.org/2000/svg">`, width, totalHeight)
101
	y := 4.0
102
	for si, sec := range sections {
103
		if si > 0 {
104
			y += sectionGap
105
		}
106
		fmt.Fprintf(&b, `<text x="0" y="%.1f" fill="white" fill-opacity="0.4" font-size="%g" font-family="Commit Mono, monospace" text-transform="uppercase" letter-spacing="1">%s</text>`,
107
			y+headerSize, headerSize, sec.Name)
108
		y += headerSize + 8.0
109
		for _, a := range sec.Attrs {
110
			barY := y + (rowHeight-barHeight)/2.0
111
			fillW := float64(a.Score) / 5.0 * trackWidth
112
			fmt.Fprintf(&b, `<text x="0" y="%.1f" fill="white" fill-opacity="0.5" font-size="9" font-family="Commit Mono, monospace">%s</text>`,
113
				y+rowHeight/2.0+3.0, a.Label)
114
			fmt.Fprintf(&b, `<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" rx="2" fill="white" fill-opacity="0.08"/>`,
115
				trackLeft, barY, trackWidth, barHeight)
116
			if fillW > 0 {
117
				fmt.Fprintf(&b, `<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" rx="2" fill="white" fill-opacity="0.6"/>`,
118
					trackLeft, barY, fillW, barHeight)
119
			}
120
			y += rowHeight
121
		}
122
	}
123
	b.WriteString("</svg>")
124
	return b.String()
125
}