https://bitbucket.org/daniel_fort/magic-lantern
Raw File
Tip revision: 4db4be951be738a688ba8df756fb6ba838b8ffdc authored by alex@thinkpad on 11 December 2017, 12:32:52 UTC
edmac: make sure the log files don't have unnecessary newlines
Tip revision: 4db4be9
plot.c

#define __plot_c__

#include <module.h>
#include <dryos.h>
#include <bmp.h>
#include <math.h>
#include <float.h>
#include <string.h>

#include "plot.h"

/* calculate an average over all sample values. might overflow target datatype range, so be warned. could be prevented with some coding effort  */
plot_data_t plot_get_average(plot_coll_t *coll, uint32_t field)
{
    plot_data_t sum = 0.0f;
    
    if(!coll || !coll->used)
    {
        return 0.0f;
    }
    
    for(uint32_t entry = 0; entry < coll->used; entry++)
    {
        sum += coll->entries[coll->fields * entry + field];
    }
    
    return sum / coll->used;
}

/* caculate extreme values, ignoring outliers that are outside a given window */
void plot_get_extremes(plot_coll_t *coll, uint32_t field, plot_data_t win_lo, plot_data_t win_hi, plot_data_t *ret_low, plot_data_t *ret_high)
{
    plot_data_t low = PLOT_MAX;
    plot_data_t high = PLOT_MIN;
    
    if(!coll || !coll->used)
    {
        return;
    }
    
    for(uint32_t entry = 0; entry < coll->used; entry++)
    {
        plot_data_t value = coll->entries[coll->fields * entry + field];
        
        /* is it within given window? */
        if(value >= win_lo && value <= win_hi)
        {
            /* if so update extreme values */
            high = MAX(high, value);
            low = MIN(low, value);
        }
    }
    
    *ret_high = high;
    *ret_low = low;
}

/* reset the number of entries in a data collection. does not free any memory */
void plot_clear(plot_coll_t *coll)
{
    if(!coll)
    {
        return;
    }
    coll->used = 0;
}

/* create a new graph at given position */
plot_graph_t *plot_alloc_graph(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
    plot_graph_t *graph = malloc(sizeof(plot_graph_t));
    
    if(!graph)
    {
        return NULL;
    }

    memset(graph, 0x00, sizeof(plot_graph_t));

    graph->x = x;
    graph->y = y;
    graph->w = w;
    graph->h = h;

    /* default config */
    graph->type = PLOT_LINEAR;
    graph->x_field = 0;
    graph->y_field = 1;
    graph->dynamic_x = 0;

    graph->dot_size = 2;
    graph->points_min = 100;
    graph->points_drawn = 0;
    graph->color_dots = COLOR_RED;
    graph->color_lines = COLOR_YELLOW;
    graph->color_border = COLOR_WHITE;
    graph->color_axis = COLOR_WHITE;
    graph->color_range = COLOR_GRAY(60);
    graph->color_bg = COLOR_GRAY(20);
    
    return graph;
}

/* free previously allocated graph */
void plot_free_graph(plot_graph_t *graph)
{
    free(graph);
}

/* allocate an empty structure for data to plot. */
plot_coll_t *plot_alloc_data(uint32_t fields)
{
    plot_coll_t *coll = malloc(sizeof(plot_coll_t));

    if(!coll)
    {
        return NULL;
    }
    
    memset(coll, 0x00, sizeof(plot_coll_t));

    if(fields)
    {
        coll->fields = fields;
    }
    else
    {
        coll->fields = 1;
    }

    coll->entry_size = sizeof(plot_data_t) * coll->fields;

    coll->extent = 1024;
    coll->entries = malloc(coll->extent * coll->entry_size);
    coll->allocated = coll->extent;
    coll->used = 0;

    return coll;
}

/* free previously allocated data */
void plot_free_data(plot_coll_t *coll)
{
    if(coll->entries)
    {
        free(coll->entries);
        coll->entries = NULL;
    }
    free(coll);
}

/* add one data "entry" consisting of "fields" whose amount were specified when calling plot_alloc_data() */
uint32_t plot_add(plot_coll_t *coll, ...)
{
    va_list argp;
    va_start(argp, coll);
    
    /* can we be defensive here when (coll == NULL)? 
       returning without going through all va_args will it render stack corrupted?
       to be checked!
    */
    if(!coll)
    {
        va_end(argp);
        return 0;
    }
    

    if(coll->used + coll->entry_size > coll->allocated)
    {
        uint32_t new_allocated = coll->allocated + coll->extent;
        uint32_t new_size = new_allocated * coll->entry_size;

        void *new_data = realloc(coll->entries, new_size);

        if(!new_data)
        {
            return -1;
        }

        coll->entries = new_data;
        coll->allocated = new_allocated;
    }

    for(uint32_t field = 0; field < coll->fields; field++)
    {
        /* when passing floats via vararg, they get promoted to double */
        plot_data_t value = va_arg(argp, double);
        coll->entries[coll->used * coll->fields + field] = value;
    }

    coll->used++;

    va_end(argp);

    return coll->used;
}

/* add many data "entry" consisting of "fields" whose amount were specified when calling plot_alloc_data() */
uint32_t plot_add_array(plot_coll_t *coll, uint32_t entry_count, plot_data_t *entries)
{
    if(!coll || !entries)
    {
        return 0;
    }
    
    if(coll->used + entry_count > coll->allocated)
    {
        uint32_t extent = (entry_count / coll->extent + 1) * coll->extent;
        uint32_t new_allocated = coll->allocated + extent;
        uint32_t new_size = new_allocated * coll->entry_size;

        void *new_data = realloc(coll->entries, new_size);

        if(!new_data)
        {
            return -1;
        }

        coll->entries = new_data;
        coll->allocated = new_allocated;
    }

    for(uint32_t entry = 0; entry < entry_count; entry++)
    {
        for(uint32_t field = 0; field < coll->fields; field++)
        {
            plot_data_t value = entries[entry * coll->entry_size + field];
            coll->entries[coll->used * coll->fields + field] = value;
        }
        coll->used++;
    }

    return coll->used;
}


/* internal: return the given field and pre-scale it */
static plot_data_t plot_get_scaled(plot_coll_t *coll, plot_graph_t *plot, uint32_t entry, uint32_t get_y_field)
{
    /* get first field's address in current entry */
    uint32_t src_field = 0;
    uint32_t max_value = 0;
    plot_win_t win;

    if(!coll || !plot)
    {
        return 0;
    }
    
    if(get_y_field)
    {
        if(plot->type == PLOT_XY)
        {
            src_field = plot->y_field;
        }
        else
        {
            src_field = 0;
        }
        max_value = plot->h;
        win = plot->y_win;
    }
    else
    {
        src_field = plot->x_field;
        max_value = plot->w;
        win = plot->x_win;
    }

    for(uint32_t field = 0; field < coll->fields; field++)
    {
        if(field == src_field)
        {
            /* the selected range in source data */
            uint32_t scale = 0;

            plot_data_t range = win.max - win.min;
            scale = (range > 0.00001) || (range < -0.00001);

            plot_data_t value = coll->entries[entry * coll->fields + field];
            if(scale)
            {
                value = ((value - win.min) * max_value) / range;
            }

            plot_data_t scaled = COERCE(value, 0, max_value);
            return scaled;
        }
    }

    return 0;
}

/* mark the graph for whole repainting */
void plot_graph_reset(plot_graph_t *plot)
{
    if(!plot)
    {
        return;
    }
    plot->points_drawn = 0;
    plot->points_last = 0;
}

void plot_fmt_float(char *buf, uint32_t buf_len, float value)
{
    int32_t left = (int32_t)fabs(value);
    int32_t right = ((int32_t)(fabs(value) * 1000.0f)) % 1000;
    char *sign = " ";
    
    if(!buf)
    {
        return;
    }

    if(value < 0)
    {
        sign = "-";
    }

    snprintf(buf, buf_len, "%s%d.%03d", sign, left, right);
}

void plot_graph_paint_range(plot_coll_t *coll, plot_graph_t *plot)
{
    if(!coll || !plot)
    {
        return;
    }
    
    if(plot->color_range != PLOT_COLOR_NONE)
    {
        char line[64];
        char buf[32];

        strcpy(line, "x: ");
        plot_fmt_float(buf, sizeof(buf), plot->x_win.min);
        strcat(line, buf);
        strcat(line, " - ");
        plot_fmt_float(buf, sizeof(buf), plot->x_win.max);
        strcat(line, buf);
        bmp_printf(FONT(FONT_SMALL,plot->color_range,plot->color_bg), plot->x + 2, plot->y + 2, line);

        strcpy(line, "y: ");
        plot_fmt_float(buf, sizeof(buf), plot->y_win.min);
        strcat(line, buf);
        strcat(line, " - ");
        plot_fmt_float(buf, sizeof(buf), plot->y_win.max);
        strcat(line, buf);
        bmp_printf(FONT(FONT_SMALL,plot->color_range,plot->color_bg), plot->x + 2, plot->y + 2 + fontspec_height(FONT_SMALL), line);
    }
}

void plot_graph_paint_grid(plot_coll_t *coll, plot_graph_t *plot)
{
    if(!coll || !plot)
    {
        return;
    }
    
    /* should we paint the axis? */
    if(plot->color_axis != PLOT_COLOR_NONE)
    {
        plot_data_t x_range = plot->x_win.max - plot->x_win.min;
        plot_data_t y_range = plot->y_win.max - plot->y_win.min;

        /* only draw if the line would be in visible range */
        if(PLOT_NONZERO(x_range) && (plot->x_win.min <= 0 && plot->x_win.max > 0))
        {
            /* translate coordinates and add to display X start */
            plot_data_t base = plot->x + (-plot->x_win.min * plot->w) / x_range;
            draw_line(base, plot->y, base, plot->y + plot->h, plot->color_axis);
        }

        if(PLOT_NONZERO(y_range) && (plot->y_win.min <= 0 && plot->y_win.max > 0))
        {
            /* translate coordinates and add to display Y start (but count upwards, so subtract) */
            plot_data_t base = (plot->y + plot->h) - (-plot->y_win.min * plot->h) / y_range;
            draw_line(plot->x, base, plot->x + plot->w, base, plot->color_axis);
        }
    }
}

/* redraw parts of the graph if some data was added */
void plot_graph_update(plot_coll_t *coll, plot_graph_t *plot)
{
    uint32_t redraw = 0;
    uint32_t points = 0;
    
    if(!coll || !plot)
    {
        return;
    }

    if(plot->points_drawn > coll->used)
    {
        plot->points_drawn = 0;
    }

    if(plot->type == PLOT_LINEAR)
    {
        if(plot->dynamic_x)
        {
            points = MAX(plot->points_min, coll->used);
        }
        else
        {
            points = plot->points_min * (coll->used / plot->points_min + 1);
        }

        /* when the number of points to be drawn exceed the current scaling, change scaling and redraw */
        if(plot->points_last != points)
        {
            plot->points_drawn = 0;
            plot->points_last = points;
        }
    }

    /* when redrawing the whole plot, also clear bg and paint borders */
    if(!plot->points_drawn)
    {
        redraw = 1;
        
        if(plot->color_bg != PLOT_COLOR_NONE)
        {
            bmp_fill(plot->color_bg, plot->x, plot->y, plot->w, plot->h);
        }
        if(plot->color_border != PLOT_COLOR_NONE)
        {
            bmp_draw_rect_chamfer(plot->color_border, plot->x, plot->y, plot->w, plot->h, 0, 0);
        }
        
        plot_graph_paint_grid(coll, plot);
    }

    /* these are the base coordinates for that plot */
    uint32_t start_x = plot->x;
    uint32_t start_y = plot->y + plot->h;


    /* for every data entry in the collection that has not been drawn yet */
    for(uint32_t entry = plot->points_drawn; entry < coll->used; entry++)
    {
        uint32_t plot_x = 0;
        uint32_t plot_y = 0;

        /* a linear plot is incrementing X for every entry and taking Y from value collection */
        if(plot->type == PLOT_LINEAR)
        {
            plot_x = (entry * plot->w) / points;
        }
        else if(plot->type == PLOT_XY)
        {
            plot_x = plot_get_scaled(coll, plot, entry, 0);
        }

        /* now get y value */
        plot_y = plot_get_scaled(coll, plot, entry, 1);

        /* now position correctly */
        uint32_t x = start_x + plot_x;
        uint32_t y = start_y - plot_y;

        if(entry != 0 && plot->color_lines != PLOT_COLOR_NONE)
        {
            draw_line(plot->last_dot_x, plot->last_dot_y, x, y, plot->color_lines);
        }

        if(plot->dot_size > 1 && plot->color_dots != PLOT_COLOR_NONE)
        {
            uint32_t size = plot->dot_size;
            
            if( (x - size > plot->x) && (x + size < plot->x + plot->w) && 
                (y - size > plot->y) && (y + size < plot->y + plot->h))
            {
                fill_circle(x, y, size, plot->color_dots);
            }
        }

        plot->last_dot_x = x;
        plot->last_dot_y = y;
        plot->points_drawn++;
    }

    if(redraw)
    {
        plot_graph_paint_range(coll, plot);
    }
}

/* repaint the whole graph */
void plot_graph_draw(plot_coll_t *coll, plot_graph_t *plot)
{
    if(!coll || !plot)
    {
        return;
    }
    
    plot_graph_reset(plot);
    plot_graph_update(coll, plot);
}

/* set the ranges to be drawn */
void plot_set_range(plot_graph_t *plot, plot_data_t x_min, plot_data_t x_max, plot_data_t y_min, plot_data_t y_max)
{
    if(!plot)
    {
        return;
    }
    
    if( (plot->x_win.min != x_min) ||
        (plot->x_win.max != x_max) ||
        (plot->y_win.min != y_min) ||
        (plot->y_win.max != y_max))
    {
        plot->points_drawn = 0;
    }

    plot->x_win.min = x_min;
    plot->x_win.max = x_max;
    plot->y_win.min = y_min;
    plot->y_win.max = y_max;
}

/* autorange a plot based on the data collection passed */
void plot_autorange(plot_coll_t *coll, plot_graph_t *plot)
{
    plot_data_t x_min = PLOT_MAX;
    plot_data_t x_max = PLOT_MIN;
    plot_data_t y_min = PLOT_MAX;
    plot_data_t y_max = PLOT_MIN;
    uint32_t y_field = 0;
    
    if(!coll || !plot)
    {
        return;
    }

    if(plot->type == PLOT_LINEAR)
    {
        x_min = 0;
        x_max = plot->points_min * (coll->used / plot->points_min + 1);
        y_field = 0;
    }
    else
    {
        for(uint32_t entry = 0; entry < coll->used; entry++)
        {
            plot_data_t value = coll->entries[entry * coll->fields + plot->x_field];

            x_min = MIN(x_min, value);
            x_max = MAX(x_max, value);
        }
        y_field = plot->y_field;
    }

    /* both plot types share the same code for Y range */
    for(uint32_t entry = 0; entry < coll->used; entry++)
    {
        plot_data_t value = coll->entries[entry * coll->fields + y_field];

        y_min = MIN(y_min, value);
        y_max = MAX(y_max, value);
    }

    plot_set_range(plot, x_min, x_max, y_min, y_max);
}


static unsigned int plot_init()
{
#if DEMO_CODE
    uint32_t width = 700;
    uint32_t height = 220;

    /* allocate plot datasets */
    plot_coll_t *coll_1 = plot_alloc_data(1);
    plot_coll_t *coll_2 = plot_alloc_data(2);
    plot_coll_t *coll_3 = plot_alloc_data(2);
    
    /* allocate plot graphics */
    plot_graph_t *plot_1 = plot_alloc_graph(5, 5, width, height);
    plot_graph_t *plot_2 = plot_alloc_graph(5, 5 + height, width/2, height);
    plot_graph_t *plot_3 = plot_alloc_graph(5 + width/2, 5 + height, width/2, height);

    /* X/Y plot */
    plot_2->type = PLOT_XY;
    plot_3->type = PLOT_XY;

    /* selecet displayed range */
    plot_set_range(plot_1, 0, 0, -1000, 1000);
    plot_set_range(plot_2, -1, 1, -1, 1);
    plot_set_range(plot_3, -1, 1, -1, 1);

    for(uint32_t value = 0; value < 10000; value++)
    {
        float x = sinf((float)value / 16.9944444f) * value / 1000;
        float y = cosf((float)value / 16.1111111f) * value / 1000;
        float z = y * cosf((float)value / 13.33f) / ((1 + (float)value / 1000.0f));

        /* add some data for every plot */
        plot_add(coll_1, (y * 1000));
        plot_add(coll_2, x, y);
        plot_add(coll_3, z, x);

        /* override displayed range and autoscale */
        plot_autorange(coll_1, plot_1);
        plot_autorange(coll_2, plot_2);
        plot_autorange(coll_3, plot_3);

        /* now paint or redraw the plots */
        plot_graph_update(coll_1, plot_1);
        plot_graph_update(coll_2, plot_2);
        plot_graph_update(coll_3, plot_3);

        bmp_printf(FONT_MED, 15, 55, "Points: %d", value * 3);
        msleep(10);
    }
    msleep(10000);

#endif

    return 0;
}

static unsigned int plot_deinit()
{
    return 0;
}


MODULE_INFO_START()
    MODULE_INIT(plot_init)
    MODULE_DEINIT(plot_deinit)
MODULE_INFO_END()

back to top