Add the old plotsi
authorSiraaj Khandkar <siraaj@khandkar.net>
Wed, 13 Mar 2019 01:41:59 +0000 (21:41 -0400)
committerSiraaj Khandkar <siraaj@khandkar.net>
Wed, 13 Mar 2019 01:41:59 +0000 (21:41 -0400)
bin/plotsi [new file with mode: 0755]

diff --git a/bin/plotsi b/bin/plotsi
new file mode 100755 (executable)
index 0000000..7a8812e
--- /dev/null
@@ -0,0 +1,310 @@
+#! /usr/bin/awk -f
+#
+# Plot Simply
+#
+# TODO: Incremental plotting (new canvas for each data line)
+# TODO: Support manual set of max y
+
+function orange(str)        {return "\033[33m"   str "\033[0m";}
+function orange_bright(str) {return "\033[1;33m" str "\033[0m";}
+function green(str)         {return "\033[32m"   str "\033[0m";}
+function grey_dark(str)     {return "\033[30m"   str "\033[0m";}
+function grey_light(str)    {return "\033[37m"   str "\033[0m";}
+
+function abs(n) {return n >= 0 ? n : -n}
+function round(n_float) {return int(n_float + 0.5)}
+function log_10(n) {return log(n) / log(10)}
+
+function width(n, w) {
+    #
+    # log(0) == -inf
+    #
+    w = 1 + round(n > 0 ? log_10(n) : (n == 0 ? 1 : log_10(-n) + 1));  # +1 for neg sign
+    #printf("n: %d, w: %d\n", n, w);
+    return w;
+}
+
+function bins_append_item_to_bin(bin, val,    bin_i) {
+    bin_i = bin_item_count[bin]++;
+    bins[bin, bin_i] = val;
+}
+
+BEGIN {
+    # CLI options: w, h, p, a, b
+    limits["canvas_width"]  = (w ? w : 70);
+    limits["canvas_height"] = (h ? h : 20);
+    char_point              = orange_bright(p ? p : ".");
+    char_pad                =   grey_dark(a ? a : "");  # FIXME: non-blank pad is broken after adding the y labels
+    char_blank              =   grey_dark(b ? b : "|");
+    aggregation             = g ? g : "mean";
+    data_field              = df ? df : 1;
+
+    y_orig_min = 0;
+    y_orig_max = 0;
+}
+
+{
+    y_orig = $data_field;
+    if (y_orig > y_orig_max) y_orig_max = y_orig;
+    if (y_orig < y_orig_min) y_orig_min = y_orig;
+    data_0[NR] = y_orig;
+}
+
+function aggregate_mean() {
+}
+
+function aggregate(array) {
+    if (aggregation == "mean") {
+        return aggregate_mean(array);
+    } else {
+        printf("Unknown aggregation: %s\n", aggregation) > "/dev/stderr";
+        exit(2);
+    }
+}
+
+function data_scaled_x_to_width(\
+    data_in, data_out, limits,
+
+    x_orig,
+    datum,
+    x_bin,
+    bins,
+    bin_item_counts,
+    bin_sum,
+    bin_item_count,
+    i,
+    bin_item,
+    bin_mean \
+) {
+    # Find limit points
+    x_orig_min = 0;
+    x_orig_max = 0;
+    for (x_orig in data_in) {
+        if (x_orig > x_orig_max) x_orig_max = x_orig;
+        if (x_orig < x_orig_min) x_orig_min = x_orig;
+    }
+    #x_orig_range = x_orig_max - x_orig_min;
+    # Place items in bins
+    x_bin_min = 0;
+    x_bin_max = 0;
+    for (x_orig in data_in) {
+        datum = data_in[x_orig];
+        x_bin = round(x_orig * limits["canvas_width_x"] / length(data_in));
+        if (x_bin > x_bin_max) x_bin_max = x_bin;
+        if (x_bin < x_bin_min) x_bin_min = x_bin;
+        bin_item_count = ++bin_item_counts[x_bin];
+        bins[x_bin, bin_item_count] = datum;
+        #printf("x_orig: %f, x_bin: %f, bin_item_count: %f, datum: %f\n", x_orig, x_bin, bin_item_count, datum);
+    }
+    # Aggregate bins
+    for (x_bin in bin_item_counts) {
+        bin_sum = 0;
+        bin_item_count = bin_item_counts[x_bin];
+        for (i = 1; i <= bin_item_count; i++) {
+            bin_item = bins[x_bin, i];
+            bin_sum += bin_item;
+            #printf("x_bin: %f, bin_item_count: %f, bin_item: %f, bin_sum: %f\n", x_bin, bin_item_count, bin_item, bin_sum);
+        }
+        bin_mean = bin_sum / bin_item_count;
+        data_out[x_bin] = bin_mean;
+        #printf("x_bin: %f, bin_mean: %f, bin_sum: %f, bin_item_count: %f\n",
+               #x_bin, bin_mean, bin_sum, bin_item_count);
+    }
+    limits["x_min"] = x_bin_min;
+    limits["x_max"] = x_bin_max;
+}
+
+function data_scaled_y_to_height(\
+    data_in, data_out, limits,
+
+    data_scaled, x, y, y_orig, y_scaled \
+) {
+    # Offset orig
+    # TODO: Is there a better, closed-form way to get the offset?
+    # TODO: Is there better way to map canvas to value ranges altogether?
+    if (y_orig_min < 0) {
+        offset_orig = -1 * y_orig_min;
+    } else {
+        offset_orig = 0;
+    } 
+    for (x in data_in) {
+        y = data_in[x];
+        #printf("x: %f, y: %f, y_min: %f, y_max: %f\n", x, y, y_min, y_max);
+        data_in_offsetted[x] = y + offset_orig;
+    }
+    y_orig_offseted_min = y_orig_min + offset_orig;
+    y_orig_offseted_max = y_orig_max + offset_orig;
+
+    # Scale to height
+    y_scaled_min = 0;
+    y_scaled_max = 0;
+    for (x in data_in_offsetted) {
+        y_orig_offsetted = data_in_offsetted[x];
+        y_scaled = \
+            y_orig_offseted_max > 0 \
+            ? round((y_orig_offsetted * limits["canvas_height"]) / y_orig_offseted_max) \
+            : 0;
+        #printf(\
+            #"x: %6.2f, y_orig_offsetted: %6.2f, y_orig_max: %6.2f, y_scaled: %6.2f\n",
+            #x,        y_orig_offsetted,         y_orig_max,        y_scaled);
+        if (y_scaled > y_scaled_max) y_scaled_max = y_scaled;
+        if (y_scaled < y_scaled_min) y_scaled_min = y_scaled;
+        data_out[x] = y_scaled
+    }
+
+    # Save limits
+    limits["y_min"] = y_scaled_min;
+    limits["y_max"] = y_scaled_max;
+    range_orig = y_orig_max - y_orig_min;
+    offset_scaled = \
+        range_orig > 0 \
+        ? round(offset_orig * limits["canvas_height"] / range_orig) \
+        : 0;
+    limits["offset_scaled"] = offset_scaled;
+    #printf("offset_orig: %f, offset_scaled: %f\n", offset_orig, offset_scaled);
+}
+
+function canvas_init(canvas, width, height,    row, col) {
+    for (row=0; row <= height; row++) {
+        for (col=0; col <= width; col++) {
+            canvas[row, col] = char_pad char_blank char_pad;
+        }
+    }
+}
+
+function canvas_overlay_highlight_ticks_x(canvas, limits,    row, col) {
+    for (col=limits["canvas_width_y"] - 1; col <= limits["canvas_width"]; col++) {
+        offset = limits["offset_scaled"];
+        #printf("offset: %f\n", offset);
+        row = 0 + offset;
+        #row = 0;
+        canvas[row, col] = char_pad green("-") char_pad;
+    }
+}
+
+function canvas_overlay_highlight_zero_row(canvas, limits,    row, col) {
+    #print "canvas_overlay_highlight_zero_row";
+    for (col=limits["canvas_width_y"] - 1; col <= limits["canvas_width"]; col++) {
+        offset = limits["offset_scaled"];
+        row = 0 + offset;
+        #printf("col: %6.2f, row: %6.2f, offset: %f\n", col, row, offset);
+        #row = 0;
+        canvas[row, col] = char_pad green("-") char_pad;
+    }
+}
+
+function canvas_overlay_highlight_zero_col(canvas, limits,    row, col) {
+    #print "canvas_overlay_highlight_zero_col";
+    for (row=0; row <= limits["canvas_height"]; row++) {
+        col = limits["canvas_width_y"];  # was also -1. Why?
+        # TODO: Refactor color/character configs to ease composition
+        canvas[row, col] = green("|");
+    }
+    canvas[limits["canvas_height"], limits["canvas_width_y"]] = green("+");
+    canvas[0                      , limits["canvas_width_y"]] = green("+");
+}
+function canvas_overlay_highlight_zero(canvas, limits) {
+    #print "canvas_overlay_highlight_zero";
+    canvas[0 + limits["offset_scaled"], 0 + limits["canvas_width_y"]] = green("+");
+}
+
+function canvas_overlay_data(canvas, data, limits,    x_data, x_canvas, y, yi, yj) {
+    #print "canvas_overlay_data";
+    for (x_data in data) {
+        x_canvas = x_data + limits["canvas_width_y"] + 1;
+        y = data[x_data];
+        # TODO: Would be nice to scale width of all cells to the widest
+        #point = y;
+        #printf("canvas_width_y: %6.2f, x0: %6.2f, x1: %6.2f, x: %6.2f, y: %6.2f\n",
+               #limits["canvas_width_y"], x0, x1, x, y);
+        # TODO: This special case for 0 is kind of a kludge - can we do better?
+        canvas[y, x_canvas] = x_data == 0 ? char_point : char_pad char_point char_pad;
+
+        if (y > limits["offset_scaled"]) {
+            for (yi = y - 1; yi >= limits["offset_scaled"]; yi--) {
+                #printf("yi: %6.2f\n", yi);
+                canvas[yi, x_canvas] = x_data == 0 ? orange("|") : char_pad orange("|") char_pad;
+            }
+        } else if (y < limits["offset_scaled"]) {
+            for (yj = limits["offset_scaled"]; yj > y; yj--) {
+                #printf("yj: %6.2f\n", yj);
+                canvas[yj, x_canvas] = x_data == 0 ? orange("|") : char_pad orange("|") char_pad;
+            }
+        }
+    }
+}
+
+function canvas_overlay_y_lab(canvas, limits,    y_lab_fmt, y_max_str, i) {
+    y_lab_fmt = "%" limits["canvas_width_y"] - 1 "d ";
+    y_max_str = sprintf(y_lab_fmt, y_orig_max);
+    y_min_str = sprintf(y_lab_fmt, y_orig_min);
+    #printf("y_width: %f, y_max_str: \"%s\", y_min_str: \"%s\"\n", limits["canvas_width_y"], y_max_str, y_min_str);
+    for (i=1; i<=length(y_max_str); i++) {
+        canvas[limits["canvas_height"], i - 1] = substr(y_max_str, i, 1);
+    }
+    canvas[0 + limits["offset_scaled"], 0 + limits["canvas_width_y"] - 1] = 0;
+    for (i=1; i<=length(y_min_str); i++) {
+        canvas[0, i - 1] = substr(y_min_str, i, 1);
+    }
+}
+
+function canvas_print(canvas, limits,    row, col) {
+    for (row = limits["canvas_height"]; row >= 0; row--) {
+        for (col = 0; col <= limits["canvas_width"]; col++) {
+            printf("%s", canvas[row, col]);
+        }
+        printf("\n");
+    }
+}
+
+END {
+    # Find maximum y number width
+    y_orig_min_width = width(y_orig_min);
+    y_orig_max_width = width(y_orig_max);
+    if (y_orig_max_width >= y_orig_min_width) {
+        y_width = y_orig_max_width;
+    } else {
+        y_width = y_orig_min_width;
+    }
+    limits["canvas_width_y"] = y_width + 1;
+    limits["canvas_width_x"] = limits["canvas_width"] - limits["canvas_width_y"];
+
+    data_scaled_x_to_width(data_0, data_1, limits);
+    data_scaled_y_to_height(data_1, data_2, limits);
+
+    canvas_init(canvas, limits["canvas_width"], limits["canvas_height"]);
+    canvas_overlay_highlight_zero_row(canvas, limits);
+    canvas_overlay_highlight_zero_col(canvas, limits);
+    canvas_overlay_highlight_zero(canvas, limits);
+    canvas_overlay_y_lab(canvas, limits);
+    canvas_overlay_data(canvas, data_2, limits);
+    #for (l in limits) {
+        #printf("limits[%s] -> %s\n", l, limits[l]);
+    #}
+    canvas_print(canvas, limits);
+}
+
+# An even better way to think about scaling: ratios!!! Duh! :-D
+#
+# val_max / val_current = width / val_scaled
+#
+# val_max        width
+# ----------- = ------------
+# val_current    val_scaled
+#
+# val_max * val_scaled = val_current * width
+#           val_scaled = (val_current * width) / val_max
+#
+#
+# num_data_points    width
+# ---------------- = -----
+# x                  1
+#
+# width * x = num_data_points
+#         x = num_data_points / width
+#
+# But that is what I already tried, and it is awkward to scale up when
+# thinking thsese terms, so it is much better to first route each data
+# point to an appropriate bin and then aggregate each bin:
+#   1. Route: bins[scale(datum)]
+#   2. Aggregate: for bin in bins: for val in bin: aggregate(val)
This page took 0.038806 seconds and 4 git commands to generate.