Rubyライブラリ「Colorable」とGvizを使ってGraphvizで綺麗なリングを描く
(追記:2013-05-15) Colorableを大幅アップデートしました(version 0.2.0)。その結果、この記事で書かれたバージョンとの互換性がなくなっています。現在の機能に関しては以下の記事を参考にしてください。
Rubyで「色」というものを扱う機会はそう多くはないでしょう。Rubyはどちらかと言うと、クライアントサイドつまり接客系ではなく、サーバーサイドつまり裏方系言語ですからね。それでも接客系言語に向けて色情報を渡したり、そのラッパーになったりする機会はあるとおもいます。そんなときはRubyでも色を扱う必要がでてきます。
実際、拙作GraphvizのRubyラッパー「Gviz」で色付きグラフを作るときには、色指定で色々と苦労します。
RubyGems.orgで検索すると、ANSIカラー以外の色関係のライブラリがそれなりの数ヒットします。しかし、ざっと見たところ自分の欲しい物が見つかりません。というか、なんか探すの面倒臭いんですよね..
そんなわけで… 車輪の再発明と思いつつも… 勉強を兼ねまして…
Colorable
というRGBとかHSBとかの色を扱うライブラリを作りました^ ^; ちょっとまだ中途半端な出来ですが。
使い方
gem install colorable
して、次のように使います。
Colorable::Color
require "colorable"
c = Colorable::Color.new(:alice_blue)
c # => rgb(240,248,255)
c.name # => "Alice Blue"
c.rgb # => [240, 248, 255]
c.hex # => "#F0F8FF"
c.hsb # => [208, 6, 100]
c2 = Colorable::Color.new([0, 255, 255])
c2 # => rgb(0,255,255)
c2.name # => "Aqua"
c2.rgb # => [0, 255, 255]
c2.hex # => "#00FFFF"
c2.hsb # => [180, 100, 100]
Color.new
はX11の色名称またはRGB値の配列を引数に取り、colorオブジェクトを生成します。色名称は文字列またはシンボルを受け付けます。そしてRGB値に基いてHEXおよびHSB(Hue, Saturation, Brightness)値を返します。
Colorには#next
,#prev
というメソッドがあります。これらはX11カラーにおける次の色、前の色を順次出力します。
c = Colorable::Color.new(:alice_blue)
c.next.name # => "Antique White"
10.times.map { c = c.next; c.name } # => ["Antique White", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "Blanched Almond", "Blue", "Blue Violet"]
c2 = Colorable::Color.new(:alice_blue)
c2.prev.name # => "Yellow Green"
10.times.map { c2 = c2.prev; c2.name } # => ["Yellow Green", "Yellow", "White Smoke", "White", "Wheat", "Violet", "Turquoise", "Tomato", "Thistle", "Teal"]
X11のカラーセット(144色)は環状になっているので#next
,#prev
は循環します。
これらのメソッドは、デフォルトでその名前順に色を出力しますが、:rgb
, :hsb
などの並びを指定する引数を渡すことにより、その並びでの色を順に出力させることができます。
c = Colorable::Color.new(:alice_blue)
c.next(:rgb).name # => "Honeydew"
10.times.map { c = c.next(:rgb); c.name } # => ["Honeydew", "Azure", "Sandy Brown", "Wheat", "Beige", "White Smoke", "Mint Cream", "Ghost White", "Salmon", "Antique White"]
c2 = Colorable::Color.new(:alice_blue)
c2.prev(:hsb).name # => "Steel Blue"
10.times.map { c2 = c2.prev(:hsb); c2.name } # => ["Steel Blue", "Light Sky Blue", "Sky Blue", "Deep Sky Blue", "Light Blue", "Powder Blue", "Cadet Blue", "Dark Turquoise", "Cyan", "Aqua"]
Colorable::Colorset
多数の色をまとめて扱いたいときは、Colorable::Colorset
を使います。
cs = Colorable::Colorset.new
cs.first(10).map(&:name) # => ["Alice Blue", "Antique White", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "Blanched Almond", "Blue"]
cs.last(10).map(&:hex) # => ["#008080", "#D8BFD8", "#FF6347", "#40E0D0", "#EE82EE", "#F5DEB3", "#FFFFFF", "#F5F5F5", "#FFFF00", "#9ACD32"]
異なる並びのカラーセットを得たいときは、Colorset.[]
メソッドを使います。
cs = Colorable::Colorset[:rgb]
cs.first(10).map(&:name) # => ["Black", "Navy", "Dark Blue", "Medium Blue", "Blue", "Dark Green", "Green2", "Teal", "Dark Cyan", "Deep Sky Blue"]
cs.last(10).map(&:rgb) # => [[255, 240, 245], [255, 245, 238], [255, 248, 220], [255, 250, 205], [255, 250, 240], [255, 250, 250], [255, 255, 0], [255, 255, 224], [255, 255, 240], [255, 255, 255]]
cs = Colorable::Colorset[:hsb]
cs.first(10).map(&:name) # => ["Black", "Dim Gray", "Gray2", "Dark Gray", "Gray", "Silver", "Light Gray", "Gainsboro", "White Smoke", "White"]
cs.last(10).map(&:hsb) # => [[302, 49, 85], [322, 89, 78], [327, 92, 100], [330, 59, 100], [338, 73, 69], [341, 6, 100], [341, 49, 86], [349, 91, 86], [351, 25, 100], [352, 29, 100]]
Gvizとともに使う
さて、ColorableがColor#next
やColorset
を使って色の並びを取得できることがわかりました。これをGviz
を使って視覚的に表現してみます。ここでは名前順、RGB順およびHSB順の色の並びを比較してみますね。Gvizはgem i gviz
で取得します。
まずは名前順です。graph.ruファイルを用意して次のコードを打ち込みます。
# graph.ru
require "colorable"
color = Colorable::Color.new(:black)
global layout:'neato', size:20
nodes shape:'circle', style:'filled'
loop do
nxt = color.next
route color.name.to_id => nxt.name.to_id
[color, nxt].each do |c|
fcolor = c.dark? ? '#FFFFFF' : '#000000'
node c.name.to_id, label:c.name, fillcolor:c.hex, fontcolor:fcolor
end
color = nxt
break if color.name == "Black"
end
save :color, :png
各色をノードとしColor#next
で順に次の色を名前順に呼び出していきます。route
メソッドで隣合うノード同士を順につないでいき、最初の色(”Black”)が現れたところでloopを抜けます。
このファイルのあるディレクトリでgviz
コマンドを実行すれば、以下の出力が得られます。
名前順の色リングができました。
ノードのサイズがばらばらですから、これを固定することでよりビジュアルに訴えるものに仕上げます。
require "colorable"
color = Colorable::Color.new(:black)
global layout:'neato', size:20
+ nodes shape:'circle', style:'filled', width:4
loop do
nxt = color.next
route color.name.to_id => nxt.name.to_id
[color, nxt].each do |c|
fcolor = c.dark? ? '#FFFFFF' : '#000000'
node c.name.to_id, label:c.name, fillcolor:c.hex, fontcolor:fcolor
end
color = nxt
break if color.name == "Black"
end
save :color, :png
出力です。
名前は隠れちゃいましたけど、なかなかキレイですね。しかし色の並びが名前順なので、色変化としての並びはばらばらです。
今度はRGB順でリングを作ってみます。nextに:rgb
の引数を渡します。
require "colorable"
color = Colorable::Color.new(:black)
global layout:'neato', size:20
nodes shape:'circle', style:'filled', width:4
loop do
+ nxt = color.next(:rgb)
route color.name.to_id => nxt.name.to_id
[color, nxt].each do |c|
fcolor = c.dark? ? '#FFFFFF' : '#000000'
node c.name.to_id, label:c.name, fillcolor:c.hex, fontcolor:fcolor
end
color = nxt
break if color.name == "Black"
end
save :color, :png
出力です。
かなりキレイな配色になりました。
しかしRGBカラーモデル(混色系)は各色成分の量で変化していくので、人間の色認識の感覚からはズレていてちょっと不十分な印象です。
今度はHSBカラーモデル(顕色系)を試します。nextの引数を:hsb
に変えます。
require "colorable"
color = Colorable::Color.new(:black)
global layout:'neato', size:20
nodes shape:'circle', style:'filled', width:4
loop do
+ nxt = color.next(:hsb)
route color.name.to_id => nxt.name.to_id
[color, nxt].each do |c|
fcolor = c.dark? ? '#FFFFFF' : '#000000'
node c.name.to_id, label:c.name, fillcolor:c.hex, fontcolor:fcolor
end
color = nxt
break if color.name == "Black"
end
save :color, :png
出力です。
人間が見てより自然な色変化になりました。
(正直もっと綺麗なグラデーションになることを期待したんですけど…もしかしたらHSBの演算が間違っているのかもしれません。あとで検証してみます。)
最後に、ノードのwidthを20にして再度HSBの並びを出力してみます。
Graphvizでこんなに綺麗なリングが描けるなんて、ちょっと感動しませんか?
以上、「Colorable」ライブラリおよびそのGvizにおける使用例を紹介しました。
(追記:2014-3-3) Gvizについてのまとめ頁を作りました。
A Color of His Own by Leo Lionni
blog comments powered by Disqus