-
Notifications
You must be signed in to change notification settings - Fork 203
Implemented an AutoUnixTimeTicks #313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ package plot | |
| import ( | ||
| "image/color" | ||
| "math" | ||
| "os" | ||
| "strconv" | ||
| "time" | ||
|
|
||
|
|
@@ -497,6 +498,158 @@ func (utt UnixTimeTicks) Ticks(min, max float64) []Tick { | |
| return ticks | ||
| } | ||
|
|
||
| // AutoUnixTimeTicks is suitable for axes representing time values. | ||
| // AutoUnixTimeTicks expects values in Unix time seconds. It will | ||
| // adjust the number of ticks according to the specified Width. If | ||
| // not specified, Width defaults to 4 inches. | ||
| type AutoUnixTimeTicks struct { | ||
| // Width is the width of the underlying graph, used to calculate | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/graph/plot/ |
||
| // the number of ticks that can fit properly with their time | ||
| // shown. | ||
| Width vg.Length | ||
| } | ||
|
|
||
| var _ Ticker = AutoUnixTimeTicks{} | ||
|
|
||
| // Inspired by https://github.com/d3/d3-scale/blob/master/src/time.js | ||
| var tickIntervals = []tickRule{ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| {time.Millisecond, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't break these over two lines. |
||
| "15:04:05.000", "15:04:05", ".000", time.Millisecond}, | ||
| {200 * time.Millisecond, | ||
| "15:04:05.000", "15:04:05", ".000", 200 * time.Millisecond}, | ||
| {500 * time.Millisecond, | ||
| "15:04:05.000", "15:04:05", ".000", 500 * time.Millisecond}, | ||
| {time.Second, | ||
| "15:04:05", "15:04", ":05", time.Second}, | ||
| {2 * time.Second, | ||
| "15:04:05", "15:04", ":05", 2 * time.Second}, | ||
| {5 * time.Second, | ||
| "15:04:05", "15:04", ":05", 5 * time.Second}, | ||
| {15 * time.Second, | ||
| "Jan 02, 15:04", "Jan 02", "15:04", 15 * time.Second}, | ||
| {30 * time.Second, | ||
| "15:04:05", "15:04", ":05", 30 * time.Second}, | ||
| {time.Minute, | ||
| "15:04:05", "15:04", ":05", time.Minute}, | ||
| {2 * time.Minute, | ||
| "Jan 02, 3:04pm", "Jan 02", "3:04pm", 2 * time.Minute}, | ||
| {5 * time.Minute, | ||
| "Jan 02, 3:04pm", "Jan 02", "3:04pm", 5 * time.Minute}, | ||
| {15 * time.Minute, | ||
| "Jan 02, 3:04pm", "Jan 02", "3:04pm", 15 * time.Minute}, | ||
| {30 * time.Minute, | ||
| "Jan 02, 3:04pm", "Jan 02", "3:04pm", 30 * time.Minute}, | ||
| {time.Hour, | ||
| "Jan 2, 3pm", "Jan 2", "3pm", time.Hour}, | ||
| {3 * time.Hour, | ||
| "Jan 2, 3pm", "Jan 2", "3pm", 3 * time.Hour}, | ||
| {6 * time.Hour, | ||
| "Jan 2, 3pm", "Jan 2", "3pm", 6 * time.Hour}, | ||
| {12 * time.Hour, | ||
| "Jan 2", "Jan 2", "3pm", 12 * time.Hour}, | ||
| {24 * time.Hour, | ||
| "Jan 2", "Jan", "2", 24 * time.Hour}, | ||
| {48 * time.Hour, | ||
| "Jan 2", "Jan", "2", 48 * time.Hour}, | ||
| {7 * 24 * time.Hour, | ||
| "Jan 2", "Jan", "2", 7 * 24 * time.Hour}, | ||
| {aMonth, // 1 month | ||
| "Jan 2006", "2006", "Jan", aMonth}, | ||
| {3 * aMonth, // 3 months | ||
| "Jan 2006", "2006", "Jan", 3 * aMonth}, | ||
| {6 * aMonth, // 6 months | ||
| "Jan 2006", "2006", "Jan", 6 * aMonth}, | ||
| {12 * aMonth, // 1 year | ||
| "2006", "", "2006", 12 * aMonth}, | ||
| {2 * 12 * aMonth, // 2 years | ||
| "2006", "", "2006", 2 * 12 * aMonth}, | ||
| {5 * 12 * aMonth, // 5 years | ||
| "2006", "", "2006", 5 * 12 * aMonth}, | ||
| {10 * 12 * aMonth, // 10 years | ||
| "2006", "", "2006", 10 * 12 * aMonth}, | ||
| } | ||
|
|
||
| var aMonth = 31 * 24 * time.Hour | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could probably be a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Called |
||
|
|
||
| type tickRule struct { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doc comments here as complete grammatical sentences please.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type does not need to exist. You can define |
||
| DurationPerInch time.Duration // use this rule for a maximum Duration per inch | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please either an SI or typesetting unit. SI would be nice, but we have what we have. Maybe express it as the reciprocal and use |
||
| LongFormat string // longer format | ||
| WatchFormat string // show long format when WatchFormat changes between ticks | ||
| ShortFormat string // incremental format, shorter | ||
| TimeWindow time.Duration // interval for ticks | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure why any of these fields are exported.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah they could.. do you think it'd be useful to let people tweak certain rules ? change the formatting.. right now, there's zero internationalization of the labels, etc.. like
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unexport them for now. |
||
| } | ||
|
|
||
| // Ticks implements plot.Ticker. | ||
| func (t AutoUnixTimeTicks) Ticks(min, max float64) []Tick { | ||
| width := t.Width | ||
| if width == 0 { | ||
| width = 4 * vg.Inch | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pet peeve: could this be (a round value) in centimeters instead of inches?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't mind changing the default value to The other place where inches are used is in conjunction with seems more fitting.. where Are you okay with keeping inches for the rules ? |
||
| } | ||
|
|
||
| minT := time.Unix(int64(min), 0).UTC() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need for the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's there to distinguish from the function parameters |
||
| maxT := time.Unix(int64(max), 0).UTC() | ||
| durationPerInch := maxT.Sub(minT) / time.Duration(width/vg.Inch) | ||
|
|
||
| rule := tickIntervals[len(tickIntervals)-1] | ||
| for idx := range tickIntervals[:len(tickIntervals)-1] { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Get the value here so you have it by the time you get to L596. |
||
| if durationPerInch < tickIntervals[idx+1].DurationPerInch { | ||
| rule = tickIntervals[idx] | ||
| break | ||
| } | ||
| } | ||
|
|
||
| ticks := []Tick{ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replace with just: var ticks []Tick
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, more left overs :) |
||
| // {Value: min, Label: minT.Format(rule.BeginFormat)}, | ||
| } | ||
|
|
||
| monthsDelta := time.Month(rule.TimeWindow / aMonth) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/monthsDelta/delta/g |
||
|
|
||
| //fmt.Println("Months delta", int(monthsDelta)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete. |
||
| start := minT.Truncate(rule.TimeWindow) | ||
| count := 0 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| lastWatch := "" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| for { | ||
| count++ | ||
| if monthsDelta > 0 { | ||
| // Count in Months now | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Full stop. |
||
| start = time.Date(start.Year(), start.Month()+monthsDelta, 1, 0, 0, 0, 0, time.UTC) | ||
| } else { | ||
| start = start.Add(rule.TimeWindow) | ||
| } | ||
|
|
||
| if count > 20 { | ||
| os.Exit(0) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's pretty ugly :)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps just return nil and document?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. woohaahh! that was just for debugging :) sorry it went through.. should just take that out.. if you have a sane time range, you won't be hitting that. |
||
| } | ||
|
|
||
| if start.Before(minT) { | ||
| continue | ||
| } | ||
| if start.After(maxT) { | ||
| break | ||
| } | ||
|
|
||
| label := "" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| newWatch := start.Format(rule.WatchFormat) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the meaning of "watch" here?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If you look into tests, you'll see there are 2 sorts of labels per graph.. long and short forms. |
||
| if lastWatch == newWatch { | ||
| label = start.Format(rule.ShortFormat) | ||
| } else { | ||
| //TODO: overwrite the first tick with the long form if we | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s|//TODO: o|// TODO(name): O| |
||
| //haven't shown a lonform at all.. instead of always | ||
| //showing the longform first. | ||
| label = start.Format(rule.LongFormat) | ||
| } | ||
| lastWatch = newWatch | ||
|
|
||
| ticks = append(ticks, Tick{ | ||
| Value: float64(start.UnixNano()) / float64(time.Second), | ||
| Label: label, | ||
| }) | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete blank line. |
||
| } | ||
|
|
||
| return ticks | ||
| } | ||
|
|
||
| // A Tick is a single tick mark on an axis. | ||
| type Tick struct { | ||
| // Value is the data value marked by this Tick. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,9 +5,13 @@ | |
| package plot | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math" | ||
| "reflect" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/gonum/plot/vg" | ||
| ) | ||
|
|
||
| func TestAxisSmallTick(t *testing.T) { | ||
|
|
@@ -59,3 +63,176 @@ func labelsOf(ticks []Tick) []string { | |
| } | ||
| return labels | ||
| } | ||
|
|
||
| func allLabelsOf(ticks []Tick) []string { | ||
| var labels []string | ||
| for _, t := range ticks { | ||
| labels = append(labels, t.Label) | ||
| } | ||
| return labels | ||
| } | ||
|
|
||
| func TestAutoUnixTimeTicks(t *testing.T) { | ||
| d := AutoUnixTimeTicks{Width: 4 * vg.Inch} | ||
| for _, test := range []struct { | ||
| min, max string | ||
| want []string | ||
| }{ | ||
| { | ||
| min: "2016-01-01 12:56:30", | ||
| max: "2016-01-01 12:56:31", | ||
| want: []string{"12:56:30.200", ".400", ".600", ".800", "12:56:31.000"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:56:01", | ||
| max: "2016-01-01 12:56:59", | ||
| want: []string{"12:56:05", ":10", ":15", ":20", ":25", ":30", ":35", ":40", ":45", ":50", ":55"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:56:30", | ||
| max: "2016-01-01 12:57:29", | ||
| want: []string{"12:56:35", ":40", ":45", ":50", ":55", "12:57:00", ":05", ":10", ":15", ":20", ":25"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 12:07:00", | ||
| want: []string{"12:02:00", "12:03:00", "12:04:00", "12:05:00", "12:06:00", "12:07:00"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 12:17:00", | ||
| want: []string{"Jan 01, 12:02pm", "12:04pm", "12:06pm", "12:08pm", "12:10pm", "12:12pm", "12:14pm", "12:16pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 12:28:00", | ||
| want: []string{"Jan 01, 12:05pm", "12:10pm", "12:15pm", "12:20pm", "12:25pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 12:35:00", | ||
| want: []string{"Jan 01, 12:05pm", "12:10pm", "12:15pm", "12:20pm", "12:25pm", "12:30pm", "12:35pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 12:40:00", | ||
| want: []string{"Jan 01, 12:05pm", "12:10pm", "12:15pm", "12:20pm", "12:25pm", "12:30pm", "12:35pm", "12:40pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 12:45:00", | ||
| want: []string{"Jan 01, 12:05pm", "12:10pm", "12:15pm", "12:20pm", "12:25pm", "12:30pm", "12:35pm", "12:40pm", "12:45pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 13:05:00", | ||
| want: []string{"Jan 01, 12:15pm", "12:30pm", "12:45pm", "1:00pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 13:05:00", | ||
| want: []string{"Jan 01, 12:15pm", "12:30pm", "12:45pm", "1:00pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-01 16:05:00", | ||
| want: []string{"Jan 1, 1pm", "2pm", "3pm", "4pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 20:01:05", | ||
| max: "2016-01-02 07:59:00", | ||
| want: []string{"Jan 1, 9pm", "10pm", "11pm", "Jan 2, 12am", "1am", "2am", "3am", "4am", "5am", "6am", "7am"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-02 13:59:00", | ||
| want: []string{"Jan 1, 6pm", "Jan 2, 12am", "6am", "12pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-04 13:59:00", | ||
| want: []string{"Jan 2", "12pm", "Jan 3", "12pm", "Jan 4", "12pm"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-06 13:59:00", | ||
| want: []string{"Jan 2", "3", "4", "5", "6"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-09 13:59:00", | ||
| want: []string{"Jan 2", "4", "6", "8"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-01-25 13:59:00", | ||
| want: []string{"Jan 2", "4", "6", "8", "10", "12", "14", "16", "18", "20", "22", "24"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-02-06 13:59:00", | ||
| want: []string{"Jan 4", "11", "18", "25", "Feb 1"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-02-28 13:59:00", | ||
| want: []string{"Jan 4", "11", "18", "25", "Feb 1", "8", "15", "22"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-04-28 13:59:00", | ||
| want: []string{"Jan 4", "11", "18", "25", "Feb 1", "8", "15", "22", "29", "Mar 7", "14", "21", "28", "Apr 4", "11", "18", "25"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-09-28 13:59:00", | ||
| want: []string{"Feb 2016", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2016-12-28 13:59:00", | ||
| want: []string{"Feb 2016", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2017-02-28 13:59:00", | ||
| want: []string{"Feb 2016", "May", "Aug", "Nov", "Feb 2017"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2017-08-28 13:59:00", | ||
| want: []string{"Feb 2016", "May", "Aug", "Nov", "Feb 2017", "May", "Aug"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2018-08-28 13:59:00", | ||
| want: []string{"Feb 2016", "Aug", "Feb 2017", "Aug", "Feb 2018", "Aug"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2020-08-28 13:59:00", | ||
| want: []string{"2016", "2017", "2018", "2019", "2020"}, | ||
| }, | ||
| { | ||
| min: "2016-01-01 12:01:05", | ||
| max: "2048-08-28 13:59:00", | ||
| want: []string{"2017", "2022", "2027", "2032", "2037", "2042", "2047"}, | ||
| }, | ||
| } { | ||
| fmt.Println("For dates", test.min, test.max) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this line |
||
| ticks := d.Ticks(dateToFloat64(test.min), dateToFloat64(test.max)) | ||
| got := allLabelsOf(ticks) | ||
| if !reflect.DeepEqual(got, test.want) { | ||
| t.Errorf("tick labels mismatch:\ndate1: %s\ndate2: %s\ngot: %#v\nwant:%q", test.min, test.max, got, test.want) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func dateToFloat64(date string) float64 { | ||
| t, err := time.Parse("2006-01-02 15:04:05", date) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| return float64(t.UTC().UnixNano()) / float64(time.Second) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced by the name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh.. I know it isn't pretty :)
UnixTimeTickswas already taken.. andAutowas used to distinguish the added feature..Could we make it
TimeTicks? and document the fact that you need Unix timestamps, or provide a function to transform a[]time.Timeinto whatever this thing needs ?