Sparkline in unicode

Revision as of 18:24, 22 June 2013 by rosettacode>Bearophile (Updated D entry)

A sparkline is a graph of successive values laid out horizontally where the height of the line is proportional to the values in succession.

Task
Sparkline in unicode
You are encouraged to solve this task according to the task description, using any language you may know.

Use the following series of Unicode characters to create a program that takes a series of numbers separated by one or more whitespace or comma characters and generates a sparkline-type bar graph of the values on a single line of output.

The eight characters: '▁▂▃▄▅▆▇█'
(Unicode values U+2581 through U+2588).

Use your program to show sparklines for the following input, here on this page:

  1. 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
  2. 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
(note the mix of separators in this second case)!
Notes
  • A space is not part of the generated sparkline.
  • The sparkline may be accompanied by simple statistics of the data such as its range.

D

Translation of: Python

<lang d>void main() {

   import std.stdio, std.range, std.algorithm, std.conv,
          std.string, std.regex;
   "Numbers please separated by space/commas: ".write;
   /*immutable*/ const numbers = readln
                                 .strip
                                 .splitter(r"[\s,]+".regex)
                                 .array /**/
                                 .to!(real[]);
   immutable mm = numbers.reduce!(min, max);
   "min: %5f; max: %5f".writefln(mm[]);
   immutable bars = iota(9601, 9609).map!(i => i.to!dchar).dtext;
   immutable div = (mm[1] - mm[0]) / (bars.length - 1);
   numbers.map!(n => bars[cast(int)((n - mm[0]) / div)]).writeln;

}</lang> The output is the same as the Python entry (but it only accepts one series of values at a time).

FALSE

<lang false>{

 variables:
 s: sign (1 or -1)
 u: current number
 f: current number fraction length
 v: current number is valid
 t: number of numbers read
 x: biggest fraction
 y: smallest number (without fraction)
 z: biggest number (without fraction)

}

{function a: test if top is 0-9, without popping the value, codes 48-57 are in range} [$$47>\57>~&]a:

{function b: test if top is ',' or ' ', without popping the value} [$$',=\' =|]b:

{function c: read a number from the input, given that the first character of the input is already on the stack} [

 1s:0u:0f:0v: {reset values}
 $'-=[1_s:%^]? {if (it is negative) set the sign value to -1 move to next}
 [a;!][48-u;10*+u:1_v:^]# {while (isnumber) do number = number * 10 + decimal and set valid number and move to next}
 $'.=[ {if (it is a decimal) move forward and read fraction}
   %^
   [a;!][48-u;10*+u:f;1+f:1_v:^]# {while (isnumber) do number = number * 10 + decimal and increase fraction length and set valid number and move to next}
 ]?
 $$'-=\'.=|[0v:]? {if next charachter is a '-' or a '.', set invalid}

]c:

{function d: normalize number/fraction from stack to max fraction and push that number} [

 [$x;=~][1+\10*\]# {while (fraction != max) fraction + 1, value * 10}
 % {pop fraction}

]d:

0t: 0x: 1_v: { nothing read, so we are still valid } ^[b;!][%^]# {read away any initial separators} [$1_=~v;&][ {while input != -1 and valid input, leaving input on the stack}

 c;! {read a number}
 t;1+t:u;s;*f;@ {increase count, push number * sign and fraction length onto the stack and bring input back up}
 f;x;>[f;x:]? {set fraction to biggest of current and previous biggest}
 [b;!][%^]# {while (isseparator) move forward}

]# v;~["error at charachter ",]? {if invalid number, tell them when} v;[ {if last number also valid, do the math}

 %        {pop the -1}
 t;2*1-q: {var q: points to next value}
 0p:      {var p: whether min/max have been set}
 [q;1+t;>][ {while q + 1 > t}
   q;ø        {current number}
   q;ø        {current fraction}
   d;!        {normalize}
   p;[$y;\>[$y:]? $z;>[$z:]?]?      {compare min/max}
   p;~[1_p:$y:$z:]?     {if (first)) set min/max}
   q;1-q:     {move pointer}
 ]#
 t;q: {point q to first value}
 [q;0>][ {while q > 0}
   q;1-øy;-7*z;y;-/ {(number - minvalue) * 7 / (maxvalue - minvalue), should result in 0..7}
   9601+,           {print character}
   q;1-q:           {move pointer}
 ]#

]?</lang> This implementation can only accept one series of numbers at a time.

Output:
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
▂▁▄▃▆▅█▇

Groovy

<lang groovy>def sparkline(List<Number> list) {

   def (min, max) = [list.min(), list.max()]
   def div = (max - min) / 7
   list.collect { (char)(0x2581 + (it-min) * div) }.join()

} def sparkline(String text) { sparkline(text.split(/[ ,]+/).collect { it as Double }) }</lang> Test Code <lang groovy>["1 2 3 4 5 6 7 8 7 6 5 4 3 2 1", "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5"].each { dataset ->

   println "  Dataset: $dataset"
   println "Sparkline: ${sparkline(dataset)}"

}</lang>

Output:
  Dataset: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline: ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
  Dataset: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline: ▂▁▄▃▆▅█▇

Java

<lang java> public class Sparkline { String bars="▁▂▃▄▅▆▇█"; public static void main(String[] args) { Sparkline now=new Sparkline(); float[] arr={1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1}; now.display1D(arr); System.out.println(now.getSparkline(arr)); float[] arr1={1.5f, 0.5f, 3.5f, 2.5f, 5.5f, 4.5f, 7.5f, 6.5f}; now.display1D(arr1); System.out.println(now.getSparkline(arr1)); } public void display1D(float[] arr) { for(int i=0;i<arr.length;i++) System.out.print(arr[i]+" "); System.out.println(); } public String getSparkline(float[] arr) { float min=Integer.MAX_VALUE; float max=Integer.MIN_VALUE; for(int i=0;i<arr.length;i++) { if(arr[i]<min) min=arr[i]; if(arr[i]>max) max=arr[i]; } float range=max-min; int num=bars.length()-1; String line=""; for(int i=0;i<arr.length;i++) {

line+=bars.charAt((int)Math.ceil(((arr[i]-min)/range*num))); } return line; } } </lang> Output:

1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 7.0 6.0 5.0 4.0 3.0 2.0 1.0 
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
1.5 0.5 3.5 2.5 5.5 4.5 7.5 6.5 
▂▁▄▃▆▅█▇

Perl 6

<lang perl6>constant @bars = '▁' ... '█'; while prompt 'Numbers separated by anything: ' -> $_ {

   my @numbers = map +*, .comb(/ '-'? \d+ ['.' \d+]? /);
   my ($mn,$mx) = @numbers.minmax.bounds;
   say "min: $mn.fmt('%5f'); max: $mx.fmt('%5f')";
   my $div = ($mx - $mn) / (@bars - 1);
   say @bars[ (@numbers X- $mn) X/ $div ].join;

}</lang>

Output:
Numbers separated by anything: 9 18 27 36 45 54 63 72 63 54 45 36 27 18 9
9 18 27 36 45 54 63 72 63 54 45 36 27 18 9
min: 9.000000; max: 72.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by anything: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
1.5 0.5 3.5 2.5 5.5 4.5 7.5 6.5
min: 0.500000; max: 7.500000
▂▁▄▃▆▅█▇
Numbers separated by anything: 3 2 1 0 -1 -2 -3 -4 -3 -2 -1 0 1 2 3  
min: -4.000000; max: 3.000000
█▇▆▅▄▃▂▁▂▃▄▅▆▇█
Numbers separated by anything: ^D

Python

<lang python>import re try: raw_input except: raw_input = input

  1. Unicode: 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608

try: bar = u'▁▂▃▄▅▆▇█' except: bar = '▁▂▃▄▅▆▇█' barcount = len(bar) - 1 while True:

   line = raw_input('Numbers please separated by space/commas: ')
   numbers = [float(n) for n in re.split(r'[\s,]+', line.strip())]
   mn, mx = min(numbers), max(numbers)
   extent = mx - mn
   sparkline = .join(bar[int( (n - mn) / extent * barcount)]
                       for n in numbers)
   print('min: %5f; max: %5f' % (mn, mx))
   print(sparkline)</lang>
Output:
Numbers separated by space/commas: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
min: 1.000000; max: 7.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by space/commas: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
min: 0.500000; max: 7.500000
▂▁▄▃▆▅█▇

Racket

<lang racket>

  1. lang racket (require syntax/parse)

(define bars "▁▂▃▄▅▆▇█") (define bar-count (string-length bars))

(define (sparks str)

 (define ns (map string->number (string-split str #rx"[ ,]" #:repeat? #t)))
 (define mn (apply min ns))
 (define bar-width (/ (- (apply max ns) mn) (- bar-count 1)))
 (apply string (for/list ([n ns]) (string-ref bars (exact-floor (/ (- n mn) bar-width))))))

(sparks "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1") (sparks "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5") </lang> Output: <lang racket> "▁▂▃▄▅▆▇█▇▆▅▄▃▂▁" "▂▁▄▃▆▅█▇" </lang>

Tcl

Works with: Tcl version 8.6

<lang tcl>package require Tcl 8.6

proc extractValues {series} {

   return [regexp -all -inline {\d+(?:\.\d*)?|\.\d+} $series]

} proc renderValue {min max value} {

   set band [expr {int(8*($value-$min)/(($max-$min)*1.01))}]
   return [format "%c" [expr {0x2581 + $band}]]

} proc sparkline {series} {

   set values [extractValues $series]
   set min [tcl::mathfunc::min {*}$values]
   set max [tcl::mathfunc::max {*}$values]
   return [join [lmap v $values {renderValue $min $max $v}] ""]

}</lang> Demonstrating: <lang tcl>set data {

   "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1"
   "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5"

} foreach series $data {

   puts "Series: $series"
   puts "Sparkline: [sparkline $series]"

}</lang>

Output:
Series: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline: ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Series: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline: ▂▁▄▃▆▅█▇