Skip to content
This repository has been archived by the owner on Aug 23, 2024. It is now read-only.

Commit

Permalink
Adds the ability to draw an XY scatter plot. (#27)
Browse files Browse the repository at this point in the history
* works more or less

* updating comment

* removing debugging printf

* adding output

* tweaks

* missed a couple series validations

* testing auto coloring

* updated output.png

* color tests etc.

* sanity check tests.

* should not use unkeyed fields anyway.
  • Loading branch information
wcharczuk authored Mar 6, 2017
1 parent 17b28be commit b713ff8
Show file tree
Hide file tree
Showing 22 changed files with 511 additions and 72 deletions.
5 changes: 4 additions & 1 deletion _examples/request_timings/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func parseFloat64(str string) float64 {
func readData() ([]time.Time, []float64) {
var xvalues []time.Time
var yvalues []float64
chart.File.ReadByLines("requests.csv", func(line string) {
err := chart.File.ReadByLines("requests.csv", func(line string) {
parts := strings.Split(line, ",")
year := parseInt(parts[0])
month := parseInt(parts[1])
Expand All @@ -33,6 +33,9 @@ func readData() ([]time.Time, []float64) {
xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC))
yvalues = append(yvalues, elapsedMillis)
})
if err != nil {
fmt.Println(err.Error())
}
return xvalues, yvalues
}

Expand Down
80 changes: 80 additions & 0 deletions _examples/scatter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import (
"log"
"net/http"

"github.com/wcharczuk/go-chart"
)

func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
Series: []chart.Series{
chart.ContinuousSeries{
Style: chart.Style{
Show: true,
StrokeWidth: chart.Disabled,
DotWidth: 3,
},
XValues: chart.Sequence.Random(32, 1024),
YValues: chart.Sequence.Random(32, 1024),
},
chart.ContinuousSeries{
Style: chart.Style{
Show: true,
StrokeWidth: chart.Disabled,
DotWidth: 5,
},
XValues: chart.Sequence.Random(16, 1024),
YValues: chart.Sequence.Random(16, 1024),
},
chart.ContinuousSeries{
Style: chart.Style{
Show: true,
StrokeWidth: chart.Disabled,
DotWidth: 7,
},
XValues: chart.Sequence.Random(8, 1024),
YValues: chart.Sequence.Random(8, 1024),
},
},
}

res.Header().Set("Content-Type", "image/png")
err := graph.Render(chart.PNG, res)
if err != nil {
log.Println(err.Error())
}

}

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))
}
Binary file added _examples/scatter/output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 25 additions & 4 deletions box.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,36 @@ 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 {
Top int
Left int
Right int
Bottom int
IsSet bool
}

// 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
}

Expand All @@ -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]
}
Expand All @@ -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]
}
Expand All @@ -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]
}
Expand All @@ -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]
}
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions box_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,17 @@ 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())
assert.Equal(5, c.Left, c.String())
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())
Expand Down
5 changes: 4 additions & 1 deletion chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,10 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {

func (c Chart) checkHasVisibleSeries() error {
hasVisibleSeries := false
var style Style
for _, s := range c.Series {
hasVisibleSeries = hasVisibleSeries || (s.GetStyle().IsZero() || s.GetStyle().Show)
style = s.GetStyle()
hasVisibleSeries = hasVisibleSeries || (style.IsZero() || style.Show)
}
if !hasVisibleSeries {
return fmt.Errorf("must have (1) visible series; make sure if you set a style, you set .Show = true")
Expand Down Expand Up @@ -511,6 +513,7 @@ func (c Chart) styleDefaultsCanvas() Style {
func (c Chart) styleDefaultsSeries(seriesIndex int) Style {
strokeColor := GetDefaultColor(seriesIndex)
return Style{
DotColor: strokeColor,
StrokeColor: strokeColor,
StrokeWidth: DefaultSeriesLineWidth,
Font: c.GetFont(),
Expand Down
90 changes: 90 additions & 0 deletions chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package chart

import (
"bytes"
"image"
"image/png"
"math"
"testing"
"time"
Expand Down Expand Up @@ -483,3 +485,91 @@ func TestChartCheckRangesWithRanges(t *testing.T) {
xr, yr, yra := c.getRanges()
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),
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.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))
}
2 changes: 2 additions & 0 deletions defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const (
DefaultChartWidth = 1024
// DefaultStrokeWidth is the default chart stroke width.
DefaultStrokeWidth = 0.0
// DefaultDotWidth is the default chart dot width.
DefaultDotWidth = 0.0
// DefaultSeriesLineWidth is the default line width.
DefaultSeriesLineWidth = 1.0
// DefaultAxisLineWidth is the line width of the axis lines.
Expand Down
38 changes: 27 additions & 11 deletions draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
var vx, vy float64
var x, y int

fill := style.GetFillColor()
if !fill.IsZero() {
style.GetFillOptions().WriteToRenderer(r)
if style.ShouldDrawStroke() && style.ShouldDrawFill() {
style.GetFillOptions().WriteDrawingOptionsToRenderer(r)
r.MoveTo(x0, y0)
for i := 1; i < vs.Len(); i++ {
vx, vy = vs.GetValue(i)
Expand All @@ -43,16 +42,33 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
r.Fill()
}

style.GetStrokeOptions().WriteToRenderer(r)
if style.ShouldDrawStroke() {
style.GetStrokeOptions().WriteDrawingOptionsToRenderer(r)

r.MoveTo(x0, y0)
for i := 1; i < vs.Len(); i++ {
vx, vy = vs.GetValue(i)
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy)
r.LineTo(x, y)
r.MoveTo(x0, y0)
for i := 1; i < vs.Len(); i++ {
vx, vy = vs.GetValue(i)
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy)
r.LineTo(x, y)
}
r.Stroke()
}

if style.ShouldDrawDot() {
dotWidth := style.GetDotWidth()

style.GetDotOptions().WriteDrawingOptionsToRenderer(r)

for i := 0; i < vs.Len(); i++ {
vx, vy = vs.GetValue(i)
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy)

r.Circle(dotWidth, x, y)
r.FillStroke()
}
}
r.Stroke()
}

// BoundedSeries draws a series that implements BoundedValueProvider.
Expand Down
Loading

0 comments on commit b713ff8

Please sign in to comment.