From b0cccfa7c760242ff6078a2b80228d0163e88036 Mon Sep 17 00:00:00 2001 From: Fredrik Wallgren Date: Mon, 15 Aug 2016 11:38:41 +0200 Subject: [PATCH] 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. --- bar_chart.go | 51 +++++++++++++++++++++++++++++++++++ chart.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++ imager.go | 10 +++++++ pie_chart.go | 37 +++++++++++++++++++++++++ raster_renderer.go | 5 ++++ 5 files changed, 170 insertions(+) create mode 100644 imager.go diff --git a/bar_chart.go b/bar_chart.go index 6a74083..6e122cf 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -2,6 +2,7 @@ package chart import ( "errors" + "image" "io" "math" @@ -132,6 +133,56 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error { 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 { var yrange Range if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() { diff --git a/chart.go b/chart.go index d53003d..bc53881 100644 --- a/chart.go +++ b/chart.go @@ -2,6 +2,7 @@ package chart import ( "errors" + "image" "io" "math" @@ -132,6 +133,72 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error { 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) { var minx, maxx float64 = math.MaxFloat64, -math.MaxFloat64 var miny, maxy float64 = math.MaxFloat64, -math.MaxFloat64 diff --git a/imager.go b/imager.go new file mode 100644 index 0000000..72bf204 --- /dev/null +++ b/imager.go @@ -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 +} diff --git a/pie_chart.go b/pie_chart.go index 720f61d..2332340 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -2,6 +2,7 @@ package chart import ( "errors" + "image" "io" "github.com/golang/freetype/truetype" @@ -98,6 +99,42 @@ func (pc PieChart) Render(rp RendererProvider, w io.Writer) error { 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) { Draw.Box(r, Box{ Right: pc.GetWidth(), diff --git a/raster_renderer.go b/raster_renderer.go index 3b9fa4d..d68cc01 100644 --- a/raster_renderer.go +++ b/raster_renderer.go @@ -212,3 +212,8 @@ func (rr *rasterRenderer) ClearTextRotation() { func (rr *rasterRenderer) Save(w io.Writer) error { return png.Encode(w, rr.i) } + +// ToImage implements the Imager interface method. +func (rr *rasterRenderer) ToImage() image.Image { + return rr.i +}