# \[SOLUTION\] #64: Text::Graph

**URL:** <https://www.ruby-forum.com/t/solution-64-text-graph/53338>\
**Category:** Ruby\
**Created:** [February 1, 2006, 11:31pm UTC](https://www.ruby-forum.com/t/solution-64-text-graph/53338 "2006-02-01T23:31:12Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Martin\_DeMello](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/f17d59/32.png) [@Martin\_DeMello](https://www.ruby-forum.com/u/Martin_DeMello)\
**Post date:** [February 1, 2006, 11:31pm UTC](https://www.ruby-forum.com/t/solution-64-text-graph/53338/1 "2006-02-01T23:31:12Z")

</div>

A port of Text::Graph, which generates pretty ascii bar graphs from  
numeric datasets, like

```
aaaa : (1)
  bb :..* (22)
 ccc :...* (43)

```

dddddd :…\* (500)  
ee :…_(1000)  
f :…_ (300)  
ghi :…\* (50)

It accepts data in the following forms (see the ‘extract’ method):

# { label =\> value, label =\> value, … }

# { :values =\> { label =\> value, …} }

# { :values =\> [values] }

# {:values =\> { label =\> value, label =\> value }, :labels =\> […]}

# {:values =\> [values], :labels =\> [labels]}

# [[label, value], [label, value], …]

# [[values], [labels]]

Numeric parameters:  
:minval  
:maxval  
:maxlen

Boolean parameters:  
:log # logarithmic scale  
:right # label justification

Display parameters:  
:marker  
:fill  
:separator  
:style # {:bar|:line} - sets default values for marker and fill  
:showval # numeric value after bar

martin

# Text::Graph

# Port of Wade Johnson’s Text::Graph for perl

# [Graph.pm - metacpan.org](http://search.cpan.org/src/GWADEJ/Text-Graph-0.23/Graph.pm)

# 

# Author: Martin DeMello [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)

module Enumerable  
def minmax  
min = 1.0/0  
max = -1.0/0  
each {|i|  
min = i if i \< min  
max = i if i \> max  
}  
[min, max]  
end

def map\_with\_index  
a = []  
each\_with\_index {|e, i| a \<\< yield(e,i)}  
a  
end  
end

class TextGraph  
include Math

def initialize(data, params = {})  
@data = extract(data)  
@params = {:style =\> (params[:style] || :bar)}  
apply\_style(@params[:style])  
@params.update(params)  
@params[:separator] ||= " :"  
end

def update\_params(par)  
apply\_style(par[:style]) if par[:style]  
@params.update(par)  
end

def apply\_style(style)  
if style == :bar  
@params[:marker] = “_"  
 @params[:fill] = "_”  
elsif style == :line  
@params[:marker] = ‘\*’  
@params[:fill] = ’ ’  
else  
raise “Invalid style #{style}”  
end  
end

def extract(data)  
if data.is\_a? Array  
if data.length == 2 and data[0].is\_a? Array and data[1].is\_a?  
Array

# [[values], [labels]]

a = {}  
a[:values] = data[0]  
a[:labels] = data[1]  
data = a  
else

# [[label, value], [label, value], …]

a = {:values =\> [], :labels =\> []}  
data.each {|i,j| a[:labels] \<\< i; a[:values] \<\< j}  
end  
end

```
if (data.length == 2) and data[:values] and data[:labels]
  if data[:values].is_a? Array

```

# {:values =\> [values], :labels =\> [labels]}

# do nothing

```
  elsif data[:values].is_a? Hash

```

# {:values =\> { label =\> value, label =\> value }, :labels =\> […]}

a = data[:labels].map {|i| data[:values][i]}  
data[:values] = a  
else  
raise “Invalid valueset”  
end  
elsif (data.length == 1) and data[:values]  
if data[:values].is\_a? Array

# { :values =\> [values] }

data[:labels] = data[:values].map {“”}  
elsif data[:values].is\_a? Hash

# { :values =\> { label =\> value, …} }

data[:labels] = data[:values].keys.sort\_by {|i| i.to\_s}  
data[:values] = data[:labels].map {|i| data[:values][i]}  
else  
raise “Invalid valueset”  
end  
else  
# { label =\> value, label =\> value, … }  
a = {}  
a[:labels] = data.keys.sort\_by {|i| i.to\_s}  
a[:values] = a[:labels].map {|i| data[i]}  
data = a  
end  
data[:labels].map! {|i| i.to\_s}  
data  
end

def make\_within(val, min, max)  
(val \< min) ? min : (val \> max ? max : val)  
end

def makebar(val, m, f)  
val = (val + 0.5).to\_i  
(val \> 0) ? (f\*(val - 1) + m) : “”  
end

def fmt\_labels (right, labels)  
len = labels.map {|i| i.length}.max  
just = right ? :rjust : :ljust  
labels.map {|i| i.send(just, len)}  
end

def make\_labelled\_lines(data)  
labels = fmt\_labels(@params[:right], data[:labels])  
lines = histogram(data)  
lines.zip(labels).map {|line, label| label + @params[:separator] +  
line}  
end

def histogram(data)  
values = data[:values].dup  
minval, maxval, maxlen =  
@params[:minval], @params[:maxval], @params[:maxlen]

```
if @params[:log]
  values.map! {|i| log(i)}
  minval = log(minval) rescue 1 if minval
  maxval = log(maxval) rescue 1 if maxval
end

min, max = values.minmax
minval ||= min
maxval ||= max
maxl = maxval - minval + 1
maxlen ||= maxl
scale = maxlen*1.0/maxl
values = values.map {|i|
  j = make_within(i, minval, maxval) - minval
  makebar(j*scale, @params[:marker], @params[:fill])
}

if(@params[:showval])
  values = values.map_with_index {|v, i|

```

v.ljust(maxlen) + “(#{data[:values][i]})”  
}  
end

```
values

```

end

def to\_s  
make\_labelled\_lines(@data).join(“\n”) + “\n”  
end  
end

if **FILE** == $0

a = TextGraph.new({  
:values =\> [1,2,4,5,10,3,5],  
:labels =\> %w(aaaa bb ccc dddddd ee f ghi)  
})

puts a

# aaaa :

# bb :\*

# ccc :\*\*\*

# dddddd :\*\*\*\*

# ee :\*\*\*\*\*\*\*\*\*

# f :\*\*

# ghi :\*\*\*\*

puts “-------------------------------------------------------------”

a.update\_params(:style =\> :line, :right =\> true, :showval =\> true)  
puts a

# aaaa : (1)

# bb :\* (2)

# ccc : \* (4)

# dddddd : \* (5)

# ee : \* (10)

# f : \* (3)

# ghi : \* (5)

puts “-------------------------------------------------------------”

b = TextGraph.new({ :a=\>1, :b=\>5, :c=\>20, :d=\>10, :e=\>17 }, {:maxlen  
=\> 10})  
puts b

# a :

# b :\*\*

# c :\*\*\*\*\*\*\*\*\*\*

# d :\*\*\*\*\*

# e :\*\*\*\*\*\*\*\*

puts “-------------------------------------------------------------”

c = TextGraph.new({:values =\> { :a=\>1, :b=\>5, :c=\>20, :d=\>10, :e=\>17  
},  
:labels =\> [:a, :c, :d]},  
{:minval =\> 0, :maxval =\> 15, :showval =\> true})  
puts c

# a :\* (1)

# c :\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* (20)

# d :\*\*\*\*\*\*\*\*\*\* (10)

puts “-------------------------------------------------------------”

d = TextGraph.new([[1,22,43,500,1000,300,50], %w(aaaa bb ccc dddddd ee  
f ghi)],  
{ :style =\> :line,  
:right =\> true, # right-justify labels  
:fill =\> ‘.’, # change fill-marker  
:log =\> true, # logarithmic graph  
:showval =\> true # show actual values  
}  
)  
puts d

```
   # aaaa : (1)
   # bb :..* (22)
   # ccc :...* (43)
   # dddddd :.....* (500)
   # ee :......*(1000)
   # f :.....* (300)
   # ghi :...* (50)

```

end
