golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/releaseschedule/main.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Releaseschedule generates the release schedule diagram used 6 // on the release schedule wiki. 7 // 8 // When this program is updated, regenerate the SVG and replace the old version 9 // on the Go Release Cycle wiki page 10 // 11 // https://go.dev/s/release 12 // https://golang.org/wiki/Go-Release-Cycle 13 package main 14 15 import ( 16 "fmt" 17 "math" 18 "os" 19 "strings" 20 21 svg "github.com/ajstarks/svgo" 22 ) 23 24 func main() { 25 if err := doMain(); err != nil { 26 panic(err) 27 } 28 } 29 30 func doMain() error { 31 f, err := os.OpenFile("release.svg", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 32 if err != nil { 33 return err 34 } 35 defer f.Close() 36 canvas := svg.New(f) 37 canvas.Start(600, 400) 38 canvas.Style("text/css", 39 `text { 40 fill: black; 41 } 42 line, path { 43 stroke: black; 44 fill: none; 45 } 46 @media (prefers-color-scheme: dark) { 47 text { 48 fill: white; 49 } 50 line, path { 51 stroke: white; 52 } 53 }`) 54 canvas.Translate(300, 200) 55 for i, month := range strings.Split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", " ") { 56 angle := func(midx int) float64 { 57 return (float64(midx) - 3) * 2 * math.Pi / 12 58 } 59 begin, end := angle(i), angle(i+1) 60 61 // Draw a single black wedge of the calendar. 62 path := fmt.Sprintf("M 0,0 L %v,%v A 100,100 0 0 0 %v,%v L 0 0", 63 100*math.Sin(begin), 100*math.Cos(begin), 100*math.Sin(end), 100*math.Cos(end)) 64 canvas.Path(path) 65 66 // Draw the text. Spin it around for readability in the second half. 67 canvas.RotateTranslate(50, 0, angle(i)*360/(2*math.Pi)+20) 68 if i < 6 { 69 canvas.Text(0, 0, month) 70 } else { 71 canvas.Group(`transform="rotate(180)"`, `transform-origin="13 -3"`) 72 canvas.Text(0, 0, month) 73 canvas.Gend() 74 } 75 canvas.Gend() 76 } 77 78 type milestone struct { 79 month, week int 80 name string 81 } 82 milestones := []milestone{ 83 {1, 1, "Planning"}, 84 {1, 3, "General development"}, 85 {5, 4, "Freeze"}, 86 {6, 2, "First RC"}, 87 {8, 2, "Release"}, 88 } 89 for relIdx, relName := range []string{"Summer", "Winter"} { 90 angle := func(m milestone) float64 { 91 return (float64(m.month-1) - 3 + (float64(m.week)-0.5)/4) * 2 * math.Pi / 12 92 } 93 94 // Shift the milestones 6 months for the winter release. 95 milestones := milestones 96 for i := range milestones { 97 milestones[i].month = (milestones[i].month + 6*relIdx) % 12 98 } 99 100 frozen := false 101 for i, m := range milestones { 102 x, y := math.Cos(angle(m)), math.Sin(angle(m)) 103 // Align the text away from the center of the circle. 104 textAnchor := "start" 105 if x < 0 { 106 textAnchor = "end" 107 } 108 // Color the arc depending on the freeze state. 109 if m.name == "Freeze" { 110 frozen = true 111 } 112 color := "green" 113 if frozen { 114 color = "blue" 115 } 116 117 // Center radius of the release arc. 118 arcRadius := float64(120 + 20*relIdx) 119 // Length of the line to the label. Vary a bit to avoid text overlap. 120 lineLength := float64(30 + 5*((i+1)%2)) 121 // Distance from the end of the line to the text. 122 textoff := float64(10) 123 124 // Draw the arc to the next milestone. 125 if i+1 < len(milestones) { 126 nx, ny := math.Cos(angle(milestones[i+1])), math.Sin(angle(milestones[i+1])) 127 canvas.Arc(int(x*arcRadius), int(y*arcRadius), int(arcRadius), int(arcRadius), 0, false, true, int(nx*arcRadius), int(ny*arcRadius), "stroke-width:10; fill:none; stroke: "+color) 128 } 129 // Draw the line from the inner edge of the arc. 130 canvas.Line(int(x*(arcRadius-5)), int(y*(arcRadius-5)), int(x*(arcRadius+lineLength)), int(y*(arcRadius+lineLength))) 131 canvas.Text(int(x*(arcRadius+lineLength+textoff)), int(y*(arcRadius+lineLength+textoff)), relName+": "+m.name, "text-anchor: "+textAnchor) 132 } 133 } 134 canvas.Gend() 135 canvas.End() 136 return f.Close() 137 }