extracted plot functionality into plot.go file
This commit is contained in:
parent
c615a3d820
commit
a2babcb962
277
example/main.go
277
example/main.go
@ -3,17 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"bitbucket.org/differenttravel/interval"
|
"bitbucket.org/differenttravel/interval"
|
||||||
"gonum.org/v1/plot"
|
|
||||||
"gonum.org/v1/plot/palette"
|
|
||||||
"gonum.org/v1/plot/plotter"
|
|
||||||
"gonum.org/v1/plot/vg"
|
|
||||||
"gonum.org/v1/plot/vg/draw"
|
|
||||||
"gonum.org/v1/plot/vg/vgimg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -21,31 +14,6 @@ const (
|
|||||||
MaxX = 40
|
MaxX = 40
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlotType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
PlotTypeIntervals PlotType = iota
|
|
||||||
PlotTypeGaps
|
|
||||||
PlotTypeOverlapped
|
|
||||||
PlotTypeMerged
|
|
||||||
)
|
|
||||||
|
|
||||||
type Superplot struct {
|
|
||||||
Plot *plot.Plot
|
|
||||||
NumElements int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
openBracket = "["
|
|
||||||
closeBracket = "]"
|
|
||||||
openParenthesis = "("
|
|
||||||
closeParenthesis = ")"
|
|
||||||
intervalOpening = openBracket
|
|
||||||
intervalClosing = closeBracket
|
|
||||||
lowInclusive bool
|
|
||||||
highInclusive bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
filename := "data.txt"
|
filename := "data.txt"
|
||||||
xys, err := readData(filename)
|
xys, err := readData(filename)
|
||||||
@ -53,7 +21,8 @@ func main() {
|
|||||||
log.Fatalf("could not read %s: %v", filename, err)
|
log.Fatalf("could not read %s: %v", filename, err)
|
||||||
}
|
}
|
||||||
intervals := initIntervals(xys)
|
intervals := initIntervals(xys)
|
||||||
err = plotData("out.png", intervals)
|
ip := interval.NewPlot(intervals.IsLowInclusive(), intervals.IsHighInclusive())
|
||||||
|
err = ip.PlotData("out.png", intervals, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("could not plot data: %v", err)
|
log.Fatalf("could not plot data: %v", err)
|
||||||
}
|
}
|
||||||
@ -94,28 +63,12 @@ func initIntervals(xys []xy) interval.Intervals {
|
|||||||
// initialize Intervals
|
// initialize Intervals
|
||||||
minLow := MinX
|
minLow := MinX
|
||||||
maxHigh := MaxX
|
maxHigh := MaxX
|
||||||
lowInclusive = true
|
lowInclusive := true
|
||||||
highInclusive = true
|
highInclusive := true
|
||||||
selfAdjustMinLow := false
|
selfAdjustMinLow := false
|
||||||
selfAdjustMaxHigh := true
|
selfAdjustMaxHigh := true
|
||||||
intervals := interval.NewIntervals(minLow, maxHigh, lowInclusive, highInclusive, selfAdjustMinLow, selfAdjustMaxHigh)
|
intervals := interval.NewIntervals(minLow, maxHigh, lowInclusive, highInclusive, selfAdjustMinLow, selfAdjustMaxHigh)
|
||||||
|
|
||||||
if intervals.IsLowInclusive() {
|
|
||||||
intervalOpening = "["
|
|
||||||
lowInclusive = true
|
|
||||||
} else {
|
|
||||||
intervalOpening = "("
|
|
||||||
lowInclusive = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if intervals.IsHighInclusive() {
|
|
||||||
intervalClosing = "]"
|
|
||||||
highInclusive = true
|
|
||||||
} else {
|
|
||||||
intervalClosing = ")"
|
|
||||||
highInclusive = false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, xy := range xys {
|
for _, xy := range xys {
|
||||||
err := intervals.AddInterval(&interval.Interval{Low: xy.x, High: xy.y})
|
err := intervals.AddInterval(&interval.Interval{Low: xy.x, High: xy.y})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -125,225 +78,3 @@ func initIntervals(xys []xy) interval.Intervals {
|
|||||||
intervals.Sort()
|
intervals.Sort()
|
||||||
return intervals
|
return intervals
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertToPlotterXYs(intervals []*interval.Interval) plotter.XYs {
|
|
||||||
pxys := plotter.XYs{}
|
|
||||||
for _, intvl := range intervals {
|
|
||||||
pxys = append(pxys, struct{ X, Y float64 }{X: float64(intvl.Low), Y: float64(intvl.High)})
|
|
||||||
}
|
|
||||||
return pxys
|
|
||||||
}
|
|
||||||
|
|
||||||
func alignPlots(plotItems []*Superplot, minLow, maxHigh int) *vgimg.Canvas {
|
|
||||||
rows, cols := len(plotItems), 1
|
|
||||||
plots := make([][]*plot.Plot, rows)
|
|
||||||
for j := 0; j < rows; j++ {
|
|
||||||
plots[j] = make([]*plot.Plot, cols)
|
|
||||||
for i := 0; i < cols; i++ {
|
|
||||||
p := plotItems[j]
|
|
||||||
|
|
||||||
// make sure the horizontal scales match
|
|
||||||
p.Plot.X.Min = float64(minLow) // MinX
|
|
||||||
p.Plot.X.Max = float64(maxHigh) //MaxX
|
|
||||||
|
|
||||||
plots[j][i] = p.Plot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img := vgimg.New(vg.Points(512), vg.Points(float64(200*rows)))
|
|
||||||
dc := draw.New(img)
|
|
||||||
|
|
||||||
t := draw.Tiles{
|
|
||||||
Rows: rows,
|
|
||||||
Cols: cols,
|
|
||||||
}
|
|
||||||
|
|
||||||
canvases := plot.Align(plots, t, dc)
|
|
||||||
for j := 0; j < rows; j++ {
|
|
||||||
for i := 0; i < cols; i++ {
|
|
||||||
if plots[j][i] != nil {
|
|
||||||
plots[j][i].Draw(canvases[j][i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFileFromCanvas(path string, img *vgimg.Canvas) error {
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
png := vgimg.PngCanvas{Canvas: img}
|
|
||||||
if _, err := png.WriteTo(f); err != nil {
|
|
||||||
return fmt.Errorf("could not write to %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
return fmt.Errorf("could not close %s: %v", path, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPlot(title string, xys plotter.XYs, plotType PlotType) (*Superplot, error) {
|
|
||||||
p, err := plot.New()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not create plot: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a grid behind the data
|
|
||||||
p.Add(plotter.NewGrid())
|
|
||||||
p.Title.Text = title
|
|
||||||
p.HideY()
|
|
||||||
p.X.Padding = vg.Length(5)
|
|
||||||
p.Y.Padding = vg.Length(20)
|
|
||||||
|
|
||||||
plotIntervals(p, plotType, xys)
|
|
||||||
return &Superplot{p, xys.Len()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func plotData(path string, intervals interval.Intervals) error {
|
|
||||||
plots := []*Superplot{}
|
|
||||||
|
|
||||||
// create Intervals plot
|
|
||||||
xysIntervals := convertToPlotterXYs(intervals.GetIntervals())
|
|
||||||
p1, err := createPlot("Intervals", xysIntervals, PlotTypeIntervals)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create plot: %v", err)
|
|
||||||
}
|
|
||||||
plots = append(plots, p1)
|
|
||||||
|
|
||||||
// create Gaps plot
|
|
||||||
xysGaps := convertToPlotterXYs(intervals.Gaps())
|
|
||||||
p2, err := createPlot("Gaps", xysGaps, PlotTypeGaps)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create plot: %v", err)
|
|
||||||
}
|
|
||||||
plots = append(plots, p2)
|
|
||||||
|
|
||||||
// create Overlapped `plot
|
|
||||||
xysOverlapped := convertToPlotterXYs(intervals.Overlapped())
|
|
||||||
p3, err := createPlot("Overlapped", xysOverlapped, PlotTypeOverlapped)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create plot: %v", err)
|
|
||||||
}
|
|
||||||
plots = append(plots, p3)
|
|
||||||
|
|
||||||
// create Merged plot
|
|
||||||
xysMerged := convertToPlotterXYs(intervals.Merge())
|
|
||||||
p4, err := createPlot("Merged", xysMerged, PlotTypeMerged)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create plot: %v", err)
|
|
||||||
}
|
|
||||||
plots = append(plots, p4)
|
|
||||||
|
|
||||||
// join all plots, align them
|
|
||||||
canvas := alignPlots(plots, intervals.GetMinLow(), intervals.GetMaxHigh())
|
|
||||||
err = createFileFromCanvas("out.png", canvas)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func plotIntervals(p *plot.Plot, plotType PlotType, xys plotter.XYs) error {
|
|
||||||
var ps []plot.Plotter
|
|
||||||
colors := getColors()
|
|
||||||
numColors := len(colors)
|
|
||||||
|
|
||||||
crossShape := draw.CrossGlyph{}
|
|
||||||
ringShape := draw.RingGlyph{}
|
|
||||||
legendWithCrossShape := false
|
|
||||||
legendWithRingShape := false
|
|
||||||
for i, xy := range xys {
|
|
||||||
pXYLow := struct{ X, Y float64 }{xy.X, float64(i)}
|
|
||||||
pXYHigh := struct{ X, Y float64 }{xy.Y, float64(i)}
|
|
||||||
pXYs := plotter.XYs{pXYLow, pXYHigh}
|
|
||||||
color := colors[i%numColors]
|
|
||||||
|
|
||||||
var s1, s2 *plotter.Scatter
|
|
||||||
var err error
|
|
||||||
label := ""
|
|
||||||
if plotType == PlotTypeIntervals {
|
|
||||||
label = fmt.Sprintf("%s%v,%v%s", intervalOpening, xy.X, xy.Y, intervalClosing)
|
|
||||||
s1, err = plotter.NewScatter(plotter.XYs{pXYLow})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create new scatter: %v", err)
|
|
||||||
}
|
|
||||||
if !lowInclusive {
|
|
||||||
s1.GlyphStyle.Shape = crossShape
|
|
||||||
if !legendWithCrossShape {
|
|
||||||
p.Legend.Add("Exclusive", s1)
|
|
||||||
legendWithCrossShape = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s1.GlyphStyle.Shape = ringShape
|
|
||||||
if !legendWithRingShape {
|
|
||||||
p.Legend.Add("Inclusive", s1)
|
|
||||||
legendWithRingShape = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s2, err = plotter.NewScatter(plotter.XYs{pXYHigh})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create new scatter: %v", err)
|
|
||||||
}
|
|
||||||
if !highInclusive {
|
|
||||||
s2.GlyphStyle.Shape = crossShape
|
|
||||||
if !legendWithCrossShape {
|
|
||||||
p.Legend.Add("Exclusive", s2)
|
|
||||||
legendWithCrossShape = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s2.GlyphStyle.Shape = ringShape
|
|
||||||
if !legendWithRingShape {
|
|
||||||
p.Legend.Add("Inclusive", s2)
|
|
||||||
legendWithRingShape = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
label = fmt.Sprintf("%s%v,%v%s", openBracket, xy.X, xy.Y, closeBracket)
|
|
||||||
s1, err = plotter.NewScatter(pXYs)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create new scatter: %v", err)
|
|
||||||
}
|
|
||||||
if !lowInclusive && !highInclusive {
|
|
||||||
s1.GlyphStyle.Shape = crossShape
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if xy.X != xy.Y {
|
|
||||||
l, err := plotter.NewLine(pXYs)
|
|
||||||
l.Color = color
|
|
||||||
l.Width = vg.Points(10)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create a new line: %v", err)
|
|
||||||
}
|
|
||||||
ps = append(ps, l)
|
|
||||||
p.Legend.Add(label, l)
|
|
||||||
} else {
|
|
||||||
s1.Color = color
|
|
||||||
if s2 != nil {
|
|
||||||
s2.Color = color
|
|
||||||
}
|
|
||||||
p.Legend.Add(label, s1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create a new scatter: %v", err)
|
|
||||||
}
|
|
||||||
ps = append(ps, s1)
|
|
||||||
if s2 != nil {
|
|
||||||
ps = append(ps, s2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.Legend.Left = false
|
|
||||||
p.Add(ps...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColors() []color.Color {
|
|
||||||
palette := palette.Rainbow(10, 0, 1, 1, 1, 1)
|
|
||||||
return palette.Colors()
|
|
||||||
}
|
|
||||||
|
304
plot.go
Normal file
304
plot.go
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package interval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gonum.org/v1/plot"
|
||||||
|
"gonum.org/v1/plot/palette"
|
||||||
|
"gonum.org/v1/plot/plotter"
|
||||||
|
"gonum.org/v1/plot/vg"
|
||||||
|
"gonum.org/v1/plot/vg/draw"
|
||||||
|
"gonum.org/v1/plot/vg/vgimg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlotType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
PlotTypeIntervals PlotType = iota
|
||||||
|
PlotTypeGaps
|
||||||
|
PlotTypeOverlapped
|
||||||
|
PlotTypeMerged
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
openBracket = "["
|
||||||
|
closeBracket = "]"
|
||||||
|
openParenthesis = "("
|
||||||
|
closeParenthesis = ")"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntervalPlot interface {
|
||||||
|
PlotData(path string, intervals Intervals, addIntervals, addGaps, addOverlapped, addMerges bool) error
|
||||||
|
CreatePlot(title string, xys plotter.XYs, plotType PlotType) (*Superplot, error)
|
||||||
|
AlignPlots(plotItems []*Superplot, minLow, maxHigh int) *vgimg.Canvas
|
||||||
|
CreateFileFromCanvas(path string, img *vgimg.Canvas) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type intervalPlot struct {
|
||||||
|
lowInclusive bool
|
||||||
|
highInclusive bool
|
||||||
|
intervalOpening string
|
||||||
|
intervalClosing string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPlot(lowInclusive bool, highInclusive bool) IntervalPlot {
|
||||||
|
opening := ""
|
||||||
|
if lowInclusive {
|
||||||
|
opening = openBracket
|
||||||
|
} else {
|
||||||
|
opening = openParenthesis
|
||||||
|
}
|
||||||
|
|
||||||
|
closing := ""
|
||||||
|
if highInclusive {
|
||||||
|
closing = closeBracket
|
||||||
|
} else {
|
||||||
|
closing = closeParenthesis
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := &intervalPlot{
|
||||||
|
lowInclusive: lowInclusive,
|
||||||
|
highInclusive: highInclusive,
|
||||||
|
intervalOpening: opening,
|
||||||
|
intervalClosing: closing,
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
type Superplot struct {
|
||||||
|
Plot *plot.Plot
|
||||||
|
NumElements int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip *intervalPlot) PlotData(path string, intervals Intervals, addIntervals, addGaps, addOverlapped, addMerges bool) error {
|
||||||
|
plots := []*Superplot{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if addIntervals {
|
||||||
|
// create Intervals plot
|
||||||
|
xysIntervals := ip.convertToPlotterXYs(intervals.GetIntervals())
|
||||||
|
p1, err := ip.CreatePlot("Intervals", xysIntervals, PlotTypeIntervals)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create plot: %v", err)
|
||||||
|
}
|
||||||
|
plots = append(plots, p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addGaps {
|
||||||
|
// create Gaps plot
|
||||||
|
xysGaps := ip.convertToPlotterXYs(intervals.Gaps())
|
||||||
|
p2, err := ip.CreatePlot("Gaps", xysGaps, PlotTypeGaps)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create plot: %v", err)
|
||||||
|
}
|
||||||
|
plots = append(plots, p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addOverlapped {
|
||||||
|
// create Overlapped `plot
|
||||||
|
xysOverlapped := ip.convertToPlotterXYs(intervals.Overlapped())
|
||||||
|
p3, err := ip.CreatePlot("Overlapped", xysOverlapped, PlotTypeOverlapped)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create plot: %v", err)
|
||||||
|
}
|
||||||
|
plots = append(plots, p3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addMerges {
|
||||||
|
// create Merged plot
|
||||||
|
xysMerged := ip.convertToPlotterXYs(intervals.Merge())
|
||||||
|
p4, err := ip.CreatePlot("Merged", xysMerged, PlotTypeMerged)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create plot: %v", err)
|
||||||
|
}
|
||||||
|
plots = append(plots, p4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// join all plots, align them
|
||||||
|
canvas := ip.AlignPlots(plots, intervals.GetMinLow(), intervals.GetMaxHigh())
|
||||||
|
err = ip.CreateFileFromCanvas("out.png", canvas)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip *intervalPlot) CreateFileFromCanvas(path string, img *vgimg.Canvas) error {
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
png := vgimg.PngCanvas{Canvas: img}
|
||||||
|
if _, err := png.WriteTo(f); err != nil {
|
||||||
|
return fmt.Errorf("could not write to %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("could not close %s: %v", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip *intervalPlot) AlignPlots(plotItems []*Superplot, minLow, maxHigh int) *vgimg.Canvas {
|
||||||
|
rows, cols := len(plotItems), 1
|
||||||
|
plots := make([][]*plot.Plot, rows)
|
||||||
|
for j := 0; j < rows; j++ {
|
||||||
|
plots[j] = make([]*plot.Plot, cols)
|
||||||
|
for i := 0; i < cols; i++ {
|
||||||
|
p := plotItems[j]
|
||||||
|
|
||||||
|
// make sure the horizontal scales match
|
||||||
|
p.Plot.X.Min = float64(minLow) // MinX
|
||||||
|
p.Plot.X.Max = float64(maxHigh) //MaxX
|
||||||
|
|
||||||
|
plots[j][i] = p.Plot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img := vgimg.New(vg.Points(512), vg.Points(float64(200*rows)))
|
||||||
|
dc := draw.New(img)
|
||||||
|
|
||||||
|
t := draw.Tiles{
|
||||||
|
Rows: rows,
|
||||||
|
Cols: cols,
|
||||||
|
}
|
||||||
|
|
||||||
|
canvases := plot.Align(plots, t, dc)
|
||||||
|
for j := 0; j < rows; j++ {
|
||||||
|
for i := 0; i < cols; i++ {
|
||||||
|
if plots[j][i] != nil {
|
||||||
|
plots[j][i].Draw(canvases[j][i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip *intervalPlot) CreatePlot(title string, xys plotter.XYs, plotType PlotType) (*Superplot, error) {
|
||||||
|
p, err := plot.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create plot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a grid behind the data
|
||||||
|
p.Add(plotter.NewGrid())
|
||||||
|
p.Title.Text = title
|
||||||
|
p.HideY()
|
||||||
|
p.X.Padding = vg.Length(5)
|
||||||
|
p.Y.Padding = vg.Length(20)
|
||||||
|
|
||||||
|
ip.plotIntervals(p, plotType, xys)
|
||||||
|
return &Superplot{p, xys.Len()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip *intervalPlot) plotIntervals(p *plot.Plot, plotType PlotType, xys plotter.XYs) error {
|
||||||
|
var ps []plot.Plotter
|
||||||
|
colors := ip.getColors()
|
||||||
|
numColors := len(colors)
|
||||||
|
|
||||||
|
crossShape := draw.CrossGlyph{}
|
||||||
|
ringShape := draw.RingGlyph{}
|
||||||
|
legendWithCrossShape := false
|
||||||
|
legendWithRingShape := false
|
||||||
|
for i, xy := range xys {
|
||||||
|
pXYLow := struct{ X, Y float64 }{xy.X, float64(i)}
|
||||||
|
pXYHigh := struct{ X, Y float64 }{xy.Y, float64(i)}
|
||||||
|
pXYs := plotter.XYs{pXYLow, pXYHigh}
|
||||||
|
color := colors[i%numColors]
|
||||||
|
|
||||||
|
var s1, s2 *plotter.Scatter
|
||||||
|
var err error
|
||||||
|
label := ""
|
||||||
|
if plotType == PlotTypeIntervals {
|
||||||
|
label = fmt.Sprintf("%s%v,%v%s", ip.intervalOpening, xy.X, xy.Y, ip.intervalClosing)
|
||||||
|
s1, err = plotter.NewScatter(plotter.XYs{pXYLow})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create new scatter: %v", err)
|
||||||
|
}
|
||||||
|
if !ip.lowInclusive {
|
||||||
|
s1.GlyphStyle.Shape = crossShape
|
||||||
|
if !legendWithCrossShape {
|
||||||
|
p.Legend.Add("Exclusive", s1)
|
||||||
|
legendWithCrossShape = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s1.GlyphStyle.Shape = ringShape
|
||||||
|
if !legendWithRingShape {
|
||||||
|
p.Legend.Add("Inclusive", s1)
|
||||||
|
legendWithRingShape = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s2, err = plotter.NewScatter(plotter.XYs{pXYHigh})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create new scatter: %v", err)
|
||||||
|
}
|
||||||
|
if !ip.highInclusive {
|
||||||
|
s2.GlyphStyle.Shape = crossShape
|
||||||
|
if !legendWithCrossShape {
|
||||||
|
p.Legend.Add("Exclusive", s2)
|
||||||
|
legendWithCrossShape = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s2.GlyphStyle.Shape = ringShape
|
||||||
|
if !legendWithRingShape {
|
||||||
|
p.Legend.Add("Inclusive", s2)
|
||||||
|
legendWithRingShape = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label = fmt.Sprintf("%s%v,%v%s", openBracket, xy.X, xy.Y, closeBracket)
|
||||||
|
s1, err = plotter.NewScatter(pXYs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create new scatter: %v", err)
|
||||||
|
}
|
||||||
|
if !ip.lowInclusive && !ip.highInclusive {
|
||||||
|
s1.GlyphStyle.Shape = crossShape
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if xy.X != xy.Y {
|
||||||
|
l, err := plotter.NewLine(pXYs)
|
||||||
|
l.Color = color
|
||||||
|
l.Width = vg.Points(10)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create a new line: %v", err)
|
||||||
|
}
|
||||||
|
ps = append(ps, l)
|
||||||
|
p.Legend.Add(label, l)
|
||||||
|
} else {
|
||||||
|
s1.Color = color
|
||||||
|
if s2 != nil {
|
||||||
|
s2.Color = color
|
||||||
|
}
|
||||||
|
p.Legend.Add(label, s1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create a new scatter: %v", err)
|
||||||
|
}
|
||||||
|
ps = append(ps, s1)
|
||||||
|
if s2 != nil {
|
||||||
|
ps = append(ps, s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Legend.Left = false
|
||||||
|
p.Add(ps...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip *intervalPlot) getColors() []color.Color {
|
||||||
|
palette := palette.Rainbow(10, 0, 1, 1, 1, 1)
|
||||||
|
return palette.Colors()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip *intervalPlot) convertToPlotterXYs(intervals []*Interval) plotter.XYs {
|
||||||
|
pxys := plotter.XYs{}
|
||||||
|
for _, intvl := range intervals {
|
||||||
|
pxys = append(pxys, struct{ X, Y float64 }{X: float64(intvl.Low), Y: float64(intvl.High)})
|
||||||
|
}
|
||||||
|
return pxys
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user