102 lines
3.3 KiB
Go
102 lines
3.3 KiB
Go
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package scale
|
|
|
|
// TickOptions specifies constraints for constructing scale ticks.
|
|
//
|
|
// A Ticks method will return the ticks at the lowest level (largest
|
|
// number of ticks) that satisfies all of the constraints. The exact
|
|
// meaning of the tick level differs between scale types, but for all
|
|
// scales higher tick levels result in ticks that are further apart
|
|
// (fewer ticks in a given interval). In general, the minor ticks are
|
|
// the ticks from one level below the major ticks.
|
|
type TickOptions struct {
|
|
// Max is the maximum number of major ticks to return.
|
|
Max int
|
|
|
|
// MinLevel and MaxLevel are the minimum and maximum tick
|
|
// levels to accept, respectively. If they are both 0, there is
|
|
// no limit on acceptable tick levels.
|
|
MinLevel, MaxLevel int
|
|
}
|
|
|
|
// A Ticker computes tick marks for a scale. The "level" of the ticks
|
|
// controls how many ticks there are and how closely they are spaced.
|
|
// Higher levels have fewer ticks, while lower levels have more ticks.
|
|
// For example, on a numerical scale, one could have ticks at every
|
|
// n*(10^level).
|
|
type Ticker interface {
|
|
// CountTicks returns the number of ticks at level in this
|
|
// scale's input range. This is equivalent to
|
|
// len(TicksAtLevel(level)), but should be much more
|
|
// efficient. CountTicks is a weakly monotonically decreasing
|
|
// function of level.
|
|
CountTicks(level int) int
|
|
|
|
// TicksAtLevel returns a slice of "nice" tick values in
|
|
// increasing order at level in this scale's input range.
|
|
// Typically, TicksAtLevel(l+1) is a subset of
|
|
// TicksAtLevel(l). That is, higher levels remove ticks from
|
|
// lower levels.
|
|
TicksAtLevel(level int) interface{}
|
|
}
|
|
|
|
// FindLevel returns the lowest level that satisfies the constraints
|
|
// given by o:
|
|
//
|
|
// * ticker.CountTicks(level) <= o.Max
|
|
//
|
|
// * o.MinLevel <= level <= o.MaxLevel (if MinLevel and MaxLevel != 0).
|
|
//
|
|
// If the constraints cannot be satisfied, it returns 0, false.
|
|
//
|
|
// guess is the level to start the optimization at.
|
|
func (o *TickOptions) FindLevel(ticker Ticker, guess int) (int, bool) {
|
|
minLevel, maxLevel := o.MinLevel, o.MaxLevel
|
|
if minLevel == 0 && maxLevel == 0 {
|
|
minLevel, maxLevel = -1000, 1000
|
|
} else if minLevel > maxLevel {
|
|
return 0, false
|
|
}
|
|
if o.Max < 1 {
|
|
return 0, false
|
|
}
|
|
|
|
// Start with the initial guess.
|
|
l := guess
|
|
if l < minLevel {
|
|
l = minLevel
|
|
} else if l > maxLevel {
|
|
l = maxLevel
|
|
}
|
|
|
|
// Optimize count against o.Max.
|
|
if ticker.CountTicks(l) <= o.Max {
|
|
// We're satisfying the o.Max and min/maxLevel
|
|
// constraints. count is monotonically decreasing, so
|
|
// decrease level to increase the count until we
|
|
// violate either o.Max or minLevel.
|
|
for l--; l >= minLevel && ticker.CountTicks(l) <= o.Max; l-- {
|
|
}
|
|
// We went one too far.
|
|
l++
|
|
} else {
|
|
// We're over o.Max. Increase level to decrease the
|
|
// count until we go below o.Max. This may cause us to
|
|
// violate maxLevel.
|
|
for l++; l <= maxLevel && ticker.CountTicks(l) > o.Max; l++ {
|
|
}
|
|
if l > maxLevel {
|
|
// We can't satisfy both o.Max and maxLevel.
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
// At this point l is the lowest value that satisfies the
|
|
// o.Max, minLevel, and maxLevel constraints.
|
|
|
|
return l, true
|
|
}
|