From a555d4ff9c4c94cf2c24f7257bf39e540ded7524 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Fri, 22 Apr 2016 10:39:07 +0200 Subject: [PATCH 1/3] plotter: use a fixed time zone for tests Fixes #276 --- plotter/timeseries_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plotter/timeseries_test.go b/plotter/timeseries_test.go index 9954874d..c4ed9c38 100644 --- a/plotter/timeseries_test.go +++ b/plotter/timeseries_test.go @@ -16,6 +16,10 @@ import ( "github.com/gonum/plot/vg/draw" ) +var ( + location = time.FixedZone("Europe/Paris", 42) +) + // Example_timeSeries draws a time series. func Example_timeSeries() { // randomPoints returns some random x, y points @@ -31,7 +35,7 @@ func Example_timeSeries() { ) pts := make(XYs, n) for i := range pts { - date := time.Date(2007+i, month, day, hour, min, sec, nsec, time.UTC).Unix() + date := time.Date(2007+i, month, day, hour, min, sec, nsec, location).Unix() pts[i].X = float64(date) pts[i].Y = float64(pts[i].X+10*rand.Float64()) * 1e-9 } From a26d2d9de50b1f616ce091be940c4327e2a61b5d Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Fri, 22 Apr 2016 23:08:52 +0200 Subject: [PATCH 2/3] axis: make float64-to-time conversion customizable --- axis.go | 11 ++++++++++- plotter/timeseries_test.go | 13 +++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/axis.go b/axis.go index cbac5ac4..e57c3794 100644 --- a/axis.go +++ b/axis.go @@ -461,6 +461,10 @@ type UnixTimeTicks struct { // Format is the textual representation of the time value. // If empty, time.RFC3339 will be used Format string + + // Convert takes a float64 value and converts it into a time.Time. + // If nil, time.Unix will be used. + Convert func(float64) time.Time } var _ Ticker = UnixTimeTicks{} @@ -473,6 +477,11 @@ func (utt UnixTimeTicks) Ticks(min, max float64) []Tick { if utt.Format == "" { utt.Format = time.RFC3339 } + if utt.Convert == nil { + utt.Convert = func(v float64) time.Time { + return time.Unix(int64(v), 0) + } + } ticks := utt.Ticker.Ticks(min, max) for i := range ticks { @@ -480,7 +489,7 @@ func (utt UnixTimeTicks) Ticks(min, max float64) []Tick { if tick.Label == "" { continue } - t := time.Unix(int64(tick.Value), 0) + t := utt.Convert(tick.Value) tick.Label = t.Format(utt.Format) } return ticks diff --git a/plotter/timeseries_test.go b/plotter/timeseries_test.go index c4ed9c38..ebf96213 100644 --- a/plotter/timeseries_test.go +++ b/plotter/timeseries_test.go @@ -16,10 +16,6 @@ import ( "github.com/gonum/plot/vg/draw" ) -var ( - location = time.FixedZone("Europe/Paris", 42) -) - // Example_timeSeries draws a time series. func Example_timeSeries() { // randomPoints returns some random x, y points @@ -35,7 +31,7 @@ func Example_timeSeries() { ) pts := make(XYs, n) for i := range pts { - date := time.Date(2007+i, month, day, hour, min, sec, nsec, location).Unix() + date := time.Date(2007+i, month, day, hour, min, sec, nsec, time.UTC).Unix() pts[i].X = float64(date) pts[i].Y = float64(pts[i].X+10*rand.Float64()) * 1e-9 } @@ -50,7 +46,12 @@ func Example_timeSeries() { log.Panic(err) } p.Title.Text = "Time Series" - p.X.Tick.Marker = plot.UnixTimeTicks{Format: "2006-01-02"} + p.X.Tick.Marker = plot.UnixTimeTicks{ + Format: "2006-01-02", + Convert: func(v float64) time.Time { + return time.Unix(int64(v), 0).UTC() + }, + } p.Y.Label.Text = "Number of Gophers\n(Billions)" p.Add(NewGrid()) From a4fa625d7749b949bb8b21a0c9e8ac72128fcc07 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Wed, 25 May 2016 01:33:30 -0700 Subject: [PATCH 3/3] all: add UnixTimeConverter and TimeFloatConverter This CL introduces the TimeFloatConverter interface that describes how a time.Time value is converted to a float64 (and back.) A default TimeFloatConverter (used by TimeTicks) is also provided in the shape of UnixTimeConverter. Test updated to be resilient against timezones (tested with 'Europe/Paris', 'America/Los_Angeles' and 'Australia/Sydney') Fixes #276. --- axis.go | 58 +++++++++++++++++-------- plotter/testdata/timeseries_golden.png | Bin 15318 -> 17018 bytes plotter/timeseries_test.go | 18 ++++---- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/axis.go b/axis.go index e57c3794..88e7370a 100644 --- a/axis.go +++ b/axis.go @@ -451,9 +451,9 @@ func (ts ConstantTicks) Ticks(float64, float64) []Tick { return ts } -// UnixTimeTicks is suitable for axes representing time values. -// UnixTimeTicks expects values in Unix time seconds. -type UnixTimeTicks struct { +// TimeTicks is suitable for axes representing time values. +// By default, TimeTicks expects values in (UTC) Unix time seconds. +type TimeTicks struct { // Ticker is used to generate a set of ticks. // If nil, DefaultTicks will be used. Ticker Ticker @@ -462,39 +462,59 @@ type UnixTimeTicks struct { // If empty, time.RFC3339 will be used Format string - // Convert takes a float64 value and converts it into a time.Time. - // If nil, time.Unix will be used. - Convert func(float64) time.Time + // Converter takes a float64 value and converts it into a time.Time. + // If nil, UnixTimeConverter will be used. + Converter TimeFloatConverter } -var _ Ticker = UnixTimeTicks{} +var _ Ticker = TimeTicks{} // Ticks implements plot.Ticker. -func (utt UnixTimeTicks) Ticks(min, max float64) []Tick { - if utt.Ticker == nil { - utt.Ticker = DefaultTicks{} +func (tt TimeTicks) Ticks(min, max float64) []Tick { + if tt.Ticker == nil { + tt.Ticker = DefaultTicks{} } - if utt.Format == "" { - utt.Format = time.RFC3339 + if tt.Format == "" { + tt.Format = time.RFC3339 } - if utt.Convert == nil { - utt.Convert = func(v float64) time.Time { - return time.Unix(int64(v), 0) - } + if tt.Converter == nil { + tt.Converter = UnixTimeConverter{} } - ticks := utt.Ticker.Ticks(min, max) + ticks := tt.Ticker.Ticks(min, max) for i := range ticks { tick := &ticks[i] if tick.Label == "" { continue } - t := utt.Convert(tick.Value) - tick.Label = t.Format(utt.Format) + t := tt.Converter.UnmarshalTime(tick.Value) + tick.Label = t.Format(tt.Format) } return ticks } +// TimeFloatConverter converts back and forth a time.Time value into +// a float64. +type TimeFloatConverter interface { + MarshalTime(t time.Time) float64 + UnmarshalTime(float64) time.Time +} + +// UnixTimeConverter converts a time.Time value into the float64 representation +// of Unix time, using the UTC time location. +type UnixTimeConverter struct{} + +// MarshalTime implements TimeFloatConverter. +func (UnixTimeConverter) MarshalTime(t time.Time) float64 { + v := t.UTC().Unix() + return float64(v) +} + +// UnmarshalTime implements TimeFloatConverter. +func (UnixTimeConverter) UnmarshalTime(v float64) time.Time { + return time.Unix(int64(v), 0).UTC() +} + // A Tick is a single tick mark on an axis. type Tick struct { // Value is the data value marked by this Tick. diff --git a/plotter/testdata/timeseries_golden.png b/plotter/testdata/timeseries_golden.png index 02a8d91b8db35e60bbf9fba628cd9af15ffbf516..c9b9c325358300bbcbbb27a417a190ad36e130a8 100644 GIT binary patch literal 17018 zcmZ8}WmJ}1v^AiFqzcmA-Q6W!0wRqZIwcjPq&r1gx=uX_t$r? zy0HhKGlT!59P+ zoh2sUJmNV_#I>}>2KEVwi1f;Jxm3-=-<$PznLOD+d|syWN%sr&{jQObk>1`C-HH4cdwX-`de!y=PZ&ZwK9TrjKg*NzI@^_^6c#3S8T)J^VU6f}dvm_MVl=Qy`J`0u0Em)`ED;H0 zFFqUhP7Cny@zK#Cvy;neR4IBAzow=z$l5(_w%bGK-hXoiP}*Vfh^d^$MZ zo2h$GuNrW5yz{wrqU;3@V#-sy$qI+b3Zr23C(O@F-S&R-)#k;<4p*9X(5q!wo0(Pn zT)keSk!dF-C0&$k_Pfi(dlgnGGUIl2wzt#~s-D7`<8yUpP-`zW0GmS0Zy%=BvChrM z*X(h)oW^JO@Zjj^zozeH()80?*yN%;)I}<(|5;fHIQ-^yb#?V{nD%THY^45}Ctvx) zk(Gr-FlP+zqesqAaR&!Z=4J~m zKE-)rZ%;->toUM*mi{=l*to1+{pzo@En(l4b|};V>D2>2bbeyeFYqq0gj`6z6xZAyrahYc zhMe+6aO4!eR93zdsdk(b=Ho{urCthtq>>Ph%iuUu8@(6V>UVolbba15h0I-NH_5Je zZQ^%>{2*P}eH)I1ii(O{H1UUEyQNh|kI1(8pOvOEXju23s;2!adlBh3-}Gj9Vgld) z@^nXrc>E6wBEE*SG?G_5?K_&z&d!vywEd|sc~XHW!≀15cb>7!4gyP(^EEDJ0|; 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≠wC;N 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 diff --git a/plotter/timeseries_test.go b/plotter/timeseries_test.go index ebf96213..40ee6b0e 100644 --- a/plotter/timeseries_test.go +++ b/plotter/timeseries_test.go @@ -18,6 +18,13 @@ import ( // Example_timeSeries draws a time series. func Example_timeSeries() { + + // xticks defines how we convert and display time.Time values. + xticks := plot.TimeTicks{ + Format: "2006-01-02\n15:04", + Converter: plot.UnixTimeConverter{}, + } + // randomPoints returns some random x, y points // with some interesting kind of trend. randomPoints := func(n int) XYs { @@ -31,8 +38,8 @@ func Example_timeSeries() { ) pts := make(XYs, n) for i := range pts { - date := time.Date(2007+i, month, day, hour, min, sec, nsec, time.UTC).Unix() - pts[i].X = float64(date) + date := time.Date(2007+i, month, day, hour, min, sec, nsec, time.UTC) + pts[i].X = xticks.Converter.MarshalTime(date) pts[i].Y = float64(pts[i].X+10*rand.Float64()) * 1e-9 } return pts @@ -46,12 +53,7 @@ func Example_timeSeries() { log.Panic(err) } p.Title.Text = "Time Series" - p.X.Tick.Marker = plot.UnixTimeTicks{ - Format: "2006-01-02", - Convert: func(v float64) time.Time { - return time.Unix(int64(v), 0).UTC() - }, - } + p.X.Tick.Marker = xticks p.Y.Label.Text = "Number of Gophers\n(Billions)" p.Add(NewGrid())