Esempio n. 1
0
File: plot.go Progetto: zzn01/plot
// New returns a new plot with some reasonable
// default settings.
func New() (*Plot, error) {
	titleFont, err := vg.MakeFont(DefaultFont, 12)
	if err != nil {
		return nil, err
	}
	x, err := makeAxis()
	if err != nil {
		return nil, err
	}
	y, err := makeAxis()
	if err != nil {
		return nil, err
	}
	legend, err := makeLegend()
	if err != nil {
		return nil, err
	}
	p := &Plot{
		BackgroundColor: color.White,
		X:               x,
		Y:               y,
		Legend:          legend,
	}
	p.Title.TextStyle = draw.TextStyle{
		Color: color.Black,
		Font:  titleFont,
	}
	return p, nil
}
Esempio n. 2
0
// NewLabels returns a new Labels using the DefaultFont and
// the DefaultFontSize.
func NewLabels(d interface {
	XYer
	Labeller
}) (*Labels, error) {
	xys, err := CopyXYs(d)
	if err != nil {
		return nil, err
	}

	if d.Len() != len(xys) {
		return nil, errors.New("Number of points does not match the number of labels")
	}

	strs := make([]string, d.Len())
	for i := range strs {
		strs[i] = d.Label(i)
	}

	fnt, err := vg.MakeFont(DefaultFont, DefaultFontSize)
	if err != nil {
		return nil, err
	}

	return &Labels{
		XYs:       xys,
		Labels:    strs,
		TextStyle: draw.TextStyle{Font: fnt},
	}, nil
}
Esempio n. 3
0
// makeLegend returns a legend with the default
// parameter settings.
func makeLegend() (Legend, error) {
	font, err := vg.MakeFont(DefaultFont, vg.Points(12))
	if err != nil {
		return Legend{}, err
	}
	return Legend{
		ThumbnailWidth: vg.Points(20),
		TextStyle:      draw.TextStyle{Font: font},
	}, nil
}
Esempio n. 4
0
func main() {
	rna, err := readJSON(in)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	minLength, maxLength, binLength = rna.Min, rna.Max, rna.Bin

	p, err := plot.New()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	mm, lo, hi, err := mouseTracks(rna.Features, highlight, palname, 15*vg.Centimeter, rna.Min-rna.Max)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	p.Add(mm...)

	p.HideAxes()

	font, err := vg.MakeFont("Helvetica", 14)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	n := rna.Sample[:strings.Index(rna.Sample, filepath.Ext(rna.Sample))]
	p.Title.Text = fmt.Sprintf(
		`%s
%s
min base quality: %v, minimum mapping score: %d
minimum identity: %d%%
length range: [%d,%d]
heat range: [%f,%f]`,
		decorate(n, format, rna.Filter),
		rna.Sample,
		rna.MinQ, rna.MapQ,
		rna.MinID,
		rna.Min, rna.Max,
		lo, hi)
	p.Title.TextStyle = draw.TextStyle{Color: color.Gray{0}, Font: font}

	err = p.Save(19*vg.Centimeter, 25*vg.Centimeter,
		decorate(filepath.Base(rna.Sample), format, rna.Filter),
	)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
Esempio n. 5
0
File: grob.go Progetto: vdobler/plot
func (text GrobText) Font() vg.Font {
	fname := text.font
	if fname == "" {
		fname = "Courier-Bold"
	}
	font, err := vg.MakeFont(fname, vg.Length(text.size))
	if err != nil {
		panic(err.Error())
	}
	return font
}
Esempio n. 6
0
// makeAxis returns a default Axis.
//
// The default range is (∞, ­∞), and thus any finite
// value is less than Min and greater than Max.
func makeAxis() (Axis, error) {
	labelFont, err := vg.MakeFont(DefaultFont, vg.Points(12))
	if err != nil {
		return Axis{}, err
	}

	tickFont, err := vg.MakeFont(DefaultFont, vg.Points(10))
	if err != nil {
		return Axis{}, err
	}

	a := Axis{
		Min: math.Inf(1),
		Max: math.Inf(-1),
		LineStyle: draw.LineStyle{
			Color: color.Black,
			Width: vg.Points(0.5),
		},
		Padding: vg.Points(5),
		Scale:   LinearScale{},
	}
	a.Label.TextStyle = draw.TextStyle{
		Color: color.Black,
		Font:  labelFont,
	}
	a.Tick.Label = draw.TextStyle{
		Color: color.Black,
		Font:  tickFont,
	}
	a.Tick.LineStyle = draw.LineStyle{
		Color: color.Black,
		Width: vg.Points(0.5),
	}
	a.Tick.Length = vg.Points(8)
	a.Tick.Marker = DefaultTicks{}

	return a, nil
}
Esempio n. 7
0
func main() {
	bf, err := readBED(in)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	p, err := plot.New()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	hs, err := tracks(scoreFeatures(bf, binLength, hg19.Chromosomes), 15*vg.Centimeter)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	p.Add(hs...)

	p.HideAxes()

	font, err := vg.MakeFont("Helvetica", 14)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	p.Title.Text = filepath.Base(in)
	p.Title.TextStyle = draw.TextStyle{Color: color.Gray{0}, Font: font}

	err = p.Save(19*vg.Centimeter, 25*vg.Centimeter, filepath.Base(in)+"."+format)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
Esempio n. 8
0
// ReplayOn applies the set of Actions recorded by the Recorder onto
// the destination Canvas.
func (c *Canvas) ReplayOn(dst vg.Canvas) error {
	if c.fonts == nil {
		c.fonts = make(map[fontID]vg.Font)
	}
	for _, a := range c.Actions {
		fa, ok := a.(*FillString)
		if !ok {
			continue
		}
		f := fontID{name: fa.Font, size: fa.Size}
		if _, exists := c.fonts[f]; !exists {
			var err error
			c.fonts[f], err = vg.MakeFont(fa.Font, fa.Size)
			if err != nil {
				return err
			}
		}
		fa.fonts = c.fonts
	}
	for _, a := range c.Actions {
		a.ApplyTo(dst)
	}
	return nil
}
Esempio n. 9
0
func tracks(scores []rings.Scorer, diameter vg.Length) ([]plot.Plotter, error) {
	var p []plot.Plotter

	radius := diameter / 2

	// Relative sizes.
	const (
		gap = 0.005

		label = 117. / 110.

		countsInner = 97. / 110.
		countsOuter = 70. / 110.

		karyotypeInner = 100. / 110.
		karyotypeOuter = 1.

		large = 6. / 110.
		small = 2. / 110.
	)

	sty := plotter.DefaultLineStyle
	sty.Width /= 2

	chr := make([]feat.Feature, len(hg19.Chromosomes))
	for i, c := range hg19.Chromosomes {
		chr[i] = c
	}
	hs, err := rings.NewGappedBlocks(
		chr,
		rings.Arc{rings.Complete / 4 * rings.CounterClockwise, rings.Complete * rings.Clockwise},
		radius*karyotypeInner, radius*karyotypeOuter, gap,
	)
	if err != nil {
		return nil, err
	}
	hs.LineStyle = sty

	p = append(p, hs)

	bands := make([]feat.Feature, len(hg19.Bands))
	cens := make([]feat.Feature, 0, len(hg19.Chromosomes))
	for i, b := range hg19.Bands {
		bands[i] = colorBand{b}
		s := b.Start()
		// This condition depends on p -> q sort order in the $karyotype.Bands variable.
		// All standard genome packages follow this, though here the test is more general than
		// actually required since hs is telocentric.
		if b.Band[0] == 'q' && (s == 0 || hg19.Bands[i-1].Band[0] == 'p') {
			cens = append(cens, colorBand{&genome.Band{Band: "cen", Desc: "Band", StartPos: s, EndPos: s, Giemsa: "acen", Chr: b.Location()}})
		}
	}
	b, err := rings.NewBlocks(bands, hs, radius*karyotypeInner, radius*karyotypeOuter)
	if err != nil {
		return nil, fmt.Errorf("bands: %v", err)
	}
	p = append(p, b)
	c, err := rings.NewBlocks(cens, hs, radius*karyotypeInner, radius*karyotypeOuter)
	if err != nil {
		return nil, fmt.Errorf("centromeres: %v", err)
	}
	p = append(p, c)

	font, err := vg.MakeFont("Helvetica", radius*large)
	if err != nil {
		return nil, err
	}
	lb, err := rings.NewLabels(hs, radius*label, rings.NameLabels(hs.Set)...)
	if err != nil {
		return nil, err
	}
	lb.TextStyle = draw.TextStyle{Color: color.Gray16{0}, Font: font}
	p = append(p, lb)

	smallFont, err := vg.MakeFont("Helvetica", radius*small)
	if err != nil {
		return nil, err
	}

	counts := make([]rings.Scorer, len(scores))
	for i, s := range scores {
		counts[i] = s.(*feature)
	}
	ct, err := rings.NewScores(counts, hs, radius*countsInner, radius*countsOuter,
		&rings.Trace{
			LineStyles: func() []draw.LineStyle {
				ls := []draw.LineStyle{sty}
				ls[0].Color = color.Gray16{0}
				return ls
			}(),
			Join: true,
			Axis: &rings.Axis{
				Angle:     rings.Complete / 4,
				Grid:      plotter.DefaultGridLineStyle,
				LineStyle: sty,
				Tick: rings.TickConfig{
					Marker:    plot.DefaultTicks{},
					LineStyle: sty,
					Length:    2,
					Label:     draw.TextStyle{Color: color.Gray16{0}, Font: smallFont},
				},
			},
		},
	)
	if err != nil {
		return nil, err
	}
	p = append(p, ct)

	return p, nil
}
Esempio n. 10
0
func TestLegendAlignment(t *testing.T) {
	font, err := vg.MakeFont(plot.DefaultFont, 10.822510822510822) // This font size gives an entry height of 10.
	if err != nil {
		t.Fatalf("failed to create font: %v", err)
	}
	l := plot.Legend{
		ThumbnailWidth: vg.Points(20),
		TextStyle:      draw.TextStyle{Font: font},
	}
	for _, n := range []string{"A", "B", "C", "D"} {
		b, err := plotter.NewBarChart(plotter.Values{0}, 1)
		if err != nil {
			t.Fatalf("failed to create bar chart %q: %v", n, err)
		}
		l.Add(n, b)
	}

	r := recorder.New(100)
	c := draw.NewCanvas(r, 100, 100)
	l.Draw(draw.Canvas{
		Canvas: c.Canvas,
		Rectangle: draw.Rectangle{
			Min: draw.Point{0, 0},
			Max: draw.Point{100, 100},
		},
	})

	got := r.Actions

	// want is a snapshot of the actions for the code above when the
	// graphical output has been visually confirmed to be correct for
	// the bar charts example show in gonum/plot#25.
	want := []recorder.Action{
		&recorder.SetColor{
			Color: color.Gray16{},
		},
		&recorder.Fill{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 30},
				{Type: vg.LineComp, X: 80, Y: 40},
				{Type: vg.LineComp, X: 100, Y: 40},
				{Type: vg.LineComp, X: 100, Y: 30},
				{Type: vg.CloseComp},
			},
		},
		&recorder.SetColor{
			Color: color.Gray16{},
		},
		&recorder.SetLineWidth{
			Width: 1,
		},
		&recorder.SetLineDash{},
		&recorder.Stroke{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 30},
				{Type: vg.LineComp, X: 80, Y: 40},
				{Type: vg.LineComp, X: 100, Y: 40},
				{Type: vg.LineComp, X: 100, Y: 30},
				{Type: vg.LineComp, X: 80, Y: 30},
			},
		},
		&recorder.SetColor{},
		&recorder.FillString{
			Font:   string("Times-Roman"),
			Size:   10.822510822510822,
			X:      69.48051948051948,
			Y:      30.82251082251082,
			String: "A",
		},
		&recorder.SetColor{
			Color: color.Gray16{},
		},
		&recorder.Fill{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 20},
				{Type: vg.LineComp, X: 80, Y: 30},
				{Type: vg.LineComp, X: 100, Y: 30},
				{Type: vg.LineComp, X: 100, Y: 20},
				{Type: vg.CloseComp},
			},
		},
		&recorder.SetColor{
			Color: color.Gray16{},
		},
		&recorder.SetLineWidth{
			Width: 1,
		},
		&recorder.SetLineDash{},
		&recorder.Stroke{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 20},
				{Type: vg.LineComp, X: 80, Y: 30},
				{Type: vg.LineComp, X: 100, Y: 30},
				{Type: vg.LineComp, X: 100, Y: 20},
				{Type: vg.LineComp, X: 80, Y: 20},
			},
		},
		&recorder.SetColor{},
		&recorder.FillString{
			Font:   string("Times-Roman"),
			Size:   10.822510822510822,
			X:      70.07575757575758,
			Y:      20.82251082251082,
			String: "B",
		},
		&recorder.SetColor{
			Color: color.Gray16{
				Y: uint16(0),
			},
		},
		&recorder.Fill{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 10},
				{Type: vg.LineComp, X: 80, Y: 20},
				{Type: vg.LineComp, X: 100, Y: 20},
				{Type: vg.LineComp, X: 100, Y: 10},
				{Type: vg.CloseComp},
			},
		},
		&recorder.SetColor{
			Color: color.Gray16{},
		},
		&recorder.SetLineWidth{
			Width: 1,
		},
		&recorder.SetLineDash{},
		&recorder.Stroke{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 10},
				{Type: vg.LineComp, X: 80, Y: 20},
				{Type: vg.LineComp, X: 100, Y: 20},
				{Type: vg.LineComp, X: 100, Y: 10},
				{Type: vg.LineComp, X: 80, Y: 10},
			},
		},
		&recorder.SetColor{},
		&recorder.FillString{
			Font:   string("Times-Roman"),
			Size:   10.822510822510822,
			X:      70.07575757575758,
			Y:      10.822510822510822,
			String: "C",
		},
		&recorder.SetColor{
			Color: color.Gray16{},
		},
		&recorder.Fill{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 0},
				{Type: vg.LineComp, X: 80, Y: 10},
				{Type: vg.LineComp, X: 100, Y: 10},
				{Type: vg.LineComp, X: 100, Y: 0},
				{Type: vg.CloseComp},
			},
		},
		&recorder.SetColor{
			Color: color.Gray16{},
		},
		&recorder.SetLineWidth{
			Width: 1,
		},
		&recorder.SetLineDash{},
		&recorder.Stroke{
			Path: vg.Path{
				{Type: vg.MoveComp, X: 80, Y: 0},
				{Type: vg.LineComp, X: 80, Y: 10},
				{Type: vg.LineComp, X: 100, Y: 10},
				{Type: vg.LineComp, X: 100, Y: 0},
				{Type: vg.LineComp, X: 80, Y: 0},
			},
		},
		&recorder.SetColor{},
		&recorder.FillString{
			Font:   string("Times-Roman"),
			Size:   10.822510822510822,
			X:      69.48051948051948,
			Y:      0.8225108225108215,
			String: "D",
		},
	}

	if !reflect.DeepEqual(got, want) {
		t.Errorf("unexpected legend actions:\ngot:\n%s\nwant:\n%s", formatActions(got), formatActions(want))
	}
}
Esempio n. 11
0
func mouseTracks(scores []rings.Scorer, highlight []string, palname string, diameter vg.Length, lenRange int) (pp []plot.Plotter, lo, hi float64, err error) {
	var p []plot.Plotter

	radius := diameter / 2

	// Relative sizes.
	const (
		gap = 0.005

		label = 117. / 110.

		countsInner = 25. / 110.
		countsOuter = 40. / 110.

		heatInner = 45. / 110.
		heatOuter = 75. / 110.

		traceInner = 80. / 110.
		traceOuter = 95. / 110.

		karyotypeInner = 100. / 110.
		karyotypeOuter = 1.

		large = 7. / 110.
		small = 2. / 110.
	)

	sty := plotter.DefaultLineStyle
	sty.Width /= 2

	chr := make([]feat.Feature, len(mm10.Chromosomes))
	for i, c := range mm10.Chromosomes {
		chr[i] = c
	}
	mm, err := rings.NewGappedBlocks(
		chr,
		rings.Arc{rings.Complete / 4 * rings.CounterClockwise, rings.Complete * rings.Clockwise},
		radius*karyotypeInner, radius*karyotypeOuter, gap,
	)
	if err != nil {
		return nil, 0, 0, err
	}
	mm.LineStyle = sty

	pal, err := brewer.GetPalette(brewer.TypeQualitative, palname, len(highlight))
	if err == nil {
		for i, hn := range highlight {
			for _, c := range mm.Set {
				if hn == strings.ToLower(c.Name()) {
					arc, err := mm.Base.ArcOf(c, nil)
					arc.Theta += rings.Complete * gap / 2
					arc.Phi -= rings.Complete * gap
					if err != nil {
						fmt.Printf("could not find: %s\n", hn)
						break
					}
					col := pal.Colors()[i]
					nc := color.NRGBAModel.Convert(col).(color.NRGBA)
					nc.A = 0x40
					h := rings.NewHighlight(
						nc,
						arc,
						radius*(traceInner-2.5/110.),
						radius*(label+5./110.),
					)
					h.LineStyle = sty
					h.LineStyle.Width /= 4
					p = append(p, h)
					break
				}
			}
		}
	} else if len(highlight) > 0 {
		fmt.Println("no palette")
	}

	p = append(p, mm)

	bands := make([]feat.Feature, len(mm10.Bands))
	cens := make([]feat.Feature, 0, len(mm10.Chromosomes))
	for i, b := range mm10.Bands {
		bands[i] = colorBand{b}
		s := b.Start()
		// This condition depends on p -> q sort order in the $karyotype.Bands variable.
		// All standard genome packages follow this, though here the test is more general than
		// actually required since mm is telocentric.
		if b.Band[0] == 'q' && (s == 0 || mm10.Bands[i-1].Band[0] == 'p') {
			cens = append(cens, colorBand{&genome.Band{Band: "cen", Desc: "Band", StartPos: s, EndPos: s, Giemsa: "acen", Chr: b.Location()}})
		}
	}
	b, err := rings.NewBlocks(bands, mm, radius*karyotypeInner, radius*karyotypeOuter)
	if err != nil {
		return nil, 0, 0, fmt.Errorf("bands: %v", err)
	}
	p = append(p, b)
	c, err := rings.NewBlocks(cens, mm, radius*karyotypeInner, radius*karyotypeOuter)
	if err != nil {
		return nil, 0, 0, fmt.Errorf("centromeres: %v", err)
	}
	p = append(p, c)

	font, err := vg.MakeFont("Helvetica", radius*large)
	if err != nil {
		return nil, 0, 0, err
	}
	lb, err := rings.NewLabels(mm, radius*label, rings.NameLabels(mm.Set)...)
	if err != nil {
		return nil, 0, 0, err
	}
	lb.TextStyle = draw.TextStyle{Color: color.Gray16{0}, Font: font}
	p = append(p, lb)

	s, err := rings.NewScores(scores, mm, radius*heatInner, radius*heatOuter,
		&rings.Heat{Palette: palette.Heat(10, 1).Colors()},
	)
	if err != nil {
		return nil, 0, 0, err
	}
	p = append(p, s)

	smallFont, err := vg.MakeFont("Helvetica", radius*small)
	if err != nil {
		return nil, 0, 0, err
	}

	traces := make([]rings.Scorer, len(scores))
	for i, s := range scores {
		traces[i] = &tfs{s.(*feature)}
	}
	t, err := rings.NewScores(traces, mm, radius*traceInner, radius*traceOuter,
		&rings.Trace{
			LineStyles: func() []draw.LineStyle {
				ls := []draw.LineStyle{sty, sty}
				for i, c := range brewer.Set1[3].Colors()[:len(ls)] {
					nc := color.NRGBAModel.Convert(c).(color.NRGBA)
					nc.A = 0x80
					ls[i].Color = nc
				}
				return ls
			}(),
			Join: true,
			Axis: &rings.Axis{
				Angle:     rings.Complete / 4,
				Grid:      plotter.DefaultGridLineStyle,
				LineStyle: sty,
				Tick: rings.TickConfig{
					Marker:    plot.DefaultTicks{},
					LineStyle: sty,
					Length:    2,
					Label:     draw.TextStyle{Color: color.Gray16{0}, Font: smallFont},
				},
			},
		},
	)
	if err != nil {
		return nil, 0, 0, err
	}
	if maxTrace != 0 {
		t.Max = maxTrace
		if t.Min > t.Max {
			return nil, 0, 0, fmt.Errorf("maximum trace out of range: min=%f", t.Min)
		}
	}
	if !math.IsInf(t.Max-t.Min, 0) {
		p = append(p, t)
	}

	counts := make([]rings.Scorer, len(scores))
	for i, s := range scores {
		counts[i] = ctfs{s.(*feature)}
	}
	ct, err := rings.NewScores(counts, mm, radius*countsInner, radius*countsOuter,
		&rings.Trace{
			LineStyles: func() []draw.LineStyle {
				ls := []draw.LineStyle{sty}
				ls[0].Color = color.Gray16{0}
				return ls
			}(),
			Join: true,
			Axis: &rings.Axis{
				Angle:     rings.Complete / 4,
				Grid:      plotter.DefaultGridLineStyle,
				LineStyle: sty,
				Tick: rings.TickConfig{
					Marker:    plot.DefaultTicks{},
					LineStyle: sty,
					Length:    2,
					Label:     draw.TextStyle{Color: color.Gray16{0}, Font: smallFont},
				},
			},
		},
	)
	if err != nil {
		return nil, 0, 0, err
	}
	if maxCounts != 0 {
		ct.Max = maxCounts
		if ct.Min > ct.Max {
			return nil, 0, 0, fmt.Errorf("maximum counts out of range: min=%f", ct.Min)
		}
	}
	p = append(p, ct)

	return p, s.Min, s.Max, nil
}
Esempio n. 12
0
func main() {
	rna, err := readJSON(in)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	minLength, maxLength, binLength = rna.Min, rna.Max, rna.Bin

	switch normalisation {
	case 0:
		weightFactors[0], weightFactors[1] = float64(rna.Totals[0]), float64(rna.Totals[1])
	case 1:
		weightFactors, err = normaliseByBin(rna)
	case 2:
		weightFactors, err = normaliseByBlock(rna)
	default:
		err = errors.New("illegal normalisation strategy")
	}
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	p, err := plot.New()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	mm, lo, hi, err := mouseTracks(rna.Features, highlight, palname, 15*vg.Centimeter, rna.Min-rna.Max)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	p.Add(mm...)

	p.HideAxes()

	font, err := vg.MakeFont("Helvetica", 14)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	for i, n := range rna.Pair {
		n = filepath.Base(n)
		rna.Pair[i] = n[:strings.Index(n, filepath.Ext(n))]
	}
	p.Title.Text = fmt.Sprintf(
		`%s
%s
%s
min base quality: %v, minimum mapping score: %d
minimum identity: %d%%
length range: [%d,%d]
heat range: [%f,%f]`,
		decorate(rna.Pair[1], format, rna.Filter),
		rna.Classes,
		rna.Pair,
		rna.MinQ, rna.MapQ,
		rna.MinID,
		rna.Min, rna.Max,
		lo, hi)
	p.Title.TextStyle = draw.TextStyle{Color: color.Gray{0}, Font: font}

	for i, class := range rna.Classes {
		rna.Classes[i] = class[strings.LastIndex(class, "/")+1:]
	}

	var classes string
	if len(rna.Classes) > 0 {
		classes = "-" + strings.Join(rna.Classes, ",")
	}
	err = p.Save(18*vg.Centimeter, 25*vg.Centimeter,
		decorate(filepath.Base(rna.Pair[1])+classes, format, rna.Filter),
	)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}