The other side of Go: draw by analyzing data

Original author: Anthony Starks
  • Transfer
Go is a universal programming language that is great for background tasks, but sometimes you may need to generate images based on incoming data. Go works great with creating visual objects. This post describes one of the methods for creating images (in particular vector graphics) based on data using the SVGo package .

The SVGo library does one single task: it generates SVG and gives it to io.Writer . I \ O package in Go allows you to output results using the necessary interface (standard output, files, network connections, web server).

For SVGo, high-level objects such as circles, rectangles, lines, polygons, and curves are paramount. Styles and attributes are secondary and apply as needed.



Reading, analysis, drawing objects.


Our example will include the following steps:
  • Defining the structure of incoming data
  • Reading data
  • Parsing and storing data structures
  • Image rendering


Here is a simple example that takes data from XML, draws primitive SVG objects from them, and returns them to standard output.
You need to understand that for your own data you can describe the structure as you like, at the same time if you get data from the API of third-party services, you will need to describe the data structure for each separately.

Example input:
This is smallThis is mediumThis is large


First, we determine the structure of the input data. You can see the correspondence between elements and attributes in Go. For example, the struct “thing” contains Top and Left parameters that specify the indentation, sep parameter which sets the distance between the elements and Items parameter which is a list and will contain nested elements. In turn, each of the nested elements also has a set of parameters such as Width , Height , Name , Color , Text .
type Thing struct {
		Top  int `xml:"top,attr"`
		Left int `xml:"left,attr"`
		Sep  int `xml:"sep,attr"`
		Item []item `xml:"item"`
	}
	type item struct {
		Width  int    `xml:"width,attr"`
		Height int    `xml:"height,attr"`
		Name   string `xml:"name,attr"`
		Color  string `xml:"color,attr"`
		Text   string `xml:",chardata"`
	}


Then we must specify the output destination for SVG (in our example, this will be standard output) and the canvas sizes:
var (
		canvas = svg.New(os.Stdout)
		width = flag.Int("w", 1024, "width") 
		height = flag.Int("h", 768, "height") 
	)


After that, we define a function for reading incoming data:
func dothing(location string) {
		f, err := os.Open(location)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return
		}
		defer f.Close()
		readthing(f)
	}


The next important step is to identify the function that is responsible for loading and analyzing incoming data. Here we will use the XML package included in the Go standard library.
func readthing(r io.Reader) {
		var t Thing
		if err := xml.NewDecoder(r).Decode(&t); err != nil {
			fmt.Fprintf(os.Stderr, "Unable to parse components (%v)\n", err)
			return
		}
		drawthing(t)
	}


When all the data is loaded, we go through it and draw the objects. To do this, we use the capabilities of the SVGo package. In our case, we set the coordinates (x, y) for each element and draw a circle that corresponds to the sizes and colors specified in the element attributes and also add text and apply vertical intervals.
	func drawthing(t Thing) {
		x := t.Left
		y := t.Top
		for _, v := range t.Item {
			style := fmt.Sprintf("font-size:%dpx;fill:%s", v.Width/2, v.Color)
			canvas.Circle(x, y, v.Height/4, "fill:"+v.Color)
			canvas.Text(x+t.Sep, y, v.Name+":"+v.Text+"/"+v.Color, style)
			y += v.Height
		} 
	}


Further we describe the main function of our example in which we will get the name of the data file
func main() {
		flag.Parse()
		for _, f := range flag.Args() {
			canvas.Start(*width, *height)
			dothing(f)
			canvas.End()
		}
	}


An example of the launch of our example and the results of its work:
$ go run rpd.go thing.xml


Little:This is small/blueMed:This is medium/greenBig:This is large/red


The finished SVG will look like this:


Using this example, you can create many different visualization tools. For example, in my work I use tools that can build both simple barcharts and bulletgraphs and more complex pie-charts and component diagrams .

You can also create images based on the data from the API of any services. For example, the “f50” program takes a word and based on it generates a grid of images obtained from the Flickr API. f50 uses the same approach except that the data is not taken from a local file but an Flickr API HTTPS request is generated.
Usage example:
f50 sunset


The Flickr API returns this response:

		...
	


To create an SVG, we need the id, secret, farm, server, and title parameters.
// makeURI converts the elements of a photo into a Flickr photo URI
	func makeURI(p Photo, imsize string) string {
		im := p.Id + "_" + p.Secret
		if len(imsize) > 0 {
			im += "_" + imsize
		}
		return fmt.Sprintf(urifmt, p.Farm, p.Server, im)
	}
	// imageGrid reads the response from Flickr, and creates a grid of images
	func imageGrid(f FlickrResp, x, y, cols, gutter int, imgsize string) {
		if f.Stat != "ok" {
			fmt.Fprintf(os.Stderr, "Status: %v\n", f.Stat)
			return
		}
		xpos := x
		for i, p := range f.Photos.Photo {
			if i%cols == 0 && i > 0 {
				xpos = x
				y += (imageHeight + gutter)
			}
			canvas.Link(makeURI(p, ""), p.Title)
			canvas.Image(xpos, y, imageWidth, imageHeight, makeURI(p, "s"))
			canvas.LinkEnd()
			xpos += (imageWidth + gutter)
		}
	}



If you open the resulting SVG in a browser, then when you hover over the picture you will see its title and when you click it, you will go to the original image.

Also popular now: