Sparkline in unicode: Difference between revisions

From Rosetta Code
Content added Content deleted
(added APL example)
(fixed note about indexing)
Line 21: Line 21:


=={{header|APL}}==
=={{header|APL}}==
{{note | this is in a 0-indexed version of APL}}
Note this is in a 0-indexed version of APL
'''Solution''':<lang APL> sparkln←{'▁▂▃▄▅▆▇█'[⌈7×⍵÷⌈/⍵]}</lang>
'''Solution''':<lang APL> sparkln←{'▁▂▃▄▅▆▇█'[⌈7×⍵÷⌈/⍵]}</lang>
'''Example''':<lang APL> sparkln 1,2,7 1 5
'''Example''':<lang APL> sparkln 1,2,7 1 5
▂▃█▂▆</lang>
▂▃█▂▆</lang>
Note: APL accepts the input with commas an spaces naturally. If one wanted to read input as a string, then remove commas and make a vector, they could use ⍎(~x=',')/x←⍞ to do so.
Note: APL accepts the input with commas an spaces naturally. If one wanted to read input as a string, then remove commas and make a vector, they could use ⍎(~x=',')/x←⍞ to do so.

=={{header|AutoHotkey}}==
=={{header|AutoHotkey}}==
{{works with|AutoHotkey_L}}
{{works with|AutoHotkey_L}}

Revision as of 06:20, 22 February 2015

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.

APL

Note → this is in a 0-indexed version of APL Solution:<lang APL> sparkln←{'▁▂▃▄▅▆▇█'[⌈7×⍵÷⌈/⍵]}</lang> Example:<lang APL> sparkln 1,2,7 1 5 ▂▃█▂▆</lang> Note: APL accepts the input with commas an spaces naturally. If one wanted to read input as a string, then remove commas and make a vector, they could use ⍎(~x=',')/x←⍞ to do so.

AutoHotkey

Works with: AutoHotkey_L

<lang AutoHotkey>SetFormat, FloatFast, 0.1 strings := ["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"]

Loop, % strings.MaxIndex() {

   SL := Sparklines(strings[A_Index])
   MsgBox, % "Min: " SL["Min"] ", Max: " SL["Max"] ", Range: " SL["Rng"] "`n" SL["Chars"]

}

Sparklines(s) {

   s := RegexReplace(s, "[^\d\.]+", ",")
   Loop, Parse, s, `,
   {
       Max := A_LoopField > Max ? A_LoopField : Max
       Min := !Min ? Max : A_LoopField < Min ? A_LoopField : Min
   }
   Rng := Max - Min
   Loop, Parse, s, `,
       Chars .= Chr(0x2581 + Round(7 * (A_LoopField - Min) / Rng))
   return, {"Min": Min, "Max": Max, "Rng": Rng, "Chars": Chars}

}</lang>

Output:
Min: 1, Max: 8, Range: 7
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

Min: 0.5, Max: 7.5, Range: 7.0
▂▁▄▃▆▅█▇

C++

<lang cpp>#include <iostream>

  1. include <sstream>
  2. include <vector>
  3. include <cmath>
  4. include <algorithm>
  5. include <locale>

class Sparkline {

   public:
       Sparkline(std::wstring &cs) : charset( cs ){
       }
       virtual ~Sparkline(){
       }
       void print(std::string spark){
           const char *delim = ", ";
           std::vector<float> data;
           // Get first non-delimiter
           std::string::size_type last = spark.find_first_not_of(delim, 0);
           // Get end of token
           std::string::size_type pos = spark.find_first_of(delim, last);
           while( pos != std::string::npos || last != std::string::npos ){
               std::string tok = spark.substr(last, pos-last);
               // Convert to float:
               std::stringstream ss(tok);
               float entry;
               ss >> entry;
               data.push_back( entry );
               last = spark.find_first_not_of(delim, pos);
               pos = spark.find_first_of(delim, last);
           }
           // Get range of dataset
           float min = *std::min_element( data.begin(), data.end() );
           float max = *std::max_element( data.begin(), data.end() );
           float skip = (charset.length()-1) / (max - min);
           std::wcout<<L"Min: "<<min<<L"; Max: "<<max<<L"; Range: "<<(max-min)<<std::endl;
           
           std::vector<float>::const_iterator it;
           for(it = data.begin(); it != data.end(); it++){
               float v = ( (*it) - min ) * skip; 
               std::wcout<<charset[ (int)floor( v ) ];
           }
           std::wcout<<std::endl;
           
       }
   private:
       std::wstring &charset;

};

int main( int argc, char **argv ){

   std::wstring charset = L"\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
   // Mainly just set up utf-8, so wcout won't narrow our characters.
   std::locale::global(std::locale("en_US.utf8"));
   Sparkline sl(charset);
   sl.print("1 2 3 4 5 6 7 8 7 6 5 4 3 2 1");
   sl.print("1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5");
   return 0;

}</lang>

Output:
Min: 1; Max: 8; Range: 7
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Min: 0.5; Max: 7.5; Range: 7
▂▁▄▃▆▅█▇

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 numbers = readln
                       .strip
                       .splitter(r"[\s,]+".regex)
                       .array /**/
                       .to!(real[]);
   immutable mm = numbers.reduce!(min, max);
   writefln("min: %4f, max: %4f", 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).

Elixir

<lang elixir> defmodule RC do

 def sparkline(str) do
   values = str |> String.split(~r/(,| )+/)
                |> Enum.map(&(elem(Float.parse(&1), 0)))
   {min, max} = {Enum.min(values), Enum.max(values)}
   IO.puts Enum.map(values, &(round((&1 - min) / (max - min) * 7 + 0x2581)))
 end 

end </lang> Usage: <lang> str1 = "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1" str2 = "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5"

RC.sparkline(str1) IO.puts "" # newline RC.sparkline(str2) </lang>

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

▂▁▄▃▆▅█▇

F#

<lang fsharp>open System open System.Globalization open System.Text.RegularExpressions

let bars = Array.map Char.ToString ("▁▂▃▄▅▆▇█".ToCharArray())

while true do

   printf "Numbers separated by anything: "
   let numbers =
       [for x in Regex.Matches(Console.ReadLine(), @"-?\d+(?:\.\d*)?") do yield x.Value]
       |> List.map (fun x -> Double.Parse(x, CultureInfo.InvariantCulture))
   if numbers.Length = 0 then System.Environment.Exit(0)
   if numbers.Length = 1 then
       printfn "A sparkline for 1 value is not very useful... ignoring entry"
   else
       let min, max = List.min numbers, List.max numbers
       printfn "min: %5f; max: %5f" min max
       let barsCount = float (bars.GetUpperBound(0))
       numbers
       |> List.map (fun x -> bars.[int ((x - min)/(max - min) * barsCount)])
       |> String.Concat
       |> printfn "%s"</lang>
Output:
Numbers separated by anything: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 
min: 1.000000; max: 8.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by anything: 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: 

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

Go

<lang go>package main

import (

   "bufio"
   "errors"
   "fmt"
   "math"
   "os"
   "regexp"
   "strconv"
   "strings"

)

func main() {

   fmt.Println("Numbers please separated by space/commas:")
   sc := bufio.NewScanner(os.Stdin)
   sc.Scan()
   s, n, min, max, err := spark(sc.Text())
   if err != nil {
       fmt.Println(err)
       return
   }
   if n == 1 {
       fmt.Println("1 value =", min)
   } else {
       fmt.Println(n, "values.  Min:", min, "Max:", max)
   }
   fmt.Println(s)

}

var sep = regexp.MustCompile(`[\s,]+`)

func spark(s0 string) (sp string, n int, min, max float64, err error) {

   ss := sep.Split(s0, -1)
   n = len(ss)
   vs := make([]float64, n)
   var v float64
   min = math.Inf(1)
   max = math.Inf(-1)
   for i, s := range ss {
       switch v, err = strconv.ParseFloat(s, 64); {
       case err != nil:
       case math.IsNaN(v):
           err = errors.New("NaN not supported.")
       case math.IsInf(v, 0):
           err = errors.New("Inf not supported.")
       default:
           if v < min {
               min = v
           }
           if v > max {
               max = v
           }
           vs[i] = v
           continue
       }
       return
   }
   if min == max {
       sp = strings.Repeat("▄", n)
   } else {
       rs := make([]rune, n)
       f := 8 / (max - min)
       for j, v := range vs {
           i := rune(f * (v - min))
           if i > 7 {
               i = 7
           }
           rs[j] = '▁' + i
       }
       sp = string(rs)
   }
   return

}</lang>

Output:
Numbers please separated by space/commas:
1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
15 values.  Min: 1 Max: 8
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers please separated by space/commas:
1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5 
8 values.  Min: 0.5 Max: 7.5
▂▁▄▃▆▅█▇

Numbers please separated by space/commas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
24 values.  Min: 1 Max: 24
▁▁▁▂▂▂▃▃▃▄▄▄▅▅▅▆▆▆▇▇▇███
Numbers please separated by space/commas:
0 99 101 699 701 800
6 values.  Min: 0 Max: 800
▁▁▂▇██
Numbers please separated by space/commas:
0 -.09 -.11 -.69 -.71 -.8
6 values.  Min: -0.8 Max: 0
██▇▂▁▁
Numbers please separated by space/commas:
3 3 3
3 values.  Min: 3 Max: 3
▄▄▄
Numbers please separated by space/commas:
1e99
1 value = 1e+99
▄
Numbers please separated by space/commas:

strconv.ParseFloat: parsing "": invalid syntax

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

Haskell

<lang haskell> import Data.Char (chr) import Data.List.Split (splitOneOf)

toSparkLine :: [Double] -> [Char] toSparkLine xs = map cl xs

   where
       top = maximum xs
       bot = minimum xs
       range = top - bot
       cl x = chr $ 0x2581 + round ((x - bot) / range * 7)

makeSparkLine :: String -> (String, Stats) makeSparkLine xs = (toSparkLine parsed, stats parsed)

   where parsed = map read $ filter (not . null) $ splitOneOf " ," xs
         

data Stats = Stats { minValue, maxValue, rangeOfValues :: Double,

   numberOfValues :: Int }

instance Show Stats where

   show (Stats mn mx r n) = "min: " ++ show mn ++ "; max: " ++ show mx ++
       "; range: " ++ show r ++ "; no. of values: " ++ show n
       

stats :: [Double] -> Stats stats xs = Stats { minValue = mn, maxValue = mx,

   rangeOfValues = mx - mn, numberOfValues = length xs }
   where
       mn = minimum xs
       mx = maximum xs

drawSparkLineWithStats :: String -> IO () drawSparkLineWithStats xs = putStrLn sp >> print st

   where (sp, st) = makeSparkLine xs

main :: IO () main = mapM_ drawSparkLineWithStats

   ["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",
   "3 2 1 0 -1 -2 -3 -4 -3 -2 -1 0 1 2 3",
   "-1000 100 1000 500 200 -400 -700 621 -189 3"]

</lang>

Output:
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
min: 1.0; max: 8.0; range: 7.0; no. of values: 15
▂▁▄▃▆▅█▇
min: 0.5; max: 7.5; range: 7.0; no. of values: 8
█▇▆▅▄▃▂▁▂▃▄▅▆▇█
min: -4.0; max: 3.0; range: 7.0; no. of values: 15
▁▅█▆▅▃▂▇▄▅
min: -1000.0; max: 1000.0; range: 2000.0; no. of values: 10

J

Solution (explicit):<lang j> spkln =: verb define

  	y spkln~ 4 u:16b2581+i.8  NB.  ▁▂▃▄▅▆▇█
  :
  	'MIN MAX' =. (<./ , >./) y
  	N         =. # x
  	x {~ <. (N-1) * (y-MIN) % MAX-MIN
  )</lang>

Solution (tacit):<lang j> spkln =: (4 u:16b2581+i.8)&$: : ([ {~ <:@#@[ * ] (- % >./@[ - ]) <./@])</lang> Solution (look Ma, no hands!):<lang j> spkln =: (4 u:16b2581+i.8)&$: : ([ {~ ((* <:@#)~ ((- % (- >./))~ <./)))</lang> Examples:<lang j> spkln 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

  spkln 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5

▁▅█▆▅▃▂▇▄▅</lang> Notes: J's grammar automatically normalizes numeric vector inputs which use a mix of whitespace and commas. If we wanted to normalize input ourselves, i.e. take string rather than numeric input (e.g. to make apples-for-apples comparisons with the languages easier) we could simply use ". ("eval") as a preprocessor, as in spkln@". (which is simple because it's still taking advantage of J's grammar to normalize numeric vectors).

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

jq

<lang jq>def sparkline:

 min as $min
 | ( (max - $min) / 7 ) as $div
 | map( 9601 +  (. - $min) * $div )
 | implode ;

def string2array:

 def tidy: select( length > 0 );
 [split(" ") | .[] | split(",") | .[] | tidy | tonumber];</lang>

Task

( "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"
)  | string2array | sparkline
Output:
$ jq -n -f -r sparkline.jq
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
▂▁▄▃▆▅█▇

NetRexx

<lang NetRexx>/* NetRexx */ options replace format comments java crossref symbols nobinary

runSample(arg) return

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ method sparkline(spark) private static

 spark = spark.changestr(',', ' ')
 bars = '\u2581 \u2582 \u2583 \u2584 \u2585 \u2586 \u2587 \u2588'
 barK = bars.words()
 nmin = spark.word(1)
 nmax = nmin
 -- get min & max values
 loop iw = 1 to spark.words()
   nval = spark.word(iw)
   nmin = nval.min(nmin)
   nmax = nval.max(nmax)
   end iw
 range = nmax - nmin + 1
 slope = 
 loop iw = 1 to spark.words()    
   point = Math.ceil((spark.word(iw) - nmin + 1) / range * barK)
   slope = slope || bars.word(point)
   end iw
 return slope nmin nmax range

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ method runSample(arg) private static

 -- sample data setup
 parse arg vals
 sparks = 0
 sparks[0] = 0
 if vals =  then do
   si = sparks[0] + 1; sparks[0] = si; sparks[si] = 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
   si = sparks[0] + 1; sparks[0] = si; sparks[si] = '1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5'
   end
 else do
   loop until vals = 
     -- split input on a ! character
     parse vals lst '!' vals
     si = sparks[0] + 1; sparks[0] = si; sparks[si] = lst
     end
   end
 -- run the samples
 loop si = 1 to sparks[0]
   vals = sparks[si]
   parse sparkline(vals) slope .
   say 'Input:        ' vals
   say 'Sparkline:    ' slope
   say
   end si
 
 return

</lang>

Output:
Input:         1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline:     ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

Input:         1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline:     ▂▁▄▃▆▅█▇

Mathematica

<lang Mathematica>toSparkline[data_String] := FromCharacterCode[Round[7 Rescale@Flatten@ImportString[data, "Table", "FieldSeparators" -> {" ", ","}]] + 16^^2581];</lang>

toSparkline["1 2 3 4 5 6 7 8 7 6 5,4 3 2 1 "]
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

toSparkline[" 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5 "]
▂▁▄▃▆▅█▇

Nim

Translation of: Python

<lang nim>import rdstdin, strutils, unicode

const bar = [9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608] const barcount = float(bar.high)

while True:

 let
   line = readLineFromStdin "Numbers please separated by space/commas: "
   numbers = line.split({' ',','}).map(parseFloat)
   mn = min(numbers)
   mx = max(numbers)
   extent = mx - mn
 var sparkline = ""
 for n in numbers:
   let i = int((n-mn) / extent * barcount)
   sparkline.add($TRune(bar[i]))
 echo "min: ", mn.formatFloat(precision = 0), "; max: ", mx.formatFloat(precision = 0)
 echo sparkline</lang>
Output:
Numbers please separated by space/commas: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
min: 1; max: 8
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers please separated by space/commas: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
min: 0.5; max: 7.5
▂▁▄▃▆▅█▇

Perl

<lang perl>binmode(STDOUT, ":utf8");

sub sparkline {

 my $s = shift;
 my @n = split(/[\s,]+/,$s);
 return unless @n;
 my($min,$max) = ($n[0],$n[0]);
 for my $v (@n) { $min = $v if $v < $min; $max = $v if $v > $max; }
 printf "min: %5f; max %5f\n", $min, $max;
 my @bars = map { chr($_) } 0x2581 .. 0x2588;
 my $div = ($max - $min) / $#bars;
 print join("", map { $bars[$div ? ($_-$min) / $div : @bars/2] } @n), "\n";
 1;

}

while (1) {

 print "Numbers separated by spaces/commas: ";
 exit unless sparkline(scalar(<>));

}</lang>

Output:
Numbers separated by spaces/commas: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
min: 1.000000; max 8.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by spaces/commas: 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 spaces/commas: 9 18 27 36 45 54 63 72 63 54 45 36 27 18 9
min: 9.000000; max 72.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by spaces/commas: 3 2 1 0 -1 -2 -3 -4 -3 -2 -1 0 1 2 3
min: -4.000000; max 3.000000
█▇▆▅▄▃▂▁▂▃▄▅▆▇█
Numbers separated by spaces/commas: 12 12 12 12
min: 12.000000; max 12.000000
▁▁▁▁
Numbers separated by spaces/commas:

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

REXX

version 1

Translation of: NetRexx
Works with: ooRexx
Works with: Regina version 3.4

<lang REXX>/* Rexx */

parse arg aaa call runSample aaa return

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sparkline:

 procedure
 parse arg spark
 spark = changestr(',', spark, ' ')
 bars = '▁ ▂ ▃ ▄ ▅ ▆ ▇ █'
 barK = words(bars)
 nmin = word(spark, 1)
 nmax = nmin
 -- get min & max values
 do iw = 1 to words(spark)
   nval = word(spark, iw)
   nmin = min(nval, nmin)
   nmax = max(nval, nmax)
   end iw
 range = nmax - nmin + 1
 slope = 
 do iw = 1 to words(spark)
   point = ceiling((word(spark, iw) - nmin + 1) / range * barK)
   slope = slope || word(bars, point)
   end iw
 return slope nmin nmax range

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ceiling: procedure

 parse arg ceil
 return trunc(ceil) + (ceil > 0) * (ceil \= trunc(ceil))

floor: procedure

 parse arg flor
 return trunc(flor) - (flor < 0) * (flor \= trunc(flor))

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ runSample: procedure

 -- sample data setup
 parse arg vals
 sparks = 0
 sparks.0 = 0
 if vals =  then do
   si = sparks.0 + 1; sparks.0 = si; sparks.si = 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
   si = sparks.0 + 1; sparks.0 = si; sparks.si = '1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5'
   end
 else do
   do until vals = 
     -- split input on a ! character
     parse var vals lst '!' vals
     si = sparks.0 + 1; sparks.0 = si; sparks.si = lst
     end
   end
 -- run the samples
 do si = 1 to sparks.0
   vals = sparks.si
   parse value sparkline(vals) with slope .
   say 'Input:        ' vals
   say 'Sparkline:    ' slope
   say
   end si
 
 return

</lang>

Output:
Input:         1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline:     ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

Input:         1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline:     ▂▁▄▃▆▅█▇

version 2

(A re-work of REXX version 1)
This version works on:

  • all versions of Regina (which may or may not support single line comments)
  • R4 and ROO (which don't support single line comments)
  • older versions of REXX such as PC/REXX and Personal REXX which don't support the changestr BIF

This version also removed some dead code, simplified the program structure and subroutines, added comments.
Single line comments were introduced in Regina 3.4.
Regina 3.6 introducted the options:   single_line_comments and noSingle_line_comments.
It should also be noted that the CMS and TSO versions of REXX (and others) don't support single line comments. <lang rexx>/*REXX program displays a sparkline (spark graph) for a group of values.*/ if arg()==0 then do /*No arguments? Use defaults.*/

                 call sparkGraph  1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
                 call sparkGraph '1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5'
                 end
            else call sparkGraph arg(1)

exit /*stick a fork in it, we're done.*/ /*──────────────────────────────────CEIL subroutine─────────────────────*/ ceil: procedure; parse arg ?; _=trunc(?); return _+(?>0)*(?\=_) /*──────────────────────────────────SPARKGRAPH subroutine───────────────*/ sparkGraph: procedure; parse arg x; say ' input: ' x /*echo values*/ x=translate(x, ' ', ",") /*remove any superfluous commas. */ $='▁▂▃▄▅▆▇█' /*chars to be used for the graph.*/ xmin=word(x,1); xmax=xmin /*assume a minimum and a maximum.*/

   do n=2  to words(x);  _=word(x,n)  /*examine successive words in  X.*/
   xmin=min(_,xmin)                   /*find the minimum value in  X.  */
   xmax=max(_,xmax)                   /*  "   "  maximum   "    "  "   */
   end   /*n*/

z=; do j=1 for words(x) /*build the output spark graph. */

        z=z||substr($,ceil((word(x,j)-xmin+1)/(xmax-xmin+1)*length($)),1)
        end   /*j*/

say 'output: ' z; say /*show the output, + a blank line*/ return</lang>

Output:

using the default input(s)

 input:  1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
output:  ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

 input:  1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
output:  ▂▁▄▃▆▅█▇

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>

Ruby

Translation of: Perl 6

with added protection for input like "0 0 0 0".

<lang ruby>bar = ('▁'..'█').to_a loop {print 'Numbers please separated by space/commas: '

 numbers = gets.split(/[\s,]+/).map(&:to_f)
 min, max = numbers.minmax
 puts "min: %5f; max: %5f"% [min, max]
 div = (max - min) / (bar.size - 1)
 puts min == max ? bar.last*numbers.size : numbers.map{|num| bar[((num - min) / div).to_i]}.join

}</lang>

Output:

Used Go testcases

Numbers please separated by space/commas: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
min: 1.000000; max: 8.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers please 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
▂▁▄▃▆▅█▇
Numbers please separated by space/commas: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
min: 1.000000; max: 24.000000
▁▁▁▁▂▂▂▃▃▃▄▄▄▄▅▅▅▆▆▆▇▇▇█
Numbers please separated by space/commas: 0 99 101 699 701 800
min: 0.000000; max: 800.000000
▁▁▁▇▇█
Numbers please separated by space/commas: 0 -.09 -.11 -.69 -.71 -.8
min: -0.800000; max: 0.000000
█▇▇▁▁▁
Numbers please separated by space/commas: 3 3 3
min: 3.000000; max: 3.000000
███
Numbers please separated by space/commas: 1e99
min: 999999999999999967336168804116691273849533185806555472917961779471295845921727862608739868455469056.000000; max: 999999999999999967336168804116691273849533185806555472917961779471295845921727862608739868455469056.000000
█

Scala

<lang scala>def mkSparks( numStr:String ) : String =

 numStr.split( "[\\s,]+" ).map(_.toFloat) match {
   case v if v.isEmpty => ""
   case v if v.length == 1 => "\u2581"
   case v =>
     (for( i <- v;
           s = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588".toCharArray;
           d = (v.max - v.min) / (s.length - 1)
      ) yield s( ((i - v.min) / d).toInt)).mkString
 }

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

// A random test... println( mkSparks( Stream.continually( math.abs(util.Random.nextInt % 8)).take(64).mkString(" ") ))</lang>

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

Seed7

<lang seed7>$ include "seed7_05.s7i";

 include "scanfile.s7i";
 include "float.s7i";
 include "utf8.s7i";

const func array float: readDataLine is func

 result
   var array float: data is 0 times 0.0;
 begin
   write("Numbers separated by anything: ");
   IN.bufferChar := getc(IN);
   skipSpace(IN);
   while IN.bufferChar <> '\n' do
     data &:= float parse getNumber(IN);
     skipSpace(IN);
     if IN.bufferChar = ',' then
       IN.bufferChar := getc(IN);
     end if;
     skipSpace(IN);
   end while;
 end func;


const proc: main is func

 local
   const string: bars is "▁▂▃▄▅▆▇█";
   var array float: data is 0 times 0.0;
   var float: min is 0.0;
   var float: max is 0.0;
   var float: number is 0.0;
   var integer: index is 0;
 begin
   OUT := STD_UTF8_OUT;
   data  := readDataLine;
   while length(data) >= 1 do
     min := data[1];
     max := data[1];
     for number range data do
       if number < min then
         min := number;
       end if;
       if number > max then
         max := number;
       end if;
     end for;
     for number range data do
       index := succ(min(trunc((number - min) * 8.0 / max), 7));
       write(bars[index]);
     end for;
     writeln;
     data  := readDataLine;
   end while;
 end func;</lang>
Output:
Numbers separated by anything: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by anything: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
▂▁▄▃▆▅█▇
Numbers separated by anything: 

Sidef

Translation of: Ruby

<lang ruby>var bar = '▁'..'█'; loop {

   print 'Numbers, please, separated by space/commas: ';
   var numbers = read(String).trim.split(/[\s,]+/).map{.to_f};
   var (min, max) = numbers.minmax...;
   say "min: %5f; max: %5f"%[min, max];
   var div = ((max - min) / bar.offset);
   say (min == max ? bar.last*numbers.len : numbers.map{|num| bar[(num - min) / div]}.join);

};</lang>

Output:
Numbers, please, separated by space/commas: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
min: 1.000000; max: 8.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers, please, 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
▂▁▄▃▆▅█▇

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

zkl

<lang zkl>var sparks=[0x2581..0x2588].apply("toString",-8); // int.toString(-8)-->UTF-8 var sl=(sparks.len()-1);

fcn sparkLine(xs){

  min:=(0.0).min(xs); max:=(0.0).max(xs);  // min/max are float reguardless of xs
  range:=max-min;  // float
  println("Range [",min,"-",max,"]", xs);
  xs.pump(String,'wrap(x){ sparks[(x - min)*sl/range] }).println();

}</lang> <lang zkl>one:="1 2 3 4 5 6 7 8 7 6 5 4 3 2 1".split(" ").apply("toInt"); two:=("1.5, 0.5 3.5, 2.5 5.5 4.5 7.5, 6.5" - ",").split(" ").apply("toFloat"); sparkLine(one); sparkLine(two);</lang>

Output:
Range [1-8]: L(1,2,3,4,5,6,7,8,7,6,5,4,3,2,1)
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Range [0.5-7.5]: L(1.5,0.5,3.5,2.5,5.5,4.5,7.5,6.5)
▂▁▄▃▆▅█▇