|
| 1 | +// Copyright ©2015 The gonum Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +package plotter |
| 6 | + |
| 7 | +import ( |
| 8 | + "image/color" |
| 9 | + "math" |
| 10 | + |
| 11 | + "gonum.org/v1/plot" |
| 12 | + "gonum.org/v1/plot/vg" |
| 13 | + "gonum.org/v1/plot/vg/draw" |
| 14 | +) |
| 15 | + |
| 16 | +// StepType specifies where to put a vertical line joining two steps |
| 17 | +type StepType int |
| 18 | + |
| 19 | +const ( |
| 20 | + // StepTypePre means that a vertical line is placed at the begining of a horizonal line (|‾‾) |
| 21 | + StepTypePre StepType = iota |
| 22 | + |
| 23 | + // StepTypeMid means that a vertical line is placed between two horizontal lines (_|‾) |
| 24 | + StepTypeMid |
| 25 | + |
| 26 | + // StepTypePost means that a vertial line is placed at the end of a horizonal line (__|) |
| 27 | + StepTypePost |
| 28 | +) |
| 29 | + |
| 30 | +// Step implements the Plotter interface, drawing a stepped line. |
| 31 | +type Step struct { |
| 32 | + // XYs is a copy of the points for this line. |
| 33 | + XYs |
| 34 | + |
| 35 | + // StepStyle is the type of step line |
| 36 | + StepType StepType |
| 37 | + |
| 38 | + // LineStyle is the style of the line connecting the points. |
| 39 | + LineStyle *draw.LineStyle |
| 40 | + |
| 41 | + // ShadeColor is the color of the shaded area. |
| 42 | + ShadeColor color.Color |
| 43 | +} |
| 44 | + |
| 45 | +// NewStep returns a Step that uses the default line style and does not draw glyphs. |
| 46 | +func NewStep(xys XYer) (*Step, error) { |
| 47 | + data, err := CopyXYs(xys) |
| 48 | + if err != nil { |
| 49 | + return nil, err |
| 50 | + } |
| 51 | + return &Step{ |
| 52 | + XYs: data, |
| 53 | + LineStyle: &DefaultLineStyle, |
| 54 | + }, nil |
| 55 | +} |
| 56 | + |
| 57 | +// Plot draws the Step, implementing the plot.Plotter interface. |
| 58 | +func (pts *Step) Plot(c draw.Canvas, plt *plot.Plot) { |
| 59 | + trX, trY := plt.Transforms(&c) |
| 60 | + ps := make([]vg.Point, len(pts.XYs)) |
| 61 | + |
| 62 | + for i, p := range pts.XYs { |
| 63 | + ps[i].X = trX(p.X) |
| 64 | + ps[i].Y = trY(p.Y) |
| 65 | + } |
| 66 | + |
| 67 | + if pts.ShadeColor != nil && len(ps) > 0 { |
| 68 | + c.SetColor(pts.ShadeColor) |
| 69 | + minY := trY(plt.Y.Min) |
| 70 | + var pa vg.Path |
| 71 | + pa.Move(vg.Point{X: ps[0].X, Y: minY}) |
| 72 | + prev := ps[0] |
| 73 | + if pts.StepType != StepTypePre { |
| 74 | + pa.Line(prev) |
| 75 | + } |
| 76 | + for _, pt := range ps[1:] { |
| 77 | + switch pts.StepType { |
| 78 | + case StepTypePre: |
| 79 | + pa.Line(vg.Point{X: prev.X, Y: pt.Y}) |
| 80 | + case StepTypeMid: |
| 81 | + pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y}) |
| 82 | + pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y}) |
| 83 | + case StepTypePost: |
| 84 | + pa.Line(vg.Point{X: pt.X, Y: prev.Y}) |
| 85 | + } |
| 86 | + pa.Line(pt) |
| 87 | + prev = pt |
| 88 | + } |
| 89 | + pa.Line(vg.Point{X: ps[len(pts.XYs)-1].X, Y: minY}) |
| 90 | + pa.Close() |
| 91 | + c.Fill(pa) |
| 92 | + } |
| 93 | + |
| 94 | + if pts.LineStyle != nil { |
| 95 | + lines := c.ClipLinesXY(ps) |
| 96 | + if len(lines) == 0 { |
| 97 | + return |
| 98 | + } |
| 99 | + c.SetLineStyle(*pts.LineStyle) |
| 100 | + for _, l := range lines { |
| 101 | + if len(l) == 0 { |
| 102 | + continue |
| 103 | + } |
| 104 | + var p vg.Path |
| 105 | + prev := l[0] |
| 106 | + p.Move(prev) |
| 107 | + for _, pt := range l[1:] { |
| 108 | + switch pts.StepType { |
| 109 | + case StepTypePre: |
| 110 | + p.Line(vg.Point{X: prev.X, Y: pt.Y}) |
| 111 | + case StepTypeMid: |
| 112 | + p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y}) |
| 113 | + p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y}) |
| 114 | + case StepTypePost: |
| 115 | + p.Line(vg.Point{X: pt.X, Y: prev.Y}) |
| 116 | + } |
| 117 | + p.Line(pt) |
| 118 | + prev = pt |
| 119 | + } |
| 120 | + c.Stroke(p) |
| 121 | + } |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +// DataRange returns the minimum and maximum |
| 126 | +// x and y values, implementing the plot.DataRanger |
| 127 | +// interface. |
| 128 | +func (pts *Step) DataRange() (xmin, xmax, ymin, ymax float64) { |
| 129 | + if pts.ShadeColor != nil { |
| 130 | + xmin, xmax, ymin, ymax = XYRange(pts) |
| 131 | + ymin = math.Min(ymin, 0.) |
| 132 | + ymax = math.Max(ymax, 0.) |
| 133 | + return |
| 134 | + } |
| 135 | + return XYRange(pts) |
| 136 | +} |
| 137 | + |
| 138 | +// Thumbnail the thumbnail for the Step, |
| 139 | +// implementing the plot.Thumbnailer interface. |
| 140 | +func (pts *Step) Thumbnail(c *draw.Canvas) { |
| 141 | + if pts.ShadeColor != nil { |
| 142 | + points := []vg.Point{ |
| 143 | + {X: c.Min.X, Y: c.Min.Y}, |
| 144 | + {X: c.Min.X, Y: c.Max.Y}, |
| 145 | + {X: c.Max.X, Y: c.Max.Y}, |
| 146 | + {X: c.Max.X, Y: c.Min.Y}, |
| 147 | + } |
| 148 | + poly := c.ClipPolygonY(points) |
| 149 | + c.FillPolygon(pts.ShadeColor, poly) |
| 150 | + } else if pts.LineStyle != nil { |
| 151 | + y := c.Center().Y |
| 152 | + c.StrokeLine2(*pts.LineStyle, c.Min.X, y, c.Max.X, y) |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +// NewStepPoints returns both a Step and a |
| 157 | +// Points for the given point data. |
| 158 | +func NewStepPoints(xys XYer) (*Step, *Scatter, error) { |
| 159 | + s, err := NewScatter(xys) |
| 160 | + if err != nil { |
| 161 | + return nil, nil, err |
| 162 | + } |
| 163 | + l := &Step{ |
| 164 | + XYs: s.XYs, |
| 165 | + LineStyle: &DefaultLineStyle, |
| 166 | + } |
| 167 | + return l, s, nil |
| 168 | +} |
0 commit comments