inching up coverage.

This commit is contained in:
Will Charczuk 2016-07-10 23:06:14 -07:00
parent 18a6e3eed1
commit 56da554741
10 changed files with 315 additions and 92 deletions

View File

@ -54,7 +54,7 @@ graph := chart.Chart{
}, },
Annotations: []chart.Annotation{ Annotations: []chart.Annotation{
chart.Annotation{ chart.Annotation{
X: float64(xvalues[len(xvalues)-1].Unix()), //todo: helpers for this. X: chart.TimeToFloat64(xvalues[len(xvalues)-1]),
Y: yvalues[len(yvalues)-1], Y: yvalues[len(yvalues)-1],
Label: chart.FloatValueFormatter(yvalues[len(yvalues)-1]), Label: chart.FloatValueFormatter(yvalues[len(yvalues)-1]),
}, },

View File

@ -1,45 +1,6 @@
package chart package chart
import ( import "math"
"math"
"time"
)
// CreateContinuousSeriesLastValueLabel returns a (1) value annotation series.
func CreateContinuousSeriesLastValueLabel(name string, xvalues, yvalues []float64, valueFormatter ValueFormatter) AnnotationSeries {
return AnnotationSeries{
Name: name,
Style: Style{
Show: true,
StrokeColor: GetDefaultSeriesStrokeColor(0),
},
Annotations: []Annotation{
Annotation{
X: xvalues[len(xvalues)-1],
Y: yvalues[len(yvalues)-1],
Label: valueFormatter(yvalues[len(yvalues)-1]),
},
},
}
}
// CreateTimeSeriesLastValueLabel returns a (1) value annotation series.
func CreateTimeSeriesLastValueLabel(name string, xvalues []time.Time, yvalues []float64, valueFormatter ValueFormatter) AnnotationSeries {
return AnnotationSeries{
Name: name,
Style: Style{
Show: true,
StrokeColor: GetDefaultSeriesStrokeColor(0),
},
Annotations: []Annotation{
Annotation{
X: float64(xvalues[len(xvalues)-1].Unix()),
Y: yvalues[len(yvalues)-1],
Label: valueFormatter(yvalues[len(yvalues)-1]),
},
},
}
}
// Annotation is a label on the chart. // Annotation is a label on the chart.
type Annotation struct { type Annotation struct {

View File

@ -15,17 +15,15 @@ type Style struct {
StrokeWidth float64 StrokeWidth float64
StrokeColor drawing.Color StrokeColor drawing.Color
FillColor drawing.Color
FillColor drawing.Color FontSize float64
FontColor drawing.Color
FontSize float64 Font *truetype.Font
FontColor drawing.Color
Font *truetype.Font
} }
// IsZero returns if the object is set or not. // IsZero returns if the object is set or not.
func (s Style) IsZero() bool { func (s Style) IsZero() bool {
return s.StrokeColor.IsZero() && s.FillColor.IsZero() && s.StrokeWidth == 0 && 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
} }
// GetStrokeColor returns the stroke color. // GetStrokeColor returns the stroke color.
@ -107,12 +105,12 @@ func (s Style) GetPadding(defaults ...Box) Box {
// WithDefaultsFrom coalesces two styles into a new style. // WithDefaultsFrom coalesces two styles into a new style.
func (s Style) WithDefaultsFrom(defaults Style) (final Style) { func (s Style) WithDefaultsFrom(defaults Style) (final Style) {
final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor)
final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth)
final.FillColor = s.GetFillColor(defaults.FillColor) final.FillColor = s.GetFillColor(defaults.FillColor)
final.FontColor = s.GetFontColor(defaults.FontColor) final.FontColor = s.GetFontColor(defaults.FontColor)
final.Font = s.GetFont(defaults.Font) final.Font = s.GetFont(defaults.Font)
final.Padding = s.GetPadding(defaults.Padding) final.Padding = s.GetPadding(defaults.Padding)
final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor)
final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth)
return return
} }

237
style_test.go Normal file
View File

@ -0,0 +1,237 @@
package chart
import (
"strings"
"testing"
"github.com/blendlabs/go-assert"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
)
func TestStyleIsZero(t *testing.T) {
assert := assert.New(t)
zero := Style{}
assert.True(zero.IsZero())
strokeColor := Style{StrokeColor: drawing.ColorWhite}
assert.False(strokeColor.IsZero())
fillColor := Style{FillColor: drawing.ColorWhite}
assert.False(fillColor.IsZero())
strokeWidth := Style{StrokeWidth: 5.0}
assert.False(strokeWidth.IsZero())
fontSize := Style{FontSize: 12.0}
assert.False(fontSize.IsZero())
fontColor := Style{FontColor: drawing.ColorWhite}
assert.False(fontColor.IsZero())
font := Style{Font: &truetype.Font{}}
assert.False(font.IsZero())
}
func TestStyleGetStrokeColor(t *testing.T) {
assert := assert.New(t)
unset := Style{}
assert.Equal(drawing.ColorTransparent, unset.GetStrokeColor())
assert.Equal(drawing.ColorWhite, unset.GetStrokeColor(drawing.ColorWhite))
set := Style{StrokeColor: drawing.ColorWhite}
assert.Equal(drawing.ColorWhite, set.GetStrokeColor())
assert.Equal(drawing.ColorWhite, set.GetStrokeColor(drawing.ColorBlack))
}
func TestStyleGetFillColor(t *testing.T) {
assert := assert.New(t)
unset := Style{}
assert.Equal(drawing.ColorTransparent, unset.GetFillColor())
assert.Equal(drawing.ColorWhite, unset.GetFillColor(drawing.ColorWhite))
set := Style{FillColor: drawing.ColorWhite}
assert.Equal(drawing.ColorWhite, set.GetFillColor())
assert.Equal(drawing.ColorWhite, set.GetFillColor(drawing.ColorBlack))
}
func TestStyleGetStrokeWidth(t *testing.T) {
assert := assert.New(t)
unset := Style{}
assert.Equal(DefaultStrokeWidth, unset.GetStrokeWidth())
assert.Equal(DefaultStrokeWidth+1, unset.GetStrokeWidth(DefaultStrokeWidth+1))
set := Style{StrokeWidth: DefaultStrokeWidth + 2}
assert.Equal(DefaultStrokeWidth+2, set.GetStrokeWidth())
assert.Equal(DefaultStrokeWidth+2, set.GetStrokeWidth(DefaultStrokeWidth+1))
}
func TestStyleGetFontSize(t *testing.T) {
assert := assert.New(t)
unset := Style{}
assert.Equal(DefaultFontSize, unset.GetFontSize())
assert.Equal(DefaultFontSize+1, unset.GetFontSize(DefaultFontSize+1))
set := Style{FontSize: DefaultFontSize + 2}
assert.Equal(DefaultFontSize+2, set.GetFontSize())
assert.Equal(DefaultFontSize+2, set.GetFontSize(DefaultFontSize+1))
}
func TestStyleGetFontColor(t *testing.T) {
assert := assert.New(t)
unset := Style{}
assert.Equal(drawing.ColorTransparent, unset.GetFontColor())
assert.Equal(drawing.ColorWhite, unset.GetFontColor(drawing.ColorWhite))
set := Style{FontColor: drawing.ColorWhite}
assert.Equal(drawing.ColorWhite, set.GetFontColor())
assert.Equal(drawing.ColorWhite, set.GetFontColor(drawing.ColorBlack))
}
func TestStyleGetFont(t *testing.T) {
assert := assert.New(t)
f, err := GetDefaultFont()
assert.Nil(err)
unset := Style{}
assert.Nil(unset.GetFont())
assert.Equal(f, unset.GetFont(f))
set := Style{Font: f}
assert.NotNil(set.GetFont())
}
func TestStyleGetPadding(t *testing.T) {
assert := assert.New(t)
unset := Style{}
assert.True(unset.GetPadding().IsZero())
assert.False(unset.GetPadding(DefaultBackgroundPadding).IsZero())
assert.Equal(DefaultBackgroundPadding, unset.GetPadding(DefaultBackgroundPadding))
set := Style{Padding: DefaultBackgroundPadding}
assert.False(set.GetPadding().IsZero())
assert.Equal(DefaultBackgroundPadding, set.GetPadding())
assert.Equal(DefaultBackgroundPadding, set.GetPadding(Box{
Top: DefaultBackgroundPadding.Top + 1,
Left: DefaultBackgroundPadding.Left + 1,
Right: DefaultBackgroundPadding.Right + 1,
Bottom: DefaultBackgroundPadding.Bottom + 1,
}))
}
func TestStyleWithDefaultsFrom(t *testing.T) {
assert := assert.New(t)
f, err := GetDefaultFont()
assert.Nil(err)
unset := Style{}
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Font: f,
Padding: DefaultBackgroundPadding,
}
coalesced := unset.WithDefaultsFrom(set)
assert.Equal(set, coalesced)
}
func TestStyleSVG(t *testing.T) {
assert := assert.New(t)
f, err := GetDefaultFont()
assert.Nil(err)
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Font: f,
Padding: DefaultBackgroundPadding,
}
svgString := set.SVG(DefaultDPI)
assert.NotEmpty(svgString)
assert.True(strings.Contains(svgString, "stroke:rgba(255,255,255,1.0)"))
assert.True(strings.Contains(svgString, "stroke-width:5"))
assert.True(strings.Contains(svgString, "fill:rgba(255,255,255,1.0)"))
}
func TestStyleSVGStroke(t *testing.T) {
assert := assert.New(t)
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding,
}
svgStroke := set.SVGStroke()
assert.False(svgStroke.StrokeColor.IsZero())
assert.NotZero(svgStroke.StrokeWidth)
assert.True(svgStroke.FillColor.IsZero())
assert.True(svgStroke.FontColor.IsZero())
}
func TestStyleSVGFill(t *testing.T) {
assert := assert.New(t)
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding,
}
svgFill := set.SVGFill()
assert.False(svgFill.FillColor.IsZero())
assert.Zero(svgFill.StrokeWidth)
assert.True(svgFill.StrokeColor.IsZero())
assert.True(svgFill.FontColor.IsZero())
}
func TestStyleSVGFillAndStroke(t *testing.T) {
assert := assert.New(t)
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding,
}
svgFillAndStroke := set.SVGFillAndStroke()
assert.False(svgFillAndStroke.FillColor.IsZero())
assert.NotZero(svgFillAndStroke.StrokeWidth)
assert.False(svgFillAndStroke.StrokeColor.IsZero())
assert.True(svgFillAndStroke.FontColor.IsZero())
}
func TestStyleSVGText(t *testing.T) {
assert := assert.New(t)
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding,
}
svgStroke := set.SVGText()
assert.True(svgStroke.StrokeColor.IsZero())
assert.Zero(svgStroke.StrokeWidth)
assert.True(svgStroke.FillColor.IsZero())
assert.False(svgStroke.FontColor.IsZero())
}

12
tick.go
View File

@ -1,5 +1,17 @@
package chart package chart
// GenerateTicksWithStep generates a set of ticks.
func GenerateTicksWithStep(ra Range, step float64, vf ValueFormatter) []Tick {
var ticks []Tick
for cursor := ra.Min; cursor < ra.Max; cursor += step {
ticks = append(ticks, Tick{
Value: cursor,
Label: vf(cursor),
})
}
return ticks
}
// Tick represents a label on an axis. // Tick represents a label on an axis.
type Tick struct { type Tick struct {
Value float64 Value float64

1
tick_test.go Normal file
View File

@ -0,0 +1 @@
package chart

View File

@ -5,6 +5,11 @@ import (
"time" "time"
) )
// TimeToFloat64 returns a float64 representation of a time.
func TimeToFloat64(t time.Time) float64 {
return float64(t.Unix())
}
// MinAndMax returns both the min and max in one pass. // MinAndMax returns both the min and max in one pass.
func MinAndMax(values ...float64) (min float64, max float64) { func MinAndMax(values ...float64) (min float64, max float64) {
if len(values) == 0 { if len(values) == 0 {

38
value_formatter_test.go Normal file
View File

@ -0,0 +1,38 @@
package chart
import (
"testing"
"time"
"github.com/blendlabs/go-assert"
)
func TestTimeValueFormatterWithFormat(t *testing.T) {
assert := assert.New(t)
d := time.Now()
di := d.Unix()
df := float64(di)
s := TimeValueFormatterWithFormat(d, DefaultDateFormat)
si := TimeValueFormatterWithFormat(di, DefaultDateFormat)
sf := TimeValueFormatterWithFormat(df, DefaultDateFormat)
assert.Equal(s, si)
assert.Equal(s, sf)
sd := TimeValueFormatter(d)
sdi := TimeValueFormatter(di)
sdf := TimeValueFormatter(df)
assert.Equal(s, sd)
assert.Equal(s, sdi)
assert.Equal(s, sdf)
}
func TestFloatValueFormatterWithFormat(t *testing.T) {
assert := assert.New(t)
v := 123.456
sv := FloatValueFormatterWithFormat(v, "%.3f")
assert.Equal("123.456", sv)
assert.Equal("", FloatValueFormatterWithFormat(123, "%.3f"))
}

View File

@ -37,7 +37,13 @@ func (xa XAxis) GetTicks(r Renderer, ra Range, vf ValueFormatter) []Tick {
func (xa XAxis) generateTicks(r Renderer, ra Range, vf ValueFormatter) []Tick { func (xa XAxis) generateTicks(r Renderer, ra Range, vf ValueFormatter) []Tick {
step := xa.getTickStep(r, ra, vf) step := xa.getTickStep(r, ra, vf)
return xa.generateTicksWithStep(ra, step, vf) return GenerateTicksWithStep(ra, step, vf)
}
func (xa XAxis) getTickStep(r Renderer, ra Range, vf ValueFormatter) float64 {
tickCount := xa.getTickCount(r, ra, vf)
step := ra.Delta() / float64(tickCount)
return step
} }
func (xa XAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int { func (xa XAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int {
@ -58,27 +64,6 @@ func (xa XAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int {
return count return count
} }
func (xa XAxis) getTickStep(r Renderer, ra Range, vf ValueFormatter) float64 {
tickCount := xa.getTickCount(r, ra, vf)
step := ra.Delta() / float64(tickCount)
return step
}
func (xa XAxis) generateTicksWithStep(ra Range, step float64, vf ValueFormatter) []Tick {
var ticks []Tick
for cursor := ra.Min; cursor < ra.Max; cursor += step {
ticks = append(ticks, Tick{
Value: cursor,
Label: vf(cursor),
})
if len(ticks) == 20 {
return ticks
}
}
return ticks
}
// Render renders the axis // Render renders the axis
func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, ticks []Tick) { func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, ticks []Tick) {
tickFontSize := xa.Style.GetFontSize(DefaultFontSize) tickFontSize := xa.Style.GetFontSize(DefaultFontSize)

View File

@ -36,14 +36,7 @@ func (ya YAxis) GetTicks(r Renderer, ra Range, vf ValueFormatter) []Tick {
func (ya YAxis) generateTicks(r Renderer, ra Range, vf ValueFormatter) []Tick { func (ya YAxis) generateTicks(r Renderer, ra Range, vf ValueFormatter) []Tick {
step := ya.getTickStep(r, ra, vf) step := ya.getTickStep(r, ra, vf)
return ya.generateTicksWithStep(ra, step, vf) return GenerateTicksWithStep(ra, step, vf)
}
func (ya YAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int {
textHeight := int(ya.Style.GetFontSize(DefaultFontSize))
height := textHeight + DefaultMinimumTickVerticalSpacing
count := int(math.Ceil(float64(ra.Domain) / float64(height)))
return count
} }
func (ya YAxis) getTickStep(r Renderer, ra Range, vf ValueFormatter) float64 { func (ya YAxis) getTickStep(r Renderer, ra Range, vf ValueFormatter) float64 {
@ -51,18 +44,11 @@ func (ya YAxis) getTickStep(r Renderer, ra Range, vf ValueFormatter) float64 {
return ra.Delta() / float64(tickCount) return ra.Delta() / float64(tickCount)
} }
func (ya YAxis) generateTicksWithStep(ra Range, step float64, vf ValueFormatter) []Tick { func (ya YAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int {
var ticks []Tick textHeight := int(ya.Style.GetFontSize(DefaultFontSize))
for cursor := ra.Min; cursor < ra.Max; cursor += step { height := textHeight + DefaultMinimumTickVerticalSpacing
ticks = append(ticks, Tick{ count := int(math.Ceil(float64(ra.Domain) / float64(height)))
Value: cursor, return count
Label: vf(cursor),
})
if len(ticks) == 20 {
return ticks
}
}
return ticks
} }
// Render renders the axis. // Render renders the axis.