need to flesh out this test more.

This commit is contained in:
Will Charczuk 2016-07-28 14:30:00 -07:00
parent 3d9cf0da0c
commit 84df29b1c6
8 changed files with 141 additions and 76 deletions

View File

@ -6,6 +6,7 @@ go:
sudo: false sudo: false
before: before:
- go get -u github.com/blendlabs/go-assert
- go get ./... - go get ./...
script: script:

View File

@ -2,18 +2,12 @@ package chart
import "math" import "math"
// Annotation is a label on the chart.
type Annotation struct {
X, Y float64
Label string
}
// AnnotationSeries is a series of labels on the chart. // AnnotationSeries is a series of labels on the chart.
type AnnotationSeries struct { type AnnotationSeries struct {
Name string Name string
Style Style Style Style
YAxis YAxisType YAxis YAxisType
Annotations []Annotation Annotations []Value2
} }
// GetName returns the name of the time series. // GetName returns the name of the time series.
@ -31,6 +25,17 @@ func (as AnnotationSeries) GetYAxis() YAxisType {
return as.YAxis return as.YAxis
} }
func (as AnnotationSeries) annotationStyleDefaults(defaults Style) Style {
return Style{
Font: defaults.Font,
FillColor: DefaultAnnotationFillColor,
FontSize: DefaultAnnotationFontSize,
StrokeColor: defaults.StrokeColor,
StrokeWidth: defaults.StrokeWidth,
Padding: DefaultAnnotationPadding,
}
}
// Measure returns a bounds box of the series. // Measure returns a bounds box of the series.
func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) Box { func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) Box {
box := Box{ box := Box{
@ -40,17 +45,11 @@ 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.InheritFrom(Style{ seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
Font: defaults.Font,
FillColor: DefaultAnnotationFillColor,
FontSize: DefaultAnnotationFontSize,
StrokeColor: defaults.StrokeColor,
StrokeWidth: defaults.StrokeWidth,
Padding: DefaultAnnotationPadding,
})
for _, a := range as.Annotations { for _, a := range as.Annotations {
lx := canvasBox.Left + xrange.Translate(a.X) style := a.Style.InheritFrom(seriesStyle)
ly := canvasBox.Bottom - yrange.Translate(a.Y) lx := canvasBox.Left + xrange.Translate(a.XValue)
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
ab := MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label) ab := MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
box.Top = MinInt(box.Top, ab.Top) box.Top = MinInt(box.Top, ab.Top)
box.Left = MinInt(box.Left, ab.Left) box.Left = MinInt(box.Left, ab.Left)
@ -64,18 +63,11 @@ 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.InheritFrom(Style{ seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
Font: defaults.Font,
FontColor: DefaultTextColor,
FillColor: DefaultAnnotationFillColor,
FontSize: DefaultAnnotationFontSize,
StrokeColor: defaults.StrokeColor,
StrokeWidth: defaults.StrokeWidth,
Padding: DefaultAnnotationPadding,
})
for _, a := range as.Annotations { for _, a := range as.Annotations {
lx := canvasBox.Left + xrange.Translate(a.X) style := a.Style.InheritFrom(seriesStyle)
ly := canvasBox.Bottom - yrange.Translate(a.Y) lx := canvasBox.Left + xrange.Translate(a.XValue)
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
DrawAnnotation(r, canvasBox, style, lx, ly, a.Label) DrawAnnotation(r, canvasBox, style, lx, ly, a.Label)
} }
} }

View File

@ -15,11 +15,11 @@ func TestAnnotationSeriesMeasure(t *testing.T) {
Style: Style{ Style: Style{
Show: true, Show: true,
}, },
Annotations: []Annotation{ Annotations: []Value2{
{X: 1.0, Y: 1.0, Label: "1.0"}, {XValue: 1.0, YValue: 1.0, Label: "1.0"},
{X: 2.0, Y: 2.0, Label: "2.0"}, {XValue: 2.0, YValue: 2.0, Label: "2.0"},
{X: 3.0, Y: 3.0, Label: "3.0"}, {XValue: 3.0, YValue: 3.0, Label: "3.0"},
{X: 4.0, Y: 4.0, Label: "4.0"}, {XValue: 4.0, YValue: 4.0, Label: "4.0"},
}, },
} }
@ -68,11 +68,11 @@ func TestAnnotationSeriesRender(t *testing.T) {
FillColor: drawing.ColorWhite, FillColor: drawing.ColorWhite,
StrokeColor: drawing.ColorBlack, StrokeColor: drawing.ColorBlack,
}, },
Annotations: []Annotation{ Annotations: []Value2{
{X: 1.0, Y: 1.0, Label: "1.0"}, {XValue: 1.0, YValue: 1.0, Label: "1.0"},
{X: 2.0, Y: 2.0, Label: "2.0"}, {XValue: 2.0, YValue: 2.0, Label: "2.0"},
{X: 3.0, Y: 3.0, Label: "3.0"}, {XValue: 3.0, YValue: 3.0, Label: "3.0"},
{X: 4.0, Y: 4.0, Label: "4.0"}, {XValue: 4.0, YValue: 4.0, Label: "4.0"},
}, },
} }

View File

@ -13,14 +13,14 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
Canvas: chart.Style{ Canvas: chart.Style{
FillColor: chart.ColorLightGray, FillColor: chart.ColorLightGray,
}, },
Values: []chart.PieChartValue{ Values: []chart.Value{
{Value: 0.2, Label: "Blue"}, {Value: 10, Label: "Blue"},
{Value: 0.2, Label: "Green"}, {Value: 9, Label: "Green"},
{Value: 0.2, Label: "Gray"}, {Value: 8, Label: "Gray"},
{Value: 0.1, Label: "Orange"}, {Value: 7, Label: "Orange"},
{Value: 0.1, Label: "HEANG"}, {Value: 6, Label: "HEANG"},
{Value: 0.1, Label: "??"}, {Value: 5, Label: "??"},
{Value: 0.1, Label: "!!"}, {Value: 2, Label: "!!"},
}, },
} }

View File

@ -7,13 +7,6 @@ import (
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
) )
// PieChartValue is a slice of a pie-chart.
type PieChartValue struct {
Style Style
Label string
Value float64
}
// PieChart is a chart that draws sections of a circle based on percentages. // PieChart is a chart that draws sections of a circle based on percentages.
type PieChart struct { type PieChart struct {
Title string Title string
@ -29,7 +22,7 @@ type PieChart struct {
Font *truetype.Font Font *truetype.Font
defaultFont *truetype.Font defaultFont *truetype.Font
Values []PieChartValue Values []Value
Elements []Renderable Elements []Renderable
} }
@ -94,11 +87,8 @@ func (pc PieChart) Render(rp RendererProvider, w io.Writer) error {
pc.drawBackground(r) pc.drawBackground(r)
pc.drawCanvas(r, canvasBox) pc.drawCanvas(r, canvasBox)
valuesWithPlaceholder, err := pc.finalizeValues(pc.Values) finalValues := pc.finalizeValues(pc.Values)
if err != nil { pc.drawSlices(r, canvasBox, finalValues)
return err
}
pc.drawSlices(r, canvasBox, valuesWithPlaceholder)
pc.drawTitle(r) pc.drawTitle(r)
for _, a := range pc.Elements { for _, a := range pc.Elements {
a(r, canvasBox, pc.styleDefaultsElements()) a(r, canvasBox, pc.styleDefaultsElements())
@ -137,7 +127,7 @@ func (pc PieChart) drawTitle(r Renderer) {
} }
} }
func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []PieChartValue) { func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
cx, cy := canvasBox.Center() cx, cy := canvasBox.Center()
diameter := MinInt(canvasBox.Width(), canvasBox.Height()) diameter := MinInt(canvasBox.Width(), canvasBox.Height())
radius := float64(diameter >> 1) radius := float64(diameter >> 1)
@ -172,6 +162,7 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []PieChartValue)
tb := r.MeasureText(v.Label) tb := r.MeasureText(v.Label)
lx = lx - (tb.Width() >> 1) lx = lx - (tb.Width() >> 1)
ly = ly + (tb.Height() >> 1)
r.Text(v.Label, lx, ly) r.Text(v.Label, lx, ly)
} }
@ -179,22 +170,8 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []PieChartValue)
} }
} }
func (pc PieChart) finalizeValues(values []PieChartValue) ([]PieChartValue, error) { func (pc PieChart) finalizeValues(values []Value) []Value {
var total float64 return Values(values).Normalize()
for _, v := range values {
total += v.Value
if total > 1.0 {
return nil, errors.New("Values total exceeded 1.0; please normalize pie chart values to [0,1.0)")
}
}
remainder := 1.0 - total
if RoundDown(remainder, 0.0001) > 0 {
return append(values, PieChartValue{
Style: pc.styleDefaultsPieChartValue(),
Value: remainder,
}), nil
}
return values, nil
} }
func (pc PieChart) getDefaultCanvasBox() Box { func (pc PieChart) getDefaultCanvasBox() Box {

31
pie_chart_test.go Normal file
View File

@ -0,0 +1,31 @@
package chart
import (
"bytes"
"testing"
assert "github.com/blendlabs/go-assert"
)
func TestPieChart(t *testing.T) {
assert := assert.New(t)
pie := PieChart{
Canvas: Style{
FillColor: ColorLightGray,
},
Values: []Value{
{Value: 10, Label: "Blue"},
{Value: 9, Label: "Green"},
{Value: 8, Label: "Gray"},
{Value: 7, Label: "Orange"},
{Value: 6, Label: "HEANG"},
{Value: 5, Label: "??"},
{Value: 2, Label: "!!"},
},
}
b := bytes.NewBuffer([]byte{})
pie.Render(PNG, b)
assert.NotZero(b.Len())
}

18
util.go
View File

@ -176,6 +176,24 @@ func SeqRand(samples int, scale float64) []float64 {
return values return values
} }
// Sum sums a set of values.
func Sum(values ...float64) float64 {
var total float64
for _, v := range values {
total += v
}
return total
}
// SumInt sums a set of values.
func SumInt(values ...int) int {
var total int
for _, v := range values {
total += v
}
return total
}
// SeqDays generates a sequence of timestamps by day, from -days to today. // SeqDays generates a sequence of timestamps by day, from -days to today.
func SeqDays(days int) []time.Time { func SeqDays(days int) []time.Time {
var values []time.Time var values []time.Time

46
value.go Normal file
View File

@ -0,0 +1,46 @@
package chart
// Value is a chart value.
type Value struct {
Style Style
Label string
Value float64
}
// Values is an array of Value.
type Values []Value
// Values returns the values.
func (vs Values) Values() []float64 {
values := make([]float64, len(vs))
for index, v := range vs {
values[index] = v.Value
}
return values
}
// ValuesNormalized returns normalized values.
func (vs Values) ValuesNormalized() []float64 {
return Normalize(vs.Values()...)
}
// Normalize returns the values normalized.
func (vs Values) Normalize() []Value {
output := make([]Value, len(vs))
total := Sum(vs.Values()...)
for index, v := range vs {
output[index] = Value{
Style: v.Style,
Label: v.Label,
Value: (v.Value / total),
}
}
return output
}
// Value2 is a two axis value.
type Value2 struct {
Style Style
Label string
XValue, YValue float64
}