// Copyright ©2015 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package plot_test import ( "bytes" "fmt" "image/color" "reflect" "testing" "github.com/igrmk/plot" "github.com/igrmk/plot/plotter" "github.com/igrmk/plot/vg" "github.com/igrmk/plot/vg/draw" "github.com/igrmk/plot/vg/recorder" ) func TestLegendAlignment(t *testing.T) { const fontSize = 10.189054726368159 // This font size gives an entry height of 10. font, err := vg.MakeFont(plot.DefaultFont, fontSize) 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) } var r recorder.Canvas c := draw.NewCanvas(&r, 100, 100) l.Draw(draw.Canvas{ Canvas: c.Canvas, Rectangle: vg.Rectangle{ Min: vg.Point{X: 0, Y: 0}, Max: vg.Point{X: 100, Y: 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, Pos: vg.Point{X: 80, Y: 30}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 40}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 40}}, {Type: vg.LineComp, Pos: vg.Point{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, Pos: vg.Point{X: 80, Y: 30}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 40}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 40}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 30}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 30}}, }, }, &recorder.SetColor{}, &recorder.FillString{ Font: string("Times-Roman"), Size: fontSize, Point: vg.Point{X: 70.09452736318407, Y: 30.18905472636816}, String: "A", }, &recorder.SetColor{ Color: color.Gray16{}, }, &recorder.Fill{ Path: vg.Path{ {Type: vg.MoveComp, Pos: vg.Point{X: 80, Y: 20}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 30}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 30}}, {Type: vg.LineComp, Pos: vg.Point{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, Pos: vg.Point{X: 80, Y: 20}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 30}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 30}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 20}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 20}}, }, }, &recorder.SetColor{}, &recorder.FillString{ Font: string("Times-Roman"), Size: fontSize, Point: vg.Point{X: 70.65671641791045, Y: 20.18905472636816}, String: "B", }, &recorder.SetColor{ Color: color.Gray16{ Y: uint16(0), }, }, &recorder.Fill{ Path: vg.Path{ {Type: vg.MoveComp, Pos: vg.Point{X: 80, Y: 10}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 20}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 20}}, {Type: vg.LineComp, Pos: vg.Point{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, Pos: vg.Point{X: 80, Y: 10}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 20}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 20}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 10}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 10}}, }, }, &recorder.SetColor{}, &recorder.FillString{ Font: string("Times-Roman"), Size: fontSize, Point: vg.Point{X: 70.65671641791045, Y: 10.189054726368159}, String: "C", }, &recorder.SetColor{ Color: color.Gray16{}, }, &recorder.Fill{ Path: vg.Path{ {Type: vg.MoveComp, Pos: vg.Point{X: 80, Y: 0}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 10}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 10}}, {Type: vg.LineComp, Pos: vg.Point{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, Pos: vg.Point{X: 80, Y: 0}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 10}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 10}}, {Type: vg.LineComp, Pos: vg.Point{X: 100, Y: 0}}, {Type: vg.LineComp, Pos: vg.Point{X: 80, Y: 0}}, }, }, &recorder.SetColor{}, &recorder.FillString{ Font: string("Times-Roman"), Size: fontSize, Point: vg.Point{X: 70.09452736318407, Y: 0.189054726368159}, String: "D", }, } if !reflect.DeepEqual(got, want) { t.Errorf("unexpected legend actions:\ngot:\n%s\nwant:\n%s", formatActions(got), formatActions(want)) t.Errorf("First diff:\n%s", printFirstDiff(got, want)) } } func formatActions(actions []recorder.Action) string { var buf bytes.Buffer for _, a := range actions { fmt.Fprintf(&buf, "\t%s\n", a.Call()) } return buf.String() } // printFirstDiff prints the first line that is different between two actions. func printFirstDiff(got, want []recorder.Action) string { var buf bytes.Buffer for i, g := range got { if i >= len(want) { fmt.Fprintf(&buf, "line %d:\n\tgot: %s\n\twant is empty", i, g.Call()) break } w := want[i] if w.Call() != g.Call() { fmt.Fprintf(&buf, "line %d:\n\tgot: %s\n\twant: %s", i, g.Call(), w.Call()) break } } if len(want) > len(got) { fmt.Fprintf(&buf, "line %d:\n\twant: %s\n\tgot is empty", len(got), want[len(got)].Call()) } return buf.String() }