diff --git a/_examples/scatter/main.go b/_examples/scatter/main.go index d1394f1..9aed893 100644 --- a/_examples/scatter/main.go +++ b/_examples/scatter/main.go @@ -48,7 +48,33 @@ func drawChart(res http.ResponseWriter, req *http.Request) { } +func unit(res http.ResponseWriter, req *http.Request) { + graph := chart.Chart{ + Height: 50, + Width: 50, + Canvas: chart.Style{ + Padding: chart.Box{IsSet: true}, + }, + Background: chart.Style{ + Padding: chart.Box{IsSet: true}, + }, + Series: []chart.Series{ + chart.ContinuousSeries{ + XValues: chart.Sequence.Float64(0, 4, 1), + YValues: chart.Sequence.Float64(0, 4, 1), + }, + }, + } + + res.Header().Set("Content-Type", "image/png") + err := graph.Render(chart.PNG, res) + if err != nil { + log.Println(err.Error()) + } +} + func main() { http.HandleFunc("/", drawChart) + http.HandleFunc("/unit", unit) log.Fatal(http.ListenAndServe(":8080", nil)) } diff --git a/box.go b/box.go index 98c1f17..aff0989 100644 --- a/box.go +++ b/box.go @@ -5,8 +5,25 @@ import ( "math" ) +var ( + // BoxZero is a preset box that represents an intentional zero value. + BoxZero = Box{IsSet: true} +) + +// NewBox returns a new (set) box. +func NewBox(top, left, right, bottom int) Box { + return Box{ + IsSet: true, + Top: top, + Left: left, + Right: right, + Bottom: bottom, + } +} + // Box represents the main 4 dimensions of a box. type Box struct { + IsSet bool Top int Left int Right int @@ -15,6 +32,9 @@ type Box struct { // IsZero returns if the box is set or not. func (b Box) IsZero() bool { + if b.IsSet { + return false + } return b.Top == 0 && b.Left == 0 && b.Right == 0 && b.Bottom == 0 } @@ -25,7 +45,7 @@ func (b Box) String() string { // GetTop returns a coalesced value with a default. func (b Box) GetTop(defaults ...int) int { - if b.Top == 0 { + if !b.IsSet && b.Top == 0 { if len(defaults) > 0 { return defaults[0] } @@ -36,7 +56,7 @@ func (b Box) GetTop(defaults ...int) int { // GetLeft returns a coalesced value with a default. func (b Box) GetLeft(defaults ...int) int { - if b.Left == 0 { + if !b.IsSet && b.Left == 0 { if len(defaults) > 0 { return defaults[0] } @@ -47,7 +67,7 @@ func (b Box) GetLeft(defaults ...int) int { // GetRight returns a coalesced value with a default. func (b Box) GetRight(defaults ...int) int { - if b.Right == 0 { + if !b.IsSet && b.Right == 0 { if len(defaults) > 0 { return defaults[0] } @@ -58,7 +78,7 @@ func (b Box) GetRight(defaults ...int) int { // GetBottom returns a coalesced value with a default. func (b Box) GetBottom(defaults ...int) int { - if b.Bottom == 0 { + if !b.IsSet && b.Bottom == 0 { if len(defaults) > 0 { return defaults[0] } @@ -91,6 +111,7 @@ func (b Box) Aspect() float64 { // Clone returns a new copy of the box. func (b Box) Clone() Box { return Box{ + IsSet: b.IsSet, Top: b.Top, Left: b.Left, Right: b.Right, diff --git a/box_test.go b/box_test.go index 89eafcf..3f3fa02 100644 --- a/box_test.go +++ b/box_test.go @@ -109,9 +109,9 @@ func TestBoxConstrain(t *testing.T) { func TestBoxOuterConstrain(t *testing.T) { assert := assert.New(t) - box := Box{0, 0, 100, 100} - canvas := Box{5, 5, 95, 95} - taller := Box{-10, 5, 50, 50} + box := NewBox(0, 0, 100, 100) + canvas := NewBox(5, 5, 95, 95) + taller := NewBox(-10, 5, 50, 50) c := canvas.OuterConstrain(box, taller) assert.Equal(15, c.Top, c.String()) @@ -119,7 +119,7 @@ func TestBoxOuterConstrain(t *testing.T) { assert.Equal(95, c.Right, c.String()) assert.Equal(95, c.Bottom, c.String()) - wider := Box{5, 5, 110, 50} + wider := NewBox(5, 5, 110, 50) d := canvas.OuterConstrain(box, wider) assert.Equal(5, d.Top, d.String()) assert.Equal(5, d.Left, d.String()) diff --git a/chart_test.go b/chart_test.go index 02ac80d..4dead70 100644 --- a/chart_test.go +++ b/chart_test.go @@ -2,6 +2,8 @@ package chart import ( "bytes" + "image" + "image/png" "math" "testing" "time" @@ -484,10 +486,22 @@ func TestChartCheckRangesWithRanges(t *testing.T) { assert.Nil(c.checkRanges(xr, yr, yra)) } +func at(i image.Image, x, y int) drawing.Color { + return drawing.ColorFromAlphaMixedRGBA(i.At(x, y).RGBA()) +} + func TestChartE2ELine(t *testing.T) { assert := assert.New(t) c := Chart{ + Height: 50, + Width: 50, + Canvas: Style{ + Padding: Box{IsSet: true}, + }, + Background: Style{ + Padding: Box{IsSet: true}, + }, Series: []Series{ ContinuousSeries{ XValues: Sequence.Float64(0, 4, 1), @@ -502,5 +516,60 @@ func TestChartE2ELine(t *testing.T) { // do color tests ... - + i, err := png.Decode(buffer) + assert.Nil(err) + + // test the bottom and top of the line + assert.Equal(drawing.ColorWhite, at(i, 0, 0)) + assert.Equal(drawing.ColorWhite, at(i, 49, 49)) + + // test a line mid point + defaultSeriesColor := GetDefaultColor(0) + assert.Equal(defaultSeriesColor, at(i, 0, 49)) + assert.Equal(defaultSeriesColor, at(i, 49, 0)) + assert.Equal(drawing.ColorFromHex("bddbf6"), at(i, 24, 24)) +} + +func TestChartE2ELineWithFill(t *testing.T) { + assert := assert.New(t) + + c := Chart{ + Height: 50, + Width: 50, + Canvas: Style{ + Padding: Box{IsSet: true}, + }, + Background: Style{ + Padding: Box{IsSet: true}, + }, + Series: []Series{ + ContinuousSeries{ + Style: Style{ + Show: true, + StrokeColor: drawing.ColorBlue, + FillColor: drawing.ColorRed, + }, + XValues: Sequence.Float64(0, 4, 1), + YValues: Sequence.Float64(0, 4, 1), + }, + }, + } + + var buffer = &bytes.Buffer{} + err := c.Render(PNG, buffer) + assert.Nil(err) + + // do color tests ... + + i, err := png.Decode(buffer) + assert.Nil(err) + + // test the bottom and top of the line + assert.Equal(drawing.ColorWhite, at(i, 0, 0)) + assert.Equal(drawing.ColorRed, at(i, 49, 49)) + + // test a line mid point + defaultSeriesColor := drawing.ColorBlue + assert.Equal(defaultSeriesColor, at(i, 0, 49)) + assert.Equal(defaultSeriesColor, at(i, 49, 0)) } diff --git a/drawing/color.go b/drawing/color.go index 488bdcd..e7099b4 100644 --- a/drawing/color.go +++ b/drawing/color.go @@ -104,6 +104,16 @@ func (c Color) Equals(other Color) bool { c.A == other.A } +// AverageWith averages two colors. +func (c Color) AverageWith(other Color) Color { + return Color{ + R: (c.R + other.R) >> 1, + G: (c.G + other.G) >> 1, + B: (c.B + other.B) >> 1, + A: c.A, + } +} + // String returns a css string representation of the color. func (c Color) String() string { fa := float64(c.A) / float64(255) diff --git a/drawing/image.go b/drawing/image.go deleted file mode 100644 index 5252fb9..0000000 --- a/drawing/image.go +++ /dev/null @@ -1,23 +0,0 @@ -package drawing - -import "image" - -// Image is a helper wraper that allows (sane) access to pixel info. -type Image struct { - Inner *image.RGBA -} - -// Width returns the image's width in pixels. -func (i Image) Width() int { - return i.Inner.Rect.Size().X -} - -// Height returns the image's height in pixels. -func (i Image) Height() int { - return i.Inner.Rect.Size().Y -} - -// At returns a pixel color at a given x/y. -func (i Image) At(x, y int) Color { - return ColorFromAlphaMixedRGBA(i.Inner.At(x, y).RGBA()) -} diff --git a/xaxis_test.go b/xaxis_test.go index 289290b..f55ea29 100644 --- a/xaxis_test.go +++ b/xaxis_test.go @@ -61,7 +61,7 @@ func TestXAxisMeasure(t *testing.T) { assert.Nil(err) ticks := []Tick{{Value: 1.0, Label: "1.0"}, {Value: 2.0, Label: "2.0"}, {Value: 3.0, Label: "3.0"}} xa := XAxis{} - xab := xa.Measure(r, Box{0, 0, 100, 100}, &ContinuousRange{Min: 1.0, Max: 3.0, Domain: 100}, style, ticks) + xab := xa.Measure(r, NewBox(0, 0, 100, 100), &ContinuousRange{Min: 1.0, Max: 3.0, Domain: 100}, style, ticks) assert.Equal(122, xab.Width()) assert.Equal(21, xab.Height()) } diff --git a/yaxis_test.go b/yaxis_test.go index b603733..86deae5 100644 --- a/yaxis_test.go +++ b/yaxis_test.go @@ -61,7 +61,7 @@ func TestYAxisMeasure(t *testing.T) { assert.Nil(err) ticks := []Tick{{Value: 1.0, Label: "1.0"}, {Value: 2.0, Label: "2.0"}, {Value: 3.0, Label: "3.0"}} ya := YAxis{} - yab := ya.Measure(r, Box{0, 0, 100, 100}, &ContinuousRange{Min: 1.0, Max: 3.0, Domain: 100}, style, ticks) + yab := ya.Measure(r, NewBox(0, 0, 100, 100), &ContinuousRange{Min: 1.0, Max: 3.0, Domain: 100}, style, ticks) assert.Equal(32, yab.Width()) assert.Equal(110, yab.Height()) } @@ -79,7 +79,7 @@ func TestYAxisSecondaryMeasure(t *testing.T) { assert.Nil(err) ticks := []Tick{{Value: 1.0, Label: "1.0"}, {Value: 2.0, Label: "2.0"}, {Value: 3.0, Label: "3.0"}} ya := YAxis{AxisType: YAxisSecondary} - yab := ya.Measure(r, Box{0, 0, 100, 100}, &ContinuousRange{Min: 1.0, Max: 3.0, Domain: 100}, style, ticks) + yab := ya.Measure(r, NewBox(0, 0, 100, 100), &ContinuousRange{Min: 1.0, Max: 3.0, Domain: 100}, style, ticks) assert.Equal(32, yab.Width()) assert.Equal(110, yab.Height()) }