1
2
3
4
5 package draw
6
7 import (
8 "fmt"
9 "image/color"
10 "math"
11 "sort"
12 "sync"
13
14 "gonum.org/v1/plot/text"
15 "gonum.org/v1/plot/vg"
16 )
17
18
19 var formats = struct {
20 sync.RWMutex
21 m map[string]func(w, h vg.Length) vg.CanvasWriterTo
22 }{
23 m: make(map[string]func(w, h vg.Length) vg.CanvasWriterTo),
24 }
25
26
27 func Formats() []string {
28 formats.RLock()
29 defer formats.RUnlock()
30
31 list := make([]string, 0, len(formats.m))
32 for name := range formats.m {
33 list = append(list, name)
34 }
35 sort.Strings(list)
36 return list
37 }
38
39
40
41
42
43
44 func RegisterFormat(name string, fn func(w, h vg.Length) vg.CanvasWriterTo) {
45 formats.Lock()
46 defer formats.Unlock()
47
48 if fn == nil {
49 panic("draw: RegisterFormat with nil function")
50 }
51 formats.m[name] = fn
52 }
53
54
55
56
57 type Canvas struct {
58 vg.Canvas
59 vg.Rectangle
60 }
61
62
63
64
65 type XAlignment = text.XAlignment
66
67 const (
68
69 XLeft = text.XLeft
70
71 XCenter = text.XCenter
72
73 XRight = text.XRight
74 )
75
76
77
78
79 type YAlignment = text.YAlignment
80
81 const (
82
83 YTop = text.YTop
84
85 YCenter = text.YCenter
86
87 YBottom = text.YBottom
88 )
89
90
91 const (
92 PosLeft = text.PosLeft
93 PosBottom = text.PosBottom
94 PosCenter = text.PosCenter
95 PosTop = text.PosTop
96 PosRight = text.PosRight
97 )
98
99
100 type LineStyle struct {
101
102 Color color.Color
103
104
105 Width vg.Length
106
107 Dashes []vg.Length
108 DashOffs vg.Length
109 }
110
111
112
113 type GlyphStyle struct {
114
115 color.Color
116
117
118 Radius vg.Length
119
120
121 Shape GlyphDrawer
122 }
123
124
125 type GlyphDrawer interface {
126
127
128 DrawGlyph(*Canvas, GlyphStyle, vg.Point)
129 }
130
131
132
133
134 func (c *Canvas) DrawGlyph(sty GlyphStyle, pt vg.Point) {
135 if sty.Shape == nil || !c.Contains(pt) {
136 return
137 }
138 c.SetColor(sty.Color)
139 sty.Shape.DrawGlyph(c, sty, pt)
140 }
141
142
143
144 func (c *Canvas) DrawGlyphNoClip(sty GlyphStyle, pt vg.Point) {
145 if sty.Shape == nil {
146 return
147 }
148 c.SetColor(sty.Color)
149 sty.Shape.DrawGlyph(c, sty, pt)
150 }
151
152
153
154 func (g GlyphStyle) Rectangle() vg.Rectangle {
155 return vg.Rectangle{
156 Min: vg.Point{X: -g.Radius, Y: -g.Radius},
157 Max: vg.Point{X: +g.Radius, Y: +g.Radius},
158 }
159 }
160
161
162 type CircleGlyph struct{}
163
164
165 func (CircleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
166 p := make(vg.Path, 0, 3)
167 p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
168 p.Arc(pt, sty.Radius, 0, 2*math.Pi)
169 p.Close()
170 c.Fill(p)
171 }
172
173
174 type RingGlyph struct{}
175
176
177 func (RingGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
178 c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
179 p := make(vg.Path, 0, 3)
180 p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
181 p.Arc(pt, sty.Radius, 0, 2*math.Pi)
182 p.Close()
183 c.Stroke(p)
184 }
185
186 const (
187 cosπover4 = vg.Length(.707106781202420)
188 sinπover6 = vg.Length(.500000000025921)
189 cosπover6 = vg.Length(.866025403769473)
190 )
191
192
193 type SquareGlyph struct{}
194
195
196 func (SquareGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
197 c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
198 x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
199 p := make(vg.Path, 0, 5)
200 p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
201 p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
202 p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
203 p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
204 p.Close()
205 c.Stroke(p)
206 }
207
208
209 type BoxGlyph struct{}
210
211
212 func (BoxGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
213 x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
214 p := make(vg.Path, 0, 5)
215 p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
216 p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
217 p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
218 p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
219 p.Close()
220 c.Fill(p)
221 }
222
223
224 type TriangleGlyph struct{}
225
226
227 func (TriangleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
228 c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
229 r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
230 p := make(vg.Path, 0, 4)
231 p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
232 p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
233 p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
234 p.Close()
235 c.Stroke(p)
236 }
237
238
239 type PyramidGlyph struct{}
240
241
242 func (PyramidGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
243 r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
244 p := make(vg.Path, 0, 4)
245 p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
246 p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
247 p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
248 p.Close()
249 c.Fill(p)
250 }
251
252
253 type PlusGlyph struct{}
254
255
256 func (PlusGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
257 c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
258 r := sty.Radius
259 p := make(vg.Path, 0, 2)
260 p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
261 p.Line(vg.Point{X: pt.X, Y: pt.Y - r})
262 c.Stroke(p)
263 p = p[:0]
264 p.Move(vg.Point{X: pt.X - r, Y: pt.Y})
265 p.Line(vg.Point{X: pt.X + r, Y: pt.Y})
266 c.Stroke(p)
267 }
268
269
270 type CrossGlyph struct{}
271
272
273 func (CrossGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
274 c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
275 r := sty.Radius * cosπover4
276 p := make(vg.Path, 0, 2)
277 p.Move(vg.Point{X: pt.X - r, Y: pt.Y - r})
278 p.Line(vg.Point{X: pt.X + r, Y: pt.Y + r})
279 c.Stroke(p)
280 p = p[:0]
281 p.Move(vg.Point{X: pt.X - r, Y: pt.Y + r})
282 p.Line(vg.Point{X: pt.X + r, Y: pt.Y - r})
283 c.Stroke(p)
284 }
285
286
287 func New(c vg.CanvasSizer) Canvas {
288 w, h := c.Size()
289 return NewCanvas(c, w, h)
290 }
291
292
293
294
295
296
297
298
299
300
301 func NewFormattedCanvas(w, h vg.Length, format string) (vg.CanvasWriterTo, error) {
302 formats.RLock()
303 defer formats.RUnlock()
304
305 for name, fn := range formats.m {
306 if format != name {
307 continue
308 }
309 return fn(w, h), nil
310 }
311 return nil, fmt.Errorf("unsupported format: %q", format)
312 }
313
314
315 func NewCanvas(c vg.Canvas, w, h vg.Length) Canvas {
316 return Canvas{
317 Canvas: c,
318 Rectangle: vg.Rectangle{
319 Min: vg.Point{X: 0, Y: 0},
320 Max: vg.Point{X: w, Y: h},
321 },
322 }
323 }
324
325
326 func (c *Canvas) Center() vg.Point {
327 return vg.Point{
328 X: (c.Max.X-c.Min.X)/2 + c.Min.X,
329 Y: (c.Max.Y-c.Min.Y)/2 + c.Min.Y,
330 }
331 }
332
333
334 func (c *Canvas) Contains(p vg.Point) bool {
335 return c.ContainsX(p.X) && c.ContainsY(p.Y)
336 }
337
338
339
340 func (c *Canvas) ContainsX(x vg.Length) bool {
341 return x <= c.Max.X+slop && x >= c.Min.X-slop
342 }
343
344
345
346 func (c *Canvas) ContainsY(y vg.Length) bool {
347 return y <= c.Max.Y+slop && y >= c.Min.Y-slop
348 }
349
350
351
352
353
354
355 func (c *Canvas) X(x float64) vg.Length {
356 return vg.Length(x)*(c.Max.X-c.Min.X) + c.Min.X
357 }
358
359
360
361
362
363
364 func (c *Canvas) Y(y float64) vg.Length {
365 return vg.Length(y)*(c.Max.Y-c.Min.Y) + c.Min.Y
366 }
367
368
369
370
371
372
373 func Crop(c Canvas, left, right, bottom, top vg.Length) Canvas {
374 minpt := vg.Point{
375 X: c.Min.X + left,
376 Y: c.Min.Y + bottom,
377 }
378 maxpt := vg.Point{
379 X: c.Max.X + right,
380 Y: c.Max.Y + top,
381 }
382 return Canvas{
383 Canvas: c,
384 Rectangle: vg.Rectangle{Min: minpt, Max: maxpt},
385 }
386 }
387
388
389 type Tiles struct {
390
391 Cols, Rows int
392
393
394 PadTop, PadBottom, PadRight, PadLeft vg.Length
395
396
397 PadX, PadY vg.Length
398 }
399
400
401
402 func (ts Tiles) At(c Canvas, x, y int) Canvas {
403 tileH := (c.Max.Y - c.Min.Y - ts.PadTop - ts.PadBottom -
404 vg.Length(ts.Rows-1)*ts.PadY) / vg.Length(ts.Rows)
405 tileW := (c.Max.X - c.Min.X - ts.PadLeft - ts.PadRight -
406 vg.Length(ts.Cols-1)*ts.PadX) / vg.Length(ts.Cols)
407
408 ymax := c.Max.Y - ts.PadTop - vg.Length(y)*(ts.PadY+tileH)
409 ymin := ymax - tileH
410 xmin := c.Min.X + ts.PadLeft + vg.Length(x)*(ts.PadX+tileW)
411 xmax := xmin + tileW
412
413 return Canvas{
414 Canvas: vg.Canvas(c),
415 Rectangle: vg.Rectangle{
416 Min: vg.Point{X: xmin, Y: ymin},
417 Max: vg.Point{X: xmax, Y: ymax},
418 },
419 }
420 }
421
422
423 func (c *Canvas) SetLineStyle(sty LineStyle) {
424 c.SetColor(sty.Color)
425 c.SetLineWidth(sty.Width)
426 c.SetLineDash(sty.Dashes, sty.DashOffs)
427 }
428
429
430
431 func (c *Canvas) StrokeLines(sty LineStyle, lines ...[]vg.Point) {
432 if len(lines) == 0 {
433 return
434 }
435
436 c.SetLineStyle(sty)
437
438 for _, l := range lines {
439 if len(l) == 0 {
440 continue
441 }
442 p := make(vg.Path, 0, len(l))
443 p.Move(l[0])
444 for _, pt := range l[1:] {
445 p.Line(pt)
446 }
447 c.Stroke(p)
448 }
449 }
450
451
452
453 func (c *Canvas) StrokeLine2(sty LineStyle, x0, y0, x1, y1 vg.Length) {
454 c.StrokeLines(sty, []vg.Point{{X: x0, Y: y0}, {X: x1, Y: y1}})
455 }
456
457
458
459
460 func (c *Canvas) ClipLinesXY(lines ...[]vg.Point) [][]vg.Point {
461 return c.ClipLinesY(c.ClipLinesX(lines...)...)
462 }
463
464
465
466
467 func (c *Canvas) ClipLinesX(lines ...[]vg.Point) (clipped [][]vg.Point) {
468 lines1 := make([][]vg.Point, 0, len(lines))
469 for _, line := range lines {
470 ls := clipLine(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0}, line)
471 lines1 = append(lines1, ls...)
472 }
473 clipped = make([][]vg.Point, 0, len(lines1))
474 for _, line := range lines1 {
475 ls := clipLine(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, line)
476 clipped = append(clipped, ls...)
477 }
478 return
479 }
480
481
482
483
484 func (c *Canvas) ClipLinesY(lines ...[]vg.Point) (clipped [][]vg.Point) {
485 lines1 := make([][]vg.Point, 0, len(lines))
486 for _, line := range lines {
487 ls := clipLine(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, line)
488 lines1 = append(lines1, ls...)
489 }
490 clipped = make([][]vg.Point, 0, len(lines1))
491 for _, line := range lines1 {
492 ls := clipLine(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1}, line)
493 clipped = append(clipped, ls...)
494 }
495 return
496 }
497
498
499
500
501 func clipLine(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (lines [][]vg.Point) {
502 l := make([]vg.Point, 0, len(pts))
503 for i := 1; i < len(pts); i++ {
504 cur, next := pts[i-1], pts[i]
505 curIn, nextIn := in(cur, clip), in(next, clip)
506 switch {
507 case curIn && nextIn:
508 l = append(l, cur)
509
510 case curIn && !nextIn:
511 l = append(l, cur, isect(cur, next, clip, norm))
512 lines = append(lines, l)
513 l = []vg.Point{}
514
515 case !curIn && !nextIn:
516
517
518 default:
519 l = append(l, isect(cur, next, clip, norm))
520 }
521 if nextIn && i == len(pts)-1 {
522 l = append(l, next)
523 }
524 }
525 if len(l) > 1 {
526 lines = append(lines, l)
527 }
528 return
529 }
530
531
532 func (c *Canvas) FillPolygon(clr color.Color, pts []vg.Point) {
533 if len(pts) == 0 {
534 return
535 }
536
537 c.SetColor(clr)
538 p := make(vg.Path, 0, len(pts)+1)
539 p.Move(pts[0])
540 for _, pt := range pts[1:] {
541 p.Line(pt)
542 }
543 p.Close()
544 c.Fill(p)
545 }
546
547
548
549
550 func (c *Canvas) ClipPolygonXY(pts []vg.Point) []vg.Point {
551 return c.ClipPolygonY(c.ClipPolygonX(pts))
552 }
553
554
555
556
557 func (c *Canvas) ClipPolygonX(pts []vg.Point) []vg.Point {
558 return clipPoly(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0},
559 clipPoly(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, pts))
560 }
561
562
563
564
565 func (c *Canvas) ClipPolygonY(pts []vg.Point) []vg.Point {
566 return clipPoly(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1},
567 clipPoly(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, pts))
568 }
569
570
571
572
573 func clipPoly(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (clipped []vg.Point) {
574 clipped = make([]vg.Point, 0, len(pts))
575 for i := 0; i < len(pts); i++ {
576 j := i + 1
577 if i == len(pts)-1 {
578 j = 0
579 }
580 cur, next := pts[i], pts[j]
581 curIn, nextIn := in(cur, clip), in(next, clip)
582 switch {
583 case curIn && nextIn:
584 clipped = append(clipped, cur)
585
586 case curIn && !nextIn:
587 clipped = append(clipped, cur, isect(cur, next, clip, norm))
588
589 case !curIn && !nextIn:
590
591
592 default:
593 clipped = append(clipped, isect(cur, next, clip, norm))
594 }
595 }
596 n := len(clipped)
597 return clipped[:n:n]
598 }
599
600
601 const slop = 3e-8
602
603 func isLeft(p, clip vg.Point) bool {
604 return p.X <= clip.X+slop
605 }
606
607 func isRight(p, clip vg.Point) bool {
608 return p.X >= clip.X-slop
609 }
610
611 func isBelow(p, clip vg.Point) bool {
612 return p.Y <= clip.Y+slop
613 }
614
615 func isAbove(p, clip vg.Point) bool {
616 return p.Y >= clip.Y-slop
617 }
618
619
620
621 func isect(p0, p1, clip, norm vg.Point) vg.Point {
622
623 t := p0.Sub(clip).Dot(norm) / p0.Sub(p1).Dot(norm)
624
625
626 return p1.Sub(p0).Scale(t).Add(p0)
627 }
628
629
630
631 func (c *Canvas) FillText(sty TextStyle, pt vg.Point, txt string) {
632 sty.Handler.Draw(c, txt, sty, pt)
633 }
634
View as plain text