Add possibility to render charts to image.Image

Add interface Imager for ToImage method.
Implement ToImage in rasterRenderer.
Implement ToImage in Chart, BarChart and PieChart.
This commit is contained in:
Fredrik Wallgren 2016-08-15 11:38:41 +02:00
parent 102f7a8aa3
commit b0cccfa7c7
No known key found for this signature in database
GPG key ID: F47F4EC105BDC53E
5 changed files with 170 additions and 0 deletions

View file

@ -2,6 +2,7 @@ package chart
import ( import (
"errors" "errors"
"image"
"io" "io"
"math" "math"
@ -132,6 +133,56 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
return r.Save(w) return r.Save(w)
} }
// ToImage renders the chart with the given renderer to an image.
func (bc BarChart) ToImage(r Renderer) (image.Image, error) {
imager, ok := r.(Imager)
if !ok {
return nil, errors.New("Renderer does not support ToImage")
}
if len(bc.Bars) == 0 {
return nil, errors.New("Please provide at least one bar.")
}
if bc.Font == nil {
defaultFont, err := GetDefaultFont()
if err != nil {
return nil, err
}
bc.defaultFont = defaultFont
}
r.SetDPI(bc.GetDPI())
bc.drawBackground(r)
var canvasBox Box
var yt []Tick
var yr Range
var yf ValueFormatter
canvasBox = bc.getDefaultCanvasBox()
yr = bc.getRanges()
yr = bc.setRangeDomains(canvasBox, yr)
yf = bc.getValueFormatters()
if bc.hasAxes() {
yt = bc.getAxesTicks(r, yr, yf)
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
yr = bc.setRangeDomains(canvasBox, yr)
}
bc.drawBars(r, canvasBox, yr)
bc.drawXAxis(r, canvasBox)
bc.drawYAxis(r, canvasBox, yr, yt)
bc.drawTitle(r)
for _, a := range bc.Elements {
a(r, canvasBox, bc.styleDefaultsElements())
}
return imager.ToImage(), nil
}
func (bc BarChart) getRanges() Range { func (bc BarChart) getRanges() Range {
var yrange Range var yrange Range
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() { if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {

View file

@ -2,6 +2,7 @@ package chart
import ( import (
"errors" "errors"
"image"
"io" "io"
"math" "math"
@ -132,6 +133,72 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
return r.Save(w) return r.Save(w)
} }
// ToImage renders the chart with the given renderer to the given io.Writer.
func (c Chart) ToImage(r Renderer) (image.Image, error) {
imager, ok := r.(Imager)
if !ok {
return nil, errors.New("Renderer does not support ToImage")
}
if len(c.Series) == 0 {
return nil, errors.New("Please provide at least one series")
}
c.YAxisSecondary.AxisType = YAxisSecondary
if c.Font == nil {
defaultFont, err := GetDefaultFont()
if err != nil {
return nil, err
}
c.defaultFont = defaultFont
}
r.SetDPI(c.GetDPI(DefaultDPI))
c.drawBackground(r)
var xt, yt, yta []Tick
xr, yr, yra := c.getRanges()
canvasBox := c.getDefaultCanvasBox()
xf, yf, yfa := c.getValueFormatters()
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
err := c.checkRanges(xr, yr, yra)
if err != nil {
return nil, err
}
if c.hasAxes() {
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
canvasBox = c.getAxisAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
// do a second pass in case things haven't settled yet.
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
canvasBox = c.getAxisAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
}
if c.hasAnnotationSeries() {
canvasBox = c.getAnnotationAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xf, yf, yfa)
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
}
c.drawCanvas(r, canvasBox)
c.drawAxes(r, canvasBox, xr, yr, yra, xt, yt, yta)
for index, series := range c.Series {
c.drawSeries(r, canvasBox, xr, yr, yra, series, index)
}
c.drawTitle(r)
for _, a := range c.Elements {
a(r, canvasBox, c.styleDefaultsElements())
}
return imager.ToImage(), nil
}
func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) { func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
var minx, maxx float64 = math.MaxFloat64, -math.MaxFloat64 var minx, maxx float64 = math.MaxFloat64, -math.MaxFloat64
var miny, maxy float64 = math.MaxFloat64, -math.MaxFloat64 var miny, maxy float64 = math.MaxFloat64, -math.MaxFloat64

10
imager.go Normal file
View file

@ -0,0 +1,10 @@
package chart
import "image"
// Imager interface is implemented by renderers that want to expose the data
// as the image.Image interface.
type Imager interface {
// ToImage returns a image.Image
ToImage() image.Image
}

View file

@ -2,6 +2,7 @@ package chart
import ( import (
"errors" "errors"
"image"
"io" "io"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
@ -98,6 +99,42 @@ func (pc PieChart) Render(rp RendererProvider, w io.Writer) error {
return r.Save(w) return r.Save(w)
} }
// ToImage renders the chart with the given renderer to an image.
func (pc PieChart) ToImage(r Renderer) (image.Image, error) {
imager, ok := r.(Imager)
if !ok {
return nil, errors.New("Renderer does not support ToImage")
}
if len(pc.Values) == 0 {
return nil, errors.New("Please provide at least one value.")
}
if pc.Font == nil {
defaultFont, err := GetDefaultFont()
if err != nil {
return nil, err
}
pc.defaultFont = defaultFont
}
r.SetDPI(pc.GetDPI(DefaultDPI))
canvasBox := pc.getDefaultCanvasBox()
canvasBox = pc.getCircleAdjustedCanvasBox(canvasBox)
pc.drawBackground(r)
pc.drawCanvas(r, canvasBox)
finalValues := pc.finalizeValues(pc.Values)
pc.drawSlices(r, canvasBox, finalValues)
pc.drawTitle(r)
for _, a := range pc.Elements {
a(r, canvasBox, pc.styleDefaultsElements())
}
return imager.ToImage(), nil
}
func (pc PieChart) drawBackground(r Renderer) { func (pc PieChart) drawBackground(r Renderer) {
Draw.Box(r, Box{ Draw.Box(r, Box{
Right: pc.GetWidth(), Right: pc.GetWidth(),

View file

@ -212,3 +212,8 @@ func (rr *rasterRenderer) ClearTextRotation() {
func (rr *rasterRenderer) Save(w io.Writer) error { func (rr *rasterRenderer) Save(w io.Writer) error {
return png.Encode(w, rr.i) return png.Encode(w, rr.i)
} }
// ToImage implements the Imager interface method.
func (rr *rasterRenderer) ToImage() image.Image {
return rr.i
}