├── Makefile ├── README └── aspark.c /Makefile: -------------------------------------------------------------------------------- 1 | all: aspark 2 | 3 | aspark: aspark.c 4 | $(CC) -std=c99 -pedantic -O2 -Wall -W -ansi aspark.c -o aspark -lm 5 | 6 | clean: 7 | rm -f aspark 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ASPARK README 2 | === 3 | 4 | aspark is a C program to display ASCII Sparklines. 5 | It is completely useless in 2011. 6 | 7 | It is a bloated versions of https://github.com/holman/spark 8 | and is intended for Enterprise Companies. 9 | 10 | Basic usage 11 | --- 12 | 13 | The program has different operation modes to get data in different ways. The 14 | simplest default operation mode is getting data from the command line: 15 | 16 | $ ./aspark 1,2,3,4,10,7,6,5 17 | `-_ 18 | __-` ` 19 | 20 | By default the program prints graphs using two rows. For better resolution you 21 | can change this using the --rows option: 22 | 23 | $ ./aspark 1,2,3,4,5,6,7,8,9,10,10,8,5,3,1 --rows 4 24 | _-``_ 25 | _` 26 | -` ` 27 | _-` `_ 28 | 29 | Sometimes graphs are more readable if the area under the curve is filled, 30 | so a --fill option is provided: 31 | 32 | $ ./aspark 1,2,3,4,5,6,7,8,9,10,10,8,5,3,1 --rows 4 --fill 33 | _o##_ 34 | _#||||| 35 | o#|||||||# 36 | _o#||||||||||#_ 37 | 38 | It is possible to use labels, specifying them using a ':' character followed 39 | by the label in the list of comma separated values, like in the following 40 | example: 41 | 42 | $ ./aspark '1,2,3,4,5:peak,4,3,1,0:base' 43 | _-`-_ 44 | -` -_ 45 | 46 | p b 47 | e a 48 | a s 49 | k e 50 | 51 | Sometimes a logarithmic scale is to be preferred since difference betwen values 52 | can be too big: 53 | 54 | $ ./aspark 1,2,3,10,50,100 55 | ` 56 | ____` 57 | 58 | $ ./aspark 1,2,3,10,50,100 --log 59 | -` 60 | __-` 61 | 62 | Stream mode 63 | --- 64 | 65 | In stream mode data is read form standard input, one value per each line: 66 | 67 | $ echo -e "1\n2\n3\n" | ./aspark --stream 68 | _` 69 | _ 70 | 71 | In this mode it is still possible to use labels, using a space and the label 72 | after the actual value, like in the following example: 73 | 74 | $ echo -e "1\n2 foo\n3\n" | ./aspark --stream 75 | _` 76 | _ 77 | 78 | f 79 | o 80 | o 81 | 82 | In stream mode it is often interesting to pipe data form other programs: 83 | 84 | $ ruby -e '(1..40).each{|x| print Math.sin(x/2),"\n"}' | ./aspark --stream --rows 4 --fill 85 | #### __## ##__ # 86 | ||||__ ||||## ##|||| __| 87 | #|||||| oo|||||| ||||||oo ||| 88 | |||||||oo__||||||||##__##||||||||__oo||| 89 | 90 | Characters frequency mode 91 | --- 92 | 93 | The last mode is enabled usign --txtfreq or --binfreq options. It is used to 94 | create a frequency table of the data received from standard input: 95 | 96 | $ cat /etc/passwd | ./aspark --txtfreq --fill --rows 4 97 | o # # _ 98 | | | # | |#_ 99 | | | |_ | # __#o_ |||__ 100 | _________#____|__#_______|______||##|#o_|__|||||_|||||__#_ 101 | 102 | !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ 103 | 104 | You can see the frequency of every single byte using --binfreq. 105 | 106 | Check aspark --help for more information about the usage. 107 | 108 | Author 109 | --- 110 | 111 | aspark was developed by Salvatore Sanfilippo during a few 112 | hours of complete relax. Since it is completely useless it will not be further 113 | developed by the author. 114 | -------------------------------------------------------------------------------- /aspark.c: -------------------------------------------------------------------------------- 1 | /* aspark.c -- ASCII Sparklines 2 | * 3 | * Copyright(C) 2011 Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Inspired by https://github.com/holman/spark 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define ASPARK_MODE_ARGUMENT 0 /* read data sequence from arguments */ 38 | #define ASPARK_MODE_STREAM 1 /* read sequence of data from standard input */ 39 | #define ASPARK_MODE_TXTFREQ 2 /* compute char frequency of text */ 40 | #define ASPARK_MODE_BINFREQ 3 /* binary (all 256 bytes) frequency */ 41 | 42 | /* This is the charset used to display the graphs, but multiple rows are used 43 | * to increase the resolution. */ 44 | char charset[] = "_-`"; 45 | char charset_fill[] = "_o#"; 46 | int charset_len = sizeof(charset)-1; 47 | 48 | /* Global config */ 49 | char *opt_data = NULL; 50 | int opt_mode = ASPARK_MODE_ARGUMENT; 51 | int opt_columns = -1; /* -1 means auto-detect number of columns. */ 52 | int opt_rows = 2; /* Number of rows to use to increase resolution. */ 53 | int opt_label_margin_top = 1; /* Spaces before labels. */ 54 | int opt_log = 0; /* Logarithmic mode */ 55 | int opt_fill = 0; /* Fill the area under the sparkline */ 56 | 57 | /* ---------------------------------------------------------------------------- 58 | * Sequences 59 | * ------------------------------------------------------------------------- */ 60 | 61 | /* A sequence is represented of many "samples" */ 62 | struct sample { 63 | double value; 64 | char *label; 65 | }; 66 | 67 | struct sequence { 68 | int length; 69 | int labels; 70 | struct sample *samples; 71 | double min, max; 72 | }; 73 | 74 | /* Create a new sequence. */ 75 | struct sequence *create_sequence(void) { 76 | struct sequence *seq = malloc(sizeof(*seq)); 77 | seq->length = 0; 78 | seq->samples = NULL; 79 | return seq; 80 | } 81 | 82 | /* Add a new sample into a sequence */ 83 | void sequence_add_sample(struct sequence *seq, double value, char *label) { 84 | if (seq->length == 0) { 85 | seq->min = seq->max = value; 86 | } else { 87 | if (value < seq->min) seq->min = value; 88 | else if (value > seq->max) seq->max = value; 89 | } 90 | seq->samples = realloc(seq->samples,sizeof(struct sample)*(seq->length+1)); 91 | seq->samples[seq->length].value = value; 92 | seq->samples[seq->length].label = label; 93 | seq->length++; 94 | if (label) seq->labels++; 95 | } 96 | 97 | /* ---------------------------------------------------------------------------- 98 | * Argument mode 99 | * ------------------------------------------------------------------------- */ 100 | 101 | /* Convert a string in the form 1,2,3.4,5:label1,6:label2 into a sequence. 102 | * On error NULL is returned and errno set accordingly: 103 | * 104 | * EINVAL => Invalid data format. */ 105 | struct sequence *argument_to_sequence(const char *arg) { 106 | struct sequence *seq = create_sequence(); 107 | char *copy, *start; 108 | 109 | start = copy = strdup(arg); 110 | while(*start) { 111 | char *label, *end, *endptr; 112 | double value; 113 | 114 | end = strchr(start,','); 115 | if (end) *end = '\0'; 116 | label = strchr(start,':'); 117 | if (label) { 118 | *label = '\0'; 119 | label++; /* skip the ':' */ 120 | } 121 | errno = 0; 122 | value = strtod(start,&endptr); 123 | if (*endptr != '\0' || errno != 0 || isinf(value) || isnan(value)) { 124 | errno = EINVAL; 125 | return NULL; 126 | } 127 | sequence_add_sample(seq,value,label ? strdup(label) : NULL); 128 | if (end) 129 | start = end+1; 130 | else 131 | break; 132 | } 133 | free(copy); 134 | return seq; 135 | } 136 | 137 | /* ---------------------------------------------------------------------------- 138 | * File frequency mode 139 | * ------------------------------------------------------------------------- */ 140 | 141 | /* Read chars from stdin until end of file, build a frequency table, and 142 | * finally translate it into samples. */ 143 | struct sequence *file_freq_to_sequence(void) { 144 | struct sequence *seq; 145 | unsigned int count[256]; 146 | int c; 147 | 148 | memset(count,0,sizeof(count)); 149 | while((c = getc(stdin)) != EOF) { 150 | if (opt_mode == ASPARK_MODE_TXTFREQ) c = toupper(c); 151 | count[c]++; 152 | } 153 | seq = create_sequence(); 154 | if (opt_mode == ASPARK_MODE_BINFREQ) { 155 | for (c = 0; c < 256; c++) { 156 | char buf[32]; 157 | 158 | snprintf(buf,sizeof(buf),"%d",c); 159 | sequence_add_sample(seq,count[c],strdup(buf)); 160 | } 161 | } else { 162 | for (c = ' '+1; c <= 'Z'; c++) { 163 | char buf[2]; 164 | 165 | snprintf(buf,sizeof(buf),"%c",c); 166 | sequence_add_sample(seq,count[c],strdup(buf)); 167 | } 168 | } 169 | return seq; 170 | } 171 | 172 | /* ---------------------------------------------------------------------------- 173 | * File stream mode 174 | * ------------------------------------------------------------------------- */ 175 | 176 | /* Reads data line after line from a standard input, where every line 177 | * represents a single data point that can be a number or optionally 178 | * a number, any number of spaces, and a label, and returns the sequence 179 | * represented by this stream. 180 | * 181 | * The function is very tolerant about spaces and badly formatted lines. */ 182 | struct sequence *datastream_to_sequence(void) { 183 | struct sequence *seq = create_sequence(); 184 | char buf[4096]; 185 | 186 | while(fgets(buf,sizeof(buf),stdin) != NULL) { 187 | char *endptr, *start, *label = NULL, *p = buf; 188 | double value; 189 | 190 | /* Skip initial spaces */ 191 | while(*p && isspace(*p)) p++; 192 | if (*p == '\0') continue; /* Empty line, skip it */ 193 | start = p; 194 | /* Find the end of the value */ 195 | while(*p && !isspace(*p)) p++; 196 | if (*p) { 197 | *p = '\0'; 198 | p++; 199 | } 200 | /* Parse the value */ 201 | errno = 0; 202 | value = strtod(start,&endptr); 203 | if (*endptr != '\0' || errno != 0) continue; /* Bad float, skip it */ 204 | /* Find the start of the label */ 205 | while(*p && isspace(*p)) p++; 206 | if (*p != '\0') { 207 | /* We have an additional label, find the end */ 208 | label = p; 209 | while(*p && !isspace(*p)) p++; 210 | *p = '\0'; 211 | } 212 | sequence_add_sample(seq,value,label ? strdup(label) : NULL); 213 | } 214 | return seq; 215 | } 216 | 217 | /* ---------------------------------------------------------------------------- 218 | * ASCII rendering of sequence 219 | * ------------------------------------------------------------------------- */ 220 | 221 | /* Render part of a sequence, so that render_sequence() call call this function 222 | * with differnent parts in order to create the full output without overflowing 223 | * the current terminal columns. */ 224 | void render_sub_sequence(struct sequence *seq, int rows, int offset, int len) 225 | { 226 | int j; 227 | double relmax = seq->max - seq->min; 228 | int steps = charset_len*rows; 229 | int row = 0; 230 | char *chars = malloc(len); 231 | int loop = 1; 232 | 233 | if (opt_log) { 234 | relmax = log(relmax+1); 235 | } else if (relmax == 0) { 236 | relmax = 1; 237 | } 238 | 239 | while(loop) { 240 | loop = 0; 241 | memset(chars,' ',len); 242 | for (j = 0; j < len; j++) { 243 | struct sample *s = &seq->samples[j+offset]; 244 | double relval = s->value - seq->min; 245 | int step; 246 | 247 | if (opt_log) relval = log(relval+1); 248 | step = (int) (relval*steps)/relmax; 249 | if (step < 0) step = 0; 250 | if (step >= steps) step = steps-1; 251 | 252 | if (row < rows) { 253 | /* Print the character needed to create the sparkline */ 254 | int charidx = step-((rows-row-1)*charset_len); 255 | loop = 1; 256 | if (charidx >= 0 && charidx < charset_len) { 257 | chars[j] = opt_fill ? charset_fill[charidx] : 258 | charset[charidx]; 259 | } else if(opt_fill && charidx >= charset_len) { 260 | chars[j] = '|'; 261 | } 262 | } else { 263 | /* Labels spacing */ 264 | if (seq->labels && row-rows < opt_label_margin_top) { 265 | loop = 1; 266 | break; 267 | } 268 | /* Print the label if needed. */ 269 | if (s->label) { 270 | int label_len = strlen(s->label); 271 | int label_char = row-rows-opt_label_margin_top; 272 | 273 | if (label_len > label_char) { 274 | loop = 1; 275 | chars[j] = s->label[label_char]; 276 | } 277 | } 278 | } 279 | } 280 | if (loop) { 281 | row++; 282 | printf("%.*s\n", len, chars); 283 | } 284 | } 285 | free(chars); 286 | } 287 | 288 | /* Turn a sequence into its ASCII representation */ 289 | void render_sequence(struct sequence *seq, int columns, int rows) { 290 | int j; 291 | 292 | for (j = 0; j < seq->length; j += columns) { 293 | int sublen = (seq->length-j) < columns ? (seq->length-j) : columns; 294 | 295 | if (j != 0) printf("\n"); 296 | render_sub_sequence(seq, rows, j, sublen); 297 | } 298 | } 299 | 300 | /* ---------------------------------------------------------------------------- 301 | * Main & Company 302 | * ------------------------------------------------------------------------- */ 303 | 304 | /* Show help and exit */ 305 | void show_help(void) { 306 | /* We use multiple printf to have strings less than 512 bytes in size 307 | * to conform to C99 minimal requirements for C compilers. */ 308 | printf( 309 | "Usage: aspark [options] [comma separated values]\n" 310 | "\n" 311 | " --help show this help\n" 312 | " --stream get data from stdin, one line per sample\n" 313 | " --binfreq show binary frequency table (data read from stdin)\n" 314 | " --txtfreq show letters frequency table (data read from stdin)\n"); 315 | printf( 316 | " --log use logarithmic scale\n" 317 | " --fill fill the space below the spark line\n" 318 | " --columns use at max columns. By default uses $COLUMNS\n" 319 | " --rows use rows to render the graph (default is 2)\n" 320 | " --label-margin-top vertical spaces between the line and labels\n" 321 | "\n"); 322 | printf( 323 | "If no --stream, --binfreq or --txtfreq option is specified data is read\n" 324 | "from the argument, example:\n\n" 325 | " $ aspark 1,2,3,4,3,1\n\n" 326 | "It is possible to specify a label using :