Ruby + Victor でSVGお絵描き（簡単な散布図を描いてみた）


Victor は Ruby で SVG 画像を作るための gem です。pure Ruby なので気軽に使えます。他の gem への依存もありません。

<!--
victor | RubyGems.org | コミュニティのGemホスティングサービス
-->
https://rubygems.org/gems/victor

<!--
DannyBen/victor: Ruby SVG Image Builder
-->
https://github.com/DannyBen/victor

リポジトリの [examples ディレクトリ](https://github.com/DannyBen/victor/tree/master/examples) を見ると雰囲気が分かります。


これを使ってためしに簡単な散布図のようなものを描いてみました。



# できあがり

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/f5616253-b1da-62a8-f236-14c8d1d0ecac.png)

ブラウザで表示させてスクリーンショットを撮ったもの。
色付きのグラフも良いものですが、白黒のも渋くて好きです。



# コード

感触がなんとなく分かればOK という程度の試みです。DRY でなかったり座標決め打ちだったりしていろいろ適当ですが気にしない。

最低限でいえば直線が描ければよくて、あとは円とかテキストが描ければなお良し！ :100:  という感じ。
あとは transform を使ってy軸ラベルを回転させたり CSS でフォントを指定したりできるな、といったあたりを試すことができました。

```ruby
require "victor"

OFFSET_X = 80
OFFSET_Y = 20
FRAME_WIDTH = 400 - 80 - 20
FONT_SIZE = 16
STROKE_STYLE = { stroke: "#000", stroke_width: 1 }

def transform_x(x)
  OFFSET_X + x * FRAME_WIDTH
end

def transform_y(y)
  OFFSET_Y + (1 - y) * FRAME_WIDTH
end

def draw_line(x1, y1, x2, y2)
  $svg.line(
    x1: x1, y1: y1,
    x2: x2, y2: y2,
    style: STROKE_STYLE
  )
end

def draw_frame
  $svg.rect(
    x: 80, y: 20, width: 300, height: 300,
    style: STROKE_STYLE, fill: "transparent"
  )
end

def draw_tick_x(x)
  font_offset_x = -12
  font_offset_y = 26
  frame_x = transform_x(x)

  draw_line frame_x, 320, frame_x, 330
  $svg.text(
    x, x: frame_x + font_offset_x, y: 320 + font_offset_y,
    font_size: FONT_SIZE, class: "text"
  )
end

def draw_axis_x
  [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].each { |x| draw_tick_x x }

  frame_x = transform_x(0.5)
  $svg.text(
    "x軸ラベル", x: frame_x, y: 320 + 50,
    text_anchor: "middle",
    font_size: FONT_SIZE, class: "text"
  )
end

def draw_tick_y(y)
  font_offset_x = -40
  font_offset_y = 5
  frame_y = transform_y(y)

  draw_line 70, frame_y, 80, frame_y
  $svg.text(
    y, x: 80 + font_offset_x, y: frame_y + font_offset_y,
    font_size: FONT_SIZE, class: "text"
  )
end

def draw_axis_y
  [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].each { |y| draw_tick_y y }

  frame_y = transform_y(0.5)
  $svg.text(
    "y軸ラベル", x: 0, y: 0, font_size: FONT_SIZE, class: "text",
    text_anchor: "middle",
    transform: "translate(26, #{frame_y}) rotate(270)"
  )
end

def plot_points(xs, ys)
  xs.zip(ys).each do |x, y|
    $svg.circle(
      cx: transform_x(x), cy: transform_y(y), r: 5,
      style: STROKE_STYLE, fill: "transparent"
    )
  end
end

def plot_line(x1, y1, x2, y2)
  draw_line(
    transform_x(x1), transform_y(y1),
    transform_x(x2), transform_y(y2)
  )
end

# --------------------------------

$svg = Victor::SVG.new(
  width: 400, height: 400,
  style: { background: "#fff" }
)

$svg.css = <<CSS
  .text { font-family: "Noto Sans CJK JP"; }
CSS

draw_frame
draw_axis_x
draw_axis_y
plot_points [0.1, 0.6, 0.61, 0.9], [0.2, 0.3, 0.31, 0.7] # xs, ys
plot_line 0.1, 0.5, 0.9, 0.9 # x1, y1, x2, y2

$svg.save "sample.svg"
```




# 関連

<!--
DannyBen/victor-cli: Command line for Victor, the Ruby SVG library
-->
https://github.com/DannyBen/victor-cli

SVG を Ruby のコードに変換する機能があるのがちょっとおもしろい。




# この記事を読んだ人は（ひょっとしたら）こちらも読んでいます

<!--
SVG::Graph（svg-graph gem）で散布図を描く - Qiita
-->
https://qiita.com/sonota88/items/86afece3b2595648a656

<!--
Galaaz を触ってみた（TruffleRuby + ggplot2 で散布図を描いてみた） - Qiita
-->
https://qiita.com/sonota88/items/b586e96f7805a5695a34