Added minute hand
This commit is contained in:
parent
9a21ca4d26
commit
03e76a5a25
16
clockface.go
16
clockface.go
@ -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))
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
18
svgWriter.go
18
svgWriter.go
@ -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)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user