blob: ec8fff43d522e88c1820447ba07dda28d269c3c4 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Releaseschedule generates the release schedule diagram used
// on the release schedule wiki.
//
// When this program is updated, regenerate the SVG and replace the old version
// on the Go Release Cycle wiki page
//
// https://go.dev/s/release
// https://golang.org/wiki/Go-Release-Cycle
package main
import (
"fmt"
"math"
"os"
"strings"
svg "github.com/ajstarks/svgo"
)
func main() {
if err := doMain(); err != nil {
panic(err)
}
}
func doMain() error {
f, err := os.OpenFile("release.svg", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer f.Close()
canvas := svg.New(f)
canvas.Start(600, 400)
canvas.Style("text/css",
`text {
fill: black;
}
line, path {
stroke: black;
fill: none;
}
@media (prefers-color-scheme: dark) {
text {
fill: white;
}
line, path {
stroke: white;
}
}`)
canvas.Translate(300, 200)
for i, month := range strings.Split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", " ") {
angle := func(midx int) float64 {
return (float64(midx) - 3) * 2 * math.Pi / 12
}
begin, end := angle(i), angle(i+1)
// Draw a single black wedge of the calendar.
path := fmt.Sprintf("M 0,0 L %v,%v A 100,100 0 0 0 %v,%v L 0 0",
100*math.Sin(begin), 100*math.Cos(begin), 100*math.Sin(end), 100*math.Cos(end))
canvas.Path(path)
// Draw the text. Spin it around for readability in the second half.
canvas.RotateTranslate(50, 0, angle(i)*360/(2*math.Pi)+20)
if i < 6 {
canvas.Text(0, 0, month)
} else {
canvas.Group(`transform="rotate(180)"`, `transform-origin="13 -3"`)
canvas.Text(0, 0, month)
canvas.Gend()
}
canvas.Gend()
}
type milestone struct {
month, week int
name string
}
milestones := []milestone{
{1, 1, "Planning"},
{1, 3, "General development"},
{5, 4, "Freeze"},
{6, 2, "First RC"},
{8, 2, "Release"},
}
for relIdx, relName := range []string{"Summer", "Winter"} {
angle := func(m milestone) float64 {
return (float64(m.month-1) - 3 + (float64(m.week)-0.5)/4) * 2 * math.Pi / 12
}
// Shift the milestones 6 months for the winter release.
milestones := milestones
for i := range milestones {
milestones[i].month = (milestones[i].month + 6*relIdx) % 12
}
frozen := false
for i, m := range milestones {
x, y := math.Cos(angle(m)), math.Sin(angle(m))
// Align the text away from the center of the circle.
textAnchor := "start"
if x < 0 {
textAnchor = "end"
}
// Color the arc depending on the freeze state.
if m.name == "Freeze" {
frozen = true
}
color := "green"
if frozen {
color = "blue"
}
// Center radius of the release arc.
arcRadius := float64(120 + 20*relIdx)
// Length of the line to the label. Vary a bit to avoid text overlap.
lineLength := float64(30 + 5*((i+1)%2))
// Distance from the end of the line to the text.
textoff := float64(10)
// Draw the arc to the next milestone.
if i+1 < len(milestones) {
nx, ny := math.Cos(angle(milestones[i+1])), math.Sin(angle(milestones[i+1]))
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)
}
// Draw the line from the inner edge of the arc.
canvas.Line(int(x*(arcRadius-5)), int(y*(arcRadius-5)), int(x*(arcRadius+lineLength)), int(y*(arcRadius+lineLength)))
canvas.Text(int(x*(arcRadius+lineLength+textoff)), int(y*(arcRadius+lineLength+textoff)), relName+": "+m.name, "text-anchor: "+textAnchor)
}
}
canvas.Gend()
canvas.End()
return f.Close()
}