2016-07-07 03:54:00 +02:00
|
|
|
package chart
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image"
|
|
|
|
"image/png"
|
|
|
|
"io"
|
2016-07-12 03:48:51 +02:00
|
|
|
"math"
|
2016-07-07 03:54:00 +02:00
|
|
|
|
|
|
|
"github.com/golang/freetype/truetype"
|
2016-07-09 02:57:14 +02:00
|
|
|
"github.com/wcharczuk/go-chart/drawing"
|
2018-04-05 07:06:34 +02:00
|
|
|
"github.com/wcharczuk/go-chart/util"
|
2016-07-07 03:54:00 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// PNG returns a new png/raster renderer.
|
2016-07-09 02:57:14 +02:00
|
|
|
func PNG(width, height int) (Renderer, error) {
|
2016-07-07 03:54:00 +02:00
|
|
|
i := image.NewRGBA(image.Rect(0, 0, width, height))
|
2016-07-09 02:57:14 +02:00
|
|
|
gc, err := drawing.NewRasterGraphicContext(i)
|
|
|
|
if err == nil {
|
|
|
|
return &rasterRenderer{
|
|
|
|
i: i,
|
|
|
|
gc: gc,
|
|
|
|
}, nil
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
2016-07-09 02:57:14 +02:00
|
|
|
return nil, err
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
2016-07-08 07:18:53 +02:00
|
|
|
// rasterRenderer renders chart commands to a bitmap.
|
2016-07-07 03:54:00 +02:00
|
|
|
type rasterRenderer struct {
|
|
|
|
i *image.RGBA
|
2016-07-09 02:57:14 +02:00
|
|
|
gc *drawing.RasterGraphicContext
|
2016-07-07 03:54:00 +02:00
|
|
|
|
2016-09-05 22:26:12 +02:00
|
|
|
rotateRadians *float64
|
2016-08-07 06:59:46 +02:00
|
|
|
|
2016-07-15 03:51:42 +02:00
|
|
|
s Style
|
2016-07-09 02:57:14 +02:00
|
|
|
}
|
|
|
|
|
2016-10-21 21:44:37 +02:00
|
|
|
func (rr *rasterRenderer) ResetStyle() {
|
|
|
|
rr.s = Style{Font: rr.s.Font}
|
|
|
|
rr.ClearTextRotation()
|
|
|
|
}
|
|
|
|
|
2016-07-10 10:11:47 +02:00
|
|
|
// GetDPI returns the dpi.
|
|
|
|
func (rr *rasterRenderer) GetDPI() float64 {
|
|
|
|
return rr.gc.GetDPI()
|
|
|
|
}
|
|
|
|
|
2016-07-09 02:57:14 +02:00
|
|
|
// SetDPI implements the interface method.
|
|
|
|
func (rr *rasterRenderer) SetDPI(dpi float64) {
|
|
|
|
rr.gc.SetDPI(dpi)
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
2018-10-12 02:21:46 +02:00
|
|
|
// SetClassName implements the interface method. However, PNGs have no classes.
|
|
|
|
func (vr *rasterRenderer) SetClassName(_ string) {
|
|
|
|
}
|
|
|
|
|
2016-07-07 03:54:00 +02:00
|
|
|
// SetStrokeColor implements the interface method.
|
2016-07-09 20:23:35 +02:00
|
|
|
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.s.StrokeColor = c
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetLineWidth implements the interface method.
|
2016-07-08 07:18:53 +02:00
|
|
|
func (rr *rasterRenderer) SetStrokeWidth(width float64) {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.s.StrokeWidth = width
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
2016-07-12 03:48:51 +02:00
|
|
|
// StrokeDashArray sets the stroke dash array.
|
|
|
|
func (rr *rasterRenderer) SetStrokeDashArray(dashArray []float64) {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.s.StrokeDashArray = dashArray
|
2016-07-12 03:48:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetFillColor implements the interface method.
|
|
|
|
func (rr *rasterRenderer) SetFillColor(c drawing.Color) {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.s.FillColor = c
|
2016-07-12 03:48:51 +02:00
|
|
|
}
|
|
|
|
|
2016-07-07 03:54:00 +02:00
|
|
|
// MoveTo implements the interface method.
|
|
|
|
func (rr *rasterRenderer) MoveTo(x, y int) {
|
|
|
|
rr.gc.MoveTo(float64(x), float64(y))
|
|
|
|
}
|
|
|
|
|
|
|
|
// LineTo implements the interface method.
|
|
|
|
func (rr *rasterRenderer) LineTo(x, y int) {
|
|
|
|
rr.gc.LineTo(float64(x), float64(y))
|
|
|
|
}
|
|
|
|
|
2016-07-28 11:34:44 +02:00
|
|
|
// QuadCurveTo implements the interface method.
|
|
|
|
func (rr *rasterRenderer) QuadCurveTo(cx, cy, x, y int) {
|
|
|
|
rr.gc.QuadCurveTo(float64(cx), float64(cy), float64(x), float64(y))
|
|
|
|
}
|
|
|
|
|
|
|
|
// ArcTo implements the interface method.
|
|
|
|
func (rr *rasterRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) {
|
|
|
|
rr.gc.ArcTo(float64(cx), float64(cy), rx, ry, startAngle, delta)
|
|
|
|
}
|
|
|
|
|
2016-07-07 03:54:00 +02:00
|
|
|
// Close implements the interface method.
|
|
|
|
func (rr *rasterRenderer) Close() {
|
|
|
|
rr.gc.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stroke implements the interface method.
|
|
|
|
func (rr *rasterRenderer) Stroke() {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.gc.SetStrokeColor(rr.s.StrokeColor)
|
|
|
|
rr.gc.SetLineWidth(rr.s.StrokeWidth)
|
2016-07-15 03:53:54 +02:00
|
|
|
rr.gc.SetLineDash(rr.s.StrokeDashArray, 0)
|
2016-07-07 03:54:00 +02:00
|
|
|
rr.gc.Stroke()
|
|
|
|
}
|
|
|
|
|
2016-07-07 23:44:03 +02:00
|
|
|
// Fill implements the interface method.
|
|
|
|
func (rr *rasterRenderer) Fill() {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.gc.SetFillColor(rr.s.FillColor)
|
2016-07-07 23:44:03 +02:00
|
|
|
rr.gc.Fill()
|
|
|
|
}
|
|
|
|
|
2016-07-07 03:54:00 +02:00
|
|
|
// FillStroke implements the interface method.
|
|
|
|
func (rr *rasterRenderer) FillStroke() {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.gc.SetFillColor(rr.s.FillColor)
|
|
|
|
rr.gc.SetStrokeColor(rr.s.StrokeColor)
|
|
|
|
rr.gc.SetLineWidth(rr.s.StrokeWidth)
|
2016-07-15 03:53:54 +02:00
|
|
|
rr.gc.SetLineDash(rr.s.StrokeDashArray, 0)
|
2016-07-07 03:54:00 +02:00
|
|
|
rr.gc.FillStroke()
|
|
|
|
}
|
|
|
|
|
2017-03-06 01:54:40 +01:00
|
|
|
// Circle fully draws a circle at a given point but does not apply the fill or stroke.
|
2016-07-07 03:54:00 +02:00
|
|
|
func (rr *rasterRenderer) Circle(radius float64, x, y int) {
|
|
|
|
xf := float64(x)
|
|
|
|
yf := float64(y)
|
2017-03-06 01:54:40 +01:00
|
|
|
|
|
|
|
rr.gc.MoveTo(xf-radius, yf) //9
|
|
|
|
rr.gc.QuadCurveTo(xf-radius, yf-radius, xf, yf-radius) //12
|
|
|
|
rr.gc.QuadCurveTo(xf+radius, yf-radius, xf+radius, yf) //3
|
|
|
|
rr.gc.QuadCurveTo(xf+radius, yf+radius, xf, yf+radius) //6
|
|
|
|
rr.gc.QuadCurveTo(xf-radius, yf+radius, xf-radius, yf) //9
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetFont implements the interface method.
|
|
|
|
func (rr *rasterRenderer) SetFont(f *truetype.Font) {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.s.Font = f
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetFontSize implements the interface method.
|
|
|
|
func (rr *rasterRenderer) SetFontSize(size float64) {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.s.FontSize = size
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetFontColor implements the interface method.
|
2016-07-09 20:23:35 +02:00
|
|
|
func (rr *rasterRenderer) SetFontColor(c drawing.Color) {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.s.FontColor = c
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Text implements the interface method.
|
|
|
|
func (rr *rasterRenderer) Text(body string, x, y int) {
|
2016-08-07 06:59:46 +02:00
|
|
|
xf, yf := rr.getCoords(x, y)
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.gc.SetFont(rr.s.Font)
|
|
|
|
rr.gc.SetFontSize(rr.s.FontSize)
|
|
|
|
rr.gc.SetFillColor(rr.s.FontColor)
|
2016-08-07 06:59:46 +02:00
|
|
|
rr.gc.CreateStringPath(body, float64(xf), float64(yf))
|
2016-07-07 23:46:52 +02:00
|
|
|
rr.gc.Fill()
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
2016-07-09 02:57:14 +02:00
|
|
|
// MeasureText returns the height and width in pixels of a string.
|
2016-07-12 03:48:51 +02:00
|
|
|
func (rr *rasterRenderer) MeasureText(body string) Box {
|
2016-07-15 03:51:42 +02:00
|
|
|
rr.gc.SetFont(rr.s.Font)
|
|
|
|
rr.gc.SetFontSize(rr.s.FontSize)
|
|
|
|
rr.gc.SetFillColor(rr.s.FontColor)
|
2016-07-09 02:57:14 +02:00
|
|
|
l, t, r, b, err := rr.gc.GetStringBounds(body)
|
|
|
|
if err != nil {
|
2016-07-12 03:48:51 +02:00
|
|
|
return Box{}
|
|
|
|
}
|
|
|
|
if l < 0 {
|
|
|
|
r = r - l // equivalent to r+(-1*l)
|
|
|
|
l = 0
|
|
|
|
}
|
|
|
|
if t < 0 {
|
|
|
|
b = b - t
|
|
|
|
t = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if l > 0 {
|
|
|
|
r = r + l
|
|
|
|
l = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if t > 0 {
|
|
|
|
b = b + t
|
|
|
|
t = 0
|
|
|
|
}
|
|
|
|
|
2016-09-01 07:11:52 +02:00
|
|
|
textBox := Box{
|
2016-07-12 03:48:51 +02:00
|
|
|
Top: int(math.Ceil(t)),
|
|
|
|
Left: int(math.Ceil(l)),
|
|
|
|
Right: int(math.Ceil(r)),
|
|
|
|
Bottom: int(math.Ceil(b)),
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
2016-09-05 22:26:12 +02:00
|
|
|
if rr.rotateRadians == nil {
|
2016-09-01 07:11:52 +02:00
|
|
|
return textBox
|
|
|
|
}
|
|
|
|
|
2017-05-13 02:12:23 +02:00
|
|
|
return textBox.Corners().Rotate(util.Math.RadiansToDegrees(*rr.rotateRadians)).Box()
|
2016-07-07 03:54:00 +02:00
|
|
|
}
|
|
|
|
|
2016-08-07 06:59:46 +02:00
|
|
|
// SetTextRotation sets a text rotation.
|
|
|
|
func (rr *rasterRenderer) SetTextRotation(radians float64) {
|
2016-09-05 22:26:12 +02:00
|
|
|
rr.rotateRadians = &radians
|
2016-08-07 06:59:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (rr *rasterRenderer) getCoords(x, y int) (xf, yf int) {
|
2016-09-05 22:26:12 +02:00
|
|
|
if rr.rotateRadians == nil {
|
2016-08-07 06:59:46 +02:00
|
|
|
xf = x
|
|
|
|
yf = y
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
rr.gc.Translate(float64(x), float64(y))
|
2016-09-05 22:26:12 +02:00
|
|
|
rr.gc.Rotate(*rr.rotateRadians)
|
2016-08-07 06:59:46 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClearTextRotation clears text rotation.
|
|
|
|
func (rr *rasterRenderer) ClearTextRotation() {
|
|
|
|
rr.gc.SetMatrixTransform(drawing.NewIdentityMatrix())
|
2016-09-05 22:26:12 +02:00
|
|
|
rr.rotateRadians = nil
|
2016-08-07 06:59:46 +02:00
|
|
|
}
|
|
|
|
|
2016-07-07 03:54:00 +02:00
|
|
|
// Save implements the interface method.
|
|
|
|
func (rr *rasterRenderer) Save(w io.Writer) error {
|
2016-08-27 23:23:55 +02:00
|
|
|
if typed, isTyped := w.(RGBACollector); isTyped {
|
|
|
|
typed.SetRGBA(rr.i)
|
|
|
|
return nil
|
|
|
|
}
|
2016-07-07 03:54:00 +02:00
|
|
|
return png.Encode(w, rr.i)
|
|
|
|
}
|