go-chart/vector_renderer.go

214 lines
5.1 KiB
Go
Raw Normal View History

2016-07-08 06:16:34 +02:00
package chart
2016-07-08 07:18:53 +02:00
import (
"bytes"
"fmt"
"io"
"strings"
"golang.org/x/image/font"
"github.com/golang/freetype/truetype"
2016-07-09 19:27:47 +02:00
"github.com/wcharczuk/go-chart/drawing"
2016-07-08 07:18:53 +02:00
)
// SVG returns a new png/raster renderer.
2016-07-09 02:57:14 +02:00
func SVG(width, height int) (Renderer, error) {
2016-07-08 07:18:53 +02:00
buffer := bytes.NewBuffer([]byte{})
2016-07-09 02:57:14 +02:00
canvas := newCanvas(buffer)
2016-07-08 07:18:53 +02:00
canvas.Start(width, height)
return &vectorRenderer{
b: buffer,
c: canvas,
s: &Style{},
p: []string{},
2016-07-09 02:57:14 +02:00
}, nil
2016-07-08 07:18:53 +02:00
}
// vectorRenderer renders chart commands to a bitmap.
type vectorRenderer struct {
2016-07-09 02:57:14 +02:00
dpi float64
b *bytes.Buffer
c *canvas
s *Style
f *truetype.Font
p []string
fc *font.Drawer
}
2016-07-10 10:11:47 +02:00
// GetDPI returns the dpi.
func (vr *vectorRenderer) GetDPI() float64 {
return vr.dpi
}
2016-07-09 02:57:14 +02:00
// SetDPI implements the interface method.
func (vr *vectorRenderer) SetDPI(dpi float64) {
vr.dpi = dpi
2016-07-08 07:18:53 +02:00
}
2016-07-09 02:57:14 +02:00
// SetStrokeColor implements the interface method.
2016-07-09 20:23:35 +02:00
func (vr *vectorRenderer) SetStrokeColor(c drawing.Color) {
2016-07-08 07:18:53 +02:00
vr.s.StrokeColor = c
}
// SetFillColor implements the interface method.
2016-07-09 20:23:35 +02:00
func (vr *vectorRenderer) SetFillColor(c drawing.Color) {
2016-07-08 07:18:53 +02:00
vr.s.FillColor = c
}
// SetLineWidth implements the interface method.
func (vr *vectorRenderer) SetStrokeWidth(width float64) {
vr.s.StrokeWidth = width
}
2016-07-12 03:48:51 +02:00
// StrokeDashArray sets the stroke dash array.
func (vr *vectorRenderer) SetStrokeDashArray(dashArray []float64) {
vr.s.StrokeDashArray = dashArray
}
2016-07-08 07:18:53 +02:00
// MoveTo implements the interface method.
func (vr *vectorRenderer) MoveTo(x, y int) {
vr.p = append(vr.p, fmt.Sprintf("M %d %d", x, y))
}
// LineTo implements the interface method.
func (vr *vectorRenderer) LineTo(x, y int) {
vr.p = append(vr.p, fmt.Sprintf("L %d %d", x, y))
}
2016-07-12 03:48:51 +02:00
// Close closes a shape.
2016-07-08 07:18:53 +02:00
func (vr *vectorRenderer) Close() {
vr.p = append(vr.p, fmt.Sprintf("Z"))
}
// Stroke draws the path with no fill.
func (vr *vectorRenderer) Stroke() {
2016-07-11 03:09:41 +02:00
vr.drawPath(vr.s.SVGStroke())
2016-07-08 07:18:53 +02:00
}
// Fill draws the path with no stroke.
func (vr *vectorRenderer) Fill() {
2016-07-11 03:09:41 +02:00
vr.drawPath(vr.s.SVGFill())
2016-07-08 07:18:53 +02:00
}
// FillStroke draws the path with both fill and stroke.
func (vr *vectorRenderer) FillStroke() {
2016-07-11 03:09:41 +02:00
vr.drawPath(vr.s.SVGFillAndStroke())
2016-07-08 07:18:53 +02:00
}
2016-07-12 03:48:51 +02:00
// drawPath draws a path.
2016-07-11 03:09:41 +02:00
func (vr *vectorRenderer) drawPath(s Style) {
vr.c.Path(strings.Join(vr.p, "\n"), s.SVG(vr.dpi))
vr.p = []string{} // clear the path
2016-07-08 07:18:53 +02:00
}
// Circle implements the interface method.
func (vr *vectorRenderer) Circle(radius float64, x, y int) {
2016-07-09 19:27:47 +02:00
vr.c.Circle(x, y, int(radius), vr.s.SVG(vr.dpi))
2016-07-08 07:18:53 +02:00
}
// SetFont implements the interface method.
func (vr *vectorRenderer) SetFont(f *truetype.Font) {
vr.f = f
}
// SetFontColor implements the interface method.
2016-07-09 20:23:35 +02:00
func (vr *vectorRenderer) SetFontColor(c drawing.Color) {
2016-07-08 07:18:53 +02:00
vr.s.FontColor = c
}
// SetFontSize implements the interface method.
func (vr *vectorRenderer) SetFontSize(size float64) {
vr.s.FontSize = size
}
2016-07-12 03:48:51 +02:00
// svgFontFace returns the font face component of an svg element style.
2016-07-08 07:18:53 +02:00
func (vr *vectorRenderer) svgFontFace() string {
family := "sans-serif"
if vr.f != nil {
name := vr.f.Name(truetype.NameIDFontFamily)
if len(name) != 0 {
family = fmt.Sprintf(`'%s',%s`, name, family)
}
}
return fmt.Sprintf("font-family:%s", family)
}
// Text draws a text blob.
func (vr *vectorRenderer) Text(body string, x, y int) {
2016-07-11 03:09:41 +02:00
s := vr.s.SVGText()
vr.c.Text(x, y, body, s.SVG(vr.dpi)+";"+vr.svgFontFace())
2016-07-08 07:18:53 +02:00
}
// MeasureText uses the truetype font drawer to measure the width of text.
2016-07-12 03:48:51 +02:00
func (vr *vectorRenderer) MeasureText(body string) (box Box) {
2016-07-09 02:57:14 +02:00
if vr.f != nil {
2016-07-08 07:18:53 +02:00
vr.fc = &font.Drawer{
Face: truetype.NewFace(vr.f, &truetype.Options{
2016-07-09 02:57:14 +02:00
DPI: vr.dpi,
2016-07-08 07:18:53 +02:00
Size: vr.s.FontSize,
}),
}
2016-07-09 19:27:47 +02:00
w := vr.fc.MeasureString(body).Ceil()
2016-07-12 03:48:51 +02:00
box.Right = w
box.Bottom = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize))
box.Width = w
box.Height = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize))
2016-07-08 07:18:53 +02:00
}
2016-07-09 02:57:14 +02:00
return
2016-07-08 07:18:53 +02:00
}
2016-07-12 03:48:51 +02:00
// Save saves the renderer's contents to a writer.
2016-07-08 07:18:53 +02:00
func (vr *vectorRenderer) Save(w io.Writer) error {
vr.c.End()
_, err := w.Write(vr.b.Bytes())
return err
}
2016-07-09 02:57:14 +02:00
func newCanvas(w io.Writer) *canvas {
return &canvas{
w: w,
}
}
type canvas struct {
w io.Writer
width int
height int
}
func (c *canvas) Start(width, height int) {
c.width = width
c.height = height
c.w.Write([]byte(fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d">\n`, c.width, c.height)))
}
func (c *canvas) Path(d string, style ...string) {
if len(style) > 0 {
c.w.Write([]byte(fmt.Sprintf(`<path d="%s" style="%s"/>\n`, d, style[0])))
} else {
c.w.Write([]byte(fmt.Sprintf(`<path d="%s"/>\n`, d)))
}
}
func (c *canvas) Text(x, y int, body string, style ...string) {
if len(style) > 0 {
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, style[0], body)))
} else {
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d">%s</text>`, x, y, body)))
}
}
func (c *canvas) Circle(x, y, r int, style ...string) {
if len(style) > 0 {
c.w.Write([]byte(fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" style="%s">`, x, y, r, style[0])))
} else {
c.w.Write([]byte(fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d">`, x, y, r)))
}
}
func (c *canvas) End() {
c.w.Write([]byte("</svg>"))
}