Example #1
0
// Approximate a circular arc using multiple
// cubic Bézier curves, one for each π/2 segment.
//
// This is from:
// 	http://hansmuller-flex.blogspot.com/2011/04/approximating-circular-arc-with-cubic.html
func arc(p *pdf.Path, comp vg.PathComp) {
	x0 := comp.X + comp.Radius*vg.Length(math.Cos(comp.Start))
	y0 := comp.Y + comp.Radius*vg.Length(math.Sin(comp.Start))
	p.Line(pdfPoint(x0, y0))

	a1 := comp.Start
	end := a1 + comp.Angle
	sign := 1.0
	if end < a1 {
		sign = -1.0
	}
	left := math.Abs(comp.Angle)

	// Square root of the machine epsilon for IEEE 64-bit floating
	// point values.  This is the equality threshold recommended
	// in Numerical Recipes, if I recall correctly—it's small enough.
	const epsilon = 1.4901161193847656e-08

	for left > epsilon {
		a2 := a1 + sign*math.Min(math.Pi/2, left)
		partialArc(p, comp.X, comp.Y, comp.Radius, a1, a2)
		left -= math.Abs(a2 - a1)
		a1 = a2
	}
}
Example #2
0
// Height returns the height of the text when using
// the given font.
func (sty TextStyle) Height(txt string) vg.Length {
	nl := textNLines(txt)
	if nl == 0 {
		return vg.Length(0)
	}
	e := sty.Font.Extents()
	return e.Height*vg.Length(nl-1) + e.Ascent
}
Example #3
0
// GlyphBoxes returns a slice of GlyphBoxes,
// one for each of the labels, implementing the
// plot.GlyphBoxer interface.
func (l *Labels) GlyphBoxes(p *plot.Plot) []plot.GlyphBox {
	bs := make([]plot.GlyphBox, len(l.Labels))
	for i, label := range l.Labels {
		bs[i].X = p.X.Norm(l.XYs[i].X)
		bs[i].Y = p.Y.Norm(l.XYs[i].Y)
		w := l.Width(label)
		h := l.Height(label)
		bs[i].Rect.Min.X = w*vg.Length(l.XAlign) + l.XOffset
		bs[i].Rect.Min.Y = h*vg.Length(l.YAlign) + l.YOffset
		bs[i].Rect.Size.X = w
		bs[i].Rect.Size.Y = h
	}
	return bs
}
Example #4
0
// draw draws the legend to the given DrawArea.
func (l *Legend) draw(da DrawArea) {
	iconx := da.Min.X
	textx := iconx + l.ThumbnailWidth + l.TextStyle.Width(" ")
	xalign := 0.0
	if !l.Left {
		iconx = da.Max().X - l.ThumbnailWidth
		textx = iconx - l.TextStyle.Width(" ")
		xalign = -1
	}
	textx += l.XOffs
	iconx += l.XOffs

	enth := l.entryHeight()
	y := da.Max().Y - enth
	if !l.Top {
		y = da.Min.Y + (enth+l.Padding)*(vg.Length(len(l.entries))-1)
	}
	y += l.YOffs

	icon := &DrawArea{
		Canvas: da.Canvas,
		Rect:   Rect{Min: Point{iconx, y}, Size: Point{l.ThumbnailWidth, enth}},
	}
	for _, e := range l.entries {
		for _, t := range e.thumbs {
			t.Thumbnail(icon)
		}
		yoffs := (enth - l.TextStyle.Height(e.text)) / 2
		da.FillText(l.TextStyle, textx, icon.Min.Y+yoffs, xalign, 0, e.text)
		icon.Min.Y -= enth + l.Padding
	}
}
Example #5
0
// FillText fills lines of text in the draw area.
// The text is offset by its width times xalign and
// its height times yalign.  x and y give the bottom
// left corner of the text befor e it is offset.
func (da *DrawArea) FillText(sty TextStyle, x, y vg.Length, xalign, yalign float64, txt string) {
	txt = strings.TrimRight(txt, "\n")
	if len(txt) == 0 {
		return
	}

	da.SetColor(sty.Color)

	ht := sty.Height(txt)
	y += ht*vg.Length(yalign) - sty.Font.Extents().Ascent
	nl := textNLines(txt)
	for i, line := range strings.Split(txt, "\n") {
		xoffs := vg.Length(xalign) * sty.Font.Width(line)
		n := vg.Length(nl - i)
		da.FillString(sty.Font, x+xoffs, y+n*sty.Font.Size, line)
	}
}
Example #6
0
// 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
}
Example #7
0
// Approximate a circular arc of fewer than π/2
// radians with cubic Bézier curve.
func partialArc(p *pdf.Path, x, y, r vg.Length, a1, a2 float64) {
	a := (a2 - a1) / 2
	x4 := r * vg.Length(math.Cos(a))
	y4 := r * vg.Length(math.Sin(a))
	x1 := x4
	y1 := -y4

	const k = 0.5522847498 // some magic constant
	f := k * vg.Length(math.Tan(a))
	x2 := x1 + f*y4
	y2 := y1 + f*x4
	x3 := x2
	y3 := -y2

	// Rotate and translate points into position.
	ar := a + a1
	sinar := vg.Length(math.Sin(ar))
	cosar := vg.Length(math.Cos(ar))
	x2r := x2*cosar - y2*sinar + x
	y2r := x2*sinar + y2*cosar + y
	x3r := x3*cosar - y3*sinar + x
	y3r := x3*sinar + y3*cosar + y
	x4 = r*vg.Length(math.Cos(a2)) + x
	y4 = r*vg.Length(math.Sin(a2)) + y
	p.Curve(pdfPoint(x2r, y2r), pdfPoint(x3r, y3r), pdfPoint(x4, y4))
}
Example #8
0
// padY returns a DrawArea that is padded vertically
// so that glyphs will no be clipped.
func padY(p *Plot, da DrawArea) DrawArea {
	glyphs := p.GlyphBoxes(p)
	b := bottomMost(&da, glyphs)
	yAxis := verticalAxis{p.Y}
	glyphs = append(glyphs, yAxis.GlyphBoxes(p)...)
	t := topMost(&da, glyphs)

	miny := da.Min.Y - b.Min.Y
	maxy := da.Max().Y - (t.Min.Y + t.Size.Y)
	by := vg.Length(b.Y)
	ty := vg.Length(t.Y)
	n := (by*maxy - ty*miny) / (by - ty)
	m := ((by-1)*maxy - ty*miny + miny) / (by - ty)
	return DrawArea{
		Canvas: vg.Canvas(da),
		Rect: Rect{
			Min:  Point{Y: n, X: da.Min.X},
			Size: Point{Y: m - n, X: da.Size.X},
		},
	}
}
Example #9
0
// padX returns a DrawArea that is padded horizontally
// so that glyphs will no be clipped.
func padX(p *Plot, da DrawArea) DrawArea {
	glyphs := p.GlyphBoxes(p)
	l := leftMost(&da, glyphs)
	xAxis := horizontalAxis{p.X}
	glyphs = append(glyphs, xAxis.GlyphBoxes(p)...)
	r := rightMost(&da, glyphs)

	minx := da.Min.X - l.Min.X
	maxx := da.Max().X - (r.Min.X + r.Size.X)
	lx := vg.Length(l.X)
	rx := vg.Length(r.X)
	n := (lx*maxx - rx*minx) / (lx - rx)
	m := ((lx-1)*maxx - rx*minx + minx) / (lx - rx)
	return DrawArea{
		Canvas: vg.Canvas(da),
		Rect: Rect{
			Min:  Point{X: n, Y: da.Min.Y},
			Size: Point{X: m - n, Y: da.Size.Y},
		},
	}
}
Example #10
0
// tickLabelWidth returns the width of the widest tick mark label.
func tickLabelWidth(sty TextStyle, ticks []Tick) vg.Length {
	maxWidth := vg.Length(0)
	for _, t := range ticks {
		if t.IsMinor() {
			continue
		}
		w := sty.Width(t.Label)
		if w > maxWidth {
			maxWidth = w
		}
	}
	return maxWidth
}
Example #11
0
// tickLabelHeight returns height of the tick mark labels.
func tickLabelHeight(sty TextStyle, ticks []Tick) vg.Length {
	maxHeight := vg.Length(0)
	for _, t := range ticks {
		if t.IsMinor() {
			continue
		}
		h := sty.Height(t.Label)
		if h > maxHeight {
			maxHeight = h
		}
	}
	return maxHeight
}
Example #12
0
// Y returns the value of x, given in the unit range,
// in the drawing coordinates of this draw area.
// A value of 0, for example, will return the minimum
// y value of the draw area and a value of 1 will
// return the maximum.
func (da *DrawArea) Y(y float64) vg.Length {
	return vg.Length(y)*(da.Max().Y-da.Min.Y) + da.Min.Y
}
Example #13
0
// X returns the value of x, given in the unit range,
// in the drawing coordinates of this draw area.
// A value of 0, for example, will return the minimum
// x value of the draw area and a value of 1 will
// return the maximum.
func (da *DrawArea) X(x float64) vg.Length {
	return vg.Length(x)*(da.Max().X-da.Min.X) + da.Min.X
}
Example #14
0
// RingGlyph is a glyph that draws the outline of a circle.
type RingGlyph struct{}

// DrawGlyph implements the Glyph interface.
func (RingGlyph) DrawGlyph(da *DrawArea, sty GlyphStyle, pt Point) {
	da.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
	var p vg.Path
	p.Move(pt.X+sty.Radius, pt.Y)
	p.Arc(pt.X, pt.Y, sty.Radius, 0, 2*math.Pi)
	p.Close()
	da.Stroke(p)
}

const (
	cosπover4 = vg.Length(.707106781202420)
	sinπover6 = vg.Length(.500000000025921)
	cosπover6 = vg.Length(.866025403769473)
)

// SquareGlyph is a glyph that draws the outline of a square.
type SquareGlyph struct{}

// DrawGlyph implements the Glyph interface.
func (SquareGlyph) DrawGlyph(da *DrawArea, sty GlyphStyle, pt Point) {
	da.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
	x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
	var p vg.Path
	p.Move(pt.X-x, pt.Y-x)
	p.Line(pt.X+x, pt.Y-x)
	p.Line(pt.X+x, pt.Y+x)
Example #15
0
func TestBubblesRadius(t *testing.T) {
	b := &Bubbles{
		MinRadius: vg.Length(0),
		MaxRadius: vg.Length(1),
	}

	tests := []struct {
		minz, maxz, z float64
		r             vg.Length
	}{
		{0, 0, 0, vg.Length(0.5)},
		{1, 1, 1, vg.Length(0.5)},
		{0, 1, 0, vg.Length(0)},
		{0, 1, 1, vg.Length(1)},
		{0, 1, 0.5, vg.Length(0.5)},
		{0, 2, 1, vg.Length(0.5)},
		{0, 4, 0, vg.Length(0)},
		{0, 4, 1, vg.Length(0.25)},
		{0, 4, 2, vg.Length(0.5)},
		{0, 4, 3, vg.Length(0.75)},
		{0, 4, 4, vg.Length(1)},
	}

	for _, test := range tests {
		b.MinZ, b.MaxZ = test.minz, test.maxz
		if r := b.radius(test.z); r != test.r {
			t.Errorf("Got incorrect radius (%g) on %v", r, test)
		}
	}
}