pure Ruby で手軽に使えます。
準備
普通にインストールするだけ。
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)
デフォルト設定では、データ点をつなぐ線とデータ点の脇の値が表示されます。あと、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)
最低限としてはこんなところでしょうか。
環境
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 なのでインストールは楽
- gnuplot などを別途インストールする必要もなし(ruby_gnuplot や Numo::Gnuplot よりもさらに依存が少ない)
- SVG::Graph という名前の通り、出力フォーマットは SVG のみ
- SVG::TT::Graph という Perl の ライブラリの移植としてスタートしたとのこと
- 散布図の場合は主に
Graph.rb
とPlot.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行程度で、モンキーパッチで対応できなくもなさそう。
ためしに描いてみたもの:
直線が引ければ曲線も引ける:
スタイルシートで制御する
(この節は 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)
時系列データ
Plot
クラスを継承した TimeSeries
というクラスがあります。
以下を参照。
- https://github.com/lumean/svg-graph2/blob/master/lib/SVG/Graph/TimeSeries.rb
- https://github.com/lumean/svg-graph2/tree/master/examples/timeseries.rb
- https://github.com/lumean/svg-graph2/tree/master/examples/timeseries.svg
まとめたもの
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)
IRuby 対応
to_iruby
メソッドが生えているので IRuby だとこんな感じで表示できます。
参考: Automatically show charts in jupyter · Issue #13 · lumean/svg-graph2
この記事を読んだ人は(ひょっとしたら)こちらも読んでいます