Files
sjy01-image-proc/vendor/gonum.org/v1/plot/plotter/line.go
2024-10-24 15:46:01 +08:00

211 lines
4.8 KiB
Go

// 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
}