fixed dependencies
This commit is contained in:
2
vendor/gonum.org/v1/plot/.codecov.yml
generated
vendored
Normal file
2
vendor/gonum.org/v1/plot/.codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
fixes:
|
||||
- "src/gonum.org/v1/plot/::"
|
||||
12
vendor/gonum.org/v1/plot/.gitignore
generated
vendored
Normal file
12
vendor/gonum.org/v1/plot/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
**/*.svg
|
||||
**/*.png
|
||||
!**/testdata/*_golden.png
|
||||
!**/testdata/*_input.png
|
||||
!**/testdata/*_golden.svg
|
||||
**/*.eps
|
||||
**/*.pdf
|
||||
**/*.jpg
|
||||
**/*.jpeg
|
||||
**/*.tex
|
||||
**/*.tif
|
||||
**/*.tiff
|
||||
5
vendor/gonum.org/v1/plot/AUTHORS
generated
vendored
Normal file
5
vendor/gonum.org/v1/plot/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Ethan Burns <burns.ethan@gmail.com>
|
||||
Steve McCoy <mccoyst@gmail.com>
|
||||
Dan Kortschak <dan.kortschak@adelaide.edu.au>
|
||||
James Bell <james@stellentus.com>
|
||||
Sebastien Binet <seb.binet@gmail.com>
|
||||
1
vendor/gonum.org/v1/plot/CONTRIBUTING.md
generated
vendored
Normal file
1
vendor/gonum.org/v1/plot/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
See the [gonum project-wide CONTRIBUTING.md file.](https://github.com/gonum/gonum/blob/master/CONTRIBUTING.md)
|
||||
23
vendor/gonum.org/v1/plot/LICENSE
generated
vendored
Normal file
23
vendor/gonum.org/v1/plot/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Copyright ©2013 The Gonum Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Gonum project nor the names of its authors and
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
38
vendor/gonum.org/v1/plot/README.md
generated
vendored
Normal file
38
vendor/gonum.org/v1/plot/README.md
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Gonum Plot
|
||||
|
||||
[](https://github.com/gonum/plot/actions)
|
||||
[](https://ci.appveyor.com/project/Gonum/plot/branch/master)
|
||||
[](https://codecov.io/gh/gonum/plot)
|
||||
[](https://coveralls.io/github/gonum/plot?branch=master)
|
||||
[](https://godoc.org/gonum.org/v1/plot)
|
||||
[](https://pkg.go.dev/gonum.org/v1/plot)
|
||||
|
||||
`gonum/plot` is the new, official fork of code.google.com/p/plotinum.
|
||||
It provides an API for building and drawing plots in Go.
|
||||
*Note* that this new API is still in flux and may change.
|
||||
See the wiki for some [example plots](http://github.com/gonum/plot/wiki/Example-plots).
|
||||
|
||||
For additional Plotters, see the [Community Plotters](https://github.com/gonum/plot/wiki/Community-Plotters) Wiki page.
|
||||
|
||||
There is a discussion list on Google Groups: gonum-dev@googlegroups.com.
|
||||
|
||||
`gonum/plot` is split into a few packages:
|
||||
|
||||
* The `plot` package provides simple interface for laying out a plot and provides primitives for drawing to it.
|
||||
* The `plotter` package provides a standard set of `Plotter`s which use the primitives provided by the `plot` package for drawing lines, scatter plots, box plots, error bars, etc. to a plot. You do not need to use the `plotter` package to make use of `gonum/plot`, however: see the wiki for a tutorial on making your own custom plotters.
|
||||
* The `plotutil` package contains a few routines that allow some common plot types to be made very easily. This package is quite new so it is not as well tested as the others and it is bound to change.
|
||||
* The `vg` package provides a generic vector graphics API that sits on top of other vector graphics back-ends such as a custom EPS back-end, draw2d, SVGo, X-Window, gopdf, and [Gio](https://gioui.org).
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available at:
|
||||
|
||||
https://godoc.org/gonum.org/v1/plot
|
||||
|
||||
## Installation
|
||||
|
||||
You can get `gonum/plot` using go get:
|
||||
|
||||
`go get gonum.org/v1/plot/...`
|
||||
|
||||
If you write a cool plotter that you think others may be interested in using, please post to the list so that we can link to it in the `gonum/plot` wiki or possibly integrate it into the `plotter` package.
|
||||
119
vendor/gonum.org/v1/plot/align.go
generated
vendored
Normal file
119
vendor/gonum.org/v1/plot/align.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright ©2017 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 plot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Align returns a two-dimensional row-major array of Canvases which will
|
||||
// produce tiled plots with DataCanvases that are evenly sized and spaced.
|
||||
// The arguments to the function are a two-dimensional row-major array
|
||||
// of plots, a tile configuration, and the canvas to which the tiled
|
||||
// plots are to be drawn.
|
||||
func Align(plots [][]*Plot, t draw.Tiles, dc draw.Canvas) [][]draw.Canvas {
|
||||
o := make([][]draw.Canvas, len(plots))
|
||||
|
||||
if len(plots) != t.Rows {
|
||||
panic(fmt.Errorf("plot: plots rows (%d) != tiles rows (%d)", len(plots), t.Rows))
|
||||
}
|
||||
|
||||
// Create the initial tiles.
|
||||
for j := 0; j < t.Rows; j++ {
|
||||
if len(plots[j]) != t.Cols {
|
||||
panic(fmt.Errorf("plot: plots row %d columns (%d) != tiles columns (%d)", j, len(plots[j]), t.Rows))
|
||||
}
|
||||
|
||||
o[j] = make([]draw.Canvas, len(plots[j]))
|
||||
for i := 0; i < t.Cols; i++ {
|
||||
o[j][i] = t.At(dc, i, j)
|
||||
}
|
||||
}
|
||||
|
||||
type posNeg struct {
|
||||
p, n float64
|
||||
}
|
||||
xSpacing := make([]posNeg, t.Cols)
|
||||
ySpacing := make([]posNeg, t.Rows)
|
||||
|
||||
// Calculate the maximum spacing between data canvases
|
||||
// for each row and column.
|
||||
for j, row := range plots {
|
||||
for i, p := range row {
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
c := o[j][i]
|
||||
dataC := p.DataCanvas(o[j][i])
|
||||
xSpacing[i].n = math.Max(float64(dataC.Min.X-c.Min.X), xSpacing[i].n)
|
||||
xSpacing[i].p = math.Max(float64(c.Max.X-dataC.Max.X), xSpacing[i].p)
|
||||
ySpacing[j].n = math.Max(float64(dataC.Min.Y-c.Min.Y), ySpacing[j].n)
|
||||
ySpacing[j].p = math.Max(float64(c.Max.Y-dataC.Max.Y), ySpacing[j].p)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the total row and column spacing.
|
||||
var xTotalSpace float64
|
||||
for _, s := range xSpacing {
|
||||
xTotalSpace += s.n + s.p
|
||||
}
|
||||
xTotalSpace += float64(t.PadX)*float64(len(xSpacing)-1) + float64(t.PadLeft+t.PadRight)
|
||||
var yTotalSpace float64
|
||||
for _, s := range ySpacing {
|
||||
yTotalSpace += s.n + s.p
|
||||
}
|
||||
yTotalSpace += float64(t.PadY)*float64(len(ySpacing)-1) + float64(t.PadTop+t.PadBottom)
|
||||
|
||||
avgWidth := vg.Length((float64(dc.Max.X-dc.Min.X) - xTotalSpace) / float64(t.Cols))
|
||||
avgHeight := vg.Length((float64(dc.Max.Y-dc.Min.Y) - yTotalSpace) / float64(t.Rows))
|
||||
|
||||
moveVertical := make([]vg.Length, t.Cols)
|
||||
for j := t.Rows - 1; j >= 0; j-- {
|
||||
row := plots[j]
|
||||
var moveHorizontal vg.Length
|
||||
for i, p := range row {
|
||||
c := o[j][i]
|
||||
|
||||
if p != nil {
|
||||
dataC := p.DataCanvas(c)
|
||||
// Adjust the horizontal and vertical spacing between
|
||||
// canvases to match the maximum for each column and row,
|
||||
// respectively.
|
||||
c = draw.Crop(c,
|
||||
vg.Length(xSpacing[i].n)-(dataC.Min.X-c.Min.X),
|
||||
c.Max.X-dataC.Max.X-vg.Length(xSpacing[i].p),
|
||||
vg.Length(ySpacing[j].n)-(dataC.Min.Y-c.Min.Y),
|
||||
c.Max.Y-dataC.Max.Y-vg.Length(ySpacing[j].p),
|
||||
)
|
||||
}
|
||||
|
||||
var width, height vg.Length
|
||||
if p == nil {
|
||||
width = c.Max.X - c.Min.X - vg.Length(xSpacing[i].p+xSpacing[i].n)
|
||||
height = c.Max.Y - c.Min.Y - vg.Length(ySpacing[j].p+ySpacing[j].n)
|
||||
} else {
|
||||
dataC := p.DataCanvas(c)
|
||||
width = dataC.Max.X - dataC.Min.X
|
||||
height = dataC.Max.Y - dataC.Min.Y
|
||||
}
|
||||
|
||||
// Adjust the canvas so that the height and width of the
|
||||
// DataCanvas is the same for all plots.
|
||||
o[j][i] = draw.Crop(c,
|
||||
moveHorizontal,
|
||||
moveHorizontal+avgWidth-width,
|
||||
moveVertical[i],
|
||||
moveVertical[i]+avgHeight-height,
|
||||
)
|
||||
moveHorizontal += avgWidth - width
|
||||
moveVertical[i] += avgHeight - height
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
24
vendor/gonum.org/v1/plot/appveyor.yml
generated
vendored
Normal file
24
vendor/gonum.org/v1/plot/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
build: off
|
||||
|
||||
image: Visual Studio 2019
|
||||
|
||||
stack: go 1.20
|
||||
|
||||
clone_folder: c:\gopath\src\gonum.org\v1\plot
|
||||
|
||||
cache:
|
||||
- '%LocalAppData%\go-build'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
environment:
|
||||
GO111MODULE: 'on'
|
||||
|
||||
build_script:
|
||||
- go version
|
||||
- go get -v -t ./...
|
||||
|
||||
test_script:
|
||||
- go test ./...
|
||||
751
vendor/gonum.org/v1/plot/axis.go
generated
vendored
Normal file
751
vendor/gonum.org/v1/plot/axis.go
generated
vendored
Normal file
@@ -0,0 +1,751 @@
|
||||
// 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 plot
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Ticker creates Ticks in a specified range
|
||||
type Ticker interface {
|
||||
// Ticks returns Ticks in a specified range
|
||||
Ticks(min, max float64) []Tick
|
||||
}
|
||||
|
||||
// Normalizer rescales values from the data coordinate system to the
|
||||
// normalized coordinate system.
|
||||
type Normalizer interface {
|
||||
// Normalize transforms a value x in the data coordinate system to
|
||||
// the normalized coordinate system.
|
||||
Normalize(min, max, x float64) float64
|
||||
}
|
||||
|
||||
// An Axis represents either a horizontal or vertical
|
||||
// axis of a plot.
|
||||
type Axis struct {
|
||||
// Min and Max are the minimum and maximum data
|
||||
// values represented by the axis.
|
||||
Min, Max float64
|
||||
|
||||
Label struct {
|
||||
// Text is the axis label string.
|
||||
Text string
|
||||
|
||||
// Padding is the distance between the label and the axis.
|
||||
Padding vg.Length
|
||||
|
||||
// TextStyle is the style of the axis label text.
|
||||
// For the vertical axis, one quarter turn
|
||||
// counterclockwise will be added to the label
|
||||
// text before drawing.
|
||||
TextStyle text.Style
|
||||
|
||||
// Position is where the axis label string should be drawn.
|
||||
// The default value is draw.PosCenter, displaying the label
|
||||
// at the center of the axis.
|
||||
// Valid values are [-1,+1], with +1 being the far right/top
|
||||
// of the axis, and -1 the far left/bottom of the axis.
|
||||
Position float64
|
||||
}
|
||||
|
||||
// LineStyle is the style of the axis line.
|
||||
draw.LineStyle
|
||||
|
||||
// Padding between the axis line and the data. Having
|
||||
// non-zero padding ensures that the data is never drawn
|
||||
// on the axis, thus making it easier to see.
|
||||
Padding vg.Length
|
||||
|
||||
Tick struct {
|
||||
// Label is the TextStyle on the tick labels.
|
||||
Label text.Style
|
||||
|
||||
// LineStyle is the LineStyle of the tick lines.
|
||||
draw.LineStyle
|
||||
|
||||
// Length is the length of a major tick mark.
|
||||
// Minor tick marks are half of the length of major
|
||||
// tick marks.
|
||||
Length vg.Length
|
||||
|
||||
// Marker returns the tick marks. Any tick marks
|
||||
// returned by the Marker function that are not in
|
||||
// range of the axis are not drawn.
|
||||
Marker Ticker
|
||||
}
|
||||
|
||||
// Scale transforms a value given in the data coordinate system
|
||||
// to the normalized coordinate system of the axis—its distance
|
||||
// along the axis as a fraction of the axis range.
|
||||
Scale Normalizer
|
||||
|
||||
// AutoRescale enables an axis to automatically adapt its minimum
|
||||
// and maximum boundaries, according to its underlying Ticker.
|
||||
AutoRescale bool
|
||||
}
|
||||
|
||||
// makeAxis returns a default Axis.
|
||||
//
|
||||
// The default range is (∞, ∞), and thus any finite
|
||||
// value is less than Min and greater than Max.
|
||||
func makeAxis(o orientation) Axis {
|
||||
|
||||
a := Axis{
|
||||
Min: math.Inf(+1),
|
||||
Max: math.Inf(-1),
|
||||
LineStyle: draw.LineStyle{
|
||||
Color: color.Black,
|
||||
Width: vg.Points(0.5),
|
||||
},
|
||||
Padding: vg.Points(5),
|
||||
Scale: LinearScale{},
|
||||
}
|
||||
a.Label.TextStyle = text.Style{
|
||||
Color: color.Black,
|
||||
Font: font.From(DefaultFont, 12),
|
||||
XAlign: draw.XCenter,
|
||||
YAlign: draw.YBottom,
|
||||
Handler: DefaultTextHandler,
|
||||
}
|
||||
a.Label.Position = draw.PosCenter
|
||||
|
||||
var (
|
||||
xalign draw.XAlignment
|
||||
yalign draw.YAlignment
|
||||
)
|
||||
switch o {
|
||||
case vertical:
|
||||
xalign = draw.XRight
|
||||
yalign = draw.YCenter
|
||||
case horizontal:
|
||||
xalign = draw.XCenter
|
||||
yalign = draw.YTop
|
||||
}
|
||||
|
||||
a.Tick.Label = text.Style{
|
||||
Color: color.Black,
|
||||
Font: font.From(DefaultFont, 10),
|
||||
XAlign: xalign,
|
||||
YAlign: yalign,
|
||||
Handler: DefaultTextHandler,
|
||||
}
|
||||
a.Tick.LineStyle = draw.LineStyle{
|
||||
Color: color.Black,
|
||||
Width: vg.Points(0.5),
|
||||
}
|
||||
a.Tick.Length = vg.Points(8)
|
||||
a.Tick.Marker = DefaultTicks{}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// sanitizeRange ensures that the range of the
|
||||
// axis makes sense.
|
||||
func (a *Axis) sanitizeRange() {
|
||||
if math.IsInf(a.Min, 0) {
|
||||
a.Min = 0
|
||||
}
|
||||
if math.IsInf(a.Max, 0) {
|
||||
a.Max = 0
|
||||
}
|
||||
if a.Min > a.Max {
|
||||
a.Min, a.Max = a.Max, a.Min
|
||||
}
|
||||
if a.Min == a.Max {
|
||||
a.Min--
|
||||
a.Max++
|
||||
}
|
||||
|
||||
if a.AutoRescale {
|
||||
marks := a.Tick.Marker.Ticks(a.Min, a.Max)
|
||||
for _, t := range marks {
|
||||
a.Min = math.Min(a.Min, t.Value)
|
||||
a.Max = math.Max(a.Max, t.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LinearScale an be used as the value of an Axis.Scale function to
|
||||
// set the axis to a standard linear scale.
|
||||
type LinearScale struct{}
|
||||
|
||||
var _ Normalizer = LinearScale{}
|
||||
|
||||
// Normalize returns the fractional distance of x between min and max.
|
||||
func (LinearScale) Normalize(min, max, x float64) float64 {
|
||||
return (x - min) / (max - min)
|
||||
}
|
||||
|
||||
// LogScale can be used as the value of an Axis.Scale function to
|
||||
// set the axis to a log scale.
|
||||
type LogScale struct{}
|
||||
|
||||
var _ Normalizer = LogScale{}
|
||||
|
||||
// Normalize returns the fractional logarithmic distance of
|
||||
// x between min and max.
|
||||
func (LogScale) Normalize(min, max, x float64) float64 {
|
||||
if min <= 0 || max <= 0 || x <= 0 {
|
||||
panic("Values must be greater than 0 for a log scale.")
|
||||
}
|
||||
logMin := math.Log(min)
|
||||
return (math.Log(x) - logMin) / (math.Log(max) - logMin)
|
||||
}
|
||||
|
||||
// InvertedScale can be used as the value of an Axis.Scale function to
|
||||
// invert the axis using any Normalizer.
|
||||
type InvertedScale struct{ Normalizer }
|
||||
|
||||
var _ Normalizer = InvertedScale{}
|
||||
|
||||
// Normalize returns a normalized [0, 1] value for the position of x.
|
||||
func (is InvertedScale) Normalize(min, max, x float64) float64 {
|
||||
return is.Normalizer.Normalize(max, min, x)
|
||||
}
|
||||
|
||||
// Norm returns the value of x, given in the data coordinate
|
||||
// system, normalized to its distance as a fraction of the
|
||||
// range of this axis. For example, if x is a.Min then the return
|
||||
// value is 0, and if x is a.Max then the return value is 1.
|
||||
func (a Axis) Norm(x float64) float64 {
|
||||
return a.Scale.Normalize(a.Min, a.Max, x)
|
||||
}
|
||||
|
||||
// drawTicks returns true if the tick marks should be drawn.
|
||||
func (a Axis) drawTicks() bool {
|
||||
return a.Tick.Width > 0 && a.Tick.Length > 0
|
||||
}
|
||||
|
||||
// A horizontalAxis draws horizontally across the bottom
|
||||
// of a plot.
|
||||
type horizontalAxis struct {
|
||||
Axis
|
||||
}
|
||||
|
||||
// size returns the height of the axis.
|
||||
func (a horizontalAxis) size() (h vg.Length) {
|
||||
if a.Label.Text != "" { // We assume that the label isn't rotated.
|
||||
h += a.Label.TextStyle.FontExtents().Descent
|
||||
h += a.Label.TextStyle.Height(a.Label.Text)
|
||||
h += a.Label.Padding
|
||||
}
|
||||
|
||||
marks := a.Tick.Marker.Ticks(a.Min, a.Max)
|
||||
if len(marks) > 0 {
|
||||
if a.drawTicks() {
|
||||
h += a.Tick.Length
|
||||
}
|
||||
h += tickLabelHeight(a.Tick.Label, marks)
|
||||
}
|
||||
h += a.Width / 2
|
||||
h += a.Padding
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// draw draws the axis along the lower edge of a draw.Canvas.
|
||||
func (a horizontalAxis) draw(c draw.Canvas) {
|
||||
var (
|
||||
x vg.Length
|
||||
y = c.Min.Y
|
||||
)
|
||||
switch a.Label.Position {
|
||||
case draw.PosCenter:
|
||||
x = c.Center().X
|
||||
case draw.PosRight:
|
||||
x = c.Max.X
|
||||
x -= a.Label.TextStyle.Width(a.Label.Text) / 2
|
||||
}
|
||||
if a.Label.Text != "" {
|
||||
descent := a.Label.TextStyle.FontExtents().Descent
|
||||
c.FillText(a.Label.TextStyle, vg.Point{X: x, Y: y + descent}, a.Label.Text)
|
||||
y += a.Label.TextStyle.Height(a.Label.Text)
|
||||
y += a.Label.Padding
|
||||
}
|
||||
|
||||
marks := a.Tick.Marker.Ticks(a.Min, a.Max)
|
||||
ticklabelheight := tickLabelHeight(a.Tick.Label, marks)
|
||||
descent := a.Tick.Label.FontExtents().Descent
|
||||
for _, t := range marks {
|
||||
x := c.X(a.Norm(t.Value))
|
||||
if !c.ContainsX(x) || t.IsMinor() {
|
||||
continue
|
||||
}
|
||||
c.FillText(a.Tick.Label, vg.Point{X: x, Y: y + ticklabelheight + descent}, t.Label)
|
||||
}
|
||||
|
||||
if len(marks) > 0 {
|
||||
y += ticklabelheight
|
||||
} else {
|
||||
y += a.Width / 2
|
||||
}
|
||||
|
||||
if len(marks) > 0 && a.drawTicks() {
|
||||
len := a.Tick.Length
|
||||
for _, t := range marks {
|
||||
x := c.X(a.Norm(t.Value))
|
||||
if !c.ContainsX(x) {
|
||||
continue
|
||||
}
|
||||
start := t.lengthOffset(len)
|
||||
c.StrokeLine2(a.Tick.LineStyle, x, y+start, x, y+len)
|
||||
}
|
||||
y += len
|
||||
}
|
||||
|
||||
c.StrokeLine2(a.LineStyle, c.Min.X, y, c.Max.X, y)
|
||||
}
|
||||
|
||||
// GlyphBoxes returns the GlyphBoxes for the tick labels.
|
||||
func (a horizontalAxis) GlyphBoxes(p *Plot) []GlyphBox {
|
||||
var (
|
||||
boxes []GlyphBox
|
||||
yoff font.Length
|
||||
)
|
||||
|
||||
if a.Label.Text != "" {
|
||||
x := a.Norm(p.X.Max)
|
||||
switch a.Label.Position {
|
||||
case draw.PosCenter:
|
||||
x = a.Norm(0.5 * (p.X.Max + p.X.Min))
|
||||
case draw.PosRight:
|
||||
x -= a.Norm(0.5 * a.Label.TextStyle.Width(a.Label.Text).Points()) // FIXME(sbinet): want data coordinates
|
||||
}
|
||||
descent := a.Label.TextStyle.FontExtents().Descent
|
||||
boxes = append(boxes, GlyphBox{
|
||||
X: x,
|
||||
Rectangle: a.Label.TextStyle.Rectangle(a.Label.Text).Add(vg.Point{Y: yoff + descent}),
|
||||
})
|
||||
yoff += a.Label.TextStyle.Height(a.Label.Text)
|
||||
yoff += a.Label.Padding
|
||||
}
|
||||
|
||||
var (
|
||||
marks = a.Tick.Marker.Ticks(a.Min, a.Max)
|
||||
height = tickLabelHeight(a.Tick.Label, marks)
|
||||
descent = a.Tick.Label.FontExtents().Descent
|
||||
)
|
||||
for _, t := range marks {
|
||||
if t.IsMinor() {
|
||||
continue
|
||||
}
|
||||
box := GlyphBox{
|
||||
X: a.Norm(t.Value),
|
||||
Rectangle: a.Tick.Label.Rectangle(t.Label).Add(vg.Point{Y: yoff + height + descent}),
|
||||
}
|
||||
boxes = append(boxes, box)
|
||||
}
|
||||
return boxes
|
||||
}
|
||||
|
||||
// A verticalAxis is drawn vertically up the left side of a plot.
|
||||
type verticalAxis struct {
|
||||
Axis
|
||||
}
|
||||
|
||||
// size returns the width of the axis.
|
||||
func (a verticalAxis) size() (w vg.Length) {
|
||||
if a.Label.Text != "" { // We assume that the label isn't rotated.
|
||||
w += a.Label.TextStyle.FontExtents().Descent
|
||||
w += a.Label.TextStyle.Height(a.Label.Text)
|
||||
w += a.Label.Padding
|
||||
}
|
||||
|
||||
marks := a.Tick.Marker.Ticks(a.Min, a.Max)
|
||||
if len(marks) > 0 {
|
||||
if lwidth := tickLabelWidth(a.Tick.Label, marks); lwidth > 0 {
|
||||
w += lwidth
|
||||
w += a.Label.TextStyle.Width(" ")
|
||||
}
|
||||
if a.drawTicks() {
|
||||
w += a.Tick.Length
|
||||
}
|
||||
}
|
||||
w += a.Width / 2
|
||||
w += a.Padding
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// draw draws the axis along the left side of a draw.Canvas.
|
||||
func (a verticalAxis) draw(c draw.Canvas) {
|
||||
var (
|
||||
x = c.Min.X
|
||||
y vg.Length
|
||||
)
|
||||
if a.Label.Text != "" {
|
||||
sty := a.Label.TextStyle
|
||||
sty.Rotation += math.Pi / 2
|
||||
x += a.Label.TextStyle.Height(a.Label.Text)
|
||||
switch a.Label.Position {
|
||||
case draw.PosCenter:
|
||||
y = c.Center().Y
|
||||
case draw.PosTop:
|
||||
y = c.Max.Y
|
||||
y -= a.Label.TextStyle.Width(a.Label.Text) / 2
|
||||
}
|
||||
descent := a.Label.TextStyle.FontExtents().Descent
|
||||
c.FillText(sty, vg.Point{X: x - descent, Y: y}, a.Label.Text)
|
||||
x += descent
|
||||
x += a.Label.Padding
|
||||
}
|
||||
marks := a.Tick.Marker.Ticks(a.Min, a.Max)
|
||||
if w := tickLabelWidth(a.Tick.Label, marks); len(marks) > 0 && w > 0 {
|
||||
x += w
|
||||
}
|
||||
|
||||
major := false
|
||||
descent := a.Tick.Label.FontExtents().Descent
|
||||
for _, t := range marks {
|
||||
y := c.Y(a.Norm(t.Value))
|
||||
if !c.ContainsY(y) || t.IsMinor() {
|
||||
continue
|
||||
}
|
||||
c.FillText(a.Tick.Label, vg.Point{X: x, Y: y + descent}, t.Label)
|
||||
major = true
|
||||
}
|
||||
if major {
|
||||
x += a.Tick.Label.Width(" ")
|
||||
}
|
||||
if a.drawTicks() && len(marks) > 0 {
|
||||
len := a.Tick.Length
|
||||
for _, t := range marks {
|
||||
y := c.Y(a.Norm(t.Value))
|
||||
if !c.ContainsY(y) {
|
||||
continue
|
||||
}
|
||||
start := t.lengthOffset(len)
|
||||
c.StrokeLine2(a.Tick.LineStyle, x+start, y, x+len, y)
|
||||
}
|
||||
x += len
|
||||
}
|
||||
|
||||
c.StrokeLine2(a.LineStyle, x, c.Min.Y, x, c.Max.Y)
|
||||
}
|
||||
|
||||
// GlyphBoxes returns the GlyphBoxes for the tick labels
|
||||
func (a verticalAxis) GlyphBoxes(p *Plot) []GlyphBox {
|
||||
var (
|
||||
boxes []GlyphBox
|
||||
xoff font.Length
|
||||
)
|
||||
|
||||
if a.Label.Text != "" {
|
||||
yoff := a.Norm(p.Y.Max)
|
||||
switch a.Label.Position {
|
||||
case draw.PosCenter:
|
||||
yoff = a.Norm(0.5 * (p.Y.Max + p.Y.Min))
|
||||
case draw.PosTop:
|
||||
yoff -= a.Norm(0.5 * a.Label.TextStyle.Width(a.Label.Text).Points()) // FIXME(sbinet): want data coordinates
|
||||
}
|
||||
|
||||
sty := a.Label.TextStyle
|
||||
sty.Rotation += math.Pi / 2
|
||||
|
||||
xoff += a.Label.TextStyle.Height(a.Label.Text)
|
||||
descent := a.Label.TextStyle.FontExtents().Descent
|
||||
boxes = append(boxes, GlyphBox{
|
||||
Y: yoff,
|
||||
Rectangle: sty.Rectangle(a.Label.Text).Add(vg.Point{X: xoff - descent}),
|
||||
})
|
||||
xoff += descent
|
||||
xoff += a.Label.Padding
|
||||
}
|
||||
|
||||
marks := a.Tick.Marker.Ticks(a.Min, a.Max)
|
||||
if w := tickLabelWidth(a.Tick.Label, marks); len(marks) != 0 && w > 0 {
|
||||
xoff += w
|
||||
}
|
||||
|
||||
var (
|
||||
ext = a.Tick.Label.FontExtents()
|
||||
desc = ext.Height - ext.Ascent // descent + linegap
|
||||
)
|
||||
for _, t := range marks {
|
||||
if t.IsMinor() {
|
||||
continue
|
||||
}
|
||||
box := GlyphBox{
|
||||
Y: a.Norm(t.Value),
|
||||
Rectangle: a.Tick.Label.Rectangle(t.Label).Add(vg.Point{X: xoff, Y: desc}),
|
||||
}
|
||||
boxes = append(boxes, box)
|
||||
}
|
||||
return boxes
|
||||
}
|
||||
|
||||
// DefaultTicks is suitable for the Tick.Marker field of an Axis,
|
||||
// it returns a reasonable default set of tick marks.
|
||||
type DefaultTicks struct{}
|
||||
|
||||
var _ Ticker = DefaultTicks{}
|
||||
|
||||
// Ticks returns Ticks in the specified range.
|
||||
func (DefaultTicks) Ticks(min, max float64) []Tick {
|
||||
if max <= min {
|
||||
panic("illegal range")
|
||||
}
|
||||
|
||||
const suggestedTicks = 3
|
||||
|
||||
labels, step, q, mag := talbotLinHanrahan(min, max, suggestedTicks, withinData, nil, nil, nil)
|
||||
majorDelta := step * math.Pow10(mag)
|
||||
if q == 0 {
|
||||
// Simple fall back was chosen, so
|
||||
// majorDelta is the label distance.
|
||||
majorDelta = labels[1] - labels[0]
|
||||
}
|
||||
|
||||
// Choose a reasonable, but ad
|
||||
// hoc formatting for labels.
|
||||
fc := byte('f')
|
||||
var off int
|
||||
if mag < -1 || 6 < mag {
|
||||
off = 1
|
||||
fc = 'g'
|
||||
}
|
||||
if math.Trunc(q) != q {
|
||||
off += 2
|
||||
}
|
||||
prec := minInt(6, maxInt(off, -mag))
|
||||
ticks := make([]Tick, len(labels))
|
||||
for i, v := range labels {
|
||||
ticks[i] = Tick{Value: v, Label: strconv.FormatFloat(v, fc, prec, 64)}
|
||||
}
|
||||
|
||||
var minorDelta float64
|
||||
// See talbotLinHanrahan for the values used here.
|
||||
switch step {
|
||||
case 1, 2.5:
|
||||
minorDelta = majorDelta / 5
|
||||
case 2, 3, 4, 5:
|
||||
minorDelta = majorDelta / step
|
||||
default:
|
||||
if majorDelta/2 < dlamchP {
|
||||
return ticks
|
||||
}
|
||||
minorDelta = majorDelta / 2
|
||||
}
|
||||
|
||||
// Find the first minor tick not greater
|
||||
// than the lowest data value.
|
||||
var i float64
|
||||
for labels[0]+(i-1)*minorDelta > min {
|
||||
i--
|
||||
}
|
||||
// Add ticks at minorDelta intervals when
|
||||
// they are not within minorDelta/2 of a
|
||||
// labelled tick.
|
||||
for {
|
||||
val := labels[0] + i*minorDelta
|
||||
if val > max {
|
||||
break
|
||||
}
|
||||
found := false
|
||||
for _, t := range ticks {
|
||||
if math.Abs(t.Value-val) < minorDelta/2 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
ticks = append(ticks, Tick{Value: val})
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return ticks
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func maxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// LogTicks is suitable for the Tick.Marker field of an Axis,
|
||||
// it returns tick marks suitable for a log-scale axis.
|
||||
type LogTicks struct {
|
||||
// Prec specifies the precision of tick rendering
|
||||
// according to the documentation for strconv.FormatFloat.
|
||||
Prec int
|
||||
}
|
||||
|
||||
var _ Ticker = LogTicks{}
|
||||
|
||||
// Ticks returns Ticks in a specified range
|
||||
func (t LogTicks) Ticks(min, max float64) []Tick {
|
||||
if min <= 0 || max <= 0 {
|
||||
panic("Values must be greater than 0 for a log scale.")
|
||||
}
|
||||
|
||||
val := math.Pow10(int(math.Log10(min)))
|
||||
max = math.Pow10(int(math.Ceil(math.Log10(max))))
|
||||
var ticks []Tick
|
||||
for val < max {
|
||||
for i := 1; i < 10; i++ {
|
||||
if i == 1 {
|
||||
ticks = append(ticks, Tick{Value: val, Label: formatFloatTick(val, t.Prec)})
|
||||
}
|
||||
ticks = append(ticks, Tick{Value: val * float64(i)})
|
||||
}
|
||||
val *= 10
|
||||
}
|
||||
ticks = append(ticks, Tick{Value: val, Label: formatFloatTick(val, t.Prec)})
|
||||
|
||||
return ticks
|
||||
}
|
||||
|
||||
// ConstantTicks is suitable for the Tick.Marker field of an Axis.
|
||||
// This function returns the given set of ticks.
|
||||
type ConstantTicks []Tick
|
||||
|
||||
var _ Ticker = ConstantTicks{}
|
||||
|
||||
// Ticks returns Ticks in a specified range
|
||||
func (ts ConstantTicks) Ticks(float64, float64) []Tick {
|
||||
return ts
|
||||
}
|
||||
|
||||
// UnixTimeIn returns a time conversion function for the given location.
|
||||
func UnixTimeIn(loc *time.Location) func(t float64) time.Time {
|
||||
return func(t float64) time.Time {
|
||||
return time.Unix(int64(t), 0).In(loc)
|
||||
}
|
||||
}
|
||||
|
||||
// UTCUnixTime is the default time conversion for TimeTicks.
|
||||
var UTCUnixTime = UnixTimeIn(time.UTC)
|
||||
|
||||
// TimeTicks is suitable for axes representing time values.
|
||||
type TimeTicks struct {
|
||||
// Ticker is used to generate a set of ticks.
|
||||
// If nil, DefaultTicks will be used.
|
||||
Ticker Ticker
|
||||
|
||||
// Format is the textual representation of the time value.
|
||||
// If empty, time.RFC3339 will be used
|
||||
Format string
|
||||
|
||||
// Time takes a float64 value and converts it into a time.Time.
|
||||
// If nil, UTCUnixTime is used.
|
||||
Time func(t float64) time.Time
|
||||
}
|
||||
|
||||
var _ Ticker = TimeTicks{}
|
||||
|
||||
// Ticks implements plot.Ticker.
|
||||
func (t TimeTicks) Ticks(min, max float64) []Tick {
|
||||
if t.Ticker == nil {
|
||||
t.Ticker = DefaultTicks{}
|
||||
}
|
||||
if t.Format == "" {
|
||||
t.Format = time.RFC3339
|
||||
}
|
||||
if t.Time == nil {
|
||||
t.Time = UTCUnixTime
|
||||
}
|
||||
|
||||
ticks := t.Ticker.Ticks(min, max)
|
||||
for i := range ticks {
|
||||
tick := &ticks[i]
|
||||
if tick.Label == "" {
|
||||
continue
|
||||
}
|
||||
tick.Label = t.Time(tick.Value).Format(t.Format)
|
||||
}
|
||||
return ticks
|
||||
}
|
||||
|
||||
// A Tick is a single tick mark on an axis.
|
||||
type Tick struct {
|
||||
// Value is the data value marked by this Tick.
|
||||
Value float64
|
||||
|
||||
// Label is the text to display at the tick mark.
|
||||
// If Label is an empty string then this is a minor
|
||||
// tick mark.
|
||||
Label string
|
||||
}
|
||||
|
||||
// IsMinor returns true if this is a minor tick mark.
|
||||
func (t Tick) IsMinor() bool {
|
||||
return t.Label == ""
|
||||
}
|
||||
|
||||
// lengthOffset returns an offset that should be added to the
|
||||
// tick mark's line to accout for its length. I.e., the start of
|
||||
// the line for a minor tick mark must be shifted by half of
|
||||
// the length.
|
||||
func (t Tick) lengthOffset(len vg.Length) vg.Length {
|
||||
if t.IsMinor() {
|
||||
return len / 2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// tickLabelHeight returns height of the tick mark labels.
|
||||
func tickLabelHeight(sty text.Style, ticks []Tick) vg.Length {
|
||||
maxHeight := vg.Length(0)
|
||||
for _, t := range ticks {
|
||||
if t.IsMinor() {
|
||||
continue
|
||||
}
|
||||
r := sty.Rectangle(t.Label)
|
||||
h := r.Max.Y - r.Min.Y
|
||||
if h > maxHeight {
|
||||
maxHeight = h
|
||||
}
|
||||
}
|
||||
return maxHeight
|
||||
}
|
||||
|
||||
// tickLabelWidth returns the width of the widest tick mark label.
|
||||
func tickLabelWidth(sty text.Style, ticks []Tick) vg.Length {
|
||||
maxWidth := vg.Length(0)
|
||||
for _, t := range ticks {
|
||||
if t.IsMinor() {
|
||||
continue
|
||||
}
|
||||
r := sty.Rectangle(t.Label)
|
||||
w := r.Max.X - r.Min.X
|
||||
if w > maxWidth {
|
||||
maxWidth = w
|
||||
}
|
||||
}
|
||||
return maxWidth
|
||||
}
|
||||
|
||||
// formatFloatTick returns a g-formated string representation of v
|
||||
// to the specified precision.
|
||||
func formatFloatTick(v float64, prec int) string {
|
||||
return strconv.FormatFloat(v, 'g', prec, 64)
|
||||
}
|
||||
|
||||
// TickerFunc is suitable for the Tick.Marker field of an Axis.
|
||||
// It is an adapter which allows to quickly setup a Ticker using a function with an appropriate signature.
|
||||
type TickerFunc func(min, max float64) []Tick
|
||||
|
||||
var _ Ticker = TickerFunc(nil)
|
||||
|
||||
// Ticks implements plot.Ticker.
|
||||
func (f TickerFunc) Ticks(min, max float64) []Tick {
|
||||
return f(min, max)
|
||||
}
|
||||
16
vendor/gonum.org/v1/plot/doc.go
generated
vendored
Normal file
16
vendor/gonum.org/v1/plot/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright ©2017 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 plot provides an API for setting up plots, and primitives for
|
||||
// drawing on plots.
|
||||
//
|
||||
// Plot is the basic type for creating a plot, setting the title, axis
|
||||
// labels, legend, tick marks, etc. Types implementing the Plotter
|
||||
// interface can draw to the data area of a plot using the primitives
|
||||
// made available by this package. Some standard implementations
|
||||
// of the Plotter interface can be found in the
|
||||
// gonum.org/v1/plot/plotter package
|
||||
// which is documented here:
|
||||
// https://godoc.org/gonum.org/v1/plot/plotter
|
||||
package plot // import "gonum.org/v1/plot"
|
||||
6
vendor/gonum.org/v1/plot/font/doc.go
generated
vendored
Normal file
6
vendor/gonum.org/v1/plot/font/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright ©2021 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 font provides types to describe and select text font faces.
|
||||
package font // import "gonum.org/v1/plot/font"
|
||||
335
vendor/gonum.org/v1/plot/font/font.go
generated
vendored
Normal file
335
vendor/gonum.org/v1/plot/font/font.go
generated
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
// Copyright ©2021 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 font
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// DefaultCache is the global cache for fonts.
|
||||
var DefaultCache *Cache = NewCache(nil)
|
||||
|
||||
// Font represents a font face.
|
||||
type Font struct {
|
||||
// Typeface identifies the Font.
|
||||
Typeface Typeface
|
||||
|
||||
// TODO(sbinet): Gio@v0.2.0 has dropped font.Font.Variant
|
||||
// we should probably follow suit.
|
||||
|
||||
// Variant is the variant of a font, such as "Mono" or "Smallcaps".
|
||||
Variant Variant
|
||||
|
||||
// Style is the style of a font, such as Regular or Italic.
|
||||
Style font.Style
|
||||
|
||||
// Weight is the weight of a font, such as Normal or Bold.
|
||||
Weight font.Weight
|
||||
|
||||
// Size is the size of the font.
|
||||
Size Length
|
||||
}
|
||||
|
||||
// Name returns a fully qualified name for the given font.
|
||||
func (f *Font) Name() string {
|
||||
v := f.Variant
|
||||
w := weightName(f.Weight)
|
||||
s := styleName(f.Style)
|
||||
|
||||
switch f.Style {
|
||||
case font.StyleNormal:
|
||||
s = ""
|
||||
if f.Weight == font.WeightNormal {
|
||||
w = "Regular"
|
||||
}
|
||||
default:
|
||||
if f.Weight == font.WeightNormal {
|
||||
w = ""
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s-%s%s", f.Typeface, v, w, s)
|
||||
}
|
||||
|
||||
// From returns a copy of the provided font with its size set.
|
||||
func From(fnt Font, size Length) Font {
|
||||
o := fnt
|
||||
o.Size = size
|
||||
return o
|
||||
}
|
||||
|
||||
// Typeface identifies a particular typeface design.
|
||||
// The empty string denotes the default typeface.
|
||||
type Typeface string
|
||||
|
||||
// Variant denotes a typeface variant, such as "Mono", "Smallcaps" or "Math".
|
||||
type Variant string
|
||||
|
||||
// Extents contains font metric information.
|
||||
type Extents struct {
|
||||
// Ascent is the distance that the text
|
||||
// extends above the baseline.
|
||||
Ascent Length
|
||||
|
||||
// Descent is the distance that the text
|
||||
// extends below the baseline. The descent
|
||||
// is given as a positive value.
|
||||
Descent Length
|
||||
|
||||
// Height is the distance from the lowest
|
||||
// descending point to the highest ascending
|
||||
// point.
|
||||
Height Length
|
||||
}
|
||||
|
||||
// Face holds a font descriptor and the associated font face.
|
||||
type Face struct {
|
||||
Font Font
|
||||
Face *opentype.Font
|
||||
}
|
||||
|
||||
// Name returns a fully qualified name for the given font.
|
||||
func (f *Face) Name() string {
|
||||
return f.Font.Name()
|
||||
}
|
||||
|
||||
// FontFace returns the opentype font face for the requested
|
||||
// dots-per-inch resolution.
|
||||
func (f *Face) FontFace(dpi float64) font.Face {
|
||||
face, err := opentype.NewFace(f.Face, &opentype.FaceOptions{
|
||||
Size: f.Font.Size.Points(),
|
||||
DPI: dpi,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return face
|
||||
}
|
||||
|
||||
// default hinting for OpenType fonts
|
||||
const defaultHinting = font.HintingNone
|
||||
|
||||
// Extents returns the FontExtents for a font.
|
||||
func (f *Face) Extents() Extents {
|
||||
var (
|
||||
// TODO(sbinet): re-use a Font-level sfnt.Buffer instead?
|
||||
buf sfnt.Buffer
|
||||
ppem = fixed.Int26_6(f.Face.UnitsPerEm())
|
||||
)
|
||||
|
||||
met, err := f.Face.Metrics(&buf, ppem, defaultHinting)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not extract font extents: %v", err))
|
||||
}
|
||||
scale := f.Font.Size / Points(float64(ppem))
|
||||
return Extents{
|
||||
Ascent: Points(float64(met.Ascent)) * scale,
|
||||
Descent: Points(float64(met.Descent)) * scale,
|
||||
Height: Points(float64(met.Height)) * scale,
|
||||
}
|
||||
}
|
||||
|
||||
// Width returns width of a string when drawn using the font.
|
||||
func (f *Face) Width(s string) Length {
|
||||
var (
|
||||
pixelsPerEm = fixed.Int26_6(f.Face.UnitsPerEm())
|
||||
|
||||
// scale converts sfnt.Unit to float64
|
||||
scale = f.Font.Size / Points(float64(pixelsPerEm))
|
||||
|
||||
width = 0
|
||||
hasPrev = false
|
||||
buf sfnt.Buffer
|
||||
prev, idx sfnt.GlyphIndex
|
||||
hinting = defaultHinting
|
||||
)
|
||||
for _, rune := range s {
|
||||
var err error
|
||||
idx, err = f.Face.GlyphIndex(&buf, rune)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not get glyph index: %v", err))
|
||||
}
|
||||
if hasPrev {
|
||||
kern, err := f.Face.Kern(&buf, prev, idx, pixelsPerEm, hinting)
|
||||
switch {
|
||||
case err == nil:
|
||||
width += int(kern)
|
||||
case errors.Is(err, sfnt.ErrNotFound):
|
||||
// no-op
|
||||
default:
|
||||
panic(fmt.Errorf("could not get kerning: %v", err))
|
||||
}
|
||||
}
|
||||
adv, err := f.Face.GlyphAdvance(&buf, idx, pixelsPerEm, hinting)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not retrieve glyph's advance: %v", err))
|
||||
}
|
||||
width += int(adv)
|
||||
prev, hasPrev = idx, true
|
||||
}
|
||||
return Points(float64(width)) * scale
|
||||
}
|
||||
|
||||
// Collection is a collection of fonts, regrouped under a common typeface.
|
||||
type Collection []Face
|
||||
|
||||
// Cache collects font faces.
|
||||
type Cache struct {
|
||||
mu sync.RWMutex
|
||||
def Typeface
|
||||
faces map[Font]*opentype.Font
|
||||
}
|
||||
|
||||
// We make Cache implement dummy GobDecoder and GobEncoder interfaces
|
||||
// to allow plot.Plot (or any other type holding a Cache) to be (de)serialized
|
||||
// with encoding/gob.
|
||||
// As Cache holds opentype.Font, the reflect-based gob (de)serialization can not
|
||||
// work: gob isn't happy with opentype.Font having no exported field:
|
||||
//
|
||||
// error: gob: type font.Cache has no exported fields
|
||||
//
|
||||
// FIXME(sbinet): perhaps encode/decode Cache.def typeface?
|
||||
|
||||
func (c *Cache) GobEncode() ([]byte, error) { return nil, nil }
|
||||
func (c *Cache) GobDecode([]byte) error {
|
||||
if c.faces == nil {
|
||||
c.faces = make(map[Font]*opentype.Font)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewCache creates a new cache of fonts from the provided collection of
|
||||
// font Faces.
|
||||
// The first font Face in the collection is set to be the default one.
|
||||
func NewCache(coll Collection) *Cache {
|
||||
cache := &Cache{
|
||||
faces: make(map[Font]*opentype.Font, len(coll)),
|
||||
}
|
||||
cache.Add(coll)
|
||||
return cache
|
||||
}
|
||||
|
||||
// Add adds a whole collection of font Faces to the font cache.
|
||||
// If the cache is empty, the first font Face in the collection is set
|
||||
// to be the default one.
|
||||
func (c *Cache) Add(coll Collection) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.faces == nil {
|
||||
c.faces = make(map[Font]*opentype.Font, len(coll))
|
||||
}
|
||||
for i, f := range coll {
|
||||
if i == 0 && c.def == "" {
|
||||
c.def = f.Font.Typeface
|
||||
}
|
||||
fnt := f.Font
|
||||
fnt.Size = 0 // store all font descriptors with the same size.
|
||||
c.faces[fnt] = f.Face
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup returns the font Face corresponding to the provided Font descriptor,
|
||||
// with the provided font size set.
|
||||
//
|
||||
// If no matching font Face could be found, the one corresponding to
|
||||
// the default typeface is selected and returned.
|
||||
func (c *Cache) Lookup(fnt Font, size Length) Face {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if len(c.faces) == 0 {
|
||||
return Face{}
|
||||
}
|
||||
|
||||
face := c.lookup(fnt)
|
||||
if face == nil {
|
||||
fnt.Typeface = c.def
|
||||
face = c.lookup(fnt)
|
||||
}
|
||||
|
||||
ff := Face{
|
||||
Font: fnt,
|
||||
Face: face,
|
||||
}
|
||||
ff.Font.Size = size
|
||||
return ff
|
||||
}
|
||||
|
||||
// Has returns whether the cache contains the exact font descriptor.
|
||||
func (c *Cache) Has(fnt Font) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
face := c.lookup(fnt)
|
||||
return face != nil
|
||||
}
|
||||
|
||||
func (c *Cache) lookup(key Font) *opentype.Font {
|
||||
key.Size = 0
|
||||
|
||||
tf := c.faces[key]
|
||||
if tf == nil {
|
||||
key := key
|
||||
key.Weight = font.WeightNormal
|
||||
tf = c.faces[key]
|
||||
}
|
||||
if tf == nil {
|
||||
key := key
|
||||
key.Style = font.StyleNormal
|
||||
tf = c.faces[key]
|
||||
}
|
||||
if tf == nil {
|
||||
key := key
|
||||
key.Style = font.StyleNormal
|
||||
key.Weight = font.WeightNormal
|
||||
tf = c.faces[key]
|
||||
}
|
||||
|
||||
return tf
|
||||
}
|
||||
|
||||
func weightName(w font.Weight) string {
|
||||
switch w {
|
||||
case font.WeightThin:
|
||||
return "Thin"
|
||||
case font.WeightExtraLight:
|
||||
return "ExtraLight"
|
||||
case font.WeightLight:
|
||||
return "Light"
|
||||
case font.WeightNormal:
|
||||
return "Regular"
|
||||
case font.WeightMedium:
|
||||
return "Medium"
|
||||
case font.WeightSemiBold:
|
||||
return "SemiBold"
|
||||
case font.WeightBold:
|
||||
return "Bold"
|
||||
case font.WeightExtraBold:
|
||||
return "ExtraBold"
|
||||
case font.WeightBlack:
|
||||
return "Black"
|
||||
}
|
||||
return fmt.Sprintf("weight(%d)", w)
|
||||
}
|
||||
|
||||
func styleName(sty font.Style) string {
|
||||
switch sty {
|
||||
case font.StyleNormal:
|
||||
return "Normal"
|
||||
case font.StyleItalic:
|
||||
return "Italic"
|
||||
case font.StyleOblique:
|
||||
return "Oblique"
|
||||
}
|
||||
return fmt.Sprintf("style(%d)", sty)
|
||||
}
|
||||
69
vendor/gonum.org/v1/plot/font/len.go
generated
vendored
Normal file
69
vendor/gonum.org/v1/plot/font/len.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright ©2021 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 font
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Length is a unit-independent representation of length.
|
||||
// Internally, the length is stored in postscript points.
|
||||
type Length float64
|
||||
|
||||
// Dots returns the length in dots for the given resolution.
|
||||
func (l Length) Dots(dpi float64) float64 {
|
||||
return float64(l) / Inch.Points() * dpi
|
||||
}
|
||||
|
||||
// Points returns the length in postscript points.
|
||||
func (l Length) Points() float64 {
|
||||
return float64(l)
|
||||
}
|
||||
|
||||
// Common lengths.
|
||||
const (
|
||||
Inch Length = 72
|
||||
Centimeter = Inch / 2.54
|
||||
Millimeter = Centimeter / 10
|
||||
)
|
||||
|
||||
// Points returns a length for the given number of points.
|
||||
func Points(pt float64) Length {
|
||||
return Length(pt)
|
||||
}
|
||||
|
||||
// ParseLength parses a Length string.
|
||||
// A Length string is a possible signed floating number with a unit.
|
||||
// e.g. "42cm" "2.4in" "66pt"
|
||||
// If no unit was given, ParseLength assumes it was (postscript) points.
|
||||
// Currently valid units are:
|
||||
//
|
||||
// - mm (millimeter)
|
||||
// - cm (centimeter)
|
||||
// - in (inch)
|
||||
// - pt (point)
|
||||
func ParseLength(value string) (Length, error) {
|
||||
var unit Length = 1
|
||||
switch {
|
||||
case strings.HasSuffix(value, "in"):
|
||||
value = value[:len(value)-len("in")]
|
||||
unit = Inch
|
||||
case strings.HasSuffix(value, "cm"):
|
||||
value = value[:len(value)-len("cm")]
|
||||
unit = Centimeter
|
||||
case strings.HasSuffix(value, "mm"):
|
||||
value = value[:len(value)-len("mm")]
|
||||
unit = Millimeter
|
||||
case strings.HasSuffix(value, "pt"):
|
||||
value = value[:len(value)-len("pt")]
|
||||
unit = 1
|
||||
}
|
||||
v, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return Length(v) * unit, nil
|
||||
}
|
||||
97
vendor/gonum.org/v1/plot/font/liberation/liberation.go
generated
vendored
Normal file
97
vendor/gonum.org/v1/plot/font/liberation/liberation.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright ©2021 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 liberation exports the Liberation fonts as a font.Collection.
|
||||
package liberation // import "gonum.org/v1/plot/font/liberation"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/go-fonts/liberation/liberationmonobold"
|
||||
"github.com/go-fonts/liberation/liberationmonobolditalic"
|
||||
"github.com/go-fonts/liberation/liberationmonoitalic"
|
||||
"github.com/go-fonts/liberation/liberationmonoregular"
|
||||
"github.com/go-fonts/liberation/liberationsansbold"
|
||||
"github.com/go-fonts/liberation/liberationsansbolditalic"
|
||||
"github.com/go-fonts/liberation/liberationsansitalic"
|
||||
"github.com/go-fonts/liberation/liberationsansregular"
|
||||
"github.com/go-fonts/liberation/liberationserifbold"
|
||||
"github.com/go-fonts/liberation/liberationserifbolditalic"
|
||||
"github.com/go-fonts/liberation/liberationserifitalic"
|
||||
"github.com/go-fonts/liberation/liberationserifregular"
|
||||
stdfnt "golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
collection font.Collection
|
||||
)
|
||||
|
||||
func Collection() font.Collection {
|
||||
once.Do(func() {
|
||||
addColl(font.Font{}, liberationserifregular.TTF)
|
||||
addColl(font.Font{Style: stdfnt.StyleItalic}, liberationserifitalic.TTF)
|
||||
addColl(font.Font{Weight: stdfnt.WeightBold}, liberationserifbold.TTF)
|
||||
addColl(font.Font{
|
||||
Style: stdfnt.StyleItalic,
|
||||
Weight: stdfnt.WeightBold,
|
||||
}, liberationserifbolditalic.TTF)
|
||||
|
||||
// mono variant
|
||||
addColl(font.Font{Variant: "Mono"}, liberationmonoregular.TTF)
|
||||
addColl(font.Font{
|
||||
Variant: "Mono",
|
||||
Style: stdfnt.StyleItalic,
|
||||
}, liberationmonoitalic.TTF)
|
||||
addColl(font.Font{
|
||||
Variant: "Mono",
|
||||
Weight: stdfnt.WeightBold,
|
||||
}, liberationmonobold.TTF)
|
||||
addColl(font.Font{
|
||||
Variant: "Mono",
|
||||
Style: stdfnt.StyleItalic,
|
||||
Weight: stdfnt.WeightBold,
|
||||
}, liberationmonobolditalic.TTF)
|
||||
|
||||
// sans-serif variant
|
||||
addColl(font.Font{Variant: "Sans"}, liberationsansregular.TTF)
|
||||
addColl(font.Font{
|
||||
Variant: "Sans",
|
||||
Style: stdfnt.StyleItalic,
|
||||
}, liberationsansitalic.TTF)
|
||||
addColl(font.Font{
|
||||
Variant: "Sans",
|
||||
Weight: stdfnt.WeightBold,
|
||||
}, liberationsansbold.TTF)
|
||||
addColl(font.Font{
|
||||
Variant: "Sans",
|
||||
Style: stdfnt.StyleItalic,
|
||||
Weight: stdfnt.WeightBold,
|
||||
}, liberationsansbolditalic.TTF)
|
||||
|
||||
n := len(collection)
|
||||
collection = collection[:n:n]
|
||||
})
|
||||
|
||||
return collection
|
||||
}
|
||||
|
||||
func addColl(fnt font.Font, ttf []byte) {
|
||||
face, err := opentype.Parse(ttf)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("vg: could not parse font: %+v", err))
|
||||
}
|
||||
fnt.Typeface = "Liberation"
|
||||
if fnt.Variant == "" {
|
||||
fnt.Variant = "Serif"
|
||||
}
|
||||
collection = append(collection, font.Face{
|
||||
Font: fnt,
|
||||
Face: face,
|
||||
})
|
||||
}
|
||||
274
vendor/gonum.org/v1/plot/labelling.go
generated
vendored
Normal file
274
vendor/gonum.org/v1/plot/labelling.go
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
// Copyright ©2017 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.
|
||||
|
||||
// This is an implementation of the Talbot, Lin and Hanrahan algorithm
|
||||
// described in doi:10.1109/TVCG.2010.130 with reference to the R
|
||||
// implementation in the labeling package, ©2014 Justin Talbot (Licensed
|
||||
// MIT+file LICENSE|Unlimited).
|
||||
|
||||
package plot
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
// dlamchE is the machine epsilon. For IEEE this is 2^{-53}.
|
||||
dlamchE = 1.0 / (1 << 53)
|
||||
|
||||
// dlamchB is the radix of the machine (the base of the number system).
|
||||
dlamchB = 2
|
||||
|
||||
// dlamchP is base * eps.
|
||||
dlamchP = dlamchB * dlamchE
|
||||
)
|
||||
|
||||
const (
|
||||
// free indicates no restriction on label containment.
|
||||
free = iota
|
||||
// containData specifies that all the data range lies
|
||||
// within the interval [label_min, label_max].
|
||||
containData
|
||||
// withinData specifies that all labels lie within the
|
||||
// interval [dMin, dMax].
|
||||
withinData
|
||||
)
|
||||
|
||||
// talbotLinHanrahan returns an optimal set of approximately want label values
|
||||
// for the data range [dMin, dMax], and the step and magnitude of the step between values.
|
||||
// containment is specifies are guarantees for label and data range containment, valid
|
||||
// values are free, containData and withinData.
|
||||
// The optional parameters Q, nice numbers, and w, weights, allow tuning of the
|
||||
// algorithm but by default (when nil) are set to the parameters described in the
|
||||
// paper.
|
||||
// The legibility function allows tuning of the legibility assessment for labels.
|
||||
// By default, when nil, legbility will set the legibility score for each candidate
|
||||
// labelling scheme to 1.
|
||||
// See the paper for an explanation of the function of Q, w and legibility.
|
||||
func talbotLinHanrahan(dMin, dMax float64, want int, containment int, Q []float64, w *weights, legibility func(lMin, lMax, lStep float64) float64) (values []float64, step, q float64, magnitude int) {
|
||||
const eps = dlamchP * 100
|
||||
|
||||
if dMin > dMax {
|
||||
panic("labelling: invalid data range: min greater than max")
|
||||
}
|
||||
|
||||
if Q == nil {
|
||||
Q = []float64{1, 5, 2, 2.5, 4, 3}
|
||||
}
|
||||
if w == nil {
|
||||
w = &weights{
|
||||
simplicity: 0.25,
|
||||
coverage: 0.2,
|
||||
density: 0.5,
|
||||
legibility: 0.05,
|
||||
}
|
||||
}
|
||||
if legibility == nil {
|
||||
legibility = unitLegibility
|
||||
}
|
||||
|
||||
if r := dMax - dMin; r < eps {
|
||||
l := make([]float64, want)
|
||||
step := r / float64(want-1)
|
||||
for i := range l {
|
||||
l[i] = dMin + float64(i)*step
|
||||
}
|
||||
magnitude = minAbsMag(dMin, dMax)
|
||||
return l, step, 0, magnitude
|
||||
}
|
||||
|
||||
type selection struct {
|
||||
// n is the number of labels selected.
|
||||
n int
|
||||
// lMin and lMax are the selected min
|
||||
// and max label values. lq is the q
|
||||
// chosen.
|
||||
lMin, lMax, lStep, lq float64
|
||||
// score is the score for the selection.
|
||||
score float64
|
||||
// magnitude is the magnitude of the
|
||||
// label step distance.
|
||||
magnitude int
|
||||
}
|
||||
best := selection{score: -2}
|
||||
|
||||
outer:
|
||||
for skip := 1; ; skip++ {
|
||||
for _, q := range Q {
|
||||
sm := maxSimplicity(q, Q, skip)
|
||||
if w.score(sm, 1, 1, 1) < best.score {
|
||||
break outer
|
||||
}
|
||||
|
||||
for have := 2; ; have++ {
|
||||
dm := maxDensity(have, want)
|
||||
if w.score(sm, 1, dm, 1) < best.score {
|
||||
break
|
||||
}
|
||||
|
||||
delta := (dMax - dMin) / float64(have+1) / float64(skip) / q
|
||||
|
||||
const maxExp = 309
|
||||
for mag := int(math.Ceil(math.Log10(delta))); mag < maxExp; mag++ {
|
||||
step := float64(skip) * q * math.Pow10(mag)
|
||||
|
||||
cm := maxCoverage(dMin, dMax, step*float64(have-1))
|
||||
if w.score(sm, cm, dm, 1) < best.score {
|
||||
break
|
||||
}
|
||||
|
||||
fracStep := step / float64(skip)
|
||||
kStep := step * float64(have-1)
|
||||
|
||||
minStart := (math.Floor(dMax/step) - float64(have-1)) * float64(skip)
|
||||
maxStart := math.Ceil(dMax/step) * float64(skip)
|
||||
for start := minStart; start <= maxStart && start != start-1; start++ {
|
||||
lMin := start * fracStep
|
||||
lMax := lMin + kStep
|
||||
|
||||
switch containment {
|
||||
case containData:
|
||||
if dMin < lMin || lMax < dMax {
|
||||
continue
|
||||
}
|
||||
case withinData:
|
||||
if lMin < dMin || dMax < lMax {
|
||||
continue
|
||||
}
|
||||
case free:
|
||||
// Free choice.
|
||||
}
|
||||
|
||||
score := w.score(
|
||||
simplicity(q, Q, skip, lMin, lMax, step),
|
||||
coverage(dMin, dMax, lMin, lMax),
|
||||
density(have, want, dMin, dMax, lMin, lMax),
|
||||
legibility(lMin, lMax, step),
|
||||
)
|
||||
if score > best.score {
|
||||
best = selection{
|
||||
n: have,
|
||||
lMin: lMin,
|
||||
lMax: lMax,
|
||||
lStep: float64(skip) * q,
|
||||
lq: q,
|
||||
score: score,
|
||||
magnitude: mag,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if best.score == -2 {
|
||||
l := make([]float64, want)
|
||||
step := (dMax - dMin) / float64(want-1)
|
||||
for i := range l {
|
||||
l[i] = dMin + float64(i)*step
|
||||
}
|
||||
magnitude = minAbsMag(dMin, dMax)
|
||||
return l, step, 0, magnitude
|
||||
}
|
||||
|
||||
l := make([]float64, best.n)
|
||||
step = best.lStep * math.Pow10(best.magnitude)
|
||||
for i := range l {
|
||||
l[i] = best.lMin + float64(i)*step
|
||||
}
|
||||
return l, best.lStep, best.lq, best.magnitude
|
||||
}
|
||||
|
||||
// minAbsMag returns the minumum magnitude of the absolute values of a and b.
|
||||
func minAbsMag(a, b float64) int {
|
||||
return int(math.Min(math.Floor(math.Log10(math.Abs(a))), (math.Floor(math.Log10(math.Abs(b))))))
|
||||
}
|
||||
|
||||
// simplicity returns the simplicity score for how will the curent q, lMin, lMax,
|
||||
// lStep and skip match the given nice numbers, Q.
|
||||
func simplicity(q float64, Q []float64, skip int, lMin, lMax, lStep float64) float64 {
|
||||
const eps = dlamchP * 100
|
||||
|
||||
for i, v := range Q {
|
||||
if v == q {
|
||||
m := math.Mod(lMin, lStep)
|
||||
v = 0
|
||||
if (m < eps || lStep-m < eps) && lMin <= 0 && 0 <= lMax {
|
||||
v = 1
|
||||
}
|
||||
return 1 - float64(i)/(float64(len(Q))-1) - float64(skip) + v
|
||||
}
|
||||
}
|
||||
panic("labelling: invalid q for Q")
|
||||
}
|
||||
|
||||
// maxSimplicity returns the maximum simplicity for q, Q and skip.
|
||||
func maxSimplicity(q float64, Q []float64, skip int) float64 {
|
||||
for i, v := range Q {
|
||||
if v == q {
|
||||
return 1 - float64(i)/(float64(len(Q))-1) - float64(skip) + 1
|
||||
}
|
||||
}
|
||||
panic("labelling: invalid q for Q")
|
||||
}
|
||||
|
||||
// coverage returns the coverage score for based on the average
|
||||
// squared distance between the extreme labels, lMin and lMax, and
|
||||
// the extreme data points, dMin and dMax.
|
||||
func coverage(dMin, dMax, lMin, lMax float64) float64 {
|
||||
r := 0.1 * (dMax - dMin)
|
||||
max := dMax - lMax
|
||||
min := dMin - lMin
|
||||
return 1 - 0.5*(max*max+min*min)/(r*r)
|
||||
}
|
||||
|
||||
// maxCoverage returns the maximum coverage achievable for the data
|
||||
// range.
|
||||
func maxCoverage(dMin, dMax, span float64) float64 {
|
||||
r := dMax - dMin
|
||||
if span <= r {
|
||||
return 1
|
||||
}
|
||||
h := 0.5 * (span - r)
|
||||
r *= 0.1
|
||||
return 1 - (h*h)/(r*r)
|
||||
}
|
||||
|
||||
// density returns the density score which measures the goodness of
|
||||
// the labelling density compared to the user defined target
|
||||
// based on the want parameter given to talbotLinHanrahan.
|
||||
func density(have, want int, dMin, dMax, lMin, lMax float64) float64 {
|
||||
rho := float64(have-1) / (lMax - lMin)
|
||||
rhot := float64(want-1) / (math.Max(lMax, dMax) - math.Min(dMin, lMin))
|
||||
if d := rho / rhot; d >= 1 {
|
||||
return 2 - d
|
||||
}
|
||||
return 2 - rhot/rho
|
||||
}
|
||||
|
||||
// maxDensity returns the maximum density score achievable for have and want.
|
||||
func maxDensity(have, want int) float64 {
|
||||
if have < want {
|
||||
return 1
|
||||
}
|
||||
return 2 - float64(have-1)/float64(want-1)
|
||||
}
|
||||
|
||||
// unitLegibility returns a default legibility score ignoring label
|
||||
// spacing.
|
||||
func unitLegibility(_, _, _ float64) float64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
// weights is a helper type to calcuate the labelling scheme's total score.
|
||||
type weights struct {
|
||||
simplicity, coverage, density, legibility float64
|
||||
}
|
||||
|
||||
// score returns the score for a labelling scheme with simplicity, s,
|
||||
// coverage, c, density, d and legibility l.
|
||||
func (w *weights) score(s, c, d, l float64) float64 {
|
||||
return w.simplicity*s + w.coverage*c + w.density*d + w.legibility*l
|
||||
}
|
||||
189
vendor/gonum.org/v1/plot/legend.go
generated
vendored
Normal file
189
vendor/gonum.org/v1/plot/legend.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// 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 plot
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// A Legend gives a description of the meaning of different
|
||||
// data elements of the plot. Each legend entry has a name
|
||||
// and a thumbnail, where the thumbnail shows a small
|
||||
// sample of the display style of the corresponding data.
|
||||
type Legend struct {
|
||||
// TextStyle is the style given to the legend
|
||||
// entry texts.
|
||||
TextStyle text.Style
|
||||
|
||||
// Padding is the amount of padding to add
|
||||
// between each entry in the legend. If Padding
|
||||
// is zero then entries are spaced based on the
|
||||
// font size.
|
||||
Padding vg.Length
|
||||
|
||||
// Top and Left specify the location of the legend.
|
||||
// If Top is true the legend is located along the top
|
||||
// edge of the plot, otherwise it is located along
|
||||
// the bottom edge. If Left is true then the legend
|
||||
// is located along the left edge of the plot, and the
|
||||
// text is positioned after the icons, otherwise it is
|
||||
// located along the right edge and the text is
|
||||
// positioned before the icons.
|
||||
Top, Left bool
|
||||
|
||||
// XOffs and YOffs are added to the legend's
|
||||
// final position.
|
||||
XOffs, YOffs vg.Length
|
||||
|
||||
// YPosition specifies the vertical position of a legend entry.
|
||||
// Valid values are [-1,+1], with +1 being the top of the
|
||||
// entry vertical space, and -1 the bottom.
|
||||
YPosition float64
|
||||
|
||||
// ThumbnailWidth is the width of legend thumbnails.
|
||||
ThumbnailWidth vg.Length
|
||||
|
||||
// entries are all of the legendEntries described
|
||||
// by this legend.
|
||||
entries []legendEntry
|
||||
}
|
||||
|
||||
// A legendEntry represents a single line of a legend, it
|
||||
// has a name and an icon.
|
||||
type legendEntry struct {
|
||||
// text is the text associated with this entry.
|
||||
text string
|
||||
|
||||
// thumbs is a slice of all of the thumbnails styles
|
||||
thumbs []Thumbnailer
|
||||
}
|
||||
|
||||
// Thumbnailer wraps the Thumbnail method, which
|
||||
// draws the small image in a legend representing the
|
||||
// style of data.
|
||||
type Thumbnailer interface {
|
||||
// Thumbnail draws an thumbnail representing
|
||||
// a legend entry. The thumbnail will usually show
|
||||
// a smaller representation of the style used
|
||||
// to plot the corresponding data.
|
||||
Thumbnail(c *draw.Canvas)
|
||||
}
|
||||
|
||||
// NewLegend returns a legend with the default parameter settings.
|
||||
func NewLegend() Legend {
|
||||
return newLegend(DefaultTextHandler)
|
||||
}
|
||||
|
||||
func newLegend(hdlr text.Handler) Legend {
|
||||
return Legend{
|
||||
YPosition: draw.PosBottom,
|
||||
ThumbnailWidth: vg.Points(20),
|
||||
TextStyle: text.Style{
|
||||
Font: font.From(DefaultFont, 12),
|
||||
Handler: hdlr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws the legend to the given draw.Canvas.
|
||||
func (l *Legend) Draw(c draw.Canvas) {
|
||||
iconx := c.Min.X
|
||||
sty := l.TextStyle
|
||||
em := sty.Rectangle(" ")
|
||||
textx := iconx + l.ThumbnailWidth + em.Max.X
|
||||
if !l.Left {
|
||||
iconx = c.Max.X - l.ThumbnailWidth
|
||||
textx = iconx - em.Max.X
|
||||
sty.XAlign--
|
||||
}
|
||||
textx += l.XOffs
|
||||
iconx += l.XOffs
|
||||
|
||||
descent := sty.FontExtents().Descent
|
||||
enth := l.entryHeight()
|
||||
y := c.Max.Y - enth - descent
|
||||
if !l.Top {
|
||||
y = c.Min.Y + (enth+l.Padding)*(vg.Length(len(l.entries))-1)
|
||||
}
|
||||
y += l.YOffs
|
||||
|
||||
icon := &draw.Canvas{
|
||||
Canvas: c.Canvas,
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: iconx, Y: y},
|
||||
Max: vg.Point{X: iconx + l.ThumbnailWidth, Y: y + enth},
|
||||
},
|
||||
}
|
||||
|
||||
if l.YPosition < draw.PosBottom || draw.PosTop < l.YPosition {
|
||||
panic("plot: invalid vertical offset for the legend's entries")
|
||||
}
|
||||
yoff := vg.Length(l.YPosition-draw.PosBottom) / 2
|
||||
yoff += descent
|
||||
|
||||
for _, e := range l.entries {
|
||||
for _, t := range e.thumbs {
|
||||
t.Thumbnail(icon)
|
||||
}
|
||||
yoffs := (enth - descent - sty.Rectangle(e.text).Max.Y) / 2
|
||||
yoffs += yoff
|
||||
c.FillText(sty, vg.Point{X: textx, Y: icon.Min.Y + yoffs}, e.text)
|
||||
icon.Min.Y -= enth + l.Padding
|
||||
icon.Max.Y -= enth + l.Padding
|
||||
}
|
||||
}
|
||||
|
||||
// Rectangle returns the extent of the Legend.
|
||||
func (l *Legend) Rectangle(c draw.Canvas) vg.Rectangle {
|
||||
var width, height vg.Length
|
||||
sty := l.TextStyle
|
||||
entryHeight := l.entryHeight()
|
||||
for i, e := range l.entries {
|
||||
width = vg.Length(math.Max(float64(width), float64(l.ThumbnailWidth+sty.Rectangle(" "+e.text).Max.X)))
|
||||
height += entryHeight
|
||||
if i != 0 {
|
||||
height += l.Padding
|
||||
}
|
||||
}
|
||||
var r vg.Rectangle
|
||||
if l.Left {
|
||||
r.Max.X = c.Max.X
|
||||
r.Min.X = c.Max.X - width
|
||||
} else {
|
||||
r.Max.X = c.Min.X + width
|
||||
r.Min.X = c.Min.X
|
||||
}
|
||||
if l.Top {
|
||||
r.Max.Y = c.Max.Y
|
||||
r.Min.Y = c.Max.Y - height
|
||||
} else {
|
||||
r.Max.Y = c.Min.Y + height
|
||||
r.Min.Y = c.Min.Y
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// entryHeight returns the height of the tallest legend
|
||||
// entry text.
|
||||
func (l *Legend) entryHeight() (height vg.Length) {
|
||||
for _, e := range l.entries {
|
||||
if h := l.TextStyle.Rectangle(e.text).Max.Y; h > height {
|
||||
height = h
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add adds an entry to the legend with the given name.
|
||||
// The entry's thumbnail is drawn as the composite of all of the
|
||||
// thumbnails.
|
||||
func (l *Legend) Add(name string, thumbs ...Thumbnailer) {
|
||||
l.entries = append(l.entries, legendEntry{text: name, thumbs: thumbs})
|
||||
}
|
||||
116
vendor/gonum.org/v1/plot/palette/hsva.go
generated
vendored
Normal file
116
vendor/gonum.org/v1/plot/palette/hsva.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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.
|
||||
|
||||
// Copyright ©2011-2013 The bíogo 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 palette
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// HSVA represents a Hue/Saturation/Value/Alpha color.
|
||||
// H, S, V and A are valid within [0, 1].
|
||||
type HSVA struct {
|
||||
H, S, V, A float64
|
||||
}
|
||||
|
||||
// HSVAModel converts any color.Color to an HSVA color.
|
||||
var HSVAModel = color.ModelFunc(hsvaModel)
|
||||
|
||||
func hsvaModel(c color.Color) color.Color {
|
||||
if _, ok := c.(HSVA); ok {
|
||||
return c
|
||||
}
|
||||
return rgbaToHsva(c.RGBA())
|
||||
}
|
||||
|
||||
// Convert r, g, b, a to HSVA
|
||||
func rgbaToHsva(r, g, b, a uint32) HSVA {
|
||||
red := float64(r)
|
||||
blue := float64(b)
|
||||
green := float64(g)
|
||||
|
||||
max := math.Max(red, green)
|
||||
max = math.Max(max, blue)
|
||||
min := math.Min(red, green)
|
||||
min = math.Min(min, blue)
|
||||
chroma := max - min
|
||||
|
||||
var hue float64
|
||||
switch {
|
||||
case chroma == 0:
|
||||
// This should really be math.NaN() since we have a 0 length vector,
|
||||
// but 0 seems to be the convention and it may simplify imports in
|
||||
// dependent packages.
|
||||
hue = 0
|
||||
case max == red:
|
||||
hue = math.Mod((green-blue)/chroma, 6)
|
||||
case max == green:
|
||||
hue = (blue-red)/chroma + 2
|
||||
case max == blue:
|
||||
hue = (red-green)/chroma + 4
|
||||
}
|
||||
|
||||
hue /= 6
|
||||
|
||||
var s float64
|
||||
if chroma != 0 {
|
||||
s = chroma / max
|
||||
}
|
||||
|
||||
return HSVA{
|
||||
H: math.Mod(math.Mod(hue, 1)+1, 1),
|
||||
S: s,
|
||||
V: max / math.MaxUint16,
|
||||
A: float64(a) / math.MaxUint16,
|
||||
}
|
||||
}
|
||||
|
||||
// RGBA allows HSVAColor to satisfy the color.Color interface.
|
||||
func (c HSVA) RGBA() (r, g, b, a uint32) {
|
||||
var red, green, blue float64
|
||||
|
||||
a = uint32(math.MaxUint16 * c.A)
|
||||
|
||||
if c.V == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if c.S == 0 {
|
||||
r, g, b = uint32(math.MaxUint16*c.V), uint32(math.MaxUint16*c.V), uint32(math.MaxUint16*c.V)
|
||||
return
|
||||
}
|
||||
|
||||
chroma := c.V * c.S
|
||||
m := c.V - chroma
|
||||
|
||||
if !math.IsNaN(c.H) {
|
||||
hue := math.Mod(c.H, 1) * 6
|
||||
x := chroma * (1 - math.Abs(math.Mod(hue, 2)-1))
|
||||
switch math.Floor(hue) {
|
||||
case 0:
|
||||
red, green = chroma, x
|
||||
case 1:
|
||||
red, green = x, chroma
|
||||
case 2:
|
||||
green, blue = chroma, x
|
||||
case 3:
|
||||
green, blue = x, chroma
|
||||
case 4:
|
||||
red, blue = x, chroma
|
||||
case 5:
|
||||
red, blue = chroma, x
|
||||
}
|
||||
} else {
|
||||
red, green, blue = 0, 0, 0
|
||||
}
|
||||
|
||||
r, g, b = uint32(math.MaxUint16*(red+m)), uint32(math.MaxUint16*(green+m)), uint32(math.MaxUint16*(blue+m))
|
||||
|
||||
return
|
||||
}
|
||||
188
vendor/gonum.org/v1/plot/palette/palette.go
generated
vendored
Normal file
188
vendor/gonum.org/v1/plot/palette/palette.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
// 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.
|
||||
|
||||
// Copyright ©2013 The bíogo 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 palette provides basic color palette handling.
|
||||
package palette // import "gonum.org/v1/plot/palette"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Palette is a collection of colors ordered into a palette.
|
||||
type Palette interface {
|
||||
Colors() []color.Color
|
||||
}
|
||||
|
||||
// DivergingPalette is a collection of colors ordered into a palette with
|
||||
// a critical class or break in the middle of the color range.
|
||||
type DivergingPalette interface {
|
||||
Palette
|
||||
|
||||
// CriticalIndex returns the indices of the lightest
|
||||
// (median) color or colors in the DivergingPalette.
|
||||
// The low and high index values will be equal when
|
||||
// there is a single median color.
|
||||
CriticalIndex() (low, high int)
|
||||
}
|
||||
|
||||
// A ColorMap maps scalar values to colors.
|
||||
type ColorMap interface {
|
||||
// At returns the color associated with the given value.
|
||||
// If the value is not between Max() and Min(), an error is returned.
|
||||
At(float64) (color.Color, error)
|
||||
|
||||
// Max returns the current maximum value of the ColorMap.
|
||||
Max() float64
|
||||
|
||||
// SetMax sets the maximum value of the ColorMap.
|
||||
SetMax(float64)
|
||||
|
||||
// Min returns the current minimum value of the ColorMap.
|
||||
Min() float64
|
||||
|
||||
// SetMin sets the minimum value of the ColorMap.
|
||||
SetMin(float64)
|
||||
|
||||
// Alpha returns the opacity value of the ColorMap.
|
||||
Alpha() float64
|
||||
|
||||
// SetAlpha sets the opacity value of the ColorMap. Zero is transparent
|
||||
// and one is completely opaque. The default value of alpha should be
|
||||
// expected to be one. The function should be expected to panic
|
||||
// if alpha is not between zero and one.
|
||||
SetAlpha(float64)
|
||||
|
||||
// Palette creates a Palette with the specified number of colors
|
||||
// from the ColorMap.
|
||||
Palette(colors int) Palette
|
||||
}
|
||||
|
||||
// DivergingColorMap maps scalar values to colors that diverge
|
||||
// from a central value.
|
||||
type DivergingColorMap interface {
|
||||
ColorMap
|
||||
|
||||
// SetConvergePoint sets the value where the diverging colors
|
||||
// should meet. The default value should be expected to be
|
||||
// (Min() + Max()) / 2. It should be expected that calling either
|
||||
// SetMax() or SetMin() will set a new default value, so for a
|
||||
// custom convergence point this function should be called after
|
||||
// SetMax() and SetMin(). The function should be expected to panic
|
||||
// if the value is not between Min() and Max().
|
||||
SetConvergePoint(float64)
|
||||
|
||||
// ConvergePoint returns the value where the diverging colors meet.
|
||||
ConvergePoint() float64
|
||||
}
|
||||
|
||||
// Hue represents a hue in HSV color space. Valid Hues are within [0, 1].
|
||||
type Hue float64
|
||||
|
||||
const (
|
||||
Red Hue = Hue(iota) / 6
|
||||
Yellow
|
||||
Green
|
||||
Cyan
|
||||
Blue
|
||||
Magenta
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrOverflow is the error returned by ColorMaps when the specified
|
||||
// value is greater than the maximum value.
|
||||
ErrOverflow = errors.New("palette: specified value > maximum")
|
||||
|
||||
// ErrUnderflow is the error returned by ColorMaps when the specified
|
||||
// value is less than the minimum value.
|
||||
ErrUnderflow = errors.New("palette: specified value < minimum")
|
||||
|
||||
// ErrNaN is the error returned by ColorMaps when the specified
|
||||
// value is NaN.
|
||||
ErrNaN = errors.New("palette: specified value == NaN")
|
||||
)
|
||||
|
||||
// Complement returns the complementary hue of a Hue.
|
||||
func (h Hue) Complement() Hue { return Hue(math.Mod(float64(h+0.5), 1)) }
|
||||
|
||||
type palette []color.Color
|
||||
|
||||
func (p palette) Colors() []color.Color { return p }
|
||||
|
||||
type divergingPalette []color.Color
|
||||
|
||||
func (p divergingPalette) Colors() []color.Color { return p }
|
||||
|
||||
func (d divergingPalette) CriticalIndex() (low, high int) {
|
||||
l := len(d)
|
||||
return (l - 1) / 2, l / 2
|
||||
}
|
||||
|
||||
// Rainbow returns a rainbow palette with the specified number of colors, saturation
|
||||
// value and alpha, and hues in the specified range.
|
||||
func Rainbow(colors int, start, end Hue, sat, val, alpha float64) Palette {
|
||||
p := make(palette, colors)
|
||||
hd := float64(end-start) / float64(colors-1)
|
||||
c := HSVA{V: val, S: sat, A: alpha}
|
||||
for i := range p {
|
||||
c.H = float64(start) + float64(i)*hd
|
||||
p[i] = color.NRGBAModel.Convert(c)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Heat returns a red to yellow palette with the specified number of colors and alpha.
|
||||
func Heat(colors int, alpha float64) Palette {
|
||||
p := make(palette, colors)
|
||||
j := colors / 4
|
||||
i := colors - j
|
||||
|
||||
hd := float64(Yellow-Red) / float64(i-1)
|
||||
c := HSVA{V: 1, S: 1, A: alpha}
|
||||
for k := range p[:i] {
|
||||
c.H = float64(Red) + float64(k)*hd
|
||||
p[k] = color.NRGBAModel.Convert(c)
|
||||
}
|
||||
if j == 0 {
|
||||
return p
|
||||
}
|
||||
|
||||
c.H = float64(Yellow)
|
||||
start, end := 1-1/(2*float64(j)), 1/(2*float64(j))
|
||||
c.S = start
|
||||
sd := (end - start) / float64(j-1)
|
||||
for k := range p[i:] {
|
||||
c.S = start + float64(k)*sd
|
||||
p[k+i] = color.NRGBAModel.Convert(c)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Radial return a diverging palette across the specified range, through white and with
|
||||
// the specified alpha.
|
||||
func Radial(colors int, start, end Hue, alpha float64) DivergingPalette {
|
||||
p := make(divergingPalette, colors)
|
||||
h := colors / 2
|
||||
c := HSVA{V: 1, A: alpha}
|
||||
ds := 0.5 / float64(h)
|
||||
for i := range p[:h] {
|
||||
c.H = float64(start)
|
||||
c.S = 0.5 - float64(i)*ds
|
||||
p[i] = color.NRGBAModel.Convert(c)
|
||||
c.H = float64(end)
|
||||
p[len(p)-1-i] = color.NRGBAModel.Convert(c)
|
||||
}
|
||||
if colors%2 != 0 {
|
||||
p[colors/2] = color.NRGBA{0xff, 0xff, 0xff, byte(math.MaxUint8 * alpha)}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
35
vendor/gonum.org/v1/plot/palette/reverse.go
generated
vendored
Normal file
35
vendor/gonum.org/v1/plot/palette/reverse.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright ©2017 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 palette
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// Reverse reverses the direction of ColorMap c.
|
||||
func Reverse(c ColorMap) ColorMap {
|
||||
return reverse{ColorMap: c}
|
||||
}
|
||||
|
||||
// reverse is a ColorMap that reverses the direction of the ColorMap it
|
||||
// contains.
|
||||
type reverse struct {
|
||||
ColorMap
|
||||
}
|
||||
|
||||
// At implements the ColorMap interface for a Reversed ColorMap.
|
||||
func (r reverse) At(v float64) (color.Color, error) {
|
||||
return r.ColorMap.At(r.Max() - (v - r.Min()))
|
||||
}
|
||||
|
||||
// Palette implements the ColorMap interface for a Reversed ColorMap.
|
||||
func (r reverse) Palette(colors int) Palette {
|
||||
c := r.ColorMap.Palette(colors).Colors()
|
||||
c2 := make([]color.Color, len(c))
|
||||
for i, j := 0, len(c)-1; i < j; i, j = i+1, j-1 {
|
||||
c2[i], c2[j] = c[j], c[i]
|
||||
}
|
||||
return palette(c2)
|
||||
}
|
||||
555
vendor/gonum.org/v1/plot/plot.go
generated
vendored
Normal file
555
vendor/gonum.org/v1/plot/plot.go
generated
vendored
Normal file
@@ -0,0 +1,555 @@
|
||||
// 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 plot
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/font/liberation"
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultFont is the name of the default font for plot text.
|
||||
DefaultFont = font.Font{
|
||||
Typeface: "Liberation",
|
||||
Variant: "Serif",
|
||||
}
|
||||
|
||||
// DefaultTextHandler is the default text handler used for text processing.
|
||||
DefaultTextHandler text.Handler
|
||||
)
|
||||
|
||||
// Plot is the basic type representing a plot.
|
||||
type Plot struct {
|
||||
Title struct {
|
||||
// Text is the text of the plot title. If
|
||||
// Text is the empty string then the plot
|
||||
// will not have a title.
|
||||
Text string
|
||||
|
||||
// Padding is the amount of padding
|
||||
// between the bottom of the title and
|
||||
// the top of the plot.
|
||||
Padding vg.Length
|
||||
|
||||
// TextStyle specifies how the plot title text should be displayed.
|
||||
TextStyle text.Style
|
||||
}
|
||||
|
||||
// BackgroundColor is the background color of the plot.
|
||||
// The default is White.
|
||||
BackgroundColor color.Color
|
||||
|
||||
// X and Y are the horizontal and vertical axes
|
||||
// of the plot respectively.
|
||||
X, Y Axis
|
||||
|
||||
// Legend is the plot's legend.
|
||||
Legend Legend
|
||||
|
||||
// TextHandler parses and formats text according to a given
|
||||
// dialect (Markdown, LaTeX, plain, ...)
|
||||
// The default is a plain text handler.
|
||||
TextHandler text.Handler
|
||||
|
||||
// plotters are drawn by calling their Plot method
|
||||
// after the axes are drawn.
|
||||
plotters []Plotter
|
||||
}
|
||||
|
||||
// Plotter is an interface that wraps the Plot method.
|
||||
// Some standard implementations of Plotter can be
|
||||
// found in the gonum.org/v1/plot/plotter
|
||||
// package, documented here:
|
||||
// https://godoc.org/gonum.org/v1/plot/plotter
|
||||
type Plotter interface {
|
||||
// Plot draws the data to a draw.Canvas.
|
||||
Plot(draw.Canvas, *Plot)
|
||||
}
|
||||
|
||||
// DataRanger wraps the DataRange method.
|
||||
type DataRanger interface {
|
||||
// DataRange returns the range of X and Y values.
|
||||
DataRange() (xmin, xmax, ymin, ymax float64)
|
||||
}
|
||||
|
||||
// orientation describes whether an axis is horizontal or vertical.
|
||||
type orientation byte
|
||||
|
||||
const (
|
||||
horizontal orientation = iota
|
||||
vertical
|
||||
)
|
||||
|
||||
// New returns a new plot with some reasonable default settings.
|
||||
func New() *Plot {
|
||||
hdlr := DefaultTextHandler
|
||||
p := &Plot{
|
||||
BackgroundColor: color.White,
|
||||
X: makeAxis(horizontal),
|
||||
Y: makeAxis(vertical),
|
||||
Legend: newLegend(hdlr),
|
||||
TextHandler: hdlr,
|
||||
}
|
||||
p.Title.TextStyle = text.Style{
|
||||
Color: color.Black,
|
||||
Font: font.From(DefaultFont, 12),
|
||||
XAlign: draw.XCenter,
|
||||
YAlign: draw.YTop,
|
||||
Handler: hdlr,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Add adds a Plotters to the plot.
|
||||
//
|
||||
// If the plotters implements DataRanger then the
|
||||
// minimum and maximum values of the X and Y
|
||||
// axes are changed if necessary to fit the range of
|
||||
// the data.
|
||||
//
|
||||
// When drawing the plot, Plotters are drawn in the
|
||||
// order in which they were added to the plot.
|
||||
func (p *Plot) Add(ps ...Plotter) {
|
||||
for _, d := range ps {
|
||||
if x, ok := d.(DataRanger); ok {
|
||||
xmin, xmax, ymin, ymax := x.DataRange()
|
||||
p.X.Min = math.Min(p.X.Min, xmin)
|
||||
p.X.Max = math.Max(p.X.Max, xmax)
|
||||
p.Y.Min = math.Min(p.Y.Min, ymin)
|
||||
p.Y.Max = math.Max(p.Y.Max, ymax)
|
||||
}
|
||||
}
|
||||
|
||||
p.plotters = append(p.plotters, ps...)
|
||||
}
|
||||
|
||||
// Draw draws a plot to a draw.Canvas.
|
||||
//
|
||||
// Plotters are drawn in the order in which they were
|
||||
// added to the plot. Plotters that implement the
|
||||
// GlyphBoxer interface will have their GlyphBoxes
|
||||
// taken into account when padding the plot so that
|
||||
// none of their glyphs are clipped.
|
||||
func (p *Plot) Draw(c draw.Canvas) {
|
||||
if p.BackgroundColor != nil {
|
||||
c.SetColor(p.BackgroundColor)
|
||||
c.Fill(c.Rectangle.Path())
|
||||
}
|
||||
|
||||
if p.Title.Text != "" {
|
||||
descent := p.Title.TextStyle.FontExtents().Descent
|
||||
c.FillText(p.Title.TextStyle, vg.Point{X: c.Center().X, Y: c.Max.Y + descent}, p.Title.Text)
|
||||
|
||||
rect := p.Title.TextStyle.Rectangle(p.Title.Text)
|
||||
c.Max.Y -= rect.Size().Y
|
||||
c.Max.Y -= p.Title.Padding
|
||||
}
|
||||
|
||||
p.X.sanitizeRange()
|
||||
x := horizontalAxis{p.X}
|
||||
p.Y.sanitizeRange()
|
||||
y := verticalAxis{p.Y}
|
||||
|
||||
ywidth := y.size()
|
||||
|
||||
xheight := x.size()
|
||||
x.draw(padX(p, draw.Crop(c, ywidth, 0, 0, 0)))
|
||||
y.draw(padY(p, draw.Crop(c, 0, 0, xheight, 0)))
|
||||
|
||||
dataC := padY(p, padX(p, draw.Crop(c, ywidth, 0, xheight, 0)))
|
||||
for _, data := range p.plotters {
|
||||
data.Plot(dataC, p)
|
||||
}
|
||||
|
||||
p.Legend.Draw(draw.Crop(c, ywidth, 0, xheight, 0))
|
||||
}
|
||||
|
||||
// DataCanvas returns a new draw.Canvas that
|
||||
// is the subset of the given draw area into which
|
||||
// the plot data will be drawn.
|
||||
func (p *Plot) DataCanvas(da draw.Canvas) draw.Canvas {
|
||||
if p.Title.Text != "" {
|
||||
rect := p.Title.TextStyle.Rectangle(p.Title.Text)
|
||||
da.Max.Y -= rect.Size().Y
|
||||
da.Max.Y -= p.Title.Padding
|
||||
}
|
||||
p.X.sanitizeRange()
|
||||
x := horizontalAxis{p.X}
|
||||
p.Y.sanitizeRange()
|
||||
y := verticalAxis{p.Y}
|
||||
return padY(p, padX(p, draw.Crop(da, y.size(), 0, x.size(), 0)))
|
||||
}
|
||||
|
||||
// DrawGlyphBoxes draws red outlines around the plot's
|
||||
// GlyphBoxes. This is intended for debugging.
|
||||
func (p *Plot) DrawGlyphBoxes(c draw.Canvas) {
|
||||
dac := p.DataCanvas(c)
|
||||
sty := draw.LineStyle{
|
||||
Color: color.RGBA{R: 255, A: 255},
|
||||
Width: vg.Points(0.5),
|
||||
}
|
||||
|
||||
drawBox := func(c draw.Canvas, b GlyphBox) {
|
||||
x := c.X(b.X) + b.Rectangle.Min.X
|
||||
y := c.Y(b.Y) + b.Rectangle.Min.Y
|
||||
c.StrokeLines(sty, []vg.Point{
|
||||
{X: x, Y: y},
|
||||
{X: x + b.Rectangle.Size().X, Y: y},
|
||||
{X: x + b.Rectangle.Size().X, Y: y + b.Rectangle.Size().Y},
|
||||
{X: x, Y: y + b.Rectangle.Size().Y},
|
||||
{X: x, Y: y},
|
||||
})
|
||||
}
|
||||
|
||||
var title vg.Length
|
||||
if p.Title.Text != "" {
|
||||
rect := p.Title.TextStyle.Rectangle(p.Title.Text)
|
||||
title += rect.Size().Y
|
||||
title += p.Title.Padding
|
||||
box := GlyphBox{
|
||||
Rectangle: rect.Add(vg.Point{
|
||||
X: c.Center().X,
|
||||
Y: c.Max.Y,
|
||||
}),
|
||||
}
|
||||
drawBox(c, box)
|
||||
}
|
||||
|
||||
for _, b := range p.GlyphBoxes(p) {
|
||||
drawBox(dac, b)
|
||||
}
|
||||
|
||||
p.X.sanitizeRange()
|
||||
p.Y.sanitizeRange()
|
||||
|
||||
x := horizontalAxis{p.X}
|
||||
y := verticalAxis{p.Y}
|
||||
|
||||
ywidth := y.size()
|
||||
xheight := x.size()
|
||||
|
||||
cx := padX(p, draw.Crop(c, ywidth, 0, 0, 0))
|
||||
for _, b := range x.GlyphBoxes(p) {
|
||||
drawBox(cx, b)
|
||||
}
|
||||
|
||||
cy := padY(p, draw.Crop(c, 0, 0, xheight, 0))
|
||||
cy.Max.Y -= title
|
||||
for _, b := range y.GlyphBoxes(p) {
|
||||
drawBox(cy, b)
|
||||
}
|
||||
}
|
||||
|
||||
// padX returns a draw.Canvas that is padded horizontally
|
||||
// so that glyphs will no be clipped.
|
||||
func padX(p *Plot, c draw.Canvas) draw.Canvas {
|
||||
glyphs := p.GlyphBoxes(p)
|
||||
l := leftMost(&c, glyphs)
|
||||
xAxis := horizontalAxis{p.X}
|
||||
glyphs = append(glyphs, xAxis.GlyphBoxes(p)...)
|
||||
r := rightMost(&c, glyphs)
|
||||
|
||||
minx := c.Min.X - l.Min.X
|
||||
maxx := c.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 draw.Canvas{
|
||||
Canvas: vg.Canvas(c),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: n, Y: c.Min.Y},
|
||||
Max: vg.Point{X: m, Y: c.Max.Y},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// rightMost returns the right-most GlyphBox.
|
||||
func rightMost(c *draw.Canvas, boxes []GlyphBox) GlyphBox {
|
||||
maxx := c.Max.X
|
||||
r := GlyphBox{X: 1}
|
||||
for _, b := range boxes {
|
||||
if b.Size().X <= 0 {
|
||||
continue
|
||||
}
|
||||
if x := c.X(b.X) + b.Min.X + b.Size().X; x > maxx && b.X <= 1 {
|
||||
maxx = x
|
||||
r = b
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// leftMost returns the left-most GlyphBox.
|
||||
func leftMost(c *draw.Canvas, boxes []GlyphBox) GlyphBox {
|
||||
minx := c.Min.X
|
||||
l := GlyphBox{}
|
||||
for _, b := range boxes {
|
||||
if b.Size().X <= 0 {
|
||||
continue
|
||||
}
|
||||
if x := c.X(b.X) + b.Min.X; x < minx && b.X >= 0 {
|
||||
minx = x
|
||||
l = b
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// padY returns a draw.Canvas that is padded vertically
|
||||
// so that glyphs will no be clipped.
|
||||
func padY(p *Plot, c draw.Canvas) draw.Canvas {
|
||||
glyphs := p.GlyphBoxes(p)
|
||||
b := bottomMost(&c, glyphs)
|
||||
yAxis := verticalAxis{p.Y}
|
||||
glyphs = append(glyphs, yAxis.GlyphBoxes(p)...)
|
||||
t := topMost(&c, glyphs)
|
||||
|
||||
miny := c.Min.Y - b.Min.Y
|
||||
maxy := c.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 draw.Canvas{
|
||||
Canvas: vg.Canvas(c),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{Y: n, X: c.Min.X},
|
||||
Max: vg.Point{Y: m, X: c.Max.X},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// topMost returns the top-most GlyphBox.
|
||||
func topMost(c *draw.Canvas, boxes []GlyphBox) GlyphBox {
|
||||
maxy := c.Max.Y
|
||||
t := GlyphBox{Y: 1}
|
||||
for _, b := range boxes {
|
||||
if b.Size().Y <= 0 {
|
||||
continue
|
||||
}
|
||||
if y := c.Y(b.Y) + b.Min.Y + b.Size().Y; y > maxy && b.Y <= 1 {
|
||||
maxy = y
|
||||
t = b
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// bottomMost returns the bottom-most GlyphBox.
|
||||
func bottomMost(c *draw.Canvas, boxes []GlyphBox) GlyphBox {
|
||||
miny := c.Min.Y
|
||||
l := GlyphBox{}
|
||||
for _, b := range boxes {
|
||||
if b.Size().Y <= 0 {
|
||||
continue
|
||||
}
|
||||
if y := c.Y(b.Y) + b.Min.Y; y < miny && b.Y >= 0 {
|
||||
miny = y
|
||||
l = b
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Transforms returns functions to transfrom
|
||||
// from the x and y data coordinate system to
|
||||
// the draw coordinate system of the given
|
||||
// draw area.
|
||||
func (p *Plot) Transforms(c *draw.Canvas) (x, y func(float64) vg.Length) {
|
||||
x = func(x float64) vg.Length { return c.X(p.X.Norm(x)) }
|
||||
y = func(y float64) vg.Length { return c.Y(p.Y.Norm(y)) }
|
||||
return
|
||||
}
|
||||
|
||||
// GlyphBoxer wraps the GlyphBoxes method.
|
||||
// It should be implemented by things that meet
|
||||
// the Plotter interface that draw glyphs so that
|
||||
// their glyphs are not clipped if drawn near the
|
||||
// edge of the draw.Canvas.
|
||||
//
|
||||
// When computing padding, the plot ignores
|
||||
// GlyphBoxes as follows:
|
||||
// If the Size.X > 0 and the X value is not in range
|
||||
// of the X axis then the box is ignored.
|
||||
// If Size.Y > 0 and the Y value is not in range of
|
||||
// the Y axis then the box is ignored.
|
||||
//
|
||||
// Also, GlyphBoxes with Size.X <= 0 are ignored
|
||||
// when computing horizontal padding and
|
||||
// GlyphBoxes with Size.Y <= 0 are ignored when
|
||||
// computing vertical padding. This is useful
|
||||
// for things like box plots and bar charts where
|
||||
// the boxes and bars are considered to be glyphs
|
||||
// in the X direction (and thus need padding), but
|
||||
// may be clipped in the Y direction (and do not
|
||||
// need padding).
|
||||
type GlyphBoxer interface {
|
||||
GlyphBoxes(*Plot) []GlyphBox
|
||||
}
|
||||
|
||||
// A GlyphBox describes the location of a glyph
|
||||
// and the offset/size of its bounding box.
|
||||
//
|
||||
// If the Rectangle.Size().X is non-positive (<= 0) then
|
||||
// the GlyphBox is ignored when computing the
|
||||
// horizontal padding, and likewise with
|
||||
// Rectangle.Size().Y and the vertical padding.
|
||||
type GlyphBox struct {
|
||||
// The glyph location in normalized coordinates.
|
||||
X, Y float64
|
||||
|
||||
// Rectangle is the offset of the glyph's minimum drawing
|
||||
// point relative to the glyph location and its size.
|
||||
vg.Rectangle
|
||||
}
|
||||
|
||||
// GlyphBoxes returns the GlyphBoxes for all plot
|
||||
// data that meet the GlyphBoxer interface.
|
||||
func (p *Plot) GlyphBoxes(*Plot) (boxes []GlyphBox) {
|
||||
for _, d := range p.plotters {
|
||||
gb, ok := d.(GlyphBoxer)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, b := range gb.GlyphBoxes(p) {
|
||||
if b.Size().X > 0 && (b.X < 0 || b.X > 1) {
|
||||
continue
|
||||
}
|
||||
if b.Size().Y > 0 && (b.Y < 0 || b.Y > 1) {
|
||||
continue
|
||||
}
|
||||
boxes = append(boxes, b)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NominalX configures the plot to have a nominal X
|
||||
// axis—an X axis with names instead of numbers. The
|
||||
// X location corresponding to each name are the integers,
|
||||
// e.g., the x value 0 is centered above the first name and
|
||||
// 1 is above the second name, etc. Labels for x values
|
||||
// that do not end up in range of the X axis will not have
|
||||
// tick marks.
|
||||
func (p *Plot) NominalX(names ...string) {
|
||||
p.X.Tick.Width = 0
|
||||
p.X.Tick.Length = 0
|
||||
p.X.Width = 0
|
||||
p.Y.Padding = p.X.Tick.Label.Width(names[0]) / 2
|
||||
ticks := make([]Tick, len(names))
|
||||
for i, name := range names {
|
||||
ticks[i] = Tick{float64(i), name}
|
||||
}
|
||||
p.X.Tick.Marker = ConstantTicks(ticks)
|
||||
}
|
||||
|
||||
// HideX configures the X axis so that it will not be drawn.
|
||||
func (p *Plot) HideX() {
|
||||
p.X.Tick.Length = 0
|
||||
p.X.Width = 0
|
||||
p.X.Tick.Marker = ConstantTicks([]Tick{})
|
||||
}
|
||||
|
||||
// HideY configures the Y axis so that it will not be drawn.
|
||||
func (p *Plot) HideY() {
|
||||
p.Y.Tick.Length = 0
|
||||
p.Y.Width = 0
|
||||
p.Y.Tick.Marker = ConstantTicks([]Tick{})
|
||||
}
|
||||
|
||||
// HideAxes hides the X and Y axes.
|
||||
func (p *Plot) HideAxes() {
|
||||
p.HideX()
|
||||
p.HideY()
|
||||
}
|
||||
|
||||
// NominalY is like NominalX, but for the Y axis.
|
||||
func (p *Plot) NominalY(names ...string) {
|
||||
p.Y.Tick.Width = 0
|
||||
p.Y.Tick.Length = 0
|
||||
p.Y.Width = 0
|
||||
p.X.Padding = p.Y.Tick.Label.Height(names[0]) / 2
|
||||
ticks := make([]Tick, len(names))
|
||||
for i, name := range names {
|
||||
ticks[i] = Tick{float64(i), name}
|
||||
}
|
||||
p.Y.Tick.Marker = ConstantTicks(ticks)
|
||||
}
|
||||
|
||||
// WriterTo returns an io.WriterTo that will write the plot as
|
||||
// the specified image format.
|
||||
//
|
||||
// Supported formats are:
|
||||
//
|
||||
// - .eps
|
||||
// - .jpg|.jpeg
|
||||
// - .pdf
|
||||
// - .png
|
||||
// - .svg
|
||||
// - .tex
|
||||
// - .tif|.tiff
|
||||
func (p *Plot) WriterTo(w, h vg.Length, format string) (io.WriterTo, error) {
|
||||
c, err := draw.NewFormattedCanvas(w, h, format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Draw(draw.New(c))
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Save saves the plot to an image file. The file format is determined
|
||||
// by the extension.
|
||||
//
|
||||
// Supported extensions are:
|
||||
//
|
||||
// - .eps
|
||||
// - .jpg|.jpeg
|
||||
// - .pdf
|
||||
// - .png
|
||||
// - .svg
|
||||
// - .tex
|
||||
// - .tif|.tiff
|
||||
func (p *Plot) Save(w, h vg.Length, file string) (err error) {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
e := f.Close()
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
|
||||
format := strings.ToLower(filepath.Ext(file))
|
||||
if len(format) != 0 {
|
||||
format = format[1:]
|
||||
}
|
||||
c, err := p.WriterTo(w, h, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.WriteTo(f)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
font.DefaultCache.Add(liberation.Collection())
|
||||
DefaultTextHandler = text.Plain{
|
||||
Fonts: font.DefaultCache,
|
||||
}
|
||||
}
|
||||
211
vendor/gonum.org/v1/plot/plotter/barchart.go
generated
vendored
Normal file
211
vendor/gonum.org/v1/plot/plotter/barchart.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
// 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"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// A BarChart presents grouped data with rectangular bars
|
||||
// with lengths proportional to the data values.
|
||||
type BarChart struct {
|
||||
Values
|
||||
|
||||
// Width is the width of the bars.
|
||||
Width vg.Length
|
||||
|
||||
// Color is the fill color of the bars.
|
||||
Color color.Color
|
||||
|
||||
// LineStyle is the style of the outline of the bars.
|
||||
draw.LineStyle
|
||||
|
||||
// Offset is added to the X location of each bar.
|
||||
// When the Offset is zero, the bars are drawn
|
||||
// centered at their X location.
|
||||
Offset vg.Length
|
||||
|
||||
// XMin is the X location of the first bar. XMin
|
||||
// can be changed to move groups of bars
|
||||
// down the X axis in order to make grouped
|
||||
// bar charts.
|
||||
XMin float64
|
||||
|
||||
// Horizontal dictates whether the bars should be in the vertical
|
||||
// (default) or horizontal direction. If Horizontal is true, all
|
||||
// X locations and distances referred to here will actually be Y
|
||||
// locations and distances.
|
||||
Horizontal bool
|
||||
|
||||
// stackedOn is the bar chart upon which
|
||||
// this bar chart is stacked.
|
||||
stackedOn *BarChart
|
||||
}
|
||||
|
||||
// NewBarChart returns a new bar chart with a single bar for each value.
|
||||
// The bars heights correspond to the values and their x locations correspond
|
||||
// to the index of their value in the Valuer.
|
||||
func NewBarChart(vs Valuer, width vg.Length) (*BarChart, error) {
|
||||
if width <= 0 {
|
||||
return nil, errors.New("plotter: width parameter was not positive")
|
||||
}
|
||||
values, err := CopyValues(vs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BarChart{
|
||||
Values: values,
|
||||
Width: width,
|
||||
Color: color.Black,
|
||||
LineStyle: DefaultLineStyle,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BarHeight returns the maximum y value of the
|
||||
// ith bar, taking into account any bars upon
|
||||
// which it is stacked.
|
||||
func (b *BarChart) BarHeight(i int) float64 {
|
||||
ht := 0.0
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
if i >= 0 && i < len(b.Values) {
|
||||
ht += b.Values[i]
|
||||
}
|
||||
if b.stackedOn != nil {
|
||||
ht += b.stackedOn.BarHeight(i)
|
||||
}
|
||||
return ht
|
||||
}
|
||||
|
||||
// StackOn stacks a bar chart on top of another,
|
||||
// and sets the XMin and Offset to that of the
|
||||
// chart upon which it is being stacked.
|
||||
func (b *BarChart) StackOn(on *BarChart) {
|
||||
b.XMin = on.XMin
|
||||
b.Offset = on.Offset
|
||||
b.stackedOn = on
|
||||
}
|
||||
|
||||
// Plot implements the plot.Plotter interface.
|
||||
func (b *BarChart) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trCat, trVal := plt.Transforms(&c)
|
||||
if b.Horizontal {
|
||||
trCat, trVal = trVal, trCat
|
||||
}
|
||||
|
||||
for i, ht := range b.Values {
|
||||
catVal := b.XMin + float64(i)
|
||||
catMin := trCat(float64(catVal))
|
||||
if !b.Horizontal {
|
||||
if !c.ContainsX(catMin) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if !c.ContainsY(catMin) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
catMin = catMin - b.Width/2 + b.Offset
|
||||
catMax := catMin + b.Width
|
||||
bottom := b.stackedOn.BarHeight(i)
|
||||
valMin := trVal(bottom)
|
||||
valMax := trVal(bottom + ht)
|
||||
|
||||
var pts []vg.Point
|
||||
var poly []vg.Point
|
||||
if !b.Horizontal {
|
||||
pts = []vg.Point{
|
||||
{X: catMin, Y: valMin},
|
||||
{X: catMin, Y: valMax},
|
||||
{X: catMax, Y: valMax},
|
||||
{X: catMax, Y: valMin},
|
||||
}
|
||||
poly = c.ClipPolygonY(pts)
|
||||
} else {
|
||||
pts = []vg.Point{
|
||||
{X: valMin, Y: catMin},
|
||||
{X: valMin, Y: catMax},
|
||||
{X: valMax, Y: catMax},
|
||||
{X: valMax, Y: catMin},
|
||||
}
|
||||
poly = c.ClipPolygonX(pts)
|
||||
}
|
||||
c.FillPolygon(b.Color, poly)
|
||||
|
||||
var outline [][]vg.Point
|
||||
if !b.Horizontal {
|
||||
pts = append(pts, vg.Point{X: catMin, Y: valMin})
|
||||
outline = c.ClipLinesY(pts)
|
||||
} else {
|
||||
pts = append(pts, vg.Point{X: valMin, Y: catMin})
|
||||
outline = c.ClipLinesX(pts)
|
||||
}
|
||||
c.StrokeLines(b.LineStyle, outline...)
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange implements the plot.DataRanger interface.
|
||||
func (b *BarChart) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
catMin := b.XMin
|
||||
catMax := catMin + float64(len(b.Values)-1)
|
||||
|
||||
valMin := math.Inf(1)
|
||||
valMax := math.Inf(-1)
|
||||
for i, val := range b.Values {
|
||||
valBot := b.stackedOn.BarHeight(i)
|
||||
valTop := valBot + val
|
||||
valMin = math.Min(valMin, math.Min(valBot, valTop))
|
||||
valMax = math.Max(valMax, math.Max(valBot, valTop))
|
||||
}
|
||||
if !b.Horizontal {
|
||||
return catMin, catMax, valMin, valMax
|
||||
}
|
||||
return valMin, valMax, catMin, catMax
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the GlyphBoxer interface.
|
||||
func (b *BarChart) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
boxes := make([]plot.GlyphBox, len(b.Values))
|
||||
for i := range b.Values {
|
||||
cat := b.XMin + float64(i)
|
||||
if !b.Horizontal {
|
||||
boxes[i].X = plt.X.Norm(cat)
|
||||
boxes[i].Rectangle = vg.Rectangle{
|
||||
Min: vg.Point{X: b.Offset - b.Width/2},
|
||||
Max: vg.Point{X: b.Offset + b.Width/2},
|
||||
}
|
||||
} else {
|
||||
boxes[i].Y = plt.Y.Norm(cat)
|
||||
boxes[i].Rectangle = vg.Rectangle{
|
||||
Min: vg.Point{Y: b.Offset - b.Width/2},
|
||||
Max: vg.Point{Y: b.Offset + b.Width/2},
|
||||
}
|
||||
}
|
||||
}
|
||||
return boxes
|
||||
}
|
||||
|
||||
// Thumbnail fulfills the plot.Thumbnailer interface.
|
||||
func (b *BarChart) Thumbnail(c *draw.Canvas) {
|
||||
pts := []vg.Point{
|
||||
{X: c.Min.X, Y: c.Min.Y},
|
||||
{X: c.Min.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Min.Y},
|
||||
}
|
||||
poly := c.ClipPolygonY(pts)
|
||||
c.FillPolygon(b.Color, poly)
|
||||
|
||||
pts = append(pts, vg.Point{X: c.Min.X, Y: c.Min.Y})
|
||||
outline := c.ClipLinesY(pts)
|
||||
c.StrokeLines(b.LineStyle, outline...)
|
||||
}
|
||||
457
vendor/gonum.org/v1/plot/plotter/boxplot.go
generated
vendored
Normal file
457
vendor/gonum.org/v1/plot/plotter/boxplot.go
generated
vendored
Normal file
@@ -0,0 +1,457 @@
|
||||
// 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"
|
||||
"sort"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// fiveStatPlot contains the shared fields for quartile
|
||||
// and box-whisker plots.
|
||||
type fiveStatPlot struct {
|
||||
// Values is a copy of the values of the values used to
|
||||
// create this box plot.
|
||||
Values
|
||||
|
||||
// Location is the location of the box along its axis.
|
||||
Location float64
|
||||
|
||||
// Median is the median value of the data.
|
||||
Median float64
|
||||
|
||||
// Quartile1 and Quartile3 are the first and
|
||||
// third quartiles of the data respectively.
|
||||
Quartile1, Quartile3 float64
|
||||
|
||||
// AdjLow and AdjHigh are the `adjacent' values
|
||||
// on the low and high ends of the data. The
|
||||
// adjacent values are the points to which the
|
||||
// whiskers are drawn.
|
||||
AdjLow, AdjHigh float64
|
||||
|
||||
// Min and Max are the extreme values of the data.
|
||||
Min, Max float64
|
||||
|
||||
// Outside are the indices of Vs for the outside points.
|
||||
Outside []int
|
||||
}
|
||||
|
||||
// BoxPlot implements the Plotter interface, drawing
|
||||
// a boxplot to represent the distribution of values.
|
||||
type BoxPlot struct {
|
||||
fiveStatPlot
|
||||
|
||||
// Offset is added to the x location of each box.
|
||||
// When the Offset is zero, the boxes are drawn
|
||||
// centered at their x location.
|
||||
Offset vg.Length
|
||||
|
||||
// Width is the width used to draw the box.
|
||||
Width vg.Length
|
||||
|
||||
// CapWidth is the width of the cap used to top
|
||||
// off a whisker.
|
||||
CapWidth vg.Length
|
||||
|
||||
// GlyphStyle is the style of the outside point glyphs.
|
||||
GlyphStyle draw.GlyphStyle
|
||||
|
||||
// FillColor is the color used to fill the box.
|
||||
// The default is no fill.
|
||||
FillColor color.Color
|
||||
|
||||
// BoxStyle is the line style for the box.
|
||||
BoxStyle draw.LineStyle
|
||||
|
||||
// MedianStyle is the line style for the median line.
|
||||
MedianStyle draw.LineStyle
|
||||
|
||||
// WhiskerStyle is the line style used to draw the
|
||||
// whiskers.
|
||||
WhiskerStyle draw.LineStyle
|
||||
|
||||
// Horizontal dictates whether the BoxPlot should be in the vertical
|
||||
// (default) or horizontal direction.
|
||||
Horizontal bool
|
||||
}
|
||||
|
||||
// NewBoxPlot returns a new BoxPlot that represents
|
||||
// the distribution of the given values. The style of
|
||||
// the box plot is that used for Tukey's schematic
|
||||
// plots in “Exploratory Data Analysis.”
|
||||
//
|
||||
// An error is returned if the boxplot is created with
|
||||
// no values.
|
||||
//
|
||||
// The fence values are 1.5x the interquartile before
|
||||
// the first quartile and after the third quartile. Any
|
||||
// value that is outside of the fences are drawn as
|
||||
// Outside points. The adjacent values (to which the
|
||||
// whiskers stretch) are the minimum and maximum
|
||||
// values that are not outside the fences.
|
||||
func NewBoxPlot(w vg.Length, loc float64, values Valuer) (*BoxPlot, error) {
|
||||
if w < 0 {
|
||||
return nil, errors.New("plotter: negative boxplot width")
|
||||
}
|
||||
|
||||
b := new(BoxPlot)
|
||||
var err error
|
||||
if b.fiveStatPlot, err = newFiveStat(w, loc, values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Width = w
|
||||
b.CapWidth = 3 * w / 4
|
||||
|
||||
b.GlyphStyle = DefaultGlyphStyle
|
||||
b.BoxStyle = DefaultLineStyle
|
||||
b.MedianStyle = DefaultLineStyle
|
||||
b.WhiskerStyle = draw.LineStyle{
|
||||
Width: vg.Points(0.5),
|
||||
Dashes: []vg.Length{vg.Points(4), vg.Points(2)},
|
||||
}
|
||||
|
||||
if len(b.Values) == 0 {
|
||||
b.Width = 0
|
||||
b.GlyphStyle.Radius = 0
|
||||
b.BoxStyle.Width = 0
|
||||
b.MedianStyle.Width = 0
|
||||
b.WhiskerStyle.Width = 0
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func newFiveStat(w vg.Length, loc float64, values Valuer) (fiveStatPlot, error) {
|
||||
var b fiveStatPlot
|
||||
b.Location = loc
|
||||
|
||||
var err error
|
||||
if b.Values, err = CopyValues(values); err != nil {
|
||||
return fiveStatPlot{}, err
|
||||
}
|
||||
|
||||
sorted := make(Values, len(b.Values))
|
||||
copy(sorted, b.Values)
|
||||
sort.Float64s(sorted)
|
||||
|
||||
if len(sorted) == 1 {
|
||||
b.Median = sorted[0]
|
||||
b.Quartile1 = sorted[0]
|
||||
b.Quartile3 = sorted[0]
|
||||
} else {
|
||||
b.Median = median(sorted)
|
||||
b.Quartile1 = median(sorted[:len(sorted)/2])
|
||||
b.Quartile3 = median(sorted[len(sorted)/2:])
|
||||
}
|
||||
b.Min = sorted[0]
|
||||
b.Max = sorted[len(sorted)-1]
|
||||
|
||||
low := b.Quartile1 - 1.5*(b.Quartile3-b.Quartile1)
|
||||
high := b.Quartile3 + 1.5*(b.Quartile3-b.Quartile1)
|
||||
b.AdjLow = math.Inf(1)
|
||||
b.AdjHigh = math.Inf(-1)
|
||||
for i, v := range b.Values {
|
||||
if v > high || v < low {
|
||||
b.Outside = append(b.Outside, i)
|
||||
continue
|
||||
}
|
||||
if v < b.AdjLow {
|
||||
b.AdjLow = v
|
||||
}
|
||||
if v > b.AdjHigh {
|
||||
b.AdjHigh = v
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// median returns the median value from a
|
||||
// sorted Values.
|
||||
func median(vs Values) float64 {
|
||||
if len(vs) == 1 {
|
||||
return vs[0]
|
||||
}
|
||||
med := vs[len(vs)/2]
|
||||
if len(vs)%2 == 0 {
|
||||
med += vs[len(vs)/2-1]
|
||||
med /= 2
|
||||
}
|
||||
return med
|
||||
}
|
||||
|
||||
// Plot draws the BoxPlot on Canvas c and Plot plt.
|
||||
func (b *BoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
b.Plot(c, plt)
|
||||
return
|
||||
}
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
x := trX(b.Location)
|
||||
if !c.ContainsX(x) {
|
||||
return
|
||||
}
|
||||
x += b.Offset
|
||||
|
||||
med := trY(b.Median)
|
||||
q1 := trY(b.Quartile1)
|
||||
q3 := trY(b.Quartile3)
|
||||
aLow := trY(b.AdjLow)
|
||||
aHigh := trY(b.AdjHigh)
|
||||
|
||||
pts := []vg.Point{
|
||||
{X: x - b.Width/2, Y: q1},
|
||||
{X: x - b.Width/2, Y: q3},
|
||||
{X: x + b.Width/2, Y: q3},
|
||||
{X: x + b.Width/2, Y: q1},
|
||||
{X: x - b.Width/2 - b.BoxStyle.Width/2, Y: q1},
|
||||
}
|
||||
box := c.ClipLinesY(pts)
|
||||
if b.FillColor != nil {
|
||||
c.FillPolygon(b.FillColor, c.ClipPolygonY(pts))
|
||||
}
|
||||
c.StrokeLines(b.BoxStyle, box...)
|
||||
|
||||
medLine := c.ClipLinesY([]vg.Point{
|
||||
{X: x - b.Width/2, Y: med},
|
||||
{X: x + b.Width/2, Y: med},
|
||||
})
|
||||
c.StrokeLines(b.MedianStyle, medLine...)
|
||||
|
||||
cap := b.CapWidth / 2
|
||||
whisks := c.ClipLinesY(
|
||||
[]vg.Point{{X: x, Y: q3}, {X: x, Y: aHigh}},
|
||||
[]vg.Point{{X: x - cap, Y: aHigh}, {X: x + cap, Y: aHigh}},
|
||||
[]vg.Point{{X: x, Y: q1}, {X: x, Y: aLow}},
|
||||
[]vg.Point{{X: x - cap, Y: aLow}, {X: x + cap, Y: aLow}},
|
||||
)
|
||||
c.StrokeLines(b.WhiskerStyle, whisks...)
|
||||
|
||||
for _, out := range b.Outside {
|
||||
y := trY(b.Value(out))
|
||||
if c.ContainsY(y) {
|
||||
c.DrawGlyphNoClip(b.GlyphStyle, vg.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum x
|
||||
// and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (b *BoxPlot) DataRange() (float64, float64, float64, float64) {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
return b.DataRange()
|
||||
}
|
||||
return b.Location, b.Location, b.Min, b.Max
|
||||
}
|
||||
|
||||
// GlyphBoxes returns a slice of GlyphBoxes for the
|
||||
// points and for the median line of the boxplot,
|
||||
// implementing the plot.GlyphBoxer interface
|
||||
func (b *BoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
return b.GlyphBoxes(plt)
|
||||
}
|
||||
|
||||
bs := make([]plot.GlyphBox, len(b.Outside)+1)
|
||||
for i, out := range b.Outside {
|
||||
bs[i].X = plt.X.Norm(b.Location)
|
||||
bs[i].Y = plt.Y.Norm(b.Value(out))
|
||||
bs[i].Rectangle = b.GlyphStyle.Rectangle()
|
||||
}
|
||||
bs[len(bs)-1].X = plt.X.Norm(b.Location)
|
||||
bs[len(bs)-1].Y = plt.Y.Norm(b.Median)
|
||||
bs[len(bs)-1].Rectangle = vg.Rectangle{
|
||||
Min: vg.Point{X: b.Offset - (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
Max: vg.Point{X: b.Offset + (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// OutsideLabels returns a *Labels that will plot
|
||||
// a label for each of the outside points. The
|
||||
// labels are assumed to correspond to the
|
||||
// points used to create the box plot.
|
||||
func (b *BoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
return b.OutsideLabels(labels)
|
||||
}
|
||||
|
||||
strs := make([]string, len(b.Outside))
|
||||
for i, out := range b.Outside {
|
||||
strs[i] = labels.Label(out)
|
||||
}
|
||||
o := boxPlotOutsideLabels{b, strs}
|
||||
ls, err := NewLabels(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off := 0.5 * b.GlyphStyle.Radius
|
||||
ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
type boxPlotOutsideLabels struct {
|
||||
box *BoxPlot
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (o boxPlotOutsideLabels) Len() int {
|
||||
return len(o.box.Outside)
|
||||
}
|
||||
|
||||
func (o boxPlotOutsideLabels) XY(i int) (float64, float64) {
|
||||
return o.box.Location, o.box.Value(o.box.Outside[i])
|
||||
}
|
||||
|
||||
func (o boxPlotOutsideLabels) Label(i int) string {
|
||||
return o.labels[i]
|
||||
}
|
||||
|
||||
// horizBoxPlot is like a regular BoxPlot, however,
|
||||
// it draws horizontally instead of Vertically.
|
||||
// TODO: Merge code for horizontal and vertical box plots as has been done for
|
||||
// bar charts.
|
||||
type horizBoxPlot struct{ *BoxPlot }
|
||||
|
||||
func (b horizBoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trX, trY := plt.Transforms(&c)
|
||||
y := trY(b.Location)
|
||||
if !c.ContainsY(y) {
|
||||
return
|
||||
}
|
||||
y += b.Offset
|
||||
|
||||
med := trX(b.Median)
|
||||
q1 := trX(b.Quartile1)
|
||||
q3 := trX(b.Quartile3)
|
||||
aLow := trX(b.AdjLow)
|
||||
aHigh := trX(b.AdjHigh)
|
||||
|
||||
pts := []vg.Point{
|
||||
{X: q1, Y: y - b.Width/2},
|
||||
{X: q3, Y: y - b.Width/2},
|
||||
{X: q3, Y: y + b.Width/2},
|
||||
{X: q1, Y: y + b.Width/2},
|
||||
{X: q1, Y: y - b.Width/2 - b.BoxStyle.Width/2},
|
||||
}
|
||||
box := c.ClipLinesX(pts)
|
||||
if b.FillColor != nil {
|
||||
c.FillPolygon(b.FillColor, c.ClipPolygonX(pts))
|
||||
}
|
||||
c.StrokeLines(b.BoxStyle, box...)
|
||||
|
||||
medLine := c.ClipLinesX([]vg.Point{
|
||||
{X: med, Y: y - b.Width/2},
|
||||
{X: med, Y: y + b.Width/2},
|
||||
})
|
||||
c.StrokeLines(b.MedianStyle, medLine...)
|
||||
|
||||
cap := b.CapWidth / 2
|
||||
whisks := c.ClipLinesX(
|
||||
[]vg.Point{{X: q3, Y: y}, {X: aHigh, Y: y}},
|
||||
[]vg.Point{{X: aHigh, Y: y - cap}, {X: aHigh, Y: y + cap}},
|
||||
[]vg.Point{{X: q1, Y: y}, {X: aLow, Y: y}},
|
||||
[]vg.Point{{X: aLow, Y: y - cap}, {X: aLow, Y: y + cap}},
|
||||
)
|
||||
c.StrokeLines(b.WhiskerStyle, whisks...)
|
||||
|
||||
for _, out := range b.Outside {
|
||||
x := trX(b.Value(out))
|
||||
if c.ContainsX(x) {
|
||||
c.DrawGlyphNoClip(b.GlyphStyle, vg.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum x
|
||||
// and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (b horizBoxPlot) DataRange() (float64, float64, float64, float64) {
|
||||
return b.Min, b.Max, b.Location, b.Location
|
||||
}
|
||||
|
||||
// GlyphBoxes returns a slice of GlyphBoxes for the
|
||||
// points and for the median line of the boxplot,
|
||||
// implementing the plot.GlyphBoxer interface
|
||||
func (b horizBoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
bs := make([]plot.GlyphBox, len(b.Outside)+1)
|
||||
for i, out := range b.Outside {
|
||||
bs[i].X = plt.X.Norm(b.Value(out))
|
||||
bs[i].Y = plt.Y.Norm(b.Location)
|
||||
bs[i].Rectangle = b.GlyphStyle.Rectangle()
|
||||
}
|
||||
bs[len(bs)-1].X = plt.X.Norm(b.Median)
|
||||
bs[len(bs)-1].Y = plt.Y.Norm(b.Location)
|
||||
bs[len(bs)-1].Rectangle = vg.Rectangle{
|
||||
Min: vg.Point{Y: b.Offset - (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
Max: vg.Point{Y: b.Offset + (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// OutsideLabels returns a *Labels that will plot
|
||||
// a label for each of the outside points. The
|
||||
// labels are assumed to correspond to the
|
||||
// points used to create the box plot.
|
||||
func (b *horizBoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
|
||||
strs := make([]string, len(b.Outside))
|
||||
for i, out := range b.Outside {
|
||||
strs[i] = labels.Label(out)
|
||||
}
|
||||
o := horizBoxPlotOutsideLabels{
|
||||
boxPlotOutsideLabels{b.BoxPlot, strs},
|
||||
}
|
||||
ls, err := NewLabels(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off := 0.5 * b.GlyphStyle.Radius
|
||||
ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
type horizBoxPlotOutsideLabels struct {
|
||||
boxPlotOutsideLabels
|
||||
}
|
||||
|
||||
func (o horizBoxPlotOutsideLabels) XY(i int) (float64, float64) {
|
||||
return o.box.Value(o.box.Outside[i]), o.box.Location
|
||||
}
|
||||
|
||||
// ValueLabels implements both the Valuer
|
||||
// and Labeller interfaces.
|
||||
type ValueLabels []struct {
|
||||
Value float64
|
||||
Label string
|
||||
}
|
||||
|
||||
// Len returns the number of items.
|
||||
func (vs ValueLabels) Len() int {
|
||||
return len(vs)
|
||||
}
|
||||
|
||||
// Value returns the value of item i.
|
||||
func (vs ValueLabels) Value(i int) float64 {
|
||||
return vs[i].Value
|
||||
}
|
||||
|
||||
// Label returns the label of item i.
|
||||
func (vs ValueLabels) Label(i int) string {
|
||||
return vs[i].Label
|
||||
}
|
||||
98
vendor/gonum.org/v1/plot/plotter/colorbar.go
generated
vendored
Normal file
98
vendor/gonum.org/v1/plot/plotter/colorbar.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright ©2017 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 (
|
||||
"image"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/palette"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// ColorBar is a plot.Plotter that draws a color bar legend for a ColorMap.
|
||||
type ColorBar struct {
|
||||
ColorMap palette.ColorMap
|
||||
|
||||
// Vertical determines wether the legend will be
|
||||
// plotted vertically or horizontally.
|
||||
// The default is false (horizontal).
|
||||
Vertical bool
|
||||
|
||||
// Colors specifies the number of colors to be
|
||||
// shown in the legend. If Colors is not specified,
|
||||
// a default will be used.
|
||||
Colors int
|
||||
}
|
||||
|
||||
// colors returns the number of colors to be shown
|
||||
// in the legend, substituting invalid values
|
||||
// with the default of one color per point.
|
||||
func (l *ColorBar) colors(c draw.Canvas) int {
|
||||
if l.Colors > 0 {
|
||||
return l.Colors
|
||||
}
|
||||
if l.Vertical {
|
||||
return int(c.Max.Y - c.Min.Y)
|
||||
}
|
||||
return int(c.Max.X - c.Min.X)
|
||||
}
|
||||
|
||||
// check determines whether the ColorBar is
|
||||
// valid in its current configuration.
|
||||
func (l *ColorBar) check() {
|
||||
if l.ColorMap == nil {
|
||||
panic("plotter: nil ColorMap in ColorBar")
|
||||
}
|
||||
if l.ColorMap.Max() == l.ColorMap.Min() {
|
||||
panic("plotter: ColorMap Max==Min")
|
||||
}
|
||||
}
|
||||
|
||||
// Plot implements the Plot method of the plot.Plotter interface.
|
||||
func (l *ColorBar) Plot(c draw.Canvas, p *plot.Plot) {
|
||||
l.check()
|
||||
colors := l.colors(c)
|
||||
var pImg *Image
|
||||
delta := (l.ColorMap.Max() - l.ColorMap.Min()) / float64(colors)
|
||||
if l.Vertical {
|
||||
img := image.NewNRGBA64(image.Rectangle{
|
||||
Min: image.Point{X: 0, Y: 0},
|
||||
Max: image.Point{X: 1, Y: colors},
|
||||
})
|
||||
for i := 0; i < colors; i++ {
|
||||
color, err := l.ColorMap.At(l.ColorMap.Min() + delta*float64(i))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
img.Set(0, colors-1-i, color)
|
||||
}
|
||||
pImg = NewImage(img, 0, l.ColorMap.Min(), 1, l.ColorMap.Max())
|
||||
} else {
|
||||
img := image.NewNRGBA64(image.Rectangle{
|
||||
Min: image.Point{X: 0, Y: 0},
|
||||
Max: image.Point{X: colors, Y: 1},
|
||||
})
|
||||
for i := 0; i < colors; i++ {
|
||||
color, err := l.ColorMap.At(l.ColorMap.Min() + delta*float64(i))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
img.Set(i, 0, color)
|
||||
}
|
||||
pImg = NewImage(img, l.ColorMap.Min(), 0, l.ColorMap.Max(), 1)
|
||||
}
|
||||
pImg.Plot(c, p)
|
||||
}
|
||||
|
||||
// DataRange implements the DataRange method
|
||||
// of the plot.DataRanger interface.
|
||||
func (l *ColorBar) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
l.check()
|
||||
if l.Vertical {
|
||||
return 0, 1, l.ColorMap.Min(), l.ColorMap.Max()
|
||||
}
|
||||
return l.ColorMap.Min(), l.ColorMap.Max(), 0, 1
|
||||
}
|
||||
203
vendor/gonum.org/v1/plot/plotter/conrec.go
generated
vendored
Normal file
203
vendor/gonum.org/v1/plot/plotter/conrec.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// 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 (
|
||||
"math"
|
||||
)
|
||||
|
||||
type point struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
type line struct {
|
||||
p1, p2 point
|
||||
}
|
||||
|
||||
func sect(h, p [5]float64, v1, v2 int) float64 {
|
||||
return (h[v2]*p[v1] - h[v1]*p[v2]) / (h[v2] - h[v1])
|
||||
}
|
||||
|
||||
// conrecLine performs an operation with a line at a given height derived
|
||||
// from data over the 2D box interval (i, j) to (i+1, j+1).
|
||||
type conrecLine func(i, j int, l line, height float64)
|
||||
|
||||
// conrec is a Go translation of the C version of CONREC by Paul Bourke:
|
||||
// http://paulbourke.net/papers/conrec/conrec.c
|
||||
//
|
||||
// conrec takes g, an m×n grid function, a sorted slice of contour heights
|
||||
// and a conrecLine function.
|
||||
//
|
||||
// For full details of the algorithm, see the paper at
|
||||
// http://paulbourke.net/papers/conrec/
|
||||
func conrec(g GridXYZ, heights []float64, fn conrecLine) {
|
||||
var (
|
||||
p1, p2 point
|
||||
|
||||
h [5]float64
|
||||
sh [5]int
|
||||
xh, yh [5]float64
|
||||
|
||||
im = [4]int{0, 1, 1, 0}
|
||||
jm = [4]int{0, 0, 1, 1}
|
||||
|
||||
// We differ from conrec.c in the assignment of a single value
|
||||
// in cases (castab in conrec.c). The value of castab[1][1][1] is
|
||||
// 3, but we set cases[1][1][1] to 0.
|
||||
//
|
||||
// axiom: When we have a section of the grid where all the
|
||||
// Z values are equal, and equal to a contour height we would
|
||||
// expect to have no internal segments to draw.
|
||||
//
|
||||
// This is covered by case g) in Paul Bourke's description of
|
||||
// the CONREC algorithm (a triangle with three vertices the lie
|
||||
// on the contour level). He says, "... case g above has no really
|
||||
// satisfactory solution and fortunately will occur rarely with
|
||||
// real arithmetic." and then goes on to show the following image:
|
||||
//
|
||||
// http://paulbourke.net/papers/conrec/conrec3.gif
|
||||
//
|
||||
// which shows case g) in the set where no edge is drawn, agreeing
|
||||
// with our axiom above.
|
||||
//
|
||||
// However, in the iteration over sh at conrec.c +44, a triangle
|
||||
// with all vertices on the plane is given sh = {0,0,0,0,0} and
|
||||
// then when the switch at conrec.c +93 happens, castab resolves
|
||||
// that to case 3 for all values of m.
|
||||
//
|
||||
// This is fixed by replacing castab/cases[1][1][1] with 0.
|
||||
cases = [3][3][3]int{
|
||||
{{0, 0, 8}, {0, 2, 5}, {7, 6, 9}},
|
||||
{{0, 3, 4}, {1, 0, 1}, {4, 3, 0}},
|
||||
{{9, 6, 7}, {5, 2, 0}, {8, 0, 0}},
|
||||
}
|
||||
)
|
||||
|
||||
c, r := g.Dims()
|
||||
for i := 0; i < c-1; i++ {
|
||||
for j := 0; j < r-1; j++ {
|
||||
dmin := math.Min(
|
||||
math.Min(g.Z(i, j), g.Z(i, j+1)),
|
||||
math.Min(g.Z(i+1, j), g.Z(i+1, j+1)),
|
||||
)
|
||||
|
||||
dmax := math.Max(
|
||||
math.Max(g.Z(i, j), g.Z(i, j+1)),
|
||||
math.Max(g.Z(i+1, j), g.Z(i+1, j+1)),
|
||||
)
|
||||
|
||||
if dmax < heights[0] || heights[len(heights)-1] < dmin {
|
||||
continue
|
||||
}
|
||||
|
||||
for k := 0; k < len(heights); k++ {
|
||||
if heights[k] < dmin || dmax < heights[k] {
|
||||
continue
|
||||
}
|
||||
for m := 4; m >= 0; m-- {
|
||||
if m > 0 {
|
||||
h[m] = g.Z(i+im[m-1], j+jm[m-1]) - heights[k]
|
||||
xh[m] = g.X(i + im[m-1])
|
||||
yh[m] = g.Y(j + jm[m-1])
|
||||
} else {
|
||||
h[0] = 0.25 * (h[1] + h[2] + h[3] + h[4])
|
||||
xh[0] = 0.50 * (g.X(i) + g.X(i+1))
|
||||
yh[0] = 0.50 * (g.Y(j) + g.Y(j+1))
|
||||
}
|
||||
switch {
|
||||
case h[m] > 0:
|
||||
sh[m] = 1
|
||||
case h[m] < 0:
|
||||
sh[m] = -1
|
||||
default:
|
||||
sh[m] = 0
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Note: at this stage the relative heights of the corners and the
|
||||
centre are in the h array, and the corresponding coordinates are
|
||||
in the xh and yh arrays. The centre of the box is indexed by 0
|
||||
and the 4 corners by 1 to 4 as shown below.
|
||||
Each triangle is then indexed by the parameter m, and the 3
|
||||
vertices of each triangle are indexed by parameters m1,m2,and m3.
|
||||
It is assumed that the centre of the box is always vertex 2
|
||||
though this isimportant only when all 3 vertices lie exactly on
|
||||
the same contour level, in which case only the side of the box
|
||||
is drawn.
|
||||
|
||||
vertex 4 +-------------------+ vertex 3
|
||||
| \ / |
|
||||
| \ m-3 / |
|
||||
| \ / |
|
||||
| \ / |
|
||||
| m=2 X m=2 | the centre is vertex 0
|
||||
| / \ |
|
||||
| / \ |
|
||||
| / m=1 \ |
|
||||
| / \ |
|
||||
vertex 1 +-------------------+ vertex 2
|
||||
*/
|
||||
|
||||
// Scan each triangle in the box.
|
||||
for m := 1; m <= 4; m++ {
|
||||
m1 := m
|
||||
const m2 = 0
|
||||
var m3 int
|
||||
if m != 4 {
|
||||
m3 = m + 1
|
||||
} else {
|
||||
m3 = 1
|
||||
}
|
||||
switch cases[sh[m1]+1][sh[m2]+1][sh[m3]+1] {
|
||||
case 0:
|
||||
continue
|
||||
|
||||
case 1: // Line between vertices 1 and 2
|
||||
p1 = point{X: xh[m1], Y: yh[m1]}
|
||||
p2 = point{X: xh[m2], Y: yh[m2]}
|
||||
|
||||
case 2: // Line between vertices 2 and 3
|
||||
p1 = point{X: xh[m2], Y: yh[m2]}
|
||||
p2 = point{X: xh[m3], Y: yh[m3]}
|
||||
|
||||
case 3: // Line between vertices 3 and 1
|
||||
p1 = point{X: xh[m3], Y: yh[m3]}
|
||||
p2 = point{X: xh[m1], Y: yh[m1]}
|
||||
|
||||
case 4: // Line between vertex 1 and side 2-3
|
||||
p1 = point{X: xh[m1], Y: yh[m1]}
|
||||
p2 = point{X: sect(h, xh, m2, m3), Y: sect(h, yh, m2, m3)}
|
||||
|
||||
case 5: // Line between vertex 2 and side 3-1
|
||||
p1 = point{X: xh[m2], Y: yh[m2]}
|
||||
p2 = point{X: sect(h, xh, m3, m1), Y: sect(h, yh, m3, m1)}
|
||||
|
||||
case 6: // Line between vertex 3 and side 1-2
|
||||
p1 = point{X: xh[m3], Y: yh[m3]}
|
||||
p2 = point{X: sect(h, xh, m1, m2), Y: sect(h, yh, m1, m2)}
|
||||
|
||||
case 7: // Line between sides 1-2 and 2-3
|
||||
p1 = point{X: sect(h, xh, m1, m2), Y: sect(h, yh, m1, m2)}
|
||||
p2 = point{X: sect(h, xh, m2, m3), Y: sect(h, yh, m2, m3)}
|
||||
|
||||
case 8: // Line between sides 2-3 and 3-1
|
||||
p1 = point{X: sect(h, xh, m2, m3), Y: sect(h, yh, m2, m3)}
|
||||
p2 = point{X: sect(h, xh, m3, m1), Y: sect(h, yh, m3, m1)}
|
||||
|
||||
case 9: // Line between sides 3-1 and 1-2
|
||||
p1 = point{X: sect(h, xh, m3, m1), Y: sect(h, yh, m3, m1)}
|
||||
p2 = point{X: sect(h, xh, m1, m2), Y: sect(h, yh, m1, m2)}
|
||||
|
||||
default:
|
||||
panic("cannot reach")
|
||||
}
|
||||
|
||||
fn(i, j, line{p1: p1, p2: p2}, heights[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
666
vendor/gonum.org/v1/plot/plotter/contour.go
generated
vendored
Normal file
666
vendor/gonum.org/v1/plot/plotter/contour.go
generated
vendored
Normal file
@@ -0,0 +1,666 @@
|
||||
// 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 (
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/palette"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Contour implements the Plotter interface, drawing
|
||||
// a contour plot of the values in the GridXYZ field.
|
||||
type Contour struct {
|
||||
GridXYZ GridXYZ
|
||||
|
||||
// Levels describes the contour heights to plot.
|
||||
Levels []float64
|
||||
|
||||
// LineStyles is the set of styles for contour
|
||||
// lines. Line styles are are applied to each level
|
||||
// in order, modulo the length of LineStyles.
|
||||
LineStyles []draw.LineStyle
|
||||
|
||||
// Palette is the color palette used to render
|
||||
// the heat map. If Palette is nil or has no
|
||||
// defined color, the Contour LineStyle color
|
||||
// is used.
|
||||
Palette palette.Palette
|
||||
|
||||
// Underflow and Overflow are colors used to draw
|
||||
// contours outside the dynamic range defined
|
||||
// by Min and Max.
|
||||
Underflow color.Color
|
||||
Overflow color.Color
|
||||
|
||||
// Min and Max define the dynamic range of the
|
||||
// heat map.
|
||||
Min, Max float64
|
||||
}
|
||||
|
||||
// NewContour creates as new contour plotter for the given data, using
|
||||
// the provided palette. If levels is nil, contours are generated for
|
||||
// the 0.01, 0.05, 0.25, 0.5, 0.75, 0.95 and 0.99 quantiles.
|
||||
// If g has Min and Max methods that return a float, those returned
|
||||
// values are used to set the respective Contour fields.
|
||||
// If the returned Contour is used when Min is greater than Max, the
|
||||
// Plot method will panic.
|
||||
func NewContour(g GridXYZ, levels []float64, p palette.Palette) *Contour {
|
||||
var min, max float64
|
||||
type minMaxer interface {
|
||||
Min() float64
|
||||
Max() float64
|
||||
}
|
||||
switch g := g.(type) {
|
||||
case minMaxer:
|
||||
min, max = g.Min(), g.Max()
|
||||
default:
|
||||
min, max = math.Inf(1), math.Inf(-1)
|
||||
c, r := g.Dims()
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
v := g.Z(i, j)
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
min = math.Min(min, v)
|
||||
max = math.Max(max, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(levels) == 0 {
|
||||
levels = quantilesR7(g, defaultQuantiles)
|
||||
}
|
||||
|
||||
return &Contour{
|
||||
GridXYZ: g,
|
||||
Levels: levels,
|
||||
LineStyles: []draw.LineStyle{DefaultLineStyle},
|
||||
Palette: p,
|
||||
Min: min,
|
||||
Max: max,
|
||||
}
|
||||
}
|
||||
|
||||
// Default quantiles for case where levels is not explicitly set.
|
||||
var defaultQuantiles = []float64{0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99}
|
||||
|
||||
// quantilesR7 returns the pth quantiles of the data in g according to the R-7 method.
|
||||
// http://en.wikipedia.org/wiki/Quantile#Estimating_the_quantiles_of_a_population
|
||||
func quantilesR7(g GridXYZ, p []float64) []float64 {
|
||||
c, r := g.Dims()
|
||||
data := make([]float64, 0, c*r)
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
if v := g.Z(i, j); !math.IsNaN(v) {
|
||||
data = append(data, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Float64s(data)
|
||||
v := make([]float64, len(p))
|
||||
for j, q := range p {
|
||||
if q == 1 {
|
||||
v[j] = data[len(data)-1]
|
||||
}
|
||||
h := float64(len(data)-1) * q
|
||||
i := int(h)
|
||||
v[j] = data[i] + (h-math.Floor(h))*(data[i+1]-data[i])
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// naive is a debugging constant. If true, Plot performs no contour path
|
||||
// reconstruction, instead rendering each path segment individually.
|
||||
const naive = false
|
||||
|
||||
// Plot implements the Plot method of the plot.Plotter interface.
|
||||
func (h *Contour) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
if h.Min > h.Max {
|
||||
panic("contour: invalid Z range: min greater than max")
|
||||
}
|
||||
|
||||
if naive {
|
||||
h.naivePlot(c, plt)
|
||||
return
|
||||
}
|
||||
|
||||
var pal []color.Color
|
||||
if h.Palette != nil {
|
||||
pal = h.Palette.Colors()
|
||||
}
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
|
||||
// Collate contour paths and draw them.
|
||||
//
|
||||
// The alternative naive approach is to draw each line segment as
|
||||
// conrec returns it. The integrated path approach allows graphical
|
||||
// optimisations and is necessary for contour fill shading.
|
||||
cp := contourPaths(h.GridXYZ, h.Levels, trX, trY)
|
||||
|
||||
// ps is a palette scaling factor to scale the palette uniformly
|
||||
// across the given levels. This enables a discordance between the
|
||||
// number of colours and the number of levels. Sorting is not
|
||||
// necessary since contourPaths sorts the levels as a side effect.
|
||||
ps := float64(len(pal)-1) / (h.Levels[len(h.Levels)-1] - h.Levels[0])
|
||||
if len(h.Levels) == 1 {
|
||||
ps = 0
|
||||
}
|
||||
|
||||
for i, z := range h.Levels {
|
||||
if math.IsNaN(z) {
|
||||
continue
|
||||
}
|
||||
for _, pa := range cp[z] {
|
||||
if isLoop(pa) {
|
||||
pa.Close()
|
||||
}
|
||||
|
||||
style := h.LineStyles[i%len(h.LineStyles)]
|
||||
var col color.Color
|
||||
switch {
|
||||
case z < h.Min:
|
||||
col = h.Underflow
|
||||
case z > h.Max:
|
||||
col = h.Overflow
|
||||
case len(pal) == 0:
|
||||
col = style.Color
|
||||
default:
|
||||
col = pal[int((z-h.Levels[0])*ps+0.5)] // Apply palette scaling.
|
||||
}
|
||||
if col != nil && style.Width != 0 {
|
||||
c.SetLineStyle(style)
|
||||
c.SetColor(col)
|
||||
c.Stroke(pa)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// naivePlot implements a naive rendering approach for contours.
|
||||
// It is here as a debugging mode since it simply draws line segments
|
||||
// generated by conrec without further computation.
|
||||
func (h *Contour) naivePlot(c draw.Canvas, plt *plot.Plot) {
|
||||
var pal []color.Color
|
||||
if h.Palette != nil {
|
||||
pal = h.Palette.Colors()
|
||||
}
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
|
||||
// Sort levels prior to palette scaling since we can't depend on
|
||||
// sorting as a side effect from calling contourPaths.
|
||||
sort.Float64s(h.Levels)
|
||||
// ps is a palette scaling factor to scale the palette uniformly
|
||||
// across the given levels. This enables a discordance between the
|
||||
// number of colours and the number of levels.
|
||||
ps := float64(len(pal)-1) / (h.Levels[len(h.Levels)-1] - h.Levels[0])
|
||||
if len(h.Levels) == 1 {
|
||||
ps = 0
|
||||
}
|
||||
|
||||
levelMap := make(map[float64]int)
|
||||
for i, z := range h.Levels {
|
||||
levelMap[z] = i
|
||||
}
|
||||
|
||||
// Draw each line segment as conrec generates it.
|
||||
var pa vg.Path
|
||||
conrec(h.GridXYZ, h.Levels, func(_, _ int, l line, z float64) {
|
||||
if math.IsNaN(z) {
|
||||
return
|
||||
}
|
||||
|
||||
pa = pa[:0]
|
||||
|
||||
x1, y1 := trX(l.p1.X), trY(l.p1.Y)
|
||||
x2, y2 := trX(l.p2.X), trY(l.p2.Y)
|
||||
|
||||
pt1 := vg.Point{X: x1, Y: y1}
|
||||
pt2 := vg.Point{X: x2, Y: y2}
|
||||
if !c.Contains(pt1) || !c.Contains(pt2) {
|
||||
return
|
||||
}
|
||||
|
||||
pa.Move(pt1)
|
||||
pa.Line(pt2)
|
||||
pa.Close()
|
||||
|
||||
style := h.LineStyles[levelMap[z]%len(h.LineStyles)]
|
||||
var col color.Color
|
||||
switch {
|
||||
case z < h.Min:
|
||||
col = h.Underflow
|
||||
case z > h.Max:
|
||||
col = h.Overflow
|
||||
case len(pal) == 0:
|
||||
col = style.Color
|
||||
default:
|
||||
col = pal[int((z-h.Levels[0])*ps+0.5)] // Apply palette scaling.
|
||||
}
|
||||
if col != nil && style.Width != 0 {
|
||||
c.SetLineStyle(style)
|
||||
c.SetColor(col)
|
||||
c.Stroke(pa)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// DataRange implements the DataRange method
|
||||
// of the plot.DataRanger interface.
|
||||
func (h *Contour) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
c, r := h.GridXYZ.Dims()
|
||||
return h.GridXYZ.X(0), h.GridXYZ.X(c - 1), h.GridXYZ.Y(0), h.GridXYZ.Y(r - 1)
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the GlyphBoxes method
|
||||
// of the plot.GlyphBoxer interface.
|
||||
func (h *Contour) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
c, r := h.GridXYZ.Dims()
|
||||
b := make([]plot.GlyphBox, 0, r*c)
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
b = append(b, plot.GlyphBox{
|
||||
X: plt.X.Norm(h.GridXYZ.X(i)),
|
||||
Y: plt.Y.Norm(h.GridXYZ.Y(j)),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: -2.5, Y: -2.5},
|
||||
Max: vg.Point{X: +2.5, Y: +2.5},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// isLoop returns true iff a vg.Path is a closed loop.
|
||||
func isLoop(p vg.Path) bool {
|
||||
s := p[0]
|
||||
e := p[len(p)-1]
|
||||
return s.Pos == e.Pos
|
||||
}
|
||||
|
||||
// contourPaths returns a collection of vg.Paths describing contour lines based
|
||||
// on the input data in m cut at the given levels. The trX and trY function
|
||||
// are coordinate transforms. The returned map contains slices of paths keyed
|
||||
// on the value of the contour level. contouPaths sorts levels ascending as a
|
||||
// side effect.
|
||||
func contourPaths(m GridXYZ, levels []float64, trX, trY func(float64) vg.Length) map[float64][]vg.Path {
|
||||
sort.Float64s(levels)
|
||||
|
||||
ends := make(map[float64]endMap)
|
||||
conts := make(contourSet)
|
||||
conrec(m, levels, func(_, _ int, l line, z float64) {
|
||||
paths(l, z, ends, conts)
|
||||
})
|
||||
ends = nil
|
||||
|
||||
// TODO(kortschak): Check that all non-loop paths have
|
||||
// both ends at boundary. If any end is not at a boundary
|
||||
// it may have a partner near by. Find this partner and join
|
||||
// the two conts by merging the near by ends at the mean
|
||||
// location. This operation is done level by level to ensure
|
||||
// close contours of different heights are not joined.
|
||||
// A partner should be a float error different end, but I
|
||||
// suspect that is is possible for a bi- or higher order
|
||||
// furcation so it may be that the path ends at middle node
|
||||
// of another path. This needs to be investigated.
|
||||
|
||||
// Excise loops from crossed paths.
|
||||
for c := range conts {
|
||||
// Always try to do quick excision in production if possible.
|
||||
c.exciseLoops(conts, true)
|
||||
}
|
||||
|
||||
// Build vg.Paths.
|
||||
paths := make(map[float64][]vg.Path)
|
||||
for c := range conts {
|
||||
paths[c.z] = append(paths[c.z], c.path(trX, trY))
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
// contourSet hold a working collection of contours.
|
||||
type contourSet map[*contour]struct{}
|
||||
|
||||
// endMap holds a working collection of available ends.
|
||||
type endMap map[point]*contour
|
||||
|
||||
// paths extends a conrecLine function to build a set of contours that represent
|
||||
// paths along contour lines. It is used as the engine for a closure where ends
|
||||
// and conts are closed around in a conrecLine function, and l and z are the
|
||||
// line and height values provided by conrec. At the end of a conrec call,
|
||||
// conts will contain a map keyed on the set of paths.
|
||||
func paths(l line, z float64, ends map[float64]endMap, conts contourSet) {
|
||||
zEnds, ok := ends[z]
|
||||
if !ok {
|
||||
zEnds = make(endMap)
|
||||
ends[z] = zEnds
|
||||
c := newContour(l, z)
|
||||
zEnds[l.p1] = c
|
||||
zEnds[l.p2] = c
|
||||
conts[c] = struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
c1, ok1 := zEnds[l.p1]
|
||||
c2, ok2 := zEnds[l.p2]
|
||||
|
||||
// New segment.
|
||||
if !ok1 && !ok2 {
|
||||
c := newContour(l, z)
|
||||
zEnds[l.p1] = c
|
||||
zEnds[l.p2] = c
|
||||
conts[c] = struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
if ok1 {
|
||||
// Add l.p2 to end of l.p1's contour.
|
||||
if !c1.extend(l, zEnds) {
|
||||
panic("internal link")
|
||||
}
|
||||
} else if ok2 {
|
||||
// Add l.p1 to end of l.p2's contour.
|
||||
if !c2.extend(l, zEnds) {
|
||||
panic("internal link")
|
||||
}
|
||||
}
|
||||
|
||||
if c1 == c2 {
|
||||
return
|
||||
}
|
||||
|
||||
// Join conts.
|
||||
if ok1 && ok2 {
|
||||
if !c1.connect(c2, zEnds) {
|
||||
panic("internal link")
|
||||
}
|
||||
delete(conts, c2)
|
||||
}
|
||||
}
|
||||
|
||||
// path is a set of points forming a path.
|
||||
type path []point
|
||||
|
||||
// contour holds a set of point lying sequentially along a contour line
|
||||
// at height z.
|
||||
type contour struct {
|
||||
z float64
|
||||
|
||||
// backward and forward must each always have at least one entry.
|
||||
backward path
|
||||
forward path
|
||||
}
|
||||
|
||||
// newContour returns a contour starting with the end points of l for the
|
||||
// height z.
|
||||
func newContour(l line, z float64) *contour {
|
||||
return &contour{z: z, forward: path{l.p1}, backward: path{l.p2}}
|
||||
}
|
||||
|
||||
func (c *contour) path(trX, trY func(float64) vg.Length) vg.Path {
|
||||
var pa vg.Path
|
||||
p := c.front()
|
||||
pa.Move(vg.Point{X: trX(p.X), Y: trY(p.Y)})
|
||||
for i := len(c.backward) - 2; i >= 0; i-- {
|
||||
p = c.backward[i]
|
||||
pa.Line(vg.Point{X: trX(p.X), Y: trY(p.Y)})
|
||||
}
|
||||
for _, p := range c.forward {
|
||||
pa.Line(vg.Point{X: trX(p.X), Y: trY(p.Y)})
|
||||
}
|
||||
|
||||
return pa
|
||||
}
|
||||
|
||||
// front returns the first point in the contour.
|
||||
func (c *contour) front() point { return c.backward[len(c.backward)-1] }
|
||||
|
||||
// back returns the last point in the contour
|
||||
func (c *contour) back() point { return c.forward[len(c.forward)-1] }
|
||||
|
||||
// extend adds the line l to the contour, updating the ends map. It returns
|
||||
// a boolean indicating whether the extension was successful.
|
||||
func (c *contour) extend(l line, ends endMap) (ok bool) {
|
||||
switch c.front() {
|
||||
case l.p1:
|
||||
c.backward = append(c.backward, l.p2)
|
||||
delete(ends, l.p1)
|
||||
ends[l.p2] = c
|
||||
return true
|
||||
case l.p2:
|
||||
c.backward = append(c.backward, l.p1)
|
||||
delete(ends, l.p2)
|
||||
ends[l.p1] = c
|
||||
return true
|
||||
}
|
||||
|
||||
switch c.back() {
|
||||
case l.p1:
|
||||
c.forward = append(c.forward, l.p2)
|
||||
delete(ends, l.p1)
|
||||
ends[l.p2] = c
|
||||
return true
|
||||
case l.p2:
|
||||
c.forward = append(c.forward, l.p1)
|
||||
delete(ends, l.p2)
|
||||
ends[l.p1] = c
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// reverse reverses the order of the point in a path and returns it.
|
||||
func (p path) reverse() path {
|
||||
for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// connect connects the contour b with the receiver, updating the ends map.
|
||||
// It returns a boolean indicating whether the connection was successful.
|
||||
func (c *contour) connect(b *contour, ends endMap) (ok bool) {
|
||||
switch c.front() {
|
||||
case b.front():
|
||||
delete(ends, c.front())
|
||||
ends[b.back()] = c
|
||||
c.backward = append(c.backward, b.backward.reverse()[1:]...)
|
||||
c.backward = append(c.backward, b.forward...)
|
||||
return true
|
||||
case b.back():
|
||||
delete(ends, c.front())
|
||||
ends[b.front()] = c
|
||||
c.backward = append(c.backward, b.forward.reverse()[1:]...)
|
||||
c.backward = append(c.backward, b.backward...)
|
||||
return true
|
||||
}
|
||||
|
||||
switch c.back() {
|
||||
case b.front():
|
||||
delete(ends, c.back())
|
||||
ends[b.back()] = c
|
||||
c.forward = append(c.forward, b.backward.reverse()[1:]...)
|
||||
c.forward = append(c.forward, b.forward...)
|
||||
return true
|
||||
case b.back():
|
||||
delete(ends, c.back())
|
||||
ends[b.front()] = c
|
||||
c.forward = append(c.forward, b.forward.reverse()[1:]...)
|
||||
c.forward = append(c.forward, b.backward...)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// exciseLoops finds loops within the contour that do not include the
|
||||
// start and end. Loops are removed from the contour and added to the
|
||||
// contour set. Loop detection is performed by Johnson's algorithm for
|
||||
// finding elementary cycles.
|
||||
func (c *contour) exciseLoops(conts contourSet, quick bool) {
|
||||
if quick {
|
||||
// Find cases we can guarantee don't need
|
||||
// a complete analysis.
|
||||
seen := make(map[point]struct{})
|
||||
var crossOvers int
|
||||
for _, p := range c.backward {
|
||||
if _, ok := seen[p]; ok {
|
||||
crossOvers++
|
||||
}
|
||||
seen[p] = struct{}{}
|
||||
}
|
||||
for _, p := range c.forward[:len(c.forward)-1] {
|
||||
if _, ok := seen[p]; ok {
|
||||
crossOvers++
|
||||
}
|
||||
seen[p] = struct{}{}
|
||||
}
|
||||
switch crossOvers {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
c.exciseQuick(conts)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
wp := append(c.backward.reverse(), c.forward...)
|
||||
g := graphFrom(wp)
|
||||
cycles := cyclesIn(g)
|
||||
if len(cycles) == 0 {
|
||||
// No further work to do but clean up after ourselves.
|
||||
// We should not have reached here.
|
||||
c.backward.reverse()
|
||||
return
|
||||
}
|
||||
delete(conts, c)
|
||||
|
||||
// Put loops into the contour set.
|
||||
for _, cyc := range cycles {
|
||||
loop := wp.subpath(cyc)
|
||||
conts[&contour{
|
||||
z: c.z,
|
||||
backward: loop[:1:1],
|
||||
forward: loop[1:],
|
||||
}] = struct{}{}
|
||||
}
|
||||
|
||||
// Find non-loop paths and keep them.
|
||||
g.remove(cycles)
|
||||
paths := wp.linearPathsIn(g)
|
||||
for _, p := range paths {
|
||||
conts[&contour{
|
||||
z: c.z,
|
||||
backward: p[:1:1],
|
||||
forward: p[1:],
|
||||
}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// graphFrom returns a graph representing the point path p.
|
||||
func graphFrom(p path) graph {
|
||||
g := make([]set, len(p))
|
||||
seen := make(map[point]int)
|
||||
for i, v := range p {
|
||||
if _, ok := seen[v]; !ok {
|
||||
seen[v] = i
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range p {
|
||||
e, ok := seen[v]
|
||||
if ok && g[e] == nil {
|
||||
g[e] = make(set)
|
||||
}
|
||||
if i < len(p)-1 {
|
||||
g[e][seen[p[i+1]]] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// subpath returns a subpath given the slice of point indices
|
||||
// into the path.
|
||||
func (p path) subpath(i []int) path {
|
||||
pa := make(path, 0, len(i))
|
||||
for _, n := range i {
|
||||
pa = append(pa, p[n])
|
||||
}
|
||||
return pa
|
||||
}
|
||||
|
||||
// linearPathsIn returns the linear paths in g created from p.
|
||||
// If g contains any cycles linearPaths will panic.
|
||||
func (p path) linearPathsIn(g graph) []path {
|
||||
var pa []path
|
||||
|
||||
var u int
|
||||
for u < len(g) {
|
||||
for ; u < len(g) && len(g[u]) == 0; u++ {
|
||||
}
|
||||
if u == len(g) {
|
||||
return pa
|
||||
}
|
||||
var curr path
|
||||
for {
|
||||
if len(g[u]) == 0 {
|
||||
curr = append(curr, p[u])
|
||||
pa = append(pa, curr)
|
||||
if u == len(g)-1 {
|
||||
return pa
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(g[u]) > 1 {
|
||||
panic("contour: not a linear path")
|
||||
}
|
||||
for v := range g[u] {
|
||||
curr = append(curr, p[u])
|
||||
u = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pa
|
||||
}
|
||||
|
||||
// exciseQuick is a heuristic approach to loop excision. It does not
|
||||
// correctly identify loops in all cases, but those cases are likely
|
||||
// to be rare.
|
||||
func (c *contour) exciseQuick(conts contourSet) {
|
||||
wp := append(c.backward.reverse(), c.forward...)
|
||||
seen := make(map[point]int)
|
||||
for j := 0; j < len(wp); {
|
||||
p := wp[j]
|
||||
if i, ok := seen[p]; ok && p != wp[0] && p != wp[len(wp)-1] {
|
||||
conts[&contour{
|
||||
z: c.z,
|
||||
backward: path{wp[i]},
|
||||
forward: append(path(nil), wp[i+1:j+1]...),
|
||||
}] = struct{}{}
|
||||
wp = append(wp[:i], wp[j:]...)
|
||||
j = i + 1
|
||||
} else {
|
||||
seen[p] = j
|
||||
j++
|
||||
}
|
||||
}
|
||||
c.backward = c.backward[:1]
|
||||
c.backward[0] = wp[0]
|
||||
c.forward = wp[1:]
|
||||
}
|
||||
232
vendor/gonum.org/v1/plot/plotter/errbars.go
generated
vendored
Normal file
232
vendor/gonum.org/v1/plot/plotter/errbars.go
generated
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
// 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 (
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// DefaultCapWidth is the default width of error bar caps.
|
||||
var DefaultCapWidth = vg.Points(5)
|
||||
|
||||
// YErrorBars implements the plot.Plotter, plot.DataRanger,
|
||||
// and plot.GlyphBoxer interfaces, drawing vertical error
|
||||
// bars, denoting error in Y values.
|
||||
type YErrorBars struct {
|
||||
XYs
|
||||
|
||||
// YErrors is a copy of the Y errors for each point.
|
||||
YErrors
|
||||
|
||||
// LineStyle is the style used to draw the error bars.
|
||||
draw.LineStyle
|
||||
|
||||
// CapWidth is the width of the caps drawn at the top
|
||||
// of each error bar.
|
||||
CapWidth vg.Length
|
||||
}
|
||||
|
||||
// NewYErrorBars returns a new YErrorBars plotter, or an error on failure.
|
||||
// The error values from the YErrorer interface are interpreted as relative
|
||||
// to the corresponding Y value. The errors for a given Y value are computed
|
||||
// by taking the absolute value of the error returned by the YErrorer
|
||||
// and subtracting the first and adding the second to the Y value.
|
||||
func NewYErrorBars(yerrs interface {
|
||||
XYer
|
||||
YErrorer
|
||||
}) (*YErrorBars, error) {
|
||||
|
||||
errors := make(YErrors, yerrs.Len())
|
||||
for i := range errors {
|
||||
errors[i].Low, errors[i].High = yerrs.YError(i)
|
||||
if err := CheckFloats(errors[i].Low, errors[i].High); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
xys, err := CopyXYs(yerrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &YErrorBars{
|
||||
XYs: xys,
|
||||
YErrors: errors,
|
||||
LineStyle: DefaultLineStyle,
|
||||
CapWidth: DefaultCapWidth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Plot implements the Plotter interface, drawing labels.
|
||||
func (e *YErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
|
||||
trX, trY := p.Transforms(&c)
|
||||
for i, err := range e.YErrors {
|
||||
x := trX(e.XYs[i].X)
|
||||
ylow := trY(e.XYs[i].Y - math.Abs(err.Low))
|
||||
yhigh := trY(e.XYs[i].Y + math.Abs(err.High))
|
||||
|
||||
bar := c.ClipLinesY([]vg.Point{{X: x, Y: ylow}, {X: x, Y: yhigh}})
|
||||
c.StrokeLines(e.LineStyle, bar...)
|
||||
e.drawCap(&c, x, ylow)
|
||||
e.drawCap(&c, x, yhigh)
|
||||
}
|
||||
}
|
||||
|
||||
// drawCap draws the cap if it is not clipped.
|
||||
func (e *YErrorBars) drawCap(c *draw.Canvas, x, y vg.Length) {
|
||||
if !c.Contains(vg.Point{X: x, Y: y}) {
|
||||
return
|
||||
}
|
||||
c.StrokeLine2(e.LineStyle, x-e.CapWidth/2, y, x+e.CapWidth/2, y)
|
||||
}
|
||||
|
||||
// DataRange implements the plot.DataRanger interface.
|
||||
func (e *YErrorBars) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
xmin, xmax = Range(XValues{e})
|
||||
ymin = math.Inf(1)
|
||||
ymax = math.Inf(-1)
|
||||
for i, err := range e.YErrors {
|
||||
y := e.XYs[i].Y
|
||||
ylow := y - math.Abs(err.Low)
|
||||
yhigh := y + math.Abs(err.High)
|
||||
ymin = math.Min(math.Min(math.Min(ymin, y), ylow), yhigh)
|
||||
ymax = math.Max(math.Max(math.Max(ymax, y), ylow), yhigh)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the plot.GlyphBoxer interface.
|
||||
func (e *YErrorBars) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
rect := vg.Rectangle{
|
||||
Min: vg.Point{
|
||||
X: -e.CapWidth / 2,
|
||||
Y: -e.LineStyle.Width / 2,
|
||||
},
|
||||
Max: vg.Point{
|
||||
X: +e.CapWidth / 2,
|
||||
Y: +e.LineStyle.Width / 2,
|
||||
},
|
||||
}
|
||||
var bs []plot.GlyphBox
|
||||
for i, err := range e.YErrors {
|
||||
x := plt.X.Norm(e.XYs[i].X)
|
||||
y := e.XYs[i].Y
|
||||
bs = append(bs,
|
||||
plot.GlyphBox{X: x, Y: plt.Y.Norm(y - err.Low), Rectangle: rect},
|
||||
plot.GlyphBox{X: x, Y: plt.Y.Norm(y + err.High), Rectangle: rect})
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// XErrorBars implements the plot.Plotter, plot.DataRanger,
|
||||
// and plot.GlyphBoxer interfaces, drawing horizontal error
|
||||
// bars, denoting error in Y values.
|
||||
type XErrorBars struct {
|
||||
XYs
|
||||
|
||||
// XErrors is a copy of the X errors for each point.
|
||||
XErrors
|
||||
|
||||
// LineStyle is the style used to draw the error bars.
|
||||
draw.LineStyle
|
||||
|
||||
// CapWidth is the width of the caps drawn at the top
|
||||
// of each error bar.
|
||||
CapWidth vg.Length
|
||||
}
|
||||
|
||||
// Returns a new XErrorBars plotter, or an error on failure. The error values
|
||||
// from the XErrorer interface are interpreted as relative to the corresponding
|
||||
// X value. The errors for a given X value are computed by taking the absolute
|
||||
// value of the error returned by the XErrorer and subtracting the first and
|
||||
// adding the second to the X value.
|
||||
func NewXErrorBars(xerrs interface {
|
||||
XYer
|
||||
XErrorer
|
||||
}) (*XErrorBars, error) {
|
||||
|
||||
errors := make(XErrors, xerrs.Len())
|
||||
for i := range errors {
|
||||
errors[i].Low, errors[i].High = xerrs.XError(i)
|
||||
if err := CheckFloats(errors[i].Low, errors[i].High); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
xys, err := CopyXYs(xerrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &XErrorBars{
|
||||
XYs: xys,
|
||||
XErrors: errors,
|
||||
LineStyle: DefaultLineStyle,
|
||||
CapWidth: DefaultCapWidth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Plot implements the Plotter interface, drawing labels.
|
||||
func (e *XErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
|
||||
trX, trY := p.Transforms(&c)
|
||||
for i, err := range e.XErrors {
|
||||
y := trY(e.XYs[i].Y)
|
||||
xlow := trX(e.XYs[i].X - math.Abs(err.Low))
|
||||
xhigh := trX(e.XYs[i].X + math.Abs(err.High))
|
||||
|
||||
bar := c.ClipLinesX([]vg.Point{{X: xlow, Y: y}, {X: xhigh, Y: y}})
|
||||
c.StrokeLines(e.LineStyle, bar...)
|
||||
e.drawCap(&c, xlow, y)
|
||||
e.drawCap(&c, xhigh, y)
|
||||
}
|
||||
}
|
||||
|
||||
// drawCap draws the cap if it is not clipped.
|
||||
func (e *XErrorBars) drawCap(c *draw.Canvas, x, y vg.Length) {
|
||||
if !c.Contains(vg.Point{X: x, Y: y}) {
|
||||
return
|
||||
}
|
||||
c.StrokeLine2(e.LineStyle, x, y-e.CapWidth/2, x, y+e.CapWidth/2)
|
||||
}
|
||||
|
||||
// DataRange implements the plot.DataRanger interface.
|
||||
func (e *XErrorBars) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
ymin, ymax = Range(YValues{e})
|
||||
xmin = math.Inf(1)
|
||||
xmax = math.Inf(-1)
|
||||
for i, err := range e.XErrors {
|
||||
x := e.XYs[i].X
|
||||
xlow := x - math.Abs(err.Low)
|
||||
xhigh := x + math.Abs(err.High)
|
||||
xmin = math.Min(math.Min(math.Min(xmin, x), xlow), xhigh)
|
||||
xmax = math.Max(math.Max(math.Max(xmax, x), xlow), xhigh)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the plot.GlyphBoxer interface.
|
||||
func (e *XErrorBars) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
rect := vg.Rectangle{
|
||||
Min: vg.Point{
|
||||
X: -e.LineStyle.Width / 2,
|
||||
Y: -e.CapWidth / 2,
|
||||
},
|
||||
Max: vg.Point{
|
||||
X: +e.LineStyle.Width / 2,
|
||||
Y: +e.CapWidth / 2,
|
||||
},
|
||||
}
|
||||
var bs []plot.GlyphBox
|
||||
for i, err := range e.XErrors {
|
||||
x := e.XYs[i].X
|
||||
y := plt.Y.Norm(e.XYs[i].Y)
|
||||
bs = append(bs,
|
||||
plot.GlyphBox{X: plt.X.Norm(x - err.Low), Y: y, Rectangle: rect},
|
||||
plot.GlyphBox{X: plt.X.Norm(x + err.High), Y: y, Rectangle: rect})
|
||||
}
|
||||
return bs
|
||||
}
|
||||
216
vendor/gonum.org/v1/plot/plotter/field.go
generated
vendored
Normal file
216
vendor/gonum.org/v1/plot/plotter/field.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright ©2019 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 (
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// FieldXY describes a two dimensional vector field where the
|
||||
// X and Y coordinates are arranged on a rectangular grid.
|
||||
type FieldXY interface {
|
||||
// Dims returns the dimensions of the grid.
|
||||
Dims() (c, r int)
|
||||
|
||||
// Vector returns the value of a vector field at (c, r).
|
||||
// It will panic if c or r are out of bounds for the field.
|
||||
Vector(c, r int) XY
|
||||
|
||||
// X returns the coordinate for the column at the index c.
|
||||
// It will panic if c is out of bounds for the grid.
|
||||
X(c int) float64
|
||||
|
||||
// Y returns the coordinate for the row at the index r.
|
||||
// It will panic if r is out of bounds for the grid.
|
||||
Y(r int) float64
|
||||
}
|
||||
|
||||
// Field implements the Plotter interface, drawing
|
||||
// a vector field of the values in the FieldXY field.
|
||||
type Field struct {
|
||||
FieldXY FieldXY
|
||||
|
||||
// DrawGlyph is the user hook to draw a field
|
||||
// vector glyph. The function should draw a unit
|
||||
// vector to (1, 0) on the vg.Canvas, c with the
|
||||
// sty LineStyle. The Field plotter will rotate
|
||||
// and scale the unit vector appropriately.
|
||||
// If the magnitude of v is zero, no scaling or
|
||||
// rotation is performed.
|
||||
//
|
||||
// The direction and magnitude of v can be used
|
||||
// to determine properties of the glyph drawing
|
||||
// but should not be used to determine size or
|
||||
// directions of the glyph.
|
||||
//
|
||||
// If DrawGlyph is nil, a simple arrow will be
|
||||
// drawn.
|
||||
DrawGlyph func(c vg.Canvas, sty draw.LineStyle, v XY)
|
||||
|
||||
// LineStyle is the style of the line used to
|
||||
// render vectors when DrawGlyph is nil.
|
||||
// Otherwise it is passed to DrawGlyph.
|
||||
LineStyle draw.LineStyle
|
||||
|
||||
// max define the dynamic range of the field.
|
||||
max float64
|
||||
}
|
||||
|
||||
// NewField creates a new vector field plotter.
|
||||
func NewField(f FieldXY) *Field {
|
||||
max := math.Inf(-1)
|
||||
c, r := f.Dims()
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
v := f.Vector(i, j)
|
||||
d := math.Hypot(v.X, v.Y)
|
||||
if math.IsNaN(d) {
|
||||
continue
|
||||
}
|
||||
max = math.Max(max, d)
|
||||
}
|
||||
}
|
||||
|
||||
return &Field{
|
||||
FieldXY: f,
|
||||
LineStyle: DefaultLineStyle,
|
||||
max: max,
|
||||
}
|
||||
}
|
||||
|
||||
// Plot implements the Plot method of the plot.Plotter interface.
|
||||
func (f *Field) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
c.Push()
|
||||
defer c.Pop()
|
||||
c.SetLineStyle(f.LineStyle)
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
|
||||
cols, rows := f.FieldXY.Dims()
|
||||
for i := 0; i < cols; i++ {
|
||||
var right, left float64
|
||||
switch i {
|
||||
case 0:
|
||||
if cols == 1 {
|
||||
right = 0.5
|
||||
} else {
|
||||
right = (f.FieldXY.X(1) - f.FieldXY.X(0)) / 2
|
||||
}
|
||||
left = -right
|
||||
case cols - 1:
|
||||
right = (f.FieldXY.X(cols-1) - f.FieldXY.X(cols-2)) / 2
|
||||
left = -right
|
||||
default:
|
||||
right = (f.FieldXY.X(i+1) - f.FieldXY.X(i)) / 2
|
||||
left = -(f.FieldXY.X(i) - f.FieldXY.X(i-1)) / 2
|
||||
}
|
||||
|
||||
for j := 0; j < rows; j++ {
|
||||
var up, down float64
|
||||
switch j {
|
||||
case 0:
|
||||
if rows == 1 {
|
||||
up = 0.5
|
||||
} else {
|
||||
up = (f.FieldXY.Y(1) - f.FieldXY.Y(0)) / 2
|
||||
}
|
||||
down = -up
|
||||
case rows - 1:
|
||||
up = (f.FieldXY.Y(rows-1) - f.FieldXY.Y(rows-2)) / 2
|
||||
down = -up
|
||||
default:
|
||||
up = (f.FieldXY.Y(j+1) - f.FieldXY.Y(j)) / 2
|
||||
down = -(f.FieldXY.Y(j) - f.FieldXY.Y(j-1)) / 2
|
||||
}
|
||||
|
||||
x, y := trX(f.FieldXY.X(i)+left), trY(f.FieldXY.Y(j)+down)
|
||||
dx, dy := trX(f.FieldXY.X(i)+right), trY(f.FieldXY.Y(j)+up)
|
||||
|
||||
if !c.Contains(vg.Point{X: x, Y: y}) || !c.Contains(vg.Point{X: dx, Y: dy}) {
|
||||
continue
|
||||
}
|
||||
|
||||
c.Push()
|
||||
c.Translate(vg.Point{X: (x + dx) / 2, Y: (y + dy) / 2})
|
||||
|
||||
v := f.FieldXY.Vector(i, j)
|
||||
s := math.Hypot(v.X, v.Y) / (2 * f.max)
|
||||
// Do not scale when the vector is zero, otherwise the
|
||||
// user cannot render special-case glyphs for that case.
|
||||
if s != 0 {
|
||||
c.Rotate(math.Atan2(v.Y, v.X))
|
||||
c.Scale(s*float64(dx-x), s*float64(dy-y))
|
||||
}
|
||||
v.X /= f.max
|
||||
v.Y /= f.max
|
||||
|
||||
if f.DrawGlyph == nil {
|
||||
drawVector(c, v)
|
||||
} else {
|
||||
f.DrawGlyph(c, f.LineStyle, v)
|
||||
}
|
||||
c.Pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawVector(c vg.Canvas, v XY) {
|
||||
if math.Hypot(v.X, v.Y) == 0 {
|
||||
return
|
||||
}
|
||||
// TODO(kortschak): Improve this arrow.
|
||||
var pa vg.Path
|
||||
pa.Move(vg.Point{})
|
||||
pa.Line(vg.Point{X: 1, Y: 0})
|
||||
pa.Close()
|
||||
c.Stroke(pa)
|
||||
}
|
||||
|
||||
// DataRange implements the DataRange method
|
||||
// of the plot.DataRanger interface.
|
||||
func (f *Field) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
c, r := f.FieldXY.Dims()
|
||||
switch c {
|
||||
case 1: // Make a unit length when there is no neighbour.
|
||||
xmax = f.FieldXY.X(0) + 0.5
|
||||
xmin = f.FieldXY.X(0) - 0.5
|
||||
default:
|
||||
xmax = f.FieldXY.X(c-1) + (f.FieldXY.X(c-1)-f.FieldXY.X(c-2))/2
|
||||
xmin = f.FieldXY.X(0) - (f.FieldXY.X(1)-f.FieldXY.X(0))/2
|
||||
}
|
||||
switch r {
|
||||
case 1: // Make a unit length when there is no neighbour.
|
||||
ymax = f.FieldXY.Y(0) + 0.5
|
||||
ymin = f.FieldXY.Y(0) - 0.5
|
||||
default:
|
||||
ymax = f.FieldXY.Y(r-1) + (f.FieldXY.Y(r-1)-f.FieldXY.Y(r-2))/2
|
||||
ymin = f.FieldXY.Y(0) - (f.FieldXY.Y(1)-f.FieldXY.Y(0))/2
|
||||
}
|
||||
return xmin, xmax, ymin, ymax
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the GlyphBoxes method
|
||||
// of the plot.GlyphBoxer interface.
|
||||
func (f *Field) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
c, r := f.FieldXY.Dims()
|
||||
b := make([]plot.GlyphBox, 0, r*c)
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
b = append(b, plot.GlyphBox{
|
||||
X: plt.X.Norm(f.FieldXY.X(i)),
|
||||
Y: plt.Y.Norm(f.FieldXY.Y(j)),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: -5, Y: -5},
|
||||
Max: vg.Point{X: +5, Y: +5},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
63
vendor/gonum.org/v1/plot/plotter/functions.go
generated
vendored
Normal file
63
vendor/gonum.org/v1/plot/plotter/functions.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 (
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Function implements the Plotter interface,
|
||||
// drawing a line for the given function.
|
||||
type Function struct {
|
||||
F func(x float64) (y float64)
|
||||
|
||||
// XMin and XMax specify the range
|
||||
// of x values to pass to F.
|
||||
XMin, XMax float64
|
||||
|
||||
Samples int
|
||||
|
||||
draw.LineStyle
|
||||
}
|
||||
|
||||
// NewFunction returns a Function that plots F using
|
||||
// the default line style with 50 samples.
|
||||
func NewFunction(f func(float64) float64) *Function {
|
||||
return &Function{
|
||||
F: f,
|
||||
Samples: 50,
|
||||
LineStyle: DefaultLineStyle,
|
||||
}
|
||||
}
|
||||
|
||||
// Plot implements the Plotter interface, drawing a line
|
||||
// that connects each point in the Line.
|
||||
func (f *Function) Plot(c draw.Canvas, p *plot.Plot) {
|
||||
trX, trY := p.Transforms(&c)
|
||||
|
||||
min, max := f.XMin, f.XMax
|
||||
if min == 0 && max == 0 {
|
||||
min = p.X.Min
|
||||
max = p.X.Max
|
||||
}
|
||||
d := (max - min) / float64(f.Samples-1)
|
||||
line := make([]vg.Point, f.Samples)
|
||||
for i := range line {
|
||||
x := min + float64(i)*d
|
||||
line[i].X = trX(x)
|
||||
line[i].Y = trY(f.F(x))
|
||||
}
|
||||
c.StrokeLines(f.LineStyle, c.ClipLinesXY(line)...)
|
||||
}
|
||||
|
||||
// Thumbnail draws a line in the given style down the
|
||||
// center of a DrawArea as a thumbnail representation
|
||||
// of the LineStyle of the function.
|
||||
func (f Function) Thumbnail(c *draw.Canvas) {
|
||||
y := c.Center().Y
|
||||
c.StrokeLine2(f.LineStyle, c.Min.X, y, c.Max.X, y)
|
||||
}
|
||||
41
vendor/gonum.org/v1/plot/plotter/glyphbox.go
generated
vendored
Normal file
41
vendor/gonum.org/v1/plot/plotter/glyphbox.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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 (
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// GlyphBoxes implements the Plotter interface, drawing
|
||||
// all of the glyph boxes of the plot. This is intended for
|
||||
// debugging.
|
||||
type GlyphBoxes struct {
|
||||
draw.LineStyle
|
||||
}
|
||||
|
||||
func NewGlyphBoxes() *GlyphBoxes {
|
||||
g := new(GlyphBoxes)
|
||||
g.Color = color.RGBA{R: 255, A: 255}
|
||||
g.Width = vg.Points(0.25)
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GlyphBoxes) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
for _, b := range plt.GlyphBoxes(plt) {
|
||||
x := c.X(b.X) + b.Rectangle.Min.X
|
||||
y := c.Y(b.Y) + b.Rectangle.Min.Y
|
||||
c.StrokeLines(g.LineStyle, []vg.Point{
|
||||
{X: x, Y: y},
|
||||
{X: x + b.Rectangle.Size().X, Y: y},
|
||||
{X: x + b.Rectangle.Size().X, Y: y + b.Rectangle.Size().Y},
|
||||
{X: x, Y: y + b.Rectangle.Size().Y},
|
||||
{X: x, Y: y},
|
||||
})
|
||||
}
|
||||
}
|
||||
81
vendor/gonum.org/v1/plot/plotter/grid.go
generated
vendored
Normal file
81
vendor/gonum.org/v1/plot/plotter/grid.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 (
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultGridLineStyle is the default style for grid lines.
|
||||
DefaultGridLineStyle = draw.LineStyle{
|
||||
Color: color.Gray{128},
|
||||
Width: vg.Points(0.25),
|
||||
}
|
||||
)
|
||||
|
||||
// Grid implements the plot.Plotter interface, drawing
|
||||
// a set of grid lines at the major tick marks.
|
||||
type Grid struct {
|
||||
// Vertical is the style of the vertical lines.
|
||||
Vertical draw.LineStyle
|
||||
|
||||
// Horizontal is the style of the horizontal lines.
|
||||
Horizontal draw.LineStyle
|
||||
}
|
||||
|
||||
// NewGrid returns a new grid with both vertical and
|
||||
// horizontal lines using the default grid line style.
|
||||
func NewGrid() *Grid {
|
||||
return &Grid{
|
||||
Vertical: DefaultGridLineStyle,
|
||||
Horizontal: DefaultGridLineStyle,
|
||||
}
|
||||
}
|
||||
|
||||
// Plot implements the plot.Plotter interface.
|
||||
func (g *Grid) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trX, trY := plt.Transforms(&c)
|
||||
|
||||
var (
|
||||
ymin = c.Min.Y
|
||||
ymax = c.Max.Y
|
||||
xmin = c.Min.X
|
||||
xmax = c.Max.X
|
||||
)
|
||||
|
||||
if g.Vertical.Color == nil {
|
||||
goto horiz
|
||||
}
|
||||
for _, tk := range plt.X.Tick.Marker.Ticks(plt.X.Min, plt.X.Max) {
|
||||
if tk.IsMinor() {
|
||||
continue
|
||||
}
|
||||
x := trX(tk.Value)
|
||||
if x > xmax || x < xmin {
|
||||
continue
|
||||
}
|
||||
c.StrokeLine2(g.Vertical, x, ymin, x, ymax)
|
||||
}
|
||||
|
||||
horiz:
|
||||
if g.Horizontal.Color == nil {
|
||||
return
|
||||
}
|
||||
for _, tk := range plt.Y.Tick.Marker.Ticks(plt.Y.Min, plt.Y.Max) {
|
||||
if tk.IsMinor() {
|
||||
continue
|
||||
}
|
||||
y := trY(tk.Value)
|
||||
if y > ymax || y < ymin {
|
||||
continue
|
||||
}
|
||||
c.StrokeLine2(g.Horizontal, xmin, y, xmax, y)
|
||||
}
|
||||
}
|
||||
274
vendor/gonum.org/v1/plot/plotter/heat.go
generated
vendored
Normal file
274
vendor/gonum.org/v1/plot/plotter/heat.go
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
// 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 (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/palette"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// GridXYZ describes three dimensional data where the X and Y
|
||||
// coordinates are arranged on a rectangular grid.
|
||||
type GridXYZ interface {
|
||||
// Dims returns the dimensions of the grid.
|
||||
Dims() (c, r int)
|
||||
|
||||
// Z returns the value of a grid value at (c, r).
|
||||
// It will panic if c or r are out of bounds for the grid.
|
||||
Z(c, r int) float64
|
||||
|
||||
// X returns the coordinate for the column at the index c.
|
||||
// It will panic if c is out of bounds for the grid.
|
||||
X(c int) float64
|
||||
|
||||
// Y returns the coordinate for the row at the index r.
|
||||
// It will panic if r is out of bounds for the grid.
|
||||
Y(r int) float64
|
||||
}
|
||||
|
||||
// HeatMap implements the Plotter interface, drawing
|
||||
// a heat map of the values in the GridXYZ field.
|
||||
type HeatMap struct {
|
||||
GridXYZ GridXYZ
|
||||
|
||||
// Palette is the color palette used to render
|
||||
// the heat map. Palette must not be nil or
|
||||
// return a zero length []color.Color.
|
||||
Palette palette.Palette
|
||||
|
||||
// Underflow and Overflow are colors used to fill
|
||||
// heat map elements outside the dynamic range
|
||||
// defined by Min and Max.
|
||||
Underflow color.Color
|
||||
Overflow color.Color
|
||||
|
||||
// NaN is the color used to fill heat map elements
|
||||
// that are NaN or do not map to a unique palette
|
||||
// color.
|
||||
NaN color.Color
|
||||
|
||||
// Min and Max define the dynamic range of the
|
||||
// heat map.
|
||||
Min, Max float64
|
||||
|
||||
// Rasterized indicates whether the heatmap
|
||||
// should be produced using raster-based drawing.
|
||||
Rasterized bool
|
||||
}
|
||||
|
||||
// NewHeatMap creates as new heat map plotter for the given data,
|
||||
// using the provided palette. If g has Min and Max methods that return
|
||||
// a float, those returned values are used to set the respective HeatMap
|
||||
// fields. If the returned HeatMap is used when Min is greater than Max,
|
||||
// the Plot method will panic.
|
||||
func NewHeatMap(g GridXYZ, p palette.Palette) *HeatMap {
|
||||
var min, max float64
|
||||
type minMaxer interface {
|
||||
Min() float64
|
||||
Max() float64
|
||||
}
|
||||
switch g := g.(type) {
|
||||
case minMaxer:
|
||||
min, max = g.Min(), g.Max()
|
||||
default:
|
||||
min, max = math.Inf(1), math.Inf(-1)
|
||||
c, r := g.Dims()
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
v := g.Z(i, j)
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
min = math.Min(min, v)
|
||||
max = math.Max(max, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &HeatMap{
|
||||
GridXYZ: g,
|
||||
Palette: p,
|
||||
Min: min,
|
||||
Max: max,
|
||||
}
|
||||
}
|
||||
|
||||
// Plot implements the Plot method of the plot.Plotter interface.
|
||||
func (h *HeatMap) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
if h.Rasterized {
|
||||
h.plotRasterized(c, plt)
|
||||
} else {
|
||||
h.plotVectorized(c, plt)
|
||||
}
|
||||
}
|
||||
|
||||
// plotRasterized plots the heatmap using raster-based drawing.
|
||||
func (h *HeatMap) plotRasterized(c draw.Canvas, plt *plot.Plot) {
|
||||
cols, rows := h.GridXYZ.Dims()
|
||||
img := image.NewRGBA64(image.Rectangle{
|
||||
Min: image.Point{X: 0, Y: 0},
|
||||
Max: image.Point{X: cols, Y: rows},
|
||||
})
|
||||
|
||||
pal := h.Palette.Colors()
|
||||
ps := float64(len(pal)-1) / (h.Max - h.Min)
|
||||
for i := 0; i < cols; i++ {
|
||||
for j := 0; j < rows; j++ {
|
||||
var col color.Color
|
||||
switch v := h.GridXYZ.Z(i, j); {
|
||||
case v < h.Min:
|
||||
col = h.Underflow
|
||||
case v > h.Max:
|
||||
col = h.Overflow
|
||||
case math.IsNaN(v), math.IsInf(ps, 0):
|
||||
col = h.NaN
|
||||
default:
|
||||
col = pal[int((v-h.Min)*ps+0.5)] // Apply palette scaling.
|
||||
}
|
||||
|
||||
if col != nil {
|
||||
img.Set(i, rows-j-1, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmin, xmax, ymin, ymax := h.DataRange()
|
||||
pImg := NewImage(img, xmin, ymin, xmax, ymax)
|
||||
pImg.Plot(c, plt)
|
||||
}
|
||||
|
||||
// plotVectorized plots the heatmap using vector-based drawing.
|
||||
func (h *HeatMap) plotVectorized(c draw.Canvas, plt *plot.Plot) {
|
||||
if h.Min > h.Max {
|
||||
panic("contour: invalid Z range: min greater than max")
|
||||
}
|
||||
pal := h.Palette.Colors()
|
||||
if len(pal) == 0 {
|
||||
panic("heatmap: empty palette")
|
||||
}
|
||||
// ps scales the palette uniformly across the data range.
|
||||
ps := float64(len(pal)-1) / (h.Max - h.Min)
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
|
||||
var pa vg.Path
|
||||
cols, rows := h.GridXYZ.Dims()
|
||||
for i := 0; i < cols; i++ {
|
||||
var right, left float64
|
||||
switch i {
|
||||
case 0:
|
||||
if cols == 1 {
|
||||
right = 0.5
|
||||
} else {
|
||||
right = (h.GridXYZ.X(1) - h.GridXYZ.X(0)) / 2
|
||||
}
|
||||
left = -right
|
||||
case cols - 1:
|
||||
right = (h.GridXYZ.X(cols-1) - h.GridXYZ.X(cols-2)) / 2
|
||||
left = -right
|
||||
default:
|
||||
right = (h.GridXYZ.X(i+1) - h.GridXYZ.X(i)) / 2
|
||||
left = -(h.GridXYZ.X(i) - h.GridXYZ.X(i-1)) / 2
|
||||
}
|
||||
|
||||
for j := 0; j < rows; j++ {
|
||||
var up, down float64
|
||||
switch j {
|
||||
case 0:
|
||||
if rows == 1 {
|
||||
up = 0.5
|
||||
} else {
|
||||
up = (h.GridXYZ.Y(1) - h.GridXYZ.Y(0)) / 2
|
||||
}
|
||||
down = -up
|
||||
case rows - 1:
|
||||
up = (h.GridXYZ.Y(rows-1) - h.GridXYZ.Y(rows-2)) / 2
|
||||
down = -up
|
||||
default:
|
||||
up = (h.GridXYZ.Y(j+1) - h.GridXYZ.Y(j)) / 2
|
||||
down = -(h.GridXYZ.Y(j) - h.GridXYZ.Y(j-1)) / 2
|
||||
}
|
||||
|
||||
x, y := trX(h.GridXYZ.X(i)+left), trY(h.GridXYZ.Y(j)+down)
|
||||
dx, dy := trX(h.GridXYZ.X(i)+right), trY(h.GridXYZ.Y(j)+up)
|
||||
|
||||
if !c.Contains(vg.Point{X: x, Y: y}) || !c.Contains(vg.Point{X: dx, Y: dy}) {
|
||||
continue
|
||||
}
|
||||
|
||||
pa = pa[:0]
|
||||
pa.Move(vg.Point{X: x, Y: y})
|
||||
pa.Line(vg.Point{X: dx, Y: y})
|
||||
pa.Line(vg.Point{X: dx, Y: dy})
|
||||
pa.Line(vg.Point{X: x, Y: dy})
|
||||
pa.Close()
|
||||
|
||||
var col color.Color
|
||||
switch v := h.GridXYZ.Z(i, j); {
|
||||
case v < h.Min:
|
||||
col = h.Underflow
|
||||
case v > h.Max:
|
||||
col = h.Overflow
|
||||
case math.IsNaN(v), math.IsInf(ps, 0):
|
||||
col = h.NaN
|
||||
default:
|
||||
col = pal[int((v-h.Min)*ps+0.5)] // Apply palette scaling.
|
||||
}
|
||||
if col != nil {
|
||||
c.SetColor(col)
|
||||
c.Fill(pa)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange implements the DataRange method
|
||||
// of the plot.DataRanger interface.
|
||||
func (h *HeatMap) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
c, r := h.GridXYZ.Dims()
|
||||
switch c {
|
||||
case 1: // Make a unit length when there is no neighbour.
|
||||
xmax = h.GridXYZ.X(0) + 0.5
|
||||
xmin = h.GridXYZ.X(0) - 0.5
|
||||
default:
|
||||
xmax = h.GridXYZ.X(c-1) + (h.GridXYZ.X(c-1)-h.GridXYZ.X(c-2))/2
|
||||
xmin = h.GridXYZ.X(0) - (h.GridXYZ.X(1)-h.GridXYZ.X(0))/2
|
||||
}
|
||||
switch r {
|
||||
case 1: // Make a unit length when there is no neighbour.
|
||||
ymax = h.GridXYZ.Y(0) + 0.5
|
||||
ymin = h.GridXYZ.Y(0) - 0.5
|
||||
default:
|
||||
ymax = h.GridXYZ.Y(r-1) + (h.GridXYZ.Y(r-1)-h.GridXYZ.Y(r-2))/2
|
||||
ymin = h.GridXYZ.Y(0) - (h.GridXYZ.Y(1)-h.GridXYZ.Y(0))/2
|
||||
}
|
||||
return xmin, xmax, ymin, ymax
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the GlyphBoxes method
|
||||
// of the plot.GlyphBoxer interface.
|
||||
func (h *HeatMap) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
c, r := h.GridXYZ.Dims()
|
||||
b := make([]plot.GlyphBox, 0, r*c)
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
b = append(b, plot.GlyphBox{
|
||||
X: plt.X.Norm(h.GridXYZ.X(i)),
|
||||
Y: plt.Y.Norm(h.GridXYZ.Y(j)),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: -5, Y: -5},
|
||||
Max: vg.Point{X: +5, Y: +5},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
230
vendor/gonum.org/v1/plot/plotter/histogram.go
generated
vendored
Normal file
230
vendor/gonum.org/v1/plot/plotter/histogram.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Histogram implements the Plotter interface,
|
||||
// drawing a histogram of the data.
|
||||
type Histogram struct {
|
||||
// Bins is the set of bins for this histogram.
|
||||
Bins []HistogramBin
|
||||
|
||||
// Width is the width of each bin.
|
||||
Width float64
|
||||
|
||||
// FillColor is the color used to fill each
|
||||
// bar of the histogram. If the color is nil
|
||||
// then the bars are not filled.
|
||||
FillColor color.Color
|
||||
|
||||
// LineStyle is the style of the outline of each
|
||||
// bar of the histogram.
|
||||
draw.LineStyle
|
||||
|
||||
// LogY allows rendering with a log-scaled Y axis.
|
||||
// When enabled, histogram bins with no entries will be discarded from
|
||||
// the histogram's DataRange.
|
||||
// The lowest Y value for the DataRange will be corrected to leave an
|
||||
// arbitrary amount of height for the smallest bin entry so it is visible
|
||||
// on the final plot.
|
||||
LogY bool
|
||||
}
|
||||
|
||||
// NewHistogram returns a new histogram
|
||||
// that represents the distribution of values
|
||||
// using the given number of bins.
|
||||
//
|
||||
// Each y value is assumed to be the frequency
|
||||
// count for the corresponding x.
|
||||
//
|
||||
// If the number of bins is non-positive than
|
||||
// a reasonable default is used.
|
||||
func NewHistogram(xy XYer, n int) (*Histogram, error) {
|
||||
if n <= 0 {
|
||||
return nil, errors.New("Histogram with non-positive number of bins")
|
||||
}
|
||||
bins, width := binPoints(xy, n)
|
||||
return &Histogram{
|
||||
Bins: bins,
|
||||
Width: width,
|
||||
FillColor: color.Gray{128},
|
||||
LineStyle: DefaultLineStyle,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewHist returns a new histogram, as in
|
||||
// NewHistogram, except that it accepts a Valuer
|
||||
// instead of an XYer.
|
||||
func NewHist(vs Valuer, n int) (*Histogram, error) {
|
||||
return NewHistogram(unitYs{vs}, n)
|
||||
}
|
||||
|
||||
type unitYs struct {
|
||||
Valuer
|
||||
}
|
||||
|
||||
func (u unitYs) XY(i int) (float64, float64) {
|
||||
return u.Value(i), 1.0
|
||||
}
|
||||
|
||||
// Plot implements the Plotter interface, drawing a line
|
||||
// that connects each point in the Line.
|
||||
func (h *Histogram) Plot(c draw.Canvas, p *plot.Plot) {
|
||||
trX, trY := p.Transforms(&c)
|
||||
|
||||
for _, bin := range h.Bins {
|
||||
ymin := c.Min.Y
|
||||
ymax := c.Min.Y
|
||||
if bin.Weight != 0 {
|
||||
ymax = trY(bin.Weight)
|
||||
}
|
||||
xmin := trX(bin.Min)
|
||||
xmax := trX(bin.Max)
|
||||
pts := []vg.Point{
|
||||
{X: xmin, Y: ymin},
|
||||
{X: xmax, Y: ymin},
|
||||
{X: xmax, Y: ymax},
|
||||
{X: xmin, Y: ymax},
|
||||
}
|
||||
if h.FillColor != nil {
|
||||
c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
|
||||
}
|
||||
pts = append(pts, vg.Point{X: xmin, Y: ymin})
|
||||
c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum X and Y values
|
||||
func (h *Histogram) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
xmin = math.Inf(+1)
|
||||
xmax = math.Inf(-1)
|
||||
ymin = math.Inf(+1)
|
||||
ymax = math.Inf(-1)
|
||||
ylow := math.Inf(+1) // ylow will hold the smallest non-zero y value.
|
||||
for _, bin := range h.Bins {
|
||||
if bin.Max > xmax {
|
||||
xmax = bin.Max
|
||||
}
|
||||
if bin.Min < xmin {
|
||||
xmin = bin.Min
|
||||
}
|
||||
if bin.Weight > ymax {
|
||||
ymax = bin.Weight
|
||||
}
|
||||
if bin.Weight < ymin {
|
||||
ymin = bin.Weight
|
||||
}
|
||||
if bin.Weight != 0 && bin.Weight < ylow {
|
||||
ylow = bin.Weight
|
||||
}
|
||||
}
|
||||
switch h.LogY {
|
||||
case true:
|
||||
if ymin == 0 && !math.IsInf(ylow, +1) {
|
||||
// Reserve a bit of space for the smallest bin to be displayed still.
|
||||
ymin = ylow * 0.5
|
||||
}
|
||||
default:
|
||||
ymin = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Normalize normalizes the histogram so that the
|
||||
// total area beneath it sums to a given value.
|
||||
func (h *Histogram) Normalize(sum float64) {
|
||||
mass := 0.0
|
||||
for _, b := range h.Bins {
|
||||
mass += b.Weight
|
||||
}
|
||||
for i := range h.Bins {
|
||||
h.Bins[i].Weight *= sum / (h.Width * mass)
|
||||
}
|
||||
}
|
||||
|
||||
// Thumbnail draws a rectangle in the given style of the histogram.
|
||||
func (h *Histogram) Thumbnail(c *draw.Canvas) {
|
||||
ymin := c.Min.Y
|
||||
ymax := c.Max.Y
|
||||
xmin := c.Min.X
|
||||
xmax := c.Max.X
|
||||
|
||||
pts := []vg.Point{
|
||||
{X: xmin, Y: ymin},
|
||||
{X: xmax, Y: ymin},
|
||||
{X: xmax, Y: ymax},
|
||||
{X: xmin, Y: ymax},
|
||||
}
|
||||
if h.FillColor != nil {
|
||||
c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
|
||||
}
|
||||
pts = append(pts, vg.Point{X: xmin, Y: ymin})
|
||||
c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
|
||||
}
|
||||
|
||||
// binPoints returns a slice containing the
|
||||
// given number of bins, and the width of
|
||||
// each bin.
|
||||
//
|
||||
// If the given number of bins is not positive
|
||||
// then a reasonable default is used. The
|
||||
// default is the square root of the sum of
|
||||
// the y values.
|
||||
func binPoints(xys XYer, n int) (bins []HistogramBin, width float64) {
|
||||
xmin, xmax := Range(XValues{xys})
|
||||
if n <= 0 {
|
||||
m := 0.0
|
||||
for i := 0; i < xys.Len(); i++ {
|
||||
_, y := xys.XY(i)
|
||||
m += math.Max(y, 1.0)
|
||||
}
|
||||
n = int(math.Ceil(math.Sqrt(m)))
|
||||
}
|
||||
if n < 1 || xmax <= xmin {
|
||||
n = 1
|
||||
}
|
||||
|
||||
bins = make([]HistogramBin, n)
|
||||
|
||||
w := (xmax - xmin) / float64(n)
|
||||
if w == 0 {
|
||||
w = 1
|
||||
}
|
||||
for i := range bins {
|
||||
bins[i].Min = xmin + float64(i)*w
|
||||
bins[i].Max = xmin + float64(i+1)*w
|
||||
}
|
||||
|
||||
for i := 0; i < xys.Len(); i++ {
|
||||
x, y := xys.XY(i)
|
||||
bin := int((x - xmin) / w)
|
||||
if x == xmax {
|
||||
bin = n - 1
|
||||
}
|
||||
if bin < 0 || bin >= n {
|
||||
panic(fmt.Sprintf("%g, xmin=%g, xmax=%g, w=%g, bin=%d, n=%d\n",
|
||||
x, xmin, xmax, w, bin, n))
|
||||
}
|
||||
bins[bin].Weight += y
|
||||
}
|
||||
return bins, w
|
||||
}
|
||||
|
||||
// A HistogramBin approximates the number of values
|
||||
// within a range by a single number (the weight).
|
||||
type HistogramBin struct {
|
||||
Min, Max float64
|
||||
Weight float64
|
||||
}
|
||||
144
vendor/gonum.org/v1/plot/plotter/image.go
generated
vendored
Normal file
144
vendor/gonum.org/v1/plot/plotter/image.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright ©2016 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 (
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Image is a plotter that draws a scaled, raster image.
|
||||
type Image struct {
|
||||
img image.Image
|
||||
cols int
|
||||
rows int
|
||||
xmin, xmax, dx float64
|
||||
ymin, ymax, dy float64
|
||||
}
|
||||
|
||||
// NewImage creates a new image plotter.
|
||||
// Image will plot img inside the rectangle defined by the
|
||||
// (xmin, ymin) and (xmax, ymax) points given in the data space.
|
||||
// The img will be scaled to fit inside the rectangle.
|
||||
func NewImage(img image.Image, xmin, ymin, xmax, ymax float64) *Image {
|
||||
if src, ok := img.(*image.Uniform); ok {
|
||||
img = uniform{
|
||||
src,
|
||||
image.Rect(0, 0, int(xmax-xmin+0.5), int(ymax-ymin+0.5)),
|
||||
}
|
||||
}
|
||||
bounds := img.Bounds()
|
||||
cols := bounds.Dx()
|
||||
rows := bounds.Dy()
|
||||
dx := math.Abs(xmax-xmin) / float64(cols)
|
||||
dy := math.Abs(ymax-ymin) / float64(rows)
|
||||
return &Image{
|
||||
img: img,
|
||||
cols: cols,
|
||||
rows: rows,
|
||||
xmin: xmin,
|
||||
xmax: xmax,
|
||||
dx: dx,
|
||||
ymin: ymin,
|
||||
ymax: ymax,
|
||||
dy: dy,
|
||||
}
|
||||
}
|
||||
|
||||
// Plot implements the Plot method of the plot.Plotter interface.
|
||||
func (img *Image) Plot(c draw.Canvas, p *plot.Plot) {
|
||||
trX, trY := p.Transforms(&c)
|
||||
xmin := trX(img.xmin)
|
||||
ymin := trY(img.ymin)
|
||||
xmax := trX(img.xmax)
|
||||
ymax := trY(img.ymax)
|
||||
rect := vg.Rectangle{
|
||||
Min: vg.Point{X: xmin, Y: ymin},
|
||||
Max: vg.Point{X: xmax, Y: ymax},
|
||||
}
|
||||
c.DrawImage(rect, img.transformFor(p))
|
||||
}
|
||||
|
||||
// DataRange implements the DataRange method
|
||||
// of the plot.DataRanger interface.
|
||||
func (img *Image) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
return img.xmin, img.xmax, img.ymin, img.ymax
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the GlyphBoxes method
|
||||
// of the plot.GlyphBoxer interface.
|
||||
func (img *Image) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
return nil
|
||||
}
|
||||
|
||||
// transform warps the image to align with non-linear axes.
|
||||
func (img *Image) transformFor(p *plot.Plot) image.Image {
|
||||
_, xLinear := p.X.Scale.(plot.LinearScale)
|
||||
_, yLinear := p.Y.Scale.(plot.LinearScale)
|
||||
if xLinear && yLinear {
|
||||
return img.img
|
||||
}
|
||||
b := img.img.Bounds()
|
||||
o := image.NewNRGBA64(b)
|
||||
for c := 0; c < img.cols; c++ {
|
||||
// Find the equivalent image column after applying axis transforms.
|
||||
cTrans := int(p.X.Norm(img.x(c)) * float64(img.cols))
|
||||
// Find the equivalent column of the previous image column after applying
|
||||
// axis transforms.
|
||||
cPrevTrans := int(p.X.Norm(img.x(maxInt(c-1, 0))) * float64(img.cols))
|
||||
for r := 0; r < img.rows; r++ {
|
||||
// Find the equivalent image row after applying axis transforms.
|
||||
rTrans := int(p.Y.Norm(img.y(r)) * float64(img.rows))
|
||||
// Find the equivalent row of the previous image row after applying
|
||||
// axis transforms.
|
||||
rPrevTrans := int(p.Y.Norm(img.y(maxInt(r-1, 0))) * float64(img.rows))
|
||||
crColor := img.img.At(c, img.rows-r-1)
|
||||
// Set all the pixels in the new image between (cPrevTrans, rPrevTrans)
|
||||
// and (cTrans, rTrans) to the color at (c,r) in the original image.
|
||||
// TODO: Improve interpolation.
|
||||
for cPrime := cPrevTrans; cPrime <= cTrans; cPrime++ {
|
||||
for rPrime := rPrevTrans; rPrime <= rTrans; rPrime++ {
|
||||
o.Set(cPrime, img.rows-rPrime-1, crColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func maxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (img *Image) x(c int) float64 {
|
||||
if c >= img.cols || c < 0 {
|
||||
panic("plotter/image: illegal range")
|
||||
}
|
||||
return img.xmin + float64(c)*img.dx
|
||||
}
|
||||
|
||||
func (img *Image) y(r int) float64 {
|
||||
if r >= img.rows || r < 0 {
|
||||
panic("plotter/image: illegal range")
|
||||
}
|
||||
return img.ymin + float64(r)*img.dy
|
||||
}
|
||||
|
||||
// uniform is a cropped uniform image.
|
||||
type uniform struct {
|
||||
*image.Uniform
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
func (img uniform) Bounds() image.Rectangle {
|
||||
return img.rect
|
||||
}
|
||||
279
vendor/gonum.org/v1/plot/plotter/johnson.go
generated
vendored
Normal file
279
vendor/gonum.org/v1/plot/plotter/johnson.go
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
// 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
|
||||
|
||||
// johnson implements Johnson's "Finding all the elementary
|
||||
// circuits of a directed graph" algorithm. SIAM J. Comput. 4(1):1975.
|
||||
//
|
||||
// Comments in the johnson methods are kept in sync with the comments
|
||||
// and labels from the paper.
|
||||
type johnson struct {
|
||||
adjacent graph // SCC adjacency list.
|
||||
b []set // Johnson's "B-list".
|
||||
blocked []bool
|
||||
s int
|
||||
|
||||
stack []int
|
||||
|
||||
result [][]int
|
||||
}
|
||||
|
||||
// cyclesIn returns the set of elementary cycles in the graph g.
|
||||
func cyclesIn(g graph) [][]int {
|
||||
j := johnson{
|
||||
adjacent: g.clone(),
|
||||
b: make([]set, len(g)),
|
||||
blocked: make([]bool, len(g)),
|
||||
}
|
||||
|
||||
// len(j.adjacent) will be the order of g until Tarjan's analysis
|
||||
// finds no SCC, at which point t.sccSubGraph returns nil and the
|
||||
// loop breaks.
|
||||
for j.s < len(j.adjacent)-1 {
|
||||
// We use the previous SCC adjacency to reduce the work needed.
|
||||
t := newTarjan(j.adjacent.subgraph(j.s))
|
||||
// A_k = adjacency structure of strong component K with least
|
||||
// vertex in subgraph of G induced by {s, s+1, ... ,n}.
|
||||
j.adjacent = t.sccSubGraph(2) // Only allow SCCs with >= 2 vertices.
|
||||
if len(j.adjacent) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// s = least vertex in V_k
|
||||
for _, v := range j.adjacent {
|
||||
s := len(j.adjacent)
|
||||
for n := range v {
|
||||
if n < s {
|
||||
s = n
|
||||
}
|
||||
}
|
||||
if s < j.s {
|
||||
j.s = s
|
||||
}
|
||||
}
|
||||
for i, v := range j.adjacent {
|
||||
if len(v) > 0 {
|
||||
j.blocked[i] = false
|
||||
j.b[i] = make(set)
|
||||
}
|
||||
}
|
||||
|
||||
//L3:
|
||||
_ = j.circuit(j.s)
|
||||
j.s++
|
||||
}
|
||||
|
||||
return j.result
|
||||
}
|
||||
|
||||
// circuit is the CIRCUIT sub-procedure in the paper.
|
||||
func (j *johnson) circuit(v int) bool {
|
||||
f := false
|
||||
j.stack = append(j.stack, v)
|
||||
j.blocked[v] = true
|
||||
|
||||
//L1:
|
||||
for w := range j.adjacent[v] {
|
||||
if w == j.s {
|
||||
// Output circuit composed of stack followed by s.
|
||||
r := make([]int, len(j.stack)+1)
|
||||
copy(r, j.stack)
|
||||
r[len(r)-1] = j.s
|
||||
j.result = append(j.result, r)
|
||||
f = true
|
||||
} else if !j.blocked[w] {
|
||||
if j.circuit(w) {
|
||||
f = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//L2:
|
||||
if f {
|
||||
j.unblock(v)
|
||||
} else {
|
||||
for w := range j.adjacent[v] {
|
||||
j.b[w][v] = struct{}{}
|
||||
}
|
||||
}
|
||||
j.stack = j.stack[:len(j.stack)-1]
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// unblock is the UNBLOCK sub-procedure in the paper.
|
||||
func (j *johnson) unblock(u int) {
|
||||
j.blocked[u] = false
|
||||
for w := range j.b[u] {
|
||||
delete(j.b[u], w)
|
||||
if j.blocked[w] {
|
||||
j.unblock(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// tarjan implements Tarjan's strongly connected component finding
|
||||
// algorithm. The implementation is from the pseudocode at
|
||||
//
|
||||
// http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm?oldid=642744644
|
||||
type tarjan struct {
|
||||
g graph
|
||||
|
||||
index int
|
||||
indexTable []int
|
||||
lowLink []int
|
||||
onStack []bool
|
||||
|
||||
stack []int
|
||||
|
||||
sccs [][]int
|
||||
}
|
||||
|
||||
// newTarjan returns a tarjan with the sccs field filled with the
|
||||
// strongly connected components of the directed graph g.
|
||||
func newTarjan(g graph) *tarjan {
|
||||
t := tarjan{
|
||||
g: g,
|
||||
|
||||
indexTable: make([]int, len(g)),
|
||||
lowLink: make([]int, len(g)),
|
||||
onStack: make([]bool, len(g)),
|
||||
}
|
||||
for v := range t.g {
|
||||
if t.indexTable[v] == 0 {
|
||||
t.strongconnect(v)
|
||||
}
|
||||
}
|
||||
return &t
|
||||
}
|
||||
|
||||
// strongconnect is the strongconnect function described in the
|
||||
// wikipedia article.
|
||||
func (t *tarjan) strongconnect(v int) {
|
||||
// Set the depth index for v to the smallest unused index.
|
||||
t.index++
|
||||
t.indexTable[v] = t.index
|
||||
t.lowLink[v] = t.index
|
||||
t.stack = append(t.stack, v)
|
||||
t.onStack[v] = true
|
||||
|
||||
// Consider successors of v.
|
||||
for w := range t.g[v] {
|
||||
if t.indexTable[w] == 0 {
|
||||
// Successor w has not yet been visited; recur on it.
|
||||
t.strongconnect(w)
|
||||
t.lowLink[v] = min(t.lowLink[v], t.lowLink[w])
|
||||
} else if t.onStack[w] {
|
||||
// Successor w is in stack s and hence in the current SCC.
|
||||
t.lowLink[v] = min(t.lowLink[v], t.indexTable[w])
|
||||
}
|
||||
}
|
||||
|
||||
// If v is a root node, pop the stack and generate an SCC.
|
||||
if t.lowLink[v] == t.indexTable[v] {
|
||||
// Start a new strongly connected component.
|
||||
var (
|
||||
scc []int
|
||||
w int
|
||||
)
|
||||
for {
|
||||
w, t.stack = t.stack[len(t.stack)-1], t.stack[:len(t.stack)-1]
|
||||
t.onStack[w] = false
|
||||
// Add w to current strongly connected component.
|
||||
scc = append(scc, w)
|
||||
if w == v {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Output the current strongly connected component.
|
||||
t.sccs = append(t.sccs, scc)
|
||||
}
|
||||
}
|
||||
|
||||
// sccSubGraph returns the graph of the tarjan's strongly connected
|
||||
// components with each SCC containing at least min vertices.
|
||||
// sccSubGraph returns nil if there is no SCC with at least min
|
||||
// members.
|
||||
func (t *tarjan) sccSubGraph(min int) graph {
|
||||
if len(t.g) == 0 {
|
||||
return nil
|
||||
}
|
||||
sub := make(graph, len(t.g))
|
||||
|
||||
var n int
|
||||
for _, scc := range t.sccs {
|
||||
if len(scc) < min {
|
||||
continue
|
||||
}
|
||||
n++
|
||||
for _, u := range scc {
|
||||
for _, v := range scc {
|
||||
if _, ok := t.g[u][v]; ok {
|
||||
if sub[u] == nil {
|
||||
sub[u] = make(set)
|
||||
}
|
||||
sub[u][v] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return sub
|
||||
}
|
||||
|
||||
// set is an integer set.
|
||||
type set map[int]struct{}
|
||||
|
||||
// graph is an edge list representation of a graph.
|
||||
type graph []set
|
||||
|
||||
// remove deletes edges that make up the given paths from the graph.
|
||||
func (g graph) remove(paths [][]int) {
|
||||
for _, p := range paths {
|
||||
for i, u := range p[:len(p)-1] {
|
||||
delete(g[u], p[i+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// subgraph returns a subgraph of g induced by {s, s+1, ... , n}. The
|
||||
// subgraph is destructively generated in g.
|
||||
func (g graph) subgraph(s int) graph {
|
||||
for u := range g[:s] {
|
||||
g[u] = nil
|
||||
}
|
||||
for u, e := range g[s:] {
|
||||
for v := range e {
|
||||
if v < s {
|
||||
delete(g[u+s], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// clone returns a deep copy of the graph g.
|
||||
func (g graph) clone() graph {
|
||||
c := make(graph, len(g))
|
||||
for u, e := range g {
|
||||
for v := range e {
|
||||
if c[u] == nil {
|
||||
c[u] = make(set)
|
||||
}
|
||||
c[u][v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
125
vendor/gonum.org/v1/plot/plotter/labels.go
generated
vendored
Normal file
125
vendor/gonum.org/v1/plot/plotter/labels.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
// 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"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultFont is the default font for label text.
|
||||
DefaultFont = plot.DefaultFont
|
||||
|
||||
// DefaultFontSize is the default font.
|
||||
DefaultFontSize = vg.Points(10)
|
||||
)
|
||||
|
||||
// Labels implements the Plotter interface,
|
||||
// drawing a set of labels at specified points.
|
||||
type Labels struct {
|
||||
XYs
|
||||
|
||||
// Labels is the set of labels corresponding
|
||||
// to each point.
|
||||
Labels []string
|
||||
|
||||
// TextStyle is the style of the label text. Each label
|
||||
// can have a different text style.
|
||||
TextStyle []text.Style
|
||||
|
||||
// Offset is added directly to the final label location.
|
||||
Offset vg.Point
|
||||
}
|
||||
|
||||
// NewLabels returns a new Labels using the DefaultFont and
|
||||
// the DefaultFontSize.
|
||||
func NewLabels(d XYLabeller) (*Labels, error) {
|
||||
xys, err := CopyXYs(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.Len() != len(xys) {
|
||||
return nil, errors.New("plotter: number of points does not match the number of labels")
|
||||
}
|
||||
|
||||
strs := make([]string, d.Len())
|
||||
for i := range strs {
|
||||
strs[i] = d.Label(i)
|
||||
}
|
||||
|
||||
styles := make([]text.Style, d.Len())
|
||||
for i := range styles {
|
||||
styles[i] = text.Style{
|
||||
Font: font.From(DefaultFont, DefaultFontSize),
|
||||
Handler: plot.DefaultTextHandler,
|
||||
}
|
||||
}
|
||||
|
||||
return &Labels{
|
||||
XYs: xys,
|
||||
Labels: strs,
|
||||
TextStyle: styles,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Plot implements the Plotter interface, drawing labels.
|
||||
func (l *Labels) Plot(c draw.Canvas, p *plot.Plot) {
|
||||
trX, trY := p.Transforms(&c)
|
||||
for i, label := range l.Labels {
|
||||
pt := vg.Point{X: trX(l.XYs[i].X), Y: trY(l.XYs[i].Y)}
|
||||
if !c.Contains(pt) {
|
||||
continue
|
||||
}
|
||||
pt = pt.Add(l.Offset)
|
||||
c.FillText(l.TextStyle[i], pt, label)
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum X and Y values
|
||||
func (l *Labels) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
return XYRange(l)
|
||||
}
|
||||
|
||||
// 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] = plot.GlyphBox{
|
||||
X: p.X.Norm(l.XYs[i].X),
|
||||
Y: p.Y.Norm(l.XYs[i].Y),
|
||||
Rectangle: l.TextStyle[i].Rectangle(label).Add(l.Offset),
|
||||
}
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// XYLabeller combines the XYer and Labeller types.
|
||||
type XYLabeller interface {
|
||||
XYer
|
||||
Labeller
|
||||
}
|
||||
|
||||
// XYLabels holds XY data with labels.
|
||||
// The ith label corresponds to the ith XY.
|
||||
type XYLabels struct {
|
||||
XYs
|
||||
Labels []string
|
||||
}
|
||||
|
||||
// Label returns the label for point index i.
|
||||
func (l XYLabels) Label(i int) string {
|
||||
return l.Labels[i]
|
||||
}
|
||||
|
||||
var _ XYLabeller = (*XYLabels)(nil)
|
||||
210
vendor/gonum.org/v1/plot/plotter/line.go
generated
vendored
Normal file
210
vendor/gonum.org/v1/plot/plotter/line.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
// 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 (
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// StepKind specifies a form of a connection of two consecutive points.
|
||||
type StepKind int
|
||||
|
||||
const (
|
||||
// NoStep connects two points by simple line
|
||||
NoStep StepKind = iota
|
||||
|
||||
// PreStep connects two points by following lines: vertical, horizontal.
|
||||
PreStep
|
||||
|
||||
// MidStep connects two points by following lines: horizontal, vertical, horizontal.
|
||||
// Vertical line is placed in the middle of the interval.
|
||||
MidStep
|
||||
|
||||
// PostStep connects two points by following lines: horizontal, vertical.
|
||||
PostStep
|
||||
)
|
||||
|
||||
// Line implements the Plotter interface, drawing a line.
|
||||
type Line struct {
|
||||
// XYs is a copy of the points for this line.
|
||||
XYs
|
||||
|
||||
// StepStyle is the kind of the step line.
|
||||
StepStyle StepKind
|
||||
|
||||
// LineStyle is the style of the line connecting the points.
|
||||
// Use zero width to disable lines.
|
||||
draw.LineStyle
|
||||
|
||||
// FillColor is the color to fill the area below the plot.
|
||||
// Use nil to disable the filling. This is the default.
|
||||
FillColor color.Color
|
||||
}
|
||||
|
||||
// NewLine returns a Line that uses the default line style and
|
||||
// does not draw glyphs.
|
||||
func NewLine(xys XYer) (*Line, error) {
|
||||
data, err := CopyXYs(xys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Line{
|
||||
XYs: data,
|
||||
LineStyle: DefaultLineStyle,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Plot draws the Line, implementing the plot.Plotter interface.
|
||||
func (pts *Line) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trX, trY := plt.Transforms(&c)
|
||||
ps := make([]vg.Point, len(pts.XYs))
|
||||
|
||||
for i, p := range pts.XYs {
|
||||
ps[i].X = trX(p.X)
|
||||
ps[i].Y = trY(p.Y)
|
||||
}
|
||||
|
||||
if pts.FillColor != nil && len(ps) > 0 {
|
||||
minY := trY(plt.Y.Min)
|
||||
fillPoly := []vg.Point{{X: ps[0].X, Y: minY}}
|
||||
switch pts.StepStyle {
|
||||
case PreStep:
|
||||
fillPoly = append(fillPoly, ps[1:]...)
|
||||
case PostStep:
|
||||
fillPoly = append(fillPoly, ps[:len(ps)-1]...)
|
||||
default:
|
||||
fillPoly = append(fillPoly, ps...)
|
||||
}
|
||||
fillPoly = append(fillPoly, vg.Point{X: ps[len(ps)-1].X, Y: minY})
|
||||
fillPoly = c.ClipPolygonXY(fillPoly)
|
||||
if len(fillPoly) > 0 {
|
||||
c.SetColor(pts.FillColor)
|
||||
var pa vg.Path
|
||||
prev := fillPoly[0]
|
||||
pa.Move(prev)
|
||||
for _, pt := range fillPoly[1:] {
|
||||
switch pts.StepStyle {
|
||||
case NoStep:
|
||||
pa.Line(pt)
|
||||
case PreStep:
|
||||
pa.Line(vg.Point{X: prev.X, Y: pt.Y})
|
||||
pa.Line(pt)
|
||||
case MidStep:
|
||||
pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
|
||||
pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
|
||||
pa.Line(pt)
|
||||
case PostStep:
|
||||
pa.Line(vg.Point{X: pt.X, Y: prev.Y})
|
||||
pa.Line(pt)
|
||||
}
|
||||
prev = pt
|
||||
}
|
||||
pa.Close()
|
||||
c.Fill(pa)
|
||||
}
|
||||
}
|
||||
|
||||
lines := c.ClipLinesXY(ps)
|
||||
if pts.LineStyle.Width != 0 && len(lines) != 0 {
|
||||
c.SetLineStyle(pts.LineStyle)
|
||||
for _, l := range lines {
|
||||
if len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
var p vg.Path
|
||||
prev := l[0]
|
||||
p.Move(prev)
|
||||
for _, pt := range l[1:] {
|
||||
switch pts.StepStyle {
|
||||
case PreStep:
|
||||
p.Line(vg.Point{X: prev.X, Y: pt.Y})
|
||||
case MidStep:
|
||||
p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
|
||||
p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
|
||||
case PostStep:
|
||||
p.Line(vg.Point{X: pt.X, Y: prev.Y})
|
||||
}
|
||||
p.Line(pt)
|
||||
prev = pt
|
||||
}
|
||||
c.Stroke(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum
|
||||
// x and y values, implementing the plot.DataRanger interface.
|
||||
func (pts *Line) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
return XYRange(pts)
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the plot.GlyphBoxer interface.
|
||||
func (pts *Line) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
r := 0.5 * pts.LineStyle.Width
|
||||
rect := vg.Rectangle{
|
||||
Min: vg.Point{
|
||||
X: -r,
|
||||
Y: -r,
|
||||
},
|
||||
Max: vg.Point{
|
||||
X: +r,
|
||||
Y: +r,
|
||||
},
|
||||
}
|
||||
|
||||
bs := make([]plot.GlyphBox, pts.XYs.Len())
|
||||
for i := range bs {
|
||||
x, y := pts.XY(i)
|
||||
bs[i] = plot.GlyphBox{
|
||||
X: plt.X.Norm(x),
|
||||
Y: plt.Y.Norm(y),
|
||||
Rectangle: rect,
|
||||
}
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// Thumbnail returns the thumbnail for the Line, implementing the plot.Thumbnailer interface.
|
||||
func (pts *Line) Thumbnail(c *draw.Canvas) {
|
||||
if pts.FillColor != nil {
|
||||
var topY vg.Length
|
||||
if pts.LineStyle.Width == 0 {
|
||||
topY = c.Max.Y
|
||||
} else {
|
||||
topY = (c.Min.Y + c.Max.Y) / 2
|
||||
}
|
||||
points := []vg.Point{
|
||||
{X: c.Min.X, Y: c.Min.Y},
|
||||
{X: c.Min.X, Y: topY},
|
||||
{X: c.Max.X, Y: topY},
|
||||
{X: c.Max.X, Y: c.Min.Y},
|
||||
}
|
||||
poly := c.ClipPolygonY(points)
|
||||
c.FillPolygon(pts.FillColor, poly)
|
||||
}
|
||||
|
||||
if pts.LineStyle.Width != 0 {
|
||||
y := c.Center().Y
|
||||
c.StrokeLine2(pts.LineStyle, c.Min.X, y, c.Max.X, y)
|
||||
}
|
||||
}
|
||||
|
||||
// NewLinePoints returns both a Line and a
|
||||
// Points for the given point data.
|
||||
func NewLinePoints(xys XYer) (*Line, *Scatter, error) {
|
||||
s, err := NewScatter(xys)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
l := &Line{
|
||||
XYs: s.XYs,
|
||||
LineStyle: DefaultLineStyle,
|
||||
}
|
||||
return l, s, nil
|
||||
}
|
||||
43
vendor/gonum.org/v1/plot/plotter/palettethumbnailer.go
generated
vendored
Normal file
43
vendor/gonum.org/v1/plot/plotter/palettethumbnailer.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright ©2017 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 (
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/palette"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// PaletteThumbnailers creates a slice of plot.Thumbnailers that can be used to
|
||||
// add legend entries for the colors in a color palette.
|
||||
func PaletteThumbnailers(p palette.Palette) []plot.Thumbnailer {
|
||||
colors := p.Colors()
|
||||
thumbnailers := make([]plot.Thumbnailer, len(colors))
|
||||
for i, c := range colors {
|
||||
thumbnailers[i] = paletteThumbnailer{color: c}
|
||||
}
|
||||
return thumbnailers
|
||||
}
|
||||
|
||||
// paletteThumbnailer implements the Thumbnailer interface
|
||||
// for color palettes.
|
||||
type paletteThumbnailer struct {
|
||||
color color.Color
|
||||
}
|
||||
|
||||
// Thumbnail satisfies the plot.Thumbnailer interface.
|
||||
func (t paletteThumbnailer) Thumbnail(c *draw.Canvas) {
|
||||
pts := []vg.Point{
|
||||
{X: c.Min.X, Y: c.Min.Y},
|
||||
{X: c.Min.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Min.Y},
|
||||
}
|
||||
poly := c.ClipPolygonY(pts)
|
||||
c.FillPolygon(t.color, poly)
|
||||
}
|
||||
268
vendor/gonum.org/v1/plot/plotter/plotter.go
generated
vendored
Normal file
268
vendor/gonum.org/v1/plot/plotter/plotter.go
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
// 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 defines a variety of standard Plotters for the
|
||||
// plot package.
|
||||
//
|
||||
// Plotters use the primitives provided by the plot package to draw to
|
||||
// the data area of a plot. This package provides some standard data
|
||||
// styles such as lines, scatter plots, box plots, labels, and more.
|
||||
//
|
||||
// New* functions return an error if the data contains Inf, NaN, or is
|
||||
// empty. Some of the New* functions return other plotter-specific errors
|
||||
// too.
|
||||
package plotter // import "gonum.org/v1/plot/plotter"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultLineStyle is the default style for drawing
|
||||
// lines.
|
||||
DefaultLineStyle = draw.LineStyle{
|
||||
Color: color.Black,
|
||||
Width: vg.Points(1),
|
||||
Dashes: []vg.Length{},
|
||||
DashOffs: 0,
|
||||
}
|
||||
|
||||
// DefaultGlyphStyle is the default style used
|
||||
// for gyph marks.
|
||||
DefaultGlyphStyle = draw.GlyphStyle{
|
||||
Color: color.Black,
|
||||
Radius: vg.Points(2.5),
|
||||
Shape: draw.RingGlyph{},
|
||||
}
|
||||
)
|
||||
|
||||
// Valuer wraps the Len and Value methods.
|
||||
type Valuer interface {
|
||||
// Len returns the number of values.
|
||||
Len() int
|
||||
|
||||
// Value returns a value.
|
||||
Value(int) float64
|
||||
}
|
||||
|
||||
// Range returns the minimum and maximum values.
|
||||
func Range(vs Valuer) (min, max float64) {
|
||||
min = math.Inf(1)
|
||||
max = math.Inf(-1)
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
v := vs.Value(i)
|
||||
min = math.Min(min, v)
|
||||
max = math.Max(max, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Values implements the Valuer interface.
|
||||
type Values []float64
|
||||
|
||||
var (
|
||||
ErrInfinity = errors.New("plotter: infinite data point")
|
||||
ErrNaN = errors.New("plotter: NaN data point")
|
||||
ErrNoData = errors.New("plotter: no data points")
|
||||
)
|
||||
|
||||
// CheckFloats returns an error if any of the arguments are NaN or Infinity.
|
||||
func CheckFloats(fs ...float64) error {
|
||||
for _, f := range fs {
|
||||
switch {
|
||||
case math.IsNaN(f):
|
||||
return ErrNaN
|
||||
case math.IsInf(f, 0):
|
||||
return ErrInfinity
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyValues returns a Values that is a copy of the values
|
||||
// from a Valuer, or an error if there are no values, or if one of
|
||||
// the copied values is a NaN or Infinity.
|
||||
func CopyValues(vs Valuer) (Values, error) {
|
||||
if vs.Len() == 0 {
|
||||
return nil, ErrNoData
|
||||
}
|
||||
cpy := make(Values, vs.Len())
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
cpy[i] = vs.Value(i)
|
||||
if err := CheckFloats(cpy[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cpy, nil
|
||||
}
|
||||
|
||||
func (vs Values) Len() int {
|
||||
return len(vs)
|
||||
}
|
||||
|
||||
func (vs Values) Value(i int) float64 {
|
||||
return vs[i]
|
||||
}
|
||||
|
||||
// XYer wraps the Len and XY methods.
|
||||
type XYer interface {
|
||||
// Len returns the number of x, y pairs.
|
||||
Len() int
|
||||
|
||||
// XY returns an x, y pair.
|
||||
XY(int) (x, y float64)
|
||||
}
|
||||
|
||||
// XYRange returns the minimum and maximum
|
||||
// x and y values.
|
||||
func XYRange(xys XYer) (xmin, xmax, ymin, ymax float64) {
|
||||
xmin, xmax = Range(XValues{xys})
|
||||
ymin, ymax = Range(YValues{xys})
|
||||
return
|
||||
}
|
||||
|
||||
// XYs implements the XYer interface.
|
||||
type XYs []XY
|
||||
|
||||
// XY is an x and y value.
|
||||
type XY struct{ X, Y float64 }
|
||||
|
||||
// CopyXYs returns an XYs that is a copy of the x and y values from
|
||||
// an XYer, or an error if one of the data points contains a NaN or
|
||||
// Infinity.
|
||||
func CopyXYs(data XYer) (XYs, error) {
|
||||
cpy := make(XYs, data.Len())
|
||||
for i := range cpy {
|
||||
cpy[i].X, cpy[i].Y = data.XY(i)
|
||||
if err := CheckFloats(cpy[i].X, cpy[i].Y); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cpy, nil
|
||||
}
|
||||
|
||||
func (xys XYs) Len() int {
|
||||
return len(xys)
|
||||
}
|
||||
|
||||
func (xys XYs) XY(i int) (float64, float64) {
|
||||
return xys[i].X, xys[i].Y
|
||||
}
|
||||
|
||||
// XValues implements the Valuer interface,
|
||||
// returning the x value from an XYer.
|
||||
type XValues struct {
|
||||
XYer
|
||||
}
|
||||
|
||||
func (xs XValues) Value(i int) float64 {
|
||||
x, _ := xs.XY(i)
|
||||
return x
|
||||
}
|
||||
|
||||
// YValues implements the Valuer interface,
|
||||
// returning the y value from an XYer.
|
||||
type YValues struct {
|
||||
XYer
|
||||
}
|
||||
|
||||
func (ys YValues) Value(i int) float64 {
|
||||
_, y := ys.XY(i)
|
||||
return y
|
||||
}
|
||||
|
||||
// XYZer wraps the Len and XYZ methods.
|
||||
type XYZer interface {
|
||||
// Len returns the number of x, y, z triples.
|
||||
Len() int
|
||||
|
||||
// XYZ returns an x, y, z triple.
|
||||
XYZ(int) (float64, float64, float64)
|
||||
|
||||
// XY returns an x, y pair.
|
||||
XY(int) (float64, float64)
|
||||
}
|
||||
|
||||
// XYZs implements the XYZer interface using a slice.
|
||||
type XYZs []XYZ
|
||||
|
||||
// XYZ is an x, y and z value.
|
||||
type XYZ struct{ X, Y, Z float64 }
|
||||
|
||||
// Len implements the Len method of the XYZer interface.
|
||||
func (xyz XYZs) Len() int {
|
||||
return len(xyz)
|
||||
}
|
||||
|
||||
// XYZ implements the XYZ method of the XYZer interface.
|
||||
func (xyz XYZs) XYZ(i int) (float64, float64, float64) {
|
||||
return xyz[i].X, xyz[i].Y, xyz[i].Z
|
||||
}
|
||||
|
||||
// XY implements the XY method of the XYer interface.
|
||||
func (xyz XYZs) XY(i int) (float64, float64) {
|
||||
return xyz[i].X, xyz[i].Y
|
||||
}
|
||||
|
||||
// CopyXYZs copies an XYZer.
|
||||
func CopyXYZs(data XYZer) (XYZs, error) {
|
||||
cpy := make(XYZs, data.Len())
|
||||
for i := range cpy {
|
||||
cpy[i].X, cpy[i].Y, cpy[i].Z = data.XYZ(i)
|
||||
if err := CheckFloats(cpy[i].X, cpy[i].Y, cpy[i].Z); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cpy, nil
|
||||
}
|
||||
|
||||
// XYValues implements the XYer interface, returning
|
||||
// the x and y values from an XYZer.
|
||||
type XYValues struct{ XYZer }
|
||||
|
||||
// XY implements the XY method of the XYer interface.
|
||||
func (xy XYValues) XY(i int) (float64, float64) {
|
||||
x, y, _ := xy.XYZ(i)
|
||||
return x, y
|
||||
}
|
||||
|
||||
// Labeller wraps the Label methods.
|
||||
type Labeller interface {
|
||||
// Label returns a label.
|
||||
Label(int) string
|
||||
}
|
||||
|
||||
// XErrorer wraps the XError method.
|
||||
type XErrorer interface {
|
||||
// XError returns two error values for X data.
|
||||
XError(int) (float64, float64)
|
||||
}
|
||||
|
||||
// Errors is a slice of low and high error values.
|
||||
type Errors []struct{ Low, High float64 }
|
||||
|
||||
// XErrors implements the XErrorer interface.
|
||||
type XErrors Errors
|
||||
|
||||
func (xe XErrors) XError(i int) (float64, float64) {
|
||||
return xe[i].Low, xe[i].High
|
||||
}
|
||||
|
||||
// YErrorer wraps the YError method.
|
||||
type YErrorer interface {
|
||||
// YError returns two error values for Y data.
|
||||
YError(int) (float64, float64)
|
||||
}
|
||||
|
||||
// YErrors implements the YErrorer interface.
|
||||
type YErrors Errors
|
||||
|
||||
func (ye YErrors) YError(i int) (float64, float64) {
|
||||
return ye[i].Low, ye[i].High
|
||||
}
|
||||
130
vendor/gonum.org/v1/plot/plotter/polygon.go
generated
vendored
Normal file
130
vendor/gonum.org/v1/plot/plotter/polygon.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright ©2016 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 (
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Polygon implements the Plotter interface, drawing a polygon.
|
||||
type Polygon struct {
|
||||
// XYs is a copy of the vertices of this polygon.
|
||||
// Each item in the array holds one ring in the
|
||||
// Polygon.
|
||||
XYs []XYs
|
||||
|
||||
// LineStyle is the style of the line around the edge
|
||||
// of the polygon.
|
||||
draw.LineStyle
|
||||
|
||||
// Color is the fill color of the polygon.
|
||||
Color color.Color
|
||||
}
|
||||
|
||||
// NewPolygon returns a polygon that uses the default line style and
|
||||
// no fill color, where xys are the rings of the polygon.
|
||||
// Different backends may render overlapping rings and self-intersections
|
||||
// differently, but all built-in backends treat inner rings
|
||||
// with the opposite winding order from the outer ring as
|
||||
// holes.
|
||||
func NewPolygon(xys ...XYer) (*Polygon, error) {
|
||||
data := make([]XYs, len(xys))
|
||||
for i, d := range xys {
|
||||
var err error
|
||||
data[i], err = CopyXYs(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Polygon{
|
||||
XYs: data,
|
||||
LineStyle: DefaultLineStyle,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Plot draws the polygon, implementing the plot.Plotter
|
||||
// interface.
|
||||
func (pts *Polygon) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trX, trY := plt.Transforms(&c)
|
||||
ps := make([][]vg.Point, len(pts.XYs))
|
||||
|
||||
for i, ring := range pts.XYs {
|
||||
ps[i] = make([]vg.Point, len(ring))
|
||||
for j, p := range ring {
|
||||
ps[i][j].X = trX(p.X)
|
||||
ps[i][j].Y = trY(p.Y)
|
||||
}
|
||||
ps[i] = c.ClipPolygonXY(ps[i])
|
||||
}
|
||||
if pts.Color != nil && len(ps) > 0 {
|
||||
c.SetColor(pts.Color)
|
||||
// allocate enough space for at least 4 path components per ring.
|
||||
// 3 is the minimum but 4 is more common.
|
||||
pa := make(vg.Path, 0, 4*len(ps))
|
||||
for _, ring := range ps {
|
||||
if len(ring) == 0 {
|
||||
continue
|
||||
}
|
||||
pa.Move(ring[0])
|
||||
for _, p := range ring[1:] {
|
||||
pa.Line(p)
|
||||
}
|
||||
pa.Close()
|
||||
}
|
||||
c.Fill(pa)
|
||||
}
|
||||
|
||||
for _, ring := range ps {
|
||||
if len(ring) > 0 && ring[len(ring)-1] != ring[0] {
|
||||
ring = append(ring, ring[0])
|
||||
}
|
||||
c.StrokeLines(pts.LineStyle, c.ClipLinesXY(ring)...)
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum
|
||||
// x and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (pts *Polygon) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
xmin = math.Inf(1)
|
||||
xmax = math.Inf(-1)
|
||||
ymin = math.Inf(1)
|
||||
ymax = math.Inf(-1)
|
||||
for _, ring := range pts.XYs {
|
||||
xmini, xmaxi := Range(XValues{ring})
|
||||
ymini, ymaxi := Range(YValues{ring})
|
||||
xmin = math.Min(xmin, xmini)
|
||||
xmax = math.Max(xmax, xmaxi)
|
||||
ymin = math.Min(ymin, ymini)
|
||||
ymax = math.Max(ymax, ymaxi)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Thumbnail creates the thumbnail for the Polygon,
|
||||
// implementing the plot.Thumbnailer interface.
|
||||
func (pts *Polygon) Thumbnail(c *draw.Canvas) {
|
||||
if pts.Color != nil {
|
||||
points := []vg.Point{
|
||||
{X: c.Min.X, Y: c.Min.Y},
|
||||
{X: c.Min.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Min.Y},
|
||||
}
|
||||
poly := c.ClipPolygonY(points)
|
||||
c.FillPolygon(pts.Color, poly)
|
||||
|
||||
points = append(points, vg.Point{X: c.Min.X, Y: c.Min.Y})
|
||||
c.StrokeLines(pts.LineStyle, points)
|
||||
} else {
|
||||
y := c.Center().Y
|
||||
c.StrokeLine2(pts.LineStyle, c.Min.X, y, c.Max.X, y)
|
||||
}
|
||||
}
|
||||
283
vendor/gonum.org/v1/plot/plotter/quartile.go
generated
vendored
Normal file
283
vendor/gonum.org/v1/plot/plotter/quartile.go
generated
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
// 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 (
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultQuartMedianStyle is a fat dot.
|
||||
DefaultQuartMedianStyle = draw.GlyphStyle{
|
||||
Color: color.Black,
|
||||
Radius: vg.Points(1.5),
|
||||
Shape: draw.CircleGlyph{},
|
||||
}
|
||||
|
||||
// DefaultQuartWhiskerStyle is a hairline.
|
||||
DefaultQuartWhiskerStyle = draw.LineStyle{
|
||||
Color: color.Black,
|
||||
Width: vg.Points(0.5),
|
||||
Dashes: []vg.Length{},
|
||||
DashOffs: 0,
|
||||
}
|
||||
)
|
||||
|
||||
// QuartPlot implements the Plotter interface, drawing
|
||||
// a plot to represent the distribution of values.
|
||||
//
|
||||
// This style of the plot appears in Tufte's "The Visual
|
||||
// Display of Quantitative Information".
|
||||
type QuartPlot struct {
|
||||
fiveStatPlot
|
||||
|
||||
// Offset is added to the x location of each plot.
|
||||
// When the Offset is zero, the plot is drawn
|
||||
// centered at its x location.
|
||||
Offset vg.Length
|
||||
|
||||
// MedianStyle is the line style for the median point.
|
||||
MedianStyle draw.GlyphStyle
|
||||
|
||||
// WhiskerStyle is the line style used to draw the
|
||||
// whiskers.
|
||||
WhiskerStyle draw.LineStyle
|
||||
|
||||
// Horizontal dictates whether the QuartPlot should be in the vertical
|
||||
// (default) or horizontal direction.
|
||||
Horizontal bool
|
||||
}
|
||||
|
||||
// NewQuartPlot returns a new QuartPlot that represents
|
||||
// the distribution of the given values.
|
||||
//
|
||||
// An error is returned if the plot is created with
|
||||
// no values.
|
||||
//
|
||||
// The fence values are 1.5x the interquartile before
|
||||
// the first quartile and after the third quartile. Any
|
||||
// value that is outside of the fences are drawn as
|
||||
// Outside points. The adjacent values (to which the
|
||||
// whiskers stretch) are the minimum and maximum
|
||||
// values that are not outside the fences.
|
||||
func NewQuartPlot(loc float64, values Valuer) (*QuartPlot, error) {
|
||||
b := new(QuartPlot)
|
||||
var err error
|
||||
if b.fiveStatPlot, err = newFiveStat(0, loc, values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.MedianStyle = DefaultQuartMedianStyle
|
||||
b.WhiskerStyle = DefaultQuartWhiskerStyle
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
// Plot draws the QuartPlot on Canvas c and Plot plt.
|
||||
func (b *QuartPlot) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
if b.Horizontal {
|
||||
b := &horizQuartPlot{b}
|
||||
b.Plot(c, plt)
|
||||
return
|
||||
}
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
x := trX(b.Location)
|
||||
if !c.ContainsX(x) {
|
||||
return
|
||||
}
|
||||
x += b.Offset
|
||||
|
||||
med := vg.Point{X: x, Y: trY(b.Median)}
|
||||
q1 := trY(b.Quartile1)
|
||||
q3 := trY(b.Quartile3)
|
||||
aLow := trY(b.AdjLow)
|
||||
aHigh := trY(b.AdjHigh)
|
||||
|
||||
c.StrokeLine2(b.WhiskerStyle, x, aHigh, x, q3)
|
||||
if c.ContainsY(med.Y) {
|
||||
c.DrawGlyphNoClip(b.MedianStyle, med)
|
||||
}
|
||||
c.StrokeLine2(b.WhiskerStyle, x, aLow, x, q1)
|
||||
|
||||
ostyle := b.MedianStyle
|
||||
ostyle.Radius = b.MedianStyle.Radius / 2
|
||||
for _, out := range b.Outside {
|
||||
y := trY(b.Value(out))
|
||||
if c.ContainsY(y) {
|
||||
c.DrawGlyphNoClip(ostyle, vg.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum x
|
||||
// and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (b *QuartPlot) DataRange() (float64, float64, float64, float64) {
|
||||
if b.Horizontal {
|
||||
b := &horizQuartPlot{b}
|
||||
return b.DataRange()
|
||||
}
|
||||
return b.Location, b.Location, b.Min, b.Max
|
||||
}
|
||||
|
||||
// GlyphBoxes returns a slice of GlyphBoxes for the plot,
|
||||
// implementing the plot.GlyphBoxer interface.
|
||||
func (b *QuartPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
if b.Horizontal {
|
||||
b := &horizQuartPlot{b}
|
||||
return b.GlyphBoxes(plt)
|
||||
}
|
||||
|
||||
bs := make([]plot.GlyphBox, len(b.Outside)+1)
|
||||
|
||||
ostyle := b.MedianStyle
|
||||
ostyle.Radius = b.MedianStyle.Radius / 2
|
||||
for i, out := range b.Outside {
|
||||
bs[i].X = plt.X.Norm(b.Location)
|
||||
bs[i].Y = plt.Y.Norm(b.Value(out))
|
||||
bs[i].Rectangle = ostyle.Rectangle()
|
||||
bs[i].Rectangle.Min.X += b.Offset
|
||||
}
|
||||
bs[len(bs)-1].X = plt.X.Norm(b.Location)
|
||||
bs[len(bs)-1].Y = plt.Y.Norm(b.Median)
|
||||
bs[len(bs)-1].Rectangle = b.MedianStyle.Rectangle()
|
||||
bs[len(bs)-1].Rectangle.Min.X += b.Offset
|
||||
return bs
|
||||
}
|
||||
|
||||
// OutsideLabels returns a *Labels that will plot
|
||||
// a label for each of the outside points. The
|
||||
// labels are assumed to correspond to the
|
||||
// points used to create the plot.
|
||||
func (b *QuartPlot) OutsideLabels(labels Labeller) (*Labels, error) {
|
||||
if b.Horizontal {
|
||||
b := &horizQuartPlot{b}
|
||||
return b.OutsideLabels(labels)
|
||||
}
|
||||
strs := make([]string, len(b.Outside))
|
||||
for i, out := range b.Outside {
|
||||
strs[i] = labels.Label(out)
|
||||
}
|
||||
o := quartPlotOutsideLabels{b, strs}
|
||||
ls, err := NewLabels(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off := 0.5 * b.MedianStyle.Radius
|
||||
ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
type quartPlotOutsideLabels struct {
|
||||
qp *QuartPlot
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (o quartPlotOutsideLabels) Len() int {
|
||||
return len(o.qp.Outside)
|
||||
}
|
||||
|
||||
func (o quartPlotOutsideLabels) XY(i int) (float64, float64) {
|
||||
return o.qp.Location, o.qp.Value(o.qp.Outside[i])
|
||||
}
|
||||
|
||||
func (o quartPlotOutsideLabels) Label(i int) string {
|
||||
return o.labels[i]
|
||||
}
|
||||
|
||||
// horizQuartPlot is like a regular QuartPlot, however,
|
||||
// it draws horizontally instead of Vertically.
|
||||
type horizQuartPlot struct{ *QuartPlot }
|
||||
|
||||
func (b horizQuartPlot) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trX, trY := plt.Transforms(&c)
|
||||
y := trY(b.Location)
|
||||
if !c.ContainsY(y) {
|
||||
return
|
||||
}
|
||||
y += b.Offset
|
||||
|
||||
med := vg.Point{X: trX(b.Median), Y: y}
|
||||
q1 := trX(b.Quartile1)
|
||||
q3 := trX(b.Quartile3)
|
||||
aLow := trX(b.AdjLow)
|
||||
aHigh := trX(b.AdjHigh)
|
||||
|
||||
c.StrokeLine2(b.WhiskerStyle, aHigh, y, q3, y)
|
||||
if c.ContainsX(med.X) {
|
||||
c.DrawGlyphNoClip(b.MedianStyle, med)
|
||||
}
|
||||
c.StrokeLine2(b.WhiskerStyle, aLow, y, q1, y)
|
||||
|
||||
ostyle := b.MedianStyle
|
||||
ostyle.Radius = b.MedianStyle.Radius / 2
|
||||
for _, out := range b.Outside {
|
||||
x := trX(b.Value(out))
|
||||
if c.ContainsX(x) {
|
||||
c.DrawGlyphNoClip(ostyle, vg.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum x
|
||||
// and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (b horizQuartPlot) DataRange() (float64, float64, float64, float64) {
|
||||
return b.Min, b.Max, b.Location, b.Location
|
||||
}
|
||||
|
||||
// GlyphBoxes returns a slice of GlyphBoxes for the plot,
|
||||
// implementing the plot.GlyphBoxer interface.
|
||||
func (b horizQuartPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
bs := make([]plot.GlyphBox, len(b.Outside)+1)
|
||||
|
||||
ostyle := b.MedianStyle
|
||||
ostyle.Radius = b.MedianStyle.Radius / 2
|
||||
for i, out := range b.Outside {
|
||||
bs[i].X = plt.X.Norm(b.Value(out))
|
||||
bs[i].Y = plt.Y.Norm(b.Location)
|
||||
bs[i].Rectangle = ostyle.Rectangle()
|
||||
bs[i].Rectangle.Min.Y += b.Offset
|
||||
}
|
||||
bs[len(bs)-1].X = plt.X.Norm(b.Median)
|
||||
bs[len(bs)-1].Y = plt.Y.Norm(b.Location)
|
||||
bs[len(bs)-1].Rectangle = b.MedianStyle.Rectangle()
|
||||
bs[len(bs)-1].Rectangle.Min.Y += b.Offset
|
||||
return bs
|
||||
}
|
||||
|
||||
// OutsideLabels returns a *Labels that will plot
|
||||
// a label for each of the outside points. The
|
||||
// labels are assumed to correspond to the
|
||||
// points used to create the plot.
|
||||
func (b *horizQuartPlot) OutsideLabels(labels Labeller) (*Labels, error) {
|
||||
strs := make([]string, len(b.Outside))
|
||||
for i, out := range b.Outside {
|
||||
strs[i] = labels.Label(out)
|
||||
}
|
||||
o := horizQuartPlotOutsideLabels{
|
||||
quartPlotOutsideLabels{b.QuartPlot, strs},
|
||||
}
|
||||
ls, err := NewLabels(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off := 0.5 * b.MedianStyle.Radius
|
||||
ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
type horizQuartPlotOutsideLabels struct {
|
||||
quartPlotOutsideLabels
|
||||
}
|
||||
|
||||
func (o horizQuartPlotOutsideLabels) XY(i int) (float64, float64) {
|
||||
return o.qp.Value(o.qp.Outside[i]), o.qp.Location
|
||||
}
|
||||
474
vendor/gonum.org/v1/plot/plotter/sankey.go
generated
vendored
Normal file
474
vendor/gonum.org/v1/plot/plotter/sankey.go
generated
vendored
Normal file
@@ -0,0 +1,474 @@
|
||||
// Copyright ©2016 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 (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/tools/bezier"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// A Sankey diagram presents stock and flow data as rectangles representing
|
||||
// the amount of each stock and lines between the stocks representing the
|
||||
// amount of each flow.
|
||||
type Sankey struct {
|
||||
// Color specifies the default fill
|
||||
// colors for the stocks and flows. If Color is not nil,
|
||||
// each stock and flow is rendered filled with Color,
|
||||
// otherwise no fill is performed. Colors can be
|
||||
// modified for individual stocks and flows.
|
||||
Color color.Color
|
||||
|
||||
// StockBarWidth is the widths of the bars representing
|
||||
// the stocks. The default value is 15% larger than the
|
||||
// height of the stock label text.
|
||||
StockBarWidth vg.Length
|
||||
|
||||
// LineStyle specifies the default border
|
||||
// line style for the stocks and flows. Styles can be
|
||||
// modified for individual stocks and flows.
|
||||
LineStyle draw.LineStyle
|
||||
|
||||
// TextStyle specifies the default stock label
|
||||
// text style. Styles can be modified for
|
||||
// individual stocks.
|
||||
TextStyle text.Style
|
||||
|
||||
flows []Flow
|
||||
|
||||
// FlowStyle is a function that specifies the
|
||||
// background color and border line style of the
|
||||
// flow based on its group name. The default
|
||||
// function uses the default Color and LineStyle
|
||||
// specified above for all groups.
|
||||
FlowStyle func(group string) (color.Color, draw.LineStyle)
|
||||
|
||||
// StockStyle is a function that specifies, for a stock
|
||||
// identified by its label and category, the label text
|
||||
// to be printed on the plot (lbl), the style of the text (ts),
|
||||
// the horizontal and vertical offsets for printing the text (xOff and yOff),
|
||||
// the color of the fill for the bar representing the stock (c),
|
||||
// and the style of the outline of the bar representing the stock (ls).
|
||||
// The default function uses the default TextStyle, color and LineStyle
|
||||
// specified above for all stocks; zero horizontal and vertical offsets;
|
||||
// and the stock label as the text to be printed on the plot.
|
||||
StockStyle func(label string, category int) (lbl string, ts text.Style, xOff, yOff vg.Length, c color.Color, ls draw.LineStyle)
|
||||
|
||||
// stocks arranges the stocks by category.
|
||||
// The first key is the category and the seond
|
||||
// key is the label.
|
||||
stocks map[int]map[string]*stock
|
||||
}
|
||||
|
||||
// StockRange returns the minimum and maximum value on the value axis
|
||||
// for the stock with the specified label and category.
|
||||
func (s *Sankey) StockRange(label string, category int) (min, max float64, err error) {
|
||||
stk, ok := s.stocks[category][label]
|
||||
if !ok {
|
||||
return 0, 0, fmt.Errorf("plotter: sankey diagram does not contain stock with label=%s and category=%d", label, category)
|
||||
}
|
||||
return stk.min, stk.max, nil
|
||||
}
|
||||
|
||||
// stock represents the amount of a stock and its plotting order.
|
||||
type stock struct {
|
||||
// receptorValue and sourceValue are the totals of the values
|
||||
// of flows coming into and going out of this stock, respectively.
|
||||
receptorValue, sourceValue float64
|
||||
|
||||
// label is the label of this stock, and category represents
|
||||
// its placement on the category axis. Together they make up a
|
||||
// unique identifier.
|
||||
label string
|
||||
category int
|
||||
|
||||
// order is the plotting order of this stock compared
|
||||
// to other stocks in the same category.
|
||||
order int
|
||||
|
||||
// min represents the beginning of the plotting location
|
||||
// on the value axis.
|
||||
min float64
|
||||
|
||||
// max is min plus the larger of receptorValue and sourceValue.
|
||||
max float64
|
||||
}
|
||||
|
||||
// A Flow represents the amount of an entity flowing between two stocks.
|
||||
type Flow struct {
|
||||
// SourceLabel and ReceptorLabel are the labels
|
||||
// of the stocks that originate and receive the flow,
|
||||
// respectively.
|
||||
SourceLabel, ReceptorLabel string
|
||||
|
||||
// SourceCategory and ReceptorCategory define
|
||||
// the locations on the category axis of the stocks that
|
||||
// originate and receive the flow, respectively. The
|
||||
// SourceCategory must be a lower number than
|
||||
// the ReceptorCategory.
|
||||
SourceCategory, ReceptorCategory int
|
||||
|
||||
// Value represents the magnitute of the flow.
|
||||
// It must be greater than or equal to zero.
|
||||
Value float64
|
||||
|
||||
// Group specifies the group that a flow belongs
|
||||
// to. It is used in assigning styles to groups
|
||||
// and creating legends.
|
||||
Group string
|
||||
}
|
||||
|
||||
// NewSankey creates a new Sankey diagram with the specified
|
||||
// flows and stocks.
|
||||
func NewSankey(flows ...Flow) (*Sankey, error) {
|
||||
var s Sankey
|
||||
|
||||
s.stocks = make(map[int]map[string]*stock)
|
||||
|
||||
s.flows = flows
|
||||
for i, f := range flows {
|
||||
// Here we make sure the stock categories are in the proper order.
|
||||
if f.SourceCategory >= f.ReceptorCategory {
|
||||
return nil, fmt.Errorf("plotter: Flow %d SourceCategory (%d) >= ReceptorCategory (%d)", i, f.SourceCategory, f.ReceptorCategory)
|
||||
}
|
||||
if f.Value < 0 {
|
||||
return nil, fmt.Errorf("plotter: Flow %d value (%g) < 0", i, f.Value)
|
||||
}
|
||||
|
||||
// Here we initialize the stock holders.
|
||||
if _, ok := s.stocks[f.SourceCategory]; !ok {
|
||||
s.stocks[f.SourceCategory] = make(map[string]*stock)
|
||||
}
|
||||
if _, ok := s.stocks[f.ReceptorCategory]; !ok {
|
||||
s.stocks[f.ReceptorCategory] = make(map[string]*stock)
|
||||
}
|
||||
|
||||
// Here we figure out the plotting order of the stocks.
|
||||
if _, ok := s.stocks[f.SourceCategory][f.SourceLabel]; !ok {
|
||||
s.stocks[f.SourceCategory][f.SourceLabel] = &stock{
|
||||
order: len(s.stocks[f.SourceCategory]),
|
||||
label: f.SourceLabel,
|
||||
category: f.SourceCategory,
|
||||
}
|
||||
}
|
||||
if _, ok := s.stocks[f.ReceptorCategory][f.ReceptorLabel]; !ok {
|
||||
s.stocks[f.ReceptorCategory][f.ReceptorLabel] = &stock{
|
||||
order: len(s.stocks[f.ReceptorCategory]),
|
||||
label: f.ReceptorLabel,
|
||||
category: f.ReceptorCategory,
|
||||
}
|
||||
}
|
||||
|
||||
// Here we add the current value to the total value of the stocks
|
||||
s.stocks[f.SourceCategory][f.SourceLabel].sourceValue += f.Value
|
||||
s.stocks[f.ReceptorCategory][f.ReceptorLabel].receptorValue += f.Value
|
||||
}
|
||||
|
||||
s.LineStyle = DefaultLineStyle
|
||||
|
||||
s.TextStyle = text.Style{
|
||||
Font: font.From(DefaultFont, DefaultFontSize),
|
||||
Rotation: math.Pi / 2,
|
||||
XAlign: draw.XCenter,
|
||||
YAlign: draw.YCenter,
|
||||
Handler: plot.DefaultTextHandler,
|
||||
}
|
||||
s.StockBarWidth = s.TextStyle.FontExtents().Height * 1.15
|
||||
|
||||
s.FlowStyle = func(_ string) (color.Color, draw.LineStyle) {
|
||||
return s.Color, s.LineStyle
|
||||
}
|
||||
|
||||
s.StockStyle = func(label string, category int) (string, text.Style, vg.Length, vg.Length, color.Color, draw.LineStyle) {
|
||||
return label, s.TextStyle, 0, 0, s.Color, s.LineStyle
|
||||
}
|
||||
|
||||
stocks := s.stockList()
|
||||
s.setStockRange(&stocks)
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// Plot implements the plot.Plotter interface.
|
||||
func (s *Sankey) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trCat, trVal := plt.Transforms(&c)
|
||||
|
||||
// sourceFlowPlaceholder and receptorFlowPlaceholder track
|
||||
// the current plotting location during
|
||||
// the plotting process.
|
||||
sourceFlowPlaceholder := make(map[*stock]float64, len(s.flows))
|
||||
receptorFlowPlaceholder := make(map[*stock]float64, len(s.flows))
|
||||
|
||||
// Here we draw the flows.
|
||||
for _, f := range s.flows {
|
||||
startStock := s.stocks[f.SourceCategory][f.SourceLabel]
|
||||
endStock := s.stocks[f.ReceptorCategory][f.ReceptorLabel]
|
||||
catStart := trCat(float64(f.SourceCategory)) + s.StockBarWidth/2
|
||||
catEnd := trCat(float64(f.ReceptorCategory)) - s.StockBarWidth/2
|
||||
valStartLow := trVal(startStock.min + sourceFlowPlaceholder[startStock])
|
||||
valEndLow := trVal(endStock.min + receptorFlowPlaceholder[endStock])
|
||||
valStartHigh := trVal(startStock.min + sourceFlowPlaceholder[startStock] + f.Value)
|
||||
valEndHigh := trVal(endStock.min + receptorFlowPlaceholder[endStock] + f.Value)
|
||||
sourceFlowPlaceholder[startStock] += f.Value
|
||||
receptorFlowPlaceholder[endStock] += f.Value
|
||||
|
||||
ptsLow := s.bezier(
|
||||
vg.Point{X: catStart, Y: valStartLow},
|
||||
vg.Point{X: catEnd, Y: valEndLow},
|
||||
)
|
||||
ptsHigh := s.bezier(
|
||||
vg.Point{X: catEnd, Y: valEndHigh},
|
||||
vg.Point{X: catStart, Y: valStartHigh},
|
||||
)
|
||||
|
||||
color, lineStyle := s.FlowStyle(f.Group)
|
||||
|
||||
// Here we fill the flow polygons.
|
||||
if color != nil {
|
||||
poly := c.ClipPolygonX(append(ptsLow, ptsHigh...))
|
||||
c.FillPolygon(color, poly)
|
||||
}
|
||||
|
||||
// Here we draw the flow edges.
|
||||
outline := c.ClipLinesX(ptsLow)
|
||||
c.StrokeLines(lineStyle, outline...)
|
||||
outline = c.ClipLinesX(ptsHigh)
|
||||
c.StrokeLines(lineStyle, outline...)
|
||||
}
|
||||
|
||||
// Here we draw the stocks.
|
||||
for _, stk := range s.stockList() {
|
||||
catLoc := trCat(float64(stk.category))
|
||||
if !c.ContainsX(catLoc) {
|
||||
continue
|
||||
}
|
||||
catMin, catMax := catLoc-s.StockBarWidth/2, catLoc+s.StockBarWidth/2
|
||||
valMin, valMax := trVal(stk.min), trVal(stk.max)
|
||||
|
||||
label, textStyle, xOff, yOff, color, lineStyle := s.StockStyle(stk.label, stk.category)
|
||||
|
||||
// Here we fill the stock bars.
|
||||
pts := []vg.Point{
|
||||
{X: catMin, Y: valMin},
|
||||
{X: catMin, Y: valMax},
|
||||
{X: catMax, Y: valMax},
|
||||
{X: catMax, Y: valMin},
|
||||
}
|
||||
if color != nil {
|
||||
c.FillPolygon(color, pts) // poly)
|
||||
}
|
||||
txtPt := vg.Point{X: (catMin+catMax)/2 + xOff, Y: (valMin+valMax)/2 + yOff}
|
||||
c.FillText(textStyle, txtPt, label)
|
||||
|
||||
// Here we draw the bottom edge.
|
||||
pts = []vg.Point{
|
||||
{X: catMin, Y: valMin},
|
||||
{X: catMax, Y: valMin},
|
||||
}
|
||||
c.StrokeLines(lineStyle, pts)
|
||||
|
||||
// Here we draw the top edge plus vertical edges where there are
|
||||
// no flows connected.
|
||||
pts = []vg.Point{
|
||||
{X: catMin, Y: valMax},
|
||||
{X: catMax, Y: valMax},
|
||||
}
|
||||
if stk.receptorValue < stk.sourceValue {
|
||||
y := trVal(stk.max - (stk.sourceValue - stk.receptorValue))
|
||||
pts = append([]vg.Point{{X: catMin, Y: y}}, pts...)
|
||||
} else if stk.sourceValue < stk.receptorValue {
|
||||
y := trVal(stk.max - (stk.receptorValue - stk.sourceValue))
|
||||
pts = append(pts, vg.Point{X: catMax, Y: y})
|
||||
}
|
||||
c.StrokeLines(lineStyle, pts)
|
||||
}
|
||||
}
|
||||
|
||||
// stockList returns a sorted list of the stocks in the diagram.
|
||||
func (s *Sankey) stockList() []*stock {
|
||||
var stocks []*stock
|
||||
for _, ss := range s.stocks {
|
||||
for _, sss := range ss {
|
||||
stocks = append(stocks, sss)
|
||||
}
|
||||
}
|
||||
sort.Sort(stockSorter(stocks))
|
||||
return stocks
|
||||
}
|
||||
|
||||
// stockSorter is a wrapper for a list of *stocks that implements
|
||||
// sort.Interface.
|
||||
type stockSorter []*stock
|
||||
|
||||
func (s stockSorter) Len() int { return len(s) }
|
||||
func (s stockSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s stockSorter) Less(i, j int) bool {
|
||||
if s[i].category != s[j].category {
|
||||
return s[i].category < s[j].category
|
||||
}
|
||||
if s[i].order != s[j].order {
|
||||
return s[i].order < s[j].order
|
||||
}
|
||||
panic(fmt.Errorf("plotter: can't sort stocks:\n%+v\n%+v", s[i], s[j]))
|
||||
}
|
||||
|
||||
// setStockRange sets the minimum and maximum values of the stock plotting locations.
|
||||
func (s *Sankey) setStockRange(stocks *[]*stock) {
|
||||
var cat int
|
||||
var min float64
|
||||
for _, stk := range *stocks {
|
||||
if stk.category != cat {
|
||||
min = 0
|
||||
}
|
||||
cat = stk.category
|
||||
stk.min = min
|
||||
if stk.sourceValue > stk.receptorValue {
|
||||
stk.max = stk.min + stk.sourceValue
|
||||
} else {
|
||||
stk.max = stk.min + stk.receptorValue
|
||||
}
|
||||
min = stk.max
|
||||
}
|
||||
}
|
||||
|
||||
// bezier creates a bezier curve between the begin and end points.
|
||||
func (s *Sankey) bezier(begin, end vg.Point) []vg.Point {
|
||||
// directionOffsetFrac is the fraction of the distance between begin.X and
|
||||
// end.X for the bezier control points.
|
||||
const directionOffsetFrac = 0.3
|
||||
inPts := []vg.Point{
|
||||
begin,
|
||||
{X: begin.X + (end.X-begin.X)*directionOffsetFrac, Y: begin.Y},
|
||||
{X: begin.X + (end.X-begin.X)*(1-directionOffsetFrac), Y: end.Y},
|
||||
end,
|
||||
}
|
||||
curve := bezier.New(inPts...)
|
||||
|
||||
// nPoints is the number of points for bezier interpolation.
|
||||
const nPoints = 20
|
||||
outPts := make([]vg.Point, nPoints)
|
||||
curve.Curve(outPts)
|
||||
return outPts
|
||||
}
|
||||
|
||||
// DataRange implements the plot.DataRanger interface.
|
||||
func (s *Sankey) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
catMin := math.Inf(1)
|
||||
catMax := math.Inf(-1)
|
||||
for cat := range s.stocks {
|
||||
c := float64(cat)
|
||||
catMin = math.Min(catMin, c)
|
||||
catMax = math.Max(catMax, c)
|
||||
}
|
||||
|
||||
stocks := s.stockList()
|
||||
valMin := math.Inf(1)
|
||||
valMax := math.Inf(-1)
|
||||
for _, stk := range stocks {
|
||||
valMin = math.Min(valMin, stk.min)
|
||||
valMax = math.Max(valMax, stk.max)
|
||||
}
|
||||
return catMin, catMax, valMin, valMax
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the GlyphBoxer interface.
|
||||
func (s *Sankey) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
stocks := s.stockList()
|
||||
boxes := make([]plot.GlyphBox, 0, len(s.flows)+len(stocks))
|
||||
|
||||
for _, stk := range stocks {
|
||||
b1 := plot.GlyphBox{
|
||||
X: plt.X.Norm(float64(stk.category)),
|
||||
Y: plt.Y.Norm((stk.min + stk.max) / 2),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: -s.StockBarWidth / 2},
|
||||
Max: vg.Point{X: s.StockBarWidth / 2},
|
||||
},
|
||||
}
|
||||
label, textStyle, xOff, yOff, _, _ := s.StockStyle(stk.label, stk.category)
|
||||
rect := textStyle.Rectangle(label)
|
||||
rect.Min.X += xOff
|
||||
rect.Max.X += xOff
|
||||
rect.Min.Y += yOff
|
||||
rect.Max.Y += yOff
|
||||
b2 := plot.GlyphBox{
|
||||
X: plt.X.Norm(float64(stk.category)),
|
||||
Y: plt.Y.Norm((stk.min + stk.max) / 2),
|
||||
Rectangle: rect,
|
||||
}
|
||||
boxes = append(boxes, b1, b2)
|
||||
}
|
||||
return boxes
|
||||
}
|
||||
|
||||
// Thumbnailers creates a group of objects that can be used to
|
||||
// add legend entries for the different flow groups in this
|
||||
// diagram, as well as the flow group labels that correspond to them.
|
||||
func (s *Sankey) Thumbnailers() (legendLabels []string, thumbnailers []plot.Thumbnailer) {
|
||||
type empty struct{}
|
||||
flowGroups := make(map[string]empty)
|
||||
for _, f := range s.flows {
|
||||
flowGroups[f.Group] = empty{}
|
||||
}
|
||||
legendLabels = make([]string, len(flowGroups))
|
||||
thumbnailers = make([]plot.Thumbnailer, len(flowGroups))
|
||||
i := 0
|
||||
for g := range flowGroups {
|
||||
legendLabels[i] = g
|
||||
i++
|
||||
}
|
||||
sort.Strings(legendLabels)
|
||||
|
||||
for i, g := range legendLabels {
|
||||
var thmb sankeyFlowThumbnailer
|
||||
thmb.Color, thmb.LineStyle = s.FlowStyle(g)
|
||||
thumbnailers[i] = plot.Thumbnailer(thmb)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// sankeyFlowThumbnailer implements the Thumbnailer interface
|
||||
// for Sankey flow groups.
|
||||
type sankeyFlowThumbnailer struct {
|
||||
draw.LineStyle
|
||||
color.Color
|
||||
}
|
||||
|
||||
// Thumbnail fulfills the plot.Thumbnailer interface.
|
||||
func (t sankeyFlowThumbnailer) Thumbnail(c *draw.Canvas) {
|
||||
// Here we draw the fill.
|
||||
pts := []vg.Point{
|
||||
{X: c.Min.X, Y: c.Min.Y},
|
||||
{X: c.Min.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Min.Y},
|
||||
}
|
||||
poly := c.ClipPolygonY(pts)
|
||||
c.FillPolygon(t.Color, poly)
|
||||
|
||||
// Here we draw the upper border.
|
||||
pts = []vg.Point{
|
||||
{X: c.Min.X, Y: c.Max.Y},
|
||||
{X: c.Max.X, Y: c.Max.Y},
|
||||
}
|
||||
outline := c.ClipLinesY(pts)
|
||||
c.StrokeLines(t.LineStyle, outline...)
|
||||
|
||||
// Here we draw the lower border.
|
||||
pts = []vg.Point{
|
||||
{X: c.Min.X, Y: c.Min.Y},
|
||||
{X: c.Max.X, Y: c.Min.Y},
|
||||
}
|
||||
outline = c.ClipLinesY(pts)
|
||||
c.StrokeLines(t.LineStyle, outline...)
|
||||
}
|
||||
85
vendor/gonum.org/v1/plot/plotter/scatter.go
generated
vendored
Normal file
85
vendor/gonum.org/v1/plot/plotter/scatter.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// 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 (
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Scatter implements the Plotter interface, drawing
|
||||
// a glyph for each of a set of points.
|
||||
type Scatter struct {
|
||||
// XYs is a copy of the points for this scatter.
|
||||
XYs
|
||||
|
||||
// GlyphStyleFunc, if not nil, specifies GlyphStyles
|
||||
// for individual points
|
||||
GlyphStyleFunc func(int) draw.GlyphStyle
|
||||
|
||||
// GlyphStyle is the style of the glyphs drawn
|
||||
// at each point.
|
||||
draw.GlyphStyle
|
||||
}
|
||||
|
||||
// NewScatter returns a Scatter that uses the
|
||||
// default glyph style.
|
||||
func NewScatter(xys XYer) (*Scatter, error) {
|
||||
data, err := CopyXYs(xys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Scatter{
|
||||
XYs: data,
|
||||
GlyphStyle: DefaultGlyphStyle,
|
||||
}, err
|
||||
}
|
||||
|
||||
// Plot draws the Scatter, implementing the plot.Plotter
|
||||
// interface.
|
||||
func (pts *Scatter) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trX, trY := plt.Transforms(&c)
|
||||
glyph := func(i int) draw.GlyphStyle { return pts.GlyphStyle }
|
||||
if pts.GlyphStyleFunc != nil {
|
||||
glyph = pts.GlyphStyleFunc
|
||||
}
|
||||
for i, p := range pts.XYs {
|
||||
c.DrawGlyph(glyph(i), vg.Point{X: trX(p.X), Y: trY(p.Y)})
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum
|
||||
// x and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (pts *Scatter) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
return XYRange(pts)
|
||||
}
|
||||
|
||||
// GlyphBoxes returns a slice of plot.GlyphBoxes,
|
||||
// implementing the plot.GlyphBoxer interface.
|
||||
func (pts *Scatter) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
glyph := func(i int) draw.GlyphStyle { return pts.GlyphStyle }
|
||||
if pts.GlyphStyleFunc != nil {
|
||||
glyph = pts.GlyphStyleFunc
|
||||
}
|
||||
bs := make([]plot.GlyphBox, len(pts.XYs))
|
||||
for i, p := range pts.XYs {
|
||||
bs[i].X = plt.X.Norm(p.X)
|
||||
bs[i].Y = plt.Y.Norm(p.Y)
|
||||
r := glyph(i).Radius
|
||||
bs[i].Rectangle = vg.Rectangle{
|
||||
Min: vg.Point{X: -r, Y: -r},
|
||||
Max: vg.Point{X: +r, Y: +r},
|
||||
}
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// Thumbnail the thumbnail for the Scatter,
|
||||
// implementing the plot.Thumbnailer interface.
|
||||
func (pts *Scatter) Thumbnail(c *draw.Canvas) {
|
||||
c.DrawGlyph(pts.GlyphStyle, c.Center())
|
||||
}
|
||||
19
vendor/gonum.org/v1/plot/plotter/volcano
generated
vendored
Normal file
19
vendor/gonum.org/v1/plot/plotter/volcano
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cat >volcano_data_test.go <<EOF
|
||||
// Generated code do not edit. Run \`go generate gonum.org/v1/plot/plotter\`.
|
||||
|
||||
// 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_test
|
||||
|
||||
import "gonum.org/v1/gonum/mat"
|
||||
|
||||
// Data extracted from RDatasets volcano data for the Maunga Whau volcano topographic data.
|
||||
var volcano = deciGrid{mat.NewDense(87, 61, []float64{
|
||||
EOF
|
||||
R -q -e 'write.table(as.data.frame(volcano), file="volcano_data_test.go", sep=", ", eol=",\n", col.names=FALSE, row.names=FALSE, append=TRUE)'
|
||||
echo >> volcano_data_test.go '})}'
|
||||
go fmt volcano_data_test.go
|
||||
381
vendor/gonum.org/v1/plot/plotutil/add.go
generated
vendored
Normal file
381
vendor/gonum.org/v1/plot/plotutil/add.go
generated
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
// 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 plotutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/plotter"
|
||||
"gonum.org/v1/plot/vg"
|
||||
)
|
||||
|
||||
type combineXYs struct{ xs, ys plotter.Valuer }
|
||||
|
||||
func (c combineXYs) Len() int { return c.xs.Len() }
|
||||
func (c combineXYs) XY(i int) (float64, float64) { return c.xs.Value(i), c.ys.Value(i) }
|
||||
|
||||
type item struct {
|
||||
name string
|
||||
value plot.Thumbnailer
|
||||
}
|
||||
|
||||
// AddStackedAreaPlots adds stacked area plot plotters to a plot.
|
||||
// The variadic arguments must be either strings
|
||||
// or plotter.Valuers. Each valuer adds a stacked area
|
||||
// plot to the plot below the stacked area plots added
|
||||
// before it. If a plotter.Valuer is immediately
|
||||
// preceeded by a string then the string value is used to
|
||||
// label the legend.
|
||||
// Plots should be added in order of tallest to shortest,
|
||||
// because they will be drawn in the order they are added
|
||||
// (i.e. later plots will be painted over earlier plots).
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddStackedAreaPlots(plt *plot.Plot, xs plotter.Valuer, vs ...interface{}) error {
|
||||
var ps []plot.Plotter
|
||||
var names []item
|
||||
name := ""
|
||||
var i int
|
||||
|
||||
for _, v := range vs {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
name = t
|
||||
|
||||
case plotter.Valuer:
|
||||
if xs.Len() != t.Len() {
|
||||
return errors.New("X/Y length mismatch")
|
||||
}
|
||||
|
||||
// Make a line plotter and set its style.
|
||||
l, err := plotter.NewLine(combineXYs{xs: xs, ys: t})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.LineStyle.Width = vg.Points(0)
|
||||
color := Color(i)
|
||||
i++
|
||||
l.FillColor = color
|
||||
|
||||
ps = append(ps, l)
|
||||
|
||||
if name != "" {
|
||||
names = append(names, item{name: name, value: l})
|
||||
name = ""
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("plotutil: AddStackedAreaPlots handles strings and plotter.Valuers, got %T", t))
|
||||
}
|
||||
}
|
||||
|
||||
plt.Add(ps...)
|
||||
for _, v := range names {
|
||||
plt.Legend.Add(v.name, v.value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddBoxPlots adds box plot plotters to a plot and
|
||||
// sets the X axis of the plot to be nominal.
|
||||
// The variadic arguments must be either strings
|
||||
// or plotter.Valuers. Each valuer adds a box plot
|
||||
// to the plot at the X location corresponding to
|
||||
// the number of box plots added before it. If a
|
||||
// plotter.Valuer is immediately preceeded by a
|
||||
// string then the string value is used to label the
|
||||
// tick mark for the box plot's X location.
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddBoxPlots(plt *plot.Plot, width vg.Length, vs ...interface{}) error {
|
||||
var ps []plot.Plotter
|
||||
var names []string
|
||||
name := ""
|
||||
for _, v := range vs {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
name = t
|
||||
|
||||
case plotter.Valuer:
|
||||
b, err := plotter.NewBoxPlot(width, float64(len(names)), t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ps = append(ps, b)
|
||||
names = append(names, name)
|
||||
name = ""
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("plotutil: AddBoxPlots handles strings and plotter.Valuers, got %T", t))
|
||||
}
|
||||
}
|
||||
plt.Add(ps...)
|
||||
plt.NominalX(names...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddScatters adds Scatter plotters to a plot.
|
||||
// The variadic arguments must be either strings
|
||||
// or plotter.XYers. Each plotter.XYer is added to
|
||||
// the plot using the next color, and glyph shape
|
||||
// via the Color and Shape functions. If a
|
||||
// plotter.XYer is immediately preceeded by
|
||||
// a string then a legend entry is added to the plot
|
||||
// using the string as the name.
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddScatters(plt *plot.Plot, vs ...interface{}) error {
|
||||
var ps []plot.Plotter
|
||||
var items []item
|
||||
name := ""
|
||||
var i int
|
||||
for _, v := range vs {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
name = t
|
||||
|
||||
case plotter.XYer:
|
||||
s, err := plotter.NewScatter(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Color = Color(i)
|
||||
s.Shape = Shape(i)
|
||||
i++
|
||||
ps = append(ps, s)
|
||||
if name != "" {
|
||||
items = append(items, item{name: name, value: s})
|
||||
name = ""
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("plotutil: AddScatters handles strings and plotter.XYers, got %T", t))
|
||||
}
|
||||
}
|
||||
plt.Add(ps...)
|
||||
for _, v := range items {
|
||||
plt.Legend.Add(v.name, v.value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLines adds Line plotters to a plot.
|
||||
// The variadic arguments must be a string
|
||||
// or one of a plotting type, plotter.XYers or *plotter.Function.
|
||||
// Each plotting type is added to
|
||||
// the plot using the next color and dashes
|
||||
// shape via the Color and Dashes functions.
|
||||
// If a plotting type is immediately preceeded by
|
||||
// a string then a legend entry is added to the plot
|
||||
// using the string as the name.
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddLines(plt *plot.Plot, vs ...interface{}) error {
|
||||
var ps []plot.Plotter
|
||||
var items []item
|
||||
name := ""
|
||||
var i int
|
||||
for _, v := range vs {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
name = t
|
||||
|
||||
case plotter.XYer:
|
||||
l, err := plotter.NewLine(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Color = Color(i)
|
||||
l.Dashes = Dashes(i)
|
||||
i++
|
||||
ps = append(ps, l)
|
||||
if name != "" {
|
||||
items = append(items, item{name: name, value: l})
|
||||
name = ""
|
||||
}
|
||||
|
||||
case *plotter.Function:
|
||||
t.Color = Color(i)
|
||||
t.Dashes = Dashes(i)
|
||||
i++
|
||||
ps = append(ps, t)
|
||||
if name != "" {
|
||||
items = append(items, item{name: name, value: t})
|
||||
name = ""
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("plotutil: AddLines handles strings, plotter.XYers and *plotter.Function, got %T", t))
|
||||
}
|
||||
}
|
||||
plt.Add(ps...)
|
||||
for _, v := range items {
|
||||
plt.Legend.Add(v.name, v.value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLinePoints adds Line and Scatter plotters to a
|
||||
// plot. The variadic arguments must be either strings
|
||||
// or plotter.XYers. Each plotter.XYer is added to
|
||||
// the plot using the next color, dashes, and glyph
|
||||
// shape via the Color, Dashes, and Shape functions.
|
||||
// If a plotter.XYer is immediately preceeded by
|
||||
// a string then a legend entry is added to the plot
|
||||
// using the string as the name.
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddLinePoints(plt *plot.Plot, vs ...interface{}) error {
|
||||
var ps []plot.Plotter
|
||||
type item struct {
|
||||
name string
|
||||
value [2]plot.Thumbnailer
|
||||
}
|
||||
var items []item
|
||||
name := ""
|
||||
var i int
|
||||
for _, v := range vs {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
name = t
|
||||
|
||||
case plotter.XYer:
|
||||
l, s, err := plotter.NewLinePoints(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Color = Color(i)
|
||||
l.Dashes = Dashes(i)
|
||||
s.Color = Color(i)
|
||||
s.Shape = Shape(i)
|
||||
i++
|
||||
ps = append(ps, l, s)
|
||||
if name != "" {
|
||||
items = append(items, item{name: name, value: [2]plot.Thumbnailer{l, s}})
|
||||
name = ""
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("plotutil: AddLinePoints handles strings and plotter.XYers, got %T", t))
|
||||
}
|
||||
}
|
||||
plt.Add(ps...)
|
||||
for _, item := range items {
|
||||
v := item.value[:]
|
||||
plt.Legend.Add(item.name, v[0], v[1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddErrorBars adds XErrorBars and YErrorBars
|
||||
// to a plot. The variadic arguments must be
|
||||
// of type plotter.XYer, and must be either a
|
||||
// plotter.XErrorer, plotter.YErrorer, or both.
|
||||
// Each errorer is added to the plot the color from
|
||||
// the Colors function corresponding to its position
|
||||
// in the argument list.
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddErrorBars(plt *plot.Plot, vs ...interface{}) error {
|
||||
var ps []plot.Plotter
|
||||
for i, v := range vs {
|
||||
added := false
|
||||
|
||||
if xerr, ok := v.(interface {
|
||||
plotter.XYer
|
||||
plotter.XErrorer
|
||||
}); ok {
|
||||
e, err := plotter.NewXErrorBars(xerr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Color = Color(i)
|
||||
ps = append(ps, e)
|
||||
added = true
|
||||
}
|
||||
|
||||
if yerr, ok := v.(interface {
|
||||
plotter.XYer
|
||||
plotter.YErrorer
|
||||
}); ok {
|
||||
e, err := plotter.NewYErrorBars(yerr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Color = Color(i)
|
||||
ps = append(ps, e)
|
||||
added = true
|
||||
}
|
||||
|
||||
if added {
|
||||
continue
|
||||
}
|
||||
panic(fmt.Sprintf("plotutil: AddErrorBars expects plotter.XErrorer or plotter.YErrorer, got %T", v))
|
||||
}
|
||||
plt.Add(ps...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddXErrorBars adds XErrorBars to a plot.
|
||||
// The variadic arguments must be
|
||||
// of type plotter.XYer, and plotter.XErrorer.
|
||||
// Each errorer is added to the plot the color from
|
||||
// the Colors function corresponding to its position
|
||||
// in the argument list.
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddXErrorBars(plt *plot.Plot, es ...interface {
|
||||
plotter.XYer
|
||||
plotter.XErrorer
|
||||
}) error {
|
||||
var ps []plot.Plotter
|
||||
for i, e := range es {
|
||||
bars, err := plotter.NewXErrorBars(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bars.Color = Color(i)
|
||||
ps = append(ps, bars)
|
||||
}
|
||||
plt.Add(ps...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddYErrorBars adds YErrorBars to a plot.
|
||||
// The variadic arguments must be
|
||||
// of type plotter.XYer, and plotter.YErrorer.
|
||||
// Each errorer is added to the plot the color from
|
||||
// the Colors function corresponding to its position
|
||||
// in the argument list.
|
||||
//
|
||||
// If an error occurs then none of the plotters are added
|
||||
// to the plot, and the error is returned.
|
||||
func AddYErrorBars(plt *plot.Plot, es ...interface {
|
||||
plotter.XYer
|
||||
plotter.YErrorer
|
||||
}) error {
|
||||
var ps []plot.Plotter
|
||||
for i, e := range es {
|
||||
bars, err := plotter.NewYErrorBars(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bars.Color = Color(i)
|
||||
ps = append(ps, bars)
|
||||
}
|
||||
plt.Add(ps...)
|
||||
return nil
|
||||
}
|
||||
119
vendor/gonum.org/v1/plot/plotutil/errorpoints.go
generated
vendored
Normal file
119
vendor/gonum.org/v1/plot/plotutil/errorpoints.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
// 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 plotutil
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"gonum.org/v1/plot/plotter"
|
||||
)
|
||||
|
||||
// ErrorPoints holds a set of x, y pairs along
|
||||
// with their X and Y errors.
|
||||
type ErrorPoints struct {
|
||||
plotter.XYs
|
||||
plotter.XErrors
|
||||
plotter.YErrors
|
||||
}
|
||||
|
||||
// NewErrorPoints returns a new ErrorPoints where each
|
||||
// point in the ErrorPoints is given by evaluating the
|
||||
// center function on the Xs and Ys for the corresponding
|
||||
// set of XY values in the pts parameter. The XError
|
||||
// and YError are computed likewise, using the err
|
||||
// function.
|
||||
//
|
||||
// This function can be useful for summarizing sets of
|
||||
// scatter points using a single point and error bars for
|
||||
// each element of the scatter.
|
||||
func NewErrorPoints(f func([]float64) (c, l, h float64), pts ...plotter.XYer) (*ErrorPoints, error) {
|
||||
|
||||
c := &ErrorPoints{
|
||||
XYs: make(plotter.XYs, len(pts)),
|
||||
XErrors: make(plotter.XErrors, len(pts)),
|
||||
YErrors: make(plotter.YErrors, len(pts)),
|
||||
}
|
||||
|
||||
for i, xy := range pts {
|
||||
xs := make([]float64, xy.Len())
|
||||
ys := make([]float64, xy.Len())
|
||||
for j := 0; j < xy.Len(); j++ {
|
||||
xs[j], ys[j] = xy.XY(j)
|
||||
if err := plotter.CheckFloats(xs[j], ys[j]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c.XYs[i].X, c.XErrors[i].Low, c.XErrors[i].High = f(xs)
|
||||
if err := plotter.CheckFloats(c.XYs[i].X, c.XErrors[i].Low, c.XErrors[i].High); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.XYs[i].Y, c.YErrors[i].Low, c.YErrors[i].High = f(ys)
|
||||
if err := plotter.CheckFloats(c.XYs[i].Y, c.YErrors[i].Low, c.YErrors[i].High); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// MeanAndConf95 returns the mean
|
||||
// and the magnitude of the 95% confidence
|
||||
// interval on the mean as low and high
|
||||
// error values.
|
||||
//
|
||||
// MeanAndConf95 may be used as
|
||||
// the f argument to NewErrorPoints.
|
||||
func MeanAndConf95(vls []float64) (mean, lowerr, higherr float64) {
|
||||
n := float64(len(vls))
|
||||
|
||||
sum := 0.0
|
||||
for _, v := range vls {
|
||||
sum += v
|
||||
}
|
||||
mean = sum / n
|
||||
|
||||
sum = 0.0
|
||||
for _, v := range vls {
|
||||
diff := v - mean
|
||||
sum += diff * diff
|
||||
}
|
||||
stdev := math.Sqrt(sum / n)
|
||||
|
||||
conf := 1.96 * stdev / math.Sqrt(n)
|
||||
return mean, conf, conf
|
||||
}
|
||||
|
||||
// MedianAndMinMax returns the median
|
||||
// value and error on the median given
|
||||
// by the minimum and maximum data
|
||||
// values.
|
||||
//
|
||||
// MedianAndMinMax may be used as
|
||||
// the f argument to NewErrorPoints.
|
||||
func MedianAndMinMax(vls []float64) (med, lowerr, higherr float64) {
|
||||
n := len(vls)
|
||||
if n == 0 {
|
||||
panic("plotutil: MedianAndMinMax: No values")
|
||||
}
|
||||
if n == 1 {
|
||||
return vls[0], 0, 0
|
||||
}
|
||||
sort.Float64s(vls)
|
||||
if n%2 == 0 {
|
||||
med = (vls[n/2]-vls[n/2-1])/2 + vls[n/2-1]
|
||||
} else {
|
||||
med = vls[n/2]
|
||||
}
|
||||
|
||||
min := vls[0]
|
||||
max := vls[0]
|
||||
for _, v := range vls {
|
||||
min = math.Min(min, v)
|
||||
max = math.Max(max, v)
|
||||
}
|
||||
|
||||
return med, med - min, max - med
|
||||
}
|
||||
116
vendor/gonum.org/v1/plot/plotutil/plotutil.go
generated
vendored
Normal file
116
vendor/gonum.org/v1/plot/plotutil/plotutil.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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 plotutil contains a small number of utilites for creating plots.
|
||||
//
|
||||
// This package is under active development so portions of it may change.
|
||||
package plotutil // import "gonum.org/v1/plot/plotutil"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// DefaultColors is a set of colors used by the Color function.
|
||||
var DefaultColors = SoftColors
|
||||
|
||||
var DarkColors = []color.Color{
|
||||
rgb(238, 46, 47),
|
||||
rgb(0, 140, 72),
|
||||
rgb(24, 90, 169),
|
||||
rgb(244, 125, 35),
|
||||
rgb(102, 44, 145),
|
||||
rgb(162, 29, 33),
|
||||
rgb(180, 56, 148),
|
||||
}
|
||||
|
||||
var SoftColors = []color.Color{
|
||||
rgb(241, 90, 96),
|
||||
rgb(122, 195, 106),
|
||||
rgb(90, 155, 212),
|
||||
rgb(250, 167, 91),
|
||||
rgb(158, 103, 171),
|
||||
rgb(206, 112, 88),
|
||||
rgb(215, 127, 180),
|
||||
}
|
||||
|
||||
func rgb(r, g, b uint8) color.RGBA {
|
||||
return color.RGBA{r, g, b, 255}
|
||||
}
|
||||
|
||||
// Color returns the ith default color, wrapping
|
||||
// if i is less than zero or greater than the max
|
||||
// number of colors in the DefaultColors slice.
|
||||
func Color(i int) color.Color {
|
||||
n := len(DefaultColors)
|
||||
if i < 0 {
|
||||
return DefaultColors[i%n+n]
|
||||
}
|
||||
return DefaultColors[i%n]
|
||||
}
|
||||
|
||||
// DefaultGlyphShapes is a set of GlyphDrawers used by
|
||||
// the Shape function.
|
||||
var DefaultGlyphShapes = []draw.GlyphDrawer{
|
||||
draw.RingGlyph{},
|
||||
draw.SquareGlyph{},
|
||||
draw.TriangleGlyph{},
|
||||
draw.CrossGlyph{},
|
||||
draw.PlusGlyph{},
|
||||
draw.CircleGlyph{},
|
||||
draw.BoxGlyph{},
|
||||
draw.PyramidGlyph{},
|
||||
}
|
||||
|
||||
// Shape returns the ith default glyph shape,
|
||||
// wrapping if i is less than zero or greater
|
||||
// than the max number of GlyphDrawers
|
||||
// in the DefaultGlyphShapes slice.
|
||||
func Shape(i int) draw.GlyphDrawer {
|
||||
n := len(DefaultGlyphShapes)
|
||||
if i < 0 {
|
||||
return DefaultGlyphShapes[i%n+n]
|
||||
}
|
||||
return DefaultGlyphShapes[i%n]
|
||||
}
|
||||
|
||||
// DefaultDashes is a set of dash patterns used by
|
||||
// the Dashes function.
|
||||
var DefaultDashes = [][]vg.Length{
|
||||
{},
|
||||
|
||||
{vg.Points(6), vg.Points(2)},
|
||||
|
||||
{vg.Points(2), vg.Points(2)},
|
||||
|
||||
{vg.Points(1), vg.Points(1)},
|
||||
|
||||
{vg.Points(5), vg.Points(2), vg.Points(1), vg.Points(2)},
|
||||
|
||||
{vg.Points(10), vg.Points(2), vg.Points(2), vg.Points(2),
|
||||
vg.Points(2), vg.Points(2), vg.Points(2), vg.Points(2)},
|
||||
|
||||
{vg.Points(10), vg.Points(2), vg.Points(2), vg.Points(2)},
|
||||
|
||||
{vg.Points(5), vg.Points(2), vg.Points(5), vg.Points(2),
|
||||
vg.Points(2), vg.Points(2), vg.Points(2), vg.Points(2)},
|
||||
|
||||
{vg.Points(4), vg.Points(2), vg.Points(4), vg.Points(1),
|
||||
vg.Points(1), vg.Points(1), vg.Points(1), vg.Points(1),
|
||||
vg.Points(1), vg.Points(1)},
|
||||
}
|
||||
|
||||
// Dashes returns the ith default dash pattern,
|
||||
// wrapping if i is less than zero or greater
|
||||
// than the max number of dash patters
|
||||
// in the DefaultDashes slice.
|
||||
func Dashes(i int) []vg.Length {
|
||||
n := len(DefaultDashes)
|
||||
if i < 0 {
|
||||
return DefaultDashes[i%n+n]
|
||||
}
|
||||
return DefaultDashes[i%n]
|
||||
}
|
||||
6
vendor/gonum.org/v1/plot/text/doc.go
generated
vendored
Normal file
6
vendor/gonum.org/v1/plot/text/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright ©2020 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 text provides types and functions to parse, format and render text.
|
||||
package text // import "gonum.org/v1/plot/text"
|
||||
268
vendor/gonum.org/v1/plot/text/latex.go
generated
vendored
Normal file
268
vendor/gonum.org/v1/plot/text/latex.go
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
// Copyright ©2020 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 text
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/go-latex/latex/drawtex"
|
||||
"github.com/go-latex/latex/font/ttf"
|
||||
"github.com/go-latex/latex/mtex"
|
||||
"github.com/go-latex/latex/tex"
|
||||
stdfnt "golang.org/x/image/font"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
)
|
||||
|
||||
// Latex parses, formats and renders LaTeX.
|
||||
type Latex struct {
|
||||
// Fonts is the cache of font faces used by this text handler.
|
||||
Fonts *font.Cache
|
||||
|
||||
// DPI is the dot-per-inch controlling the font resolution used by LaTeX.
|
||||
// If zero, the resolution defaults to 72.
|
||||
DPI float64
|
||||
}
|
||||
|
||||
var _ Handler = (*Latex)(nil)
|
||||
|
||||
// Cache returns the cache of fonts used by the text handler.
|
||||
func (hdlr Latex) Cache() *font.Cache {
|
||||
return hdlr.Fonts
|
||||
}
|
||||
|
||||
// Extents returns the Extents of a font.
|
||||
func (hdlr Latex) Extents(fnt font.Font) font.Extents {
|
||||
face := hdlr.Fonts.Lookup(fnt, fnt.Size)
|
||||
return face.Extents()
|
||||
}
|
||||
|
||||
// Lines splits a given block of text into separate lines.
|
||||
func (hdlr Latex) Lines(txt string) []string {
|
||||
txt = strings.TrimRight(txt, "\n")
|
||||
return strings.Split(txt, "\n")
|
||||
}
|
||||
|
||||
// Box returns the bounding box of the given non-multiline text where:
|
||||
// - width is the horizontal space from the origin.
|
||||
// - height is the vertical space above the baseline.
|
||||
// - depth is the vertical space below the baseline, a positive number.
|
||||
func (hdlr Latex) Box(txt string, fnt font.Font) (width, height, depth vg.Length) {
|
||||
cnv := drawtex.New()
|
||||
face := hdlr.Fonts.Lookup(fnt, fnt.Size)
|
||||
fnts := hdlr.fontsFor(fnt)
|
||||
box, err := mtex.Parse(txt, face.Font.Size.Points(), latexDPI, ttf.NewFrom(cnv, fnts))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not parse math expression: %w", err))
|
||||
}
|
||||
|
||||
var sh tex.Ship
|
||||
sh.Call(0, 0, box.(tex.Tree))
|
||||
|
||||
width = vg.Length(box.Width())
|
||||
height = vg.Length(box.Height())
|
||||
depth = vg.Length(box.Depth())
|
||||
|
||||
// Add a bit of space, with a linegap as mtex.Box is returning
|
||||
// a very tight bounding box.
|
||||
// See gonum/plot#661.
|
||||
if depth != 0 {
|
||||
var (
|
||||
e = face.Extents()
|
||||
linegap = e.Height - (e.Ascent + e.Descent)
|
||||
)
|
||||
depth += linegap
|
||||
}
|
||||
|
||||
dpi := vg.Length(hdlr.dpi() / latexDPI)
|
||||
return width * dpi, height * dpi, depth * dpi
|
||||
}
|
||||
|
||||
// Draw renders the given text with the provided style and position
|
||||
// on the canvas.
|
||||
func (hdlr Latex) Draw(c vg.Canvas, txt string, sty Style, pt vg.Point) {
|
||||
cnv := drawtex.New()
|
||||
face := hdlr.Fonts.Lookup(sty.Font, sty.Font.Size)
|
||||
fnts := hdlr.fontsFor(sty.Font)
|
||||
box, err := mtex.Parse(txt, face.Font.Size.Points(), latexDPI, ttf.NewFrom(cnv, fnts))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not parse math expression: %w", err))
|
||||
}
|
||||
|
||||
var sh tex.Ship
|
||||
sh.Call(0, 0, box.(tex.Tree))
|
||||
|
||||
w := box.Width()
|
||||
h := box.Height()
|
||||
d := box.Depth()
|
||||
|
||||
dpi := hdlr.dpi() / latexDPI
|
||||
o := latex{
|
||||
cnv: c,
|
||||
fonts: hdlr.Fonts,
|
||||
sty: sty,
|
||||
pt: pt,
|
||||
w: vg.Length(w * dpi),
|
||||
h: vg.Length((h + d) * dpi),
|
||||
cos: 1,
|
||||
sin: 0,
|
||||
}
|
||||
e := face.Extents()
|
||||
o.xoff = vg.Length(sty.XAlign) * o.w
|
||||
o.yoff = o.h + o.h*vg.Length(sty.YAlign) - (e.Height - e.Ascent)
|
||||
|
||||
if sty.Rotation != 0 {
|
||||
sin64, cos64 := math.Sincos(sty.Rotation)
|
||||
o.cos = vg.Length(cos64)
|
||||
o.sin = vg.Length(sin64)
|
||||
|
||||
o.cnv.Push()
|
||||
defer o.cnv.Pop()
|
||||
o.cnv.Rotate(sty.Rotation)
|
||||
}
|
||||
|
||||
err = o.Render(w/latexDPI, (h+d)/latexDPI, dpi, cnv)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not render math expression: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (hdlr *Latex) fontsFor(fnt font.Font) *ttf.Fonts {
|
||||
rm := fnt
|
||||
rm.Variant = "Serif"
|
||||
rm.Weight = stdfnt.WeightNormal
|
||||
rm.Style = stdfnt.StyleNormal
|
||||
|
||||
it := rm
|
||||
it.Style = stdfnt.StyleItalic
|
||||
|
||||
bf := rm
|
||||
bf.Style = stdfnt.StyleNormal
|
||||
bf.Weight = stdfnt.WeightBold
|
||||
|
||||
bfit := bf
|
||||
bfit.Style = stdfnt.StyleItalic
|
||||
|
||||
return &ttf.Fonts{
|
||||
Rm: hdlr.Fonts.Lookup(rm, fnt.Size).Face,
|
||||
Default: hdlr.Fonts.Lookup(rm, fnt.Size).Face,
|
||||
It: hdlr.Fonts.Lookup(it, fnt.Size).Face,
|
||||
Bf: hdlr.Fonts.Lookup(bf, fnt.Size).Face,
|
||||
BfIt: hdlr.Fonts.Lookup(bfit, fnt.Size).Face,
|
||||
}
|
||||
}
|
||||
|
||||
// latexDPI is the default LaTeX resolution used for computing the LaTeX
|
||||
// layout of equations and regular text.
|
||||
// Dimensions are then rescaled to the desired resolution.
|
||||
const latexDPI = 72.0
|
||||
|
||||
func (hdlr Latex) dpi() float64 {
|
||||
if hdlr.DPI == 0 {
|
||||
return latexDPI
|
||||
}
|
||||
return hdlr.DPI
|
||||
}
|
||||
|
||||
type latex struct {
|
||||
cnv vg.Canvas
|
||||
fonts *font.Cache
|
||||
sty Style
|
||||
pt vg.Point
|
||||
|
||||
w vg.Length
|
||||
h vg.Length
|
||||
|
||||
cos vg.Length
|
||||
sin vg.Length
|
||||
|
||||
xoff vg.Length
|
||||
yoff vg.Length
|
||||
}
|
||||
|
||||
var _ mtex.Renderer = (*latex)(nil)
|
||||
|
||||
func (r *latex) Render(width, height, dpi float64, c *drawtex.Canvas) error {
|
||||
r.cnv.SetColor(r.sty.Color)
|
||||
|
||||
for _, op := range c.Ops() {
|
||||
switch op := op.(type) {
|
||||
case drawtex.GlyphOp:
|
||||
r.drawGlyph(dpi, op)
|
||||
case drawtex.RectOp:
|
||||
r.drawRect(dpi, op)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown drawtex op %T", op))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *latex) drawGlyph(dpi float64, op drawtex.GlyphOp) {
|
||||
pt := r.pt
|
||||
if r.sty.Rotation != 0 {
|
||||
pt.X, pt.Y = r.rotate(pt.X, pt.Y)
|
||||
}
|
||||
|
||||
pt = pt.Add(vg.Point{
|
||||
X: r.xoff + vg.Length(op.X*dpi),
|
||||
Y: r.yoff - vg.Length(op.Y*dpi),
|
||||
})
|
||||
|
||||
fnt := font.Face{
|
||||
Font: font.From(r.sty.Font, vg.Length(op.Glyph.Size)),
|
||||
Face: op.Glyph.Font,
|
||||
}
|
||||
r.cnv.FillString(fnt, pt, op.Glyph.Symbol)
|
||||
}
|
||||
|
||||
func (r *latex) drawRect(dpi float64, op drawtex.RectOp) {
|
||||
x1 := r.xoff + vg.Length(op.X1*dpi)
|
||||
x2 := r.xoff + vg.Length(op.X2*dpi)
|
||||
y1 := r.yoff - vg.Length(op.Y1*dpi)
|
||||
y2 := r.yoff - vg.Length(op.Y2*dpi)
|
||||
|
||||
pt := r.pt
|
||||
if r.sty.Rotation != 0 {
|
||||
pt.X, pt.Y = r.rotate(pt.X, pt.Y)
|
||||
}
|
||||
|
||||
pts := []vg.Point{
|
||||
vg.Point{X: x1, Y: y1}.Add(pt),
|
||||
vg.Point{X: x2, Y: y1}.Add(pt),
|
||||
vg.Point{X: x2, Y: y2}.Add(pt),
|
||||
vg.Point{X: x1, Y: y2}.Add(pt),
|
||||
vg.Point{X: x1, Y: y1}.Add(pt),
|
||||
}
|
||||
|
||||
fillPolygon(r.cnv, r.sty.Color, pts)
|
||||
}
|
||||
|
||||
func (r *latex) rotate(x, y vg.Length) (vg.Length, vg.Length) {
|
||||
u := x*r.cos + y*r.sin
|
||||
v := y*r.cos - x*r.sin
|
||||
return u, v
|
||||
}
|
||||
|
||||
// FillPolygon fills a polygon with the given color.
|
||||
func fillPolygon(c vg.Canvas, clr color.Color, pts []vg.Point) {
|
||||
if len(pts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetColor(clr)
|
||||
p := make(vg.Path, 0, len(pts)+1)
|
||||
p.Move(pts[0])
|
||||
for _, pt := range pts[1:] {
|
||||
p.Line(pt)
|
||||
}
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
86
vendor/gonum.org/v1/plot/text/plain.go
generated
vendored
Normal file
86
vendor/gonum.org/v1/plot/text/plain.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright ©2020 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 text // import "gonum.org/v1/plot/text"
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
)
|
||||
|
||||
// Plain is a text/plain handler.
|
||||
type Plain struct {
|
||||
Fonts *font.Cache
|
||||
}
|
||||
|
||||
var _ Handler = (*Plain)(nil)
|
||||
|
||||
// Cache returns the cache of fonts used by the text handler.
|
||||
func (hdlr Plain) Cache() *font.Cache {
|
||||
return hdlr.Fonts
|
||||
}
|
||||
|
||||
// Extents returns the Extents of a font.
|
||||
func (hdlr Plain) Extents(fnt font.Font) font.Extents {
|
||||
face := hdlr.Fonts.Lookup(fnt, fnt.Size)
|
||||
return face.Extents()
|
||||
}
|
||||
|
||||
// Lines splits a given block of text into separate lines.
|
||||
func (hdlr Plain) Lines(txt string) []string {
|
||||
txt = strings.TrimRight(txt, "\n")
|
||||
return strings.Split(txt, "\n")
|
||||
}
|
||||
|
||||
// Box returns the bounding box of the given non-multiline text where:
|
||||
// - width is the horizontal space from the origin.
|
||||
// - height is the vertical space above the baseline.
|
||||
// - depth is the vertical space below the baseline, a positive number.
|
||||
func (hdlr Plain) Box(txt string, fnt font.Font) (width, height, depth vg.Length) {
|
||||
face := hdlr.Fonts.Lookup(fnt, fnt.Size)
|
||||
ext := face.Extents()
|
||||
width = face.Width(txt)
|
||||
height = ext.Ascent
|
||||
depth = ext.Descent
|
||||
|
||||
return width, height, depth
|
||||
}
|
||||
|
||||
// Draw renders the given text with the provided style and position
|
||||
// on the canvas.
|
||||
func (hdlr Plain) Draw(c vg.Canvas, txt string, sty Style, pt vg.Point) {
|
||||
txt = strings.TrimRight(txt, "\n")
|
||||
if len(txt) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
fnt := hdlr.Fonts.Lookup(sty.Font, sty.Font.Size)
|
||||
c.SetColor(sty.Color)
|
||||
|
||||
if sty.Rotation != 0 {
|
||||
c.Push()
|
||||
c.Rotate(sty.Rotation)
|
||||
}
|
||||
|
||||
sin64, cos64 := math.Sincos(sty.Rotation)
|
||||
cos := vg.Length(cos64)
|
||||
sin := vg.Length(sin64)
|
||||
pt.X, pt.Y = pt.Y*sin+pt.X*cos, pt.Y*cos-pt.X*sin
|
||||
|
||||
lines := hdlr.Lines(txt)
|
||||
ht := sty.Height(txt)
|
||||
pt.Y += ht*vg.Length(sty.YAlign) - fnt.Extents().Ascent
|
||||
for i, line := range lines {
|
||||
xoffs := vg.Length(sty.XAlign) * fnt.Width(line)
|
||||
n := vg.Length(len(lines) - i)
|
||||
c.FillString(fnt, pt.Add(vg.Point{X: xoffs, Y: n * sty.Font.Size}), line)
|
||||
}
|
||||
|
||||
if sty.Rotation != 0 {
|
||||
c.Pop()
|
||||
}
|
||||
}
|
||||
200
vendor/gonum.org/v1/plot/text/text.go
generated
vendored
Normal file
200
vendor/gonum.org/v1/plot/text/text.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright ©2021 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 text
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
)
|
||||
|
||||
// Handler parses, formats and renders text.
|
||||
type Handler interface {
|
||||
// Cache returns the cache of fonts used by the text handler.
|
||||
Cache() *font.Cache
|
||||
|
||||
// Extents returns the Extents of a font.
|
||||
Extents(fnt font.Font) font.Extents
|
||||
|
||||
// Lines splits a given block of text into separate lines.
|
||||
Lines(txt string) []string
|
||||
|
||||
// Box returns the bounding box of the given non-multiline text where:
|
||||
// - width is the horizontal space from the origin.
|
||||
// - height is the vertical space above the baseline.
|
||||
// - depth is the vertical space below the baseline, a positive number.
|
||||
Box(txt string, fnt font.Font) (width, height, depth vg.Length)
|
||||
|
||||
// Draw renders the given text with the provided style and position
|
||||
// on the canvas.
|
||||
Draw(c vg.Canvas, txt string, sty Style, pt vg.Point)
|
||||
}
|
||||
|
||||
// XAlignment specifies text alignment in the X direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using XAlignment(desired number).
|
||||
type XAlignment float64
|
||||
|
||||
const (
|
||||
// XLeft aligns the left edge of the text with the specified location.
|
||||
XLeft XAlignment = 0
|
||||
// XCenter aligns the horizontal center of the text with the specified location.
|
||||
XCenter XAlignment = -0.5
|
||||
// XRight aligns the right edge of the text with the specified location.
|
||||
XRight XAlignment = -1
|
||||
)
|
||||
|
||||
// YAlignment specifies text alignment in the Y direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using YAlignment(desired number).
|
||||
type YAlignment float64
|
||||
|
||||
const (
|
||||
// YTop aligns the top of of the text with the specified location.
|
||||
YTop YAlignment = -1
|
||||
// YCenter aligns the vertical center of the text with the specified location.
|
||||
YCenter YAlignment = -0.5
|
||||
// YBottom aligns the bottom of the text with the specified location.
|
||||
YBottom YAlignment = 0
|
||||
)
|
||||
|
||||
// Position specifies the text position.
|
||||
const (
|
||||
PosLeft = -1
|
||||
PosBottom = -1
|
||||
PosCenter = 0
|
||||
PosTop = +1
|
||||
PosRight = +1
|
||||
)
|
||||
|
||||
// Style describes what text will look like.
|
||||
type Style struct {
|
||||
// Color is the text color.
|
||||
Color color.Color
|
||||
|
||||
// Font is the font description.
|
||||
Font font.Font
|
||||
|
||||
// Rotation is the text rotation in radians, performed around the axis
|
||||
// defined by XAlign and YAlign.
|
||||
Rotation float64
|
||||
|
||||
// XAlign and YAlign specify the alignment of the text.
|
||||
XAlign XAlignment
|
||||
YAlign YAlignment
|
||||
|
||||
// Handler parses and formats text according to a given
|
||||
// dialect (Markdown, LaTeX, plain, ...)
|
||||
// The default is a plain text handler.
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
// FontExtents returns the extents of this Style's font.
|
||||
func (s Style) FontExtents() font.Extents {
|
||||
return s.Handler.Extents(s.Font)
|
||||
}
|
||||
|
||||
// Width returns the width of lines of text
|
||||
// when using the given font before any text rotation is applied.
|
||||
func (s Style) Width(txt string) (max vg.Length) {
|
||||
w, _ := s.box(txt)
|
||||
return w
|
||||
}
|
||||
|
||||
// Height returns the height of the text when using
|
||||
// the given font before any text rotation is applied.
|
||||
func (s Style) Height(txt string) vg.Length {
|
||||
_, h := s.box(txt)
|
||||
return h
|
||||
}
|
||||
|
||||
// box returns the bounding box of a possibly multi-line text.
|
||||
func (s Style) box(txt string) (w, h vg.Length) {
|
||||
var (
|
||||
lines = s.Handler.Lines(txt)
|
||||
e = s.FontExtents()
|
||||
linegap = (e.Height - e.Ascent - e.Descent)
|
||||
)
|
||||
for i, line := range lines {
|
||||
ww, hh, dd := s.Handler.Box(line, s.Font)
|
||||
if ww > w {
|
||||
w = ww
|
||||
}
|
||||
h += hh + dd
|
||||
if i > 0 {
|
||||
h += linegap
|
||||
}
|
||||
}
|
||||
|
||||
return w, h
|
||||
}
|
||||
|
||||
// Rectangle returns a rectangle giving the bounds of
|
||||
// this text assuming that it is drawn at (0, 0).
|
||||
func (s Style) Rectangle(txt string) vg.Rectangle {
|
||||
e := s.Handler.Extents(s.Font)
|
||||
w, h := s.box(txt)
|
||||
desc := vg.Length(e.Height - e.Ascent) // descent + linegap
|
||||
xoff := vg.Length(s.XAlign) * w
|
||||
yoff := vg.Length(s.YAlign)*h - desc
|
||||
|
||||
// lower left corner
|
||||
p1 := rotatePoint(s.Rotation, vg.Point{X: xoff, Y: yoff})
|
||||
// upper left corner
|
||||
p2 := rotatePoint(s.Rotation, vg.Point{X: xoff, Y: h + yoff})
|
||||
// lower right corner
|
||||
p3 := rotatePoint(s.Rotation, vg.Point{X: w + xoff, Y: yoff})
|
||||
// upper right corner
|
||||
p4 := rotatePoint(s.Rotation, vg.Point{X: w + xoff, Y: h + yoff})
|
||||
|
||||
return vg.Rectangle{
|
||||
Max: vg.Point{
|
||||
X: max(p1.X, p2.X, p3.X, p4.X),
|
||||
Y: max(p1.Y, p2.Y, p3.Y, p4.Y),
|
||||
},
|
||||
Min: vg.Point{
|
||||
X: min(p1.X, p2.X, p3.X, p4.X),
|
||||
Y: min(p1.Y, p2.Y, p3.Y, p4.Y),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// rotatePoint applies rotation theta (in radians) about the origin to point p.
|
||||
func rotatePoint(theta float64, p vg.Point) vg.Point {
|
||||
if theta == 0 {
|
||||
return p
|
||||
}
|
||||
x := float64(p.X)
|
||||
y := float64(p.Y)
|
||||
|
||||
sin, cos := math.Sincos(theta)
|
||||
|
||||
return vg.Point{
|
||||
X: vg.Length(x*cos - y*sin),
|
||||
Y: vg.Length(y*cos + x*sin),
|
||||
}
|
||||
}
|
||||
|
||||
func max(d ...vg.Length) vg.Length {
|
||||
o := vg.Length(math.Inf(-1))
|
||||
for _, dd := range d {
|
||||
if dd > o {
|
||||
o = dd
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func min(d ...vg.Length) vg.Length {
|
||||
o := vg.Length(math.Inf(1))
|
||||
for _, dd := range d {
|
||||
if dd < o {
|
||||
o = dd
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
80
vendor/gonum.org/v1/plot/tools/bezier/bezier.go
generated
vendored
Normal file
80
vendor/gonum.org/v1/plot/tools/bezier/bezier.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright ©2013 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 bezier implements 2D Bézier curve calculation.
|
||||
package bezier // import "gonum.org/v1/plot/tools/bezier"
|
||||
|
||||
import "gonum.org/v1/plot/vg"
|
||||
|
||||
type point struct {
|
||||
Point, Control vg.Point
|
||||
}
|
||||
|
||||
// Curve implements Bezier curve calculation according to the algorithm of Robert D. Miller.
|
||||
//
|
||||
// Graphics Gems 5, 'Quick and Simple Bézier Curve Drawing', pages 206-209.
|
||||
type Curve []point
|
||||
|
||||
// NewCurve returns a Curve initialized with the control points in cp.
|
||||
func New(cp ...vg.Point) Curve {
|
||||
if len(cp) == 0 {
|
||||
return nil
|
||||
}
|
||||
c := make(Curve, len(cp))
|
||||
for i, p := range cp {
|
||||
c[i].Point = p
|
||||
}
|
||||
|
||||
var w vg.Length
|
||||
for i, p := range c {
|
||||
switch i {
|
||||
case 0:
|
||||
w = 1
|
||||
case 1:
|
||||
w = vg.Length(len(c)) - 1
|
||||
default:
|
||||
w *= vg.Length(len(c)-i) / vg.Length(i)
|
||||
}
|
||||
c[i].Control.X = p.Point.X * w
|
||||
c[i].Control.Y = p.Point.Y * w
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Point returns the point at t along the curve, where 0 ≤ t ≤ 1.
|
||||
func (c Curve) Point(t float64) vg.Point {
|
||||
c[0].Point = c[0].Control
|
||||
u := t
|
||||
for i, p := range c[1:] {
|
||||
c[i+1].Point = vg.Point{
|
||||
X: p.Control.X * vg.Length(u),
|
||||
Y: p.Control.Y * vg.Length(u),
|
||||
}
|
||||
u *= t
|
||||
}
|
||||
|
||||
var (
|
||||
t1 = 1 - t
|
||||
tt = t1
|
||||
)
|
||||
p := c[len(c)-1].Point
|
||||
for i := len(c) - 2; i >= 0; i-- {
|
||||
p.X += c[i].Point.X * vg.Length(tt)
|
||||
p.Y += c[i].Point.Y * vg.Length(tt)
|
||||
tt *= t1
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Curve returns a slice of vg.Point, p, filled with points along the Bézier curve described by c.
|
||||
// If the length of p is less than 2, the curve points are undefined. The length of p is not
|
||||
// altered by the call.
|
||||
func (c Curve) Curve(p []vg.Point) []vg.Point {
|
||||
for i, nf := 0, float64(len(p)-1); i < len(p); i++ {
|
||||
p[i] = c.Point(float64(i) / nf)
|
||||
}
|
||||
return p
|
||||
}
|
||||
51
vendor/gonum.org/v1/plot/version.go
generated
vendored
Normal file
51
vendor/gonum.org/v1/plot/version.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright ©2019 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.
|
||||
|
||||
//go:build go1.12
|
||||
// +build go1.12
|
||||
|
||||
package plot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
const root = "gonum.org/v1/plot"
|
||||
|
||||
// Version returns the version of Gonum/plot and its checksum. The returned
|
||||
// values are only valid in binaries built with module support.
|
||||
//
|
||||
// If a replace directive exists in the Gonum/plot go.mod, the replace will
|
||||
// be reported in the version in the following format:
|
||||
//
|
||||
// "version=>[replace-path] [replace-version]"
|
||||
//
|
||||
// and the replace sum will be returned in place of the original sum.
|
||||
//
|
||||
// The exact version format returned by Version may change in future.
|
||||
func Version() (version, sum string) {
|
||||
b, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "", ""
|
||||
}
|
||||
for _, m := range b.Deps {
|
||||
if m.Path == root {
|
||||
if m.Replace != nil {
|
||||
switch {
|
||||
case m.Replace.Version != "" && m.Replace.Path != "":
|
||||
return fmt.Sprintf("%s=>%s %s", m.Version, m.Replace.Path, m.Replace.Version), m.Replace.Sum
|
||||
case m.Replace.Version != "":
|
||||
return fmt.Sprintf("%s=>%s", m.Version, m.Replace.Version), m.Replace.Sum
|
||||
case m.Replace.Path != "":
|
||||
return fmt.Sprintf("%s=>%s", m.Version, m.Replace.Path), m.Replace.Sum
|
||||
default:
|
||||
return m.Version + "*", m.Sum + "*"
|
||||
}
|
||||
}
|
||||
return m.Version, m.Sum
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
633
vendor/gonum.org/v1/plot/vg/draw/canvas.go
generated
vendored
Normal file
633
vendor/gonum.org/v1/plot/vg/draw/canvas.go
generated
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
// 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 draw // import "gonum.org/v1/plot/vg/draw"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/vg"
|
||||
)
|
||||
|
||||
// formats holds the registered canvas image formats
|
||||
var formats = struct {
|
||||
sync.RWMutex
|
||||
m map[string]func(w, h vg.Length) vg.CanvasWriterTo
|
||||
}{
|
||||
m: make(map[string]func(w, h vg.Length) vg.CanvasWriterTo),
|
||||
}
|
||||
|
||||
// Formats returns the sorted list of registered vg formats.
|
||||
func Formats() []string {
|
||||
formats.RLock()
|
||||
defer formats.RUnlock()
|
||||
|
||||
list := make([]string, 0, len(formats.m))
|
||||
for name := range formats.m {
|
||||
list = append(list, name)
|
||||
}
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// RegisterFormat registers an image format for use by NewFormattedCanvas.
|
||||
// name is the name of the format, like "jpeg" or "png".
|
||||
// fn is the construction function to call for the format.
|
||||
//
|
||||
// RegisterFormat panics if fn is nil.
|
||||
func RegisterFormat(name string, fn func(w, h vg.Length) vg.CanvasWriterTo) {
|
||||
formats.Lock()
|
||||
defer formats.Unlock()
|
||||
|
||||
if fn == nil {
|
||||
panic("draw: RegisterFormat with nil function")
|
||||
}
|
||||
formats.m[name] = fn
|
||||
}
|
||||
|
||||
// A Canvas is a vector graphics canvas along with
|
||||
// an associated Rectangle defining a section of the canvas
|
||||
// to which drawing should take place.
|
||||
type Canvas struct {
|
||||
vg.Canvas
|
||||
vg.Rectangle
|
||||
}
|
||||
|
||||
// XAlignment specifies text alignment in the X direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using XAlignment(desired number).
|
||||
type XAlignment = text.XAlignment
|
||||
|
||||
const (
|
||||
// XLeft aligns the left edge of the text with the specified location.
|
||||
XLeft = text.XLeft
|
||||
// XCenter aligns the horizontal center of the text with the specified location.
|
||||
XCenter = text.XCenter
|
||||
// XRight aligns the right edge of the text with the specified location.
|
||||
XRight = text.XRight
|
||||
)
|
||||
|
||||
// YAlignment specifies text alignment in the Y direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using YAlignment(desired number).
|
||||
type YAlignment = text.YAlignment
|
||||
|
||||
const (
|
||||
// YTop aligns the top of of the text with the specified location.
|
||||
YTop = text.YTop
|
||||
// YCenter aligns the vertical center of the text with the specified location.
|
||||
YCenter = text.YCenter
|
||||
// YBottom aligns the bottom of the text with the specified location.
|
||||
YBottom = text.YBottom
|
||||
)
|
||||
|
||||
// Position specifies the text position.
|
||||
const (
|
||||
PosLeft = text.PosLeft
|
||||
PosBottom = text.PosBottom
|
||||
PosCenter = text.PosCenter
|
||||
PosTop = text.PosTop
|
||||
PosRight = text.PosRight
|
||||
)
|
||||
|
||||
// LineStyle describes what a line will look like.
|
||||
type LineStyle struct {
|
||||
// Color is the color of the line.
|
||||
Color color.Color
|
||||
|
||||
// Width is the width of the line.
|
||||
Width vg.Length
|
||||
|
||||
Dashes []vg.Length
|
||||
DashOffs vg.Length
|
||||
}
|
||||
|
||||
// A GlyphStyle specifies the look of a glyph used to draw
|
||||
// a point on a plot.
|
||||
type GlyphStyle struct {
|
||||
// Color is the color used to draw the glyph.
|
||||
color.Color
|
||||
|
||||
// Radius specifies the size of the glyph's radius.
|
||||
Radius vg.Length
|
||||
|
||||
// Shape draws the shape of the glyph.
|
||||
Shape GlyphDrawer
|
||||
}
|
||||
|
||||
// A GlyphDrawer wraps the DrawGlyph function.
|
||||
type GlyphDrawer interface {
|
||||
// DrawGlyph draws the glyph at the given
|
||||
// point, with the given color and radius.
|
||||
DrawGlyph(*Canvas, GlyphStyle, vg.Point)
|
||||
}
|
||||
|
||||
// DrawGlyph draws the given glyph to the draw
|
||||
// area. If the point is not within the Canvas
|
||||
// or the sty.Shape is nil then nothing is drawn.
|
||||
func (c *Canvas) DrawGlyph(sty GlyphStyle, pt vg.Point) {
|
||||
if sty.Shape == nil || !c.Contains(pt) {
|
||||
return
|
||||
}
|
||||
c.SetColor(sty.Color)
|
||||
sty.Shape.DrawGlyph(c, sty, pt)
|
||||
}
|
||||
|
||||
// DrawGlyphNoClip draws the given glyph to the draw
|
||||
// area. If the sty.Shape is nil then nothing is drawn.
|
||||
func (c *Canvas) DrawGlyphNoClip(sty GlyphStyle, pt vg.Point) {
|
||||
if sty.Shape == nil {
|
||||
return
|
||||
}
|
||||
c.SetColor(sty.Color)
|
||||
sty.Shape.DrawGlyph(c, sty, pt)
|
||||
}
|
||||
|
||||
// Rectangle returns the rectangle surrounding this glyph,
|
||||
// assuming that it is drawn centered at 0,0
|
||||
func (g GlyphStyle) Rectangle() vg.Rectangle {
|
||||
return vg.Rectangle{
|
||||
Min: vg.Point{X: -g.Radius, Y: -g.Radius},
|
||||
Max: vg.Point{X: +g.Radius, Y: +g.Radius},
|
||||
}
|
||||
}
|
||||
|
||||
// CircleGlyph is a glyph that draws a solid circle.
|
||||
type CircleGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the GlyphDrawer interface.
|
||||
func (CircleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
p := make(vg.Path, 0, 3)
|
||||
p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
|
||||
p.Arc(pt, sty.Radius, 0, 2*math.Pi)
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// RingGlyph is a glyph that draws the outline of a circle.
|
||||
type RingGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (RingGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
p := make(vg.Path, 0, 3)
|
||||
p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
|
||||
p.Arc(pt, sty.Radius, 0, 2*math.Pi)
|
||||
p.Close()
|
||||
c.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(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
|
||||
p := make(vg.Path, 0, 5)
|
||||
p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
|
||||
p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// BoxGlyph is a glyph that draws a filled square.
|
||||
type BoxGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (BoxGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
|
||||
p := make(vg.Path, 0, 5)
|
||||
p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
|
||||
p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// TriangleGlyph is a glyph that draws the outline of a triangle.
|
||||
type TriangleGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (TriangleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
|
||||
p := make(vg.Path, 0, 4)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// PyramidGlyph is a glyph that draws a filled triangle.
|
||||
type PyramidGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (PyramidGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
|
||||
p := make(vg.Path, 0, 4)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// PlusGlyph is a glyph that draws a plus sign
|
||||
type PlusGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (PlusGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius
|
||||
p := make(vg.Path, 0, 2)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X, Y: pt.Y - r})
|
||||
c.Stroke(p)
|
||||
p = p[:0]
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y})
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// CrossGlyph is a glyph that draws a big X.
|
||||
type CrossGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (CrossGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius * cosπover4
|
||||
p := make(vg.Path, 0, 2)
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y - r})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y + r})
|
||||
c.Stroke(p)
|
||||
p = p[:0]
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y - r})
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// New returns a new (bounded) draw.Canvas.
|
||||
func New(c vg.CanvasSizer) Canvas {
|
||||
w, h := c.Size()
|
||||
return NewCanvas(c, w, h)
|
||||
}
|
||||
|
||||
// NewFormattedCanvas creates a new vg.CanvasWriterTo with the specified
|
||||
// image format. Supported formats need to be registered by importing one or
|
||||
// more of the following packages:
|
||||
//
|
||||
// - gonum.org/v1/plot/vg/vgeps: provides eps
|
||||
// - gonum.org/v1/plot/vg/vgimg: provides png, jpg|jpeg, tif|tiff
|
||||
// - gonum.org/v1/plot/vg/vgpdf: provides pdf
|
||||
// - gonum.org/v1/plot/vg/vgsvg: provides svg
|
||||
// - gonum.org/v1/plot/vg/vgtex: provides tex
|
||||
func NewFormattedCanvas(w, h vg.Length, format string) (vg.CanvasWriterTo, error) {
|
||||
formats.RLock()
|
||||
defer formats.RUnlock()
|
||||
|
||||
for name, fn := range formats.m {
|
||||
if format != name {
|
||||
continue
|
||||
}
|
||||
return fn(w, h), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported format: %q", format)
|
||||
}
|
||||
|
||||
// NewCanvas returns a new (bounded) draw.Canvas of the given size.
|
||||
func NewCanvas(c vg.Canvas, w, h vg.Length) Canvas {
|
||||
return Canvas{
|
||||
Canvas: c,
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: 0, Y: 0},
|
||||
Max: vg.Point{X: w, Y: h},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Center returns the center point of the area
|
||||
func (c *Canvas) Center() vg.Point {
|
||||
return vg.Point{
|
||||
X: (c.Max.X-c.Min.X)/2 + c.Min.X,
|
||||
Y: (c.Max.Y-c.Min.Y)/2 + c.Min.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the Canvas contains the point.
|
||||
func (c *Canvas) Contains(p vg.Point) bool {
|
||||
return c.ContainsX(p.X) && c.ContainsY(p.Y)
|
||||
}
|
||||
|
||||
// ContainsX returns true if the Canvas contains the
|
||||
// x coordinate.
|
||||
func (c *Canvas) ContainsX(x vg.Length) bool {
|
||||
return x <= c.Max.X+slop && x >= c.Min.X-slop
|
||||
}
|
||||
|
||||
// ContainsY returns true if the Canvas contains the
|
||||
// y coordinate.
|
||||
func (c *Canvas) ContainsY(y vg.Length) bool {
|
||||
return y <= c.Max.Y+slop && y >= c.Min.Y-slop
|
||||
}
|
||||
|
||||
// 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 (c *Canvas) X(x float64) vg.Length {
|
||||
return vg.Length(x)*(c.Max.X-c.Min.X) + c.Min.X
|
||||
}
|
||||
|
||||
// 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 (c *Canvas) Y(y float64) vg.Length {
|
||||
return vg.Length(y)*(c.Max.Y-c.Min.Y) + c.Min.Y
|
||||
}
|
||||
|
||||
// Crop returns a new Canvas corresponding to the Canvas
|
||||
// c with the given lengths added to the minimum
|
||||
// and maximum x and y values of the Canvas's Rectangle.
|
||||
// Note that cropping the right and top sides of the canvas
|
||||
// requires specifying negative values of right and top.
|
||||
func Crop(c Canvas, left, right, bottom, top vg.Length) Canvas {
|
||||
minpt := vg.Point{
|
||||
X: c.Min.X + left,
|
||||
Y: c.Min.Y + bottom,
|
||||
}
|
||||
maxpt := vg.Point{
|
||||
X: c.Max.X + right,
|
||||
Y: c.Max.Y + top,
|
||||
}
|
||||
return Canvas{
|
||||
Canvas: c,
|
||||
Rectangle: vg.Rectangle{Min: minpt, Max: maxpt},
|
||||
}
|
||||
}
|
||||
|
||||
// Tiles creates regular subcanvases from a Canvas.
|
||||
type Tiles struct {
|
||||
// Cols and Rows specify the number of rows and columns of tiles.
|
||||
Cols, Rows int
|
||||
// PadTop, PadBottom, PadRight, and PadLeft specify the padding
|
||||
// on the corresponding side of each tile.
|
||||
PadTop, PadBottom, PadRight, PadLeft vg.Length
|
||||
// PadX and PadY specify the padding between columns and rows
|
||||
// of tiles respectively..
|
||||
PadX, PadY vg.Length
|
||||
}
|
||||
|
||||
// At returns the subcanvas within c that corresponds to the
|
||||
// tile at column x, row y.
|
||||
func (ts Tiles) At(c Canvas, x, y int) Canvas {
|
||||
tileH := (c.Max.Y - c.Min.Y - ts.PadTop - ts.PadBottom -
|
||||
vg.Length(ts.Rows-1)*ts.PadY) / vg.Length(ts.Rows)
|
||||
tileW := (c.Max.X - c.Min.X - ts.PadLeft - ts.PadRight -
|
||||
vg.Length(ts.Cols-1)*ts.PadX) / vg.Length(ts.Cols)
|
||||
|
||||
ymax := c.Max.Y - ts.PadTop - vg.Length(y)*(ts.PadY+tileH)
|
||||
ymin := ymax - tileH
|
||||
xmin := c.Min.X + ts.PadLeft + vg.Length(x)*(ts.PadX+tileW)
|
||||
xmax := xmin + tileW
|
||||
|
||||
return Canvas{
|
||||
Canvas: vg.Canvas(c),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: xmin, Y: ymin},
|
||||
Max: vg.Point{X: xmax, Y: ymax},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetLineStyle sets the current line style
|
||||
func (c *Canvas) SetLineStyle(sty LineStyle) {
|
||||
c.SetColor(sty.Color)
|
||||
c.SetLineWidth(sty.Width)
|
||||
c.SetLineDash(sty.Dashes, sty.DashOffs)
|
||||
}
|
||||
|
||||
// StrokeLines draws a line connecting a set of points
|
||||
// in the given Canvas.
|
||||
func (c *Canvas) StrokeLines(sty LineStyle, lines ...[]vg.Point) {
|
||||
if len(lines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetLineStyle(sty)
|
||||
|
||||
for _, l := range lines {
|
||||
if len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
p := make(vg.Path, 0, len(l))
|
||||
p.Move(l[0])
|
||||
for _, pt := range l[1:] {
|
||||
p.Line(pt)
|
||||
}
|
||||
c.Stroke(p)
|
||||
}
|
||||
}
|
||||
|
||||
// StrokeLine2 draws a line between two points in the given
|
||||
// Canvas.
|
||||
func (c *Canvas) StrokeLine2(sty LineStyle, x0, y0, x1, y1 vg.Length) {
|
||||
c.StrokeLines(sty, []vg.Point{{X: x0, Y: y0}, {X: x1, Y: y1}})
|
||||
}
|
||||
|
||||
// ClipLinesXY returns a slice of lines that
|
||||
// represent the given line clipped in both
|
||||
// X and Y directions.
|
||||
func (c *Canvas) ClipLinesXY(lines ...[]vg.Point) [][]vg.Point {
|
||||
return c.ClipLinesY(c.ClipLinesX(lines...)...)
|
||||
}
|
||||
|
||||
// ClipLinesX returns a slice of lines that
|
||||
// represent the given line clipped in the
|
||||
// X direction.
|
||||
func (c *Canvas) ClipLinesX(lines ...[]vg.Point) (clipped [][]vg.Point) {
|
||||
lines1 := make([][]vg.Point, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
ls := clipLine(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0}, line)
|
||||
lines1 = append(lines1, ls...)
|
||||
}
|
||||
clipped = make([][]vg.Point, 0, len(lines1))
|
||||
for _, line := range lines1 {
|
||||
ls := clipLine(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, line)
|
||||
clipped = append(clipped, ls...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClipLinesY returns a slice of lines that
|
||||
// represent the given line clipped in the
|
||||
// Y direction.
|
||||
func (c *Canvas) ClipLinesY(lines ...[]vg.Point) (clipped [][]vg.Point) {
|
||||
lines1 := make([][]vg.Point, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
ls := clipLine(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, line)
|
||||
lines1 = append(lines1, ls...)
|
||||
}
|
||||
clipped = make([][]vg.Point, 0, len(lines1))
|
||||
for _, line := range lines1 {
|
||||
ls := clipLine(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1}, line)
|
||||
clipped = append(clipped, ls...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// clipLine performs clipping of a line by a single
|
||||
// clipping line specified by the norm, clip point,
|
||||
// and in function.
|
||||
func clipLine(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (lines [][]vg.Point) {
|
||||
l := make([]vg.Point, 0, len(pts))
|
||||
for i := 1; i < len(pts); i++ {
|
||||
cur, next := pts[i-1], pts[i]
|
||||
curIn, nextIn := in(cur, clip), in(next, clip)
|
||||
switch {
|
||||
case curIn && nextIn:
|
||||
l = append(l, cur)
|
||||
|
||||
case curIn && !nextIn:
|
||||
l = append(l, cur, isect(cur, next, clip, norm))
|
||||
lines = append(lines, l)
|
||||
l = []vg.Point{}
|
||||
|
||||
case !curIn && !nextIn:
|
||||
// do nothing
|
||||
|
||||
default: // !curIn && nextIn
|
||||
l = append(l, isect(cur, next, clip, norm))
|
||||
}
|
||||
if nextIn && i == len(pts)-1 {
|
||||
l = append(l, next)
|
||||
}
|
||||
}
|
||||
if len(l) > 1 {
|
||||
lines = append(lines, l)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FillPolygon fills a polygon with the given color.
|
||||
func (c *Canvas) FillPolygon(clr color.Color, pts []vg.Point) {
|
||||
if len(pts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetColor(clr)
|
||||
p := make(vg.Path, 0, len(pts)+1)
|
||||
p.Move(pts[0])
|
||||
for _, pt := range pts[1:] {
|
||||
p.Line(pt)
|
||||
}
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// ClipPolygonXY returns a slice of lines that
|
||||
// represent the given polygon clipped in both
|
||||
// X and Y directions.
|
||||
func (c *Canvas) ClipPolygonXY(pts []vg.Point) []vg.Point {
|
||||
return c.ClipPolygonY(c.ClipPolygonX(pts))
|
||||
}
|
||||
|
||||
// ClipPolygonX returns a slice of lines that
|
||||
// represent the given polygon clipped in the
|
||||
// X direction.
|
||||
func (c *Canvas) ClipPolygonX(pts []vg.Point) []vg.Point {
|
||||
return clipPoly(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0},
|
||||
clipPoly(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, pts))
|
||||
}
|
||||
|
||||
// ClipPolygonY returns a slice of lines that
|
||||
// represent the given polygon clipped in the
|
||||
// Y direction.
|
||||
func (c *Canvas) ClipPolygonY(pts []vg.Point) []vg.Point {
|
||||
return clipPoly(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1},
|
||||
clipPoly(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, pts))
|
||||
}
|
||||
|
||||
// clipPoly performs clipping of a polygon by a single
|
||||
// clipping line specified by the norm, clip point,
|
||||
// and in function.
|
||||
func clipPoly(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (clipped []vg.Point) {
|
||||
clipped = make([]vg.Point, 0, len(pts))
|
||||
for i := 0; i < len(pts); i++ {
|
||||
j := i + 1
|
||||
if i == len(pts)-1 {
|
||||
j = 0
|
||||
}
|
||||
cur, next := pts[i], pts[j]
|
||||
curIn, nextIn := in(cur, clip), in(next, clip)
|
||||
switch {
|
||||
case curIn && nextIn:
|
||||
clipped = append(clipped, cur)
|
||||
|
||||
case curIn && !nextIn:
|
||||
clipped = append(clipped, cur, isect(cur, next, clip, norm))
|
||||
|
||||
case !curIn && !nextIn:
|
||||
// do nothing
|
||||
|
||||
default: // !curIn && nextIn
|
||||
clipped = append(clipped, isect(cur, next, clip, norm))
|
||||
}
|
||||
}
|
||||
n := len(clipped)
|
||||
return clipped[:n:n]
|
||||
}
|
||||
|
||||
// slop is some slop for floating point equality
|
||||
const slop = 3e-8 // ≈ √1⁻¹⁵
|
||||
|
||||
func isLeft(p, clip vg.Point) bool {
|
||||
return p.X <= clip.X+slop
|
||||
}
|
||||
|
||||
func isRight(p, clip vg.Point) bool {
|
||||
return p.X >= clip.X-slop
|
||||
}
|
||||
|
||||
func isBelow(p, clip vg.Point) bool {
|
||||
return p.Y <= clip.Y+slop
|
||||
}
|
||||
|
||||
func isAbove(p, clip vg.Point) bool {
|
||||
return p.Y >= clip.Y-slop
|
||||
}
|
||||
|
||||
// isect returns the intersection of a line p0→p1 with the
|
||||
// clipping line specified by the clip point and normal.
|
||||
func isect(p0, p1, clip, norm vg.Point) vg.Point {
|
||||
// t = (norm · (p0 - clip)) / (norm · (p0 - p1))
|
||||
t := p0.Sub(clip).Dot(norm) / p0.Sub(p1).Dot(norm)
|
||||
|
||||
// p = p0 + t*(p1 - p0)
|
||||
return p1.Sub(p0).Scale(t).Add(p0)
|
||||
}
|
||||
|
||||
// FillText fills lines of text in the draw area.
|
||||
// pt specifies the location where the text is to be drawn.
|
||||
func (c *Canvas) FillText(sty TextStyle, pt vg.Point, txt string) {
|
||||
sty.Handler.Draw(c, txt, sty, pt)
|
||||
}
|
||||
6
vendor/gonum.org/v1/plot/vg/draw/doc.go
generated
vendored
Normal file
6
vendor/gonum.org/v1/plot/vg/draw/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright ©2021 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 draw provides types and functions to draw shapes on a vg.Canvas.
|
||||
package draw
|
||||
12
vendor/gonum.org/v1/plot/vg/draw/text.go
generated
vendored
Normal file
12
vendor/gonum.org/v1/plot/vg/draw/text.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright ©2020 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 draw // import "gonum.org/v1/plot/vg/draw"
|
||||
|
||||
import (
|
||||
"gonum.org/v1/plot/text"
|
||||
)
|
||||
|
||||
type TextHandler = text.Handler
|
||||
type TextStyle = text.Style
|
||||
12
vendor/gonum.org/v1/plot/vg/draw/text_plain.go
generated
vendored
Normal file
12
vendor/gonum.org/v1/plot/vg/draw/text_plain.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright ©2020 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 draw // import "gonum.org/v1/plot/vg/draw"
|
||||
|
||||
import "gonum.org/v1/plot/text"
|
||||
|
||||
// PlainTextHandler is a text/plain handler.
|
||||
type PlainTextHandler = text.Plain
|
||||
|
||||
var _ text.Handler = (*PlainTextHandler)(nil)
|
||||
66
vendor/gonum.org/v1/plot/vg/geom.go
generated
vendored
Normal file
66
vendor/gonum.org/v1/plot/vg/geom.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright ©2016 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 vg
|
||||
|
||||
// A Point is a location in 2d space.
|
||||
//
|
||||
// Points are used for drawing, not for data. For
|
||||
// data, see the XYer interface.
|
||||
type Point struct {
|
||||
X, Y Length
|
||||
}
|
||||
|
||||
// Dot returns the dot product of two points.
|
||||
func (p Point) Dot(q Point) Length {
|
||||
return p.X*q.X + p.Y*q.Y
|
||||
}
|
||||
|
||||
// Add returns the component-wise sum of two points.
|
||||
func (p Point) Add(q Point) Point {
|
||||
return Point{p.X + q.X, p.Y + q.Y}
|
||||
}
|
||||
|
||||
// Sub returns the component-wise difference of two points.
|
||||
func (p Point) Sub(q Point) Point {
|
||||
return Point{p.X - q.X, p.Y - q.Y}
|
||||
}
|
||||
|
||||
// Scale returns the component-wise product of a point and a scalar.
|
||||
func (p Point) Scale(s Length) Point {
|
||||
return Point{p.X * s, p.Y * s}
|
||||
}
|
||||
|
||||
// A Rectangle represents a rectangular region of 2d space.
|
||||
type Rectangle struct {
|
||||
Min Point
|
||||
Max Point
|
||||
}
|
||||
|
||||
// Size returns the width and height of a Rectangle.
|
||||
func (r Rectangle) Size() Point {
|
||||
return Point{
|
||||
X: r.Max.X - r.Min.X,
|
||||
Y: r.Max.Y - r.Min.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Add returns the rectangle r translated by p.
|
||||
func (r Rectangle) Add(p Point) Rectangle {
|
||||
return Rectangle{
|
||||
Min: r.Min.Add(p),
|
||||
Max: r.Max.Add(p),
|
||||
}
|
||||
}
|
||||
|
||||
// Path returns the path of a Rect specified by its
|
||||
// upper left corner, width and height.
|
||||
func (r Rectangle) Path() (p Path) {
|
||||
p.Move(r.Min)
|
||||
p.Line(Point{X: r.Max.X, Y: r.Min.Y})
|
||||
p.Line(r.Max)
|
||||
p.Line(Point{X: r.Min.X, Y: r.Max.Y})
|
||||
p.Close()
|
||||
return
|
||||
}
|
||||
37
vendor/gonum.org/v1/plot/vg/len.go
generated
vendored
Normal file
37
vendor/gonum.org/v1/plot/vg/len.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 vg
|
||||
|
||||
import "gonum.org/v1/plot/font"
|
||||
|
||||
// A Length is a unit-independent representation of length.
|
||||
// Internally, the length is stored in postscript points.
|
||||
type Length = font.Length
|
||||
|
||||
// Points returns a length for the given number of points.
|
||||
func Points(pt float64) Length {
|
||||
return font.Points(pt)
|
||||
}
|
||||
|
||||
// Common lengths.
|
||||
const (
|
||||
Inch = font.Inch
|
||||
Centimeter = font.Centimeter
|
||||
Millimeter = font.Millimeter
|
||||
)
|
||||
|
||||
// ParseLength parses a Length string.
|
||||
// A Length string is a possible signed floating number with a unit.
|
||||
// e.g. "42cm" "2.4in" "66pt"
|
||||
// If no unit was given, ParseLength assumes it was (postscript) points.
|
||||
// Currently valid units are:
|
||||
//
|
||||
// - mm (millimeter)
|
||||
// - cm (centimeter)
|
||||
// - in (inch)
|
||||
// - pt (point)
|
||||
func ParseLength(value string) (Length, error) {
|
||||
return font.ParseLength(value)
|
||||
}
|
||||
129
vendor/gonum.org/v1/plot/vg/tee.go
generated
vendored
Normal file
129
vendor/gonum.org/v1/plot/vg/tee.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright ©2020 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 vg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
)
|
||||
|
||||
// MultiCanvas creates a canvas that duplicates its drawing operations to all
|
||||
// the provided canvases, similar to the Unix tee(1) command.
|
||||
//
|
||||
// Each drawing operation is sent to each listed canvas, one at a time.
|
||||
func MultiCanvas(cs ...Canvas) Canvas {
|
||||
return teeCanvas{cs}
|
||||
}
|
||||
|
||||
type teeCanvas struct {
|
||||
cs []Canvas
|
||||
}
|
||||
|
||||
// SetLineWidth sets the width of stroked paths.
|
||||
// If the width is not positive then stroked lines
|
||||
// are not drawn.
|
||||
func (tee teeCanvas) SetLineWidth(w Length) {
|
||||
for _, c := range tee.cs {
|
||||
c.SetLineWidth(w)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLineDash sets the dash pattern for lines.
|
||||
// The pattern slice specifies the lengths of
|
||||
// alternating dashes and gaps, and the offset
|
||||
// specifies the distance into the dash pattern
|
||||
// to start the dash.
|
||||
func (tee teeCanvas) SetLineDash(pattern []Length, offset Length) {
|
||||
for _, c := range tee.cs {
|
||||
c.SetLineDash(pattern, offset)
|
||||
}
|
||||
}
|
||||
|
||||
// SetColor sets the current drawing color.
|
||||
// Note that fill color and stroke color are
|
||||
// the same, so if you want different fill
|
||||
// and stroke colors then you must set a color,
|
||||
// draw fills, set a new color and then draw lines.
|
||||
func (tee teeCanvas) SetColor(c color.Color) {
|
||||
for _, canvas := range tee.cs {
|
||||
canvas.SetColor(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate applies a rotation transform to the context.
|
||||
// The parameter is specified in radians.
|
||||
func (tee teeCanvas) Rotate(rad float64) {
|
||||
for _, c := range tee.cs {
|
||||
c.Rotate(rad)
|
||||
}
|
||||
}
|
||||
|
||||
// Translate applies a translational transform to the context.
|
||||
func (tee teeCanvas) Translate(pt Point) {
|
||||
for _, c := range tee.cs {
|
||||
c.Translate(pt)
|
||||
}
|
||||
}
|
||||
|
||||
// Scale applies a scaling transform to the context.
|
||||
func (tee teeCanvas) Scale(x, y float64) {
|
||||
for _, c := range tee.cs {
|
||||
c.Scale(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// Push saves the current line width, the
|
||||
// current dash pattern, the current
|
||||
// transforms, and the current color
|
||||
// onto a stack so that the state can later
|
||||
// be restored by calling Pop().
|
||||
func (tee teeCanvas) Push() {
|
||||
for _, c := range tee.cs {
|
||||
c.Push()
|
||||
}
|
||||
}
|
||||
|
||||
// Pop restores the context saved by the
|
||||
// corresponding call to Push().
|
||||
func (tee teeCanvas) Pop() {
|
||||
for _, c := range tee.cs {
|
||||
c.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
// Stroke strokes the given path.
|
||||
func (tee teeCanvas) Stroke(p Path) {
|
||||
for _, c := range tee.cs {
|
||||
c.Stroke(p)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill fills the given path.
|
||||
func (tee teeCanvas) Fill(p Path) {
|
||||
for _, c := range tee.cs {
|
||||
c.Fill(p)
|
||||
}
|
||||
}
|
||||
|
||||
// FillString fills in text at the specified
|
||||
// location using the given font.
|
||||
// If the font size is zero, the text is not drawn.
|
||||
func (tee teeCanvas) FillString(f font.Face, pt Point, text string) {
|
||||
for _, c := range tee.cs {
|
||||
c.FillString(f, pt, text)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawImage draws the image, scaled to fit
|
||||
// the destination rectangle.
|
||||
func (tee teeCanvas) DrawImage(rect Rectangle, img image.Image) {
|
||||
for _, c := range tee.cs {
|
||||
c.DrawImage(rect, img)
|
||||
}
|
||||
}
|
||||
|
||||
var _ Canvas = (*teeCanvas)(nil)
|
||||
189
vendor/gonum.org/v1/plot/vg/vg.go
generated
vendored
Normal file
189
vendor/gonum.org/v1/plot/vg/vg.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// 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 vg defines an interface for drawing 2D vector graphics.
|
||||
// This package is designed with the hope that many different
|
||||
// vector graphics back-ends can conform to the interface.
|
||||
package vg // import "gonum.org/v1/plot/vg"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
)
|
||||
|
||||
// A Canvas is the main drawing interface for 2D vector
|
||||
// graphics.
|
||||
// The origin is in the bottom left corner.
|
||||
type Canvas interface {
|
||||
// SetLineWidth sets the width of stroked paths.
|
||||
// If the width is not positive then stroked lines
|
||||
// are not drawn.
|
||||
//
|
||||
// The initial line width is 1 point.
|
||||
SetLineWidth(Length)
|
||||
|
||||
// SetLineDash sets the dash pattern for lines.
|
||||
// The pattern slice specifies the lengths of
|
||||
// alternating dashes and gaps, and the offset
|
||||
// specifies the distance into the dash pattern
|
||||
// to start the dash.
|
||||
//
|
||||
// The initial dash pattern is a solid line.
|
||||
SetLineDash(pattern []Length, offset Length)
|
||||
|
||||
// SetColor sets the current drawing color.
|
||||
// Note that fill color and stroke color are
|
||||
// the same, so if you want different fill
|
||||
// and stroke colors then you must set a color,
|
||||
// draw fills, set a new color and then draw lines.
|
||||
//
|
||||
// The initial color is black.
|
||||
// If SetColor is called with a nil color then black is used.
|
||||
SetColor(color.Color)
|
||||
|
||||
// Rotate applies a rotation transform to the context.
|
||||
// The parameter is specified in radians.
|
||||
Rotate(rad float64)
|
||||
|
||||
// Translate applies a translational transform
|
||||
// to the context.
|
||||
Translate(pt Point)
|
||||
|
||||
// Scale applies a scaling transform to the
|
||||
// context.
|
||||
Scale(x, y float64)
|
||||
|
||||
// Push saves the current line width, the
|
||||
// current dash pattern, the current
|
||||
// transforms, and the current color
|
||||
// onto a stack so that the state can later
|
||||
// be restored by calling Pop().
|
||||
Push()
|
||||
|
||||
// Pop restores the context saved by the
|
||||
// corresponding call to Push().
|
||||
Pop()
|
||||
|
||||
// Stroke strokes the given path.
|
||||
Stroke(Path)
|
||||
|
||||
// Fill fills the given path.
|
||||
Fill(Path)
|
||||
|
||||
// FillString fills in text at the specified
|
||||
// location using the given font.
|
||||
// If the font size is zero, the text is not drawn.
|
||||
FillString(f font.Face, pt Point, text string)
|
||||
|
||||
// DrawImage draws the image, scaled to fit
|
||||
// the destination rectangle.
|
||||
DrawImage(rect Rectangle, img image.Image)
|
||||
}
|
||||
|
||||
// CanvasSizer is a Canvas with a defined size.
|
||||
type CanvasSizer interface {
|
||||
Canvas
|
||||
Size() (x, y Length)
|
||||
}
|
||||
|
||||
// CanvasWriterTo is a CanvasSizer with a WriteTo method.
|
||||
type CanvasWriterTo interface {
|
||||
CanvasSizer
|
||||
io.WriterTo
|
||||
}
|
||||
|
||||
// Initialize sets all of the canvas's values to their
|
||||
// initial values.
|
||||
func Initialize(c Canvas) {
|
||||
c.SetLineWidth(Points(1))
|
||||
c.SetLineDash([]Length{}, 0)
|
||||
c.SetColor(color.Black)
|
||||
}
|
||||
|
||||
type Path []PathComp
|
||||
|
||||
// Move moves the current location of the path to
|
||||
// the given point.
|
||||
func (p *Path) Move(pt Point) {
|
||||
*p = append(*p, PathComp{Type: MoveComp, Pos: pt})
|
||||
}
|
||||
|
||||
// Line draws a line from the current point to the
|
||||
// given point.
|
||||
func (p *Path) Line(pt Point) {
|
||||
*p = append(*p, PathComp{Type: LineComp, Pos: pt})
|
||||
}
|
||||
|
||||
// Arc adds an arc to the path defined by the center
|
||||
// point of the arc's circle, the radius of the circle
|
||||
// and the start and sweep angles.
|
||||
func (p *Path) Arc(pt Point, rad Length, s, a float64) {
|
||||
*p = append(*p, PathComp{
|
||||
Type: ArcComp,
|
||||
Pos: pt,
|
||||
Radius: rad,
|
||||
Start: s,
|
||||
Angle: a,
|
||||
})
|
||||
}
|
||||
|
||||
// QuadTo adds a quadratic curve element to the path,
|
||||
// given by the control point p1 and end point pt.
|
||||
func (p *Path) QuadTo(p1, pt Point) {
|
||||
*p = append(*p, PathComp{Type: CurveComp, Pos: pt, Control: []Point{p1}})
|
||||
}
|
||||
|
||||
// CubeTo adds a cubic curve element to the path,
|
||||
// given by the control points p1 and p2 and the end point pt.
|
||||
func (p *Path) CubeTo(p1, p2, pt Point) {
|
||||
*p = append(*p, PathComp{Type: CurveComp, Pos: pt, Control: []Point{p1, p2}})
|
||||
}
|
||||
|
||||
// Close closes the path by connecting the current
|
||||
// location to the start location with a line.
|
||||
func (p *Path) Close() {
|
||||
*p = append(*p, PathComp{Type: CloseComp})
|
||||
}
|
||||
|
||||
// Constants that tag the type of each path
|
||||
// component.
|
||||
const (
|
||||
MoveComp = iota
|
||||
LineComp
|
||||
ArcComp
|
||||
CurveComp
|
||||
CloseComp
|
||||
)
|
||||
|
||||
// A PathComp is a component of a path structure.
|
||||
type PathComp struct {
|
||||
// Type is the type of a particluar component.
|
||||
// Based on the type, each of the following
|
||||
// fields may have a different meaning or may
|
||||
// be meaningless.
|
||||
Type int
|
||||
|
||||
// The Pos field is used as the destination
|
||||
// of a MoveComp or LineComp and is the center
|
||||
// point of an ArcComp. It is not used in
|
||||
// the CloseComp.
|
||||
Pos Point
|
||||
|
||||
// Control is one or two intermediate points
|
||||
// for a CurveComp used by QuadTo and CubeTo.
|
||||
Control []Point
|
||||
|
||||
// Radius is only used for ArcComps, it is
|
||||
// the radius of the circle defining the arc.
|
||||
Radius Length
|
||||
|
||||
// Start and Angle are only used for ArcComps.
|
||||
// They define the start angle and sweep angle of
|
||||
// the arc around the circle. The units of the
|
||||
// angle are radians.
|
||||
Start, Angle float64
|
||||
}
|
||||
232
vendor/gonum.org/v1/plot/vg/vgeps/vgeps.go
generated
vendored
Normal file
232
vendor/gonum.org/v1/plot/vg/vgeps/vgeps.go
generated
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
// 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 vgeps implements the vg.Canvas interface using
|
||||
// encapsulated postscript.
|
||||
package vgeps // import "gonum.org/v1/plot/vg/vgeps"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
draw.RegisterFormat("eps", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return New(w, h)
|
||||
})
|
||||
}
|
||||
|
||||
// DPI is the nominal resolution of drawing in EPS.
|
||||
const DPI = 72
|
||||
|
||||
type Canvas struct {
|
||||
stack []context
|
||||
w, h vg.Length
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
type context struct {
|
||||
color color.Color
|
||||
width vg.Length
|
||||
dashes []vg.Length
|
||||
offs vg.Length
|
||||
font string
|
||||
fsize vg.Length
|
||||
}
|
||||
|
||||
// pr is the amount of precision to use when outputting float64s.
|
||||
const pr = 5
|
||||
|
||||
// New returns a new Canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
return NewTitle(w, h, "")
|
||||
}
|
||||
|
||||
// NewTitle returns a new Canvas with the given title string.
|
||||
func NewTitle(w, h vg.Length, title string) *Canvas {
|
||||
c := &Canvas{
|
||||
stack: []context{{}},
|
||||
w: w,
|
||||
h: h,
|
||||
buf: new(bytes.Buffer),
|
||||
}
|
||||
c.buf.WriteString("%%!PS-Adobe-3.0 EPSF-3.0\n")
|
||||
c.buf.WriteString("%%Creator gonum.org/v1/plot/vg/vgeps\n")
|
||||
c.buf.WriteString("%%Title: " + title + "\n")
|
||||
c.buf.WriteString(fmt.Sprintf("%%%%BoundingBox: 0 0 %.*g %.*g\n",
|
||||
pr, w.Dots(DPI),
|
||||
pr, h.Dots(DPI)))
|
||||
c.buf.WriteString(fmt.Sprintf("%%%%CreationDate: %s\n", time.Now()))
|
||||
c.buf.WriteString("%%Orientation: Portrait\n")
|
||||
c.buf.WriteString("%%EndComments\n")
|
||||
c.buf.WriteString("\n")
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
// context returns the top context on the stack.
|
||||
func (e *Canvas) context() *context {
|
||||
return &e.stack[len(e.stack)-1]
|
||||
}
|
||||
|
||||
func (e *Canvas) SetLineWidth(w vg.Length) {
|
||||
if e.context().width != w {
|
||||
e.context().width = w
|
||||
fmt.Fprintf(e.buf, "%.*g setlinewidth\n", pr, w.Dots(DPI))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) SetLineDash(dashes []vg.Length, o vg.Length) {
|
||||
cur := e.context().dashes
|
||||
dashEq := len(dashes) == len(cur)
|
||||
for i := 0; dashEq && i < len(dashes); i++ {
|
||||
if dashes[i] != cur[i] {
|
||||
dashEq = false
|
||||
}
|
||||
}
|
||||
if !dashEq || e.context().offs != o {
|
||||
e.context().dashes = dashes
|
||||
e.context().offs = o
|
||||
e.buf.WriteString("[")
|
||||
for _, d := range dashes {
|
||||
fmt.Fprintf(e.buf, " %.*g", pr, d.Dots(DPI))
|
||||
}
|
||||
e.buf.WriteString(" ] ")
|
||||
fmt.Fprintf(e.buf, "%.*g setdash\n", pr, o.Dots(DPI))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) SetColor(c color.Color) {
|
||||
if c == nil {
|
||||
c = color.Black
|
||||
}
|
||||
if e.context().color != c {
|
||||
e.context().color = c
|
||||
r, g, b, _ := c.RGBA()
|
||||
mx := float64(math.MaxUint16)
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g %.*g setrgbcolor\n", pr, float64(r)/mx,
|
||||
pr, float64(g)/mx, pr, float64(b)/mx)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) Rotate(r float64) {
|
||||
fmt.Fprintf(e.buf, "%.*g rotate\n", pr, r*180/math.Pi)
|
||||
}
|
||||
|
||||
func (e *Canvas) Translate(pt vg.Point) {
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g translate\n",
|
||||
pr, pt.X.Dots(DPI), pr, pt.Y.Dots(DPI))
|
||||
}
|
||||
|
||||
func (e *Canvas) Scale(x, y float64) {
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g scale\n", pr, x, pr, y)
|
||||
}
|
||||
|
||||
func (e *Canvas) Push() {
|
||||
e.stack = append(e.stack, *e.context())
|
||||
e.buf.WriteString("gsave\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) Pop() {
|
||||
e.stack = e.stack[:len(e.stack)-1]
|
||||
e.buf.WriteString("grestore\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) Stroke(path vg.Path) {
|
||||
if e.context().width <= 0 {
|
||||
return
|
||||
}
|
||||
e.trace(path)
|
||||
e.buf.WriteString("stroke\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) Fill(path vg.Path) {
|
||||
e.trace(path)
|
||||
e.buf.WriteString("fill\n")
|
||||
}
|
||||
|
||||
func (e *Canvas) trace(path vg.Path) {
|
||||
e.buf.WriteString("newpath\n")
|
||||
for _, comp := range path {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, comp.Pos.X, pr, comp.Pos.Y)
|
||||
case vg.LineComp:
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g lineto\n", pr, comp.Pos.X, pr, comp.Pos.Y)
|
||||
case vg.ArcComp:
|
||||
end := comp.Start + comp.Angle
|
||||
arcOp := "arc"
|
||||
if comp.Angle < 0 {
|
||||
arcOp = "arcn"
|
||||
}
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g %.*g %.*g %.*g %s\n", pr, comp.Pos.X, pr, comp.Pos.Y,
|
||||
pr, comp.Radius, pr, comp.Start*180/math.Pi, pr,
|
||||
end*180/math.Pi, arcOp)
|
||||
case vg.CurveComp:
|
||||
var p1, p2 vg.Point
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
p1 = comp.Control[0]
|
||||
p2 = p1
|
||||
case 2:
|
||||
p1 = comp.Control[0]
|
||||
p2 = comp.Control[1]
|
||||
default:
|
||||
panic("vgeps: invalid number of control points")
|
||||
}
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g %.*g %.*g %.*g %.*g curveto\n",
|
||||
pr, p1.X, pr, p1.Y, pr, p2.X, pr, p2.Y, pr, comp.Pos.X, pr, comp.Pos.Y)
|
||||
case vg.CloseComp:
|
||||
e.buf.WriteString("closepath\n")
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Canvas) FillString(fnt font.Face, pt vg.Point, str string) {
|
||||
if e.context().font != fnt.Name() || e.context().fsize != fnt.Font.Size {
|
||||
e.context().font = fnt.Name()
|
||||
e.context().fsize = fnt.Font.Size
|
||||
fmt.Fprintf(e.buf, "/%s findfont %.*g scalefont setfont\n",
|
||||
fnt.Name(), pr, fnt.Font.Size)
|
||||
}
|
||||
fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, pt.X.Dots(DPI), pr, pt.Y.Dots(DPI))
|
||||
fmt.Fprintf(e.buf, "(%s) show\n", str)
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
// FIXME: https://github.com/gonum/plot/issues/271
|
||||
panic("vgeps: DrawImage not implemented")
|
||||
}
|
||||
|
||||
// WriteTo writes the canvas to an io.Writer.
|
||||
func (e *Canvas) WriteTo(w io.Writer) (int64, error) {
|
||||
b := bufio.NewWriter(w)
|
||||
n, err := e.buf.WriteTo(b)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
m, err := fmt.Fprintln(b, "showpage")
|
||||
n += int64(m)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, b.Flush()
|
||||
}
|
||||
423
vendor/gonum.org/v1/plot/vg/vgimg/vgimg.go
generated
vendored
Normal file
423
vendor/gonum.org/v1/plot/vg/vgimg/vgimg.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
// 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 vgimg implements the vg.Canvas interface using
|
||||
// git.sr.ht/~sbinet/gg as a backend to output raster images.
|
||||
package vgimg // import "gonum.org/v1/plot/vg/vgimg"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
|
||||
"git.sr.ht/~sbinet/gg"
|
||||
"golang.org/x/image/tiff"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
vgdraw "gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vgdraw.RegisterFormat("png", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return PngCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("jpg", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return JpegCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("jpeg", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return JpegCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("tif", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return TiffCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
|
||||
vgdraw.RegisterFormat("tiff", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return TiffCanvas{Canvas: New(w, h)}
|
||||
})
|
||||
}
|
||||
|
||||
// Canvas implements the vg.Canvas interface,
|
||||
// drawing to an image.Image using draw2d.
|
||||
type Canvas struct {
|
||||
ctx *gg.Context
|
||||
img draw.Image
|
||||
w, h vg.Length
|
||||
color []color.Color
|
||||
|
||||
// dpi is the number of dots per inch for this canvas.
|
||||
dpi int
|
||||
|
||||
// width is the current line width.
|
||||
width vg.Length
|
||||
|
||||
// backgroundColor is the background color, set by
|
||||
// UseBackgroundColor.
|
||||
backgroundColor color.Color
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultDPI is the default dot resolution for image
|
||||
// drawing in dots per inch.
|
||||
DefaultDPI = 96
|
||||
|
||||
// DefaultWidth and DefaultHeight are the default canvas
|
||||
// dimensions.
|
||||
DefaultWidth = 4 * vg.Inch
|
||||
DefaultHeight = 4 * vg.Inch
|
||||
)
|
||||
|
||||
// New returns a new image canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
return NewWith(UseWH(w, h), UseBackgroundColor(color.White))
|
||||
}
|
||||
|
||||
// NewWith returns a new image canvas created according to the specified
|
||||
// options. The currently accepted options are UseWH,
|
||||
// UseDPI, UseImage, and UseImageWithContext.
|
||||
// Each of the options specifies the size of the canvas (UseWH, UseImage),
|
||||
// the resolution of the canvas (UseDPI), or both (useImageWithContext).
|
||||
// If size or resolution are not specified, defaults are used.
|
||||
// It panics if size and resolution are overspecified (i.e., too many options are
|
||||
// passed).
|
||||
func NewWith(o ...option) *Canvas {
|
||||
c := new(Canvas)
|
||||
c.backgroundColor = color.White
|
||||
var g uint32
|
||||
for _, opt := range o {
|
||||
f := opt(c)
|
||||
if g&f != 0 {
|
||||
panic("incompatible options")
|
||||
}
|
||||
g |= f
|
||||
}
|
||||
if c.dpi == 0 {
|
||||
c.dpi = DefaultDPI
|
||||
}
|
||||
if c.w == 0 { // h should also == 0.
|
||||
if c.img == nil {
|
||||
c.w = DefaultWidth
|
||||
c.h = DefaultHeight
|
||||
} else {
|
||||
w := float64(c.img.Bounds().Max.X - c.img.Bounds().Min.X)
|
||||
h := float64(c.img.Bounds().Max.Y - c.img.Bounds().Min.Y)
|
||||
c.w = vg.Length(w/float64(c.dpi)) * vg.Inch
|
||||
c.h = vg.Length(h/float64(c.dpi)) * vg.Inch
|
||||
}
|
||||
}
|
||||
if c.img == nil {
|
||||
w := c.w / vg.Inch * vg.Length(c.dpi)
|
||||
h := c.h / vg.Inch * vg.Length(c.dpi)
|
||||
c.img = draw.Image(image.NewRGBA(image.Rect(0, 0, int(w+0.5), int(h+0.5))))
|
||||
}
|
||||
if c.ctx == nil {
|
||||
c.ctx = gg.NewContextForImage(c.img)
|
||||
c.ctx.SetLineCapButt()
|
||||
c.img = c.ctx.Image().(draw.Image)
|
||||
c.ctx.InvertY()
|
||||
}
|
||||
draw.Draw(c.img, c.img.Bounds(), &image.Uniform{c.backgroundColor}, image.Point{}, draw.Src)
|
||||
c.color = []color.Color{color.Black}
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// These constants are used to ensure that the options
|
||||
// used when initializing a canvas are compatible with
|
||||
// each other.
|
||||
const (
|
||||
setsDPI uint32 = 1 << iota
|
||||
setsSize
|
||||
setsBackground
|
||||
)
|
||||
|
||||
type option func(*Canvas) uint32
|
||||
|
||||
// UseWH specifies the width and height of the canvas.
|
||||
// The size is rounded up to the nearest pixel.
|
||||
func UseWH(w, h vg.Length) option {
|
||||
return func(c *Canvas) uint32 {
|
||||
if w <= 0 || h <= 0 {
|
||||
panic("w and h must both be > 0.")
|
||||
}
|
||||
c.w, c.h = w, h
|
||||
return setsSize
|
||||
}
|
||||
}
|
||||
|
||||
// UseDPI sets the dots per inch of a canvas. It should only be
|
||||
// used as an option argument when initializing a new canvas.
|
||||
func UseDPI(dpi int) option {
|
||||
if dpi <= 0 {
|
||||
panic("DPI must be > 0.")
|
||||
}
|
||||
return func(c *Canvas) uint32 {
|
||||
c.dpi = dpi
|
||||
return setsDPI
|
||||
}
|
||||
}
|
||||
|
||||
// UseImage specifies an image to create
|
||||
// the canvas from. The
|
||||
// minimum point of the given image
|
||||
// should probably be 0,0.
|
||||
//
|
||||
// Note that a copy of the input image is performed.
|
||||
// This means that modifications applied to the canvas are not reflected
|
||||
// on the original image.
|
||||
func UseImage(img draw.Image) option {
|
||||
return func(c *Canvas) uint32 {
|
||||
c.img = img
|
||||
return setsSize | setsBackground
|
||||
}
|
||||
}
|
||||
|
||||
// UseImageWithContext specifies both an image
|
||||
// and a graphic context to create the canvas from.
|
||||
// The minimum point of the given image
|
||||
// should probably be 0,0.
|
||||
func UseImageWithContext(img draw.Image, ctx *gg.Context) option {
|
||||
return func(c *Canvas) uint32 {
|
||||
c.img = img
|
||||
c.ctx = ctx
|
||||
return setsSize | setsBackground
|
||||
}
|
||||
}
|
||||
|
||||
// UseBackgroundColor specifies the image background color.
|
||||
// Without UseBackgroundColor, the default color is white.
|
||||
func UseBackgroundColor(c color.Color) option {
|
||||
return func(canvas *Canvas) uint32 {
|
||||
canvas.backgroundColor = c
|
||||
return setsBackground
|
||||
}
|
||||
}
|
||||
|
||||
// Image returns the image the canvas is drawing to.
|
||||
//
|
||||
// The dimensions of the returned image must not be modified.
|
||||
func (c *Canvas) Image() draw.Image {
|
||||
return c.img
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineWidth(w vg.Length) {
|
||||
c.width = w
|
||||
c.ctx.SetLineWidth(w.Dots(c.DPI()))
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineDash(ds []vg.Length, offs vg.Length) {
|
||||
dashes := make([]float64, len(ds))
|
||||
for i, d := range ds {
|
||||
dashes[i] = d.Dots(c.DPI())
|
||||
}
|
||||
c.ctx.SetDashOffset(offs.Dots(c.DPI()))
|
||||
c.ctx.SetDash(dashes...)
|
||||
}
|
||||
|
||||
func (c *Canvas) SetColor(clr color.Color) {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
c.ctx.SetColor(clr)
|
||||
c.color[len(c.color)-1] = clr
|
||||
}
|
||||
|
||||
func (c *Canvas) Rotate(t float64) {
|
||||
c.ctx.Rotate(t)
|
||||
}
|
||||
|
||||
func (c *Canvas) Translate(pt vg.Point) {
|
||||
c.ctx.Translate(pt.X.Dots(c.DPI()), pt.Y.Dots(c.DPI()))
|
||||
}
|
||||
|
||||
func (c *Canvas) Scale(x, y float64) {
|
||||
c.ctx.Scale(x, y)
|
||||
}
|
||||
|
||||
func (c *Canvas) Push() {
|
||||
c.color = append(c.color, c.color[len(c.color)-1])
|
||||
c.ctx.Push()
|
||||
}
|
||||
|
||||
func (c *Canvas) Pop() {
|
||||
c.color = c.color[:len(c.color)-1]
|
||||
c.ctx.Pop()
|
||||
}
|
||||
|
||||
func (c *Canvas) Stroke(p vg.Path) {
|
||||
if c.width <= 0 {
|
||||
return
|
||||
}
|
||||
c.outline(p)
|
||||
c.ctx.Stroke()
|
||||
}
|
||||
|
||||
func (c *Canvas) Fill(p vg.Path) {
|
||||
c.outline(p)
|
||||
c.ctx.Fill()
|
||||
}
|
||||
|
||||
func (c *Canvas) outline(p vg.Path) {
|
||||
for _, comp := range p {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
c.ctx.MoveTo(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()))
|
||||
|
||||
case vg.LineComp:
|
||||
c.ctx.LineTo(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()))
|
||||
|
||||
case vg.ArcComp:
|
||||
c.ctx.DrawArc(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
|
||||
comp.Radius.Dots(c.DPI()),
|
||||
comp.Start, comp.Start+comp.Angle,
|
||||
)
|
||||
|
||||
case vg.CurveComp:
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
c.ctx.QuadraticTo(
|
||||
comp.Control[0].X.Dots(c.DPI()), comp.Control[0].Y.Dots(c.DPI()),
|
||||
comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
|
||||
)
|
||||
case 2:
|
||||
c.ctx.CubicTo(
|
||||
comp.Control[0].X.Dots(c.DPI()), comp.Control[0].Y.Dots(c.DPI()),
|
||||
comp.Control[1].X.Dots(c.DPI()), comp.Control[1].Y.Dots(c.DPI()),
|
||||
comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
|
||||
)
|
||||
default:
|
||||
panic("vgimg: invalid number of control points")
|
||||
}
|
||||
|
||||
case vg.CloseComp:
|
||||
c.ctx.ClosePath()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown path component: %d", comp.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DPI returns the resolution of the receiver in pixels per inch.
|
||||
func (c *Canvas) DPI() float64 {
|
||||
return float64(c.dpi)
|
||||
}
|
||||
|
||||
func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) {
|
||||
if font.Font.Size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.ctx.Push()
|
||||
defer c.ctx.Pop()
|
||||
|
||||
face := font.FontFace(c.DPI())
|
||||
defer face.Close()
|
||||
|
||||
c.ctx.SetFontFace(face)
|
||||
|
||||
x := pt.X.Dots(c.DPI())
|
||||
y := pt.Y.Dots(c.DPI())
|
||||
h := c.h.Dots(c.DPI())
|
||||
|
||||
c.ctx.InvertY()
|
||||
c.ctx.DrawString(str, x, h-y)
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
var (
|
||||
dpi = c.DPI()
|
||||
min = rect.Min
|
||||
xmin = min.X.Dots(dpi)
|
||||
ymin = min.Y.Dots(dpi)
|
||||
rsz = rect.Size()
|
||||
width = rsz.X.Dots(dpi)
|
||||
height = rsz.Y.Dots(dpi)
|
||||
dx = float64(img.Bounds().Dx())
|
||||
dy = float64(img.Bounds().Dy())
|
||||
)
|
||||
c.ctx.Push()
|
||||
c.ctx.Scale(1, -1)
|
||||
c.ctx.Translate(xmin, -ymin-height)
|
||||
c.ctx.Scale(width/dx, height/dy)
|
||||
c.ctx.DrawImage(img, 0, 0)
|
||||
c.ctx.Pop()
|
||||
}
|
||||
|
||||
// WriterCounter implements the io.Writer interface, and counts
|
||||
// the total number of bytes written.
|
||||
type writerCounter struct {
|
||||
io.Writer
|
||||
n int64
|
||||
}
|
||||
|
||||
func (w *writerCounter) Write(p []byte) (int, error) {
|
||||
n, err := w.Writer.Write(p)
|
||||
w.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// A JpegCanvas is an image canvas with a WriteTo method
|
||||
// that writes a jpeg image.
|
||||
type JpegCanvas struct {
|
||||
*Canvas
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface, writing a jpeg image.
|
||||
func (c JpegCanvas) WriteTo(w io.Writer) (int64, error) {
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := jpeg.Encode(b, c.img, nil); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
|
||||
// A PngCanvas is an image canvas with a WriteTo method that
|
||||
// writes a png image.
|
||||
type PngCanvas struct {
|
||||
*Canvas
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface, writing a png image.
|
||||
func (c PngCanvas) WriteTo(w io.Writer) (int64, error) {
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := png.Encode(b, c.img); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
|
||||
// A TiffCanvas is an image canvas with a WriteTo method that
|
||||
// writes a tiff image.
|
||||
type TiffCanvas struct {
|
||||
*Canvas
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface, writing a tiff image.
|
||||
func (c TiffCanvas) WriteTo(w io.Writer) (int64, error) {
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := tiff.Encode(b, c.img, nil); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
251
vendor/gonum.org/v1/plot/vg/vgpdf/cp1252.map
generated
vendored
Normal file
251
vendor/gonum.org/v1/plot/vg/vgpdf/cp1252.map
generated
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
!00 U+0000 .notdef
|
||||
!01 U+0001 .notdef
|
||||
!02 U+0002 .notdef
|
||||
!03 U+0003 .notdef
|
||||
!04 U+0004 .notdef
|
||||
!05 U+0005 .notdef
|
||||
!06 U+0006 .notdef
|
||||
!07 U+0007 .notdef
|
||||
!08 U+0008 .notdef
|
||||
!09 U+0009 .notdef
|
||||
!0A U+000A .notdef
|
||||
!0B U+000B .notdef
|
||||
!0C U+000C .notdef
|
||||
!0D U+000D .notdef
|
||||
!0E U+000E .notdef
|
||||
!0F U+000F .notdef
|
||||
!10 U+0010 .notdef
|
||||
!11 U+0011 .notdef
|
||||
!12 U+0012 .notdef
|
||||
!13 U+0013 .notdef
|
||||
!14 U+0014 .notdef
|
||||
!15 U+0015 .notdef
|
||||
!16 U+0016 .notdef
|
||||
!17 U+0017 .notdef
|
||||
!18 U+0018 .notdef
|
||||
!19 U+0019 .notdef
|
||||
!1A U+001A .notdef
|
||||
!1B U+001B .notdef
|
||||
!1C U+001C .notdef
|
||||
!1D U+001D .notdef
|
||||
!1E U+001E .notdef
|
||||
!1F U+001F .notdef
|
||||
!20 U+0020 space
|
||||
!21 U+0021 exclam
|
||||
!22 U+0022 quotedbl
|
||||
!23 U+0023 numbersign
|
||||
!24 U+0024 dollar
|
||||
!25 U+0025 percent
|
||||
!26 U+0026 ampersand
|
||||
!27 U+0027 quotesingle
|
||||
!28 U+0028 parenleft
|
||||
!29 U+0029 parenright
|
||||
!2A U+002A asterisk
|
||||
!2B U+002B plus
|
||||
!2C U+002C comma
|
||||
!2D U+002D hyphen
|
||||
!2E U+002E period
|
||||
!2F U+002F slash
|
||||
!30 U+0030 zero
|
||||
!31 U+0031 one
|
||||
!32 U+0032 two
|
||||
!33 U+0033 three
|
||||
!34 U+0034 four
|
||||
!35 U+0035 five
|
||||
!36 U+0036 six
|
||||
!37 U+0037 seven
|
||||
!38 U+0038 eight
|
||||
!39 U+0039 nine
|
||||
!3A U+003A colon
|
||||
!3B U+003B semicolon
|
||||
!3C U+003C less
|
||||
!3D U+003D equal
|
||||
!3E U+003E greater
|
||||
!3F U+003F question
|
||||
!40 U+0040 at
|
||||
!41 U+0041 A
|
||||
!42 U+0042 B
|
||||
!43 U+0043 C
|
||||
!44 U+0044 D
|
||||
!45 U+0045 E
|
||||
!46 U+0046 F
|
||||
!47 U+0047 G
|
||||
!48 U+0048 H
|
||||
!49 U+0049 I
|
||||
!4A U+004A J
|
||||
!4B U+004B K
|
||||
!4C U+004C L
|
||||
!4D U+004D M
|
||||
!4E U+004E N
|
||||
!4F U+004F O
|
||||
!50 U+0050 P
|
||||
!51 U+0051 Q
|
||||
!52 U+0052 R
|
||||
!53 U+0053 S
|
||||
!54 U+0054 T
|
||||
!55 U+0055 U
|
||||
!56 U+0056 V
|
||||
!57 U+0057 W
|
||||
!58 U+0058 X
|
||||
!59 U+0059 Y
|
||||
!5A U+005A Z
|
||||
!5B U+005B bracketleft
|
||||
!5C U+005C backslash
|
||||
!5D U+005D bracketright
|
||||
!5E U+005E asciicircum
|
||||
!5F U+005F underscore
|
||||
!60 U+0060 grave
|
||||
!61 U+0061 a
|
||||
!62 U+0062 b
|
||||
!63 U+0063 c
|
||||
!64 U+0064 d
|
||||
!65 U+0065 e
|
||||
!66 U+0066 f
|
||||
!67 U+0067 g
|
||||
!68 U+0068 h
|
||||
!69 U+0069 i
|
||||
!6A U+006A j
|
||||
!6B U+006B k
|
||||
!6C U+006C l
|
||||
!6D U+006D m
|
||||
!6E U+006E n
|
||||
!6F U+006F o
|
||||
!70 U+0070 p
|
||||
!71 U+0071 q
|
||||
!72 U+0072 r
|
||||
!73 U+0073 s
|
||||
!74 U+0074 t
|
||||
!75 U+0075 u
|
||||
!76 U+0076 v
|
||||
!77 U+0077 w
|
||||
!78 U+0078 x
|
||||
!79 U+0079 y
|
||||
!7A U+007A z
|
||||
!7B U+007B braceleft
|
||||
!7C U+007C bar
|
||||
!7D U+007D braceright
|
||||
!7E U+007E asciitilde
|
||||
!7F U+007F .notdef
|
||||
!80 U+20AC Euro
|
||||
!82 U+201A quotesinglbase
|
||||
!83 U+0192 florin
|
||||
!84 U+201E quotedblbase
|
||||
!85 U+2026 ellipsis
|
||||
!86 U+2020 dagger
|
||||
!87 U+2021 daggerdbl
|
||||
!88 U+02C6 circumflex
|
||||
!89 U+2030 perthousand
|
||||
!8A U+0160 Scaron
|
||||
!8B U+2039 guilsinglleft
|
||||
!8C U+0152 OE
|
||||
!8E U+017D Zcaron
|
||||
!91 U+2018 quoteleft
|
||||
!92 U+2019 quoteright
|
||||
!93 U+201C quotedblleft
|
||||
!94 U+201D quotedblright
|
||||
!95 U+2022 bullet
|
||||
!96 U+2013 endash
|
||||
!97 U+2014 emdash
|
||||
!98 U+02DC tilde
|
||||
!99 U+2122 trademark
|
||||
!9A U+0161 scaron
|
||||
!9B U+203A guilsinglright
|
||||
!9C U+0153 oe
|
||||
!9E U+017E zcaron
|
||||
!9F U+0178 Ydieresis
|
||||
!A0 U+00A0 space
|
||||
!A1 U+00A1 exclamdown
|
||||
!A2 U+00A2 cent
|
||||
!A3 U+00A3 sterling
|
||||
!A4 U+00A4 currency
|
||||
!A5 U+00A5 yen
|
||||
!A6 U+00A6 brokenbar
|
||||
!A7 U+00A7 section
|
||||
!A8 U+00A8 dieresis
|
||||
!A9 U+00A9 copyright
|
||||
!AA U+00AA ordfeminine
|
||||
!AB U+00AB guillemotleft
|
||||
!AC U+00AC logicalnot
|
||||
!AD U+00AD hyphen
|
||||
!AE U+00AE registered
|
||||
!AF U+00AF macron
|
||||
!B0 U+00B0 degree
|
||||
!B1 U+00B1 plusminus
|
||||
!B2 U+00B2 twosuperior
|
||||
!B3 U+00B3 threesuperior
|
||||
!B4 U+00B4 acute
|
||||
!B5 U+00B5 mu
|
||||
!B6 U+00B6 paragraph
|
||||
!B7 U+00B7 periodcentered
|
||||
!B8 U+00B8 cedilla
|
||||
!B9 U+00B9 onesuperior
|
||||
!BA U+00BA ordmasculine
|
||||
!BB U+00BB guillemotright
|
||||
!BC U+00BC onequarter
|
||||
!BD U+00BD onehalf
|
||||
!BE U+00BE threequarters
|
||||
!BF U+00BF questiondown
|
||||
!C0 U+00C0 Agrave
|
||||
!C1 U+00C1 Aacute
|
||||
!C2 U+00C2 Acircumflex
|
||||
!C3 U+00C3 Atilde
|
||||
!C4 U+00C4 Adieresis
|
||||
!C5 U+00C5 Aring
|
||||
!C6 U+00C6 AE
|
||||
!C7 U+00C7 Ccedilla
|
||||
!C8 U+00C8 Egrave
|
||||
!C9 U+00C9 Eacute
|
||||
!CA U+00CA Ecircumflex
|
||||
!CB U+00CB Edieresis
|
||||
!CC U+00CC Igrave
|
||||
!CD U+00CD Iacute
|
||||
!CE U+00CE Icircumflex
|
||||
!CF U+00CF Idieresis
|
||||
!D0 U+00D0 Eth
|
||||
!D1 U+00D1 Ntilde
|
||||
!D2 U+00D2 Ograve
|
||||
!D3 U+00D3 Oacute
|
||||
!D4 U+00D4 Ocircumflex
|
||||
!D5 U+00D5 Otilde
|
||||
!D6 U+00D6 Odieresis
|
||||
!D7 U+00D7 multiply
|
||||
!D8 U+00D8 Oslash
|
||||
!D9 U+00D9 Ugrave
|
||||
!DA U+00DA Uacute
|
||||
!DB U+00DB Ucircumflex
|
||||
!DC U+00DC Udieresis
|
||||
!DD U+00DD Yacute
|
||||
!DE U+00DE Thorn
|
||||
!DF U+00DF germandbls
|
||||
!E0 U+00E0 agrave
|
||||
!E1 U+00E1 aacute
|
||||
!E2 U+00E2 acircumflex
|
||||
!E3 U+00E3 atilde
|
||||
!E4 U+00E4 adieresis
|
||||
!E5 U+00E5 aring
|
||||
!E6 U+00E6 ae
|
||||
!E7 U+00E7 ccedilla
|
||||
!E8 U+00E8 egrave
|
||||
!E9 U+00E9 eacute
|
||||
!EA U+00EA ecircumflex
|
||||
!EB U+00EB edieresis
|
||||
!EC U+00EC igrave
|
||||
!ED U+00ED iacute
|
||||
!EE U+00EE icircumflex
|
||||
!EF U+00EF idieresis
|
||||
!F0 U+00F0 eth
|
||||
!F1 U+00F1 ntilde
|
||||
!F2 U+00F2 ograve
|
||||
!F3 U+00F3 oacute
|
||||
!F4 U+00F4 ocircumflex
|
||||
!F5 U+00F5 otilde
|
||||
!F6 U+00F6 odieresis
|
||||
!F7 U+00F7 divide
|
||||
!F8 U+00F8 oslash
|
||||
!F9 U+00F9 ugrave
|
||||
!FA U+00FA uacute
|
||||
!FB U+00FB ucircumflex
|
||||
!FC U+00FC udieresis
|
||||
!FD U+00FD yacute
|
||||
!FE U+00FE thorn
|
||||
!FF U+00FF ydieresis
|
||||
490
vendor/gonum.org/v1/plot/vg/vgpdf/vgpdf.go
generated
vendored
Normal file
490
vendor/gonum.org/v1/plot/vg/vgpdf/vgpdf.go
generated
vendored
Normal file
@@ -0,0 +1,490 @@
|
||||
// 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 vgpdf implements the vg.Canvas interface
|
||||
// using gofpdf (github.com/phpdave11/gofpdf).
|
||||
package vgpdf // import "gonum.org/v1/plot/vg/vgpdf"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
pdf "github.com/go-pdf/fpdf"
|
||||
stdfnt "golang.org/x/image/font"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// codePageEncoding holds informations about the characters encoding of TrueType
|
||||
// font files, needed by gofpdf to embed fonts in a PDF document.
|
||||
// We use cp1252 (code page 1252, Windows Western) to encode characters.
|
||||
// See:
|
||||
// - https://en.wikipedia.org/wiki/Windows-1252
|
||||
//
|
||||
// TODO: provide a Canvas-level func option to embed fonts with a user provided
|
||||
// code page schema?
|
||||
//
|
||||
//go:embed cp1252.map
|
||||
var codePageEncoding []byte
|
||||
|
||||
func init() {
|
||||
draw.RegisterFormat("pdf", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return New(w, h)
|
||||
})
|
||||
}
|
||||
|
||||
// DPI is the nominal resolution of drawing in PDF.
|
||||
const DPI = 72
|
||||
|
||||
// Canvas implements the vg.Canvas interface,
|
||||
// drawing to a PDF.
|
||||
type Canvas struct {
|
||||
doc *pdf.Fpdf
|
||||
w, h vg.Length
|
||||
|
||||
dpi int
|
||||
numImages int
|
||||
stack []context
|
||||
fonts map[font.Font]struct{}
|
||||
|
||||
// Switch to embed fonts in PDF file.
|
||||
// The default is to embed fonts.
|
||||
// This makes the PDF file more portable but also larger.
|
||||
embed bool
|
||||
}
|
||||
|
||||
type context struct {
|
||||
fill color.Color
|
||||
line color.Color
|
||||
width vg.Length
|
||||
}
|
||||
|
||||
// New creates a new PDF Canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
cfg := pdf.InitType{
|
||||
UnitStr: "pt",
|
||||
Size: pdf.SizeType{Wd: w.Points(), Ht: h.Points()},
|
||||
}
|
||||
c := &Canvas{
|
||||
doc: pdf.NewCustom(&cfg),
|
||||
w: w,
|
||||
h: h,
|
||||
dpi: DPI,
|
||||
stack: make([]context, 1),
|
||||
fonts: make(map[font.Font]struct{}),
|
||||
embed: true,
|
||||
}
|
||||
c.NextPage()
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// EmbedFonts specifies whether the resulting PDF canvas should
|
||||
// embed the fonts or not.
|
||||
// EmbedFonts returns the previous value before modification.
|
||||
func (c *Canvas) EmbedFonts(v bool) bool {
|
||||
prev := c.embed
|
||||
c.embed = v
|
||||
return prev
|
||||
}
|
||||
|
||||
func (c *Canvas) DPI() float64 {
|
||||
return float64(c.dpi)
|
||||
}
|
||||
|
||||
func (c *Canvas) context() *context {
|
||||
return &c.stack[len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineWidth(w vg.Length) {
|
||||
c.context().width = w
|
||||
lw := c.unit(w)
|
||||
c.doc.SetLineWidth(lw)
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) {
|
||||
ds := make([]float64, len(dashes))
|
||||
for i, d := range dashes {
|
||||
ds[i] = c.unit(d)
|
||||
}
|
||||
c.doc.SetDashPattern(ds, c.unit(offs))
|
||||
}
|
||||
|
||||
func (c *Canvas) SetColor(clr color.Color) {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
c.context().line = clr
|
||||
c.context().fill = clr
|
||||
r, g, b, a := rgba(clr)
|
||||
c.doc.SetFillColor(r, g, b)
|
||||
c.doc.SetDrawColor(r, g, b)
|
||||
c.doc.SetTextColor(r, g, b)
|
||||
c.doc.SetAlpha(a, "Normal")
|
||||
}
|
||||
|
||||
func (c *Canvas) Rotate(r float64) {
|
||||
c.doc.TransformRotate(-r*180/math.Pi, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Canvas) Translate(pt vg.Point) {
|
||||
xp, yp := c.pdfPoint(pt)
|
||||
c.doc.TransformTranslate(xp, yp)
|
||||
}
|
||||
|
||||
func (c *Canvas) Scale(x float64, y float64) {
|
||||
c.doc.TransformScale(x*100, y*100, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Canvas) Push() {
|
||||
c.stack = append(c.stack, *c.context())
|
||||
c.doc.TransformBegin()
|
||||
}
|
||||
|
||||
func (c *Canvas) Pop() {
|
||||
c.doc.TransformEnd()
|
||||
c.stack = c.stack[:len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) Stroke(p vg.Path) {
|
||||
if c.context().width > 0 {
|
||||
c.pdfPath(p, "D")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Canvas) Fill(p vg.Path) {
|
||||
c.pdfPath(p, "F")
|
||||
}
|
||||
|
||||
func (c *Canvas) FillString(fnt font.Face, pt vg.Point, str string) {
|
||||
if fnt.Font.Size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.font(fnt, pt)
|
||||
style := ""
|
||||
if fnt.Font.Weight == stdfnt.WeightBold {
|
||||
style += "B"
|
||||
}
|
||||
if fnt.Font.Style == stdfnt.StyleItalic {
|
||||
style += "I"
|
||||
}
|
||||
c.doc.SetFont(fnt.Name(), style, c.unit(fnt.Font.Size))
|
||||
|
||||
c.Push()
|
||||
defer c.Pop()
|
||||
c.Translate(pt)
|
||||
// go-fpdf uses the top left corner as origin.
|
||||
c.Scale(1, -1)
|
||||
left, top, right, bottom := c.sbounds(fnt, str)
|
||||
w := right - left
|
||||
h := bottom - top
|
||||
margin := c.doc.GetCellMargin()
|
||||
|
||||
c.doc.MoveTo(-left-margin, top)
|
||||
c.doc.CellFormat(w, h, str, "", 0, "BL", false, 0, "")
|
||||
}
|
||||
|
||||
func (c *Canvas) sbounds(fnt font.Face, txt string) (left, top, right, bottom float64) {
|
||||
_, h := c.doc.GetFontSize()
|
||||
style := ""
|
||||
if fnt.Font.Weight == stdfnt.WeightBold {
|
||||
style += "B"
|
||||
}
|
||||
if fnt.Font.Style == stdfnt.StyleItalic {
|
||||
style += "I"
|
||||
}
|
||||
d := c.doc.GetFontDesc(fnt.Name(), style)
|
||||
if d.Ascent == 0 {
|
||||
// not defined (standard font?), use average of 81%
|
||||
top = 0.81 * h
|
||||
} else {
|
||||
top = -float64(d.Ascent) * h / float64(d.Ascent-d.Descent)
|
||||
}
|
||||
return 0, top, c.doc.GetStringWidth(txt), top + h
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
opts := pdf.ImageOptions{ImageType: "png", ReadDpi: true}
|
||||
name := c.imageName()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err := png.Encode(buf, img)
|
||||
if err != nil {
|
||||
log.Panicf("error encoding image to PNG: %v", err)
|
||||
}
|
||||
c.doc.RegisterImageOptionsReader(name, opts, buf)
|
||||
|
||||
xp, yp := c.pdfPoint(rect.Min)
|
||||
wp, hp := c.pdfPoint(rect.Size())
|
||||
|
||||
c.doc.ImageOptions(name, xp, yp, wp, hp, false, opts, 0, "")
|
||||
}
|
||||
|
||||
// font registers a font and a size with the PDF canvas.
|
||||
func (c *Canvas) font(fnt font.Face, pt vg.Point) {
|
||||
if _, ok := c.fonts[fnt.Font]; ok {
|
||||
return
|
||||
}
|
||||
name := fnt.Name()
|
||||
key := fontKey{font: fnt, embed: c.embed}
|
||||
raw := new(bytes.Buffer)
|
||||
_, err := fnt.Face.WriteSourceTo(nil, raw)
|
||||
if err != nil {
|
||||
log.Panicf("vgpdf: could not generate font %q data for PDF: %+v", name, err)
|
||||
}
|
||||
|
||||
zdata, jdata, err := getFont(key, raw.Bytes(), codePageEncoding)
|
||||
if err != nil {
|
||||
log.Panicf("vgpdf: could not generate font data for PDF: %v", err)
|
||||
}
|
||||
|
||||
c.fonts[fnt.Font] = struct{}{}
|
||||
c.doc.AddFontFromBytes(name, "", jdata, zdata)
|
||||
}
|
||||
|
||||
// pdfPath processes a vg.Path and applies it to the canvas.
|
||||
func (c *Canvas) pdfPath(path vg.Path, style string) {
|
||||
var (
|
||||
xp float64
|
||||
yp float64
|
||||
)
|
||||
for _, comp := range path {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
xp, yp = c.pdfPoint(comp.Pos)
|
||||
c.doc.MoveTo(xp, yp)
|
||||
case vg.LineComp:
|
||||
c.doc.LineTo(c.pdfPoint(comp.Pos))
|
||||
case vg.ArcComp:
|
||||
c.arc(comp, style)
|
||||
case vg.CurveComp:
|
||||
px, py := c.pdfPoint(comp.Pos)
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
cx, cy := c.pdfPoint(comp.Control[0])
|
||||
c.doc.CurveTo(cx, cy, px, py)
|
||||
case 2:
|
||||
cx, cy := c.pdfPoint(comp.Control[0])
|
||||
dx, dy := c.pdfPoint(comp.Control[1])
|
||||
c.doc.CurveBezierCubicTo(cx, cy, dx, dy, px, py)
|
||||
default:
|
||||
panic("vgpdf: invalid number of control points")
|
||||
}
|
||||
case vg.CloseComp:
|
||||
c.doc.LineTo(xp, yp)
|
||||
c.doc.ClosePath()
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
|
||||
}
|
||||
}
|
||||
c.doc.DrawPath(style)
|
||||
}
|
||||
|
||||
func (c *Canvas) arc(comp vg.PathComp, style string) {
|
||||
x0 := comp.Pos.X + comp.Radius*vg.Length(math.Cos(comp.Start))
|
||||
y0 := comp.Pos.Y + comp.Radius*vg.Length(math.Sin(comp.Start))
|
||||
c.doc.LineTo(c.pdfPointXY(x0, y0))
|
||||
r := c.unit(comp.Radius)
|
||||
const deg = 180 / math.Pi
|
||||
angle := comp.Angle * deg
|
||||
beg := comp.Start * deg
|
||||
end := beg + angle
|
||||
x := c.unit(comp.Pos.X)
|
||||
y := c.unit(comp.Pos.Y)
|
||||
c.doc.Arc(x, y, r, r, angle, beg, end, style)
|
||||
x1 := comp.Pos.X + comp.Radius*vg.Length(math.Cos(comp.Start+comp.Angle))
|
||||
y1 := comp.Pos.Y + comp.Radius*vg.Length(math.Sin(comp.Start+comp.Angle))
|
||||
c.doc.MoveTo(c.pdfPointXY(x1, y1))
|
||||
}
|
||||
|
||||
func (c *Canvas) pdfPointXY(x, y vg.Length) (float64, float64) {
|
||||
return c.unit(x), c.unit(y)
|
||||
}
|
||||
|
||||
func (c *Canvas) pdfPoint(pt vg.Point) (float64, float64) {
|
||||
return c.unit(pt.X), c.unit(pt.Y)
|
||||
}
|
||||
|
||||
// unit returns a fpdf.Unit, converted from a vg.Length.
|
||||
func (c *Canvas) unit(l vg.Length) float64 {
|
||||
return l.Dots(c.DPI())
|
||||
}
|
||||
|
||||
// imageName generates a unique image name for this PDF canvas
|
||||
func (c *Canvas) imageName() string {
|
||||
c.numImages++
|
||||
return fmt.Sprintf("image_%03d.png", c.numImages)
|
||||
}
|
||||
|
||||
// WriterCounter implements the io.Writer interface, and counts
|
||||
// the total number of bytes written.
|
||||
type writerCounter struct {
|
||||
io.Writer
|
||||
n int64
|
||||
}
|
||||
|
||||
func (w *writerCounter) Write(p []byte) (int, error) {
|
||||
n, err := w.Writer.Write(p)
|
||||
w.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// WriteTo writes the Canvas to an io.Writer.
|
||||
// After calling Write, the canvas is closed
|
||||
// and may no longer be used for drawing.
|
||||
func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
|
||||
c.Pop()
|
||||
c.doc.Close()
|
||||
wc := writerCounter{Writer: w}
|
||||
b := bufio.NewWriter(&wc)
|
||||
if err := c.doc.Output(b); err != nil {
|
||||
return wc.n, err
|
||||
}
|
||||
err := b.Flush()
|
||||
return wc.n, err
|
||||
}
|
||||
|
||||
// rgba converts a Go color into a gofpdf 3-tuple int + 1 float64
|
||||
func rgba(c color.Color) (int, int, int, float64) {
|
||||
if c == nil {
|
||||
c = color.Black
|
||||
}
|
||||
r, g, b, a := c.RGBA()
|
||||
return int(r >> 8), int(g >> 8), int(b >> 8), float64(a) / math.MaxUint16
|
||||
}
|
||||
|
||||
type fontsCache struct {
|
||||
sync.RWMutex
|
||||
cache map[fontKey]fontVal
|
||||
}
|
||||
|
||||
// fontKey represents a PDF font request.
|
||||
// fontKey needs to know whether the font will be embedded or not,
|
||||
// as gofpdf.MakeFont will generate different informations.
|
||||
type fontKey struct {
|
||||
font font.Face
|
||||
embed bool
|
||||
}
|
||||
|
||||
type fontVal struct {
|
||||
z, j []byte
|
||||
}
|
||||
|
||||
func (c *fontsCache) get(key fontKey) (fontVal, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
v, ok := c.cache[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (c *fontsCache) add(k fontKey, v fontVal) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.cache[k] = v
|
||||
}
|
||||
|
||||
var pdfFonts = &fontsCache{
|
||||
cache: make(map[fontKey]fontVal),
|
||||
}
|
||||
|
||||
func getFont(key fontKey, font, encoding []byte) (z, j []byte, err error) {
|
||||
if v, ok := pdfFonts.get(key); ok {
|
||||
return v.z, v.j, nil
|
||||
}
|
||||
|
||||
v, err := makeFont(key, font, encoding)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return v.z, v.j, nil
|
||||
}
|
||||
|
||||
func makeFont(key fontKey, font, encoding []byte) (val fontVal, err error) {
|
||||
tmpdir, err := os.MkdirTemp("", "gofpdf-makefont-")
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
indir := filepath.Join(tmpdir, "input")
|
||||
err = os.Mkdir(indir, 0755)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
outdir := filepath.Join(tmpdir, "output")
|
||||
err = os.Mkdir(outdir, 0755)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
fname := filepath.Join(indir, "font.ttf")
|
||||
encname := filepath.Join(indir, "cp1252.map")
|
||||
|
||||
err = os.WriteFile(fname, font, 0644)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(encname, encoding, 0644)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
err = pdf.MakeFont(fname, encname, outdir, io.Discard, key.embed)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
if key.embed {
|
||||
z, err := os.ReadFile(filepath.Join(outdir, "font.z"))
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
val.z = z
|
||||
}
|
||||
|
||||
j, err := os.ReadFile(filepath.Join(outdir, "font.json"))
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
val.j = j
|
||||
|
||||
pdfFonts.add(key, val)
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// NextPage creates a new page in the final PDF document.
|
||||
// The new page is the new current page.
|
||||
// Modifications applied to the canvas will only be applied to that new page.
|
||||
func (c *Canvas) NextPage() {
|
||||
if c.doc.PageNo() > 0 {
|
||||
c.Pop()
|
||||
}
|
||||
c.doc.SetMargins(0, 0, 0)
|
||||
c.doc.AddPage()
|
||||
c.Push()
|
||||
c.Translate(vg.Point{X: 0, Y: c.h})
|
||||
c.Scale(1, -1)
|
||||
}
|
||||
650
vendor/gonum.org/v1/plot/vg/vgsvg/vgsvg.go
generated
vendored
Normal file
650
vendor/gonum.org/v1/plot/vg/vgsvg/vgsvg.go
generated
vendored
Normal file
@@ -0,0 +1,650 @@
|
||||
// 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 vgsvg uses svgo (github.com/ajstarks/svgo)
|
||||
// as a backend for vg.
|
||||
//
|
||||
// By default, gonum/plot uses the Liberation fonts.
|
||||
// When embedding was not requested during plot creation, it may happen that
|
||||
// the generated SVG plot may not display well if the Liberation fonts are not
|
||||
// available to the program displaying the SVG plot.
|
||||
// See gonum.org/v1/plot/vg/vgsvg#Example_standardFonts for how to work around
|
||||
// this issue.
|
||||
//
|
||||
// Alternatively, users may want to install the Liberation fonts on their system:
|
||||
// - https://en.wikipedia.org/wiki/Liberation_fonts
|
||||
package vgsvg // import "gonum.org/v1/plot/vg/vgsvg"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
svgo "github.com/ajstarks/svgo"
|
||||
xfnt "golang.org/x/image/font"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
draw.RegisterFormat("svg", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return New(w, h)
|
||||
})
|
||||
}
|
||||
|
||||
// pr is the precision to use when outputting float64s.
|
||||
const pr = 5
|
||||
|
||||
const (
|
||||
// DefaultWidth and DefaultHeight are the default canvas
|
||||
// dimensions.
|
||||
DefaultWidth = 4 * vg.Inch
|
||||
DefaultHeight = 4 * vg.Inch
|
||||
)
|
||||
|
||||
// Canvas implements the vg.Canvas interface, drawing to a SVG document.
|
||||
//
|
||||
// By default, fonts used by the canvas are not embedded in the produced
|
||||
// SVG document. This results in smaller but less portable SVG plots.
|
||||
// Users wanting completely portable SVG documents should create SVG canvases
|
||||
// with the EmbedFonts function.
|
||||
type Canvas struct {
|
||||
svg *svgo.SVG
|
||||
w, h vg.Length
|
||||
|
||||
hdr *bytes.Buffer // hdr is the SVG prelude, it may contain embedded fonts.
|
||||
buf *bytes.Buffer // buf is the SVG document.
|
||||
stack []context
|
||||
|
||||
// Switch to embed fonts in SVG file.
|
||||
// The default is to *not* embed fonts.
|
||||
// Embedding fonts makes the SVG file larger but also more portable.
|
||||
embed bool
|
||||
fonts map[string]struct{} // set of already embedded fonts
|
||||
}
|
||||
|
||||
type context struct {
|
||||
color color.Color
|
||||
dashArray []vg.Length
|
||||
dashOffset vg.Length
|
||||
lineWidth vg.Length
|
||||
gEnds int
|
||||
}
|
||||
|
||||
type option func(*Canvas)
|
||||
|
||||
// UseWH specifies the width and height of the canvas.
|
||||
func UseWH(w, h vg.Length) option {
|
||||
return func(c *Canvas) {
|
||||
if w <= 0 || h <= 0 {
|
||||
panic("vgsvg: w and h must both be > 0")
|
||||
}
|
||||
c.w = w
|
||||
c.h = h
|
||||
}
|
||||
}
|
||||
|
||||
// EmbedFonts specifies whether fonts should be embedded inside
|
||||
// the SVG canvas.
|
||||
func EmbedFonts(v bool) option {
|
||||
return func(c *Canvas) {
|
||||
c.embed = v
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new image canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
return NewWith(UseWH(w, h))
|
||||
}
|
||||
|
||||
// NewWith returns a new image canvas created according to the specified
|
||||
// options. The currently accepted options is UseWH. If size is not
|
||||
// specified, the default is used.
|
||||
func NewWith(opts ...option) *Canvas {
|
||||
buf := new(bytes.Buffer)
|
||||
c := &Canvas{
|
||||
svg: svgo.New(buf),
|
||||
w: DefaultWidth,
|
||||
h: DefaultHeight,
|
||||
hdr: new(bytes.Buffer),
|
||||
buf: buf,
|
||||
stack: []context{{}},
|
||||
embed: false,
|
||||
fonts: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
// This is like svg.Start, except it uses floats
|
||||
// and specifies the units.
|
||||
fmt.Fprintf(c.hdr, `<?xml version="1.0"?>
|
||||
<!-- Generated by SVGo and Plotinum VG -->
|
||||
<svg width="%.*gpt" height="%.*gpt" viewBox="0 0 %.*g %.*g"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">`+"\n",
|
||||
pr, c.w,
|
||||
pr, c.h,
|
||||
pr, c.w,
|
||||
pr, c.h,
|
||||
)
|
||||
|
||||
if c.embed {
|
||||
fmt.Fprintf(c.hdr, "<defs>\n\t<style>\n")
|
||||
}
|
||||
|
||||
// Swap the origin to the bottom left.
|
||||
// This must be matched with a </g> when saving,
|
||||
// before the closing </svg>.
|
||||
c.svg.Gtransform(fmt.Sprintf("scale(1, -1) translate(0, -%.*g)", pr, c.h.Points()))
|
||||
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
func (c *Canvas) context() *context {
|
||||
return &c.stack[len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineWidth(w vg.Length) {
|
||||
c.context().lineWidth = w
|
||||
}
|
||||
|
||||
func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) {
|
||||
c.context().dashArray = dashes
|
||||
c.context().dashOffset = offs
|
||||
}
|
||||
|
||||
func (c *Canvas) SetColor(clr color.Color) {
|
||||
c.context().color = clr
|
||||
}
|
||||
|
||||
func (c *Canvas) Rotate(rot float64) {
|
||||
rot = rot * 180 / math.Pi
|
||||
c.svg.Rotate(rot)
|
||||
c.context().gEnds++
|
||||
}
|
||||
|
||||
func (c *Canvas) Translate(pt vg.Point) {
|
||||
c.svg.Gtransform(fmt.Sprintf("translate(%.*g, %.*g)", pr, pt.X.Points(), pr, pt.Y.Points()))
|
||||
c.context().gEnds++
|
||||
}
|
||||
|
||||
func (c *Canvas) Scale(x, y float64) {
|
||||
c.svg.ScaleXY(x, y)
|
||||
c.context().gEnds++
|
||||
}
|
||||
|
||||
func (c *Canvas) Push() {
|
||||
top := *c.context()
|
||||
top.gEnds = 0
|
||||
c.stack = append(c.stack, top)
|
||||
}
|
||||
|
||||
func (c *Canvas) Pop() {
|
||||
for i := 0; i < c.context().gEnds; i++ {
|
||||
c.svg.Gend()
|
||||
}
|
||||
c.stack = c.stack[:len(c.stack)-1]
|
||||
}
|
||||
|
||||
func (c *Canvas) Stroke(path vg.Path) {
|
||||
if c.context().lineWidth.Points() <= 0 {
|
||||
return
|
||||
}
|
||||
c.svg.Path(c.pathData(path),
|
||||
style(elm("fill", "#000000", "none"),
|
||||
elm("stroke", "none", colorString(c.context().color)),
|
||||
elm("stroke-opacity", "1", opacityString(c.context().color)),
|
||||
elm("stroke-width", "1", "%.*g", pr, c.context().lineWidth.Points()),
|
||||
elm("stroke-dasharray", "none", dashArrayString(c)),
|
||||
elm("stroke-dashoffset", "0", "%.*g", pr, c.context().dashOffset.Points())))
|
||||
}
|
||||
|
||||
func (c *Canvas) Fill(path vg.Path) {
|
||||
c.svg.Path(c.pathData(path),
|
||||
style(elm("fill", "#000000", colorString(c.context().color)),
|
||||
elm("fill-opacity", "1", opacityString(c.context().color))))
|
||||
}
|
||||
|
||||
func (c *Canvas) pathData(path vg.Path) string {
|
||||
buf := new(bytes.Buffer)
|
||||
var x, y float64
|
||||
for _, comp := range path {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
fmt.Fprintf(buf, "M%.*g,%.*g", pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
x = comp.Pos.X.Points()
|
||||
y = comp.Pos.Y.Points()
|
||||
case vg.LineComp:
|
||||
fmt.Fprintf(buf, "L%.*g,%.*g", pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
x = comp.Pos.X.Points()
|
||||
y = comp.Pos.Y.Points()
|
||||
case vg.ArcComp:
|
||||
r := comp.Radius.Points()
|
||||
sin, cos := math.Sincos(comp.Start)
|
||||
x0 := comp.Pos.X.Points() + r*cos
|
||||
y0 := comp.Pos.Y.Points() + r*sin
|
||||
if x0 != x || y0 != y {
|
||||
fmt.Fprintf(buf, "L%.*g,%.*g", pr, x0, pr, y0)
|
||||
}
|
||||
if math.Abs(comp.Angle) >= 2*math.Pi {
|
||||
x, y = circle(buf, c, &comp)
|
||||
} else {
|
||||
x, y = arc(buf, c, &comp)
|
||||
}
|
||||
case vg.CurveComp:
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
fmt.Fprintf(buf, "Q%.*g,%.*g,%.*g,%.*g",
|
||||
pr, comp.Control[0].X.Points(), pr, comp.Control[0].Y.Points(),
|
||||
pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
case 2:
|
||||
fmt.Fprintf(buf, "C%.*g,%.*g,%.*g,%.*g,%.*g,%.*g",
|
||||
pr, comp.Control[0].X.Points(), pr, comp.Control[0].Y.Points(),
|
||||
pr, comp.Control[1].X.Points(), pr, comp.Control[1].Y.Points(),
|
||||
pr, comp.Pos.X.Points(), pr, comp.Pos.Y.Points())
|
||||
default:
|
||||
panic("vgsvg: invalid number of control points")
|
||||
}
|
||||
x = comp.Pos.X.Points()
|
||||
y = comp.Pos.Y.Points()
|
||||
case vg.CloseComp:
|
||||
buf.WriteString("Z")
|
||||
default:
|
||||
panic(fmt.Sprintf("vgsvg: unknown path component type: %d", comp.Type))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// circle adds circle path data to the given writer.
|
||||
// Circles must be drawn using two arcs because
|
||||
// SVG disallows the start and end point of an arc
|
||||
// from being at the same location.
|
||||
func circle(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
|
||||
angle := 2 * math.Pi
|
||||
if comp.Angle < 0 {
|
||||
angle = -2 * math.Pi
|
||||
}
|
||||
angle += remainder(comp.Angle, 2*math.Pi)
|
||||
if angle >= 4*math.Pi {
|
||||
panic("Impossible angle")
|
||||
}
|
||||
|
||||
s0, c0 := math.Sincos(comp.Start + 0.5*angle)
|
||||
s1, c1 := math.Sincos(comp.Start + angle)
|
||||
|
||||
r := comp.Radius.Points()
|
||||
x0 := comp.Pos.X.Points() + r*c0
|
||||
y0 := comp.Pos.Y.Points() + r*s0
|
||||
x = comp.Pos.X.Points() + r*c1
|
||||
y = comp.Pos.Y.Points() + r*s1
|
||||
|
||||
fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
|
||||
large(angle/2), sweep(angle/2), pr, x0, pr, y0) //
|
||||
fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
|
||||
large(angle/2), sweep(angle/2), pr, x, pr, y)
|
||||
return
|
||||
}
|
||||
|
||||
// remainder returns the remainder of x/y.
|
||||
// We don't use math.Remainder because it
|
||||
// seems to return incorrect values due to how
|
||||
// IEEE defines the remainder operation…
|
||||
func remainder(x, y float64) float64 {
|
||||
return (x/y - math.Trunc(x/y)) * y
|
||||
}
|
||||
|
||||
// arc adds arc path data to the given writer.
|
||||
// Arc can only be used if the arc's angle is
|
||||
// less than a full circle, if it is greater then
|
||||
// circle should be used instead.
|
||||
func arc(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
|
||||
r := comp.Radius.Points()
|
||||
sin, cos := math.Sincos(comp.Start + comp.Angle)
|
||||
x = comp.Pos.X.Points() + r*cos
|
||||
y = comp.Pos.Y.Points() + r*sin
|
||||
fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
|
||||
large(comp.Angle), sweep(comp.Angle), pr, x, pr, y)
|
||||
return
|
||||
}
|
||||
|
||||
// sweep returns the arc sweep flag value for
|
||||
// the given angle.
|
||||
func sweep(a float64) int {
|
||||
if a < 0 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// large returns the arc's large flag value for
|
||||
// the given angle.
|
||||
func large(a float64) int {
|
||||
if math.Abs(a) >= math.Pi {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// FillString draws str at position pt using the specified font.
|
||||
// Text passed to FillString is escaped with html.EscapeString.
|
||||
func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) {
|
||||
name := svgFontDescr(font)
|
||||
sty := style(
|
||||
name,
|
||||
elm("font-size", "medium", "%.*gpx", pr, font.Font.Size.Points()),
|
||||
elm("fill", "#000000", colorString(c.context().color)),
|
||||
)
|
||||
if sty != "" {
|
||||
sty = "\n\t" + sty
|
||||
}
|
||||
fmt.Fprintf(
|
||||
c.buf,
|
||||
`<text x="%.*g" y="%.*g" transform="scale(1, -1)"%s>%s</text>`+"\n",
|
||||
pr, pt.X.Points(), pr, -pt.Y.Points(), sty, html.EscapeString(str),
|
||||
)
|
||||
|
||||
if c.embed {
|
||||
c.embedFont(name, font)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := png.Encode(buf, img)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("vgsvg: error encoding image to PNG: %+v", err))
|
||||
}
|
||||
str := "data:image/jpg;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
rsz := rect.Size()
|
||||
min := rect.Min
|
||||
var (
|
||||
width = rsz.X.Points()
|
||||
height = rsz.Y.Points()
|
||||
xmin = min.X.Points()
|
||||
ymin = min.Y.Points()
|
||||
)
|
||||
fmt.Fprintf(
|
||||
c.buf,
|
||||
`<image x="%v" y="%v" width="%v" height="%v" xlink:href="%s" %s />`+"\n",
|
||||
xmin,
|
||||
-ymin-height,
|
||||
width,
|
||||
height,
|
||||
str,
|
||||
// invert y so image is not upside-down
|
||||
`transform="scale(1, -1)"`,
|
||||
)
|
||||
}
|
||||
|
||||
// svgFontDescr returns a SVG compliant font name from the provided font face.
|
||||
func svgFontDescr(fnt font.Face) string {
|
||||
var (
|
||||
family = svgFamilyName(fnt)
|
||||
variant = svgVariantName(fnt.Font.Variant)
|
||||
style = svgStyleName(fnt.Font.Style)
|
||||
weight = svgWeightName(fnt.Font.Weight)
|
||||
)
|
||||
|
||||
o := "font-family:" + family + ";" +
|
||||
"font-variant:" + variant + ";" +
|
||||
"font-weight:" + weight + ";" +
|
||||
"font-style:" + style
|
||||
return o
|
||||
}
|
||||
|
||||
func svgFamilyName(fnt font.Face) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family
|
||||
var buf sfnt.Buffer
|
||||
name, err := fnt.Face.Name(&buf, sfnt.NameIDFamily)
|
||||
if err != nil {
|
||||
// this should never happen unless the underlying sfnt.Font data
|
||||
// is somehow corrupted.
|
||||
panic(fmt.Errorf(
|
||||
"vgsvg: could not extract family name from font %q: %+v",
|
||||
fnt.Font.Typeface,
|
||||
err,
|
||||
))
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func svgVariantName(v font.Variant) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant
|
||||
str := strings.ToLower(string(v))
|
||||
switch str {
|
||||
case "smallcaps":
|
||||
return "small-caps"
|
||||
case "mono", "monospace",
|
||||
"sans", "sansserif", "sans-serif",
|
||||
"serif":
|
||||
// handle mismatch between the meaning of gonum/plot/font.Font#Variant
|
||||
// and SVG's meaning for font-variant.
|
||||
// For SVG, mono, ... serif is encoded in the font-family attribute
|
||||
// whereas for gonum/plot it describes a variant among a collection of fonts.
|
||||
//
|
||||
// It shouldn't matter much if an invalid font-variant value is written
|
||||
// out (browsers will just ignore it; Firefox 98 and Chromium 91 do so.)
|
||||
return "normal"
|
||||
case "":
|
||||
return "none"
|
||||
default:
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
func svgStyleName(sty xfnt.Style) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style
|
||||
switch sty {
|
||||
case xfnt.StyleNormal:
|
||||
return "normal"
|
||||
case xfnt.StyleItalic:
|
||||
return "italic"
|
||||
case xfnt.StyleOblique:
|
||||
return "oblique"
|
||||
default:
|
||||
panic(fmt.Errorf("vgsvg: invalid font style %+v (v=%d)", sty, int(sty)))
|
||||
}
|
||||
}
|
||||
|
||||
func svgWeightName(w xfnt.Weight) string {
|
||||
// see:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
|
||||
switch w {
|
||||
case xfnt.WeightThin:
|
||||
return "100"
|
||||
case xfnt.WeightExtraLight:
|
||||
return "200"
|
||||
case xfnt.WeightLight:
|
||||
return "300"
|
||||
case xfnt.WeightNormal:
|
||||
return "normal"
|
||||
case xfnt.WeightMedium:
|
||||
return "500"
|
||||
case xfnt.WeightSemiBold:
|
||||
return "600"
|
||||
case xfnt.WeightBold:
|
||||
return "bold"
|
||||
case xfnt.WeightExtraBold:
|
||||
return "800"
|
||||
case xfnt.WeightBlack:
|
||||
return "900"
|
||||
default:
|
||||
panic(fmt.Errorf("vgsvg: invalid font weight %+v (v=%d)", w, int(w)))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Canvas) embedFont(name string, f font.Face) {
|
||||
if _, dup := c.fonts[name]; dup {
|
||||
return
|
||||
}
|
||||
c.fonts[name] = struct{}{}
|
||||
|
||||
raw := new(bytes.Buffer)
|
||||
_, err := f.Face.WriteSourceTo(nil, raw)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("vg/vgsvg: could not read font raw data: %+v", err))
|
||||
}
|
||||
|
||||
fmt.Fprintf(c.hdr, "\t\t@font-face{\n")
|
||||
fmt.Fprintf(c.hdr, "\t\t\tfont-family:%q;\n", svgFamilyName(f))
|
||||
fmt.Fprintf(c.hdr,
|
||||
"\t\t\tfont-variant:%s;font-weight:%s;font-style:%s;\n",
|
||||
svgVariantName(f.Font.Variant),
|
||||
svgWeightName(f.Font.Weight),
|
||||
svgStyleName(f.Font.Style),
|
||||
)
|
||||
|
||||
fmt.Fprintf(
|
||||
c.hdr,
|
||||
"\t\t\tsrc: url(data:font/ttf;charset=utf-8;base64,%s) format(\"truetype\");\n",
|
||||
base64.StdEncoding.EncodeToString(raw.Bytes()),
|
||||
)
|
||||
fmt.Fprintf(c.hdr, "\t\t}\n")
|
||||
}
|
||||
|
||||
type cwriter struct {
|
||||
w *bufio.Writer
|
||||
n int64
|
||||
}
|
||||
|
||||
func (c *cwriter) Write(p []byte) (int, error) {
|
||||
n, err := c.w.Write(p)
|
||||
c.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// WriteTo writes the canvas to an io.Writer.
|
||||
func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
|
||||
b := &cwriter{w: bufio.NewWriter(w)}
|
||||
|
||||
if c.embed {
|
||||
fmt.Fprintf(c.hdr, "\t</style>\n</defs>\n")
|
||||
}
|
||||
|
||||
_, err := c.hdr.WriteTo(b)
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
|
||||
_, err = c.buf.WriteTo(b)
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
|
||||
// Close the groups and svg in the output buffer
|
||||
// so that the Canvas is not closed and can be
|
||||
// used again if needed.
|
||||
for i := 0; i < c.nEnds(); i++ {
|
||||
_, err = fmt.Fprintln(b, "</g>")
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(b, "</svg>")
|
||||
if err != nil {
|
||||
return b.n, err
|
||||
}
|
||||
|
||||
return b.n, b.w.Flush()
|
||||
}
|
||||
|
||||
// nEnds returns the number of group ends
|
||||
// needed before the SVG is saved.
|
||||
func (c *Canvas) nEnds() int {
|
||||
n := 1 // close the transform that moves the origin
|
||||
for _, ctx := range c.stack {
|
||||
n += ctx.gEnds
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// style returns a style string composed of
|
||||
// all of the given elements. If the elements
|
||||
// are all empty then the empty string is
|
||||
// returned.
|
||||
func style(elms ...string) string {
|
||||
str := ""
|
||||
for _, e := range elms {
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
if str != "" {
|
||||
str += ";"
|
||||
}
|
||||
str += e
|
||||
}
|
||||
if str == "" {
|
||||
return ""
|
||||
}
|
||||
return "style=\"" + str + "\""
|
||||
}
|
||||
|
||||
// elm returns a style element string with the
|
||||
// given key and value. If the value matches
|
||||
// default then the empty string is returned.
|
||||
func elm(key, def, f string, vls ...interface{}) string {
|
||||
value := fmt.Sprintf(f, vls...)
|
||||
if value == def {
|
||||
return ""
|
||||
}
|
||||
return key + ":" + value
|
||||
}
|
||||
|
||||
// dashArrayString returns a string representing the
|
||||
// dash array specification.
|
||||
func dashArrayString(c *Canvas) string {
|
||||
str := ""
|
||||
for i, d := range c.context().dashArray {
|
||||
str += fmt.Sprintf("%.*g", pr, d.Points())
|
||||
if i < len(c.context().dashArray)-1 {
|
||||
str += ","
|
||||
}
|
||||
}
|
||||
if str == "" {
|
||||
str = "none"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// colorString returns the hexadecimal string representation of the color
|
||||
func colorString(clr color.Color) string {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
r, g, b, _a := clr.RGBA()
|
||||
a := 255.0 / float64(_a)
|
||||
return fmt.Sprintf("#%02X%02X%02X", int(float64(r)*a),
|
||||
int(float64(g)*a), int(float64(b)*a))
|
||||
}
|
||||
|
||||
// opacityString returns the opacity value of the given color.
|
||||
func opacityString(clr color.Color) string {
|
||||
if clr == nil {
|
||||
clr = color.Black
|
||||
}
|
||||
_, _, _, a := clr.RGBA()
|
||||
return fmt.Sprintf("%.*g", pr, float64(a)/math.MaxUint16)
|
||||
}
|
||||
329
vendor/gonum.org/v1/plot/vg/vgtex/canvas.go
generated
vendored
Normal file
329
vendor/gonum.org/v1/plot/vg/vgtex/canvas.go
generated
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
// Copyright ©2016 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 vgtex provides a vg.Canvas implementation for LaTeX, targeted at
|
||||
// the TikZ/PGF LaTeX package: https://sourceforge.net/projects/pgf
|
||||
//
|
||||
// vgtex generates PGF instructions that will be interpreted and rendered by LaTeX.
|
||||
// vgtex allows to put any valid LaTeX notation inside plot's strings.
|
||||
package vgtex // import "gonum.org/v1/plot/vg/vgtex"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gonum.org/v1/plot/font"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
const degPerRadian = 180 / math.Pi
|
||||
|
||||
const (
|
||||
defaultHeader = `%%%%%% generated by gonum/plot %%%%%%
|
||||
\documentclass{standalone}
|
||||
\usepackage{pgf}
|
||||
\begin{document}
|
||||
`
|
||||
defaultFooter = "\\end{document}\n"
|
||||
)
|
||||
|
||||
func init() {
|
||||
draw.RegisterFormat("tex", func(w, h vg.Length) vg.CanvasWriterTo {
|
||||
return NewDocument(w, h)
|
||||
})
|
||||
}
|
||||
|
||||
// Canvas implements the vg.Canvas interface, translating drawing
|
||||
// primitives from gonum/plot to PGF.
|
||||
type Canvas struct {
|
||||
buf *bytes.Buffer
|
||||
w, h vg.Length
|
||||
stack []context
|
||||
|
||||
// If document is true, Canvas.WriteTo will generate a standalone
|
||||
// .tex file that can be fed to, e.g., pdflatex.
|
||||
document bool
|
||||
id int64 // id is a unique identifier for this canvas
|
||||
}
|
||||
|
||||
type context struct {
|
||||
color color.Color
|
||||
dashArray []vg.Length
|
||||
dashOffset vg.Length
|
||||
linew vg.Length
|
||||
}
|
||||
|
||||
// New returns a new LaTeX canvas.
|
||||
func New(w, h vg.Length) *Canvas {
|
||||
return newCanvas(w, h, false)
|
||||
}
|
||||
|
||||
// NewDocument returns a new LaTeX canvas that can be readily
|
||||
// compiled into a standalone document.
|
||||
func NewDocument(w, h vg.Length) *Canvas {
|
||||
return newCanvas(w, h, true)
|
||||
}
|
||||
|
||||
func newCanvas(w, h vg.Length, document bool) *Canvas {
|
||||
c := &Canvas{
|
||||
buf: new(bytes.Buffer),
|
||||
w: w,
|
||||
h: h,
|
||||
document: document,
|
||||
id: time.Now().UnixNano(),
|
||||
}
|
||||
if !document {
|
||||
c.wtex(`%%%% gonum/plot created for LaTeX/pgf`)
|
||||
c.wtex(`%%%% you need to add:`)
|
||||
c.wtex(`%%%% \usepackage{pgf}`)
|
||||
c.wtex(`%%%% to your LaTeX document`)
|
||||
}
|
||||
c.wtex("")
|
||||
c.wtex(`\begin{pgfpicture}`)
|
||||
c.stack = make([]context, 1)
|
||||
vg.Initialize(c)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Canvas) context() *context {
|
||||
return &c.stack[len(c.stack)-1]
|
||||
}
|
||||
|
||||
// Size returns the width and height of the canvas.
|
||||
func (c *Canvas) Size() (w, h vg.Length) {
|
||||
return c.w, c.h
|
||||
}
|
||||
|
||||
// SetLineWidth implements the vg.Canvas.SetLineWidth method.
|
||||
func (c *Canvas) SetLineWidth(w vg.Length) {
|
||||
c.context().linew = w
|
||||
}
|
||||
|
||||
// SetLineDash implements the vg.Canvas.SetLineDash method.
|
||||
func (c *Canvas) SetLineDash(pattern []vg.Length, offset vg.Length) {
|
||||
c.context().dashArray = pattern
|
||||
c.context().dashOffset = offset
|
||||
}
|
||||
|
||||
// SetColor implements the vg.Canvas.SetColor method.
|
||||
func (c *Canvas) SetColor(clr color.Color) {
|
||||
c.context().color = clr
|
||||
}
|
||||
|
||||
// Rotate implements the vg.Canvas.Rotate method.
|
||||
func (c *Canvas) Rotate(rad float64) {
|
||||
c.wtex(`\pgftransformrotate{%g}`, rad*degPerRadian)
|
||||
}
|
||||
|
||||
// Translate implements the vg.Canvas.Translate method.
|
||||
func (c *Canvas) Translate(pt vg.Point) {
|
||||
c.wtex(`\pgftransformshift{\pgfpoint{%gpt}{%gpt}}`, pt.X, pt.Y)
|
||||
}
|
||||
|
||||
// Scale implements the vg.Canvas.Scale method.
|
||||
func (c *Canvas) Scale(x, y float64) {
|
||||
c.wtex(`\pgftransformxscale{%g}`, x)
|
||||
c.wtex(`\pgftransformyscale{%g}`, y)
|
||||
}
|
||||
|
||||
// Push implements the vg.Canvas.Push method.
|
||||
func (c *Canvas) Push() {
|
||||
c.wtex(`\begin{pgfscope}`)
|
||||
c.stack = append(c.stack, *c.context())
|
||||
}
|
||||
|
||||
// Pop implements the vg.Canvas.Pop method.
|
||||
func (c *Canvas) Pop() {
|
||||
c.stack = c.stack[:len(c.stack)-1]
|
||||
c.wtex(`\end{pgfscope}`)
|
||||
c.wtex("")
|
||||
}
|
||||
|
||||
// Stroke implements the vg.Canvas.Stroke method.
|
||||
func (c *Canvas) Stroke(p vg.Path) {
|
||||
if c.context().linew <= 0 {
|
||||
return
|
||||
}
|
||||
c.Push()
|
||||
c.wstyle()
|
||||
c.wpath(p)
|
||||
c.wtex(`\pgfusepath{stroke}`)
|
||||
c.Pop()
|
||||
}
|
||||
|
||||
// Fill implements the vg.Canvas.Fill method.
|
||||
func (c *Canvas) Fill(p vg.Path) {
|
||||
c.Push()
|
||||
c.wstyle()
|
||||
c.wpath(p)
|
||||
c.wtex(`\pgfusepath{fill}`)
|
||||
c.Pop()
|
||||
}
|
||||
|
||||
// FillString implements the vg.Canvas.FillString method.
|
||||
func (c *Canvas) FillString(f font.Face, pt vg.Point, text string) {
|
||||
c.Push()
|
||||
c.wcolor()
|
||||
pt.X += 0.5 * f.Width(text)
|
||||
c.wtex(`\pgftext[base,at={\pgfpoint{%gpt}{%gpt}}]{{\fontsize{%gpt}{%gpt}\selectfont %s}}`, pt.X, pt.Y, f.Font.Size, f.Font.Size, text)
|
||||
c.Pop()
|
||||
}
|
||||
|
||||
// DrawImage implements the vg.Canvas.DrawImage method.
|
||||
// DrawImage will first save the image inside a PNG file and have the
|
||||
// generated LaTeX reference that file.
|
||||
// The file name will be "gonum-pgf-image-<canvas-id>-<time.Now()>.png
|
||||
func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
|
||||
fname := fmt.Sprintf("gonum-pgf-image-%v-%v.png", c.id, time.Now().UnixNano())
|
||||
f, err := os.Create(fname)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
err = png.Encode(f, img)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("vgtex: error encoding image to PNG: %v", err))
|
||||
}
|
||||
|
||||
var (
|
||||
xmin = rect.Min.X
|
||||
ymin = rect.Min.Y
|
||||
width = rect.Size().X
|
||||
height = rect.Size().Y
|
||||
)
|
||||
c.wtex(`\pgftext[base,left,at=\pgfpoint{%gpt}{%gpt}]{\pgfimage[height=%gpt,width=%gpt]{%s}}`, xmin, ymin, height, width, fname)
|
||||
}
|
||||
|
||||
func (c *Canvas) indent(s string) string {
|
||||
return strings.Repeat(s, len(c.stack))
|
||||
}
|
||||
|
||||
func (c *Canvas) wtex(s string, args ...interface{}) {
|
||||
fmt.Fprintf(c.buf, c.indent(" ")+s+"\n", args...)
|
||||
}
|
||||
|
||||
func (c *Canvas) wstyle() {
|
||||
c.wdash()
|
||||
c.wlineWidth()
|
||||
c.wcolor()
|
||||
}
|
||||
|
||||
func (c *Canvas) wdash() {
|
||||
if len(c.context().dashArray) == 0 {
|
||||
c.wtex(`\pgfsetdash{}{0pt}`)
|
||||
return
|
||||
}
|
||||
str := `\pgfsetdash{`
|
||||
for _, d := range c.context().dashArray {
|
||||
str += fmt.Sprintf("{%gpt}", d)
|
||||
}
|
||||
str += fmt.Sprintf("}{%gpt}", c.context().dashOffset)
|
||||
c.wtex(str)
|
||||
}
|
||||
|
||||
func (c *Canvas) wlineWidth() {
|
||||
c.wtex(`\pgfsetlinewidth{%gpt}`, c.context().linew)
|
||||
}
|
||||
|
||||
func (c *Canvas) wcolor() {
|
||||
col := c.context().color
|
||||
if col == nil {
|
||||
col = color.Black
|
||||
}
|
||||
r, g, b, a := col.RGBA()
|
||||
// FIXME(sbinet) \color will last until the end of the current TeX group
|
||||
// use \pgfsetcolor and \pgfsetstrokecolor instead.
|
||||
// it needs a named color: define it on the fly (storing it at the beginning
|
||||
// of the document.)
|
||||
c.wtex(
|
||||
`\color[rgb]{%g,%g,%g}`,
|
||||
float64(r)/math.MaxUint16,
|
||||
float64(g)/math.MaxUint16,
|
||||
float64(b)/math.MaxUint16,
|
||||
)
|
||||
|
||||
opacity := float64(a) / math.MaxUint16
|
||||
c.wtex(`\pgfsetstrokeopacity{%g}`, opacity)
|
||||
c.wtex(`\pgfsetfillopacity{%g}`, opacity)
|
||||
}
|
||||
|
||||
func (c *Canvas) wpath(p vg.Path) {
|
||||
for _, comp := range p {
|
||||
switch comp.Type {
|
||||
case vg.MoveComp:
|
||||
c.wtex(`\pgfpathmoveto{\pgfpoint{%gpt}{%gpt}}`, comp.Pos.X, comp.Pos.Y)
|
||||
case vg.LineComp:
|
||||
c.wtex(`\pgflineto{\pgfpoint{%gpt}{%gpt}}`, comp.Pos.X, comp.Pos.Y)
|
||||
case vg.ArcComp:
|
||||
start := comp.Start * degPerRadian
|
||||
angle := comp.Angle * degPerRadian
|
||||
r := comp.Radius
|
||||
c.wtex(`\pgfpatharc{%g}{%g}{%gpt}`, start, angle, r)
|
||||
case vg.CurveComp:
|
||||
var a, b vg.Point
|
||||
switch len(comp.Control) {
|
||||
case 1:
|
||||
a = comp.Control[0]
|
||||
b = a
|
||||
case 2:
|
||||
a = comp.Control[0]
|
||||
b = comp.Control[1]
|
||||
default:
|
||||
panic("vgtex: invalid number of control points")
|
||||
}
|
||||
c.wtex(`\pgfcurveto{\pgfpoint{%gpt}{%gpt}}{\pgfpoint{%gpt}{%gpt}}{\pgfpoint{%gpt}{%gpt}}`,
|
||||
a.X, a.Y, b.X, b.Y, comp.Pos.X, comp.Pos.Y)
|
||||
case vg.CloseComp:
|
||||
c.wtex("%% path-close")
|
||||
default:
|
||||
panic(fmt.Errorf("vgtex: unknown path component type: %v", comp.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface, writing a LaTeX/pgf plot.
|
||||
func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
|
||||
var (
|
||||
n int64
|
||||
nn int
|
||||
err error
|
||||
)
|
||||
b := bufio.NewWriter(w)
|
||||
if c.document {
|
||||
nn, err = b.Write([]byte(defaultHeader))
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
m, err := c.buf.WriteTo(b)
|
||||
n += m
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
nn, err = fmt.Fprintf(b, "\\end{pgfpicture}\n")
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if c.document {
|
||||
nn, err = b.Write([]byte(defaultFooter))
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, b.Flush()
|
||||
}
|
||||
16
vendor/gonum.org/v1/plot/vgall.go
generated
vendored
Normal file
16
vendor/gonum.org/v1/plot/vgall.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright ©2017 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.
|
||||
|
||||
//go:build !minimal
|
||||
// +build !minimal
|
||||
|
||||
package plot // import "gonum.org/v1/plot"
|
||||
|
||||
import (
|
||||
_ "gonum.org/v1/plot/vg/vgeps"
|
||||
_ "gonum.org/v1/plot/vg/vgimg"
|
||||
_ "gonum.org/v1/plot/vg/vgpdf"
|
||||
_ "gonum.org/v1/plot/vg/vgsvg"
|
||||
_ "gonum.org/v1/plot/vg/vgtex"
|
||||
)
|
||||
Reference in New Issue
Block a user