Skip to content

Commit 21a4159

Browse files
authored
plot/vg/draw: introduce and use TextHandler
1 parent ee96f17 commit 21a4159

File tree

15 files changed

+452
-146
lines changed

15 files changed

+452
-146
lines changed

axis.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ func makeAxis(o orientation) (Axis, error) {
114114
Scale: LinearScale{},
115115
}
116116
a.Label.TextStyle = draw.TextStyle{
117-
Color: color.Black,
118-
Font: labelFont,
119-
XAlign: draw.XCenter,
120-
YAlign: draw.YBottom,
117+
Color: color.Black,
118+
Font: labelFont,
119+
XAlign: draw.XCenter,
120+
YAlign: draw.YBottom,
121+
Handler: DefaultTextHandler,
121122
}
122123
a.Label.Position = draw.PosCenter
123124

@@ -135,10 +136,11 @@ func makeAxis(o orientation) (Axis, error) {
135136
}
136137

137138
a.Tick.Label = draw.TextStyle{
138-
Color: color.Black,
139-
Font: tickFont,
140-
XAlign: xalign,
141-
YAlign: yalign,
139+
Color: color.Black,
140+
Font: tickFont,
141+
XAlign: xalign,
142+
YAlign: yalign,
143+
Handler: DefaultTextHandler,
142144
}
143145
a.Tick.LineStyle = draw.LineStyle{
144146
Color: color.Black,

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ go 1.13
55
require (
66
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af
77
github.com/fogleman/gg v1.3.0
8+
github.com/go-latex/latex v0.0.0-20200518072620-0806b477ea35
89
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
9-
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5
10+
github.com/jung-kurt/gofpdf v1.16.2
1011
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495
11-
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067
12+
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
1213
gonum.org/v1/gonum v0.7.0
1314
rsc.io/pdf v0.1.1
1415
)

go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
22
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ=
33
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
4+
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
5+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 h1:WXb3TSNmHp2vHoCroCIB1foO/yQ36swABL8aOVeDpgg=
47
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
58
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
69
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
10+
github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ=
11+
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
12+
github.com/go-latex/latex v0.0.0-20200518072620-0806b477ea35 h1:uroDDLmuCK5Pz5J/Ef5vCL6F0sJmAtZFTm0/cF027F4=
13+
github.com/go-latex/latex v0.0.0-20200518072620-0806b477ea35/go.mod h1:PNI+CcWytn/2Z/9f1SGOOYn0eILruVyp0v2/iAs8asQ=
714
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
815
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
16+
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
917
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0=
1018
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
19+
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
20+
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
21+
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
22+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
23+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
24+
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
25+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
1126
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1227
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
1328
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -17,6 +32,9 @@ golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxT
1732
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
1833
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
1934
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
35+
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
36+
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
37+
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
2038
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
2139
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
2240
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

gob/gob.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"gonum.org/v1/plot"
1212
"gonum.org/v1/plot/plotter"
13+
"gonum.org/v1/plot/text"
1314
)
1415

1516
func init() {
@@ -43,4 +44,7 @@ func init() {
4344
gob.Register(plotter.XYZs{})
4445
gob.Register(plotter.XYValues{})
4546

47+
// vg/draw.TextStyle
48+
gob.Register(plot.DefaultTextHandler)
49+
gob.Register(text.Plain{})
4650
}

legend.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,22 @@ func NewLegend() (Legend, error) {
8484
return Legend{
8585
YPosition: draw.PosBottom,
8686
ThumbnailWidth: vg.Points(20),
87-
TextStyle: draw.TextStyle{Font: font},
87+
TextStyle: draw.TextStyle{
88+
Font: font,
89+
Handler: DefaultTextHandler,
90+
},
8891
}, nil
8992
}
9093

9194
// Draw draws the legend to the given draw.Canvas.
9295
func (l *Legend) Draw(c draw.Canvas) {
9396
iconx := c.Min.X
9497
sty := l.TextStyle
95-
textx := iconx + l.ThumbnailWidth + sty.Rectangle(" ").Max.X
98+
em := sty.Rectangle(" ")
99+
textx := iconx + l.ThumbnailWidth + em.Max.X
96100
if !l.Left {
97101
iconx = c.Max.X - l.ThumbnailWidth
98-
textx = iconx - l.TextStyle.Rectangle(" ").Max.X
102+
textx = iconx - em.Max.X
99103
sty.XAlign--
100104
}
101105
textx += l.XOffs

plot.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ import (
1212
"path/filepath"
1313
"strings"
1414

15+
"gonum.org/v1/plot/text"
1516
"gonum.org/v1/plot/vg"
1617
"gonum.org/v1/plot/vg/draw"
1718
)
1819

1920
var (
2021
// DefaultFont is the name of the default font for plot text.
2122
DefaultFont = "Times-Roman"
23+
24+
// DefaultTextHandler is the default text handler used for text processing.
25+
DefaultTextHandler draw.TextHandler = text.Plain{}
2226
)
2327

2428
// Plot is the basic type representing a plot.
@@ -103,10 +107,11 @@ func New() (*Plot, error) {
103107
Legend: legend,
104108
}
105109
p.Title.TextStyle = draw.TextStyle{
106-
Color: color.Black,
107-
Font: titleFont,
108-
XAlign: draw.XCenter,
109-
YAlign: draw.YTop,
110+
Color: color.Black,
111+
Font: titleFont,
112+
XAlign: draw.XCenter,
113+
YAlign: draw.YTop,
114+
Handler: DefaultTextHandler,
110115
}
111116
return p, nil
112117
}
@@ -148,7 +153,8 @@ func (p *Plot) Draw(c draw.Canvas) {
148153
}
149154
if p.Title.Text != "" {
150155
c.FillText(p.Title.TextStyle, vg.Point{X: c.Center().X, Y: c.Max.Y}, p.Title.Text)
151-
c.Max.Y -= p.Title.Height(p.Title.Text) - p.Title.Font.Extents().Descent
156+
_, h, d := p.Title.Handler.Box(p.Title.Text, p.Title.Font)
157+
c.Max.Y -= h + d
152158
c.Max.Y -= p.Title.Padding
153159
}
154160

plot_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ func TestLegendAlignment(t *testing.T) {
2828
}
2929
l := plot.Legend{
3030
ThumbnailWidth: vg.Points(20),
31-
TextStyle: draw.TextStyle{Font: font},
31+
TextStyle: draw.TextStyle{
32+
Font: font,
33+
Handler: plot.DefaultTextHandler,
34+
},
3235
}
3336
for _, n := range []string{"A", "B", "C", "D"} {
3437
b, err := plotter.NewBarChart(plotter.Values{0}, 1)

plotter/labels.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ func NewLabels(d XYLabeller) (*Labels, error) {
6262

6363
styles := make([]draw.TextStyle, d.Len())
6464
for i := range styles {
65-
styles[i] = draw.TextStyle{Font: fnt}
65+
styles[i] = draw.TextStyle{
66+
Font: fnt,
67+
Handler: plot.DefaultTextHandler,
68+
}
6669
}
6770

6871
return &Labels{

plotter/sankey.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ func NewSankey(flows ...Flow) (*Sankey, error) {
183183
Rotation: math.Pi / 2,
184184
XAlign: draw.XCenter,
185185
YAlign: draw.YCenter,
186+
Handler: plot.DefaultTextHandler,
186187
}
187188
s.StockBarWidth = s.TextStyle.Font.Extents().Height * 1.15
188189

text/doc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright ©2020 The Gonum Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package text provides types and functions to parse, format and render text.
6+
package text // import "gonum.org/v1/plot/text"

text/latex.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright ©2020 The Gonum Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package text
6+
7+
import (
8+
"fmt"
9+
"math"
10+
11+
"github.com/go-latex/latex/drawtex"
12+
"github.com/go-latex/latex/font/ttf"
13+
"github.com/go-latex/latex/mtex"
14+
"github.com/go-latex/latex/tex"
15+
16+
"gonum.org/v1/plot/vg"
17+
"gonum.org/v1/plot/vg/draw"
18+
)
19+
20+
// Latex parses, formats and renders LaTeX.
21+
type Latex struct {
22+
// DPI is the dot-per-inch controlling the font resolution used by LaTeX.
23+
// If zero, the resolution defaults to 72.
24+
DPI float64
25+
}
26+
27+
var _ draw.TextHandler = (*Latex)(nil)
28+
29+
// Box returns the bounding box of the given text where:
30+
// - width is the horizontal space from the origin.
31+
// - height is the vertical space above the baseline.
32+
// - depth is the vertical space below the baseline, a negative number.
33+
func (hdlr Latex) Box(txt string, fnt vg.Font) (width, height, depth vg.Length) {
34+
cnv := drawtex.New()
35+
fnts := &ttf.Fonts{
36+
Rm: fnt.Font(),
37+
Default: fnt.Font(),
38+
It: fnt.Font(), // FIXME(sbinet): need a gonum/plot font set
39+
}
40+
box, err := mtex.Parse(txt, fnt.Size.Points(), 72, ttf.NewFrom(cnv, fnts))
41+
if err != nil {
42+
panic(fmt.Errorf("could not parse math expression: %w", err))
43+
}
44+
45+
var sh tex.Ship
46+
sh.Call(0, 0, box.(tex.Tree))
47+
48+
width = vg.Length(box.Width())
49+
height = vg.Length(box.Height())
50+
depth = vg.Length(box.Depth())
51+
52+
return width, height, -depth
53+
}
54+
55+
// Draw renders the given text with the provided style and position
56+
// on the canvas.
57+
func (hdlr Latex) Draw(c *draw.Canvas, txt string, sty draw.TextStyle, pt vg.Point) {
58+
dpi := hdlr.DPI
59+
if dpi == 0 {
60+
dpi = 72
61+
}
62+
cnv := drawtex.New()
63+
fnts := &ttf.Fonts{
64+
Rm: sty.Font.Font(),
65+
Default: sty.Font.Font(),
66+
It: sty.Font.Font(), // FIXME(sbinet): need a gonum/plot font set
67+
}
68+
box, err := mtex.Parse(txt, sty.Font.Size.Points(), 72, ttf.NewFrom(cnv, fnts))
69+
if err != nil {
70+
panic(fmt.Errorf("could not parse math expression: %w", err))
71+
}
72+
73+
var sh tex.Ship
74+
sh.Call(0, 0, box.(tex.Tree))
75+
76+
w := box.Width()
77+
h := box.Height()
78+
d := box.Depth()
79+
80+
o := latex{
81+
cnv: c,
82+
sty: sty,
83+
pt: pt,
84+
}
85+
err = o.Render(w/72, math.Ceil(h+math.Max(d, 0))/72, dpi, cnv)
86+
if err != nil {
87+
panic(fmt.Errorf("could not render math expression: %w", err))
88+
}
89+
}
90+
91+
type latex struct {
92+
cnv *draw.Canvas
93+
sty draw.TextStyle
94+
pt vg.Point
95+
}
96+
97+
var _ mtex.Renderer = (*latex)(nil)
98+
99+
func (r *latex) Render(width, height, dpi float64, c *drawtex.Canvas) error {
100+
r.cnv.SetColor(r.sty.Color)
101+
r.cnv = &draw.Canvas{
102+
Canvas: r.cnv.Canvas,
103+
Rectangle: vg.Rectangle{
104+
Min: vg.Point{X: r.pt.X, Y: r.pt.Y},
105+
Max: vg.Point{X: r.pt.X + vg.Length(width), Y: r.pt.Y + vg.Length(height)},
106+
},
107+
}
108+
109+
for _, op := range c.Ops() {
110+
switch op := op.(type) {
111+
case drawtex.GlyphOp:
112+
r.drawGlyph(dpi, op)
113+
case drawtex.RectOp:
114+
r.drawRect(dpi, op)
115+
default:
116+
panic(fmt.Errorf("unknown drawtex op %T", op))
117+
}
118+
}
119+
120+
return nil
121+
}
122+
123+
func (r *latex) drawGlyph(dpi float64, op drawtex.GlyphOp) {
124+
fnt := r.sty.Font
125+
fnt.Size = vg.Length(op.Glyph.Size)
126+
127+
x := vg.Length(op.X * dpi / 72)
128+
y := vg.Length(op.Y * dpi / 72)
129+
r.cnv.FillString(fnt, vg.Point{X: x, Y: y}, op.Glyph.Symbol)
130+
}
131+
132+
func (r *latex) drawRect(dpi float64, op drawtex.RectOp) {
133+
x1 := vg.Length(op.X1 * dpi / 72)
134+
x2 := vg.Length(op.X2 * dpi / 72)
135+
y1 := vg.Length(op.Y1 * dpi / 72)
136+
y2 := vg.Length(op.Y2 * dpi / 72)
137+
pts := []vg.Point{
138+
{X: x1, Y: y1},
139+
{X: x2, Y: y1},
140+
{X: x2, Y: y2},
141+
{X: x2, Y: y2},
142+
{X: x1, Y: y1},
143+
}
144+
145+
r.cnv.FillPolygon(r.sty.Color, pts)
146+
}

text/plain.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright ©2020 The Gonum Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package text // import "gonum.org/v1/plot/text"
6+
7+
import "gonum.org/v1/plot/vg/draw"
8+
9+
// Plain is a text/plain handler.
10+
type Plain = draw.PlainTextHandler
11+
12+
var _ draw.TextHandler = (*Plain)(nil)

0 commit comments

Comments
 (0)