LoginSignup
1
0

More than 1 year has passed since last update.

SVG::Graph(svg-graph gem)で散布図を描く

Last updated at Posted at 2022-07-03

pure Ruby で手軽に使えます。

image.png

準備

普通にインストールするだけ。

gem install svg-graph

ただし、 Ruby 3.0.0 で REXML が bundled gem になったようなので、3.0.0 以降の場合は gem install rexml も必要です。

最低限の例

SVGファイルに出力する最低限の例です。

require "svggraph"

options = {}

g = SVG::Graph::Plot.new(options) # options は必須

points = [
  1.4, 11,
  1.9, 22,
  2.5, 33,
  4.1, 44,
  4.5, 55
]

g.add_data(
  data: points,
  title: "系列1" # 必須
)

svg = g.burn
File.write("sample_min_1.svg", svg)

image.png

デフォルト設定では、データ点をつなぐ線とデータ点の脇の値が表示されます。あと、SVG なので日本語も普通に使えます。

点だけの表示にする+きりがよい目盛にするための設定も追加したものがこちら。

require "svggraph"

options = {
  scale_x_integers: true,
  scale_y_integers: true,
  show_lines: false,
  show_data_values: false,
  min_x_value: 0,
  min_y_value: 0,
  scale_y_divisions: 10, # y軸目盛の間隔
}

g = SVG::Graph::Plot.new(options)

points = [
  1.4, 11,
  1.9, 22,
  2.5, 33,
  4.1, 44,
  4.5, 55
]

g.add_data(
  data: points,
  title: "系列1  " # 末尾のスペースは幅の調節用
)

svg = g.burn
File.write("sample_min_2.svg", svg)

image.png

最低限としてはこんなところでしょうか。

環境

Ubuntu Linux 18.04
ruby -v #=> ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
gem:
  rexml (3.2.5)
  svg-graph (2.2.1)

しくみ

  • REXML で直にXMLを組み立てている

メモ

  • pure Ruby + REXML なのでインストールは楽 :thumbsup: :thumbsup: :thumbsup: :thumbsup:
    • gnuplot などを別途インストールする必要もなし(ruby_gnuplotNumo::Gnuplot よりもさらに依存が少ない)
  • SVG::Graph という名前の通り、出力フォーマットは SVG のみ
  • SVG::TT::Graph という Perl の ライブラリの移植としてスタートしたとのこと
  • 散布図の場合は主に Graph.rbPlot.rb を見ればよくて、 コメント行を除いた合計は 1,400行弱、ヒアドキュメントで書かれている CSS を除外すると約 1,100行。 なので、使い方が分からないときやうまく動かないときでもサッと読めるサイズ感。

このライブラリだけでどんな図でも描ける! という感じではなさそうですが、pure Ruby かつ小規模・少依存なので、最初の選択肢として検討するとよさそうです。これで十分用が足りるならこれでいいでしょう、っていう。

自分が使いそうな機能についてメモ

設定項目についてはコメントで説明されています。まずはこれを見ましょう。

https://github.com/lumean/svg-graph2/blob/master/lib/SVG/Graph/Graph.rb
https://github.com/lumean/svg-graph2/blob/master/lib/SVG/Graph/Plot.rb

rubydoc.info では以下(v2.2.1 のもの):

https://www.rubydoc.info/gems/svg-graph/2.2.1/SVG/Graph/Graph
https://www.rubydoc.info/gems/svg-graph/2.2.1/SVG/Graph/Plot

グラフのタイトル, 軸タイトル

options = {
  graph_title: "タイトル",
  show_graph_title: true,
  x_title: "x軸タイトル",
  show_x_title: true,
  y_title: "y軸タイトル",
  show_y_title: true,
  # ...
}

g = SVG::Graph::Plot.new(options)

# ...

複数系列の描画

Plot#add_data を複数回呼んで追加する方式。

g.add_data(
  data: points1,
  title: "系列1"
)

g.add_data(
  data: points2,
  title: "系列2"
)

スタイルシートを一部だけ変えたい

たとえば、軸タイトルはデフォルトでは赤い文字で描画されます。これを黒に変えたいという場合。

svg = g.burn

svg.sub!("</style>", <<~STYLE)
  .xAxisTitle { fill: #000000; }
  .yAxisTitle { fill: #000000; }
  </style>
STYLE

ちょっと強引ですが、一番手っ取り早いのはこれですかね……。

SVG::Graph#inline_style_sheet という設定項目が存在しているのですが、スタイルシートをまるごと切り替える動作になっているようです。

系列ごとに点・線の表示を指定したい

ソースを修正する

(2022-09-24 追記)
ソースを修正せずに済む方法もありました。次の節を参照してください。

散布図を描いたら回帰直線も描画したいところですが、現状だと点や線の表示・非表示は系列ごとではなくグラフ全体での設定になっています。

ただ、雑に試してみたところ、下記のように改造すれば系列ごとの設定が可能でした。雑です。

--- a/lib/SVG/Graph/Plot.rb
+++ b/lib/SVG/Graph/Plot.rb
@@ -402,17 +402,18 @@ module SVG
             })
           end
 
-          if show_lines
+          if data.fetch(:show_lines, show_lines)
             @graph.add_element( "path", {
               "d" => "M#{x_start} #{y_start} #{lpath}",
               "class" => "line#{line}"
             })
           end
 
-          if show_data_points || show_data_values || add_popups
+          _show_data_points = data.fetch(:show_data_points, show_data_points)
+          if _show_data_points || show_data_values || add_popups
             x_points.each_index { |idx|
               c = calc_coords(x_points[idx] -  x_min, y_points[idx] -  y_min)
-              if show_data_points
+              if _show_data_points
                 shape_selection_string = data[:description][idx].to_s
                 if !data[:shape][idx].nil?
                   shape_selection_string = data[:shape][idx].to_s

Plot#draw_data は 50行程度で、モンキーパッチで対応できなくもなさそう。

ためしに描いてみたもの:

image.png

直線が引ければ曲線も引ける:

image.png

スタイルシートで制御する

(この節は 2022-09-24 に追記しました)
ソースの修正やモンキーパッチをしなくてもスタイルシートの指定で対応できました。

  • グラフ全体のオプションで show_lines: true と指定しておく
  • 系列ごとに display: none で線や点を非表示にする

という方法です。

require "svggraph"

options = {
  show_data_values: false,
  show_lines: true,

  scale_x_integers: true,
  scale_y_integers: true,
  min_x_value: 0,
  min_y_value: 0,
}

g = SVG::Graph::Plot.new(options)

points1 = [ 1.0, 11,  1.8, 25,  3.1, 32 ]
points2 = [ 1.0, 21,  1.8, 35,  3.1, 42 ]

g.add_data(data: points1, title: "series 1")
g.add_data(data: points2, title: "series 2")

svg = g.burn

svg.sub!("</style>", <<~STYLE)
  .line1 { display: none; }
  .dataPoint2 { display: none; }
</style>
STYLE

File.write("sample.svg", svg)

image.png

時系列データ

Plot クラスを継承した TimeSeries というクラスがあります。
以下を参照。

まとめたもの

require "svggraph"

options = {
  scale_x_integers: true,
  scale_y_integers: true,

  graph_title: "タイトル",
  show_graph_title: true,
  x_title: "x軸タイトル",
  show_x_title: true,
  y_title: "y軸タイトル",
  show_y_title: true,

  # x, y の範囲
  min_x_value: 0,
  max_x_value: 6,
  min_y_value: 0,
  max_y_value: 60,

  scale_y_divisions: 10, # y軸目盛の間隔
}

g = SVG::Graph::Plot.new(options)

points1 = [
  1.0, 11,
  1.8, 25,
  3.1, 32,
]

points2 = [
  1.0, 21,
  1.8, 35,
  3.1, 42,
]

g.add_data(
  data: points1,
  title: "系列1  "
)

g.add_data(
  data: points2,
  title: "系列2"
)

svg = g.burn

svg.sub!("</style>", <<~STYLE)
  .xAxisTitle { fill: #000000; }
  .yAxisTitle { fill: #000000; }
</style>
STYLE

File.write("sample_full.svg", svg)

image.png

IRuby 対応

to_iruby メソッドが生えているので IRuby だとこんな感じで表示できます。

image.png

参考: Automatically show charts in jupyter · Issue #13 · lumean/svg-graph2

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0