[关闭]
@Yori 2015-01-14T14:11:15.000000Z 字数 4050 阅读 1424

The Other Side of Go

Go


注:此文为[The other side of Go: Programming Pictures, the Read, Parse, Draw Pattern]翻译

在项目中,Go往往被认为适合用来做后端开发,而有时我们也会遇到图形上的需求,在处理图形效果时Go的表现同样十分出色。这篇文章将会介绍如何通过SVGo来将数据生成图像(特别是矢量图像)。

在SVGo的API中,提供了一个功能:生成标准 SVG 到一个 io.Writer。这样一来,得益于Go中I/O包的灵活性,我们可以在任何需要输出的地方输出图片,如标准输出、文件、网络链接或者Web Server 中。

SVGo的设计思想使我们在对高层对象(即圆、矩形、线、 多边形、曲线等)进行操作时,可以想到利用程序的逻辑性来管理其布局和元素之间的关系,并且根据需要向该高层对象应用图形样式和其他属性。

1


读取/解析/绘制 模式

一个根据自己或者网络资源生成图像的方式就是读取/解析/绘制模式。这个模式一般包括以下几个步骤:

下面通过一个简单的例子,解释如何通过从XML(使用JSON也非常相似)中获取数据并且生成一个简单的可视化的SVG。需要注意的是,如果采用自定义数据,你可以自由的定义数据的结构,但如果使用其他诸如Internet service APIs的数据来源时,需要遵循它们定义好的数据结构。

下面是本例中用到的XML 输入(thing.xml)

  1. <thing top="100" left="100" sep="100">
  2. <item width="50" height="50" name="Little" color="blue">This is small</item>
  3. <item width="75" height="100" name="Med" color="green">This is medium</item>
  4. <item width="100" height="200" name="Big" color="red">This is large</item>
  5. </thing>

首先,定义和输入匹配的数据结构。在例子中,我们可以看到在结构体中定义的元素和数据属性之间的对应关系:结构体'Thing'中定义了图形初始时的顶部和左侧的位置和元素的间距。'Thing'中包含了多个'item',每个'item'都有高度、宽度、名称、颜色和文本。

  1. type Thing struct {
  2. Top int `xml:"top,attr"`
  3. Left int `xml:"left,attr"`
  4. Sep int `xml:"sep,attr"`
  5. Item []item `xml:"item"`
  6. }
  7. type item struct {
  8. Width int `xml:"width,attr"`
  9. Height int `xml:"height,attr"`
  10. Name string `xml:"name,attr"`
  11. Color string `xml:"color,attr"`
  12. Text string `xml:",chardata"`
  13. }

声明了新的svg对象并指明画布尺寸

  1. var (
  2. canvas = svg.New(os.Stdout)
  3. width = flag.Int("w", 1024, "width")
  4. height = flag.Int("h", 768, "height")
  5. )

接下来,实现一个方法用读入数据

  1. func dothing(location string) {
  2. f, err := os.Open(location)
  3. if err != nil {
  4. fmt.Fprintf(os.Stderr, "%v\n", err)
  5. return
  6. }
  7. defer f.Close()
  8. readthing(f)
  9. }

接下来实现的是一个重要的功能,即解析数据并加载定义的图形结构。这里解析直接使用了Go的标准库的XML包:通过io.Reader传递到NewDecoder,并Decode一个'Thing'实例。

  1. func readthing(r io.Reader) {
  2. var t Thing
  3. if err := xml.NewDecoder(r).Decode(&t); err != nil {
  4. fmt.Fprintf(os.Stderr, "Unable to parse components (%v)\n", err)
  5. return
  6. }
  7. drawthing(t)
  8. }

最后, 在加载并解析了数据之后,使用SVGo库使图像可视化.在这个例子中,item表现为圆圈,每个item都设置了初始坐标,并且都有各自的尺寸和颜色,然后为每个item添加描述文本,最后给item之间设置垂直间距。

  1. func drawthing(t Thing) {
  2. x := t.Left
  3. y := t.Top
  4. for _, v := range t.Item {
  5. style := fmt.Sprintf("font-size:%dpx;fill:%s", v.Width/2, v.Color)
  6. canvas.Circle(x, y, v.Height/4, "fill:"+v.Color)
  7. canvas.Text(x+t.Sep, y, v.Name+":"+v.Text+"/"+v.Color, style)
  8. y += v.Height
  9. }
  10. }

在主函数中初始化画布并调用方法dothing()读入数据

  1. func main() {
  2. flag.Parse()
  3. for _, f := range flag.Args() {
  4. canvas.Start(*width, *height)
  5. dothing(f)
  6. canvas.End()
  7. }
  8. }

运行这个程序,在命令行中读入数据文件(thing.xml)

  1. $ go run rpd.go thing.xml
  2. <?xml version="1.0"?>
  3. <!-- Generated by SVGo -->
  4. <svg width="1024" height="768"
  5. xmlns="http://www.w3.org/2000/svg"
  6. xmlns:xlink="http://www.w3.org/1999/xlink">
  7. <circle cx="100" cy="100" r="12" style="fill:blue"/>
  8. <text x="200" y="100" style="font-size:25px;fill:blue">Little:This is small/blue</text>
  9. <circle cx="100" cy="150" r="25" style="fill:green"/>
  10. <text x="200" y="150" style="font-size:37px;fill:green">Med:This is medium/green</text>
  11. <circle cx="100" cy="250" r="50" style="fill:red"/>
  12. <text x="200" y="250" style="font-size:50px;fill:red">Big:This is large/red</text>
  13. </svg>

在浏览器中查看,这个例子的结果看起来像这样:
2

通过这种模式,我们可以构建很多可视化工具.比如在工作我有工具来构建传统的诸如 柱形图表 和 bulletgraphs 之类的图表,同时也用来替代饼图 , 路线图, 组件 图 , 时间、热图和计分网格。

同样你也可以用 Internet APIs提供的数据生成SVG.举个例子, 'f50' 程序需要一个关键字和显示通过Flicker的"interestingness"算法筛选的图片的网格。'f50' 采用了和上面例子一样的模式,但是通过发送HTTPS请求,解析XML响应来获取数据生成图片

$ f50 sunset

响应内容:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <rsp stat="ok">
  3. <photo id="15871035007" ... secret="84d59df678" server="7546" farm="8" title="flickr-gopher" ... />
  4. <photo id="15433662714" ... secret="3b9358c61d" server="7559" farm="8" title="Laurence Maroney 2006..." ... />
  5. ...
  6. </rsp>

'f50'选取id、secret、farm、server和title属性来构建这张图片,如下所示:

  1. // makeURI converts the elements of a photo into a Flickr photo URI
  2. func makeURI(p Photo, imsize string) string {
  3. im := p.Id + "_" + p.Secret
  4. if len(imsize) > 0 {
  5. im += "_" + imsize
  6. }
  7. return fmt.Sprintf(urifmt, p.Farm, p.Server, im)
  8. }
  9. // imageGrid reads the response from Flickr, and creates a grid of images
  10. func imageGrid(f FlickrResp, x, y, cols, gutter int, imgsize string) {
  11. if f.Stat != "ok" {
  12. fmt.Fprintf(os.Stderr, "Status: %v\n", f.Stat)
  13. return
  14. }
  15. xpos := x
  16. for i, p := range f.Photos.Photo {
  17. if i%cols == 0 && i > 0 {
  18. xpos = x
  19. y += (imageHeight + gutter)
  20. }
  21. canvas.Link(makeURI(p, ""), p.Title)
  22. canvas.Image(xpos, y, imageWidth, imageHeight, makeURI(p, "s"))
  23. canvas.LinkEnd()
  24. xpos += (imageWidth + gutter)
  25. }
  26. }

如果你在浏览器中查看结果,将鼠标悬停在一张图片上,你会看到这张图片的标题,并且点击可以看到大图
3

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注