func PlotGraph(title string, timestamps []int64, w io.Writer) error { p, err := plot.New() if err != nil { return err } p.Title.Text = title p.X.Label.Text = "Time" p.Y.Label.Text = "Number of stars" p.Y.Min = 0 points := make(plotter.XYs, len(timestamps)) for i, timestamp := range timestamps { points[i].X = float64(timestamp) points[i].Y = float64(i + 1) } plotutil.AddLinePoints(p, "Stars", points) c := vgimg.New(4*vg.Inch, 4*vg.Inch) cpng := vgimg.PngCanvas{c} p.Draw(draw.New(cpng)) if _, err := cpng.WriteTo(w); err != nil { return err } return nil }
func TestTextGrobs(t *testing.T) { // Output file, err := os.Create("text.png") if err != nil { t.Fatalf("%", err) } pngCanvas := vgimg.PngCanvas{Canvas: vgimg.New(10*vg.Inch, 8*vg.Inch)} vg.Initialize(pngCanvas) allVP := Viewport{ X0: 0, Y0: 0, Width: 10 * vg.Inch, Height: 8 * vg.Inch, Canvas: pngCanvas, } bg := GrobRect{xmin: 0, ymin: 0, xmax: 1, ymax: 1, fill: BuiltinColors["gray60"]} bg.Draw(allVP) gridVP := allVP.Sub(0.1, 0.1, 0.35, 0.35) drawTextGrid(gridVP, 0) gridVP = allVP.Sub(0.55, 0.1, 0.35, 0.35) drawTextGrid(gridVP, 45./180*math.Pi) gridVP = allVP.Sub(0.1, 0.55, 0.35, 0.35) drawTextGrid(gridVP, 135./180*math.Pi) gridVP = allVP.Sub(0.55, 0.55, 0.35, 0.35) drawTextGrid(gridVP, 90./180*math.Pi) pngCanvas.WriteTo(file) file.Close() }
// WritePNG renders plot to a png file of size width x height. func (p *Plot) WritePNG(filename string, width, height vg.Length) error { file, err := os.Create(filename) if err != nil { return err } defer file.Close() canvas := vgimg.PngCanvas{Canvas: vgimg.New(width, height)} canvas.Translate(-width/2, -height/2) p.DumpTo(canvas, width, height) canvas.WriteTo(file) return nil }
func TestIndividualSteps(t *testing.T) { aes := AesMapping{ "x": "Height", "y": "Weight", } plot, err := NewPlot(measurement, aes) if err != nil { t.Fatalf("Unxpected error: %s", err) } // // Add layers to plot // rawData := Layer{ Name: "Raw Data", Stat: nil, // identity Geom: GeomPoint{ Style: AesMapping{ "color": "red", "shape": "diamond", }}} plot.Layers = append(plot.Layers, &rawData) linReg := Layer{ Name: "Linear regression", Stat: &StatLinReq{}, Geom: GeomABLine{ Style: AesMapping{ "color": "green", "linetype": "dashed", }, }, // StatLinReq produces intercept/slope suitable for GeomABLine GeomMapping: nil, } plot.Layers = append(plot.Layers, &linReg) ageLabel := Layer{ Name: "Age Label", DataMapping: AesMapping{ "value": "Age", }, Stat: &StatLabel{Format: "%.0f years"}, Geom: GeomText{ Style: AesMapping{ "color": "blue", "angle": "0.7", // "45°", // TODO: parsing ° fails "family": "Helvetica", "size": "10", // TODO: should come from DefaultTheme }, }, GeomMapping: nil, } plot.Layers = append(plot.Layers, &ageLabel) histogram := Layer{ Name: "Histogram", DataMapping: AesMapping{"y": ""}, // clear mapping of y to Height Stat: &StatBin{Drop: true}, StatMapping: AesMapping{ "y": "count", }, Geom: GeomBar{ Style: AesMapping{ "fill": "gray50", }, }, } plot.Layers = append(plot.Layers, &histogram) // Set up the one panel. plot.CreatePanels() if len(plot.Panels) != 1 { t.Fatalf("Got %d panel rows, expected 1.", len(plot.Panels)) } if len(plot.Panels[0]) != 1 { t.Fatalf("Got %d panel cols, expected 1.", len(plot.Panels[0])) } panel := plot.Panels[0][0] // 2. PrepareData panel.PrepareData() if fields := panel.Layers[0].Data.FieldNames(); !same(fields, []string{"x", "y"}) { t.Errorf("Layer 0 DF has fields %v", fields) } if fields := panel.Layers[1].Data.FieldNames(); !same(fields, []string{"x", "y"}) { t.Errorf("Layer 1 DF has fields %v", fields) } if sx, ok := panel.Scales["x"]; !ok { t.Errorf("Missing x scale") } else { if sx.Discrete || sx.Transform != &IdentityScale || sx.Aesthetic != "x" { t.Errorf("Scale x = %+v", sx) } } if sy, ok := panel.Scales["y"]; !ok { t.Errorf("Missing y scale") } else { if sy.Discrete || sy.Transform != &IdentityScale || sy.Aesthetic != "y" { t.Errorf("Scale y = %+v", sy) } } // 3. ComputeStatistics panel.ComputeStatistics() // No statistic on layer 0: data field is unchanges if fields := panel.Layers[0].Data.FieldNames(); !same(fields, []string{"x", "y"}) { t.Errorf("Layer 0 DF has fields %v", fields) } // StatLinReq produces intercept and slope if fields := panel.Layers[1].Data.FieldNames(); !same(fields, []string{"intercept", "slope", "interceptErr", "slopeErr"}) { t.Errorf("Layer 1 DF has fields %v", fields) } data := panel.Layers[1].Data if data.N != 1 { t.Errorf("Got %d data in lin req df.", panel.Layers[1].Data.N) } t.Logf("Intercept = %.2f Slope = %.2f", data.Columns["intercept"].Data[0], data.Columns["slope"].Data[0]) // StatLabels produces labels if fields := panel.Layers[2].Data.FieldNames(); !same(fields, []string{"x", "y", "text"}) { t.Errorf("Layer 2 %q has fields %v", panel.Layers[3].Name, fields) } data = panel.Layers[2].Data if data.N != 20 { t.Errorf("Got %d data in label df.", panel.Layers[2].Data.N) } // StatBin produces bins if fields := panel.Layers[3].Data.FieldNames(); !same(fields, []string{"x", "count", "ncount", "density", "ndensity"}) { t.Errorf("Layer 3 %q has fields %v", panel.Layers[3].Name, fields) } data = panel.Layers[3].Data if data.N != 11 { t.Errorf("Got %d data in binned df.", panel.Layers[3].Data.N) } // 4. Wireing panel.WireStatToGeom() for a, s := range panel.Scales { fmt.Printf("====== Scale %s %q ========\n", a, s.Name) fmt.Printf("%s\n", s.String()) } // 5. Test Construct ConstructGeoms. This shouldn't change much as // GeomABLine doesn't reparametrize and we don't do position adjustments. panel.ConstructGeoms() // 6. FinalizeScales panel.FinalizeScales() // Only x and y are set up if sx, ok := panel.Scales["x"]; !ok { t.Errorf("Missing x scale") } else { if sx.Pos == nil { t.Errorf("Missing Pos for x scale.") } if sx.DomainMin > 1.62 || sx.DomainMax < 1.95 { t.Errorf("Bad training: %f %f", sx.DomainMin, sx.DomainMax) } } fmt.Printf("%s\n", panel.Scales["x"]) fmt.Printf("%s\n", panel.Scales["y"]) // 7. Render Geoms to Grobs using scales (Step7). panel.RenderGeoms() fmt.Println("Layer 0, raw data") for _, grob := range panel.Layers[0].Grobs { fmt.Println(" ", grob.String()) } fmt.Println("Layer 1, linear regression") for _, grob := range panel.Layers[1].Grobs { fmt.Println(" ", grob.String()) } fmt.Println("Layer 2, labels") for _, grob := range panel.Layers[2].Grobs { fmt.Println(" ", grob.String()) } fmt.Println("Layer 3, histogram") for _, grob := range panel.Layers[3].Grobs { fmt.Println(" ", grob.String()) } // Output pngCanvas := vgimg.PngCanvas{Canvas: vgimg.New(800, 600)} pngCanvas.Translate(-400, -300) vp := Viewport{ X0: 50, Y0: 50, Width: 700, Height: 500, Canvas: pngCanvas, } file, err := os.Create("example.png") if err != nil { t.Fatalf("%", err) } panel.Draw(vp, true, true) if false { fmt.Println("Layer 0, raw data") for _, grob := range panel.Layers[0].Grobs { grob.Draw(vp) } fmt.Println("Layer 1, linear regression") for _, grob := range panel.Layers[1].Grobs { grob.Draw(vp) } fmt.Println("Layer 2, labels") for _, grob := range panel.Layers[2].Grobs { grob.Draw(vp) } fmt.Println("Layer 3, histogram") for _, grob := range panel.Layers[3].Grobs { grob.Draw(vp) } } pngCanvas.WriteTo(file) file.Close() }
func drawPng(b *bytes.Buffer, p *plot.Plot, width, height float64) { w, h := vg.Inches(width), vg.Inches(height) c := vgimg.PngCanvas{Canvas: vgimg.New(w, h)} p.Draw(plot.MakeDrawArea(c)) c.WriteTo(b) }
func (c *client) run() { var err error dir, err := ioutil.TempDir("", "snfusion-web-") if err != nil { log.Printf("error creating temporary directory: %v\n", err) return } defer func() { c.srv.unregister <- c c.ws.Close() c.srv = nil os.RemoveAll(dir) }() type params struct { ID int `json:"id"` NumIters int `json:"num_iters"` NumCarbons float64 `json:"num_carbons"` Seed int64 `json:"seed"` } type genReply struct { ID int `json:"id"` Stage string `json:"stage"` Err error `json:"err"` Msg string `json:"msg"` Engine sim.Engine `json:"engine"` } type plotReply struct { ID int `json:"id"` Stage string `json:"stage"` Err error `json:"err"` SVG string `json:"svg"` } type zipReply struct { ID int `json:"id"` Stage string `json:"stage"` Err error `json:"err"` Href string `json:"href"` } for { param := params{ NumIters: 100000, NumCarbons: 60, Seed: 1234, } log.Printf("waiting for simulation parameters...\n") err = websocket.JSON.Receive(c.ws, ¶m) if err != nil { log.Printf("error rcv: %v\n", err) return } id := param.ID msgbuf := new(bytes.Buffer) msg := log.New(msgbuf, "snfusion-sim: ", 0) engine := sim.Engine{ NumIters: param.NumIters, NumCarbons: param.NumCarbons, Seed: param.Seed, } engine.SetLogger(msg) log.Printf("processing... %#v\n", engine) csvbuf := new(bytes.Buffer) errc := make(chan error) ticker := time.NewTicker(1 * time.Second) go func() { errc <- engine.Run(csvbuf) ticker.Stop() }() err = <-errc if err != nil { log.Printf("error: %v\n", err) _ = websocket.JSON.Send(c.ws, genReply{ ID: id, Err: err, Engine: engine, Stage: "gen-done", Msg: msgbuf.String(), }) return } err = websocket.JSON.Send(c.ws, genReply{ ID: id, Err: err, Engine: engine, Stage: "gen-done", Msg: msgbuf.String(), }) if err != nil { log.Printf("error sending data: %v\n", err) return } csvdata := make([]byte, len(csvbuf.Bytes())) copy(csvdata, csvbuf.Bytes()) log.Printf("running post-processing...\n") r := csv.NewReader(csvbuf) r.Comma = ';' r.Comment = '#' table := make([]plotter.XYs, len(engine.Population)) for i := range table { table[i] = make(plotter.XYs, engine.NumIters+1) } for ix := 0; ix < engine.NumIters+1; ix++ { var text []string text, err = r.Read() if err != nil { break } for i := range engine.Population { table[i][ix].X = float64(ix) table[i][ix].Y = float64(atoi(text[i])) } } if err == io.EOF { err = nil } if err != nil { log.Printf("error reading data: %v\n", err) return } p, err := plot.New() if err != nil { panic(err) } p.Title.Text = fmt.Sprintf( "Time evolution of nuclei C%v-O%v (seed=%d)", engine.NumCarbons, 100-engine.NumCarbons, engine.Seed, ) p.X.Label.Text = "Iteration number" p.Y.Label.Text = "Atomic mass of nuclei" for i, n := range engine.Population { line, err := plotter.NewLine(table[i]) if err != nil { log.Fatalf( "error adding data points for nucleus %v: %v\n", n, err, ) } line.LineStyle.Color = col(n) line.LineStyle.Width = vg.Points(1) p.Add(line) p.Legend.Add(label(n), line) } p.Add(plotter.NewGrid()) p.Legend.Top = true p.Legend.XOffs = -1 * vg.Centimeter figX := 25 * vg.Centimeter figY := figX / vg.Length(math.Phi) // Create a Canvas for writing SVG images. canvas := vgsvg.New(figX, figY) // Draw to the Canvas. p.Draw(draw.New(canvas)) outsvg := new(bytes.Buffer) _, err = canvas.WriteTo(outsvg) if err != nil { log.Printf("error svg: %v\n", err) return } err = websocket.JSON.Send(c.ws, plotReply{ ID: id, Err: err, SVG: outsvg.String(), Stage: "plot-done", }) if err != nil { log.Printf("error sending data: %v\n", err) return } pngcanvas := vgimg.PngCanvas{Canvas: vgimg.New(figX, figY)} p.Draw(draw.New(pngcanvas)) outpng := new(bytes.Buffer) _, err = pngcanvas.WriteTo(outpng) if err != nil { log.Printf("error png: %v\n", err) return } href := filepath.Join(dir, fmt.Sprintf("output-%d.zip", id)) zipf, err := os.Create(href) if err != nil { log.Printf("error creating zip file: %v\n", err) } defer zipf.Close() zipw := zip.NewWriter(zipf) defer zipw.Close() for _, file := range []struct { Name string Body []byte }{ {"output.csv", csvdata}, {"output.png", outpng.Bytes()}, } { ff, err := zipw.Create(file.Name) if err != nil { log.Printf("error creating zip content %v: %v\n", file.Name, err) return } _, err = ff.Write(file.Body) if err != nil { log.Printf("error writing zip content %v: %v\n", file.Name, err) return } } err = zipw.Close() if err != nil { log.Printf("error closing zip-writer: %v\n", err) return } err = zipf.Close() if err != nil { log.Printf("error closing zip-file: %v\n", err) return } err = websocket.JSON.Send(c.ws, zipReply{ ID: id, Err: err, Href: href, Stage: "zip-done", }) if err != nil { log.Printf("error sending zip: %v\n", err) return } log.Printf("saved report under %v\n", href) } }
func TestGraphicGrobs(t *testing.T) { // Output file, err := os.Create("grobs.png") if err != nil { t.Fatalf("%", err) } pngCanvas := vgimg.PngCanvas{Canvas: vgimg.New(10*vg.Inch, 8*vg.Inch)} vg.Initialize(pngCanvas) pngCanvas.Translate(-5*vg.Inch, -4*vg.Inch) dot := func(x, y float64) { pngCanvas.Push() pngCanvas.SetColor(BuiltinColors["red"]) pngCanvas.SetLineWidth(5) var p vg.Path xr := vg.Length(x) * vg.Inch yr := vg.Length(y) * vg.Inch p.Move(xr-5, yr-5) p.Line(xr-5, yr+5) p.Line(xr+5, yr+5) p.Line(xr+5, yr-5) p.Close() pngCanvas.Fill(p) pngCanvas.Pop() } dot(0, 0) dot(5, 0) dot(0, 4) allVP := Viewport{ X0: 0, Y0: 0, Width: 10 * vg.Inch, Height: 8 * vg.Inch, Canvas: pngCanvas, } innerVP := allVP.Sub(0.05, 0.05, 0.9, 0.9) bg := GrobRect{xmin: 0, ymin: 0, xmax: 1, ymax: 1, fill: BuiltinColors["gray80"]} bg.Draw(innerVP) cols := []string{"red", "green", "blue", "cyan", "magenta", "yellow", "white", "gray", "black"} // Draw points in all shapes, three sizes and all builtin colors. points := []Grob{} x, y := 0.1, 0.1 for shape := DotPoint; shape <= StarPoint; shape++ { for size := 2; size < 7; size += 2 { y = 0.05 for _, col := range cols { g := GrobPoint{ x: x, y: y, size: float64(size), shape: shape, color: BuiltinColors[col], } points = append(points, g) y += 0.035 } x += 0.021 } } x, y = 0.02, 0.05 for _, col := range cols { g := GrobText{ x: x, y: y, text: col, size: 10, color: BuiltinColors[col], vjust: 0.5, hjust: 0, } points = append(points, g) y += 0.035 } x, y = 0.121, 0.36 for shape := DotPoint; shape <= StarPoint; shape++ { dy := float64(shape%2) * 0.015 g := GrobText{ x: x, y: y + dy, text: shape.String(), size: 10, color: BuiltinColors["black"], vjust: 0.5, hjust: 0.5, } points = append(points, g) x += 3 * 0.021 } for _, grob := range points { grob.Draw(innerVP) } // Draw lines with different styles and widths. lines := []Grob{} x, y = 0.1, 0.45 for lt := SolidLine; lt <= TwodashLine; lt++ { x = 0.1 for size := 1; size < 8; size += 2 { g := GrobLine{ x0: x, y0: y, x1: x + 0.18, y1: y, size: float64(size), linetype: lt, color: BuiltinColors["black"], } lines = append(lines, g) x += 0.22 } y += 0.04 } for _, grob := range lines { grob.Draw(innerVP) } // Draw rectangles rectVP := innerVP.Sub(0.1, 0.7, 0.4, 0.3) rect := []Grob{} bgr := GrobRect{xmin: 0, ymin: 0, xmax: 1, ymax: 1, fill: BuiltinColors["gray40"]} bgr.Draw(rectVP) x, y = 0.0, 0.0 w, h := 0.5, 0.5 for _, col := range cols { g := GrobRect{ xmin: x, ymin: y, xmax: x + w, ymax: y + h, fill: BuiltinColors[col], } rect = append(rect, g) x += w y += h w /= 2 h /= 2 } for _, grob := range rect { grob.Draw(rectVP) } // Draw path pathVP := innerVP.Sub(0.55, 0.7, 0.4, 0.3) bgp := GrobRect{xmin: 0, ymin: 0, xmax: 1, ymax: 1, fill: BuiltinColors["gray"]} bgp.Draw(pathVP) sin := make([]struct{ x, y float64 }, 50) for i := range sin { k := float64(i) / float64(len(sin)-1) x = k * 2 * math.Pi println(float64(i)/float64(len(sin)), x, math.Sin(x)) y = 0.4 * math.Sin(x) sin[i].x = k sin[i].y = 0.55 + y } g := GrobPath{ points: sin, size: 4, linetype: SolidLine, color: BuiltinColors["white"], } g.Draw(pathVP) cos := make([]struct{ x, y float64 }, 25) for i := range cos { k := float64(i) / float64(len(cos)-1) x = k * 4 * math.Pi y = 0.3 * math.Cos(x) cos[i].x = k cos[i].y = 0.45 + y } g = GrobPath{ points: cos, size: 2, linetype: DottedLine, color: BuiltinColors["green"], } g.Draw(pathVP) pngCanvas.WriteTo(file) file.Close() }