Added minute hand

This commit is contained in:
mitch 2022-01-20 00:45:27 -05:00
parent 9a21ca4d26
commit 03e76a5a25
4 changed files with 135 additions and 40 deletions

View File

@ -14,9 +14,21 @@ func secondsInRadians(t time.Time) float64 {
return math.Pi / (30 / (float64(t.Second()))) return math.Pi / (30 / (float64(t.Second())))
} }
func secondHandPoint(t time.Time) Point { func minutesInRadians(t time.Time) float64 {
angle := secondsInRadians(t) return secondsInRadians(t)/60 + math.Pi/(30/(float64(t.Minute())))
}
func angleToPoint(angle float64) Point {
x := math.Sin(angle) x := math.Sin(angle)
y := math.Cos(angle) y := math.Cos(angle)
return Point{x, y} return Point{x, y}
} }
func secondHandPoint(t time.Time) Point {
return angleToPoint(secondsInRadians(t))
}
func minuteHandPoint(t time.Time) Point {
return angleToPoint(minutesInRadians(t))
}

View File

@ -7,49 +7,84 @@ import (
"time" "time"
) )
type Svg struct { type SVG struct {
XMLName xml.Name `xml:"svg"` XMLName xml.Name `xml:"svg"`
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"` Xmlns string `xml:"xmlns,attr"`
Width string `xml:"width,attr"` Width string `xml:"width,attr"`
Height string `xml:"height,attr"` Height string `xml:"height,attr"`
ViewBox string `xml:"viewBox,attr"` ViewBox string `xml:"viewBox,attr"`
Version string `xml:"version,attr"` Version string `xml:"version,attr"`
Circle struct { Circle Circle `xml:"circle"`
Text string `xml:",chardata"` Line []Line `xml:"line"`
Cx string `xml:"cx,attr"` }
Cy string `xml:"cy,attr"`
R string `xml:"r,attr"` type Circle struct {
Style string `xml:"style,attr"` Cx float64 `xml:"cx,attr"`
} `xml:"circle"` Cy float64 `xml:"cy,attr"`
Line []struct { R float64 `xml:"r,attr"`
Text string `xml:",chardata"` }
X1 string `xml:"x1,attr"`
Y1 string `xml:"y1,attr"` type Line struct {
X2 string `xml:"x2,attr"` X1 float64 `xml:"x1,attr"`
Y2 string `xml:"y2,attr"` Y1 float64 `xml:"y1,attr"`
Style string `xml:"style,attr"` X2 float64 `xml:"x2,attr"`
} `xml:"line"` Y2 float64 `xml:"y2,attr"`
}
func containsLine(l Line, ls []Line) bool {
for _, line := range ls {
if line == l {
return true
}
}
return false
} }
func TestSVGWriterAtMidnight(t *testing.T) { func TestSVGWriterAtMidnight(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC) cases := []struct {
b := bytes.Buffer{} time time.Time
line Line
SVGWriter(&b, tm) }{
{simpleTime(0, 0, 0),
svg := Svg{} Line{150, 150, 150, 60}},
{simpleTime(0, 0, 30),
xml.Unmarshal(b.Bytes(), &svg) Line{150, 150, 150, 240}},
x2 := "150.000"
y2 := "60.000"
for _, line := range svg.Line {
if line.X2 == x2 && line.Y2 == y2 {
return
}
} }
t.Errorf("Expected to find the second hand with x2 of %v and y2 of %v, in the SVG output %v", x2, y2, b.String()) for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
b := bytes.Buffer{}
SVGWriter(&b, c.time)
svg := SVG{}
xml.Unmarshal(b.Bytes(), &svg)
if !containsLine(c.line, svg.Line) {
t.Errorf("Expected to find the second hand line %+v, in the SVG lines %+v", c.line, svg.Line)
}
})
}
}
func TestSVGWriterMinuteHand(t *testing.T) {
cases := []struct {
time time.Time
line Line
}{
{simpleTime(0, 0, 0),
Line{150, 150, 150, 70}},
}
for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
b := bytes.Buffer{}
SVGWriter(&b, c.time)
svg := SVG{}
xml.Unmarshal(b.Bytes(), &svg)
if !containsLine(c.line, svg.Line) {
t.Errorf("Expected to find the second hand line %+v, in the SVG lines %+v", c.line, svg.Line)
}
})
}
} }

View File

@ -27,6 +27,25 @@ func TestSecondsInRadians(t *testing.T) {
} }
} }
func TestMinutesInRadians(t *testing.T) {
cases := []struct {
time time.Time
angle float64
}{
{simpleTime(0, 30, 0), math.Pi},
{simpleTime(0, 0, 7), 7 * (math.Pi / (30 * 60))},
}
for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := minutesInRadians(c.time)
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}
func TestSecondHandPoint(t *testing.T) { func TestSecondHandPoint(t *testing.T) {
cases := []struct { cases := []struct {
time time.Time time time.Time
@ -46,6 +65,25 @@ func TestSecondHandPoint(t *testing.T) {
} }
} }
func TestMinuteHandPoint(t *testing.T) {
cases := []struct {
time time.Time
point Point
}{
{simpleTime(0, 30, 0), Point{0, -1}},
{simpleTime(0, 45, 0), Point{-1, 0}},
}
for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := minuteHandPoint(c.time)
if !roughlyEqualPoint(got, c.point) {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}
func roughlyEqualFloat64(a, b float64) bool { func roughlyEqualFloat64(a, b float64) bool {
const equalityThreshold = 1e-7 const equalityThreshold = 1e-7
return math.Abs(a-b) < equalityThreshold return math.Abs(a-b) < equalityThreshold

View File

@ -7,6 +7,7 @@ import (
) )
const secondHandLength = 90 const secondHandLength = 90
const minuteHandLength = 80
const clockCentreX = 150 const clockCentreX = 150
const clockCentreY = 150 const clockCentreY = 150
@ -27,13 +28,22 @@ func SVGWriter(w io.Writer, t time.Time) {
io.WriteString(w, svgStart) io.WriteString(w, svgStart)
io.WriteString(w, bezel) io.WriteString(w, bezel)
SecondHand(w, t) SecondHand(w, t)
MinuteHand(w, t)
io.WriteString(w, svgEnd) io.WriteString(w, svgEnd)
} }
func makeHand(p Point, length float64) Point {
p = Point{p.X * length, p.Y * length}
p = Point{p.X, -p.Y}
return Point{p.X + clockCentreX, p.Y + clockCentreY}
}
func SecondHand(w io.Writer, t time.Time) { func SecondHand(w io.Writer, t time.Time) {
p := secondHandPoint(t) p := makeHand(secondHandPoint(t), secondHandLength)
p = Point{p.X * secondHandLength, p.Y * secondHandLength} //scale
p = Point{p.X, -p.Y} //flip
p = Point{p.X + clockCentreX, p.Y + clockCentreY} //translate
fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#f00;stroke-width:3px;"/>`, p.X, p.Y) fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#f00;stroke-width:3px;"/>`, p.X, p.Y)
} }
func MinuteHand(w io.Writer, t time.Time) {
p := makeHand(minuteHandPoint(t), minuteHandLength)
fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#000;stroke-width:3px;"/>`, p.X, p.Y)
}