# vim: set fileencoding=utf-8 : import math, os, re, string, sys __all__ = ["guess_series", "main", "spark_chars", "sparkify"] spark_chars = u"▁▂▃▄▅▆▇█" """Eight unicode characters of (nearly) steadily increasing height.""" def sparkify(series, minimum=None, maximum=None, rows=1): u"""Converts to a sparkline string. Example: >>> sparkify([ 0.5, 1.2, 3.5, 7.3, 8.0, 12.5, float("nan"), 15.0, 14.2, 11.8, 6.1, ... 1.9 ]) u'▁▁▂▄▅▇ ██▆▄▂' >>> sparkify([1, 1, -2, 3, -5, 8, -13]) u'▆▆▅▆▄█▁' Raises ValueError if input data cannot be converted to float. Raises TypeError if series is not an iterable. """ series = [float(n) for n in series] if all(not math.isfinite(n) for n in series): return u" " * len(series) minimum = min(filter(math.isfinite, series)) if minimum is None else minimum maximum = max(filter(math.isfinite, series)) if maximum is None else maximum data_range = maximum - minimum if data_range == 0.0: # Graph a baseline if every input value is equal. return u"".join([spark_chars[0] if math.isfinite(i) else u" " for i in series]) row_res = len(spark_chars) resolution = row_res * rows coefficient = (resolution - 1.0) / data_range def clamp(n): return min(max(n, minimum), maximum) def spark_index(n): """An integer from 0 to (resolution-1) proportional to the data range""" return int(round((clamp(n) - minimum) * coefficient)) output = [] for r in range(rows - 1, -1, -1): row_out = [] row_min = row_res * r row_max = row_min + row_res - 1 for n in series: if not math.isfinite(n): row_out.append(" ") continue i = spark_index(n) if i < row_min: row_out.append(" ") elif i > row_max: row_out.append(spark_chars[-1]) else: row_out.append(spark_chars[i % row_res]) output.append(u"".join(row_out)) return os.linesep.join(output) def _convert_to_float(n): try: return float(n) except: return None def guess_series(input_string): u"""Tries to convert into a list of floats. Example: >>> guess_series("0.5 1.2 3.5 7.3 8 nan 12.5, 13.2," ... "15.0, 14.2, 11.8, 6.1, 1.9") [0.5, 1.2, 3.5, 7.3, 8.0, nan, 12.5, 13.2, 15.0, 14.2, 11.8, 6.1, 1.9] """ float_finder = re.compile(r"(nan|[-+]?[0-9]*\.?[0-9]+(?:e[-+]?[0-9]+)?)", re.I) return [ i for i in [ _convert_to_float(j) for j in float_finder.findall(input_string) # Remove entires we couldn't convert to a sensible value. ] if i is not None ] def main(): u"""Reads from command line args or stdin and prints a sparkline from the data. Requires at least 2 data points as input. """ import argparse from pkg_resources import require parser = argparse.ArgumentParser(description=main.__doc__) parser.add_argument("data", nargs="*", help="Floating point data, any delimiter.") parser.add_argument( "--version", "-v", action="store_true", help="Display the version number and exit.", ) parser.add_argument("--min", type=float, help="Set smaller values to MIN.") parser.add_argument("--max", type=float, help="Set larger values to MAX.") parser.add_argument( "--rows", "-r", type=int, default=1, help="Number of rows high the graph will be.", ) args = parser.parse_args() if args.version: version = require("pysparklines")[0].version print(version) sys.exit(0) if os.isatty(0) and not args.data: parser.print_help() sys.exit(1) elif args.data: arg_string = u" ".join(args.data) else: arg_string = sys.stdin.read() try: print( sparkify( guess_series(arg_string), minimum=args.min, maximum=args.max, rows=args.rows, ) ) except Exception: # should be more specific, but better than a bare except clause sys.stderr.write("Could not convert input data to valid sparkline" + os.linesep) sys.exit(1) if __name__ == "__main__": main()