#!/usr/bin/awk -f # This program is a copy of guff, a plot device. https://github.com/silentbicycle/guff # My copy here is written in awk instead of C, has no compelling benefit. # Public domain. @thingskatedid # Modified for my purposes by sdomi (ja@sdomi.pl) # Run as awk -v x=xyz ... or env variables for stuff? # Assumptions: the data is evenly spaced along the x-axis # TODO: moving average # TODO: trend lines, or guess at complexities # TODO: points vs. lines # TODO: colourblind safe scheme # TODO: center data around the 0 axis # TODO: scanning for all float formats input, including -inf, NaN etc # TODO: guess at whether to use lines or circles, based on delta within a window? function hastitle() { for (i = 1; i <= NF; i++) { if ($i ~ /[^-0-9.]/) { return 1 } } return 0 } function amax(a, i, max) { max = -1 for (i in a) { if (max == -1 || a[i] > a[max]) { max = i } } return max } function normalise( delta) { for (i = 1; i <= NF; i++) { max[i] = 0 min[i] = 0 for (j = 1; j <= NR; j++) { if (a[i, j] != "#") { if (a[i, j] > max[i]) { max[i] = a[i, j] } else if (a[i, j] < min[i]) { min[i] = a[i, j] } } } delta[i] = max[i] - min[i] for (j = 1; j <= NR; j++) { if (a[i, j] != "#") { a[i, j] -= min[i] if (delta[i] > 0) { a[i, j] /= delta[i] } } } } # TODO: rescale to center around 0 # Here the data is squished slightly in descending order of deltas. # Each column is scaled independently anyway, so they're never to scale. # The idea here is to help show intutively which are smaller, but without # actually drawing them to size (since then very small deltas would not be # visible at all). k = 0 prev = -1 while (length(delta) > 0) { i = amax(delta) # Several columns can share the same delta # Formatting to %.3f here is just for sake of rounding if (prev != -1 && sprintf("%.3f", prev) != sprintf("%.3f", delta[i])) { k++ } # +2 to squish things upwards a bit scale = (NF + 2 - k) / (NF + 2) # there's no need to scale by 1 if (scale != 1) { for (j = 1; j <= NR; j++) { if (a[i, j] != "#") { a[i, j] *= scale } } } prev = delta[i] delete delta[i] } } # internal coordinates to svg coordinates function point(x, y) { x = x * (chart_width - 2 * xmargin) + xmargin y = (height - 2 * ymargin) - y * (height - 2 * ymargin) + ymargin return sprintf("%u,%u", x, y) } function line(i) { last_was_empty = 0 printf "" printf "\n" last_was_empty = 0 } function circles(i) { for (j = 1; j <= NR; j++) { p = point((j - 1) / NR, a[i, j]) split(p, q, ",") printf "\n", q[1], q[2] } } function legend_text(i, title) { printf " \n", chart_width + gutter, i * line_height printf " \n", -10, -line_height / 2 + 5, color[i], color[i] printf " %-*s[%.3g, %.3g]\n", sprintf("fill: %s; font-size: %upx; font-family: mono", fg, font_size), (title_width > 0) ? title_width + 1 : 0, title, min[i], max[i] printf " \n" } function display() { print "" printf "\n", "http://www.w3.org/2000/svg", chart_width + gutter + legend_width, height+line_height printf "" title_width = 0 for (i = 1; i <= NF; i++) { if (length(title[i]) > title_width) { title_width = length(title[i]) } } for (i = 1; i <= NF; i++) { line(i) circles(i) } if (length(title)) { for (i = 1; i <= NF; i++) { legend_text(i, title[i]) } } printf "###START_DATE###", 5, height+line_height-3 printf "###END_DATE###", chart_width-158, height+line_height-3 printf "", height+line_height/2, 10 printf "", chart_width, height+line_height/2, chart_width, 10 print "" } NR == 1 { if (hastitle()) { for (i = 1; i <= NF; i++) { title[i] = $i } NR-- next } } { for (i = 1; i <= NF; i++) { a[i, NR] = $i #if (a[i, NR] == "?") { # printf "%s", a[i, NR] #} } } END { fg = "#eeeeee" alpha = "ff" # Bang Wong's colour-safe palette, https://www.nature.com/articles/nmeth.1618 # (using just the last five colours) color[3] = "#CC79A7" color[1] = "#009E73" color[2] = "#F0E442" color[4] = "#0072B2" color[5] = "#D55E00" color[6] = fg #if (NF == 1) { # color[1] = fg #} if (NF > length(color)) { print "too many fields" >> "/dev/stderr" exit 1 } chart_width=600 legend_width=256 height=120 xmargin=0 ymargin=5 gutter=30 font_size=15 line_height=20 # the data is scaled 0..1 for our internal coordinate space normalise() display() }