// Copyright ©2016 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 plotter_test import ( "fmt" "image/color" "log" "os" "gonum.org/v1/plot" "gonum.org/v1/plot/plotter" "gonum.org/v1/plot/text" "gonum.org/v1/plot/vg" "gonum.org/v1/plot/vg/draw" "gonum.org/v1/plot/vg/vgimg" ) // ExampleSankey_sample creates a simple sankey diagram. // The output can be found at https://github.com/gonum/plot/blob/master/plotter/testdata/sankeySimple_golden.png. func ExampleSankey_simple() { p := plot.New() // Define the stock categories const ( treeType int = iota consumer fate ) categoryLabels := []string{"Tree type", "Consumer", "Fate"} flows := []plotter.Flow{ { SourceCategory: treeType, SourceLabel: "Large", ReceptorCategory: consumer, ReceptorLabel: "Mohamed", Value: 5, }, { SourceCategory: treeType, SourceLabel: "Small", ReceptorCategory: consumer, ReceptorLabel: "Mohamed", Value: 2, }, { SourceCategory: treeType, SourceLabel: "Large", ReceptorCategory: consumer, ReceptorLabel: "Sofia", Value: 3, }, { SourceCategory: treeType, SourceLabel: "Small", ReceptorCategory: consumer, ReceptorLabel: "Sofia", Value: 1, }, { SourceCategory: treeType, SourceLabel: "Large", ReceptorCategory: consumer, ReceptorLabel: "Wei", Value: 6, }, { SourceCategory: consumer, SourceLabel: "Mohamed", ReceptorCategory: fate, ReceptorLabel: "Eaten", Value: 6, }, { SourceCategory: consumer, SourceLabel: "Mohamed", ReceptorCategory: fate, ReceptorLabel: "Waste", Value: 1, }, { SourceCategory: consumer, SourceLabel: "Sofia", ReceptorCategory: fate, ReceptorLabel: "Eaten", Value: 3, }, { SourceCategory: consumer, SourceLabel: "Sofia", ReceptorCategory: fate, ReceptorLabel: "Waste", Value: 0.5, // An unbalanced flow }, { SourceCategory: consumer, SourceLabel: "Wei", ReceptorCategory: fate, ReceptorLabel: "Eaten", Value: 5, }, { SourceCategory: consumer, SourceLabel: "Wei", ReceptorCategory: fate, ReceptorLabel: "Waste", Value: 1, }, { SourceCategory: treeType, SourceLabel: "Large", ReceptorCategory: fate, ReceptorLabel: "Waste", Value: 1, }, { SourceCategory: treeType, SourceLabel: "Small", ReceptorCategory: fate, ReceptorLabel: "Waste", Value: 0.3, }, } sankey, err := plotter.NewSankey(flows...) if err != nil { log.Panic(err) } p.Add(sankey) p.Y.Label.Text = "Number of apples" p.NominalX(categoryLabels...) err = p.Save(vg.Points(300), vg.Points(180), "testdata/sankeySimple.png") if err != nil { log.Panic(err) } } // ExampleSankey_grouped creates a sankey diagram with grouped flows. // The output can be found at https://github.com/gonum/plot/blob/master/plotter/testdata/sankeyGrouped_golden.png. func ExampleSankey_grouped() { p := plot.New() c := vgimg.New(vg.Points(300), vg.Points(180)) dc := draw.New(c) // Define the stock categories const ( treeType int = iota consumer fate ) categoryLabels := []string{"Tree type", "Consumer", "Fate"} flows := []plotter.Flow{ { SourceCategory: treeType, SourceLabel: "LargeLargeLargeLargeLargeLargeLargeLargeLarge", ReceptorCategory: consumer, ReceptorLabel: "Mohamed", Group: "Apples", Value: 5, }, { SourceCategory: treeType, SourceLabel: "LargeLargeLargeLargeLargeLargeLargeLargeLarge", ReceptorCategory: consumer, ReceptorLabel: "Mohamed", Group: "Dates", Value: 3, }, { SourceCategory: treeType, SourceLabel: "Small", ReceptorCategory: consumer, ReceptorLabel: "Mohamed", Group: "Lychees", Value: 2, }, { SourceCategory: treeType, SourceLabel: "LargeLargeLargeLargeLargeLargeLargeLargeLarge", ReceptorCategory: consumer, ReceptorLabel: "Sofia", Group: "Apples", Value: 3, }, { SourceCategory: treeType, SourceLabel: "LargeLargeLargeLargeLargeLargeLargeLargeLarge", ReceptorCategory: consumer, ReceptorLabel: "Sofia", Group: "Dates", Value: 4, }, { SourceCategory: treeType, SourceLabel: "Small", ReceptorCategory: consumer, ReceptorLabel: "Sofia", Group: "Apples", Value: 1, }, { SourceCategory: treeType, SourceLabel: "LargeLargeLargeLargeLargeLargeLargeLargeLarge", ReceptorCategory: consumer, ReceptorLabel: "Wei", Group: "Lychees", Value: 6, }, { SourceCategory: treeType, SourceLabel: "Small", ReceptorCategory: consumer, ReceptorLabel: "Wei", Group: "Apples", Value: 3, }, { SourceCategory: consumer, SourceLabel: "Mohamed", ReceptorCategory: fate, ReceptorLabel: "Eaten", Group: "Apples", Value: 4, }, { SourceCategory: consumer, SourceLabel: "Mohamed", ReceptorCategory: fate, ReceptorLabel: "Waste", Group: "Apples", Value: 1, }, { SourceCategory: consumer, SourceLabel: "Mohamed", ReceptorCategory: fate, ReceptorLabel: "Eaten", Group: "Dates", Value: 3, }, { SourceCategory: consumer, SourceLabel: "Mohamed", ReceptorCategory: fate, ReceptorLabel: "Waste", Group: "Lychees", Value: 2, }, { SourceCategory: consumer, SourceLabel: "Sofia", ReceptorCategory: fate, ReceptorLabel: "Eaten", Group: "Apples", Value: 4, }, { SourceCategory: consumer, SourceLabel: "Sofia", ReceptorCategory: fate, ReceptorLabel: "Eaten", Group: "Dates", Value: 3, }, { SourceCategory: consumer, SourceLabel: "Sofia", ReceptorCategory: fate, ReceptorLabel: "Waste", Group: "Dates", Value: 1, }, { SourceCategory: consumer, SourceLabel: "Wei", ReceptorCategory: fate, ReceptorLabel: "Eaten", Group: "Lychees", Value: 6, }, { SourceCategory: consumer, SourceLabel: "Wei", ReceptorCategory: fate, ReceptorLabel: "Eaten", Group: "Apples", Value: 2, }, { SourceCategory: consumer, SourceLabel: "Wei", ReceptorCategory: fate, ReceptorLabel: "Waste", Group: "Apples", Value: 1, }, { SourceCategory: treeType, SourceLabel: "LargeLargeLargeLargeLargeLargeLargeLargeLarge", ReceptorCategory: fate, ReceptorLabel: "Waste", Group: "Apples", Value: 1, }, { SourceCategory: treeType, SourceLabel: "LargeLargeLargeLargeLargeLargeLargeLargeLarge", ReceptorCategory: fate, ReceptorLabel: "Waste", Group: "Dates", Value: 1, }, { SourceCategory: treeType, SourceLabel: "Small", ReceptorCategory: fate, ReceptorLabel: "Waste", Group: "Lychees", Value: 0.3, }, } sankey, err := plotter.NewSankey(flows...) if err != nil { log.Panic(err) } // Here we specify the FLowStyle function to set the // colors of the different fruit groups. sankey.FlowStyle = func(group string) (color.Color, draw.LineStyle) { switch group { case "Lychees": return color.NRGBA{R: 242, G: 169, B: 178, A: 100}, sankey.LineStyle case "Apples": return color.NRGBA{R: 91, G: 194, B: 54, A: 100}, sankey.LineStyle case "Dates": return color.NRGBA{R: 112, G: 22, B: 0, A: 100}, sankey.LineStyle default: panic(fmt.Errorf("invalid group %s", group)) } } // Here we set the StockStyle function to give an example of // setting a custom style for one of the stocks. sankey.StockStyle = func(label string, category int) (string, text.Style, vg.Length, vg.Length, color.Color, draw.LineStyle) { if label == "Small" && category == treeType { // Here we demonstrate how to rotate the label text // and change the style of the stock bar. ts := sankey.TextStyle ts.Rotation = 0.0 ts.XAlign = draw.XRight ls := sankey.LineStyle ls.Color = color.White xOff := -sankey.StockBarWidth / 2 yOff := vg.Length(0) return "small", ts, xOff, yOff, color.Black, ls } if label == "LargeLargeLargeLargeLargeLargeLargeLargeLarge" && category == treeType { // Here we demonstrate how to replace a long label that doesn't fit // in the existing space with a shorter version. Note that because // we are not able to account for the difference between the overall // canvas size and the size of the plotting area here, if a label // was only slightly larger than the available space, it would not // be caught and replaced. min, max, err := sankey.StockRange(label, category) if err != nil { log.Panic(err) } _, yTr := p.Transforms(&dc) barHeight := yTr(max) - yTr(min) if sankey.TextStyle.Width(label) > barHeight { return "large", sankey.TextStyle, 0, 0, color.White, sankey.LineStyle } } return label, sankey.TextStyle, 0, 0, color.White, sankey.LineStyle } p.Add(sankey) p.Y.Label.Text = "Number of fruit pieces" p.NominalX(categoryLabels...) legendLabels, thumbs := sankey.Thumbnailers() for i, l := range legendLabels { t := thumbs[i] p.Legend.Add(l, t) } p.Legend.Top = true p.X.Max = 3.05 // give room for the legend // Add boundary boxes for debugging. p.Add(plotter.NewGlyphBoxes()) p.Draw(dc) pngimg := vgimg.PngCanvas{Canvas: c} f, err := os.Create("testdata/sankeyGrouped.png") if err != nil { log.Panic(err) } if _, err = pngimg.WriteTo(f); err != nil { log.Panic(err) } }