diff --git a/axis.go b/axis.go index 37586f1..f018df6 100644 --- a/axis.go +++ b/axis.go @@ -15,5 +15,6 @@ type Axis interface { GetName() string GetStyle() Style GetTicks(r Renderer, ra Range, vf ValueFormatter) []Tick + GetGridLines(ticks []Tick) []GridLine Render(c *Chart, r Renderer, canvasBox Box, ra Range, ticks []Tick) } diff --git a/chart.go b/chart.go index ba03027..e0cd5f4 100644 --- a/chart.go +++ b/chart.go @@ -24,8 +24,9 @@ type Chart struct { YAxis YAxis YAxisSecondary YAxis - Font *truetype.Font - Series []Series + Font *truetype.Font + Series []Series + Elements []Renderable } // GetDPI returns the dpi for the chart. @@ -100,6 +101,10 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error { } c.drawTitle(r) + for _, a := range c.Elements { + a(r, canvasBox) + } + return r.Save(w) } diff --git a/defaults.go b/defaults.go index 8b74650..ace5ba7 100644 --- a/defaults.go +++ b/defaults.go @@ -82,6 +82,8 @@ var ( DefaultFillColor = drawing.Color{R: 0, G: 217, B: 116, A: 255} // DefaultAnnotationFillColor is the default annotation background color. DefaultAnnotationFillColor = drawing.Color{R: 255, G: 255, B: 255, A: 255} + // DefaultGridLineColor is the default grid line color. + DefaultGridLineColor = drawing.Color{R: 239, G: 239, B: 239, A: 255} ) var ( diff --git a/grid_line.go b/grid_line.go index d692eba..6fce265 100644 --- a/grid_line.go +++ b/grid_line.go @@ -30,14 +30,27 @@ func (gl GridLine) Horizontal() bool { // Render renders the gridline func (gl GridLine) Render(r Renderer, canvasBox Box, ra Range) { - lineleft := canvasBox.Left - lineright := canvasBox.Right - lineheight := canvasBox.Bottom - ra.Translate(gl.Value) + if gl.IsVertical { + lineLeft := canvasBox.Left + ra.Translate(gl.Value) + lineBottom := canvasBox.Bottom + lineTop := canvasBox.Top - r.SetStrokeColor(gl.Style.GetStrokeColor(DefaultAxisColor)) - r.SetStrokeWidth(gl.Style.GetStrokeWidth(DefaultAxisLineWidth)) + r.SetStrokeColor(gl.Style.GetStrokeColor(DefaultAxisColor)) + r.SetStrokeWidth(gl.Style.GetStrokeWidth(DefaultAxisLineWidth)) - r.MoveTo(lineleft, lineheight) - r.LineTo(lineright, lineheight) - r.Stroke() + r.MoveTo(lineLeft, lineBottom) + r.LineTo(lineLeft, lineTop) + r.Stroke() + } else { + lineLeft := canvasBox.Left + lineRight := canvasBox.Right + lineHeight := canvasBox.Bottom - ra.Translate(gl.Value) + + r.SetStrokeColor(gl.Style.GetStrokeColor(DefaultAxisColor)) + r.SetStrokeWidth(gl.Style.GetStrokeWidth(DefaultAxisLineWidth)) + + r.MoveTo(lineLeft, lineHeight) + r.LineTo(lineRight, lineHeight) + r.Stroke() + } } diff --git a/renderable.go b/renderable.go index 8216685..c1205ee 100644 --- a/renderable.go +++ b/renderable.go @@ -1,6 +1,4 @@ package chart -// Renderable is a type that can be rendered onto a chart. -type Renderable interface { - Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) -} +// Renderable is a function that can be called to render custom elements on the chart. +type Renderable func(r Renderer, canvasBox Box) diff --git a/series.go b/series.go index af75d5e..aae51fb 100644 --- a/series.go +++ b/series.go @@ -3,5 +3,5 @@ package chart // Series is an alias to Renderable. type Series interface { GetYAxis() YAxisType - Renderable + Render(r Renderer, canvasBox Box, xrange, yrange Range, s Style) } diff --git a/testserver/main.go b/testserver/main.go index d570e97..269d596 100644 --- a/testserver/main.go +++ b/testserver/main.go @@ -46,6 +46,12 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult { Style: chart.Style{ Show: true, }, + GridMajorStyle: chart.Style{ + Show: true, + }, + GridMinorStyle: chart.Style{ + Show: true, + }, }, YAxis: chart.YAxis{ Style: chart.Style{ @@ -57,6 +63,12 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult { StrokeWidth: 1.0, }, }, + GridMajorStyle: chart.Style{ + Show: true, + }, + GridMinorStyle: chart.Style{ + Show: true, + }, }, Series: []chart.Series{ chart.TimeSeries{ diff --git a/xaxis.go b/xaxis.go index 402d544..1a2a525 100644 --- a/xaxis.go +++ b/xaxis.go @@ -12,6 +12,10 @@ type XAxis struct { ValueFormatter ValueFormatter Range Range Ticks []Tick + + GridLines []GridLine + GridMajorStyle Style + GridMinorStyle Style } // GetName returns the name. @@ -62,6 +66,41 @@ func (xa XAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int { return count } +// GetGridLines returns the gridlines for the axis. +func (xa XAxis) GetGridLines(ticks []Tick) []GridLine { + if len(xa.GridLines) > 0 { + return xa.GridLines + } + return xa.generateGridLines(ticks) +} + +func (xa XAxis) generateGridLines(ticks []Tick) []GridLine { + var gl []GridLine + isMinor := false + minorStyle := Style{ + StrokeColor: DefaultGridLineColor.WithAlpha(100), + StrokeWidth: 1.0, + } + majorStyle := Style{ + StrokeColor: DefaultGridLineColor, + StrokeWidth: 1.0, + } + for _, t := range ticks { + s := majorStyle + if isMinor { + s = minorStyle + } + gl = append(gl, GridLine{ + Style: s, + IsMinor: isMinor, + IsVertical: true, + Value: t.Value, + }) + isMinor = !isMinor + } + return gl +} + // Measure returns the bounds of the axis. func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, ticks []Tick) Box { defaultFont, _ := GetDefaultFont() @@ -121,4 +160,13 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, ticks []Tick) { r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight) r.Stroke() } + + if xa.GridMajorStyle.Show || xa.GridMinorStyle.Show { + for _, gl := range xa.GetGridLines(ticks) { + if (gl.IsMinor && xa.GridMinorStyle.Show) || + (!gl.IsMinor && xa.GridMajorStyle.Show) { + gl.Render(r, canvasBox, ra) + } + } + } } diff --git a/yaxis.go b/yaxis.go index 47e8786..2dc9439 100644 --- a/yaxis.go +++ b/yaxis.go @@ -18,6 +18,10 @@ type YAxis struct { ValueFormatter ValueFormatter Range Range Ticks []Tick + + GridLines []GridLine + GridMajorStyle Style + GridMinorStyle Style } // GetName returns the name. @@ -62,6 +66,40 @@ func (ya YAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int { return int(math.Ceil(float64(ra.Domain) / float64(tb.Height()+DefaultMinimumTickVerticalSpacing))) } +// GetGridLines returns the gridlines for the axis. +func (ya YAxis) GetGridLines(ticks []Tick) []GridLine { + if len(ya.GridLines) > 0 { + return ya.GridLines + } + return ya.generateGridLines(ticks) +} + +func (ya YAxis) generateGridLines(ticks []Tick) []GridLine { + var gl []GridLine + isMinor := false + minorStyle := Style{ + StrokeColor: DefaultGridLineColor.WithAlpha(100), + StrokeWidth: 1.0, + } + majorStyle := Style{ + StrokeColor: DefaultGridLineColor, + StrokeWidth: 1.0, + } + for _, t := range ticks { + s := majorStyle + if isMinor { + s = minorStyle + } + gl = append(gl, GridLine{ + Style: s, + IsMinor: isMinor, + Value: t.Value, + }) + isMinor = !isMinor + } + return gl +} + // Measure returns the bounds of the axis. func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, ticks []Tick) Box { defaultFont, _ := GetDefaultFont() @@ -159,4 +197,13 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, ticks []Tick) { if ya.Zero.Style.Show { ya.Zero.Render(r, canvasBox, ra) } + + if ya.GridMajorStyle.Show || ya.GridMinorStyle.Show { + for _, gl := range ya.GetGridLines(ticks) { + if (gl.IsMinor && ya.GridMinorStyle.Show) || + (!gl.IsMinor && ya.GridMajorStyle.Show) { + gl.Render(r, canvasBox, ra) + } + } + } }