技術メモ

技術メモ

ラフなメモ

gonum/plotを用いたSVGファイルの生成

plotするデータのサンプルは Example plots を用いることにします。

package main

import (
    "bytes"
    "io/ioutil"
    "math"
    "math/rand"

    "gonum.org/v1/plot/vg/draw"
    "gonum.org/v1/plot/vg/vgsvg"

    "gonum.org/v1/plot/vg"

    "gonum.org/v1/plot"
    "gonum.org/v1/plot/plotter"
    "gonum.org/v1/plot/plotutil"
)

func main() {
    p, err := plot.New()
    if err != nil {
        panic(err)
    }
    p.X.Label.Text = "X"
    p.Y.Label.Text = "Y"

    err = plotutil.AddLinePoints(p,
        "First", randomPoints(15),
        "Second", randomPoints(15),
        "Third", randomPoints(15))
    if err != nil {
        panic(err)
    }

    // create SVG.
    size := 20 * vg.Centimeter
    canvas := vgsvg.New(size, size/vg.Length(math.Phi))
    p.Draw(draw.New(canvas))
    out := new(bytes.Buffer)
    _, err = canvas.WriteTo(out)
    if err != nil {
        panic(err)
    }

    if err := ioutil.WriteFile("./points.svg", out.Bytes(), 0644); err != nil {
        panic(err)
    }
}

// randomPoints returns some random x, y points.
func randomPoints(n int) plotter.XYs {
    pts := make(plotter.XYs, n)
    for i := range pts {
        if i == 0 {
            pts[i].X = rand.Float64()
        } else {
            pts[i].X = pts[i-1].X + rand.Float64()
        }
        pts[i].Y = pts[i].X + 10*rand.Float64()
    }
    return pts
}

本質的には以下の実装が主要な部分です。

vgsvgパッケージの New メソッドを用いて*Canvasを生成します。

   size := 20 * vg.Centimeter
    canvas := vgsvg.New(size, size/vg.Length(math.Phi))

*Legendの Draw メソッドを用いて*Canvasに凡例を与えます。

p.Draw(draw.New(canvas))

*Canvasのバイナリを WriteTo メソッドでバイナリとして書き出します。

   out := new(bytes.Buffer)
    _, err = canvas.WriteTo(out)

あとはファイルなり標準出力なりに出力すればよいです。以下のような画像が生成されます。

X 1 5 9 Y 3 9 15 First Second Third

SVGファイル形式は、ファイルの中身は単なるXMLで構成された文字列であるため、JavaScriptなどと親和性があります。サーバーサイドで生成したファイルをWebSocketを用いてフロントエンドに描画することも容易にできます。

SVGファイルの文字列

<?xml version="1.0"?>
<!-- Generated by SVGo and Plotinum VG -->
<svg width="566.93pt" height="350.38pt" viewBox="0 0 566.93 350.38"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
<g transform="scale(1, -1) translate(0, -350.38)">
<path d="M0,0L566.93,0L566.93,350.38L0,350.38Z" style="fill:#FFFFFF" />
<text x="299.96" y="-3.8613" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:12px">X</text>
<text x="70.525" y="-15.602" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:10px">1</text>
<text x="312.94" y="-15.602" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:10px">5</text>
<text x="555.36" y="-15.602" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:10px">9</text>
<path d="M73.025,25.23L73.025,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M315.44,25.23L315.44,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M557.86,25.23L557.86,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M133.63,29.23L133.63,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M194.23,29.23L194.23,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M254.84,29.23L254.84,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M376.05,29.23L376.05,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M436.65,29.23L436.65,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M497.25,29.23L497.25,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M44.166,33.23L564.43,33.23" style="fill:none;stroke:#000000;stroke-width:0.5" />
<g transform="rotate(90)">
<text x="190.1" y="11.555" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:12px">Y</text>
</g>
<text x="20.416" y="-77.155" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:10px">3</text>
<text x="20.416" y="-189.04" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:10px">9</text>
<text x="15.416" y="-300.92" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:10px">15</text>
<path d="M27.916,81.877L35.916,81.877" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M27.916,193.76L35.916,193.76" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M27.916,305.64L35.916,305.64" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M31.916,137.82L35.916,137.82" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M31.916,249.7L35.916,249.7" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M35.916,40.98L35.916,347.88" style="fill:none;stroke:#000000;stroke-width:0.5" />
<path d="M49.065,212.59L89.34,131.22L115.08,185.59L119.05,87.931L124.93,116.66L156.15,221.88L169.14,145.14L188.42,167.52L205.57,140.02L246.72,138.78L259.04,169.11L293.62,273.29L311.39,173.32L357,170.48L409.44,278.01" style="fill:none;stroke:#F15A60" />
<path d="M51.565,212.59A2.5,2.5 0 1 1 46.565,212.59A2.5,2.5 0 1 1 51.565,212.59Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M91.84,131.22A2.5,2.5 0 1 1 86.84,131.22A2.5,2.5 0 1 1 91.84,131.22Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M117.58,185.59A2.5,2.5 0 1 1 112.58,185.59A2.5,2.5 0 1 1 117.58,185.59Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M121.55,87.931A2.5,2.5 0 1 1 116.55,87.931A2.5,2.5 0 1 1 121.55,87.931Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M127.43,116.66A2.5,2.5 0 1 1 122.43,116.66A2.5,2.5 0 1 1 127.43,116.66Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M158.65,221.88A2.5,2.5 0 1 1 153.65,221.88A2.5,2.5 0 1 1 158.65,221.88Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M171.64,145.14A2.5,2.5 0 1 1 166.64,145.14A2.5,2.5 0 1 1 171.64,145.14Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M190.92,167.52A2.5,2.5 0 1 1 185.92,167.52A2.5,2.5 0 1 1 190.92,167.52Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M208.07,140.02A2.5,2.5 0 1 1 203.07,140.02A2.5,2.5 0 1 1 208.07,140.02Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M249.22,138.78A2.5,2.5 0 1 1 244.22,138.78A2.5,2.5 0 1 1 249.22,138.78Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M261.54,169.11A2.5,2.5 0 1 1 256.54,169.11A2.5,2.5 0 1 1 261.54,169.11Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M296.12,273.29A2.5,2.5 0 1 1 291.12,273.29A2.5,2.5 0 1 1 296.12,273.29Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M313.89,173.32A2.5,2.5 0 1 1 308.89,173.32A2.5,2.5 0 1 1 313.89,173.32Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M359.5,170.48A2.5,2.5 0 1 1 354.5,170.48A2.5,2.5 0 1 1 359.5,170.48Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M411.94,278.01A2.5,2.5 0 1 1 406.94,278.01A2.5,2.5 0 1 1 411.94,278.01Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<path d="M44.166,40.98L53.761,151.89L112.87,71.657L148.91,78.957L190.85,137.06L201.35,184.97L234.33,146.15L259.98,201.05L275.34,159.43L323.14,189.01L376.5,193.36L430.7,172.81L489.91,186.71L503.38,304L518.02,239.59" style="fill:none;stroke:#7AC36A;stroke-dasharray:6,2" />
<path d="M42.032,38.847L46.3,38.847L46.3,43.114L42.032,43.114Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M51.628,149.76L55.895,149.76L55.895,154.03L51.628,154.03Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M110.73,69.523L115,69.523L115,73.791L110.73,73.791Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M146.78,76.823L151.05,76.823L151.05,81.091L146.78,81.091Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M188.72,134.93L192.99,134.93L192.99,139.2L188.72,139.2Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M199.22,182.83L203.49,182.83L203.49,187.1L199.22,187.1Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M232.2,144.01L236.47,144.01L236.47,148.28L232.2,148.28Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M257.84,198.91L262.11,198.91L262.11,203.18L257.84,203.18Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M273.21,157.3L277.48,157.3L277.48,161.57L273.21,161.57Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M321,186.87L325.27,186.87L325.27,191.14L321,191.14Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M374.37,191.23L378.63,191.23L378.63,195.5L374.37,195.5Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M428.57,170.67L432.84,170.67L432.84,174.94L428.57,174.94Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M487.77,184.57L492.04,184.57L492.04,188.84L487.77,188.84Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M501.25,301.87L505.51,301.87L505.51,306.13L501.25,306.13Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M515.88,237.46L520.15,237.46L520.15,241.73L515.88,241.73Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<path d="M68.955,181.66L117.5,194.44L128.59,141.56L182.95,205.7L242.28,268.63L247.78,190.31L303.96,293.71L325.05,250.95L368.13,240.51L407.5,250.38L453.3,236.89L461.22,347.88L515.54,240.8L559.25,314.38L564.43,320.64" style="fill:none;stroke:#5A9BD4;stroke-dasharray:2,2" />
<path d="M68.955,184.79L66.248,180.1L71.661,180.1Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M117.5,197.56L114.8,192.87L120.21,192.87Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M128.59,144.68L125.88,139.99L131.29,139.99Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M182.95,208.83L180.24,204.14L185.66,204.14Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M242.28,271.75L239.57,267.06L244.98,267.06Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M247.78,193.44L245.08,188.75L250.49,188.75Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M303.96,296.84L301.26,292.15L306.67,292.15Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M325.05,254.08L322.34,249.39L327.76,249.39Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M368.13,243.64L365.43,238.95L370.84,238.95Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M407.5,253.51L404.79,248.82L410.2,248.82Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M453.3,240.01L450.6,235.33L456.01,235.33Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M461.22,351.01L458.51,346.32L463.93,346.32Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M515.54,243.92L512.84,239.24L518.25,239.24Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M559.25,317.5L556.54,312.81L561.95,312.81Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M564.43,323.76L561.72,319.08L567.14,319.08Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<path d="M546.93,67.924L566.93,67.924" style="fill:none;stroke:#F15A60" />
<path d="M559.43,67.924A2.5,2.5 0 1 1 554.43,67.924A2.5,2.5 0 1 1 559.43,67.924Z" style="fill:none;stroke:#F15A60;stroke-width:0.5" />
<text x="521.92" y="-62.258" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:12px">First</text>
<path d="M546.93,56.146L566.93,56.146" style="fill:none;stroke:#7AC36A;stroke-dasharray:6,2" />
<path d="M554.8,54.013L559.06,54.013L559.06,58.28L554.8,58.28Z" style="fill:none;stroke:#7AC36A;stroke-width:0.5" />
<text x="508.6" y="-50.48" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:12px">Second</text>
<path d="M546.93,44.369L566.93,44.369" style="fill:none;stroke:#5A9BD4;stroke-dasharray:2,2" />
<path d="M556.93,47.494L554.22,42.807L559.64,42.807Z" style="fill:none;stroke:#5A9BD4;stroke-width:0.5" />
<text x="517.27" y="-38.703" transform="scale(1, -1)"
    style="font-family:Times;font-weight:normal;font-style:normal;font-size:12px">Third</text>
</g>
</svg>