-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathheatmap.c
194 lines (171 loc) · 5.32 KB
/
heatmap.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include <stdint.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "color_scales.h"
// We're holding a color scale, which has some domain,
// with 2-4 stops, k. We compute the sum and count of
// data points, per domain region, i.e. the k + 1 regions
// defined by the k stops.
#include "heatmap_struct.h"
void tally_domains(struct summary *summary, struct scale *s, float *data, uint32_t *order, int start, int end) {
int h = s->count;
float v;
int d;
memset(summary, 0, sizeof(struct summary));
for (int i = start; i < end; ++i) {
float v = data[order[i]];
if (isnan(v)) {
continue;
}
if (v >= s->domain[h - 1]) {
d = h;
} else if (v <= s->domain[0]) {
d = 0;
} else {
switch (h) { // consider splitting into multiple fns to avoid this switch
case 4:
if (v > s->domain[2]) {
d = 3;
break;
}
case 3:
if (v > s->domain[1]) {
d = 2;
break;
}
case 2:
d = 1;
}
}
summary->count[d]++;
summary->sum[d] += v;
}
}
static int sum(uint32_t *counts, int n) {
int s = 0;
for (int i = 0; i < n; ++i) {
s += counts[i];
}
return s;
}
typedef uint32_t region_color_fn(uint32_t (*)(ctx, double), ctx, float *, uint32_t *, int, int);
static uint32_t region_color_default(uint32_t (*colorfn)(ctx, double), ctx ctx, float *d, uint32_t *order, int start, int end) {
struct scale *s = ctx.scale;
struct summary sy;
tally_domains(&sy, s, d, order, start, end);
int ranges = s->count + 1;
int total = sum(sy.count, ranges);
uint32_t color;
if (total) {
double r = 0, g = 0, b = 0;
for (int i = 0; i < ranges; ++i) {
int c = sy.count[i];
if (c) {
uint32_t rcolor = colorfn(ctx, sy.sum[i] / c);
int rc = R(rcolor);
r += ((double)rc * rc * c) / total;
int gc = G(rcolor);
g += ((double)gc * gc * c) / total;
int bc = B(rcolor);
b += ((double)bc * bc * c) / total;
}
}
color = RGB((int)sqrt(r), (int)sqrt(g), (int)sqrt(b));
} else {
color = RGB(0x80, 0x80, 0x80);
}
return color;
}
uint32_t region_color_linear_test(void *vctx, float *d, uint32_t *order, int start, int end) {
ctx ctx;
ctx.scale = vctx;
return region_color_default(get_method(LINEAR), ctx, d, order, start, end);
}
static int randmax(int m) {
return (int)(((double)rand() * m) / RAND_MAX);
}
// XXX look into eliminating this alloc/free
static uint32_t region_color_ordinal(uint32_t (*colorfn)(ctx, double), ctx ctx, float *d, uint32_t *order, int start, int end) {
// The js algorithm is to pick randomly from the
// non-null values in the range [start, end). It does this
// by first filtering the data. Not sure there's a better way.
float *filtered = malloc((end - start) * sizeof(float));
float *p = filtered;
for (int i = start; i < end; ++i) {
float v = d[order[i]];
if (!isnan(v)) {
*p++ = v;
}
}
int count = p - filtered;
uint32_t color = count ? colorfn(ctx, filtered[randmax(count)]) : 0xFF808080;
free(filtered);
return color;
}
static void fill_region(uint32_t *img, int width, int elstart, int elsize,
int ry, int rheight, uint32_t c32) {
for (int y = ry; y < ry + rheight; ++y) {
int pxRow = y * width;
int buffStart = pxRow + elstart;
int buffEnd = buffStart + elsize;
// try 64 bits? try simd when it's available.
for (int l = buffStart; l < buffEnd;) {
img[l++] = c32;
}
}
}
static void project_samples(
region_color_fn region_color,
color_fn colorfn,
ctx ctx, float *d, uint32_t *order, int first, int count,
uint32_t *img, int width, int height,
int elstart, int elsize) {
for (int i = 0; i < count; ++i) {
int ry = i * height / count;
int rheight = (i + 1) * height / count - ry;
uint32_t color = region_color(colorfn, ctx, d, order, first + i, first + i + 1);
if (color != 0xFF808080) {
fill_region(img, width, elstart, elsize, ry, rheight, color);
}
}
}
// divide & take ceiling
static int div_ceil(int x, int y) {
return (x + y - 1) / y;
}
static void project_pixels(
region_color_fn region_color,
color_fn colorfn,
ctx ctx, float *d, uint32_t *order, int first, int count,
uint32_t *img, int width, int height,
int elstart, int elsize) {
for (int ry = 0; ry < height; ++ry) {
int rstart = div_ceil(ry * count, height);
int rend = div_ceil((ry + 1) * count, height) - 1;
uint32_t color = region_color(colorfn, ctx, d, order, first + rstart, first + rend + 1);
if (color != 0xFF808080) {
fill_region(img, width, elstart, elsize, ry, 1, color);
}
}
}
void draw_subcolumn(
enum type type,
void *vctx, float *d, uint32_t *order, int first, int count,
uint32_t *img, int width, int height,
int elstart, int elsize) {
ctx ctx;
// Using a union to represent different pointer types. Maybe we should
// just use void*. We can't pass it in as a union because the wasm
// entry point doesn't handle the union correctly. So, passing it as void*
// and populating the union here. Doesn't really matter which union member
// we assign, since they're the same size, and location.
ctx.scale = vctx;
region_color_fn *region_color = type == ORDINAL ? region_color_ordinal : region_color_default;
color_fn *colorfn = get_method(type);
if (height > count) {
project_samples(region_color, colorfn, ctx, d, order, first, count, img, width, height, elstart, elsize);
} else {
project_pixels(region_color, colorfn, ctx, d, order, first, count, img, width, height, elstart, elsize);
}
}