introduces the Range interface (instead of a concrete type).

This commit is contained in:
Will Charczuk 2016-07-21 14:11:27 -07:00
parent 8af50213c3
commit b0934ee2e3
27 changed files with 331 additions and 172 deletions

View File

@ -40,7 +40,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
Bottom: 0,
}
if as.Style.IsZero() || as.Style.Show {
style := as.Style.WithDefaultsFrom(Style{
style := as.Style.InheritFrom(Style{
Font: defaults.Font,
FillColor: DefaultAnnotationFillColor,
FontSize: DefaultAnnotationFontSize,
@ -64,7 +64,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
// Render draws the series.
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
if as.Style.IsZero() || as.Style.Show {
style := as.Style.WithDefaultsFrom(Style{
style := as.Style.InheritFrom(Style{
Font: defaults.Font,
FontColor: DefaultTextColor,
FillColor: DefaultAnnotationFillColor,

View File

@ -29,12 +29,12 @@ func TestAnnotationSeriesMeasure(t *testing.T) {
f, err := GetDefaultFont()
assert.Nil(err)
xrange := Range{
xrange := &ContinuousRange{
Min: 1.0,
Max: 4.0,
Domain: 100,
}
yrange := Range{
yrange := &ContinuousRange{
Min: 1.0,
Max: 4.0,
Domain: 100,
@ -82,12 +82,12 @@ func TestAnnotationSeriesRender(t *testing.T) {
f, err := GetDefaultFont()
assert.Nil(err)
xrange := Range{
xrange := &ContinuousRange{
Min: 1.0,
Max: 4.0,
Domain: 100,
}
yrange := Range{
yrange := &ContinuousRange{
Min: 1.0,
Max: 4.0,
Domain: 100,

14
axis.go
View File

@ -14,7 +14,17 @@ const (
type Axis interface {
GetName() string
GetStyle() Style
GetTicks(r Renderer, ra Range, vf ValueFormatter) []Tick
GetTicks() []Tick
GenerateTicks(r Renderer, ra Range, vf ValueFormatter) []Tick
// GetGridLines returns the gridlines for the axis.
GetGridLines(ticks []Tick) []GridLine
Render(c *Chart, r Renderer, canvasBox Box, ra Range, ticks []Tick)
// Measure should return an absolute box for the axis.
// This is used when auto-fitting the canvas to the background.
Measure(r Renderer, canvasBox Box, ra Range, style Style, ticks []Tick) Box
// Render renders the axis.
Render(r Renderer, canvasBox Box, ra Range, style Style, ticks []Tick)
}

View File

@ -108,7 +108,7 @@ func (bbs *BollingerBandsSeries) GetBoundedLastValue() (x, y1, y2 float64) {
// Render renders the series.
func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
s := bbs.Style.WithDefaultsFrom(defaults.WithDefaultsFrom(Style{
s := bbs.Style.InheritFrom(defaults.InheritFrom(Style{
StrokeWidth: 1.0,
StrokeColor: DefaultAxisColor.WithAlpha(64),
FillColor: DefaultAxisColor.WithAlpha(32),

101
chart.go
View File

@ -137,7 +137,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
var miny, maxy float64 = math.MaxFloat64, 0
var minya, maxya float64 = math.MaxFloat64, 0
hasSecondaryAxis := false
seriesMappedToSecondaryAxis := false
// note: a possible future optimization is to not scan the series values if
// all axis are represented by either custom ticks or custom ranges.
@ -162,7 +162,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
minya = math.Min(minya, vy2)
maxya = math.Max(maxya, vy1)
maxya = math.Max(maxya, vy2)
hasSecondaryAxis = true
seriesMappedToSecondaryAxis = true
}
}
} else if vp, isValueProvider := s.(ValueProvider); isValueProvider {
@ -179,27 +179,40 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
} else if seriesAxis == YAxisSecondary {
minya = math.Min(minya, vy)
maxya = math.Max(maxya, vy)
hasSecondaryAxis = true
seriesMappedToSecondaryAxis = true
}
}
}
}
}
if xrange == nil {
xrange = &ContinuousRange{}
}
if yrange == nil {
yrange = &ContinuousRange{}
}
if yrangeAlt == nil {
yrangeAlt = &ContinuousRange{}
}
if len(c.XAxis.Ticks) > 0 {
tickMin, tickMax := math.MaxFloat64, 0.0
for _, t := range c.XAxis.Ticks {
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}
xrange.Min = tickMin
xrange.Max = tickMax
} else if !c.XAxis.Range.IsZero() {
xrange.Min = c.XAxis.Range.Min
xrange.Max = c.XAxis.Range.Max
xrange.SetMin(tickMin)
xrange.SetMax(tickMax)
} else if c.XAxis.Range != nil && !c.XAxis.Range.IsZero() {
xrange.SetMin(c.XAxis.Range.GetMin())
xrange.SetMax(c.XAxis.Range.GetMax())
} else {
xrange.Min = minx
xrange.Max = maxx
xrange.SetMin(minx)
xrange.SetMax(maxx)
}
if len(c.YAxis.Ticks) > 0 {
@ -208,15 +221,20 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}
yrange.Min = tickMin
yrange.Max = tickMax
} else if !c.YAxis.Range.IsZero() {
yrange.Min = c.YAxis.Range.Min
yrange.Max = c.YAxis.Range.Max
yrange.SetMin(tickMin)
yrange.SetMax(tickMax)
} else if c.YAxis.Range != nil && !c.YAxis.Range.IsZero() {
yrange.SetMin(c.YAxis.Range.GetMin())
yrange.SetMax(c.YAxis.Range.GetMax())
} else {
yrange.Min = miny
yrange.Max = maxy
yrange.Min, yrange.Max = yrange.GetRoundedRangeBounds()
yrange.SetMin(miny)
yrange.SetMax(maxy)
delta := yrange.GetDelta()
roundTo := GetRoundToForDelta(delta)
rmin, rmax := RoundDown(yrange.GetMin(), roundTo), RoundUp(yrange.GetMax(), roundTo)
yrange.SetMin(rmin)
yrange.SetMax(rmax)
}
if len(c.YAxisSecondary.Ticks) > 0 {
@ -225,30 +243,34 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}
yrangeAlt.Min = tickMin
yrangeAlt.Max = tickMax
} else if !c.YAxisSecondary.Range.IsZero() {
yrangeAlt.Min = c.YAxisSecondary.Range.Min
yrangeAlt.Max = c.YAxisSecondary.Range.Max
} else if hasSecondaryAxis {
yrangeAlt.Min = minya
yrangeAlt.Max = maxya
yrangeAlt.Min, yrangeAlt.Max = yrangeAlt.GetRoundedRangeBounds()
yrangeAlt.SetMin(tickMin)
yrangeAlt.SetMax(tickMax)
} else if c.YAxisSecondary.Range != nil && !c.YAxisSecondary.Range.IsZero() {
yrangeAlt.SetMin(c.YAxisSecondary.Range.GetMin())
yrangeAlt.SetMax(c.YAxisSecondary.Range.GetMax())
} else if seriesMappedToSecondaryAxis {
yrangeAlt.SetMin(minya)
yrangeAlt.SetMax(maxya)
delta := yrangeAlt.GetDelta()
roundTo := GetRoundToForDelta(delta)
rmin, rmax := RoundDown(yrangeAlt.GetMin(), roundTo), RoundUp(yrangeAlt.GetMax(), roundTo)
yrangeAlt.SetMin(rmin)
yrangeAlt.SetMax(rmax)
}
return
}
func (c Chart) checkRanges(xr, yr, yra Range) error {
if math.IsInf(xr.Delta(), 0) || math.IsNaN(xr.Delta()) {
if math.IsInf(xr.GetDelta(), 0) || math.IsNaN(xr.GetDelta()) || xr.GetDelta() == 0 {
return errors.New("Invalid (infinite or NaN) x-range delta")
}
if math.IsInf(yr.Delta(), 0) || math.IsNaN(yr.Delta()) {
if math.IsInf(yr.GetDelta(), 0) || math.IsNaN(yr.GetDelta()) || yr.GetDelta() == 0 {
return errors.New("Invalid (infinite or NaN) y-range delta")
}
if c.hasSecondarySeries() {
if math.IsInf(yra.Delta(), 0) || math.IsNaN(yra.Delta()) {
if math.IsInf(yra.GetDelta(), 0) || math.IsNaN(yra.GetDelta()) || yra.GetDelta() == 0 {
return errors.New("Invalid (infinite or NaN) y-secondary-range delta")
}
}
@ -320,14 +342,11 @@ func (c Chart) getAxisAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra R
return canvasBox.OuterConstrain(c.Box(), axesOuterBox)
}
func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (xr2, yr2, yra2 Range) {
xr2.Min, xr2.Max = xr.Min, xr.Max
xr2.Domain = canvasBox.Width()
yr2.Min, yr2.Max = yr.Min, yr.Max
yr2.Domain = canvasBox.Height()
yra2.Min, yra2.Max = yra.Min, yra.Max
yra2.Domain = canvasBox.Height()
return
func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (Range, Range, Range) {
xr.SetDomain(canvasBox.Width())
yr.SetDomain(canvasBox.Height())
yra.SetDomain(canvasBox.Height())
return xr, yr, yra
}
func (c Chart) hasAnnotationSeries() bool {
@ -372,7 +391,7 @@ func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr,
}
func (c Chart) getBackgroundStyle() Style {
return c.Background.WithDefaultsFrom(c.styleDefaultsBackground())
return c.Background.InheritFrom(c.styleDefaultsBackground())
}
func (c Chart) drawBackground(r Renderer) {
@ -383,7 +402,7 @@ func (c Chart) drawBackground(r Renderer) {
}
func (c Chart) getCanvasStyle() Style {
return c.Canvas.WithDefaultsFrom(c.styleDefaultsCanvas())
return c.Canvas.InheritFrom(c.styleDefaultsCanvas())
}
func (c Chart) drawCanvas(r Renderer, canvasBox Box) {

View File

@ -77,24 +77,24 @@ func TestChartGetRanges(t *testing.T) {
}
xrange, yrange, yrangeAlt := c.getRanges()
assert.Equal(-2.0, xrange.Min)
assert.Equal(5.0, xrange.Max)
assert.Equal(-2.0, xrange.GetMin())
assert.Equal(5.0, xrange.GetMax())
assert.Equal(-2.1, yrange.Min)
assert.Equal(4.5, yrange.Max)
assert.Equal(-2.1, yrange.GetMin())
assert.Equal(4.5, yrange.GetMax())
assert.Equal(10.0, yrangeAlt.Min)
assert.Equal(14.0, yrangeAlt.Max)
assert.Equal(10.0, yrangeAlt.GetMin())
assert.Equal(14.0, yrangeAlt.GetMax())
cSet := Chart{
XAxis: XAxis{
Range: Range{Min: 9.8, Max: 19.8},
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
},
YAxis: YAxis{
Range: Range{Min: 9.9, Max: 19.9},
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
},
YAxisSecondary: YAxis{
Range: Range{Min: 9.7, Max: 19.7},
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
},
Series: []Series{
ContinuousSeries{
@ -114,14 +114,14 @@ func TestChartGetRanges(t *testing.T) {
}
xr2, yr2, yra2 := cSet.getRanges()
assert.Equal(9.8, xr2.Min)
assert.Equal(19.8, xr2.Max)
assert.Equal(9.8, xr2.GetMin())
assert.Equal(19.8, xr2.GetMax())
assert.Equal(9.9, yr2.Min)
assert.Equal(19.9, yr2.Max)
assert.Equal(9.9, yr2.GetMin())
assert.Equal(19.9, yr2.GetMax())
assert.Equal(9.7, yra2.Min)
assert.Equal(19.7, yra2.Max)
assert.Equal(9.7, yra2.GetMin())
assert.Equal(19.7, yra2.GetMax())
}
func TestChartGetRangesUseTicks(t *testing.T) {
@ -139,7 +139,7 @@ func TestChartGetRangesUseTicks(t *testing.T) {
{4.0, "4.0"},
{5.0, "Five"},
},
Range: Range{
Range: &ContinuousRange{
Min: -5.0,
Max: 5.0,
},
@ -153,10 +153,10 @@ func TestChartGetRangesUseTicks(t *testing.T) {
}
xr, yr, yar := c.getRanges()
assert.Equal(-2.0, xr.Min)
assert.Equal(2.0, xr.Max)
assert.Equal(0.0, yr.Min)
assert.Equal(5.0, yr.Max)
assert.Equal(-2.0, xr.GetMin())
assert.Equal(2.0, xr.GetMax())
assert.Equal(0.0, yr.GetMin())
assert.Equal(5.0, yr.GetMax())
assert.True(yar.IsZero(), yar.String())
}
@ -165,7 +165,7 @@ func TestChartGetRangesUseUserRanges(t *testing.T) {
c := Chart{
YAxis: YAxis{
Range: Range{
Range: &ContinuousRange{
Min: -5.0,
Max: 5.0,
},
@ -179,10 +179,10 @@ func TestChartGetRangesUseUserRanges(t *testing.T) {
}
xr, yr, yar := c.getRanges()
assert.Equal(-2.0, xr.Min)
assert.Equal(2.0, xr.Max)
assert.Equal(-5.0, yr.Min)
assert.Equal(5.0, yr.Max)
assert.Equal(-2.0, xr.GetMin())
assert.Equal(2.0, xr.GetMax())
assert.Equal(-5.0, yr.GetMin())
assert.Equal(5.0, yr.GetMax())
assert.True(yar.IsZero(), yar.String())
}
@ -310,15 +310,15 @@ func TestChartGetAxesTicks(t *testing.T) {
c := Chart{
XAxis: XAxis{
Style: Style{Show: true},
Range: Range{Min: 9.8, Max: 19.8},
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
},
YAxis: YAxis{
Style: Style{Show: true},
Range: Range{Min: 9.9, Max: 19.9},
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
},
YAxisSecondary: YAxis{
Style: Style{Show: true},
Range: Range{Min: 9.7, Max: 19.7},
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
},
}
xr, yr, yar := c.getRanges()
@ -337,7 +337,7 @@ func TestChartSingleSeries(t *testing.T) {
Width: 1024,
Height: 400,
YAxis: YAxis{
Range: Range{
Range: &ContinuousRange{
Min: 0.0,
Max: 4.0,
},
@ -377,7 +377,7 @@ func TestChartRegressionBadRangesByUser(t *testing.T) {
c := Chart{
YAxis: YAxis{
Range: Range{
Range: &ContinuousRange{
Min: math.Inf(-1),
Max: math.Inf(1), // this could really happen? eh.
},

32
concat_series.go Normal file
View File

@ -0,0 +1,32 @@
package chart
// ConcatSeries is a special type of series that concatenates its `InnerSeries`.
type ConcatSeries []Series
// Len returns the length of the concatenated set of series.
func (cs ConcatSeries) Len() int {
total := 0
for _, s := range cs {
if typed, isValueProvider := s.(ValueProvider); isValueProvider {
total += typed.Len()
}
}
return total
}
// GetValue returns the value at the (meta) index (i.e 0 => totalLen-1)
func (cs ConcatSeries) GetValue(index int) (x, y float64) {
cursor := 0
for _, s := range cs {
if typed, isValueProvider := s.(ValueProvider); isValueProvider {
len := typed.Len()
if index < cursor+len {
x, y = typed.GetValue(index - cursor) //FENCEPOSTS.
return
}
cursor += typed.Len()
}
}
return
}

41
concat_series_test.go Normal file
View File

@ -0,0 +1,41 @@
package chart
import (
"testing"
assert "github.com/blendlabs/go-assert"
)
func TestConcatSeries(t *testing.T) {
assert := assert.New(t)
s1 := ContinuousSeries{
XValues: Seq(1.0, 10.0),
YValues: Seq(1.0, 10.0),
}
s2 := ContinuousSeries{
XValues: Seq(11, 20.0),
YValues: Seq(10.0, 1.0),
}
s3 := ContinuousSeries{
XValues: Seq(21, 30.0),
YValues: Seq(1.0, 10.0),
}
cs := ConcatSeries([]Series{s1, s2, s3})
assert.Equal(30, cs.Len())
x0, y0 := cs.GetValue(0)
assert.Equal(1.0, x0)
assert.Equal(1.0, y0)
xm, ym := cs.GetValue(19)
assert.Equal(20.0, xm)
assert.Equal(1.0, ym)
xn, yn := cs.GetValue(29)
assert.Equal(30.0, xn)
assert.Equal(10.0, yn)
}

67
continuous_range.go Normal file
View File

@ -0,0 +1,67 @@
package chart
import (
"fmt"
"math"
)
// ContinuousRange represents a boundary for a set of numbers.
type ContinuousRange struct {
Min float64
Max float64
Domain int
}
// IsZero returns if the ContinuousRange has been set or not.
func (r ContinuousRange) IsZero() bool {
return (r.Min == 0 || math.IsNaN(r.Min)) &&
(r.Max == 0 || math.IsNaN(r.Max)) &&
r.Domain == 0
}
// GetMin gets the min value for the continuous range.
func (r ContinuousRange) GetMin() float64 {
return r.Min
}
// SetMin sets the min value for the continuous range.
func (r *ContinuousRange) SetMin(min float64) {
r.Min = min
}
// GetMax returns the max value for the continuous range.
func (r ContinuousRange) GetMax() float64 {
return r.Max
}
// SetMax sets the max value for the continuous range.
func (r *ContinuousRange) SetMax(max float64) {
r.Max = max
}
// GetDelta returns the difference between the min and max value.
func (r ContinuousRange) GetDelta() float64 {
return r.Max - r.Min
}
// GetDomain returns the range domain.
func (r ContinuousRange) GetDomain() int {
return r.Domain
}
// SetDomain sets the range domain.
func (r *ContinuousRange) SetDomain(domain int) {
r.Domain = domain
}
// String returns a simple string for the ContinuousRange.
func (r ContinuousRange) String() string {
return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain)
}
// Translate maps a given value into the ContinuousRange space.
func (r ContinuousRange) Translate(value float64) int {
normalized := value - r.Min
ratio := normalized / r.GetDelta()
return int(math.Ceil(ratio * float64(r.Domain)))
}

View File

@ -9,7 +9,7 @@ import (
func TestRangeTranslate(t *testing.T) {
assert := assert.New(t)
values := []float64{1.0, 2.0, 2.5, 2.7, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
r := Range{Domain: 1000}
r := ContinuousRange{Domain: 1000}
r.Min, r.Max = MinAndMax(values...)
// delta = ~7.0

View File

@ -50,6 +50,6 @@ func (cs ContinuousSeries) GetYAxis() YAxisType {
// Render renders the series.
func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := cs.Style.WithDefaultsFrom(defaults)
style := cs.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, cs)
}

View File

@ -114,7 +114,7 @@ func DrawHistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Styl
//calculate bar width?
seriesLength := vs.Len()
barWidth := int(math.Floor(float64(xrange.Domain) / float64(seriesLength)))
barWidth := int(math.Floor(float64(xrange.GetDomain()) / float64(seriesLength)))
if len(barWidths) > 0 {
barWidth = barWidths[0]
}
@ -271,9 +271,9 @@ func CreateLegend(c *Chart, userDefaults ...Style) Renderable {
var legendStyle Style
if len(userDefaults) > 0 {
legendStyle = userDefaults[0].WithDefaultsFrom(chartDefaults.WithDefaultsFrom(legendDefaults))
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
} else {
legendStyle = chartDefaults.WithDefaultsFrom(legendDefaults)
legendStyle = chartDefaults.InheritFrom(legendDefaults)
}
// DEFAULTS
@ -292,7 +292,7 @@ func CreateLegend(c *Chart, userDefaults ...Style) Renderable {
if s.GetStyle().IsZero() || s.GetStyle().Show {
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().WithDefaultsFrom(c.styleDefaultsSeries(index)))
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
}
}
}

View File

@ -96,6 +96,6 @@ func (ema *EMASeries) ensureCachedValues() {
// Render renders the series.
func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ema.Style.WithDefaultsFrom(defaults)
style := ema.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, ema)
}

View File

@ -17,7 +17,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
Style: chart.Style{
Show: true,
},
Range: chart.Range{
Range: chart.ContinuousRange{
Min: 0.0,
Max: 10.0,
},

View File

@ -52,6 +52,6 @@ func (hs HistogramSeries) GetBoundedValue(index int) (x, y1, y2 float64) {
// Render implements Series.Render.
func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := hs.Style.WithDefaultsFrom(defaults)
style := hs.Style.InheritFrom(defaults)
DrawHistogramSeries(r, canvasBox, xrange, yrange, style, hs)
}

View File

@ -192,7 +192,7 @@ func (macds *MACDSignalSeries) ensureSignal() {
// Render renders the series.
func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := macds.Style.WithDefaultsFrom(defaults)
style := macds.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, macds)
}
@ -284,6 +284,6 @@ func (macdl *MACDLineSeries) ensureEMASeries() {
// Render renders the series.
func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := macdl.Style.WithDefaultsFrom(defaults)
style := macdl.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, macdl)
}

View File

@ -1,44 +1,41 @@
package chart
import (
"fmt"
"math"
)
// Range represents a boundary for a set of numbers.
type Range struct {
Min float64
Max float64
Domain int
// NameProvider is a type that returns a name.
type NameProvider interface {
GetName() string
}
// IsZero returns if the range has been set or not.
func (r Range) IsZero() bool {
return (r.Min == 0 || math.IsNaN(r.Min)) &&
(r.Max == 0 || math.IsNaN(r.Max)) &&
r.Domain == 0
// StyleProvider is a type that returns a style.
type StyleProvider interface {
GetStyle() Style
}
// Delta returns the difference between the min and max value.
func (r Range) Delta() float64 {
return r.Max - r.Min
// IsZeroable is a type that returns if it's been set or not.
type IsZeroable interface {
IsZero() bool
}
// String returns a simple string for the range.
func (r Range) String() string {
return fmt.Sprintf("Range [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain)
// Stringable is a type that has a string representation.
type Stringable interface {
String() string
}
// Translate maps a given value into the range space.
func (r Range) Translate(value float64) int {
normalized := value - r.Min
ratio := normalized / r.Delta()
return int(math.Ceil(ratio * float64(r.Domain)))
}
// Range is a
type Range interface {
Stringable
IsZeroable
// GetRoundedRangeBounds returns some `prettified` range bounds.
func (r Range) GetRoundedRangeBounds() (min, max float64) {
delta := r.Max - r.Min
roundTo := GetRoundToForDelta(delta)
return RoundDown(r.Min, roundTo), RoundUp(r.Max, roundTo)
GetMin() float64
SetMin(min float64)
GetMax() float64
SetMax(max float64)
GetDelta() float64
GetDomain() int
SetDomain(domain int)
// Translate the range to the domain.
Translate(value float64) int
}

View File

@ -85,6 +85,6 @@ func (sma SMASeries) getAverage(index int) float64 {
// Render renders the series.
func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := sma.Style.WithDefaultsFrom(defaults)
style := sma.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, sma)
}

View File

@ -28,6 +28,7 @@ func (s Style) IsZero() bool {
return s.StrokeColor.IsZero() && s.FillColor.IsZero() && s.StrokeWidth == 0 && s.FontColor.IsZero() && s.FontSize == 0 && s.Font == nil
}
// String returns a text representation of the style.
func (s Style) String() string {
if s.IsZero() {
return "{}"
@ -184,8 +185,18 @@ func (s Style) GetPadding(defaults ...Box) Box {
return s.Padding
}
// WithDefaultsFrom coalesces two styles into a new style.
func (s Style) WithDefaultsFrom(defaults Style) (final Style) {
// PersistToRenderer passes the style onto a renderer.
func (s Style) PersistToRenderer(r Renderer) {
r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth())
r.SetStrokeDashArray(s.GetStrokeDashArray())
r.SetFont(s.GetFont())
r.SetFontColor(s.GetFontColor())
r.SetFontSize(s.GetFontSize())
}
// InheritFrom coalesces two styles into a new style.
func (s Style) InheritFrom(defaults Style) (final Style) {
final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor)
final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth)
final.StrokeDashArray = s.GetStrokeDashArray(defaults.StrokeDashArray)

View File

@ -142,7 +142,7 @@ func TestStyleWithDefaultsFrom(t *testing.T) {
Padding: DefaultBackgroundPadding,
}
coalesced := unset.WithDefaultsFrom(set)
coalesced := unset.InheritFrom(set)
assert.Equal(set, coalesced)
}

View File

@ -3,7 +3,7 @@ package chart
// GenerateTicksWithStep generates a set of ticks.
func GenerateTicksWithStep(ra Range, step float64, vf ValueFormatter) []Tick {
var ticks []Tick
min, max := ra.Min, ra.Max
min, max := ra.GetMin(), ra.GetMax()
for cursor := min; cursor <= max; cursor += step {
ticks = append(ticks, Tick{
Value: cursor,

View File

@ -9,6 +9,6 @@ import (
func TestGenerateTicksWithStep(t *testing.T) {
assert := assert.New(t)
ticks := GenerateTicksWithStep(Range{Min: 1.0, Max: 10.0, Domain: 100}, 1.0, FloatValueFormatter)
ticks := GenerateTicksWithStep(&ContinuousRange{Min: 1.0, Max: 10.0, Domain: 100}, 1.0, FloatValueFormatter)
assert.Len(ticks, 10)
}

View File

@ -56,6 +56,6 @@ func (ts TimeSeries) GetYAxis() YAxisType {
// Render renders the series.
func (ts TimeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ts.Style.WithDefaultsFrom(defaults)
style := ts.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, ts)
}

View File

@ -44,7 +44,7 @@ func (xa XAxis) generateTicks(r Renderer, ra Range, defaults Style, vf ValueForm
func (xa XAxis) getTickStep(r Renderer, ra Range, defaults Style, vf ValueFormatter) float64 {
tickCount := xa.getTickCount(r, ra, defaults, vf)
step := ra.Delta() / float64(tickCount)
step := ra.GetDelta() / float64(tickCount)
return step
}
@ -53,8 +53,8 @@ func (xa XAxis) getTickCount(r Renderer, ra Range, defaults Style, vf ValueForma
r.SetFontSize(xa.Style.GetFontSize(defaults.GetFontSize(DefaultFontSize)))
// take a cut at determining the 'widest' value.
l0 := vf(ra.Min)
ln := vf(ra.Max)
l0 := vf(ra.GetMin())
ln := vf(ra.GetMax())
ll := l0
if len(ln) > len(l0) {
ll = ln
@ -62,7 +62,7 @@ func (xa XAxis) getTickCount(r Renderer, ra Range, defaults Style, vf ValueForma
llb := r.MeasureText(ll)
textWidth := llb.Width()
width := textWidth + DefaultMinimumTickHorizontalSpacing
count := int(math.Ceil(float64(ra.Domain) / float64(width)))
count := int(math.Ceil(float64(ra.GetDomain()) / float64(width)))
return count
}
@ -76,13 +76,7 @@ func (xa XAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis.
func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
r.SetStrokeColor(xa.Style.GetStrokeColor(defaults.StrokeColor))
r.SetStrokeWidth(xa.Style.GetStrokeWidth(defaults.StrokeWidth))
r.SetStrokeDashArray(xa.Style.GetStrokeDashArray())
r.SetFont(xa.Style.GetFont(defaults.GetFont()))
r.SetFontColor(xa.Style.GetFontColor(DefaultAxisColor))
r.SetFontSize(xa.Style.GetFontSize(defaults.GetFontSize()))
xa.Style.InheritFrom(defaults).PersistToRenderer(r)
sort.Sort(Ticks(ticks))
var left, right, top, bottom = math.MaxInt32, 0, math.MaxInt32, 0
@ -110,12 +104,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
// Render renders the axis
func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
r.SetStrokeColor(xa.Style.GetStrokeColor(defaults.StrokeColor))
r.SetStrokeWidth(xa.Style.GetStrokeWidth(defaults.StrokeWidth))
r.SetStrokeDashArray(xa.Style.GetStrokeDashArray())
r.SetFont(xa.Style.GetFont(defaults.GetFont()))
r.SetFontColor(xa.Style.GetFontColor(DefaultAxisColor))
r.SetFontSize(xa.Style.GetFontSize(defaults.GetFontSize()))
xa.Style.InheritFrom(defaults).PersistToRenderer(r)
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
r.LineTo(canvasBox.Right, canvasBox.Bottom)

View File

@ -16,7 +16,7 @@ func TestXAxisGetTickCount(t *testing.T) {
assert.Nil(err)
xa := XAxis{}
xr := Range{Min: 10, Max: 100, Domain: 1024}
xr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,
@ -36,14 +36,14 @@ func TestXAxisGetTickStep(t *testing.T) {
assert.Nil(err)
xa := XAxis{}
xr := Range{Min: 10, Max: 100, Domain: 1024}
xr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,
}
vf := FloatValueFormatter
step := xa.getTickStep(r, xr, styleDefaults, vf)
assert.Equal(xr.Delta()/16.0, step)
assert.Equal(xr.GetDelta()/16.0, step)
}
func TestXAxisGetTicks(t *testing.T) {
@ -56,7 +56,7 @@ func TestXAxisGetTicks(t *testing.T) {
assert.Nil(err)
xa := XAxis{}
xr := Range{Min: 10, Max: 100, Domain: 1024}
xr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,
@ -78,7 +78,7 @@ func TestXAxisGetTicksWithUserDefaults(t *testing.T) {
xa := XAxis{
Ticks: []Tick{{Value: 1.0, Label: "1.0"}},
}
xr := Range{Min: 10, Max: 100, Domain: 1024}
xr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,

View File

@ -17,9 +17,10 @@ type YAxis struct {
ValueFormatter ValueFormatter
Range Range
Ticks []Tick
GridLines []GridLine
Ticks []Tick
GridLines []GridLine
GridMajorStyle Style
GridMinorStyle Style
}
@ -51,7 +52,7 @@ func (ya YAxis) generateTicks(r Renderer, ra Range, defaults Style, vf ValueForm
func (ya YAxis) getTickStep(r Renderer, ra Range, defaults Style, vf ValueFormatter) float64 {
tickCount := ya.getTickCount(r, ra, defaults, vf)
step := ra.Delta() / float64(tickCount)
step := ra.GetDelta() / float64(tickCount)
return step
}
@ -59,9 +60,9 @@ func (ya YAxis) getTickCount(r Renderer, ra Range, defaults Style, vf ValueForma
r.SetFont(ya.Style.GetFont(defaults.GetFont()))
r.SetFontSize(ya.Style.GetFontSize(defaults.GetFontSize(DefaultFontSize)))
//given the domain, figure out how many ticks we can draw ...
label := vf(ra.Min)
label := vf(ra.GetMin())
tb := r.MeasureText(label)
count := int(math.Ceil(float64(ra.Domain) / float64(tb.Height()+DefaultMinimumTickVerticalSpacing)))
count := int(math.Ceil(float64(ra.GetDomain()) / float64(tb.Height()+DefaultMinimumTickVerticalSpacing)))
return count
}
@ -75,11 +76,7 @@ func (ya YAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis.
func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
r.SetStrokeColor(ya.Style.GetStrokeColor(defaults.StrokeColor))
r.SetStrokeWidth(ya.Style.GetStrokeWidth(defaults.StrokeWidth))
r.SetFont(ya.Style.GetFont(defaults.GetFont()))
r.SetFontColor(ya.Style.GetFontColor(DefaultAxisColor))
r.SetFontSize(ya.Style.GetFontSize(defaults.GetFontSize()))
ya.Style.InheritFrom(defaults).PersistToRenderer(r)
sort.Sort(Ticks(ticks))
@ -122,11 +119,7 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
// Render renders the axis.
func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
r.SetStrokeColor(ya.Style.GetStrokeColor(defaults.StrokeColor))
r.SetStrokeWidth(ya.Style.GetStrokeWidth(defaults.StrokeWidth))
r.SetFont(ya.Style.GetFont(defaults.GetFont()))
r.SetFontColor(ya.Style.GetFontColor(DefaultAxisColor))
r.SetFontSize(ya.Style.GetFontSize(defaults.GetFontSize(DefaultFontSize)))
ya.Style.InheritFrom(defaults).PersistToRenderer(r)
sort.Sort(Ticks(ticks))

View File

@ -16,7 +16,7 @@ func TestYAxisGetTickCount(t *testing.T) {
assert.Nil(err)
ya := YAxis{}
yr := Range{Min: 10, Max: 100, Domain: 1024}
yr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,
@ -36,14 +36,14 @@ func TestYAxisGetTickStep(t *testing.T) {
assert.Nil(err)
ya := YAxis{}
yr := Range{Min: 10, Max: 100, Domain: 1024}
yr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,
}
vf := FloatValueFormatter
step := ya.getTickStep(r, yr, styleDefaults, vf)
assert.Equal(yr.Delta()/34.0, step)
assert.Equal(yr.GetDelta()/34.0, step)
}
func TestYAxisGetTicks(t *testing.T) {
@ -56,7 +56,7 @@ func TestYAxisGetTicks(t *testing.T) {
assert.Nil(err)
ya := YAxis{}
yr := Range{Min: 10, Max: 100, Domain: 1024}
yr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,
@ -78,7 +78,7 @@ func TestYAxisGetTicksWithUserDefaults(t *testing.T) {
ya := YAxis{
Ticks: []Tick{{Value: 1.0, Label: "1.0"}},
}
yr := Range{Min: 10, Max: 100, Domain: 1024}
yr := &ContinuousRange{Min: 10, Max: 100, Domain: 1024}
styleDefaults := Style{
Font: f,
FontSize: 10.0,