Beispiel #1
0
// Draw the plotinum logo.
func Example_logo() *plot.Plot {
	p, err := plot.New()
	if err != nil {
		panic(err)
	}

	plotter.DefaultLineStyle.Width = vg.Points(1)
	plotter.DefaultGlyphStyle.Radius = vg.Points(3)

	p.Y.Tick.Marker = plot.ConstantTicks([]plot.Tick{
		{0, "0"}, {0.25, ""}, {0.5, "0.5"}, {0.75, ""}, {1, "1"},
	})
	p.X.Tick.Marker = plot.ConstantTicks([]plot.Tick{
		{0, "0"}, {0.25, ""}, {0.5, "0.5"}, {0.75, ""}, {1, "1"},
	})

	pts := plotter.XYs{{0, 0}, {0, 1}, {0.5, 1}, {0.5, 0.6}, {0, 0.6}}
	line := must(plotter.NewLine(pts)).(*plotter.Line)
	scatter := must(plotter.NewScatter(pts)).(*plotter.Scatter)
	p.Add(line, scatter)

	pts = plotter.XYs{{1, 0}, {0.75, 0}, {0.75, 0.75}}
	line = must(plotter.NewLine(pts)).(*plotter.Line)
	scatter = must(plotter.NewScatter(pts)).(*plotter.Scatter)
	p.Add(line, scatter)

	pts = plotter.XYs{{0.5, 0.5}, {1, 0.5}}
	line = must(plotter.NewLine(pts)).(*plotter.Line)
	scatter = must(plotter.NewScatter(pts)).(*plotter.Scatter)
	p.Add(line, scatter)

	return p
}
Beispiel #2
0
// 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
	names := make(map[*plotter.Scatter]string)
	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 != "" {
				names[s] = name
				name = ""
			}

		default:
			panic(fmt.Sprintf("AddScatters handles strings and plotter.XYers, got %T", t))
		}
	}
	plt.Add(ps...)
	for p, n := range names {
		plt.Legend.Add(n, p)
	}
	return nil
}
Beispiel #3
0
func plotData(name string, us, ys, ts, fs []float64) {
	p, err := plot.New()
	if err != nil {
		fmt.Printf("Cannot create new plot: %s\n", err)
		return
	}
	p.Title.Text = "Least-square fit of convex function"
	p.X.Min = -0.1
	p.X.Max = 2.3
	p.Y.Min = -1.1
	p.Y.Max = 7.2
	p.Add(plotter.NewGrid())

	pts := plotter.NewScatter(dataset(us, ys))
	pts.GlyphStyle.Color = color.RGBA{R: 255, A: 255}

	fit := plotter.NewLine(dataset(ts, fs))
	fit.LineStyle.Width = vg.Points(1)
	fit.LineStyle.Color = color.RGBA{B: 255, A: 255}

	p.Add(pts)
	p.Add(fit)
	if err := p.Save(4, 4, name); err != nil {
		fmt.Printf("Save to '%s' failed: %s\n", name, err)
	}
}
Beispiel #4
0
// Example_errBars draws points and error bars.
func Example_errBars() *plot.Plot {

	type errPoints struct {
		plotter.XYs
		plotter.YErrors
		plotter.XErrors
	}

	rand.Seed(int64(0))
	n := 15
	data := errPoints{
		XYs:     randomPoints(n),
		YErrors: plotter.YErrors(randomError(n)),
		XErrors: plotter.XErrors(randomError(n)),
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	scatter := must(plotter.NewScatter(data)).(*plotter.Scatter)
	scatter.Shape = plot.CrossGlyph{}
	xerrs, err := plotter.NewXErrorBars(data)
	if err != nil {
		panic(err)
	}
	yerrs, err := plotter.NewYErrorBars(data)
	if err != nil {
		panic(err)
	}
	p.Add(scatter, xerrs, yerrs)
	p.Add(plotter.NewGlyphBoxes())

	return p
}
Beispiel #5
0
func linesPlot() *plot.Plot {
	// Get some random points
	rand.Seed(int64(0))
	n := 10
	scatterData := randomPoints(n)
	lineData := randomPoints(n)
	linePointsData := randomPoints(n)

	// Create a new plot, set its title and
	// axis labels.
	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Points Example"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	// Draw a grid behind the data
	p.Add(plotter.NewGrid())

	// Make a scatter plotter and set its style.
	s, err := plotter.NewScatter(scatterData)
	if err != nil {
		panic(err)
	}
	s.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255}

	// Make a line plotter and set its style.
	l, err := plotter.NewLine(lineData)
	if err != nil {
		panic(err)
	}
	l.LineStyle.Width = vg.Points(1)
	l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)}
	l.LineStyle.Color = color.RGBA{B: 255, A: 255}

	// Make a line plotter with points and set its style.
	lpLine, lpPoints, err := plotter.NewLinePoints(linePointsData)
	if err != nil {
		panic(err)
	}
	lpLine.Color = color.RGBA{G: 255, A: 255}
	lpPoints.Shape = plot.PyramidGlyph{}
	lpPoints.Color = color.RGBA{R: 255, A: 255}

	// Add the plotters to the plot, with a legend
	// entry for each
	p.Add(s, l, lpLine, lpPoints)
	p.Legend.Add("scatter", s)
	p.Legend.Add("line", l)
	p.Legend.Add("line points", lpLine, lpPoints)
	return p
}
Beispiel #6
0
// RamaPlotParts produces plots, in png format for the ramachandran data (phi and psi dihedrals)
// contained in data. Data points in tag (maximun 4) are highlighted in the plot.
// the extension must be included in plotname. Returns an error or nil. In RamaPlotParts
// The data is divided in several slices, where each is represented differently in the plot
func RamaPlotParts(data [][][]float64, tag [][]int, title, plotname string) error {
	var err error
	if data == nil {
		return Error{ErrNilData, "", "RamaPlot", "", true}
	}
	// Create a new plot, set its title and
	// axis labels.
	p, err2 := basicRamaPlot(title)
	if err2 != nil {
		return Error{err2.Error(), "", "RamaPlotParts", "", true}
	}
	var tagged int
	for key, val := range data {
		temp := make(plotter.XYs, 1) //len(val))
		//	fmt.Println(key, len(val))
		for k, v := range val {
			temp[0].X = v[0]
			temp[0].Y = v[1]
			// Make a scatter plotter and set its style.
			s, err := plotter.NewScatter(temp) //(pts)
			if err != nil {
				return Error{err.Error(), "", "RamaPlotParts", "", true}

			}
			if tag != nil {
				if len(tag) < len(data) {
					return Error{ErrInconsistentData, "", "RamaPlotParts", "If a non-nil tag slice is provided it must contain an element (which can be nil) for each element in the dihedral slice", true}
				}
				if tag[key] != nil && isInInt(tag[key], k) {
					s.GlyphStyle.Shape, err = getShape(tagged)
					tagged++
				}
			}
			//set the colors
			r, g, b := colors(key, len(data))
			fmt.Println("DATA POINT", key, "color", r, g, b)
			s.GlyphStyle.Color = color.RGBA{R: r, B: b, G: g, A: 255}
			//The tagging procedure is a bit complex.
			p.Add(s)
		}

	}
	filename := fmt.Sprintf("%s.png", plotname)
	//here I  intentionally shadow err.
	if err := p.Save(5, 5, filename); err != nil {
		return Error{err2.Error(), "", "RamaPlotParts", "", true}
	}

	return err
}
Beispiel #7
0
// createScatter creates a scatter graph from provided x,y data and title
func createScatter(xdat, ydat []float64, title string) {
	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Add(plotter.NewGrid())
	p.Title.Text = title

	scatdata := xyer{xdat, ydat}
	s := plotter.NewScatter(scatdata)
	s.GlyphStyle.Radius = 2
	s.GlyphStyle.Shape = &plot.BoxGlyph{}
	s.GlyphStyle.Color = color.RGBA{G: 100, A: 255}
	p.Add(s)

	if err := p.Save(5, 5, "out/"+title+".png"); err != nil {
		panic(err)
	}
}
Beispiel #8
0
// Example_points draws some scatter points, a line,
// and a line with points.
func Example_points() *plot.Plot {
	rand.Seed(int64(0))

	n := 15
	scatterData := randomPoints(n)
	lineData := randomPoints(n)
	linePointsData := randomPoints(n)

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Points Example"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	p.Add(plotter.NewGrid())

	s := must(plotter.NewScatter(scatterData)).(*plotter.Scatter)
	s.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255}
	s.GlyphStyle.Radius = vg.Points(3)

	l := must(plotter.NewLine(lineData)).(*plotter.Line)
	l.LineStyle.Width = vg.Points(1)
	l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)}
	l.LineStyle.Color = color.RGBA{B: 255, A: 255}

	lpLine, lpPoints, err := plotter.NewLinePoints(linePointsData)
	if err != nil {
		panic(err)
	}
	lpLine.Color = color.RGBA{G: 255, A: 255}
	lpPoints.Shape = plot.CircleGlyph{}
	lpPoints.Color = color.RGBA{R: 255, A: 255}

	p.Add(s, l, lpLine, lpPoints)
	p.Legend.Add("scatter", s)
	p.Legend.Add("line", l)
	p.Legend.Add("line points", lpLine, lpPoints)

	return p
}
Beispiel #9
0
	/*	if (key-critical)*norm>255{
			r=uint8(norm*(key-critical))
			g=90
			b=255-r

			}
		}
		fmt.Println(r,g,b, norm, steps)
	*/
	return r, g, b
}

// Produce plots, in png format for the ramachandran data (psi and phi dihedrals)
// contained in data. Data points in tag (maximun 4) are highlighted in the plot.
// the extension must be included in plotname. Returns an error or nil*/
func RamaPlot(data [][]float64, tag []int, title, plotname string) error {
	var err error
	if data == nil {
		return Error{ErrNilData, "", "RamaPlot", "", true}
	}
	// Create a new plot, set its title and
	// axis labels.
	p, err := basicRamaPlot(title)
	if err != nil {
		return Error{err.Error(), "", "RamaPlot", "", true}

	}
	temp := make(plotter.XYs, 1)
	var tagged int //How many residues have been tagged?
	for key, val := range data {
		temp[0].X = val[0]
		temp[0].Y = val[1]
		// Make a scatter plotter and set its style.
		s, err := plotter.NewScatter(temp) //(pts)
		if err != nil {
			return Error{err.Error(), "", "RamaPlot", "", true}
		}
		r, g, b := colors(key, len(data))
		if tag != nil && isInInt(tag, key) {
			s.GlyphStyle.Shape, err = getShape(tagged)
			tagged++
		}
		s.GlyphStyle.Color = color.RGBA{R: r, B: b, G: g, A: 255}
		//		fmt.Println(r,b,g, key, norm, len(data)) //////////////////////////
		// Add the plotter
		p.Add(s)
	}
	// Save the plot to a PNG file.
	filename := fmt.Sprintf("%s.png", plotname)
	//here I  intentionally shadow err.
	if err := p.Save(4, 4, filename); err != nil {
		return Error{err.Error(), "", "RamaPlot", "", true}
	}
	return err
}
Beispiel #10
0
// 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.
func AddScatters(plt *plot.Plot, vs ...interface{}) {
	name := ""
	var i int
	for _, v := range vs {
		switch t := v.(type) {
		case string:
			name = t

		case plotter.XYer:
			s := plotter.NewScatter(t)
			s.Color = Color(i)
			s.Shape = Shape(i)
			i++
			plt.Add(s)
			if name != "" {
				plt.Legend.Add(name, s)
				name = ""
			}

		default:
			panic(fmt.Sprintf("AddScatters handles strings and plotter.XYers, got %T", t))
		}
	}
}
Beispiel #11
0
// Function called by rest of code to plot graphs
func Plot(fname string, title string, labels []string, xdata []float64, ydata ...[]float64) {
	if len(fname) == 0 {
		fname = "output"
	}
	fname = fmt.Sprintf("out/%v h=%v m=%v M=%v l=%v γ=%v.png", fname, h, m, M, l, gamma)

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	fmt.Print(">> Plotting \"" + title + "\" to \"" + fname + "\"")

	// Add grid
	p.Add(plotter.NewGrid())
	p.Title.Text = title
	p.Title.Padding = 10.0
	p.X.Label.Text = labels[0]
	p.Y.Label.Text = labels[1]
	p.Legend.Top = true
	p.Legend.Left = true
	p.Legend.YOffs = -5.0
	p.Legend.YOffs = -15.0
	p.Legend.Padding = 10.0
	p.Legend.ThumbnailWidth = 30.0

	var s *plotter.Scatter

	// This is to allow for plotting of vertical lines for stability graphs
	stabtest := false
	last := len(ydata)
	if len(labels) == last {
		stabtest = true
		last -= 2
	}

	// Loop through data and
	for i := 0; i < last; i++ {
		s = plotter.NewScatter(&_points{xdata, ydata[i]})
		s.GlyphStyle.Color = cols[i]
		s.GlyphStyle.Radius = 1
		s.GlyphStyle.Shape = plot.CircleGlyph{}
		p.Add(s)
		if len(ydata) > 1 {
			p.Legend.Add(labels[i+2], s)
		}
	}

	if stabtest {
		s = plotter.NewScatter(&_points{ydata[1], ydata[2]})
		s.GlyphStyle.Color = cols[0]
		s.GlyphStyle.Radius = 1
		s.GlyphStyle.Shape = plot.CircleGlyph{}
		p.Add(s)
	}

	// Save the plot to a PNG file.
	if err := p.Save(7, 7, fname); err != nil {
		panic(err)
	}
	fmt.Print(" ... Done\n")
}
func VisualizeSignificantEvents(events SignificantEvents, filename string, options SignificantEventsOptions) {
	firstTime := events.FirstTime()
	tr := func(t time.Time) float64 {
		return t.Sub(firstTime).Seconds()
	}

	minX := options.MinX
	maxX := 0.0
	maxY := 0.0
	minY := math.MaxFloat64

	histograms := map[string]plot.Plotter{}
	scatters := map[string][]plot.Plotter{}
	verticalLines := []plot.Plotter{}

	colorCounter := 0
	for _, name := range events.OrderedNames() {
		xys := plotter.XYs{}
		xErrs := plotter.XErrors{}
		for _, event := range events[name] {
			if event.V > 0 {
				xys = append(xys, struct{ X, Y float64 }{tr(event.T), event.V})
				xErrs = append(xErrs, struct{ Low, High float64 }{-event.V / 2, event.V / 2})

				if tr(event.T) > maxX {
					maxX = tr(event.T)
				}
				if event.V > maxY {
					maxY = event.V
				}
				if event.V < minY {
					minY = event.V
				}
			}
		}

		if len(xys) == 0 {
			say.Println(0, "No data for %s", name)
			continue
		}

		if options.MarkedEvents != nil {
			ls, ok := options.MarkedEvents[name]
			if ok {
				for _, event := range events[name] {
					l := viz.NewVerticalLine(tr(event.T))
					l.LineStyle = ls
					verticalLines = append(verticalLines, l)
				}
			}
		}

		s, err := plotter.NewScatter(xys)
		say.ExitIfError("Couldn't create scatter plot", err)

		s.GlyphStyle = plot.GlyphStyle{
			Color:  viz.OrderedColor(colorCounter),
			Radius: 2,
			Shape:  plot.CircleGlyph{},
		}

		xErrsPlot, err := plotter.NewXErrorBars(struct {
			plotter.XYer
			plotter.XErrorer
		}{xys, xErrs})
		say.ExitIfError("Couldn't create x errors plot", err)
		xErrsPlot.LineStyle = viz.LineStyle(viz.OrderedColor(colorCounter), 1)

		scatters[name] = []plot.Plotter{s, xErrsPlot}

		durations := events[name].Data()
		h := viz.NewHistogram(durations, 20, durations.Min(), durations.Max())
		h.LineStyle = viz.LineStyle(viz.OrderedColor(colorCounter), 1)
		histograms[name] = h
		colorCounter++
	}

	if options.MaxX != 0 {
		maxX = options.MaxX
	}

	maxY = math.Pow(10, math.Ceil(math.Log10(maxY)))
	minY = math.Pow(10, math.Floor(math.Log10(minY)))

	b := &viz.Board{}
	n := len(histograms) + 1
	padding := 0.1 / float64(n-1)
	height := (1.0 - padding*float64(n-1)) / float64(n)
	histWidth := 0.3
	scatterWidth := 0.7
	y := 1 - height - padding - height

	allScatterPlot, _ := plot.New()
	allScatterPlot.Title.Text = "All Events"
	allScatterPlot.X.Label.Text = "Time (s)"
	allScatterPlot.Y.Label.Text = "Duration (s)"
	allScatterPlot.Y.Scale = plot.LogScale
	allScatterPlot.Y.Tick.Marker = plot.LogTicks

	for _, name := range events.OrderedNames() {
		histogram, ok := histograms[name]
		if !ok {
			continue
		}
		scatter := scatters[name]

		allScatterPlot.Add(scatter[0])
		allScatterPlot.Add(scatter[1])

		histogramPlot, _ := plot.New()
		histogramPlot.Title.Text = name
		histogramPlot.X.Label.Text = "Duration (s)"
		histogramPlot.Y.Label.Text = "N"
		histogramPlot.Add(histogram)

		scatterPlot, _ := plot.New()
		scatterPlot.Title.Text = name
		scatterPlot.X.Label.Text = "Time (s)"
		scatterPlot.Y.Label.Text = "Duration (s)"
		scatterPlot.Y.Scale = plot.LogScale
		scatterPlot.Y.Tick.Marker = plot.LogTicks
		scatterPlot.Add(scatter...)
		scatterPlot.Add(verticalLines...)
		scatterPlot.X.Min = minX
		scatterPlot.X.Max = maxX
		scatterPlot.Y.Min = 1e-5
		scatterPlot.Y.Max = maxY

		b.AddSubPlot(histogramPlot, viz.Rect{0, y, histWidth, height})
		b.AddSubPlot(scatterPlot, viz.Rect{histWidth, y, scatterWidth, height})

		y -= height + padding
	}

	allScatterPlot.Add(verticalLines...)
	allScatterPlot.X.Min = minX
	allScatterPlot.X.Max = maxX
	allScatterPlot.Y.Min = 1e-5
	allScatterPlot.Y.Max = maxY
	fmt.Println("all", minX, maxX)

	b.AddSubPlot(allScatterPlot, viz.Rect{histWidth, 1 - height, scatterWidth, height})

	b.Save(16.0, 5*float64(n), filename)
}
func VisualizeSignificantEvents(events SignificantEvents, filename string, options SignificantEventsOptions) {
	firstTime := events.FirstTime()
	tr := func(t time.Time) float64 {
		return t.Sub(firstTime).Seconds()
	}

	minX := 0.0
	if options.MinX != 0 {
		minX = options.MinX
	}
	if !options.MinT.IsZero() {
		minX = tr(options.MinT)
	}
	maxX := 0.0
	maxY := 0.0
	minY := math.MaxFloat64

	scatters := map[string][]plot.Plotter{}
	verticalLines := []plot.Plotter{}
	lineOverlays := []plot.Plotter{}

	colorCounter := 0
	for _, name := range events.OrderedNames() {
		xys := plotter.XYs{}
		xErrs := plotter.XErrors{}
		for _, event := range events[name] {
			if event.V > 0 {
				xys = append(xys, struct{ X, Y float64 }{tr(event.T), event.V})
				xErrs = append(xErrs, struct{ Low, High float64 }{-event.V / 2, event.V / 2})

				if tr(event.T) > maxX {
					maxX = tr(event.T)
				}
				if event.V > maxY {
					maxY = event.V
				}
				if event.V < minY {
					minY = event.V
				}
			}
		}

		if len(xys) == 0 {
			say.Println(0, "No data for %s", name)
			continue
		}

		if options.MarkedEvents != nil {
			ls, ok := options.MarkedEvents[name]
			if ok {
				for _, event := range events[name] {
					l := viz.NewVerticalLine(tr(event.T))
					l.LineStyle = ls
					verticalLines = append(verticalLines, l)
				}
			}
		}

		s, err := plotter.NewScatter(xys)
		say.ExitIfError("Couldn't create scatter plot", err)

		s.GlyphStyle = plot.GlyphStyle{
			Color:  viz.OrderedColor(colorCounter),
			Radius: 2,
			Shape:  plot.CircleGlyph{},
		}

		xErrsPlot, err := plotter.NewXErrorBars(struct {
			plotter.XYer
			plotter.XErrorer
		}{xys, xErrs})
		say.ExitIfError("Couldn't create x errors plot", err)
		xErrsPlot.LineStyle = viz.LineStyle(viz.OrderedColor(colorCounter), 1)

		scatters[name] = []plot.Plotter{s, xErrsPlot}

		colorCounter++
	}

	for _, marker := range options.VerticalMarkers {
		l := viz.NewVerticalLine(tr(marker.T))
		l.LineStyle = marker.LineStyle
		verticalLines = append(verticalLines, l)
	}

	for _, lineOverlay := range options.LineOverlays {
		xys := plotter.XYs{}
		for _, event := range lineOverlay.Events {
			if event.V > 0 {
				xys = append(xys, struct{ X, Y float64 }{tr(event.T), event.V})
			}
		}

		l, s, err := plotter.NewLinePoints(xys)
		say.ExitIfError("Couldn't create scatter plot", err)

		l.LineStyle = lineOverlay.LineStyle
		s.GlyphStyle = plot.GlyphStyle{
			Color:  lineOverlay.LineStyle.Color,
			Radius: lineOverlay.LineStyle.Width,
			Shape:  plot.CrossGlyph{},
		}
		lineOverlays = append(lineOverlays, l, s)
	}

	if options.MaxX != 0 {
		maxX = options.MaxX
	}
	if !options.MaxT.IsZero() {
		maxX = tr(options.MaxT)
	}

	maxY = math.Pow(10, math.Ceil(math.Log10(maxY)))

	if options.MaxY != 0 {
		maxY = options.MaxY
	}

	minY = math.Pow(10, math.Floor(math.Log10(minY)))

	n := len(scatters) + 1
	b := viz.NewUniformBoard(1, n, 0.01)

	allScatterPlot, _ := plot.New()
	allScatterPlot.Title.Text = "All Events"
	allScatterPlot.X.Label.Text = "Time (s)"
	allScatterPlot.Y.Label.Text = "Duration (s)"
	allScatterPlot.Y.Scale = plot.LogScale
	allScatterPlot.Y.Tick.Marker = plot.LogTicks

	for i, name := range events.OrderedNames() {
		scatter, ok := scatters[name]
		if !ok {
			continue
		}

		allScatterPlot.Add(scatter[0])
		allScatterPlot.Add(scatter[1])

		scatterPlot, _ := plot.New()
		scatterPlot.Title.Text = name
		scatterPlot.X.Label.Text = "Time (s)"
		scatterPlot.Y.Label.Text = "Duration (s)"
		scatterPlot.Y.Scale = plot.LogScale
		scatterPlot.Y.Tick.Marker = plot.LogTicks
		scatterPlot.Add(scatter...)
		scatterPlot.Add(verticalLines...)
		scatterPlot.Add(lineOverlays...)
		scatterPlot.Add(options.OverlayPlots...)
		scatterPlot.X.Min = minX
		scatterPlot.X.Max = maxX
		scatterPlot.Y.Min = 1e-5
		scatterPlot.Y.Max = maxY

		b.AddSubPlotAt(scatterPlot, 0, n-2-i)
	}

	allScatterPlot.Add(verticalLines...)
	allScatterPlot.Add(lineOverlays...)
	allScatterPlot.Add(options.OverlayPlots...)
	allScatterPlot.X.Min = minX
	allScatterPlot.X.Max = maxX
	allScatterPlot.Y.Min = 1e-5
	allScatterPlot.Y.Max = maxY
	fmt.Println("all", minX, maxX)

	b.AddSubPlotAt(allScatterPlot, 0, n-1)

	width := 12.0
	if options.WidthStretch > 0 {
		width = width * options.WidthStretch
	}
	b.Save(width, 5*float64(n), filename)
}