gintctl/src/plot.c

241 lines
5.5 KiB
C

#include <gint/display.h>
#include <gintctl/plot.h>
#include <gintctl/util.h>
#include <stdio.h>
struct tick_info
{
int count;
int32_t value[64];
uint16_t px_pos[64];
int8_t primary[64];
int max_width;
int max_height;
};
static int graph_x(struct plot *s, int v)
{
return s->graph.x + (s->graph.w-1) * (v-s->min_x) / (s->max_x-s->min_x);
}
static int graph_y(struct plot *s, int v)
{
return s->graph.y + s->graph.h -
(s->graph.h-1) * (v-s->min_y) / (s->max_y-s->min_y);
}
static void layout_ticks(struct plot *s, int xy, struct tick_info *i)
{
struct plot_ticks *t = xy ? &s->ticks_y : &s->ticks_x;
char str[256];
char const *fmt = t->format ? t->format : "%d";
i->count = 0;
i->max_width = 0;
i->max_height = 0;
if(!t->multiples) return;
int subdivs = t->subtick_divisions > 0 ? t->subtick_divisions : 1;
/* Start at the minimum value and work towards the maximum one */
int min = xy ? s->min_y : s->min_x;
int max = xy ? s->max_y : s->max_x;
int v = min + t->multiples - 1;
v -= (v % t->multiples);
while(v <= max && i->count < 64)
{
i->primary[i->count] = (v % (t->multiples * subdivs) == 0);
i->value[i->count] = v;
i->px_pos[i->count] = xy ? graph_y(s, v) : graph_x(s, v);
(i->count)++;
if(t->formatter) t->formatter(str, 256, v);
else snprintf(str, 256, fmt, v);
int w, h;
dsize(str, NULL, &w, &h);
if(w > i->max_width) i->max_width = w;
if(h > i->max_height) i->max_height = h;
v += t->multiples;
}
}
void plot(struct plot *s)
{
struct tick_info tx, ty;
char str[256];
int tick_w, tick_h;
int32_t *data_x = s->data_x;
int32_t *data_y = s->data_y;
int axis_spacing = _(2,3);
int tick_length = _(2,4);
/* Determine the bounds of the rendering area */
s->min_x = 0;
s->max_x = 0;
s->min_y = 0;
s->max_y = 0;
for(int i = 0; i < s->data_len; i++)
{
if(data_x[i] < s->min_x) s->min_x = data_x[i];
if(data_x[i] > s->max_x) s->max_x = data_x[i];
if(data_y[i] < s->min_y) s->min_y = data_y[i];
if(data_y[i] > s->max_y) s->max_y = data_y[i];
}
if(s->min_x == s->max_x || s->min_y == s->max_y) return;
/* Determine the number, position and size of ticks */
font_t const *f = dfont(NULL);
dfont(f);
/* Start with vertical ticks */
s->graph.y = s->area.y;
s->graph.h = s->area.h - axis_spacing - f->line_height;
layout_ticks(s, 1, &ty);
/* Continue with horizontal ticks */
s->graph.x = s->area.x + axis_spacing + ty.max_width;
s->graph.w = s->area.w - (s->graph.x - s->area.x);
layout_ticks(s, 0, &tx);
/* Render grid, first secondary ticks, then primary ticks */
int dotted = s->grid.dotted;
for(int i = 0; i < ty.count; i++) if(!ty.primary[i])
{
int y = ty.px_pos[i];
for(int x = s->graph.x; x < s->graph.x + s->graph.w; x++)
{
if(dotted && !((x^y) & 1)) continue;
dpixel(x, y, s->grid.secondary_color);
}
}
for(int i = 0; i < tx.count; i++) if(!tx.primary[i])
{
int x = tx.px_pos[i];
for(int y = s->graph.y; y < s->graph.y + s->graph.h; y++)
{
if(dotted && !((x^y) & 1)) continue;
dpixel(x, y, s->grid.secondary_color);
}
}
for(int i = 0; i < ty.count; i++) if(ty.primary[i])
{
int y = ty.px_pos[i];
for(int x = s->graph.x; x < s->graph.x + s->graph.w; x++)
{
if(dotted && !((x^y) & 1)) continue;
dpixel(x, y, s->grid.primary_color);
}
}
for(int i = 0; i < tx.count; i++) if(tx.primary[i])
{
int x = tx.px_pos[i];
for(int y = s->graph.y; y < s->graph.y + s->graph.h; y++)
{
if(dotted && !((x^y) & 1)) continue;
dpixel(x, y, s->grid.primary_color);
}
}
/* Render ticks */
int horz_axis = s->graph.y + s->graph.h;
int vert_axis = s->graph.x;
for(int i = 0; i < ty.count; i++)
{
char const *format = s->ticks_y.format;
if(!format) format = "%d";
if(s->ticks_y.formatter)
s->ticks_y.formatter(str, 256, ty.value[i]);
else snprintf(str, 256, format, ty.value[i]);
dsize(str, NULL, NULL, &tick_h);
/* Try to center the text left of the tick, but move it up or
down if it overflows from the render region */
int y = ty.px_pos[i] - (tick_h >> 1);
if(y < s->graph.y)
y = s->graph.y;
if(y + tick_h > s->graph.y + s->graph.h)
y = s->graph.y + s->graph.h - tick_h;
dline(vert_axis, ty.px_pos[i], vert_axis + tick_length,
ty.px_pos[i], C_BLACK);
if(!ty.primary[i]) continue;
dtext_opt(vert_axis - axis_spacing, y, C_BLACK, C_NONE,
DTEXT_RIGHT, DTEXT_TOP, str);
}
for(int i = 0; i < tx.count; i++)
{
char const *format = s->ticks_y.format;
if(!format) format = "%d";
if(s->ticks_x.formatter)
s->ticks_x.formatter(str, 256, tx.value[i]);
else snprintf(str, 256, format, tx.value[i]);
dsize(str, NULL, &tick_w, NULL);
/* Try to center the text below the tick, but move it left or
right if it overflows from the render region */
int x = tx.px_pos[i] - ((tick_w+1) >> 1);
if(x < s->graph.x)
x = s->graph.x;
if(x+tick_w > s->graph.x + s->graph.w)
x = s->graph.x + s->graph.w - tick_w;
dline(tx.px_pos[i], horz_axis, tx.px_pos[i],
horz_axis - tick_length, C_BLACK);
if(!tx.primary[i]) continue;
dtext(x, horz_axis + axis_spacing, C_BLACK, str);
}
/* Render axes */
int x2 = s->area.x + s->area.w - 1;
dline(vert_axis, s->area.y, vert_axis, horz_axis, C_BLACK);
dline(vert_axis, horz_axis, x2, horz_axis, C_BLACK);
dline(vert_axis, s->area.y, x2, s->area.y, C_BLACK);
dline(x2, s->area.y, x2, horz_axis, C_BLACK);
/* Plot data */
int last_x = 0;
int last_y = 0;
for(int i = 0; i < s->data_len; i++)
{
int x = graph_x(s, s->data_x[i]);
int y = graph_y(s, s->data_y[i]);
if(i > 0) dline(last_x, last_y, x, y, s->color);
last_x = x;
last_y = y;
}
}