Пример #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
}
Пример #2
0
// AddLines adds Line 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 dashes
// shape via the Color and Dashes 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 AddLines(plt *plot.Plot, vs ...interface{}) error {
	var ps []plot.Plotter
	names := make(map[*plotter.Line]string)
	name := ""
	var i int
	for _, v := range vs {
		switch t := v.(type) {
		case string:
			name = t

		case plotter.XYer:
			l, err := plotter.NewLine(t)
			if err != nil {
				return err
			}
			l.Color = Color(i)
			l.Dashes = Dashes(i)
			i++
			ps = append(ps, l)
			if name != "" {
				names[l] = name
				name = ""
			}

		default:
			panic(fmt.Sprintf("AddLines handles strings and plotter.XYers, got %T", t))
		}
	}
	plt.Add(ps...)
	for p, n := range names {
		plt.Legend.Add(n, p)
	}
	return nil
}
Пример #3
0
func (s *Plots) DrawTps() {
	p, err := plot.New()
	if err != nil {
		panic(err)
	}

	p.Title.Text = "Response Time"
	p.X.Label.Text = "Time (seconds)"
	p.Y.Label.Text = "Response time (ms)"

	p.Add(plotter.NewGrid())

	// Make a line plotter and set its style.
	l, err := plotter.NewLine(s.intervalsTps)
	if err != nil {
		panic(err)
	}

	l.LineStyle.Color = color.RGBA{B: 255, A: 255}

	p.Add(l)
	p.Legend.Add("line", l)

	// Save the plot to a PNG file.
	if err := p.Save(128*vg.Inch, 32*vg.Inch, "tps.svg"); err != nil {
		panic(err)
	}
}
Пример #4
0
func DataTableToPng(b *bytes.Buffer, dt *db.DataTable, title string, width, height float64, xLabel string) error {
	p, err := plot.New()
	if err != nil {
		return err
	}

	p.Title.Text = title
	p.X.Label.Text = xLabel
	p.Y.Label.Text = "msec" // TODO: Fix this.

	// TODO: need new ticker function to handle equalX (while keeping xLabel as selected)
	if xLabel == common.TimeName {
		p.X.Tick.Marker = TimeTicks
	}
	p.Legend.Top = true

	numColumns := len(dt.ColumnNames)
	lines := make([]plotter.XYs, numColumns-1) // Skip X column.

	for _, dRow := range dt.Data {
		xp := (*dRow)[0]
		if xp != nil {
			for col := 1; col < numColumns; col++ { // Skip X column.
				yp := (*dRow)[col]
				if yp != nil {
					lines[col-1] = append(lines[col-1], struct{ X, Y float64 }{X: *xp, Y: *yp})
				}
			}
		}
	}

	colorList := getColors(numColumns - 1) // Skip X column.

	for i, line := range lines {
		columnName := dt.ColumnNames[i+1]
		l, err := plotter.NewLine(line)
		if err != nil {
			return err
		}
		if strings.Index(columnName, common.RegressNamePrefix) == 0 { // If regression value.
			l.LineStyle.Color = color.RGBA{255, 0, 0, 255}
			l.LineStyle.Width = vg.Points(2.0)
		} else {
			l.LineStyle.Color = colorList[i]
			l.LineStyle.Width = vg.Points(1.5)
		}
		p.Add(l)
		p.Legend.Add(columnName, l)
	}

	tPng := time.Now()
	drawPng(b, p, width, height)
	glog.V(3).Infof("PERF: makePng time: %v", time.Now().Sub(tPng))
	return nil
}
Пример #5
0
func (p *Plot) Write(f string) error {
	for i, ln := range p.lines {
		l, _ := plotter.NewLine(ln.pts)
		l.LineStyle.Width = vg.Points(2)
		l.LineStyle.Color = colors[i%len(colors)]
		p.p.Legend.Add(ln.name, l)
		p.p.Add(l)
	}

	return p.p.Save(10*vg.Inch, 10*vg.Inch, f)
}
Пример #6
0
func lines(w vg.Length) (*plot.Plot, error) {
	p, err := plot.New()
	if err != nil {
		return nil, err
	}

	pts := plotter.XYs{{0, 0}, {0, 1}, {1, 0}, {1, 1}}
	line, err := plotter.NewLine(pts)
	line.Width = w
	if err != nil {
		return nil, err
	}
	p.Add(line)

	return p, nil
}
Пример #7
0
// AddStackedAreaPlots adds stacked area plot plotters to a plot.
// The variadic arguments must be either strings
// or plotter.Valuers.  Each valuer adds a stacked area
// plot to the plot below the stacked area plots added
// before it.  If a plotter.Valuer is immediately
// preceeded by a string then the string value is used to
// label the legend.
// Plots should be added in order of tallest to shortest,
// because they will be drawn in the order they are added
// (i.e. later plots will be painted over earlier plots).
//
// If an error occurs then none of the plotters are added
// to the plot, and the error is returned.
func AddStackedAreaPlots(plt *plot.Plot, xs plotter.Valuer, vs ...interface{}) error {
	var ps []plot.Plotter
	names := make(map[*plotter.Line]string)
	name := ""
	var i int

	for _, v := range vs {
		switch t := v.(type) {
		case string:
			name = t

		case plotter.Valuer:
			if xs.Len() != t.Len() {
				return errors.New("X/Y length mismatch")
			}

			// Make a line plotter and set its style.
			l, err := plotter.NewLine(combineXYs{xs: xs, ys: t})
			if err != nil {
				return err
			}

			l.LineStyle.Width = vg.Points(0)
			color := Color(i)
			i++
			l.ShadeColor = &color

			ps = append(ps, l)

			if name != "" {
				names[l] = name
				name = ""
			}

		default:
			panic(fmt.Sprintf("AddStackedAreaPlots handles strings and plotter.Valuers, got %T", t))
		}
	}

	plt.Add(ps...)
	for p, n := range names {
		plt.Legend.Add(n, p)
	}

	return nil
}
Пример #8
0
func (plt *plttr) addDiscrete(name string, sig Discrete) {
	// TODO there appears to be a bug in gonum plot where certain
	// dashed lines for a particular result will not render correctly.
	// Raw calls of plotutil are just tossed in here for now and avoids
	// dashed lines.
	l, err := plotter.NewLine(xyer([]float64(sig)))
	if err != nil {
		panic(err)
	}
	l.Color = plotutil.Color(plt.nlines)
	// l.Dashes = plotutil.Dashes(plt.nlines)

	plt.Add(l)
	plt.Legend.Add(name, l)

	plt.nlines++
}
Пример #9
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 = draw.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
}
Пример #10
0
func (s *Plots) DrawLats() {
	p, err := plot.New()
	if err != nil {
		panic(err)
	}

	p.Title.Text = "Response Time"
	p.X.Label.Text = "Time (seconds)"
	p.Y.Label.Text = "Response time (ms)"

	p.Add(plotter.NewGrid())

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

	// Make a line plotter and set its style.
	l, err := plotter.NewLine(s.intervalLats)
	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}

	p.Add(scatter, l)
	p.Legend.Add("scatter", scatter)
	p.Legend.Add("line", l)

	// Save the plot to a PNG file.
	if err := p.Save(64*vg.Inch, 24*vg.Inch, "points.png"); err != nil {
		panic(err)
	}
}
Пример #11
0
func createBoard(somePlot *plot.Plot) {
	pts := make(plotter.XYs, 35)

	pts[0].X = float64(8.5)
	pts[0].Y = float64(.5)
	pts[1].X = float64(.5)
	pts[1].Y = float64(.5)

	pts[2].X = float64(.5)
	pts[2].Y = float64(1.5)
	pts[3].X = float64(8.5)
	pts[3].Y = float64(1.5)

	pts[4].X = float64(8.5)
	pts[4].Y = float64(2.5)
	pts[5].X = float64(.5)
	pts[5].Y = float64(2.5)

	pts[6].X = float64(.5)
	pts[6].Y = float64(3.5)
	pts[7].X = float64(8.5)
	pts[7].Y = float64(3.5)

	pts[8].X = float64(8.5)
	pts[8].Y = float64(4.5)
	pts[9].X = float64(.5)
	pts[9].Y = float64(4.5)

	pts[10].X = float64(.5)
	pts[10].Y = float64(5.5)
	pts[11].X = float64(8.5)
	pts[11].Y = float64(5.5)

	pts[12].X = float64(8.5)
	pts[12].Y = float64(6.5)
	pts[13].X = float64(.5)
	pts[13].Y = float64(6.5)

	pts[14].X = float64(.5)
	pts[14].Y = float64(7.5)
	pts[15].X = float64(8.5)
	pts[15].Y = float64(7.5)

	pts[16].X = float64(8.5)
	pts[16].Y = float64(8.5)
	pts[17].X = float64(.5)
	pts[17].Y = float64(8.5)

	pts[18].X = float64(.5)
	pts[18].Y = float64(.5)

	pts[19].X = float64(1.5)
	pts[19].Y = float64(.5)
	pts[20].X = float64(1.5)
	pts[20].Y = float64(8.5)

	pts[21].X = float64(2.5)
	pts[21].Y = float64(8.5)
	pts[22].X = float64(2.5)
	pts[22].Y = float64(.5)

	pts[23].X = float64(3.5)
	pts[23].Y = float64(.5)
	pts[24].X = float64(3.5)
	pts[24].Y = float64(8.5)

	pts[25].X = float64(4.5)
	pts[25].Y = float64(8.5)
	pts[26].X = float64(4.5)
	pts[26].Y = float64(.5)

	pts[27].X = float64(5.5)
	pts[27].Y = float64(.5)
	pts[28].X = float64(5.5)
	pts[28].Y = float64(8.5)

	pts[29].X = float64(6.5)
	pts[29].Y = float64(8.5)
	pts[30].X = float64(6.5)
	pts[30].Y = float64(.5)

	pts[31].X = float64(7.5)
	pts[31].Y = float64(.5)
	pts[32].X = float64(7.5)
	pts[32].Y = float64(8.5)

	pts[33].X = float64(8.5)
	pts[33].Y = float64(8.5)
	pts[34].X = float64(8.5)
	pts[34].Y = float64(.5)

	lines, _ := plotter.NewLine(pts)
	lines.LineStyle.Width = vg.Points(1)
	lines.LineStyle.Color = color.RGBA{R: 0, B: 0, A: 255}

	myPlot.Add(lines)
}
Пример #12
0
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, &param)
		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)
	}

}
Пример #13
0
func TestPersistency(t *testing.T) {
	// Get some random points
	rand.Seed(0) // The default random seed is 1.
	n := 15
	scatterData := randomPoints(n)
	lineData := randomPoints(n)
	linePointsData := randomPoints(n)

	p, err := plot.New()
	if err != nil {
		t.Fatalf("error creating plot: %v\n", err)
	}

	p.Title.Text = "Plot Example"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	// Use a custom tick marker function that computes the default
	// tick marks and re-labels the major ticks with commas.
	p.Y.Tick.Marker = commaTicks{}

	// 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 = draw.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)

	// Save the plot to a PNG file.
	err = p.Save(4, 4, "test-persistency.png")
	if err != nil {
		t.Fatalf("error saving to PNG: %v\n", err)
	}
	defer os.Remove("test-persistency.png")

	buf := new(bytes.Buffer)
	enc := gob.NewEncoder(buf)
	err = enc.Encode(p)
	if err != nil {
		t.Fatalf("error gob-encoding plot: %v\n", err)
	}

	// TODO(sbinet): impl. BinaryMarshal for plot.Plot and vg.Font
	// {
	// 	dec := gob.NewDecoder(buf)
	// 	var p plot.Plot
	// 	err = dec.Decode(&p)
	// 	if err != nil {
	// 		t.Fatalf("error gob-decoding plot: %v\n", err)
	// 	}
	// 	// Save the plot to a PNG file.
	// 	err = p.Save(4, 4, "test-persistency-readback.png")
	// 	if err != nil {
	// 		t.Fatalf("error saving to PNG: %v\n", err)
	// 	}
	//  defer os.Remove("test-persistency-readback.png")
	// }

}
Пример #14
0
func main() {
	ifname := flag.String("f", "output.csv", "input CSV file to analyze")
	ofname := flag.String("o", "output.png", "output PNG file")

	flag.Parse()

	log.SetPrefix("snfusion-plot: ")
	log.SetFlags(0)

	f, err := os.Open(*ifname)
	if err != nil {
		log.Fatalf("error opening %s: %v\n", *ifname, err)
	}
	defer f.Close()

	var engine sim.Engine
	var hdr []byte
	{
		scanner := bufio.NewScanner(f)
		for scanner.Scan() {
			data := scanner.Bytes()
			if !bytes.HasPrefix(data, []byte("#")) {
				break
			}
			if !bytes.HasPrefix(data, sim.HeaderCSV) {
				continue
			}
			hdr = make([]byte, len(data)-len(sim.HeaderCSV))
			copy(hdr, data[len(sim.HeaderCSV):])
			break
		}
		err = scanner.Err()
		if err == io.EOF {
			err = nil
		}
		if err != nil {
			log.Fatalf("error peeking at meta-data: %v\n", err)
		}
		if len(hdr) == 0 {
			log.Fatalf("could not find meta-data in file %s\n",
				*ifname,
			)
		}
		_, err = f.Seek(0, 0)
		if err != nil {
			log.Fatalf("error rewinding input file %s: %v\n",
				*ifname,
				err,
			)
		}
	}
	err = json.Unmarshal(hdr, &engine)
	if err != nil {
		log.Fatalf("error reading meta-data: %v\n", err)
	}

	log.Printf("plotting...\n")
	log.Printf("NumIters:   %d\n", engine.NumIters)
	log.Printf("NumCarbons: %v\n", engine.NumCarbons)
	log.Printf("Seed:       %d\n", engine.Seed)
	log.Printf("Nuclei:     %v\n", engine.Population)

	r := csv.NewReader(f)
	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.Fatalf("error reading data: %v\n", err)
	}

	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(phi)

	// Save the plot to a PNG file.
	if err := p.Save(figX, figY, *ofname); err != nil {
		panic(err)
	}
}