miller/c/mapping/mapper_bar.c

281 lines
9.1 KiB
C

#include "lib/mlrutil.h"
#include "lib/string_builder.h"
#include "containers/lrec.h"
#include "lib/string_array.h"
#include "containers/mixutil.h"
#include "mapping/mappers.h"
#include "cli/argparse.h"
typedef struct _mapper_bar_state_t {
ap_state_t* pargp;
string_array_t* pfield_names;
char fill_char;
char oob_char;
char blank_char;
double lo;
double hi;
int width;
char** bars;
sllv_t* precords; // only for auto mode
} mapper_bar_state_t;
static void mapper_bar_usage(FILE* o, char* argv0, char* verb);
static mapper_t* mapper_bar_parse_cli(int* pargi, int argc, char** argv,
cli_reader_opts_t* _, cli_writer_opts_t* __);
static mapper_t* mapper_bar_alloc(ap_state_t* pargp, string_array_t* pfield_names,
char fill_char, char oob_char, char blank_char, double lo, double hi,
int width, int do_auto);
static void mapper_bar_free(mapper_t* pmapper, context_t* _);
static sllv_t* mapper_bar_process_no_auto(lrec_t* pinrec, context_t* pctx, void* pvstate);
static sllv_t* mapper_bar_process_auto(lrec_t* pinrec, context_t* pctx, void* pvstate);
#define DEFAULT_FILL_CHAR '*'
#define DEFAULT_OOB_CHAR '#'
#define DEFAULT_BLANK_CHAR '.'
#define DEFAULT_LO 0.0
#define DEFAULT_HI 100.0
#define DEFAULT_WIDTH 40
#define SB_ALLOC_LENGTH 128
// ----------------------------------------------------------------
mapper_setup_t mapper_bar_setup = {
.verb = "bar",
.pusage_func = mapper_bar_usage,
.pparse_func = mapper_bar_parse_cli,
.ignores_input = FALSE,
};
// ----------------------------------------------------------------
static void mapper_bar_usage(FILE* o, char* argv0, char* verb) {
fprintf(o, "Usage: %s %s [options]\n", argv0, verb);
fprintf(o, "Replaces a numeric field with a number of asterisks, allowing for cheesy\n");
fprintf(o, "bar plots. These align best with --opprint or --oxtab output format.\n");
fprintf(o, "Options:\n");
fprintf(o, "-f {a,b,c} Field names to convert to bars.\n");
fprintf(o, "-c {character} Fill character: default '%c'.\n", DEFAULT_FILL_CHAR);
fprintf(o, "-x {character} Out-of-bounds character: default '%c'.\n", DEFAULT_OOB_CHAR);
fprintf(o, "-b {character} Blank character: default '%c'.\n", DEFAULT_BLANK_CHAR);
fprintf(o, "--lo {lo} Lower-limit value for min-width bar: default '%lf'.\n", DEFAULT_LO);
fprintf(o, "--hi {hi} Upper-limit value for max-width bar: default '%lf'.\n", DEFAULT_HI);
fprintf(o, "-w {n} Bar-field width: default '%d'.\n", DEFAULT_WIDTH);
fprintf(o, "--auto Automatically computes limits, ignoring --lo and --hi.\n");
fprintf(o, " Holds all records in memory before producing any output.\n");
}
// ----------------------------------------------------------------
static mapper_t* mapper_bar_parse_cli(int* pargi, int argc, char** argv,
cli_reader_opts_t* _, cli_writer_opts_t* __)
{
string_array_t* pfield_names = NULL;
char* fill_string = NULL;
char* oob_string = NULL;
char* blank_string = NULL;
double lo = DEFAULT_LO;
double hi = DEFAULT_HI;
int width = DEFAULT_WIDTH;
int do_auto = FALSE;
char* verb = argv[(*pargi)++];
ap_state_t* pstate = ap_alloc();
ap_define_string_array_flag(pstate, "-f", &pfield_names);
ap_define_string_flag(pstate, "-c", &fill_string);
ap_define_string_flag(pstate, "-x", &oob_string);
ap_define_string_flag(pstate, "-b", &blank_string);
ap_define_float_flag(pstate, "--lo", &lo);
ap_define_float_flag(pstate, "--hi", &hi);
ap_define_int_flag(pstate, "-w", &width);
ap_define_true_flag(pstate, "--auto", &do_auto);
if (!ap_parse(pstate, verb, pargi, argc, argv)) {
mapper_bar_usage(stderr, argv[0], verb);
return NULL;
}
if (pfield_names == NULL) {
mapper_bar_usage(stderr, argv[0], verb);
return NULL;
}
char fill_char = DEFAULT_FILL_CHAR;
char oob_char = DEFAULT_OOB_CHAR;
char blank_char = DEFAULT_BLANK_CHAR;
if (fill_string != NULL) {
if (strlen(fill_string) != 1) {
mapper_bar_usage(stderr, argv[0], verb);
return NULL;
}
fill_char = fill_string[0];
}
if (oob_string != NULL) {
if (strlen(oob_string) != 1) {
mapper_bar_usage(stderr, argv[0], verb);
return NULL;
}
oob_char = oob_string[0];
}
if (blank_string != NULL) {
if (strlen(blank_string) != 1) {
mapper_bar_usage(stderr, argv[0], verb);
return NULL;
}
blank_char = blank_string[0];
}
return mapper_bar_alloc(pstate, pfield_names, fill_char, oob_char, blank_char,
lo, hi, width, do_auto);
}
// ----------------------------------------------------------------
static mapper_t* mapper_bar_alloc(ap_state_t* pargp, string_array_t* pfield_names,
char fill_char, char oob_char, char blank_char, double lo, double hi,
int width, int do_auto)
{
mapper_t* pmapper = mlr_malloc_or_die(sizeof(mapper_t));
mapper_bar_state_t* pstate = mlr_malloc_or_die(sizeof(mapper_bar_state_t));
pstate->pargp = pargp;
pstate->pfield_names = pfield_names;
pstate->fill_char = fill_char;
pstate->oob_char = oob_char;
pstate->blank_char = blank_char;
pstate->lo = lo;
pstate->hi = hi;
pstate->width = width;
pstate->bars = mlr_malloc_or_die((pstate->width + 1) * sizeof(char*));
for (int i = 0; i <= pstate->width; i++) {
pstate->bars[i] = mlr_malloc_or_die(pstate->width + 1);
char* bar = pstate->bars[i];
memset(bar, pstate->blank_char, pstate->width);
bar[pstate->width] = 0;
if (i == 0) {
bar[0] = pstate->oob_char;
} else if (i < pstate->width) {
memset(bar, pstate->fill_char, i);
} else {
memset(bar, pstate->fill_char, pstate->width);
bar[pstate->width-1] = pstate->oob_char;
}
pstate->bars[i] = bar;
}
pstate->precords = do_auto ? sllv_alloc() : NULL;
pmapper->pprocess_func = do_auto
? mapper_bar_process_auto
: mapper_bar_process_no_auto;
pmapper->pvstate = (void*)pstate;
pmapper->pfree_func = mapper_bar_free;
return pmapper;
}
static void mapper_bar_free(mapper_t* pmapper, context_t* _) {
mapper_bar_state_t* pstate = (mapper_bar_state_t*)pmapper->pvstate;
string_array_free(pstate->pfield_names);
for (int i = 0; i <= pstate->width; i++)
free(pstate->bars[i]);
free(pstate->bars);
ap_free(pstate->pargp);
free(pstate);
free(pmapper);
}
// ----------------------------------------------------------------
static sllv_t* mapper_bar_process_no_auto(lrec_t* pinrec, context_t* pctx, void* pvstate) {
if (pinrec == NULL) // end of stream
return sllv_single(NULL);
mapper_bar_state_t* pstate = (mapper_bar_state_t*)pvstate;
int n = pstate->pfield_names->length;
for (int i = 0; i < n; i++) {
char* name = pstate->pfield_names->strings[i];
char* sval = lrec_get(pinrec, name);
if (sval == NULL)
continue;
double dval = mlr_double_from_string_or_die(sval);
int idx = (int)(pstate->width * (dval - pstate->lo) / (pstate->hi - pstate->lo));
if (idx < 0)
idx = 0;
if (idx > pstate->width)
idx = pstate->width;
lrec_put(pinrec, name, pstate->bars[idx], NO_FREE);
}
return sllv_single(pinrec);
}
// ----------------------------------------------------------------
static sllv_t* mapper_bar_process_auto(lrec_t* pinrec, context_t* pctx, void* pvstate) {
mapper_bar_state_t* pstate = (mapper_bar_state_t*)pvstate;
if (pinrec != NULL) { // not end of stream
sllv_append(pstate->precords, pinrec);
return NULL;
}
// end of stream
int n = pstate->pfield_names->length;
string_builder_t* psb = sb_alloc(SB_ALLOC_LENGTH);
// Loop over field names to be barred
for (int i = 0; i < n; i++) {
char* name = pstate->pfield_names->strings[i];
double lo = 0.0;
double hi = 0.0;
// First pass computes lo and hi from the data
int j = 0;
for (sllve_t* pe = pstate->precords->phead; pe != NULL; pe = pe->pnext, j++) {
lrec_t* prec = pe->pvvalue;
char* sval = lrec_get(prec, name);
if (sval == NULL)
continue;
double dval = mlr_double_from_string_or_die(sval);
if (j == 0 || dval < lo)
lo = dval;
if (j == 0 || dval > hi)
hi = dval;
}
// Second pass applies the bars. There is some redundant computation
// which could be hoisted out of the loop for performance ... but this
// verb computes data solely for visual inspection and I take the
// nominal use case to be tens or hundreds of records. So, optimization
// isn't worth the effort here.
char* slo = mlr_alloc_string_from_double(lo, "%g");
char* shi = mlr_alloc_string_from_double(hi, "%g");
for (sllve_t* pe = pstate->precords->phead; pe != NULL; pe = pe->pnext) {
lrec_t* prec = pe->pvvalue;
char* sval = lrec_get(prec, name);
if (sval == NULL)
continue;
double dval = mlr_double_from_string_or_die(sval);
int idx = (int)(pstate->width * (dval - lo) / (hi - lo));
if (idx < 0)
idx = 0;
if (idx > pstate->width)
idx = pstate->width;
sb_append_string(psb, "[");
sb_append_string(psb, slo);
sb_append_string(psb, "]");
sb_append_string(psb, pstate->bars[idx]);
sb_append_string(psb, "[");
sb_append_string(psb, shi);
sb_append_string(psb, "]");
lrec_put(prec, name, sb_finish(psb), FREE_ENTRY_VALUE);
}
free(slo);
free(shi);
}
sb_free(psb);
sllv_append(pstate->precords, NULL);
return pstate->precords;
}