go-hep.org/x/hep@v0.38.1/hplot/s2d.go (about) 1 // Copyright ©2016 The go-hep 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 hplot 6 7 import ( 8 "image/color" 9 "math" 10 11 "gonum.org/v1/plot" 12 "gonum.org/v1/plot/plotter" 13 "gonum.org/v1/plot/vg" 14 "gonum.org/v1/plot/vg/draw" 15 ) 16 17 // S2D plots a set of 2-dim points with error bars. 18 type S2D struct { 19 Data plotter.XYer 20 21 // GlyphStyle is the style of the glyphs drawn 22 // at each point. 23 draw.GlyphStyle 24 25 // LineStyle is the style of the line drawn 26 // connecting each point. 27 // Use zero width to disable. 28 LineStyle draw.LineStyle 29 30 // XErrs is the x error bars plotter. 31 XErrs *plotter.XErrorBars 32 33 // YErrs is the y error bars plotter. 34 YErrs *plotter.YErrorBars 35 36 // Band displays a colored band between the y-min and y-max error bars. 37 Band *Band 38 39 // Steps controls the style of the connecting 40 // line (NoSteps, HiSteps, etc...) 41 Steps StepsKind 42 } 43 44 // withXErrBars enables the X error bars 45 func (pts *S2D) withXErrBars() error { 46 xerr, ok := pts.Data.(plotter.XErrorer) 47 if !ok { 48 return nil 49 } 50 51 type xerrT struct { 52 plotter.XYer 53 plotter.XErrorer 54 } 55 xplt, err := plotter.NewXErrorBars(xerrT{pts.Data, xerr}) 56 if err != nil { 57 return err 58 } 59 60 pts.XErrs = xplt 61 return nil 62 } 63 64 // withYErrBars enables the Y error bars 65 func (pts *S2D) withYErrBars() error { 66 yerr, ok := pts.Data.(plotter.YErrorer) 67 if !ok { 68 return nil 69 } 70 71 type yerrT struct { 72 plotter.XYer 73 plotter.YErrorer 74 } 75 yplt, err := plotter.NewYErrorBars(yerrT{pts.Data, yerr}) 76 if err != nil { 77 return err 78 } 79 80 pts.YErrs = yplt 81 return nil 82 } 83 84 // withBand enables the band between ymin-ymax error bars. 85 func (pts *S2D) withBand() error { 86 yerr, ok := pts.Data.(plotter.YErrorer) 87 if !ok { 88 return nil 89 } 90 91 var ( 92 top plotter.XYs 93 bot plotter.XYs 94 ) 95 96 switch pts.Steps { 97 98 case NoSteps: 99 top = make(plotter.XYs, pts.Data.Len()) 100 bot = make(plotter.XYs, pts.Data.Len()) 101 for i := range top { 102 x, y := pts.Data.XY(i) 103 ymin, ymax := yerr.YError(i) 104 top[i].X = x 105 top[i].Y = y + math.Abs(ymax) 106 bot[i].X = x 107 bot[i].Y = y - math.Abs(ymin) 108 } 109 110 case HiSteps: 111 top = make(plotter.XYs, 2*pts.Data.Len()) 112 bot = make(plotter.XYs, 2*pts.Data.Len()) 113 xerr := pts.Data.(plotter.XErrorer) 114 for i := range top { 115 idata := i / 2 116 x, y := pts.Data.XY(idata) 117 xmin, xmax := xerr.XError(idata) 118 ymin, ymax := yerr.YError(idata) 119 switch { 120 case i%2 != 0: 121 top[i].X = x + math.Abs(xmax) 122 top[i].Y = y + math.Abs(ymax) 123 bot[i].X = x + math.Abs(xmax) 124 bot[i].Y = y - math.Abs(ymin) 125 default: 126 top[i].X = x - math.Abs(xmin) 127 top[i].Y = y + math.Abs(ymax) 128 bot[i].X = x - math.Abs(xmin) 129 bot[i].Y = y - math.Abs(ymin) 130 } 131 } 132 case PreSteps: 133 panic("presteps not implemented") 134 case MidSteps: 135 panic("midsteps not implemented") 136 case PostSteps: 137 panic("poststeps not implemented") 138 } 139 pts.Band = NewBand(color.Gray{200}, top, bot) 140 return nil 141 } 142 143 // NewS2D creates a 2-dim scatter plot from a XYer. 144 func NewS2D(data plotter.XYer, opts ...Options) *S2D { 145 s := &S2D{ 146 Data: data, 147 GlyphStyle: plotter.DefaultGlyphStyle, 148 } 149 s.GlyphStyle.Shape = draw.CrossGlyph{} 150 151 cfg := newConfig(opts) 152 153 s.Steps = cfg.steps 154 155 if cfg.bars.xerrs { 156 _ = s.withXErrBars() 157 } 158 159 if cfg.bars.yerrs { 160 _ = s.withYErrBars() 161 } 162 163 if cfg.band { 164 _ = s.withBand() 165 } 166 167 if cfg.glyph != (draw.GlyphStyle{}) { 168 s.GlyphStyle = cfg.glyph 169 } 170 171 switch s.Steps { 172 case HiSteps: 173 // check we have ErrX for all data points. 174 xerrs := s.Data.(plotter.XErrorer) 175 for i := range s.Data.Len() { 176 xmin, xmax := xerrs.XError(i) 177 if xmin == 0 && xmax == 0 { 178 panic("s2d with HiSteps needs XErr informations for all points") 179 } 180 } 181 } 182 183 return s 184 } 185 186 // Plot draws the Scatter, implementing the plot.Plotter 187 // interface. 188 func (pts *S2D) Plot(c draw.Canvas, plt *plot.Plot) { 189 trX, trY := plt.Transforms(&c) 190 if pts.Band != nil { 191 pts.Band.Plot(c, plt) 192 } 193 194 for i := range pts.Data.Len() { 195 x, y := pts.Data.XY(i) 196 c.DrawGlyph(pts.GlyphStyle, vg.Point{X: trX(x), Y: trY(y)}) 197 } 198 199 if pts.LineStyle.Width > 0 { 200 201 data, err := plotter.CopyXYs(pts.Data) 202 if err != nil { 203 panic(err) 204 } 205 206 switch pts.Steps { 207 case HiSteps: 208 xerr := pts.Data.(plotter.XErrorer) 209 dsteps := make(plotter.XYs, 0, 2*len(data)) 210 for i, d := range data { 211 xmin, xmax := xerr.XError(i) 212 dsteps = append(dsteps, plotter.XY{X: d.X - xmin, Y: d.Y}) 213 dsteps = append(dsteps, plotter.XY{X: d.X + xmax, Y: d.Y}) 214 } 215 data = dsteps 216 case PreSteps: 217 var ( 218 prev plotter.XY 219 dsteps = make(plotter.XYs, 0, 2*len(data)) 220 ) 221 prev.X, prev.Y = data.XY(0) 222 dsteps = append(dsteps, prev) 223 for _, pt := range data[1:] { 224 dsteps = append(dsteps, plotter.XY{X: prev.X, Y: pt.Y}) 225 dsteps = append(dsteps, pt) 226 prev = pt 227 } 228 data = dsteps 229 case MidSteps: 230 var ( 231 prev plotter.XY 232 dsteps = make(plotter.XYs, 0, 2*len(data)) 233 ) 234 prev.X, prev.Y = data.XY(0) 235 dsteps = append(dsteps, prev) 236 for _, pt := range data[1:] { 237 dsteps = append(dsteps, plotter.XY{X: 0.5 * (prev.X + pt.X), Y: prev.Y}) 238 dsteps = append(dsteps, plotter.XY{X: 0.5 * (prev.X + pt.X), Y: pt.Y}) 239 dsteps = append(dsteps, pt) 240 prev = pt 241 } 242 data = dsteps 243 case PostSteps: 244 var ( 245 prev plotter.XY 246 dsteps = make(plotter.XYs, 0, 2*len(data)) 247 ) 248 prev.X, prev.Y = data.XY(0) 249 dsteps = append(dsteps, prev) 250 for _, pt := range data[1:] { 251 dsteps = append(dsteps, plotter.XY{X: pt.X, Y: prev.Y}) 252 dsteps = append(dsteps, pt) 253 prev = pt 254 } 255 data = dsteps 256 case NoSteps: 257 // ok. 258 } 259 260 line := plotter.Line{ 261 XYs: data, 262 LineStyle: pts.LineStyle, 263 } 264 line.Plot(c, plt) 265 } 266 267 if pts.XErrs != nil { 268 pts.XErrs.LineStyle.Color = pts.GlyphStyle.Color 269 pts.XErrs.Plot(c, plt) 270 } 271 if pts.YErrs != nil { 272 pts.YErrs.LineStyle.Color = pts.GlyphStyle.Color 273 pts.YErrs.Plot(c, plt) 274 } 275 } 276 277 // DataRange returns the minimum and maximum 278 // x and y values, implementing the plot.DataRanger 279 // interface. 280 func (pts *S2D) DataRange() (xmin, xmax, ymin, ymax float64) { 281 if dr, ok := pts.Data.(plot.DataRanger); ok { 282 xmin, xmax, ymin, ymax = dr.DataRange() 283 } else { 284 xmin, xmax, ymin, ymax = plotter.XYRange(pts.Data) 285 } 286 287 if pts.XErrs != nil { 288 xmin1, xmax1, ymin1, ymax1 := pts.XErrs.DataRange() 289 xmin = math.Min(xmin1, xmin) 290 xmax = math.Max(xmax1, xmax) 291 ymin = math.Min(ymin1, ymin) 292 ymax = math.Max(ymax1, ymax) 293 } 294 295 if pts.YErrs != nil { 296 xmin1, xmax1, ymin1, ymax1 := pts.YErrs.DataRange() 297 xmin = math.Min(xmin1, xmin) 298 xmax = math.Max(xmax1, xmax) 299 ymin = math.Min(ymin1, ymin) 300 ymax = math.Max(ymax1, ymax) 301 } 302 303 return xmin, xmax, ymin, ymax 304 } 305 306 // GlyphBoxes returns a slice of plot.GlyphBoxes, 307 // implementing the plot.GlyphBoxer interface. 308 func (pts *S2D) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { 309 bs := make([]plot.GlyphBox, pts.Data.Len()) 310 for i := range pts.Data.Len() { 311 x, y := pts.Data.XY(i) 312 bs[i].X = plt.X.Norm(x) 313 bs[i].Y = plt.Y.Norm(y) 314 bs[i].Rectangle = pts.GlyphStyle.Rectangle() 315 } 316 if pts.XErrs != nil { 317 bs = append(bs, pts.XErrs.GlyphBoxes(plt)...) 318 } 319 if pts.YErrs != nil { 320 bs = append(bs, pts.YErrs.GlyphBoxes(plt)...) 321 } 322 return bs 323 } 324 325 // Thumbnail the thumbnail for the Scatter, 326 // implementing the plot.Thumbnailer interface. 327 func (pts *S2D) Thumbnail(c *draw.Canvas) { 328 ymin := c.Min.Y 329 ymax := c.Max.Y 330 xmin := c.Min.X 331 xmax := c.Max.X 332 333 if pts.Band != nil { 334 box := []vg.Point{ 335 {X: xmin, Y: ymin}, 336 {X: xmax, Y: ymin}, 337 {X: xmax, Y: ymax}, 338 {X: xmin, Y: ymax}, 339 {X: xmin, Y: ymin}, 340 } 341 c.FillPolygon(pts.Band.FillColor, c.ClipPolygonXY(box)) 342 } 343 344 if pts.LineStyle.Width != 0 { 345 ymid := c.Center().Y 346 line := []vg.Point{{X: xmin, Y: ymid}, {X: xmax, Y: ymid}} 347 c.StrokeLines(pts.LineStyle, c.ClipLinesX(line)...) 348 349 } 350 351 if pts.GlyphStyle != (draw.GlyphStyle{}) { 352 c.DrawGlyph(pts.GlyphStyle, c.Center()) 353 if pts.YErrs != nil { 354 var ( 355 yerrs = pts.YErrs 356 vsize = 0.5 * ((ymax - ymin) * 0.95) 357 x = c.Center().X 358 ylo = c.Center().Y - vsize 359 yup = c.Center().Y + vsize 360 xylo = vg.Point{X: x, Y: ylo} 361 xyup = vg.Point{X: x, Y: yup} 362 line = []vg.Point{xylo, xyup} 363 bar = c.ClipLinesY(line) 364 ) 365 c.StrokeLines(yerrs.LineStyle, bar...) 366 for _, pt := range []vg.Point{xylo, xyup} { 367 if c.Contains(pt) { 368 c.StrokeLine2(yerrs.LineStyle, 369 pt.X-yerrs.CapWidth/2, 370 pt.Y, 371 pt.X+yerrs.CapWidth/2, 372 pt.Y, 373 ) 374 } 375 } 376 } 377 } 378 }