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

View File

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

14
axis.go
View File

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

View File

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

View File

@ -50,6 +50,6 @@ func (cs ContinuousSeries) GetYAxis() YAxisType {
// Render renders the series. // Render renders the series.
func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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) 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? //calculate bar width?
seriesLength := vs.Len() 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 { if len(barWidths) > 0 {
barWidth = barWidths[0] barWidth = barWidths[0]
} }
@ -271,9 +271,9 @@ func CreateLegend(c *Chart, userDefaults ...Style) Renderable {
var legendStyle Style var legendStyle Style
if len(userDefaults) > 0 { if len(userDefaults) > 0 {
legendStyle = userDefaults[0].WithDefaultsFrom(chartDefaults.WithDefaultsFrom(legendDefaults)) legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
} else { } else {
legendStyle = chartDefaults.WithDefaultsFrom(legendDefaults) legendStyle = chartDefaults.InheritFrom(legendDefaults)
} }
// DEFAULTS // DEFAULTS
@ -292,7 +292,7 @@ func CreateLegend(c *Chart, userDefaults ...Style) Renderable {
if s.GetStyle().IsZero() || s.GetStyle().Show { if s.GetStyle().IsZero() || s.GetStyle().Show {
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries { if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName()) 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. // Render renders the series.
func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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) DrawLineSeries(r, canvasBox, xrange, yrange, style, ema)
} }

View File

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

View File

@ -52,6 +52,6 @@ func (hs HistogramSeries) GetBoundedValue(index int) (x, y1, y2 float64) {
// Render implements Series.Render. // Render implements Series.Render.
func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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) DrawHistogramSeries(r, canvasBox, xrange, yrange, style, hs)
} }

View File

@ -192,7 +192,7 @@ func (macds *MACDSignalSeries) ensureSignal() {
// Render renders the series. // Render renders the series.
func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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) DrawLineSeries(r, canvasBox, xrange, yrange, style, macds)
} }
@ -284,6 +284,6 @@ func (macdl *MACDLineSeries) ensureEMASeries() {
// Render renders the series. // Render renders the series.
func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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) DrawLineSeries(r, canvasBox, xrange, yrange, style, macdl)
} }

View File

@ -1,44 +1,41 @@
package chart package chart
import ( // NameProvider is a type that returns a name.
"fmt" type NameProvider interface {
"math" GetName() string
)
// Range represents a boundary for a set of numbers.
type Range struct {
Min float64
Max float64
Domain int
} }
// IsZero returns if the range has been set or not. // StyleProvider is a type that returns a style.
func (r Range) IsZero() bool { type StyleProvider interface {
return (r.Min == 0 || math.IsNaN(r.Min)) && GetStyle() Style
(r.Max == 0 || math.IsNaN(r.Max)) &&
r.Domain == 0
} }
// Delta returns the difference between the min and max value. // IsZeroable is a type that returns if it's been set or not.
func (r Range) Delta() float64 { type IsZeroable interface {
return r.Max - r.Min IsZero() bool
} }
// String returns a simple string for the range. // Stringable is a type that has a string representation.
func (r Range) String() string { type Stringable interface {
return fmt.Sprintf("Range [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain) String() string
} }
// Translate maps a given value into the range space. // Range is a
func (r Range) Translate(value float64) int { type Range interface {
normalized := value - r.Min Stringable
ratio := normalized / r.Delta() IsZeroable
return int(math.Ceil(ratio * float64(r.Domain)))
}
// GetRoundedRangeBounds returns some `prettified` range bounds. GetMin() float64
func (r Range) GetRoundedRangeBounds() (min, max float64) { SetMin(min float64)
delta := r.Max - r.Min
roundTo := GetRoundToForDelta(delta) GetMax() float64
return RoundDown(r.Min, roundTo), RoundUp(r.Max, roundTo) 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. // Render renders the series.
func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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) 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 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 { func (s Style) String() string {
if s.IsZero() { if s.IsZero() {
return "{}" return "{}"
@ -184,8 +185,18 @@ func (s Style) GetPadding(defaults ...Box) Box {
return s.Padding return s.Padding
} }
// WithDefaultsFrom coalesces two styles into a new style. // PersistToRenderer passes the style onto a renderer.
func (s Style) WithDefaultsFrom(defaults Style) (final Style) { 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.StrokeColor = s.GetStrokeColor(defaults.StrokeColor)
final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth) final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth)
final.StrokeDashArray = s.GetStrokeDashArray(defaults.StrokeDashArray) final.StrokeDashArray = s.GetStrokeDashArray(defaults.StrokeDashArray)

View File

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

View File

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

View File

@ -9,6 +9,6 @@ import (
func TestGenerateTicksWithStep(t *testing.T) { func TestGenerateTicksWithStep(t *testing.T) {
assert := assert.New(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) assert.Len(ticks, 10)
} }

View File

@ -56,6 +56,6 @@ func (ts TimeSeries) GetYAxis() YAxisType {
// Render renders the series. // Render renders the series.
func (ts TimeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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) 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 { func (xa XAxis) getTickStep(r Renderer, ra Range, defaults Style, vf ValueFormatter) float64 {
tickCount := xa.getTickCount(r, ra, defaults, vf) tickCount := xa.getTickCount(r, ra, defaults, vf)
step := ra.Delta() / float64(tickCount) step := ra.GetDelta() / float64(tickCount)
return step 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))) r.SetFontSize(xa.Style.GetFontSize(defaults.GetFontSize(DefaultFontSize)))
// take a cut at determining the 'widest' value. // take a cut at determining the 'widest' value.
l0 := vf(ra.Min) l0 := vf(ra.GetMin())
ln := vf(ra.Max) ln := vf(ra.GetMax())
ll := l0 ll := l0
if len(ln) > len(l0) { if len(ln) > len(l0) {
ll = ln ll = ln
@ -62,7 +62,7 @@ func (xa XAxis) getTickCount(r Renderer, ra Range, defaults Style, vf ValueForma
llb := r.MeasureText(ll) llb := r.MeasureText(ll)
textWidth := llb.Width() textWidth := llb.Width()
width := textWidth + DefaultMinimumTickHorizontalSpacing width := textWidth + DefaultMinimumTickHorizontalSpacing
count := int(math.Ceil(float64(ra.Domain) / float64(width))) count := int(math.Ceil(float64(ra.GetDomain()) / float64(width)))
return count return count
} }
@ -76,13 +76,7 @@ func (xa XAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis. // Measure returns the bounds of the axis.
func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box { func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
r.SetStrokeColor(xa.Style.GetStrokeColor(defaults.StrokeColor)) xa.Style.InheritFrom(defaults).PersistToRenderer(r)
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()))
sort.Sort(Ticks(ticks)) sort.Sort(Ticks(ticks))
var left, right, top, bottom = math.MaxInt32, 0, math.MaxInt32, 0 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 // Render renders the axis
func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) { func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
r.SetStrokeColor(xa.Style.GetStrokeColor(defaults.StrokeColor)) xa.Style.InheritFrom(defaults).PersistToRenderer(r)
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()))
r.MoveTo(canvasBox.Left, canvasBox.Bottom) r.MoveTo(canvasBox.Left, canvasBox.Bottom)
r.LineTo(canvasBox.Right, canvasBox.Bottom) r.LineTo(canvasBox.Right, canvasBox.Bottom)

View File

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

View File

@ -17,9 +17,10 @@ type YAxis struct {
ValueFormatter ValueFormatter ValueFormatter ValueFormatter
Range Range Range Range
Ticks []Tick
GridLines []GridLine Ticks []Tick
GridLines []GridLine
GridMajorStyle Style GridMajorStyle Style
GridMinorStyle 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 { func (ya YAxis) getTickStep(r Renderer, ra Range, defaults Style, vf ValueFormatter) float64 {
tickCount := ya.getTickCount(r, ra, defaults, vf) tickCount := ya.getTickCount(r, ra, defaults, vf)
step := ra.Delta() / float64(tickCount) step := ra.GetDelta() / float64(tickCount)
return step 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.SetFont(ya.Style.GetFont(defaults.GetFont()))
r.SetFontSize(ya.Style.GetFontSize(defaults.GetFontSize(DefaultFontSize))) r.SetFontSize(ya.Style.GetFontSize(defaults.GetFontSize(DefaultFontSize)))
//given the domain, figure out how many ticks we can draw ... //given the domain, figure out how many ticks we can draw ...
label := vf(ra.Min) label := vf(ra.GetMin())
tb := r.MeasureText(label) 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 return count
} }
@ -75,11 +76,7 @@ func (ya YAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis. // Measure returns the bounds of the axis.
func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box { func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
r.SetStrokeColor(ya.Style.GetStrokeColor(defaults.StrokeColor)) ya.Style.InheritFrom(defaults).PersistToRenderer(r)
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()))
sort.Sort(Ticks(ticks)) 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. // Render renders the axis.
func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) { func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
r.SetStrokeColor(ya.Style.GetStrokeColor(defaults.StrokeColor)) ya.Style.InheritFrom(defaults).PersistToRenderer(r)
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)))
sort.Sort(Ticks(ticks)) sort.Sort(Ticks(ticks))

View File

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