/
bubbles.go
120 lines (103 loc) · 2.78 KB
/
bubbles.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package plotter
import (
"errors"
"image/color"
"math"
"github.com/gonum/plot"
"github.com/gonum/plot/vg"
"github.com/gonum/plot/vg/draw"
)
// Bubbles implements the Plotter interface, drawing
// a bubble plot of x, y, z triples where the z value
// determines the radius of the bubble.
type Bubbles struct {
XYZs
// Color is the color of the bubbles.
color.Color
// MinRadius and MaxRadius give the minimum
// and maximum bubble radius respectively.
// The radii of each bubble is interpolated linearly
// between these two values.
MinRadius, MaxRadius vg.Length
// MinZ and MaxZ are the minimum and
// maximum Z values from the data.
MinZ, MaxZ float64
}
// NewBubbles creates as new bubble plot plotter for
// the given data, with a minimum and maximum
// bubble radius.
func NewBubbles(xyz XYZer, min, max vg.Length) (*Bubbles, error) {
cpy, err := CopyXYZs(xyz)
if err != nil {
return nil, err
}
if min > max {
return nil, errors.New("Min bubble radius is greater than the max radius")
}
minz := cpy[0].Z
maxz := cpy[0].Z
for _, d := range cpy {
minz = math.Min(minz, d.Z)
maxz = math.Max(maxz, d.Z)
}
return &Bubbles{
XYZs: cpy,
MinRadius: min,
MaxRadius: max,
MinZ: minz,
MaxZ: maxz,
}, nil
}
// Plot implements the Plot method of the plot.Plotter interface.
func (bs *Bubbles) Plot(c draw.Canvas, plt *plot.Plot) error {
trX, trY := plt.Transforms(&c)
c.SetColor(bs.Color)
for _, d := range bs.XYZs {
x := trX(d.X)
y := trY(d.Y)
pt := vg.Point{X: x, Y: y}
if !c.Contains(pt) {
continue
}
rad := bs.radius(d.Z)
// draw a circle centered at x, y
var p vg.Path
p.Move(vg.Point{X: x + rad, Y: y})
p.Arc(pt, rad, 0, 2*math.Pi)
p.Close()
c.Fill(p)
}
return nil
}
// radius returns the radius of a bubble by linear interpolation.
func (bs *Bubbles) radius(z float64) vg.Length {
rng := bs.MaxRadius - bs.MinRadius
if bs.MaxZ == bs.MinZ {
return rng/2 + bs.MinRadius
}
d := (z - bs.MinZ) / (bs.MaxZ - bs.MinZ)
return vg.Length(d)*rng + bs.MinRadius
}
// DataRange implements the DataRange method
// of the plot.DataRanger interface.
func (bs *Bubbles) DataRange() (xmin, xmax, ymin, ymax float64) {
return XYRange(XYValues{bs.XYZs})
}
// GlyphBoxes implements the GlyphBoxes method
// of the plot.GlyphBoxer interface.
func (bs *Bubbles) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
boxes := make([]plot.GlyphBox, len(bs.XYZs))
for i, d := range bs.XYZs {
boxes[i].X = plt.X.Norm(d.X)
boxes[i].Y = plt.Y.Norm(d.Y)
r := bs.radius(d.Z)
boxes[i].Rectangle = vg.Rectangle{
Min: vg.Point{X: -r, Y: -r},
Max: vg.Point{X: +r, Y: +r},
}
}
return boxes
}