Merge pull request #8 from wcharczuk/wcharczuk/market-hours
Consolidating date functions & tweaking market hours range
This commit is contained in:
commit
818dd0113b
24 changed files with 706 additions and 283 deletions
|
@ -6,7 +6,7 @@ import "math"
|
||||||
type AnnotationSeries struct {
|
type AnnotationSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
Annotations []Value2
|
Annotations []Value2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ func (as AnnotationSeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (as AnnotationSeries) GetYAxis() yAxisType {
|
func (as AnnotationSeries) GetYAxis() YAxisType {
|
||||||
return as.YAxis
|
return as.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
axis.go
15
axis.go
|
@ -1,24 +1,25 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
type tickPosition int
|
// TickPosition is an enumeration of possible tick drawing positions.
|
||||||
|
type TickPosition int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TickPositionUnset means to use the default tick position.
|
// TickPositionUnset means to use the default tick position.
|
||||||
TickPositionUnset tickPosition = 0
|
TickPositionUnset TickPosition = 0
|
||||||
// TickPositionBetweenTicks draws the labels for a tick between the previous and current tick.
|
// TickPositionBetweenTicks draws the labels for a tick between the previous and current tick.
|
||||||
TickPositionBetweenTicks tickPosition = 1
|
TickPositionBetweenTicks TickPosition = 1
|
||||||
// TickPositionUnderTick draws the tick below the tick.
|
// TickPositionUnderTick draws the tick below the tick.
|
||||||
TickPositionUnderTick tickPosition = 2
|
TickPositionUnderTick TickPosition = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// YAxisType is a type of y-axis; it can either be primary or secondary.
|
// YAxisType is a type of y-axis; it can either be primary or secondary.
|
||||||
type yAxisType int
|
type YAxisType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// YAxisPrimary is the primary axis.
|
// YAxisPrimary is the primary axis.
|
||||||
YAxisPrimary yAxisType = 0
|
YAxisPrimary YAxisType = 0
|
||||||
// YAxisSecondary is the secondary axis.
|
// YAxisSecondary is the secondary axis.
|
||||||
YAxisSecondary yAxisType = 1
|
YAxisSecondary YAxisType = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Axis is a chart feature detailing what values happen where.
|
// Axis is a chart feature detailing what values happen where.
|
||||||
|
|
|
@ -7,7 +7,7 @@ import "math"
|
||||||
type BollingerBandsSeries struct {
|
type BollingerBandsSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
Period int
|
Period int
|
||||||
K float64
|
K float64
|
||||||
|
@ -27,7 +27,7 @@ func (bbs BollingerBandsSeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (bbs BollingerBandsSeries) GetYAxis() yAxisType {
|
func (bbs BollingerBandsSeries) GetYAxis() YAxisType {
|
||||||
return bbs.YAxis
|
return bbs.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ type ContinuousSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
|
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
XValues []float64
|
XValues []float64
|
||||||
YValues []float64
|
YValues []float64
|
||||||
|
@ -44,7 +44,7 @@ func (cs ContinuousSeries) GetValueFormatters() (x, y ValueFormatter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (cs ContinuousSeries) GetYAxis() yAxisType {
|
func (cs ContinuousSeries) GetYAxis() YAxisType {
|
||||||
return cs.YAxis
|
return cs.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package date
|
package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -52,33 +52,40 @@ var (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// NYSEOpen is when the NYSE opens.
|
// NYSEOpen is when the NYSE opens.
|
||||||
NYSEOpen = ClockTime(9, 30, 0, 0, Eastern())
|
NYSEOpen = Date.Time(9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
// NYSEClose is when the NYSE closes.
|
// NYSEClose is when the NYSE closes.
|
||||||
NYSEClose = ClockTime(16, 0, 0, 0, Eastern())
|
NYSEClose = Date.Time(16, 0, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
// NASDAQOpen is when NASDAQ opens.
|
// NASDAQOpen is when NASDAQ opens.
|
||||||
NASDAQOpen = ClockTime(9, 30, 0, 0, Eastern())
|
NASDAQOpen = Date.Time(9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
// NASDAQClose is when NASDAQ closes.
|
// NASDAQClose is when NASDAQ closes.
|
||||||
NASDAQClose = ClockTime(16, 0, 0, 0, Eastern())
|
NASDAQClose = Date.Time(16, 0, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
// NYSEArcaOpen is when NYSEARCA opens.
|
// NYSEArcaOpen is when NYSEARCA opens.
|
||||||
NYSEArcaOpen = ClockTime(4, 0, 0, 0, Eastern())
|
NYSEArcaOpen = Date.Time(4, 0, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
// NYSEArcaClose is when NYSEARCA closes.
|
// NYSEArcaClose is when NYSEARCA closes.
|
||||||
NYSEArcaClose = ClockTime(20, 0, 0, 0, Eastern())
|
NYSEArcaClose = Date.Time(20, 0, 0, 0, Date.Eastern())
|
||||||
)
|
)
|
||||||
|
|
||||||
// HolidayProvider is a function that returns if a given time falls on a holiday.
|
// HolidayProvider is a function that returns if a given time falls on a holiday.
|
||||||
type HolidayProvider func(time.Time) bool
|
type HolidayProvider func(time.Time) bool
|
||||||
|
|
||||||
// DefaultHolidayProvider implements `HolidayProvider` and just returns false.
|
// defaultHolidayProvider implements `HolidayProvider` and just returns false.
|
||||||
func DefaultHolidayProvider(_ time.Time) bool { return false }
|
func defaultHolidayProvider(_ time.Time) bool { return false }
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Date contains utility functions that operate on dates.
|
||||||
|
Date = &date{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type date struct{}
|
||||||
|
|
||||||
// IsNYSEHoliday returns if a date was/is on a nyse holiday day.
|
// IsNYSEHoliday returns if a date was/is on a nyse holiday day.
|
||||||
func IsNYSEHoliday(t time.Time) bool {
|
func (d date) IsNYSEHoliday(t time.Time) bool {
|
||||||
te := t.In(Eastern())
|
te := t.In(d.Eastern())
|
||||||
if te.Year() == 2013 {
|
if te.Year() == 2013 {
|
||||||
if te.Month() == 1 {
|
if te.Month() == 1 {
|
||||||
return te.Day() == 1 || te.Day() == 21
|
return te.Day() == 1 || te.Day() == 21
|
||||||
|
@ -192,17 +199,17 @@ func IsNYSEHoliday(t time.Time) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNYSEArcaHoliday returns that returns if a given time falls on a holiday.
|
// IsNYSEArcaHoliday returns that returns if a given time falls on a holiday.
|
||||||
func IsNYSEArcaHoliday(t time.Time) bool {
|
func (d date) IsNYSEArcaHoliday(t time.Time) bool {
|
||||||
return IsNYSEHoliday(t)
|
return d.IsNYSEHoliday(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNASDAQHoliday returns if a date was a NASDAQ holiday day.
|
// IsNASDAQHoliday returns if a date was a NASDAQ holiday day.
|
||||||
func IsNASDAQHoliday(t time.Time) bool {
|
func (d date) IsNASDAQHoliday(t time.Time) bool {
|
||||||
return IsNYSEHoliday(t)
|
return d.IsNYSEHoliday(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eastern returns the eastern timezone.
|
// Eastern returns the eastern timezone.
|
||||||
func Eastern() *time.Location {
|
func (d date) Eastern() *time.Location {
|
||||||
if _eastern == nil {
|
if _eastern == nil {
|
||||||
_easternLock.Lock()
|
_easternLock.Lock()
|
||||||
defer _easternLock.Unlock()
|
defer _easternLock.Unlock()
|
||||||
|
@ -213,103 +220,113 @@ func Eastern() *time.Location {
|
||||||
return _eastern
|
return _eastern
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClockTime returns a new time.Time for the given clock components.
|
// Time returns a new time.Time for the given clock components.
|
||||||
func ClockTime(hour, min, sec, nsec int, loc *time.Location) time.Time {
|
func (d date) Time(hour, min, sec, nsec int, loc *time.Location) time.Time {
|
||||||
return time.Date(0, 0, 0, hour, min, sec, nsec, loc)
|
return time.Date(0, 0, 0, hour, min, sec, nsec, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d date) Date(year, month, day int, loc *time.Location) time.Time {
|
||||||
|
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, loc)
|
||||||
|
}
|
||||||
|
|
||||||
// On returns the clock components of clock (hour,minute,second) on the date components of d.
|
// On returns the clock components of clock (hour,minute,second) on the date components of d.
|
||||||
func On(clock, d time.Time) time.Time {
|
func (d date) On(clock, cd time.Time) time.Time {
|
||||||
return time.Date(d.Year(), d.Month(), d.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location())
|
tzAdjusted := cd.In(clock.Location())
|
||||||
|
return time.Date(tzAdjusted.Year(), tzAdjusted.Month(), tzAdjusted.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoonOn is a shortcut for On(Time(12,0,0), cd) a.k.a. noon on a given date.
|
||||||
|
func (d date) NoonOn(cd time.Time) time.Time {
|
||||||
|
return time.Date(cd.Year(), cd.Month(), cd.Day(), 12, 0, 0, 0, cd.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional returns a pointer reference to a given time.
|
// Optional returns a pointer reference to a given time.
|
||||||
func Optional(t time.Time) *time.Time {
|
func (d date) Optional(t time.Time) *time.Time {
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWeekDay returns if the day is a monday->friday.
|
// IsWeekDay returns if the day is a monday->friday.
|
||||||
func IsWeekDay(day time.Weekday) bool {
|
func (d date) IsWeekDay(day time.Weekday) bool {
|
||||||
return !IsWeekendDay(day)
|
return !d.IsWeekendDay(day)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWeekendDay returns if the day is a monday->friday.
|
// IsWeekendDay returns if the day is a monday->friday.
|
||||||
func IsWeekendDay(day time.Weekday) bool {
|
func (d date) IsWeekendDay(day time.Weekday) bool {
|
||||||
return day == time.Saturday || day == time.Sunday
|
return day == time.Saturday || day == time.Sunday
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeDate returns if a timestamp is strictly before another date (ignoring hours, minutes etc.)
|
// Before returns if a timestamp is strictly before another date (ignoring hours, minutes etc.)
|
||||||
func BeforeDate(before, reference time.Time) bool {
|
func (d date) Before(before, reference time.Time) bool {
|
||||||
if before.Year() < reference.Year() {
|
tzAdjustedBefore := before.In(reference.Location())
|
||||||
|
if tzAdjustedBefore.Year() < reference.Year() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if before.Month() < reference.Month() {
|
if tzAdjustedBefore.Month() < reference.Month() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return before.Year() == reference.Year() && before.Month() == reference.Month() && before.Day() < reference.Day()
|
return tzAdjustedBefore.Year() == reference.Year() && tzAdjustedBefore.Month() == reference.Month() && tzAdjustedBefore.Day() < reference.Day()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextMarketOpen returns the next market open after a given time.
|
// NextMarketOpen returns the next market open after a given time.
|
||||||
func NextMarketOpen(after, openTime time.Time, isHoliday HolidayProvider) time.Time {
|
func (d date) NextMarketOpen(after, openTime time.Time, isHoliday HolidayProvider) time.Time {
|
||||||
afterEastern := after.In(Eastern())
|
afterLocalized := after.In(openTime.Location())
|
||||||
todaysOpen := On(openTime, afterEastern)
|
todaysOpen := d.On(openTime, afterLocalized)
|
||||||
|
|
||||||
if isHoliday == nil {
|
if isHoliday == nil {
|
||||||
isHoliday = DefaultHolidayProvider
|
isHoliday = defaultHolidayProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
if afterEastern.Before(todaysOpen) && IsWeekDay(todaysOpen.Weekday()) && !isHoliday(todaysOpen) {
|
todayIsValidTradingDay := d.IsWeekDay(todaysOpen.Weekday()) && !isHoliday(todaysOpen)
|
||||||
|
|
||||||
|
if (afterLocalized.Equal(todaysOpen) || afterLocalized.Before(todaysOpen)) && todayIsValidTradingDay {
|
||||||
return todaysOpen
|
return todaysOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
if afterEastern.Equal(todaysOpen) { //rare but it might happen.
|
for cursorDay := 1; cursorDay < 7; cursorDay++ {
|
||||||
return todaysOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
for cursorDay := 1; cursorDay < 6; cursorDay++ {
|
|
||||||
newDay := todaysOpen.AddDate(0, 0, cursorDay)
|
newDay := todaysOpen.AddDate(0, 0, cursorDay)
|
||||||
if IsWeekDay(newDay.Weekday()) && !isHoliday(afterEastern) {
|
isValidTradingDay := d.IsWeekDay(newDay.Weekday()) && !isHoliday(newDay)
|
||||||
return On(openTime, newDay)
|
if isValidTradingDay {
|
||||||
|
return d.On(openTime, newDay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Epoch //we should never reach this.
|
panic("Have exhausted day window looking for next market open.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextMarketClose returns the next market close after a given time.
|
// NextMarketClose returns the next market close after a given time.
|
||||||
func NextMarketClose(after, closeTime time.Time, isHoliday HolidayProvider) time.Time {
|
func (d date) NextMarketClose(after, closeTime time.Time, isHoliday HolidayProvider) time.Time {
|
||||||
afterEastern := after.In(Eastern())
|
afterLocalized := after.In(closeTime.Location())
|
||||||
|
|
||||||
if isHoliday == nil {
|
if isHoliday == nil {
|
||||||
isHoliday = DefaultHolidayProvider
|
isHoliday = defaultHolidayProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
todaysClose := On(closeTime, afterEastern)
|
todaysClose := d.On(closeTime, afterLocalized)
|
||||||
if afterEastern.Before(todaysClose) && IsWeekDay(todaysClose.Weekday()) && !isHoliday(todaysClose) {
|
if afterLocalized.Before(todaysClose) && d.IsWeekDay(todaysClose.Weekday()) && !isHoliday(todaysClose) {
|
||||||
return todaysClose
|
return todaysClose
|
||||||
}
|
}
|
||||||
|
|
||||||
if afterEastern.Equal(todaysClose) { //rare but it might happen.
|
if afterLocalized.Equal(todaysClose) { //rare but it might happen.
|
||||||
return todaysClose
|
return todaysClose
|
||||||
}
|
}
|
||||||
|
|
||||||
for cursorDay := 1; cursorDay < 6; cursorDay++ {
|
for cursorDay := 1; cursorDay < 6; cursorDay++ {
|
||||||
newDay := todaysClose.AddDate(0, 0, cursorDay)
|
newDay := todaysClose.AddDate(0, 0, cursorDay)
|
||||||
if IsWeekDay(newDay.Weekday()) && !isHoliday(newDay) {
|
if d.IsWeekDay(newDay.Weekday()) && !isHoliday(newDay) {
|
||||||
return On(closeTime, newDay)
|
return d.On(closeTime, newDay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Epoch //we should never reach this.
|
panic("Have exhausted day window looking for next market close.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateMarketSecondsBetween calculates the number of seconds the market was open between two dates.
|
// CalculateMarketSecondsBetween calculates the number of seconds the market was open between two dates.
|
||||||
func CalculateMarketSecondsBetween(start, end, marketOpen, marketClose time.Time, isHoliday HolidayProvider) (seconds int64) {
|
func (d date) CalculateMarketSecondsBetween(start, end, marketOpen, marketClose time.Time, isHoliday HolidayProvider) (seconds int64) {
|
||||||
startEastern := start.In(Eastern())
|
startEastern := start.In(d.Eastern())
|
||||||
endEastern := end.In(Eastern())
|
endEastern := end.In(d.Eastern())
|
||||||
|
|
||||||
startMarketOpen := On(marketOpen, startEastern)
|
startMarketOpen := d.On(marketOpen, startEastern)
|
||||||
startMarketClose := On(marketClose, startEastern)
|
startMarketClose := d.On(marketClose, startEastern)
|
||||||
|
|
||||||
if !IsWeekendDay(startMarketOpen.Weekday()) && !isHoliday(startMarketOpen) {
|
if !d.IsWeekendDay(startMarketOpen.Weekday()) && !isHoliday(startMarketOpen) {
|
||||||
if (startEastern.Equal(startMarketOpen) || startEastern.After(startMarketOpen)) && startEastern.Before(startMarketClose) {
|
if (startEastern.Equal(startMarketOpen) || startEastern.After(startMarketOpen)) && startEastern.Before(startMarketClose) {
|
||||||
if endEastern.Before(startMarketClose) {
|
if endEastern.Before(startMarketClose) {
|
||||||
seconds += int64(endEastern.Sub(startEastern) / time.Second)
|
seconds += int64(endEastern.Sub(startEastern) / time.Second)
|
||||||
|
@ -319,17 +336,17 @@ func CalculateMarketSecondsBetween(start, end, marketOpen, marketClose time.Time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor := NextMarketOpen(startMarketClose, marketOpen, isHoliday)
|
cursor := d.NextMarketOpen(startMarketClose, marketOpen, isHoliday)
|
||||||
for BeforeDate(cursor, endEastern) {
|
for d.Before(cursor, endEastern) {
|
||||||
if IsWeekDay(cursor.Weekday()) && !isHoliday(cursor) {
|
if d.IsWeekDay(cursor.Weekday()) && !isHoliday(cursor) {
|
||||||
close := NextMarketClose(cursor, marketClose, isHoliday)
|
close := d.NextMarketClose(cursor, marketClose, isHoliday)
|
||||||
seconds += int64(close.Sub(cursor) / time.Second)
|
seconds += int64(close.Sub(cursor) / time.Second)
|
||||||
}
|
}
|
||||||
cursor = cursor.AddDate(0, 0, 1)
|
cursor = cursor.AddDate(0, 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
finalMarketOpen := NextMarketOpen(cursor, marketOpen, isHoliday)
|
finalMarketOpen := d.NextMarketOpen(cursor, marketOpen, isHoliday)
|
||||||
finalMarketClose := NextMarketClose(cursor, marketClose, isHoliday)
|
finalMarketClose := d.NextMarketClose(cursor, marketClose, isHoliday)
|
||||||
if endEastern.After(finalMarketOpen) {
|
if endEastern.After(finalMarketOpen) {
|
||||||
if endEastern.Before(finalMarketClose) {
|
if endEastern.Before(finalMarketClose) {
|
||||||
seconds += int64(endEastern.Sub(finalMarketOpen) / time.Second)
|
seconds += int64(endEastern.Sub(finalMarketOpen) / time.Second)
|
||||||
|
@ -341,13 +358,45 @@ func CalculateMarketSecondsBetween(start, end, marketOpen, marketClose time.Time
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format returns a string representation of a date.
|
const (
|
||||||
func format(t time.Time) string {
|
_secondsPerDay = 60 * 60 * 24
|
||||||
return t.Format("2006-01-02")
|
)
|
||||||
|
|
||||||
|
func (d date) Diff(t1, t2 time.Time) (days int64) {
|
||||||
|
t1n := t1.Unix()
|
||||||
|
t2n := t2.Unix()
|
||||||
|
diff := t1n - t2n
|
||||||
|
return diff / (_secondsPerDay)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses a date from a string.
|
// NextDay returns the timestamp advanced a day.
|
||||||
func parse(str string) time.Time {
|
func (d date) NextDay(ts time.Time) time.Time {
|
||||||
res, _ := time.Parse("2006-01-02", str)
|
return ts.AddDate(0, 0, 1)
|
||||||
return res
|
}
|
||||||
|
|
||||||
|
// NextHour returns the next timestamp on the hour.
|
||||||
|
func (d date) NextHour(ts time.Time) time.Time {
|
||||||
|
//advance a full hour ...
|
||||||
|
advanced := ts.Add(time.Hour)
|
||||||
|
minutes := time.Duration(advanced.Minute()) * time.Minute
|
||||||
|
final := advanced.Add(-minutes)
|
||||||
|
return time.Date(final.Year(), final.Month(), final.Day(), final.Hour(), 0, 0, 0, final.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextDayOfWeek returns the next instance of a given weekday after a given timestamp.
|
||||||
|
func (d date) NextDayOfWeek(after time.Time, dayOfWeek time.Weekday) time.Time {
|
||||||
|
afterWeekday := after.Weekday()
|
||||||
|
if afterWeekday == dayOfWeek {
|
||||||
|
return after.AddDate(0, 0, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 vs 5 ~ add 4 days
|
||||||
|
if afterWeekday < dayOfWeek {
|
||||||
|
dayDelta := int(dayOfWeek - afterWeekday)
|
||||||
|
return after.AddDate(0, 0, dayDelta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5 vs 1, add 7-(5-1) ~ 3 days
|
||||||
|
dayDelta := 7 - int(afterWeekday-dayOfWeek)
|
||||||
|
return after.AddDate(0, 0, dayDelta)
|
||||||
}
|
}
|
|
@ -1,98 +0,0 @@
|
||||||
package date
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBeforeDate(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
assert.True(BeforeDate(parse("2015-07-02"), parse("2016-07-01")))
|
|
||||||
assert.True(BeforeDate(parse("2016-06-01"), parse("2016-07-01")))
|
|
||||||
assert.True(BeforeDate(parse("2016-07-01"), parse("2016-07-02")))
|
|
||||||
|
|
||||||
assert.False(BeforeDate(parse("2016-07-01"), parse("2016-07-01")))
|
|
||||||
assert.False(BeforeDate(parse("2016-07-03"), parse("2016-07-01")))
|
|
||||||
assert.False(BeforeDate(parse("2016-08-03"), parse("2016-07-01")))
|
|
||||||
assert.False(BeforeDate(parse("2017-08-03"), parse("2016-07-01")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNextMarketOpen(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
beforeOpen := time.Date(2016, 07, 18, 9, 0, 0, 0, Eastern())
|
|
||||||
todayOpen := time.Date(2016, 07, 18, 9, 30, 0, 0, Eastern())
|
|
||||||
|
|
||||||
afterOpen := time.Date(2016, 07, 18, 9, 31, 0, 0, Eastern())
|
|
||||||
tomorrowOpen := time.Date(2016, 07, 19, 9, 30, 0, 0, Eastern())
|
|
||||||
|
|
||||||
afterFriday := time.Date(2016, 07, 22, 9, 31, 0, 0, Eastern())
|
|
||||||
mondayOpen := time.Date(2016, 07, 25, 9, 30, 0, 0, Eastern())
|
|
||||||
|
|
||||||
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Eastern())
|
|
||||||
|
|
||||||
assert.True(todayOpen.Equal(NextMarketOpen(beforeOpen, NYSEOpen, IsNYSEHoliday)))
|
|
||||||
assert.True(tomorrowOpen.Equal(NextMarketOpen(afterOpen, NYSEOpen, IsNYSEHoliday)))
|
|
||||||
assert.True(mondayOpen.Equal(NextMarketOpen(afterFriday, NYSEOpen, IsNYSEHoliday)))
|
|
||||||
assert.True(mondayOpen.Equal(NextMarketOpen(weekend, NYSEOpen, IsNYSEHoliday)))
|
|
||||||
|
|
||||||
testRegression := time.Date(2016, 07, 18, 16, 0, 0, 0, Eastern())
|
|
||||||
shouldbe := time.Date(2016, 07, 19, 9, 30, 0, 0, Eastern())
|
|
||||||
|
|
||||||
assert.True(shouldbe.Equal(NextMarketOpen(testRegression, NYSEOpen, IsNYSEHoliday)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNextMarketClose(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
beforeClose := time.Date(2016, 07, 18, 15, 0, 0, 0, Eastern())
|
|
||||||
todayClose := time.Date(2016, 07, 18, 16, 00, 0, 0, Eastern())
|
|
||||||
|
|
||||||
afterClose := time.Date(2016, 07, 18, 16, 1, 0, 0, Eastern())
|
|
||||||
tomorrowClose := time.Date(2016, 07, 19, 16, 00, 0, 0, Eastern())
|
|
||||||
|
|
||||||
afterFriday := time.Date(2016, 07, 22, 16, 1, 0, 0, Eastern())
|
|
||||||
mondayClose := time.Date(2016, 07, 25, 16, 0, 0, 0, Eastern())
|
|
||||||
|
|
||||||
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Eastern())
|
|
||||||
|
|
||||||
assert.True(todayClose.Equal(NextMarketClose(beforeClose, NYSEClose, IsNYSEHoliday)))
|
|
||||||
assert.True(tomorrowClose.Equal(NextMarketClose(afterClose, NYSEClose, IsNYSEHoliday)))
|
|
||||||
assert.True(mondayClose.Equal(NextMarketClose(afterFriday, NYSEClose, IsNYSEHoliday)))
|
|
||||||
assert.True(mondayClose.Equal(NextMarketClose(weekend, NYSEClose, IsNYSEHoliday)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalculateMarketSecondsBetween(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
start := time.Date(2016, 07, 18, 9, 30, 0, 0, Eastern())
|
|
||||||
end := time.Date(2016, 07, 22, 16, 00, 0, 0, Eastern())
|
|
||||||
|
|
||||||
shouldbe := 5 * 6.5 * 60 * 60
|
|
||||||
|
|
||||||
assert.Equal(shouldbe, CalculateMarketSecondsBetween(start, end, NYSEOpen, NYSEClose, IsNYSEHoliday))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalculateMarketSecondsBetween1D(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
start := time.Date(2016, 07, 22, 9, 45, 0, 0, Eastern())
|
|
||||||
end := time.Date(2016, 07, 22, 15, 45, 0, 0, Eastern())
|
|
||||||
|
|
||||||
shouldbe := 6 * 60 * 60
|
|
||||||
|
|
||||||
assert.Equal(shouldbe, CalculateMarketSecondsBetween(start, end, NYSEOpen, NYSEClose, IsNYSEHoliday))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalculateMarketSecondsBetweenLTM(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Eastern())
|
|
||||||
end := time.Date(2016, 07, 01, 9, 30, 0, 0, Eastern())
|
|
||||||
|
|
||||||
shouldbe := 253 * 6.5 * 60 * 60 //253 full market days since this date last year.
|
|
||||||
assert.Equal(shouldbe, CalculateMarketSecondsBetween(start, end, NYSEOpen, NYSEClose, IsNYSEHoliday))
|
|
||||||
}
|
|
224
date_test.go
Normal file
224
date_test.go
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parse(v string) time.Time {
|
||||||
|
ts, _ := time.Parse("2006-01-02", v)
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateTime(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
ts := Date.Time(5, 6, 7, 8, time.UTC)
|
||||||
|
assert.Equal(05, ts.Hour())
|
||||||
|
assert.Equal(06, ts.Minute())
|
||||||
|
assert.Equal(07, ts.Second())
|
||||||
|
assert.Equal(8, ts.Nanosecond())
|
||||||
|
assert.Equal(time.UTC, ts.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateDate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
ts := Date.Date(2015, 5, 6, time.UTC)
|
||||||
|
assert.Equal(2015, ts.Year())
|
||||||
|
assert.Equal(5, ts.Month())
|
||||||
|
assert.Equal(6, ts.Day())
|
||||||
|
assert.Equal(time.UTC, ts.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateOn(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
ts := Date.On(Date.Time(5, 4, 3, 2, time.UTC), Date.Date(2016, 6, 7, Date.Eastern()))
|
||||||
|
assert.Equal(2016, ts.Year())
|
||||||
|
assert.Equal(6, ts.Month())
|
||||||
|
assert.Equal(7, ts.Day())
|
||||||
|
assert.Equal(5, ts.Hour())
|
||||||
|
assert.Equal(4, ts.Minute())
|
||||||
|
assert.Equal(3, ts.Second())
|
||||||
|
assert.Equal(2, ts.Nanosecond())
|
||||||
|
assert.Equal(time.UTC, ts.Location())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateNoonOn(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
noon := Date.NoonOn(time.Date(2016, 04, 03, 02, 01, 0, 0, time.UTC))
|
||||||
|
|
||||||
|
assert.Equal(2016, noon.Year())
|
||||||
|
assert.Equal(4, noon.Month())
|
||||||
|
assert.Equal(3, noon.Day())
|
||||||
|
assert.Equal(12, noon.Hour())
|
||||||
|
assert.Equal(0, noon.Minute())
|
||||||
|
assert.Equal(time.UTC, noon.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateBefore(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
assert.True(Date.Before(parse("2015-07-02"), parse("2016-07-01")))
|
||||||
|
assert.True(Date.Before(parse("2016-06-01"), parse("2016-07-01")))
|
||||||
|
assert.True(Date.Before(parse("2016-07-01"), parse("2016-07-02")))
|
||||||
|
|
||||||
|
assert.False(Date.Before(parse("2016-07-01"), parse("2016-07-01")))
|
||||||
|
assert.False(Date.Before(parse("2016-07-03"), parse("2016-07-01")))
|
||||||
|
assert.False(Date.Before(parse("2016-08-03"), parse("2016-07-01")))
|
||||||
|
assert.False(Date.Before(parse("2017-08-03"), parse("2016-07-01")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateBeforeHandlesTimezones(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
tuesdayUTC := time.Date(2016, 8, 02, 22, 00, 0, 0, time.UTC)
|
||||||
|
mondayUTC := time.Date(2016, 8, 01, 1, 00, 0, 0, time.UTC)
|
||||||
|
sundayEST := time.Date(2016, 7, 31, 22, 00, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
assert.True(Date.Before(sundayEST, tuesdayUTC))
|
||||||
|
assert.False(Date.Before(sundayEST, mondayUTC))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextMarketOpen(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
beforeOpen := time.Date(2016, 07, 18, 9, 0, 0, 0, Date.Eastern())
|
||||||
|
todayOpen := time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
afterOpen := time.Date(2016, 07, 18, 9, 31, 0, 0, Date.Eastern())
|
||||||
|
tomorrowOpen := time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
afterFriday := time.Date(2016, 07, 22, 9, 31, 0, 0, Date.Eastern())
|
||||||
|
mondayOpen := time.Date(2016, 07, 25, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
assert.True(todayOpen.Equal(Date.NextMarketOpen(beforeOpen, NYSEOpen, Date.IsNYSEHoliday)))
|
||||||
|
assert.True(tomorrowOpen.Equal(Date.NextMarketOpen(afterOpen, NYSEOpen, Date.IsNYSEHoliday)))
|
||||||
|
assert.True(mondayOpen.Equal(Date.NextMarketOpen(afterFriday, NYSEOpen, Date.IsNYSEHoliday)))
|
||||||
|
assert.True(mondayOpen.Equal(Date.NextMarketOpen(weekend, NYSEOpen, Date.IsNYSEHoliday)))
|
||||||
|
|
||||||
|
assert.Equal(Date.Eastern(), todayOpen.Location())
|
||||||
|
assert.Equal(Date.Eastern(), tomorrowOpen.Location())
|
||||||
|
assert.Equal(Date.Eastern(), mondayOpen.Location())
|
||||||
|
|
||||||
|
testRegression := time.Date(2016, 07, 18, 16, 0, 0, 0, Date.Eastern())
|
||||||
|
shouldbe := time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
assert.True(shouldbe.Equal(Date.NextMarketOpen(testRegression, NYSEOpen, Date.IsNYSEHoliday)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextMarketClose(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
beforeClose := time.Date(2016, 07, 18, 15, 0, 0, 0, Date.Eastern())
|
||||||
|
todayClose := time.Date(2016, 07, 18, 16, 00, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
afterClose := time.Date(2016, 07, 18, 16, 1, 0, 0, Date.Eastern())
|
||||||
|
tomorrowClose := time.Date(2016, 07, 19, 16, 00, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
afterFriday := time.Date(2016, 07, 22, 16, 1, 0, 0, Date.Eastern())
|
||||||
|
mondayClose := time.Date(2016, 07, 25, 16, 0, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
assert.True(todayClose.Equal(Date.NextMarketClose(beforeClose, NYSEClose, Date.IsNYSEHoliday)))
|
||||||
|
assert.True(tomorrowClose.Equal(Date.NextMarketClose(afterClose, NYSEClose, Date.IsNYSEHoliday)))
|
||||||
|
assert.True(mondayClose.Equal(Date.NextMarketClose(afterFriday, NYSEClose, Date.IsNYSEHoliday)))
|
||||||
|
assert.True(mondayClose.Equal(Date.NextMarketClose(weekend, NYSEClose, Date.IsNYSEHoliday)))
|
||||||
|
|
||||||
|
assert.Equal(Date.Eastern(), todayClose.Location())
|
||||||
|
assert.Equal(Date.Eastern(), tomorrowClose.Location())
|
||||||
|
assert.Equal(Date.Eastern(), mondayClose.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateMarketSecondsBetween(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
end := time.Date(2016, 07, 22, 16, 00, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
shouldbe := 5 * 6.5 * 60 * 60
|
||||||
|
|
||||||
|
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen, NYSEClose, Date.IsNYSEHoliday))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateMarketSecondsBetween1D(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2016, 07, 22, 9, 45, 0, 0, Date.Eastern())
|
||||||
|
end := time.Date(2016, 07, 22, 15, 45, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
shouldbe := 6 * 60 * 60
|
||||||
|
|
||||||
|
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen, NYSEClose, Date.IsNYSEHoliday))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateMarketSecondsBetweenLTM(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
end := time.Date(2016, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
|
shouldbe := 253 * 6.5 * 60 * 60 //253 full market days since this date last year.
|
||||||
|
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen, NYSEClose, Date.IsNYSEHoliday))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateNextHour(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
next := Date.NextHour(start)
|
||||||
|
assert.Equal(2015, next.Year())
|
||||||
|
assert.Equal(07, next.Month())
|
||||||
|
assert.Equal(01, next.Day())
|
||||||
|
assert.Equal(10, next.Hour())
|
||||||
|
assert.Equal(00, next.Minute())
|
||||||
|
|
||||||
|
next = Date.NextHour(next)
|
||||||
|
assert.Equal(11, next.Hour())
|
||||||
|
|
||||||
|
next = Date.NextHour(next)
|
||||||
|
assert.Equal(12, next.Hour())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateNextDayOfWeek(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
weds := Date.Date(2016, 8, 10, time.UTC)
|
||||||
|
fri := Date.Date(2016, 8, 12, time.UTC)
|
||||||
|
sun := Date.Date(2016, 8, 14, time.UTC)
|
||||||
|
mon := Date.Date(2016, 8, 15, time.UTC)
|
||||||
|
weds2 := Date.Date(2016, 8, 17, time.UTC)
|
||||||
|
|
||||||
|
nextFri := Date.NextDayOfWeek(weds, time.Friday)
|
||||||
|
nextSunday := Date.NextDayOfWeek(weds, time.Sunday)
|
||||||
|
nextMonday := Date.NextDayOfWeek(weds, time.Monday)
|
||||||
|
nextWeds := Date.NextDayOfWeek(weds, time.Wednesday)
|
||||||
|
|
||||||
|
assert.Equal(fri.Year(), nextFri.Year())
|
||||||
|
assert.Equal(fri.Month(), nextFri.Month())
|
||||||
|
assert.Equal(fri.Day(), nextFri.Day())
|
||||||
|
|
||||||
|
assert.Equal(sun.Year(), nextSunday.Year())
|
||||||
|
assert.Equal(sun.Month(), nextSunday.Month())
|
||||||
|
assert.Equal(sun.Day(), nextSunday.Day())
|
||||||
|
|
||||||
|
assert.Equal(mon.Year(), nextMonday.Year())
|
||||||
|
assert.Equal(mon.Month(), nextMonday.Month())
|
||||||
|
assert.Equal(mon.Day(), nextMonday.Day())
|
||||||
|
|
||||||
|
assert.Equal(weds2.Year(), nextWeds.Year())
|
||||||
|
assert.Equal(weds2.Month(), nextWeds.Month())
|
||||||
|
assert.Equal(weds2.Day(), nextWeds.Day())
|
||||||
|
|
||||||
|
assert.Equal(time.UTC, nextFri.Location())
|
||||||
|
assert.Equal(time.UTC, nextSunday.Location())
|
||||||
|
assert.Equal(time.UTC, nextMonday.Location())
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ const (
|
||||||
type EMASeries struct {
|
type EMASeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
Period int
|
Period int
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValueProvider
|
||||||
|
@ -28,7 +28,7 @@ func (ema EMASeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (ema EMASeries) GetYAxis() yAxisType {
|
func (ema EMASeries) GetYAxis() YAxisType {
|
||||||
return ema.YAxis
|
return ema.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
44
examples/market_hours/main.go
Normal file
44
examples/market_hours/main.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
start := chart.Date.Date(2016, 6, 20, chart.Date.Eastern())
|
||||||
|
end := chart.Date.Date(2016, 07, 21, chart.Date.Eastern())
|
||||||
|
xv := chart.Sequence.MarketHours(start, end, chart.NYSEOpen, chart.NYSEClose, chart.Date.IsNYSEHoliday)
|
||||||
|
yv := chart.Sequence.RandomWithAverage(len(xv), 200, 10)
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
|
Range: &chart.MarketHoursRange{
|
||||||
|
MarketOpen: chart.NYSEOpen,
|
||||||
|
MarketClose: chart.NYSEClose,
|
||||||
|
HolidayProvider: chart.Date.IsNYSEHoliday,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
chart.TimeSeries{
|
||||||
|
XValues: xv,
|
||||||
|
YValues: yv,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ package chart
|
||||||
type HistogramSeries struct {
|
type HistogramSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValueProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ func (hs HistogramSeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which yaxis the series is mapped to.
|
// GetYAxis returns which yaxis the series is mapped to.
|
||||||
func (hs HistogramSeries) GetYAxis() yAxisType {
|
func (hs HistogramSeries) GetYAxis() YAxisType {
|
||||||
return hs.YAxis
|
return hs.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ package chart
|
||||||
type LinearRegressionSeries struct {
|
type LinearRegressionSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
Window int
|
Window int
|
||||||
Offset int
|
Offset int
|
||||||
|
@ -28,7 +28,7 @@ func (lrs LinearRegressionSeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (lrs LinearRegressionSeries) GetYAxis() yAxisType {
|
func (lrs LinearRegressionSeries) GetYAxis() YAxisType {
|
||||||
return lrs.YAxis
|
return lrs.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ const (
|
||||||
type MACDSeries struct {
|
type MACDSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValueProvider
|
||||||
|
|
||||||
PrimaryPeriod int
|
PrimaryPeriod int
|
||||||
|
@ -56,7 +56,7 @@ func (macd MACDSeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (macd MACDSeries) GetYAxis() yAxisType {
|
func (macd MACDSeries) GetYAxis() YAxisType {
|
||||||
return macd.YAxis
|
return macd.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func (macd *MACDSeries) ensureChildSeries() {
|
||||||
type MACDSignalSeries struct {
|
type MACDSignalSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValueProvider
|
||||||
|
|
||||||
PrimaryPeriod int
|
PrimaryPeriod int
|
||||||
|
@ -150,7 +150,7 @@ func (macds MACDSignalSeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (macds MACDSignalSeries) GetYAxis() yAxisType {
|
func (macds MACDSignalSeries) GetYAxis() YAxisType {
|
||||||
return macds.YAxis
|
return macds.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange
|
||||||
type MACDLineSeries struct {
|
type MACDLineSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValueProvider
|
||||||
|
|
||||||
PrimaryPeriod int
|
PrimaryPeriod int
|
||||||
|
@ -223,7 +223,7 @@ func (macdl MACDLineSeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (macdl MACDLineSeries) GetYAxis() yAxisType {
|
func (macdl MACDLineSeries) GetYAxis() YAxisType {
|
||||||
return macdl.YAxis
|
return macdl.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/date"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarketHoursRange is a special type of range that compresses a time range into just the
|
// MarketHoursRange is a special type of range that compresses a time range into just the
|
||||||
|
@ -16,13 +14,18 @@ type MarketHoursRange struct {
|
||||||
MarketOpen time.Time
|
MarketOpen time.Time
|
||||||
MarketClose time.Time
|
MarketClose time.Time
|
||||||
|
|
||||||
HolidayProvider date.HolidayProvider
|
HolidayProvider HolidayProvider
|
||||||
|
|
||||||
ValueFormatter ValueFormatter
|
ValueFormatter ValueFormatter
|
||||||
|
|
||||||
Domain int
|
Domain int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTimezone returns the timezone for the market hours range.
|
||||||
|
func (mhr MarketHoursRange) GetTimezone() *time.Location {
|
||||||
|
return mhr.GetMarketOpen().Location()
|
||||||
|
}
|
||||||
|
|
||||||
// IsZero returns if the range is setup or not.
|
// IsZero returns if the range is setup or not.
|
||||||
func (mhr MarketHoursRange) IsZero() bool {
|
func (mhr MarketHoursRange) IsZero() bool {
|
||||||
return mhr.Min.IsZero() && mhr.Max.IsZero()
|
return mhr.Min.IsZero() && mhr.Max.IsZero()
|
||||||
|
@ -40,7 +43,7 @@ func (mhr MarketHoursRange) GetMax() float64 {
|
||||||
|
|
||||||
// GetEffectiveMax gets either the close on the max, or the max itself.
|
// GetEffectiveMax gets either the close on the max, or the max itself.
|
||||||
func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
||||||
maxClose := date.On(mhr.MarketClose, mhr.Max)
|
maxClose := Date.On(mhr.MarketClose, mhr.Max)
|
||||||
if maxClose.After(mhr.Max) {
|
if maxClose.After(mhr.Max) {
|
||||||
return maxClose
|
return maxClose
|
||||||
}
|
}
|
||||||
|
@ -50,11 +53,13 @@ func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
||||||
// SetMin sets the min value.
|
// SetMin sets the min value.
|
||||||
func (mhr *MarketHoursRange) SetMin(min float64) {
|
func (mhr *MarketHoursRange) SetMin(min float64) {
|
||||||
mhr.Min = Float64ToTime(min)
|
mhr.Min = Float64ToTime(min)
|
||||||
|
mhr.Min = mhr.Min.In(mhr.GetTimezone())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMax sets the max value.
|
// SetMax sets the max value.
|
||||||
func (mhr *MarketHoursRange) SetMax(max float64) {
|
func (mhr *MarketHoursRange) SetMax(max float64) {
|
||||||
mhr.Max = Float64ToTime(max)
|
mhr.Max = Float64ToTime(max)
|
||||||
|
mhr.Max = mhr.Max.In(mhr.GetTimezone())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelta gets the delta.
|
// GetDelta gets the delta.
|
||||||
|
@ -75,53 +80,102 @@ func (mhr *MarketHoursRange) SetDomain(domain int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHolidayProvider coalesces a userprovided holiday provider and the date.DefaultHolidayProvider.
|
// GetHolidayProvider coalesces a userprovided holiday provider and the date.DefaultHolidayProvider.
|
||||||
func (mhr MarketHoursRange) GetHolidayProvider() date.HolidayProvider {
|
func (mhr MarketHoursRange) GetHolidayProvider() HolidayProvider {
|
||||||
if mhr.HolidayProvider == nil {
|
if mhr.HolidayProvider == nil {
|
||||||
return date.DefaultHolidayProvider
|
return defaultHolidayProvider
|
||||||
}
|
}
|
||||||
return mhr.HolidayProvider
|
return mhr.HolidayProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMarketOpen returns the market open time.
|
||||||
|
func (mhr MarketHoursRange) GetMarketOpen() time.Time {
|
||||||
|
if mhr.MarketOpen.IsZero() {
|
||||||
|
return NYSEOpen
|
||||||
|
}
|
||||||
|
return mhr.MarketOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMarketClose returns the market close time.
|
||||||
|
func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
||||||
|
if mhr.MarketClose.IsZero() {
|
||||||
|
return NYSEClose
|
||||||
|
}
|
||||||
|
return mhr.MarketClose
|
||||||
|
}
|
||||||
|
|
||||||
// GetTicks returns the ticks for the range.
|
// GetTicks returns the ticks for the range.
|
||||||
// This is to override the default continous ticks that would be generated for the range.
|
// This is to override the default continous ticks that would be generated for the range.
|
||||||
func (mhr *MarketHoursRange) GetTicks(vf ValueFormatter) []Tick {
|
func (mhr *MarketHoursRange) GetTicks(r Renderer, defaults Style, vf ValueFormatter) []Tick {
|
||||||
var ticks []Tick
|
times := Sequence.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
||||||
cursor := date.On(mhr.MarketClose, mhr.Min)
|
if timesWidth <= mhr.Domain {
|
||||||
maxClose := date.On(mhr.MarketClose, mhr.Max)
|
return mhr.makeTicks(vf, times)
|
||||||
|
|
||||||
for date.BeforeDate(cursor, maxClose) {
|
|
||||||
if date.IsWeekDay(cursor.Weekday()) && !mhr.GetHolidayProvider()(cursor) {
|
|
||||||
ticks = append(ticks, Tick{
|
|
||||||
Value: TimeToFloat64(cursor),
|
|
||||||
Label: vf(cursor),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = cursor.AddDate(0, 0, 1)
|
times = Sequence.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
endMarketClose := date.On(mhr.MarketClose, cursor)
|
times = Sequence.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
if date.IsWeekDay(endMarketClose.Weekday()) && !mhr.GetHolidayProvider()(endMarketClose) {
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
ticks = append(ticks, Tick{
|
if timesWidth <= mhr.Domain {
|
||||||
Value: TimeToFloat64(endMarketClose),
|
return mhr.makeTicks(vf, times)
|
||||||
Label: vf(endMarketClose),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
times = Sequence.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
|
}
|
||||||
|
|
||||||
|
times = Sequence.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenerateContinuousTicks(r, mhr, false, defaults, vf)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MarketHoursRange) measureTimes(r Renderer, defaults Style, vf ValueFormatter, times []time.Time) int {
|
||||||
|
defaults.GetTextOptions().WriteToRenderer(r)
|
||||||
|
var total int
|
||||||
|
for index, t := range times {
|
||||||
|
timeLabel := vf(t)
|
||||||
|
|
||||||
|
labelBox := r.MeasureText(timeLabel)
|
||||||
|
total += labelBox.Width()
|
||||||
|
if index > 0 {
|
||||||
|
total += DefaultMinimumTickHorizontalSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MarketHoursRange) makeTicks(vf ValueFormatter, times []time.Time) []Tick {
|
||||||
|
ticks := make([]Tick, len(times))
|
||||||
|
for index, t := range times {
|
||||||
|
ticks[index] = Tick{
|
||||||
|
Value: TimeToFloat64(t),
|
||||||
|
Label: vf(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
return ticks
|
return ticks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mhr MarketHoursRange) String() string {
|
func (mhr MarketHoursRange) String() string {
|
||||||
return fmt.Sprintf("MarketHoursRange [%s, %s] => %d", mhr.Min.Format(DefaultDateMinuteFormat), mhr.Max.Format(DefaultDateMinuteFormat), mhr.Domain)
|
return fmt.Sprintf("MarketHoursRange [%s, %s] => %d", mhr.Min.Format(time.RFC3339), mhr.Max.Format(time.RFC3339), mhr.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate maps a given value into the ContinuousRange space.
|
// Translate maps a given value into the ContinuousRange space.
|
||||||
func (mhr MarketHoursRange) Translate(value float64) int {
|
func (mhr MarketHoursRange) Translate(value float64) int {
|
||||||
valueTime := Float64ToTime(value)
|
valueTime := Float64ToTime(value)
|
||||||
valueTimeEastern := valueTime.In(date.Eastern())
|
valueTimeEastern := valueTime.In(Date.Eastern())
|
||||||
totalSeconds := date.CalculateMarketSecondsBetween(mhr.Min, mhr.GetEffectiveMax(), mhr.MarketOpen, mhr.MarketClose, mhr.HolidayProvider)
|
totalSeconds := Date.CalculateMarketSecondsBetween(mhr.Min, mhr.GetEffectiveMax(), mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
||||||
valueDelta := date.CalculateMarketSecondsBetween(mhr.Min, valueTimeEastern, mhr.MarketOpen, mhr.MarketClose, mhr.HolidayProvider)
|
valueDelta := Date.CalculateMarketSecondsBetween(mhr.Min, valueTimeEastern, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
||||||
translated := int((float64(valueDelta) / float64(totalSeconds)) * float64(mhr.Domain))
|
translated := int((float64(valueDelta) / float64(totalSeconds)) * float64(mhr.Domain))
|
||||||
return translated
|
return translated
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,17 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
assert "github.com/blendlabs/go-assert"
|
||||||
"github.com/wcharczuk/go-chart/date"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarketHoursRangeGetDelta(t *testing.T) {
|
func TestMarketHoursRangeGetDelta(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
r := &MarketHoursRange{
|
r := &MarketHoursRange{
|
||||||
Min: time.Date(2016, 07, 19, 9, 30, 0, 0, date.Eastern()),
|
Min: time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern()),
|
||||||
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, date.Eastern()),
|
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, Date.Eastern()),
|
||||||
MarketOpen: date.NYSEOpen,
|
MarketOpen: NYSEOpen,
|
||||||
MarketClose: date.NYSEClose,
|
MarketClose: NYSEClose,
|
||||||
HolidayProvider: date.IsNYSEHoliday,
|
HolidayProvider: Date.IsNYSEHoliday,
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NotZero(r.GetDelta())
|
assert.NotZero(r.GetDelta())
|
||||||
|
@ -26,15 +25,15 @@ func TestMarketHoursRangeTranslate(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
r := &MarketHoursRange{
|
r := &MarketHoursRange{
|
||||||
Min: time.Date(2016, 07, 18, 9, 30, 0, 0, date.Eastern()),
|
Min: time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern()),
|
||||||
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, date.Eastern()),
|
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, Date.Eastern()),
|
||||||
MarketOpen: date.NYSEOpen,
|
MarketOpen: NYSEOpen,
|
||||||
MarketClose: date.NYSEClose,
|
MarketClose: NYSEClose,
|
||||||
HolidayProvider: date.IsNYSEHoliday,
|
HolidayProvider: Date.IsNYSEHoliday,
|
||||||
Domain: 1000,
|
Domain: 1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
weds := time.Date(2016, 07, 20, 9, 30, 0, 0, date.Eastern())
|
weds := time.Date(2016, 07, 20, 9, 30, 0, 0, Date.Eastern())
|
||||||
|
|
||||||
assert.Equal(0, r.Translate(TimeToFloat64(r.Min)))
|
assert.Equal(0, r.Translate(TimeToFloat64(r.Min)))
|
||||||
assert.Equal(400, r.Translate(TimeToFloat64(weds)))
|
assert.Equal(400, r.Translate(TimeToFloat64(weds)))
|
||||||
|
@ -44,18 +43,30 @@ func TestMarketHoursRangeTranslate(t *testing.T) {
|
||||||
func TestMarketHoursRangeGetTicks(t *testing.T) {
|
func TestMarketHoursRangeGetTicks(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
r := &MarketHoursRange{
|
r, err := PNG(1024, 1024)
|
||||||
Min: time.Date(2016, 07, 18, 9, 30, 0, 0, date.Eastern()),
|
assert.Nil(err)
|
||||||
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, date.Eastern()),
|
|
||||||
MarketOpen: date.NYSEOpen,
|
f, err := GetDefaultFont()
|
||||||
MarketClose: date.NYSEClose,
|
assert.Nil(err)
|
||||||
HolidayProvider: date.IsNYSEHoliday,
|
|
||||||
Domain: 1000,
|
defaults := Style{
|
||||||
|
Font: f,
|
||||||
|
FontSize: 10,
|
||||||
|
FontColor: ColorBlack,
|
||||||
}
|
}
|
||||||
|
|
||||||
ticks := r.GetTicks(TimeValueFormatter)
|
ra := &MarketHoursRange{
|
||||||
|
Min: Date.On(NYSEOpen, Date.Date(2016, 07, 18, Date.Eastern())),
|
||||||
|
Max: Date.On(NYSEClose, Date.Date(2016, 07, 22, Date.Eastern())),
|
||||||
|
MarketOpen: NYSEOpen,
|
||||||
|
MarketClose: NYSEClose,
|
||||||
|
HolidayProvider: Date.IsNYSEHoliday,
|
||||||
|
Domain: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
ticks := ra.GetTicks(r, defaults, TimeValueFormatter)
|
||||||
assert.NotEmpty(ticks)
|
assert.NotEmpty(ticks)
|
||||||
assert.Len(ticks, 5)
|
assert.Len(ticks, 5)
|
||||||
assert.NotEqual(TimeToFloat64(r.Min), ticks[0].Value)
|
assert.NotEqual(TimeToFloat64(ra.Min), ticks[0].Value)
|
||||||
assert.NotEmpty(ticks[0].Label)
|
assert.NotEmpty(ticks[0].Label)
|
||||||
}
|
}
|
||||||
|
|
104
sequence.go
104
sequence.go
|
@ -45,6 +45,19 @@ func (s sequence) Random(samples int, scale float64) []float64 {
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Random generates a fixed length sequence of random values with a given average, above and below that average by (-scale, scale)
|
||||||
|
func (s sequence) RandomWithAverage(samples int, average, scale float64) []float64 {
|
||||||
|
rnd := rand.New(rand.NewSource(time.Now().Unix()))
|
||||||
|
values := make([]float64, samples)
|
||||||
|
|
||||||
|
for x := 0; x < samples; x++ {
|
||||||
|
jitter := scale - (rnd.Float64() * (2 * scale))
|
||||||
|
values[x] = average + jitter
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
// Days generates a sequence of timestamps by day, from -days to today.
|
// Days generates a sequence of timestamps by day, from -days to today.
|
||||||
func (s sequence) Days(days int) []time.Time {
|
func (s sequence) Days(days int) []time.Time {
|
||||||
var values []time.Time
|
var values []time.Time
|
||||||
|
@ -53,3 +66,94 @@ func (s sequence) Days(days int) []time.Time {
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s sequence) MarketHours(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := Date.On(marketOpen, from)
|
||||||
|
toClose := Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
todayOpen := Date.On(marketOpen, cursor)
|
||||||
|
todayClose := Date.On(marketClose, cursor)
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||||
|
|
||||||
|
if (cursor.Equal(todayOpen) || cursor.After(todayOpen)) && (cursor.Equal(todayClose) || cursor.Before(todayClose)) && isValidTradingDay {
|
||||||
|
times = append(times, cursor)
|
||||||
|
}
|
||||||
|
if cursor.After(todayClose) {
|
||||||
|
cursor = Date.NextMarketOpen(cursor, marketOpen, isHoliday)
|
||||||
|
} else {
|
||||||
|
cursor = Date.NextHour(cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sequence) MarketHourQuarters(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := Date.On(marketOpen, from)
|
||||||
|
toClose := Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||||
|
|
||||||
|
if isValidTradingDay {
|
||||||
|
todayOpen := Date.On(marketOpen, cursor)
|
||||||
|
todayNoon := Date.NoonOn(cursor)
|
||||||
|
today2pm := Date.On(Date.Time(14, 0, 0, 0, cursor.Location()), cursor)
|
||||||
|
todayClose := Date.On(marketClose, cursor)
|
||||||
|
times = append(times, todayOpen, todayNoon, today2pm, todayClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = Date.NextDay(cursor)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sequence) MarketDayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := Date.On(marketOpen, from)
|
||||||
|
toClose := Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||||
|
if isValidTradingDay {
|
||||||
|
todayClose := Date.On(marketClose, cursor)
|
||||||
|
times = append(times, todayClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = Date.NextDay(cursor)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sequence) MarketDayAlternateCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := Date.On(marketOpen, from)
|
||||||
|
toClose := Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||||
|
if isValidTradingDay {
|
||||||
|
todayClose := Date.On(marketClose, cursor)
|
||||||
|
times = append(times, todayClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = cursor.AddDate(0, 0, 2)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sequence) MarketDayMondayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := Date.On(marketClose, from)
|
||||||
|
toClose := Date.On(marketClose, to)
|
||||||
|
|
||||||
|
for cursor.Equal(toClose) || cursor.Before(toClose) {
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||||
|
if isValidTradingDay {
|
||||||
|
times = append(times, cursor)
|
||||||
|
}
|
||||||
|
println("advance to next monday", cursor.Format(DefaultDateFormat))
|
||||||
|
cursor = Date.NextDayOfWeek(cursor, time.Monday)
|
||||||
|
println(cursor.Format(DefaultDateFormat))
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
assert "github.com/blendlabs/go-assert"
|
||||||
)
|
)
|
||||||
|
@ -15,3 +16,30 @@ func TestSequenceFloat64(t *testing.T) {
|
||||||
desc := Sequence.Float64(10.0, 1.0)
|
desc := Sequence.Float64(10.0, 1.0)
|
||||||
assert.Len(desc, 10)
|
assert.Len(desc, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSequenceMarketHours(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
today := time.Date(2016, 07, 01, 12, 0, 0, 0, Date.Eastern())
|
||||||
|
mh := Sequence.MarketHours(today, today, NYSEOpen, NYSEClose, Date.IsNYSEHoliday)
|
||||||
|
assert.Len(mh, 8)
|
||||||
|
assert.Equal(Date.Eastern(), mh[0].Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceMarketQuarters(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
today := time.Date(2016, 07, 01, 12, 0, 0, 0, Date.Eastern())
|
||||||
|
mh := Sequence.MarketHourQuarters(today, today, NYSEOpen, NYSEClose, Date.IsNYSEHoliday)
|
||||||
|
assert.Len(mh, 4)
|
||||||
|
assert.Equal(9, mh[0].Hour())
|
||||||
|
assert.Equal(30, mh[0].Minute())
|
||||||
|
assert.Equal(Date.Eastern(), mh[0].Location())
|
||||||
|
|
||||||
|
assert.Equal(12, mh[1].Hour())
|
||||||
|
assert.Equal(00, mh[1].Minute())
|
||||||
|
assert.Equal(Date.Eastern(), mh[1].Location())
|
||||||
|
|
||||||
|
assert.Equal(14, mh[2].Hour())
|
||||||
|
assert.Equal(00, mh[2].Minute())
|
||||||
|
assert.Equal(Date.Eastern(), mh[2].Location())
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package chart
|
||||||
// Series is an alias to Renderable.
|
// Series is an alias to Renderable.
|
||||||
type Series interface {
|
type Series interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
GetYAxis() yAxisType
|
GetYAxis() YAxisType
|
||||||
GetStyle() Style
|
GetStyle() Style
|
||||||
Render(r Renderer, canvasBox Box, xrange, yrange Range, s Style)
|
Render(r Renderer, canvasBox Box, xrange, yrange Range, s Style)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const (
|
||||||
type SMASeries struct {
|
type SMASeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
Period int
|
Period int
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValueProvider
|
||||||
|
@ -26,7 +26,7 @@ func (sma SMASeries) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (sma SMASeries) GetYAxis() yAxisType {
|
func (sma SMASeries) GetYAxis() YAxisType {
|
||||||
return sma.YAxis
|
return sma.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
style.go
19
style.go
|
@ -8,6 +8,13 @@ import (
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StyleShow is a prebuilt style with the `Show` property set to true.
|
||||||
|
func StyleShow() Style {
|
||||||
|
return Style{
|
||||||
|
Show: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Style is a simple style set.
|
// Style is a simple style set.
|
||||||
type Style struct {
|
type Style struct {
|
||||||
Show bool
|
Show bool
|
||||||
|
@ -22,9 +29,9 @@ type Style struct {
|
||||||
FontColor drawing.Color
|
FontColor drawing.Color
|
||||||
Font *truetype.Font
|
Font *truetype.Font
|
||||||
|
|
||||||
TextHorizontalAlign textHorizontalAlign
|
TextHorizontalAlign TextHorizontalAlign
|
||||||
TextVerticalAlign textVerticalAlign
|
TextVerticalAlign TextVerticalAlign
|
||||||
TextWrap textWrap
|
TextWrap TextWrap
|
||||||
TextLineSpacing int
|
TextLineSpacing int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +198,7 @@ func (s Style) GetPadding(defaults ...Box) Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTextHorizontalAlign returns the horizontal alignment.
|
// GetTextHorizontalAlign returns the horizontal alignment.
|
||||||
func (s Style) GetTextHorizontalAlign(defaults ...textHorizontalAlign) textHorizontalAlign {
|
func (s Style) GetTextHorizontalAlign(defaults ...TextHorizontalAlign) TextHorizontalAlign {
|
||||||
if s.TextHorizontalAlign == TextHorizontalAlignUnset {
|
if s.TextHorizontalAlign == TextHorizontalAlignUnset {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
return defaults[0]
|
return defaults[0]
|
||||||
|
@ -202,7 +209,7 @@ func (s Style) GetTextHorizontalAlign(defaults ...textHorizontalAlign) textHoriz
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTextVerticalAlign returns the vertical alignment.
|
// GetTextVerticalAlign returns the vertical alignment.
|
||||||
func (s Style) GetTextVerticalAlign(defaults ...textVerticalAlign) textVerticalAlign {
|
func (s Style) GetTextVerticalAlign(defaults ...TextVerticalAlign) TextVerticalAlign {
|
||||||
if s.TextVerticalAlign == TextVerticalAlignUnset {
|
if s.TextVerticalAlign == TextVerticalAlignUnset {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
return defaults[0]
|
return defaults[0]
|
||||||
|
@ -213,7 +220,7 @@ func (s Style) GetTextVerticalAlign(defaults ...textVerticalAlign) textVerticalA
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTextWrap returns the word wrap.
|
// GetTextWrap returns the word wrap.
|
||||||
func (s Style) GetTextWrap(defaults ...textWrap) textWrap {
|
func (s Style) GetTextWrap(defaults ...TextWrap) TextWrap {
|
||||||
if s.TextWrap == TextWrapUnset {
|
if s.TextWrap == TextWrapUnset {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
return defaults[0]
|
return defaults[0]
|
||||||
|
|
40
text.go
40
text.go
|
@ -3,51 +3,51 @@ package chart
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
// TextHorizontalAlign is an enum for the horizontal alignment options.
|
// TextHorizontalAlign is an enum for the horizontal alignment options.
|
||||||
type textHorizontalAlign int
|
type TextHorizontalAlign int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TextHorizontalAlignUnset is the unset state for text horizontal alignment.
|
// TextHorizontalAlignUnset is the unset state for text horizontal alignment.
|
||||||
TextHorizontalAlignUnset textHorizontalAlign = 0
|
TextHorizontalAlignUnset TextHorizontalAlign = 0
|
||||||
// TextHorizontalAlignLeft aligns a string horizontally so that it's left ligature starts at horizontal pixel 0.
|
// TextHorizontalAlignLeft aligns a string horizontally so that it's left ligature starts at horizontal pixel 0.
|
||||||
TextHorizontalAlignLeft textHorizontalAlign = 1
|
TextHorizontalAlignLeft TextHorizontalAlign = 1
|
||||||
// TextHorizontalAlignCenter left aligns a string horizontally so that there are equal pixels
|
// TextHorizontalAlignCenter left aligns a string horizontally so that there are equal pixels
|
||||||
// to the left and to the right of a string within a box.
|
// to the left and to the right of a string within a box.
|
||||||
TextHorizontalAlignCenter textHorizontalAlign = 2
|
TextHorizontalAlignCenter TextHorizontalAlign = 2
|
||||||
// TextHorizontalAlignRight right aligns a string horizontally so that the right ligature ends at the right-most pixel
|
// TextHorizontalAlignRight right aligns a string horizontally so that the right ligature ends at the right-most pixel
|
||||||
// of a box.
|
// of a box.
|
||||||
TextHorizontalAlignRight textHorizontalAlign = 3
|
TextHorizontalAlignRight TextHorizontalAlign = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// TextWrap is an enum for the word wrap options.
|
// TextWrap is an enum for the word wrap options.
|
||||||
type textWrap int
|
type TextWrap int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TextWrapUnset is the unset state for text wrap options.
|
// TextWrapUnset is the unset state for text wrap options.
|
||||||
TextWrapUnset textWrap = 0
|
TextWrapUnset TextWrap = 0
|
||||||
// TextWrapNone will spill text past horizontal boundaries.
|
// TextWrapNone will spill text past horizontal boundaries.
|
||||||
TextWrapNone textWrap = 1
|
TextWrapNone TextWrap = 1
|
||||||
// TextWrapWord will split a string on words (i.e. spaces) to fit within a horizontal boundary.
|
// TextWrapWord will split a string on words (i.e. spaces) to fit within a horizontal boundary.
|
||||||
TextWrapWord textWrap = 2
|
TextWrapWord TextWrap = 2
|
||||||
// TextWrapRune will split a string on a rune (i.e. utf-8 codepage) to fit within a horizontal boundary.
|
// TextWrapRune will split a string on a rune (i.e. utf-8 codepage) to fit within a horizontal boundary.
|
||||||
TextWrapRune textWrap = 3
|
TextWrapRune TextWrap = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// TextVerticalAlign is an enum for the vertical alignment options.
|
// TextVerticalAlign is an enum for the vertical alignment options.
|
||||||
type textVerticalAlign int
|
type TextVerticalAlign int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TextVerticalAlignUnset is the unset state for vertical alignment options.
|
// TextVerticalAlignUnset is the unset state for vertical alignment options.
|
||||||
TextVerticalAlignUnset textVerticalAlign = 0
|
TextVerticalAlignUnset TextVerticalAlign = 0
|
||||||
// TextVerticalAlignBaseline aligns text according to the "baseline" of the string, or where a normal ascender begins.
|
// TextVerticalAlignBaseline aligns text according to the "baseline" of the string, or where a normal ascender begins.
|
||||||
TextVerticalAlignBaseline textVerticalAlign = 1
|
TextVerticalAlignBaseline TextVerticalAlign = 1
|
||||||
// TextVerticalAlignBottom aligns the text according to the lowers pixel of any of the ligatures (ex. g or q both extend below the baseline).
|
// TextVerticalAlignBottom aligns the text according to the lowers pixel of any of the ligatures (ex. g or q both extend below the baseline).
|
||||||
TextVerticalAlignBottom textVerticalAlign = 2
|
TextVerticalAlignBottom TextVerticalAlign = 2
|
||||||
// TextVerticalAlignMiddle aligns the text so that there is an equal amount of space above and below the top and bottom of the ligatures.
|
// TextVerticalAlignMiddle aligns the text so that there is an equal amount of space above and below the top and bottom of the ligatures.
|
||||||
TextVerticalAlignMiddle textVerticalAlign = 3
|
TextVerticalAlignMiddle TextVerticalAlign = 3
|
||||||
// TextVerticalAlignMiddleBaseline aligns the text veritcally so that there is an equal number of pixels above and below the baseline of the string.
|
// TextVerticalAlignMiddleBaseline aligns the text veritcally so that there is an equal number of pixels above and below the baseline of the string.
|
||||||
TextVerticalAlignMiddleBaseline textVerticalAlign = 4
|
TextVerticalAlignMiddleBaseline TextVerticalAlign = 4
|
||||||
// TextVerticalAlignTop alignts the text so that the top of the ligatures are at y-pixel 0 in the container.
|
// TextVerticalAlignTop alignts the text so that the top of the ligatures are at y-pixel 0 in the container.
|
||||||
TextVerticalAlignTop textVerticalAlign = 5
|
TextVerticalAlignTop TextVerticalAlign = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -57,9 +57,9 @@ var (
|
||||||
|
|
||||||
// TextStyle encapsulates text style options.
|
// TextStyle encapsulates text style options.
|
||||||
type TextStyle struct {
|
type TextStyle struct {
|
||||||
HorizontalAlign textHorizontalAlign
|
HorizontalAlign TextHorizontalAlign
|
||||||
VerticalAlign textVerticalAlign
|
VerticalAlign TextVerticalAlign
|
||||||
Wrap textWrap
|
Wrap TextWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
type text struct{}
|
type text struct{}
|
||||||
|
|
2
tick.go
2
tick.go
|
@ -4,7 +4,7 @@ import "math"
|
||||||
|
|
||||||
// TicksProvider is a type that provides ticks.
|
// TicksProvider is a type that provides ticks.
|
||||||
type TicksProvider interface {
|
type TicksProvider interface {
|
||||||
GetTicks(vf ValueFormatter) []Tick
|
GetTicks(r Renderer, defaults Style, vf ValueFormatter) []Tick
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick represents a label on an axis.
|
// Tick represents a label on an axis.
|
||||||
|
|
|
@ -7,7 +7,7 @@ type TimeSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
|
|
||||||
YAxis yAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
XValues []time.Time
|
XValues []time.Time
|
||||||
YValues []float64
|
YValues []float64
|
||||||
|
@ -50,7 +50,7 @@ func (ts TimeSeries) GetValueFormatters() (x, y ValueFormatter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
func (ts TimeSeries) GetYAxis() yAxisType {
|
func (ts TimeSeries) GetYAxis() YAxisType {
|
||||||
return ts.YAxis
|
return ts.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
xaxis.go
11
xaxis.go
|
@ -13,7 +13,7 @@ type XAxis struct {
|
||||||
Range Range
|
Range Range
|
||||||
Ticks []Tick
|
Ticks []Tick
|
||||||
|
|
||||||
TickPosition tickPosition
|
TickPosition TickPosition
|
||||||
|
|
||||||
GridLines []GridLine
|
GridLines []GridLine
|
||||||
GridMajorStyle Style
|
GridMajorStyle Style
|
||||||
|
@ -31,7 +31,7 @@ func (xa XAxis) GetStyle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTickPosition returns the tick position option for the axis.
|
// GetTickPosition returns the tick position option for the axis.
|
||||||
func (xa XAxis) GetTickPosition(defaults ...tickPosition) tickPosition {
|
func (xa XAxis) GetTickPosition(defaults ...TickPosition) TickPosition {
|
||||||
if xa.TickPosition == TickPositionUnset {
|
if xa.TickPosition == TickPositionUnset {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
return defaults[0]
|
return defaults[0]
|
||||||
|
@ -51,7 +51,7 @@ func (xa XAxis) GetTicks(r Renderer, ra Range, defaults Style, vf ValueFormatter
|
||||||
return xa.Ticks
|
return xa.Ticks
|
||||||
}
|
}
|
||||||
if tp, isTickProvider := ra.(TicksProvider); isTickProvider {
|
if tp, isTickProvider := ra.(TicksProvider); isTickProvider {
|
||||||
return tp.GetTicks(vf)
|
return tp.GetTicks(r, defaults, vf)
|
||||||
}
|
}
|
||||||
tickStyle := xa.Style.InheritFrom(defaults)
|
tickStyle := xa.Style.InheritFrom(defaults)
|
||||||
return GenerateContinuousTicks(r, ra, false, tickStyle, vf)
|
return GenerateContinuousTicks(r, ra, false, tickStyle, vf)
|
||||||
|
@ -72,7 +72,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
|
|
||||||
tp := xa.GetTickPosition()
|
tp := xa.GetTickPosition()
|
||||||
|
|
||||||
var left, right, top, bottom = math.MaxInt32, 0, math.MaxInt32, 0
|
var left, right, bottom = math.MaxInt32, 0, 0
|
||||||
for index, t := range ticks {
|
for index, t := range ticks {
|
||||||
v := t.Value
|
v := t.Value
|
||||||
tickStyle.GetTextOptions().WriteToRenderer(r)
|
tickStyle.GetTextOptions().WriteToRenderer(r)
|
||||||
|
@ -94,14 +94,13 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
top = Math.MinInt(top, canvasBox.Bottom)
|
|
||||||
left = Math.MinInt(left, ltx)
|
left = Math.MinInt(left, ltx)
|
||||||
right = Math.MaxInt(right, rtx)
|
right = Math.MaxInt(right, rtx)
|
||||||
bottom = Math.MaxInt(bottom, ty)
|
bottom = Math.MaxInt(bottom, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Box{
|
return Box{
|
||||||
Top: top,
|
Top: canvasBox.Bottom,
|
||||||
Left: left,
|
Left: left,
|
||||||
Right: right,
|
Right: right,
|
||||||
Bottom: bottom,
|
Bottom: bottom,
|
||||||
|
|
4
yaxis.go
4
yaxis.go
|
@ -13,7 +13,7 @@ type YAxis struct {
|
||||||
|
|
||||||
Zero GridLine
|
Zero GridLine
|
||||||
|
|
||||||
AxisType yAxisType
|
AxisType YAxisType
|
||||||
|
|
||||||
ValueFormatter ValueFormatter
|
ValueFormatter ValueFormatter
|
||||||
Range Range
|
Range Range
|
||||||
|
@ -45,7 +45,7 @@ func (ya YAxis) GetTicks(r Renderer, ra Range, defaults Style, vf ValueFormatter
|
||||||
return ya.Ticks
|
return ya.Ticks
|
||||||
}
|
}
|
||||||
if tp, isTickProvider := ra.(TicksProvider); isTickProvider {
|
if tp, isTickProvider := ra.(TicksProvider); isTickProvider {
|
||||||
return tp.GetTicks(vf)
|
return tp.GetTicks(r, defaults, vf)
|
||||||
}
|
}
|
||||||
tickStyle := ya.Style.InheritFrom(defaults)
|
tickStyle := ya.Style.InheritFrom(defaults)
|
||||||
return GenerateContinuousTicks(r, ra, true, tickStyle, vf)
|
return GenerateContinuousTicks(r, ra, true, tickStyle, vf)
|
||||||
|
|
Loading…
Reference in a new issue