Sparkline in unicode

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

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

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*/ auto 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: ▂▁▄▃▆▅█▇