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.


Task

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←{'▁▂▃▄▅▆▇█'[⌊0.5+7×⍵÷⌈/⍵]}</lang> Example:<lang APL> sparkln 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 ▂▃▄▅▅▆▇█▇▆▅▅▄▃▂

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

▂▁▄▃▆▅█▇ </lang> Note → APL accepts the input with commas and spaces naturally. If one wanted to read input as a string they could use ⍎⍞ to do so.


AppleScript

<lang AppleScript>use framework "Foundation" -- Yosemite onwards – for splitting by regex

-- SPARKLINE -----------------------------------------------------------------

-- sparkLine :: [Num] -> String on sparkLine(xs)

   set min to minimumBy(my numericOrdering, xs)
   set max to maximumBy(my numericOrdering, xs)
   set dataRange to max - min
   
   -- scale :: Num -> Num
   script scale
       on |λ|(x)
           ((x - min) * 7) / dataRange
       end |λ|
   end script
   
   -- bucket :: Num -> String
   script bucket
       on |λ|(n)
           if n ≥ 0 and n < 8 then
               item (n + 1 as integer) of "▁▂▃▄▅▆▇█"
           else
               missing value
           end if
       end |λ|
   end script
   
   intercalate("", map(bucket, map(scale, xs)))

end sparkLine

-- numericOrdering :: Num -> Num -> (-1 | 0 | 1) on numericOrdering(a, b)

   if a < b then
       -1
   else
       if a > b then
           1
       else
           0
       end if
   end if

end numericOrdering


-- TEST ---------------------------------------------------------------------- on run

   -- splitNumbers :: String -> [Real]
   script splitNumbers
       script asReal
           on |λ|(x)
               x as real
           end |λ|
       end script
       
       on |λ|(s)
           map(asReal, splitRegex("[\\s,]+", s))
       end |λ|
   end script
   
   map(sparkLine, map(splitNumbers, ["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"]))
   
   -- {"▁▂▃▄▅▆▇█▇▆▅▄▃▂▁","▂▁▄▃▆▅█▇","█▇▆▅▄▃▂▁▂▃▄▅▆▇█","▁▅█▆▅▃▂▇▄▅"}

end run


-- GENERIC LIBRARY FUNCTIONS -------------------------------------------------

-- foldl :: (a -> b -> a) -> a -> [b] -> a on foldl(f, startValue, xs)

   tell mReturn(f)
       set v to startValue
       set lng to length of xs
       repeat with i from 1 to lng
           set v to |λ|(v, item i of xs, i, xs)
       end repeat
       return v
   end tell

end foldl

-- intercalate :: Text -> [Text] -> Text on intercalate(strText, lstText)

   set {dlm, my text item delimiters} to {my text item delimiters, strText}
   set strJoined to lstText as text
   set my text item delimiters to dlm
   return strJoined

end intercalate

-- map :: (a -> b) -> [a] -> [b] on map(f, xs)

   tell mReturn(f)
       set lng to length of xs
       set lst to {}
       repeat with i from 1 to lng
           set end of lst to |λ|(item i of xs, i, xs)
       end repeat
       return lst
   end tell

end map

-- maximumBy :: (a -> a -> Ordering) -> [a] -> a on maximumBy(f, xs)

   script max
       property cmp : f
       on |λ|(a, b)
           if a is missing value or cmp(a, b) < 0 then
               b
           else
               a
           end if
       end |λ|
   end script
   
   foldl(max, missing value, xs)

end maximumBy

-- minimumBy :: (a -> a -> Ordering) -> [a] -> a on minimumBy(f, xs)

   script min
       property cmp : f
       on |λ|(a, b)
           if a is missing value or cmp(a, b) > 0 then
               b
           else
               a
           end if
       end |λ|
   end script
   
   foldl(min, missing value, xs)

end minimumBy

-- Lift 2nd class handler function into 1st class script wrapper -- mReturn :: Handler -> Script on mReturn(f)

   if class of f is script then
       f
   else
       script
           property |λ| : f
       end script
   end if

end mReturn

-- regexMatches :: RegexPattern -> String -> [{location:Int, length:Int}] on regexMatches(strRegex, str)

   set ca to current application
   set oRgx to ca's NSRegularExpression's regularExpressionWithPattern:strRegex ¬
       options:((ca's NSRegularExpressionAnchorsMatchLines as integer)) |error|:(missing value)
   set oString to ca's NSString's stringWithString:str
   set oMatches to oRgx's matchesInString:oString options:0 range:{location:0, |length|:oString's |length|()}
   
   set lstMatches to {}
   set lng to count of oMatches
   repeat with i from 1 to lng
       set end of lstMatches to range() of item i of oMatches
   end repeat
   lstMatches

end regexMatches

-- splitRegex :: RegexPattern -> String -> [String] on splitRegex(strRegex, str)

   set lstMatches to regexMatches(strRegex, str)
   if length of lstMatches > 0 then
       script preceding
           on |λ|(a, x)
               set iFrom to start of a
               set iLocn to (location of x)
               
               if iLocn > iFrom then
                   set strPart to text (iFrom + 1) thru iLocn of str
               else
                   set strPart to ""
               end if
               {parts:parts of a & strPart, start:iLocn + (length of x) - 1}
           end |λ|
       end script
       
       set recLast to foldl(preceding, {parts:[], start:0}, lstMatches)
       
       set iFinal to start of recLast
       if iFinal < length of str then
           parts of recLast & text (iFinal + 1) thru -1 of str
       else
           parts of recLast & ""
       end if
   else
       {str}
   end if

end splitRegex</lang>

{"▁▂▃▄▅▆▇█▇▆▅▄▃▂▁","▂▁▄▃▆▅█▇","█▇▆▅▄▃▂▁▂▃▄▅▆▇█","▁▅█▆▅▃▂▇▄▅"}

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

This seemingly simple task turns out to be very complicated for languages like C. As the characters to be printed are Unicode, there is no platform independent way to do so. The implementation below works on Linux but not on Windows, detailed investigations show that there is no way, at least no reliable and/or easily reproducible way, to accomplish this task via C on Windows. This is not due to any lacunae in C but due to the vagaries of the Windows Command shell.

Linux version

Accepts data via command line, prints out usage on incorrect invocation. <lang C>

  1. include<string.h>
  2. include<stdlib.h>
  3. include<locale.h>
  4. include<stdio.h>
  5. include<wchar.h>
  6. include<math.h>

int main(int argC,char* argV[]) { double* arr,min,max; char* str; int i,len; if(argC == 1) printf("Usage : %s ",argV[0]); else{ arr = (double*)malloc((argC-1)*sizeof(double)); for(i=1;i<argC;i++){ len = strlen(argV[i]);

if(argV[i][len-1]==','){ str = (char*)malloc(len*sizeof(char)); strncpy(str,argV[i],len-1); arr[i-1] = atof(str); free(str); } else arr[i-1] = atof(argV[i]); if(i==1){ min = arr[i-1]; max = arr[i-1]; } else{ min=(min<arr[i-1]?min:arr[i-1]); max=(max>arr[i-1]?max:arr[i-1]); } }

printf("\n%Max : %lf,Min : %lf,Range : %lf\n",max,min,max-min);

setlocale(LC_ALL, "");

for(i=1;i<argC;i++){ printf("%lc", (wint_t)(9601 + (int)ceil((arr[i-1]-min)/(max-min)*7))); } } return 0; } </lang> Invocation and output :

/home/aamrun/rosettaCode>./sparkLine 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Max : 8.000000,Min : 1.000000,Range : 7.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
/home/aamrun/rosettaCode>./sparkLine 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Max : 7.500000,Min : 0.500000,Range : 7.000000
▂▁▄▃▆▅█▇

C++

<lang cpp>

  1. include <iostream>
  2. include <sstream>
  3. include <vector>
  4. include <cmath>
  5. include <algorithm>
  6. 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
▂▁▄▃▆▅█▇

Clojure

<lang Clojure>(defn sparkline [nums]

 (let [sparks   "▁▂▃▄▅▆▇█"
       high     (apply max nums)
       low      (apply min nums)
       spread   (- high low)
       quantize #(Math/round (* 7.0 (/ (- % low) spread)))]
       (apply str (map #(nth sparks (quantize %)) nums))))

(defn spark [line]

 (if line
   (let [nums (read-string (str "[" line "]"))] 
     (println (sparkline nums))
     (recur (read-line)))))

(spark (read-line))</lang>

Output:
$ clj sparkline.clj <<<$'1 2 3 4 5 6 7 8 7 6 5 4 3 2 1\n1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5'
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
▂▁▄▃▆▅█▇

Common Lisp

<lang lisp>(defun buckets (numbers)

 (loop with min = (apply #'min numbers)
       with max = (apply #'max numbers)
       with width = (/ (- max min) 7)
       for base from (- min (/ width 2)) by width
       repeat 8
       collect (cons base (+ base width))))

(defun bucket-for-number (number buckets)

 (loop for i from 0
       for (min . max) in buckets
       when (and (<= min number) (< number max))
         return i))

(defun sparkline (numbers)

 (loop with buckets = (buckets numbers)
       with sparks = "▁▂▃▄▅▆▇█"
       with sparkline = (make-array (length numbers) :element-type 'character)
       with min = (apply #'min numbers)
       with max = (apply #'max numbers)
       for number in numbers
       for i from 0
       for bucket = (bucket-for-number number buckets)
       do (setf (aref sparkline i) (char sparks bucket))
       finally (format t "Min: ~A, Max: ~A, Range: ~A~%" min max (- max min))
               (write-line sparkline)))

(defun string->numbers (string)

 (flet ((delimiterp (c)
          (or (char= c #\Space) (char= c #\,))))
   (loop for prev-end = 0 then end
         while prev-end
         for start = (position-if-not #'delimiterp string :start prev-end)
         for end = (position-if #'delimiterp string :start start)
         for number = (read-from-string string t nil :start start :end end)
         do (assert (numberp number))
         collect number)))

(defun string->sparkline (string)

 (sparkline (string->numbers string)))

(string->sparkline "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1") (string->sparkline "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5")</lang>

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

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_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


Or, stripping back a little: <lang haskell>import Data.List.Split (splitOneOf) import Data.List (findIndex) import Control.Arrow ((&&&))

sparkLine :: [Float] -> String sparkLine xs =

 let (mn, mx) = (minimum &&& maximum) xs
     w = (mx - mn) / 8
     lbounds = ((mn +) . (w *)) <$> [1 .. 7]
 in fmap
      (\x ->
          case findIndex (> x) lbounds of
            Nothing -> '█'
            Just i -> "▁▂▃▄▅▆▇" !! i)
      xs

parseFloats :: String -> [Float] parseFloats = fmap read . filter (not . null) . splitOneOf " ,"

main :: IO () main =

 mapM_
   putStrLn
   ((sparkLine . parseFloats) <$>
    [ "0, 1, 19, 20"
    , "0, 999, 4000, 4999, 7000, 7999"
    , "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"
    ])</lang>
Output:
▁▁██
▁▁▅▅██
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
▂▁▄▃▆▅█▇

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 =: (u:9601+i.8)&$: : ([ {~ ((<.@* <:@#)~ ((- % (- >./))~ <./)))</lang>

Examples:

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

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).

Note also: the font my browser uses to render these sparkline characters looks awful. Other fonts look better.

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


JavaScript

ES6

<lang JavaScript>(() => {

   'use strict';
   const main = () => {
       // sparkLine :: [Num] -> String
       const sparkLine = xs => {
           const hist = dataBins(8)(xs);
           return unlines([
               concat(map(
                   i => '▁▂▃▄▅▆▇█' [i],
                   hist.indexes
               )),
               unwords(xs),
               [
                   'Min: ' + hist.min,
                   'Mean: ' + hist.mean.toFixed(2),
                   'Median: ' + hist.median,
                   'Max: ' + hist.max,
               ].join('\t'),
               
           ]);
       };


       // dataBins :: Int -> [Num] ->
       //      {indexes:: [Int], min:: Float, max:: Float,
       //        range :: Float, lbounds :: [Float]}
       const dataBins = intBins => xs => {
           const
               iLast = intBins - 1,
               ys = sort(xs),
               mn = ys[0],
               mx = last(ys),
               rng = mx - mn,
               w = rng / intBins,
               lng = xs.length,
               mid = lng / 2,
               lbounds = map(
                   i => mn + (w * i),
                   enumFromTo(1, iLast)
               );
           return {
               indexes: map(
                   x => {
                       const mb = findIndex(b => b > x, lbounds);
                       return mb.Nothing ? (
                           iLast
                       ) : mb.Just;
                   },
                   xs
               ),
               lbounds: lbounds,
               min: mn,
               median: even(lng) ? (
                   sum([ys[mid - 1], ys[mid]]) / 2
               ) : ys[Math.floor(mid)],
               mean: sum(xs) / lng,
               max: mx,
               range: rng
           };
       };
       // numbersFromString :: String -> [Float]
       const numbersFromString = s =>
           map(x => parseFloat(x, 10),
               s.split(/[,\s]+/)
           );
       return unlines(map(
           compose(sparkLine, numbersFromString),
           [
               '0, 1, 19, 20',
               '0, 999, 4000, 4999, 7000, 7999',
               '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'
           ]
       ));
   };
   // GENERIC FUNCTIONS ----------------------------
   // Just :: a -> Maybe a
   const Just = x => ({
       type: 'Maybe',
       Nothing: false,
       Just: x
   });
   // Nothing :: Maybe a
   const Nothing = () => ({
       type: 'Maybe',
       Nothing: true,
   });
   // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
   const compose = (f, g) => x => f(g(x));
   // concat :: a -> [a]
   // concat :: [String] -> String
   const concat = xs =>
       0 < xs.length ? (() => {
           const unit = 'string' !== typeof xs[0] ? (
               []
           ) : ;
           return unit.concat.apply(unit, xs);
       })() : [];


   // enumFromTo :: (Int, Int) -> [Int]
   const enumFromTo = (m, n) =>
       Array.from({
           length: 1 + n - m
       }, (_, i) => m + i);
   // even :: Int -> Bool
   const even = n => 0 === n % 2;
   // last :: [a] -> a
   const last = xs =>
       0 < xs.length ? xs.slice(-1)[0] : undefined;
   // map :: (a -> b) -> [a] -> [b]
   const map = (f, xs) =>
       (Array.isArray(xs) ? (
           xs
       ) : xs.split()).map(f);
   // sort :: Ord a => [a] -> [a]
   const sort = xs => xs.slice()
       .sort((a, b) => a < b ? -1 : (a > b ? 1 : 0));


   // findIndex :: (a -> Bool) -> [a] -> Maybe Int
   const findIndex = (p, xs) => {
       const
           i = (
               'string' !== typeof xs ? (
                   xs
               ) : xs.split()
           ).findIndex(p);
       return -1 !== i ? (
           Just(i)
       ) : Nothing();
   };
   // sum :: [Num] -> Num
   const sum = xs => xs.reduce((a, x) => a + x, 0);
   // unlines :: [String] -> String
   const unlines = xs => xs.join('\n');
   // unwords :: [String] -> String
   const unwords = xs => xs.join(' ');
   // MAIN ---
   return main();

})();</lang>

Output:
▁▁██
0 1 19 20
Min: 0    Mean: 10.00    Median: 10    Max: 20

▁▁▅▅██
0 999 4000 4999 7000 7999
Min: 0    Mean: 4166.17    Median: 4499.5    Max: 7999

▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Min: 1    Mean: 4.27    Median: 4    Max: 8

▂▁▄▃▆▅█▇
1.5 0.5 3.5 2.5 5.5 4.5 7.5 6.5
Min: 0.5    Mean: 4.00    Median: 4    Max: 7.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
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
▂▁▄▃▆▅█▇

Julia

Works with: Julia version 0.6

<lang julia>function sparklineit(arr::Vector{<:Integer})

   sparkchars = '\u2581':'\u2588'
   dyn = length(sparkchars)
   lo, hi = extrema(arr)
   b = @. max(ceil(Int, dyn * (arr - lo) / (hi - lo)), 1)
   return join(sparkchars[b])

end

v = rand(0:10, 10) println("$v → ", sparklineit(v)) v = 10rand(10) println("$(round.(v, 2)) → ", sparklineit(v))</lang>

Output:
[6, 3, 9, 5, 1, 10, 0, 1, 3, 6] → ▅▃█▄▁█▁▁▃▅
[0.57, 0.14, 4.73, 6.61, 6.9, 0.8, 9.71, 7.39, 2.75, 5.7] → ▁▁▄▆▆▁█▇▃▅

Kotlin

Translation of: Java

<lang scala>internal const val bars = "▁▂▃▄▅▆▇█" internal const val n = bars.length - 1

fun <T: Number> Iterable<T>.toSparkline(): String {

   var min = Double.MAX_VALUE
   var max = Double.MIN_VALUE
   val doubles = map { it.toDouble() }
   doubles.forEach { i -> when { i < min -> min = i; i > max -> max = i } }
   val range = max - min
   return doubles.fold("") { line, d -> line + bars[Math.ceil((d - min) / range * n).toInt()] }

}

fun String.toSparkline() = replace(",", "").split(" ").map { it.toFloat() }.toSparkline()

fun main(args: Array<String>) {

   val s1 = "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1"
   println(s1)
   println(s1.toSparkline())
   val s2 = "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5"
   println(s2)
   println(s2.toSparkline())

}</lang>

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

LiveCode

<lang LiveCode>command sparklines listOfNums

   local utfbase=0x2581
   local tStats, utfp, tmin,tmax,trange
   put listOfNums into tStats
   replace ", " with space in tStats
   replace space with comma in tStats
   put min(tStats) into tmin
   put max(tStats) into tmax
   put tmax - tmin into trange
   put "Min:" && tmin && tab into plot
   put "Max:" && tmax && tab after plot
   put "Range:" && trange && tab after plot
   put "Mean" && average(tStats) && tab after plot
   put "Stdev:" && standardDeviation(tStats) && tab after plot
   put "Variance:" && variance(tStats) && return after plot
   
   repeat for each item i in tStats
       put  (round(i - tmin/trange * 7)) + utfbase into utfp 
       put numToCodepoint(utfp) after plot
   end repeat
   put plot

end sparklines</lang>

Test

sparklines("1 2 3 4 5 6 7 8 7 6 5 4 3 2 1")
Min: 1 	Max: 8 	Range: 7 	Mean 4.266667 	Stdev: 2.250926 	Variance: 5.066667 
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

sparklines("1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5")
Min: 0.5 	Max: 7.5 	Range: 7 	Mean 4 	Stdev: 2.44949 	Variance: 6 
▂▁▄▃▆▅█▇

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

M2000 Interpreter

This statement a=cdr(a) make a new array from a excluding the first item, so can be return the empty array (,). In Row$(a) a is passing by value, but is a pointer to array, so we get a pointer to point to same array. When we do the a=cdr(a) we change pointer so variable (a) point to a new array.


The first program use auto arrays (or tuple), so we have to convert to string for output to clipboard (Print dat can be used to print the array as is to screen), and the second program get the input as string, so we have to make it an array at execution time, using then param() for inline parameters in expressions. A Random(param("1,10")) is the same as Random(1,10) (we can use only literal values in param(string_arg)). Because dat get from dat$ only the data, we can use Eval$("("+dat$+")"). Eval$() get all string as an expression for evaluation.


<lang M2000 Interpreter> Module CheckIt {

     Function Row$(a) {
         def item$(a)=str$(car(a)#val(0),0)
         rep$=item$(a)
         while len(a)
         rep$+=", "+item$(a)
         a=cdr(a)
         End While
         =rep$
     }
     Font "Dejavu Sans Mono"
     Cls
     Const bar$="▁▂▃▄▅▆▇█"
     Document doc$
     data1=(1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1)
     data2=(1.5, 0.5, 3.5, 2.5, 5.5, 4.5, 7.5, 6.5)
     SparkLine(data1)
     SparkLine(data2)
     Clipboard doc$
     Sub SparkLine(dat as array)
           Local min=dat#min(), range=(dat#max()-dat#min()), range1=7/range
           Local item, rep$="Input:"+Row$(dat)+{
           }
           item=each(dat)
           While item
           rep$+=Mid$(bar$,(array(item)-min)*range1+1 ,1)
           End While
           rep$+=" ("+str$(range,1033)+")"
           doc$=rep$+{
           }
           Report rep$
     End Sub

} Checkit </lang>

Function Param() get a string an put it as inline code in expressions

<lang M2000 Interpreter> Module CheckIt {

     Font "Dejavu Sans Mono"
     Cls
     Const bar$="▁▂▃▄▅▆▇█"
     Document doc$
     data1$="1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1"
     data2$="1.5, 0.5, 3.5, 2.5, 5.5, 4.5, 7.5, 6.5"
     SparkLine(data1$)
     SparkLine(data2$)
     Clipboard doc$
     Sub SparkLine(dat$)
           dat=(param(dat$))
           Rem dat=eval$("("+dat$+")")
           Local min=dat#min(), range=(dat#max()-dat#min()), range1=7/range
           Local item, rep$="Input:"+dat$+{
           }
           item=each(dat)
           While item
           rep$+=Mid$(bar$,(array(item)-min)*range1+1 ,1)
           End While
           rep$+=" ("+str$(range,1033)+")"
           doc$=rep$+{
           }
           Report rep$
     End Sub

} Checkit </lang>

Third program use strings for stack input, which skip coma as white space. We have to pass to current stack, here calling a function (which always have a new stack for values). First we flush the stack (make it empty), then we use Stack Dat$ to parse the dat$ (also empty the string). Last we rtuern an array using array() using as argument a stack object. Read only function [] do two things replace the current stack object with an empty one, and return the old stack.


<lang M2000 Interpreter> Module CheckIt {

     Function ExtractDat(dat$) {
           Flush
           Stack dat$
           =array([])
     }
     Font "Dejavu Sans Mono"
     Cls
     Const bar$="▁▂▃▄▅▆▇█"
     Document doc$
     data1$="1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1"
     data2$="1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5 "
     SparkLine(data1$)
     SparkLine(data2$)
     Clipboard doc$
     Sub SparkLine(dat$)
           dat=ExtractDat(dat$)
           Rem dat=eval$("("+dat$+")")
           Local min=dat#min(), range=(dat#max()-dat#min()), range1=7/range
           Local item, rep$="Input:"+dat$+{
           }
           item=each(dat)
           While item
           rep$+=Mid$(bar$,(array(item)-min)*range1+1 ,1)
           End While
           rep$+=" ("+str$(range,1033)+")"
           doc$=rep$+{
           }
           Report rep$
     End Sub

} Checkit </lang>

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

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"); our @sparks=map {chr} 0x2581 .. 0x2588; sub sparkline(@) {

   my @n=map {0+$_} grep {length} @_ or return "";
   my($min,$max)=($n[0])x2;
   if (@n>1) {
       for (@n[1..$#n]) {
           if    ($_<$min) { $min=$_ }
           elsif ($_>$max) { $max=$_ }
       }
   }
   my $sparkline="";
   for(@n) {
       my $height=int( $max==$min ? @sparks/2 : ($_-$min)/($max-$min)*@sparks );
       $height=$#sparks if $height>$#sparks;
       $sparkline.=$sparks[$height];
   }
   my $summary=sprintf "%d values; range %s..%s", scalar(@n), $min, $max;
   return wantarray ? ($summary, "\n", $sparkline, "\n") : $sparkline;

}

  1. one number per line
  2. print sparkline( <> );
  1. in scalar context, get just the sparkline without summary or trailing newline
  2. my $sl=sparkline( <> ); print $sl;
  1. one sparkline per line

print sparkline( split /[\s,]+/ ) while <>; </lang>

Output:
$ echo 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 | perl -Mstrict -w spark.pl # strict and warn not needed but it passes both
15 values; range 1..8
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

$ echo 0 1 19 20 | perl spark.pl
4 values; range 0..20
▁▁██

$ echo 0 999 4000 4999 7000 7999  | perl spark.pl
6 values; range 0..7999
▁▁▅▅██

$ echo 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5 | perl spark.pl
8 values; range 0.5..7.5
▂▁▄▃▆▅█▇

$ echo -9e9 1.2345 6e5  | perl spark.pl
3 values; range -9000000000..600000
▁██

$ echo 12 12 12 12 | perl spark.pl
4 values; range 12..12
▅▅▅▅

Most Perl builds have IEEE-754 floats:

$ echo {1..8}e307  | perl spark.pl
8 values; range 1e+307..8e+307
▁▂▃▄▅▆▇█

$ echo {1..8}e308  | perl spark.pl  
8 values; range 1e+308..Inf
▁▁▁▁▁▁▁▁

$ echo -{1..8}e307 | perl spark.pl  
8 values; range -8e+307..-1e+307
█▇▆▅▄▃▂▁

Perl 6

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

   my @numbers = map +*, .comb(/ '-'? [[\d+ ['.' \d*]?] | ['.' \d+]] /);
   my ($mn,$mx) = @numbers.minmax.bounds;
   say "min: $mn.fmt('%5f'); max: $mx.fmt('%5f')";
   say @bars[ @numbers.map: { @bars * ($_ - $mn) / ($mx - $mn) min @bars - 1 } ].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

Phix

Works fine on Linux, with or without unicode_console.e as that does little apart from check environment settings for clues, but on my windows box, on which it invokes kernel32/SetConsoleOutputCP(CP_UTF8), unicode_console.e improves it only slightly, getting just two of the eight characters right. Changing the font on the windows(10) console to "NSimSum" or "SimSun-ExtB" (manually, by right clicking on the title bar and selecting properties/fonts) improved things considerably. <lang Phix>include builtins\unicode_console.e -- (0.8.0+)

constant tests = {"0 1 19 20", "0 0 1 1",

                 "0 999 4000 4999 7000 7999", "1 1 2 2 3 3",
                 "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"}

for i=1 to length(tests) do

   sequence ti = split_any(tests[i]," ,",no_empty:=true)
   for j=1 to length(ti) do
       {{ti[j]}} = scanf(ti[j],"%f")
   end for
   atom mn = min(ti),
        mx = max(ti),
        range = mx-mn
   printf(1,"Min :%g, Max :%g, Range :%g\n",{mn,mx,range})

   if unicode_console() then
       for j=1 to length(ti) do
           ti[j] = #2581 + min(7,floor((ti[j]-mn)/range*8))
       end for
       printf(1,"%s\n",{utf32_to_utf8(ti)})
   else
       puts(1,"unicode is not supported\n")
   end if

end for</lang>

Output:
Min :0, Max :20, Range :20
▁▁██
Min :0, Max :1, Range :1
▁▁██
Min :0, Max :7999, Range :7999
▁▁▅▅██
Min :1, Max :3, Range :2
▁▁▅▅██
Min :1, Max :8, Range :7
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Min :0.5, Max :7.5, Range :7
▂▁▄▃▆▅█▇

PicoLisp

<lang PicoLisp>(de sparkLine (Lst)

  (let (Min (apply min Lst)  Max (apply max Lst)  Rng (- Max Min))
     (for N Lst
        (prin
           (char (+ 9601 (*/ (- N Min) 7 Rng)) ) ) )
     (prinl) ) )</lang>

Test: <lang PicoLisp>(sparkLine (str "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1")) (sparkLine (scl 1 (str "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5")))</lang> Output:

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

Python

<lang python># -*- coding: utf-8 -*-

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

bar = '▁▂▃▄▅▆▇█' barcount = len(bar)

def sparkline(numbers):

   mn, mx = min(numbers), max(numbers)
   extent = mx - mn
   sparkline = .join(bar[min([barcount - 1,
                                int((n - mn) / extent * barcount)])]
                       for n in numbers)
   return mn, mx, sparkline

if __name__ == '__main__':

   import re
   
   for line in ("0 0 1 1; 0 1 19 20; 0 999 4000 4999 7000 7999;"
                "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 ").split(';'):
       print("\nNumbers:", line)
       numbers = [float(n) for n in re.split(r'[\s,]+', line.strip())]
       mn, mx, sp = sparkline(numbers)
       print('  min: %5f; max: %5f' % (mn, mx))
       print("  " + sp)</lang>
Output:
Numbers: 0 0 1 1
  min: 0.000000; max: 1.000000
  ▁▁██

Numbers:  0 1 19 20
  min: 0.000000; max: 20.000000
  ▁▁██

Numbers:  0 999 4000 4999 7000 7999
  min: 0.000000; max: 7999.000000
  ▁▁▅▅██

Numbers: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
  min: 1.000000; max: 8.000000
  ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

Numbers: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5 
  min: 0.500000; max: 7.500000
  ▂▁▄▃▆▅█▇


Or, by functional composition: <lang python>import re


  1. sparkLine :: [Num] -> String

def sparkLine(xs):

   (rng, lo) = rangeFrom(xs)
   unit = rng / 7
   return .join(map(
       lambda n: chr(9601 + int((n - lo) / unit)),
       xs
   ))


  1. TEST ----------------------------------------------------
  1. sparkAndStats :: String -> String

def sparkAndStats(s):

   xs = readFloats(s)
   (rng, lo) = rangeFrom(xs)
   return sparkLine(xs).ljust(16) + '\n' + \
       str(len(xs)).rjust(3) + \
       ' data points in range' + \
       str(lo).rjust(8) + ' to ' + str(lo + rng).rjust(6)


  1. main :: IO ()

def main():

   print (
       unlines(
           map(
               sparkAndStats,
               [
                   '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'
               ]
           )
       )
   )


  1. GENERIC FUNCTIONS ---------------------------------------


  1. rangeFrom :: [Float] -> Float

def rangeFrom(xs):

   lo = min(xs)
   return (max(xs) - lo, lo)


  1. readFloats :: String -> [Float]

def readFloats(s):

   return list(map(
       float,
       re.split('[\s,]+', s)
   ))


  1. unlines :: [String] -> String

def unlines(xs):

   return '\n'.join(xs)


  1. MAIN -------------------

if __name__ == '__main__':

   main()</lang>
Output:
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁ 
 15 data points in range     1.0 to    8.0
▂▁▄▃▆▅█▇        
  8 data points in range     0.5 to    7.5
█▇▆▅▄▃▂▁▂▃▄▅▆▇█ 
 15 data points in range    -4.0 to    3.0
▁▄█▆▅▃▂▆▃▄      
 10 data points in range -1000.0 to 1000.0

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 introduced the options:   single_line_comments and noSingle_line_comments.

It should also be noted that the CMS and TSO versions of REXX (and practically all 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 /*Optional arguments? Then 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 all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ ceil: procedure; parse arg ?; _=trunc(?); return _+(?>0)*(?\=_) /*──────────────────────────────────────────────────────────────────────────────────────*/ sparkGraph: procedure; parse arg x; say ' input: ' x /*echo the values.*/

           x= translate(x, ' ', ",")                  /*remove any superfluous commas. */
           $= '▁▂▃▄▅▆▇█';         L= length($)    /*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) * L), 1)
                  end   /*j*/
           say 'output: '  z;   say;  return          /*show the output, + a blank line*/</lang>
output   when using the default inputs:
 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
█

Rust

<lang rust> const BARS: &'static str = "▁▂▃▄▅▆▇█";

fn print_sparkline(s: &str){

   let v = BARS.chars().collect::<Vec<char>>();
   let line: String = s.replace(",", " ").split(" ")
                           .filter(|x| !x.is_empty())
                           .map(|x| v[x.parse::<f64>().unwrap().ceil() as usize - 1])
                           .collect();
   println!("{:?}", line);

}

fn main(){

   let s1 = "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1";
   print_sparkline(s1);
   let s2 = "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5";
   print_sparkline(s2);

} </lang>

Output:

"▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"

"▂▁▄▃▆▅█▇"

S-lang

<lang S-lang> % Just to demonstrate alternate ways of defining unicode: private variable spchrs = "\u{2581}\u{2582}\u{2583}\u{2584}\u{2585}\u{2586}\u{2587}\u{2588}"; private variable spchrs_alt = "▁▂▃▄▅▆▇█";

define sparkline(arrstr) {

   variable a = strtok(arrstr, " \t,"), alen = length(a), out = "";
   a = atof(a);
   variable amin = min(a), amax = max(a), span = amax - amin, i, d;
   _for i (0, alen-1, 1)
       if (span != 0) {
           % int() truncates; adding .5 here to round:
           d = int((a[i] - amin) * 7.0 / span + 0.5);
           out += substr(spchrs, d+1, 1);
       }
       else
           out += substr(spchrs, 4, 1);
   print(out);

}

if (not _slang_utf8_ok) error("Sorry, UTF8 mode is not on."); sparkline("1 2 3 4 5 6 7 8 7 6 5 4 3 2 1"); sparkline("1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5 "); </lang>

Output:

"▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"

"▂▁▄▃▆▅█▇"

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_n};
   var (min, max) = numbers.minmax;
   say "min: %5f; max: %5f"%(min, max);
   var div = ((max - min) / bar.end);
   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)
▂▁▄▃▆▅█▇