File: | content/fetchers/about/chart.c |
Warning: | line 260, column 19 Although the value stored to 'vallen' is used in the enclosing expression, the value is never actually read from 'vallen' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* |
2 | * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> |
3 | * |
4 | * This file is part of NetSurf. |
5 | * |
6 | * NetSurf is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; version 2 of the License. |
9 | * |
10 | * NetSurf is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | /** |
20 | * \file |
21 | * content generator for the about scheme chart page |
22 | * |
23 | * A chart consists of the figure area in which a chart a title and a |
24 | * key are placed. |
25 | * |
26 | * |
27 | */ |
28 | |
29 | #include <stdbool.h> |
30 | #include <stddef.h> |
31 | #include <stdlib.h> |
32 | #include <string.h> |
33 | #include <math.h> |
34 | #include <stdio.h> |
35 | |
36 | #include "utils/config.h" |
37 | #include "netsurf/inttypes.h" |
38 | #include "utils/config.h" |
39 | #include "utils/utils.h" |
40 | #include "utils/errors.h" |
41 | #include "utils/nsurl.h" |
42 | |
43 | #include "private.h" |
44 | #include "chart.h" |
45 | |
46 | /** minimum figure dimension */ |
47 | #define FIGURE_MIN_WIDTH150 150 |
48 | #define FIGURE_MIN_HEIGHT100 100 |
49 | |
50 | enum chart_type { |
51 | CHART_TYPE_UNKNOWN, |
52 | CHART_TYPE_PIE, |
53 | }; |
54 | |
55 | /* type of chart key */ |
56 | enum key_type { |
57 | CHART_KEY_UNSET, |
58 | CHART_KEY_NONE, |
59 | CHART_KEY_LEFT, |
60 | CHART_KEY_RIGHT, |
61 | CHART_KEY_TOP, |
62 | CHART_KEY_BOT, |
63 | CHART_KEY_END |
64 | }; |
65 | |
66 | |
67 | struct chart_label { |
68 | char *title; /* label title */ |
69 | unsigned int colour; /* colour */ |
70 | }; |
71 | |
72 | struct chart_series { |
73 | unsigned int len; /* number of values in the series */ |
74 | float *value; /* array of values */ |
75 | }; |
76 | |
77 | #define MAX_SERIES4 4 |
78 | |
79 | struct chart_data { |
80 | unsigned int series_len; |
81 | struct chart_series series[MAX_SERIES4]; |
82 | |
83 | unsigned int label_len; /* number of labels */ |
84 | struct chart_label *label; |
85 | |
86 | }; |
87 | |
88 | /** |
89 | * parameters for a chart figure |
90 | */ |
91 | struct chart_param { |
92 | enum chart_type type; |
93 | enum key_type key; /* what type of key to use */ |
94 | unsigned int width; /* width of figure */ |
95 | unsigned int height; /* height of figure */ |
96 | char *title; /* title */ |
97 | struct { |
98 | unsigned int x; |
99 | unsigned int y; |
100 | unsigned int width; |
101 | unsigned int height; |
102 | } area; /* chart area within figure */ |
103 | struct chart_data data; |
104 | }; |
105 | |
106 | #define DEF_COLOUR_NUM8 8 |
107 | /** default colour series */ |
108 | static unsigned int colour_series[DEF_COLOUR_NUM8] = |
109 | { |
110 | 0x00ff00, /* green */ |
111 | 0x0000ff, /* blue */ |
112 | 0xff0000, /* red */ |
113 | 0xffff00, /* yellow */ |
114 | 0x00ffff, /* cyan */ |
115 | 0xff00ff, /* pink */ |
116 | 0x777777, /* grey */ |
117 | 0x000000, /* black */ |
118 | }; |
119 | |
120 | |
121 | /* ensures there are labels present for every value */ |
122 | static nserror ensure_label_count(struct chart_param *chart, unsigned int count) |
123 | { |
124 | unsigned int lidx; |
125 | int deltac; |
126 | struct chart_label *nlabels; |
127 | |
128 | deltac = count - chart->data.label_len; |
129 | if (deltac <= 0) { |
130 | /* there are enough labels */ |
131 | return NSERROR_OK; |
132 | } |
133 | |
134 | nlabels = realloc(chart->data.label, |
135 | count * sizeof(struct chart_label)); |
136 | if (nlabels == NULL((void*)0)) { |
137 | return NSERROR_NOMEM; |
138 | } |
139 | chart->data.label = nlabels; |
140 | |
141 | for (lidx = chart->data.label_len; lidx < count; lidx++) { |
142 | chart->data.label[lidx].title = calloc(1, 20); |
143 | snprintf(chart->data.label[lidx].title, 19, "item %d", lidx + 1); |
144 | chart->data.label[lidx].colour = colour_series[lidx % DEF_COLOUR_NUM8]; |
145 | } |
146 | |
147 | chart->data.label_len = count; |
148 | |
149 | return NSERROR_OK; |
150 | } |
151 | |
152 | /** |
153 | * extract values for a series |
154 | */ |
155 | static nserror |
156 | extract_series_values(struct chart_param *chart, |
157 | unsigned int series_num, |
158 | const char *valstr, |
159 | size_t valstrlen) |
160 | { |
161 | nserror res; |
162 | unsigned int valcur; |
163 | size_t valstart;/* value start in valstr */ |
164 | size_t vallen; /* value end in valstr */ |
165 | struct chart_series *series; |
166 | |
167 | series = chart->data.series + series_num; |
168 | |
169 | /* ensure we do not leak any data in this series */ |
170 | if (series->value != NULL((void*)0)) { |
171 | free(series->value); |
172 | } |
173 | |
174 | /* count how many values present */ |
175 | for (series->len = 1, valstart=0; valstart < valstrlen; valstart++) { |
176 | if (valstr[valstart] == ',') { |
177 | series->len++; |
178 | } |
179 | } |
180 | |
181 | /* allocate storage for values */ |
182 | series->value = calloc(series->len, sizeof(float)); |
183 | if (series->value == NULL((void*)0)) { |
184 | return NSERROR_NOMEM; |
185 | } |
186 | |
187 | /* extract values from query string */ |
188 | for (valcur = 0, vallen = 0, valstart = 0; |
189 | (valstart < valstrlen) && (valcur < series->len); |
190 | valstart += vallen, valcur++) { |
191 | /* get query section length */ |
192 | vallen = 0; |
193 | while (((valstart + vallen) < valstrlen) && |
194 | (valstr[valstart + vallen] != ',')) { |
195 | vallen++; |
196 | } |
197 | |
198 | series->value[valcur] = strtof(valstr + valstart, NULL((void*)0)); |
199 | vallen++; /* account for , separator */ |
200 | } |
201 | |
202 | res = ensure_label_count(chart, series->len); |
203 | |
204 | return res; |
205 | } |
206 | |
207 | |
208 | /** |
209 | * extract values for next series |
210 | */ |
211 | static nserror |
212 | extract_next_series_values(struct chart_param *chart, |
213 | const char *valstr, |
214 | size_t valstrlen) |
215 | { |
216 | nserror res; |
217 | |
218 | if (chart->data.series_len >= MAX_SERIES4) { |
219 | return NSERROR_NOSPACE; |
220 | } |
221 | |
222 | res = extract_series_values(chart, |
223 | chart->data.series_len, |
224 | valstr, |
225 | valstrlen); |
226 | if (res == NSERROR_OK) { |
227 | chart->data.series_len++; |
228 | } |
229 | |
230 | return res; |
231 | } |
232 | |
233 | |
234 | /** |
235 | * extract label title |
236 | */ |
237 | static nserror |
238 | extract_series_labels(struct chart_param *chart, |
239 | const char *valstr, |
240 | size_t valstrlen) |
241 | { |
242 | nserror res; |
243 | unsigned int valcount; /* count of values in valstr */ |
244 | unsigned int valcur; |
245 | size_t valstart;/* value start in valstr */ |
246 | size_t vallen; /* value end in valstr */ |
247 | |
248 | for (valcount = 1, valstart=0; valstart < valstrlen; valstart++) { |
249 | if (valstr[valstart] == ',') { |
250 | valcount++; |
251 | } |
252 | } |
253 | |
254 | res = ensure_label_count(chart, valcount); |
255 | if (res != NSERROR_OK) { |
256 | return res; |
257 | } |
258 | |
259 | |
260 | for (valcur = 0, vallen = 0, valstart = 0; |
Although the value stored to 'vallen' is used in the enclosing expression, the value is never actually read from 'vallen' | |
261 | (valstart < valstrlen) && (valcur < chart->data.label_len); |
262 | valstart += vallen, valcur++) { |
263 | /* get query section length */ |
264 | vallen = 0; |
265 | while (((valstart + vallen) < valstrlen) && |
266 | (valstr[valstart + vallen] != ',')) { |
267 | vallen++; |
268 | } |
269 | |
270 | chart->data.label[valcur].title = strndup(valstr + valstart, vallen); |
271 | vallen++; /* account for , separator */ |
272 | } |
273 | return NSERROR_OK; |
274 | } |
275 | |
276 | |
277 | /** |
278 | * extract labels colour |
279 | */ |
280 | static nserror |
281 | extract_series_colours(struct chart_param *chart, |
282 | const char *valstr, |
283 | size_t valstrlen) |
284 | { |
285 | return NSERROR_OK; |
286 | } |
287 | |
288 | /** |
289 | * process a part of a query |
290 | */ |
291 | static nserror |
292 | process_query_section(const char *str, size_t len, struct chart_param *chart) |
293 | { |
294 | nserror res = NSERROR_OK; |
295 | |
296 | if ((len > 6) && |
297 | (strncmp(str, "width=", 6) == 0)) { |
298 | /* figure width */ |
299 | chart->width = strtoul(str + 6, NULL((void*)0), 10); |
300 | } else if ((len > 7) && |
301 | (strncmp(str, "height=", 7) == 0)) { |
302 | /* figure height */ |
303 | chart->height = strtoul(str + 7, NULL((void*)0), 10); |
304 | } else if ((len > 8) && |
305 | (strncmp(str, "cawidth=", 8) == 0)) { |
306 | /* chart area width */ |
307 | chart->area.width = strtoul(str + 8, NULL((void*)0), 10); |
308 | } else if ((len > 9) && |
309 | (strncmp(str, "caheight=", 9) == 0)) { |
310 | /* chart area height */ |
311 | chart->area.height = strtoul(str + 9, NULL((void*)0), 10); |
312 | } else if ((len > 4) && |
313 | (strncmp(str, "key=", 4) == 0)) { |
314 | /* figure has key */ |
315 | chart->key = strtoul(str + 4, NULL((void*)0), 10); |
316 | } else if ((len > 6) && |
317 | (strncmp(str, "title=", 6) == 0)) { |
318 | chart->title = strndup(str + 6, len - 6); |
319 | } else if ((len > 5) && |
320 | (strncmp(str, "type=", 5) == 0)) { |
321 | if (strncmp(str + 5, "pie", len - 5) == 0) { |
322 | chart->type = CHART_TYPE_PIE; |
323 | } else { |
324 | chart->type = CHART_TYPE_UNKNOWN; |
325 | } |
326 | } else if ((len > 7) && |
327 | (strncmp(str, "values=", 7) == 0)) { |
328 | res = extract_next_series_values(chart, str + 7, len - 7); |
329 | } else if ((len > 7) && |
330 | (strncmp(str, "labels=", 7) == 0)) { |
331 | res = extract_series_labels(chart, str + 7, len - 7); |
332 | } else if ((len > 8) && |
333 | (strncmp(str, "colours=", 8) == 0)) { |
334 | res = extract_series_colours(chart, str + 8, len - 8); |
335 | } |
336 | |
337 | return res; |
338 | } |
339 | |
340 | |
341 | |
342 | static nserror |
343 | chart_from_query(struct nsurl *url, struct chart_param *chart) |
344 | { |
345 | nserror res; |
346 | char *querystr; |
347 | size_t querylen; |
348 | size_t kvstart;/* key value start */ |
349 | size_t kvlen; /* key value end */ |
350 | |
351 | res = nsurl_get(url, NSURL_QUERY, &querystr, &querylen); |
352 | if (res != NSERROR_OK) { |
353 | return res; |
354 | } |
355 | |
356 | for (kvlen = 0, kvstart = 0; kvstart < querylen; kvstart += kvlen) { |
357 | /* get query section length */ |
358 | kvlen = 0; |
359 | while (((kvstart + kvlen) < querylen) && |
360 | (querystr[kvstart + kvlen] != '&')) { |
361 | kvlen++; |
362 | } |
363 | |
364 | res = process_query_section(querystr + kvstart, kvlen, chart); |
365 | if (res != NSERROR_OK) { |
366 | break; |
367 | } |
368 | kvlen++; /* account for & separator */ |
369 | } |
370 | free(querystr); |
371 | |
372 | /* sanity check dimensions */ |
373 | if (chart->width < FIGURE_MIN_WIDTH150) { |
374 | /* bad width - check height */ |
375 | if (chart->height < FIGURE_MIN_HEIGHT100) { |
376 | /* both bad set to defaults */ |
377 | chart->width = FIGURE_MIN_WIDTH150; |
378 | chart->height = FIGURE_MIN_HEIGHT100; |
379 | } else { |
380 | /* base width on valid height */ |
381 | chart->width = (chart->height * 3) / 2; |
382 | } |
383 | } else { |
384 | /* good width check height */ |
385 | if (chart->height < FIGURE_MIN_HEIGHT100) { |
386 | /* base height on valid width */ |
387 | chart->height = (chart->width * 2) / 3; |
388 | } |
389 | } |
390 | |
391 | /* ensure legend type correct */ |
392 | if ((chart->key == CHART_KEY_UNSET) || |
393 | (chart->key >= CHART_KEY_END )) { |
394 | /* default to putting key on right */ |
395 | chart->key = CHART_KEY_RIGHT; |
396 | } |
397 | |
398 | return NSERROR_OK; |
399 | } |
400 | |
401 | |
402 | static nserror |
403 | output_pie_legend(struct fetch_about_context *ctx, struct chart_param *chart) |
404 | { |
405 | nserror res; |
406 | unsigned int lblidx; |
407 | unsigned int legend_width; |
408 | unsigned int legend_height; |
409 | unsigned int vertical_spacing; |
410 | |
411 | switch (chart->key) { |
412 | |
413 | case CHART_KEY_NONE: |
414 | break; |
415 | case CHART_KEY_RIGHT: |
416 | legend_width = chart->width - chart->area.width - chart->area.x; |
417 | legend_width -= 10; /* margin */ |
418 | legend_height = chart->height; |
419 | vertical_spacing = legend_height / (chart->data.label_len + 1); |
420 | |
421 | for(lblidx = 0; lblidx < chart->data.label_len ; lblidx++) { |
422 | res = fetch_about_ssenddataf(ctx, |
423 | "<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"#%06x\" />", |
424 | chart->width - legend_width, |
425 | (vertical_spacing * lblidx) + (vertical_spacing/2), |
426 | vertical_spacing * 2 / 3, |
427 | vertical_spacing * 2 / 3, |
428 | chart->data.label[lblidx].colour); |
429 | if (res != NSERROR_OK) { |
430 | return res; |
431 | } |
432 | res = fetch_about_ssenddataf(ctx, |
433 | "<text x=\"%d\" y=\"%d\" fill=\"#%06x\" >%s</text>", |
434 | chart->width - legend_width + vertical_spacing, |
435 | vertical_spacing * (lblidx+1), |
436 | chart->data.label[lblidx].colour, |
437 | chart->data.label[lblidx].title); |
438 | if (res != NSERROR_OK) { |
439 | return res; |
440 | } |
441 | } |
442 | break; |
443 | default: |
444 | break; |
445 | } |
446 | |
447 | return NSERROR_OK; |
448 | } |
449 | |
450 | static float |
451 | compute_series_total(struct chart_param *chart, unsigned int series) |
452 | { |
453 | float total; |
454 | unsigned int curdata; |
455 | |
456 | for (total = 0, curdata = 0; |
457 | curdata < chart->data.series[series].len; |
458 | curdata++) { |
459 | total += chart->data.series[series].value[curdata]; |
460 | } |
461 | return total; |
462 | } |
463 | |
464 | /** |
465 | * render the data as a pie chart svg |
466 | */ |
467 | static bool_Bool |
468 | pie_chart(struct fetch_about_context *ctx, struct chart_param *chart) |
469 | { |
470 | nserror res; |
471 | float ra; /* pie a radius */ |
472 | float rb; /* pie b radius */ |
473 | float series_total; |
474 | unsigned int curdata; /* current data point index */ |
475 | float last_x, last_y; |
476 | float end_x, end_y; |
477 | float start; |
478 | float extent; |
479 | bool_Bool large; |
480 | float circle_centre_x, circle_centre_y; |
481 | |
482 | /* ensure there is data to render */ |
483 | if ((chart->data.series_len < 1) || (chart->data.series[0].len < 2)) { |
484 | return NSERROR_BAD_PARAMETER; |
485 | } |
486 | |
487 | /* get the first series total value */ |
488 | series_total = compute_series_total(chart, 0); |
489 | if (series_total == 0) { |
490 | /* dividing by zero is embarasing */ |
491 | return NSERROR_BAD_PARAMETER; |
492 | } |
493 | |
494 | /* |
495 | * need to ensure the chart area is setup correctly |
496 | * |
497 | * this is left to each chart type as different charts |
498 | * have differnt requirements |
499 | */ |
500 | if ((chart->area.width == 0) || (chart->area.height == 0)) { |
501 | /* |
502 | * pie chart defaults to square of smaller of figure |
503 | * width and height |
504 | */ |
505 | if (chart->width > chart->height) { |
506 | chart->area.width = chart->area.height = (chart->height - chart->area.x); |
507 | } else { |
508 | chart->area.width = chart->area.height = (chart->width - chart->area.y); |
509 | } |
510 | } |
511 | |
512 | /* content is going to return ok */ |
513 | fetch_about_set_http_code(ctx, 200); |
514 | |
515 | /* content type */ |
516 | if (fetch_about_send_header(ctx, |
517 | "Content-Type: image/svg; charset=utf-8")) { |
518 | goto aborted; |
519 | } |
520 | |
521 | /* get the pie charts elipse radii */ |
522 | ra = chart->area.width / 2; |
523 | rb = chart->area.height / 2; |
524 | |
525 | /* get the offset to the circle centre */ |
526 | circle_centre_x = chart->area.x + ra; |
527 | circle_centre_y = chart->area.y + rb; |
528 | |
529 | |
530 | /* svg header */ |
531 | res = fetch_about_ssenddataf(ctx, |
532 | "<svg width=\"%u\" height=\"%u\" " |
533 | "xmlns=\"http://www.w3.org/2000/svg\">\n", |
534 | chart->width, chart->height); |
535 | if (res != NSERROR_OK) { |
536 | goto aborted; |
537 | } |
538 | |
539 | /* generate the legend */ |
540 | res = output_pie_legend(ctx, chart); |
541 | if (res != NSERROR_OK) { |
542 | goto aborted; |
543 | } |
544 | |
545 | /* plot the arcs */ |
546 | start = -M_PI_21.57079632679489661923; |
547 | last_x = (ra * cos(start)); |
548 | last_y = (rb * sin(start)); |
549 | |
550 | /* iterate over each data point creating a slice o pie */ |
551 | for (curdata=0; curdata < chart->data.series[0].len; curdata++) { |
552 | extent = ((chart->data.series[0].value[curdata] / series_total) * 2 * M_PI3.14159265358979323846); |
553 | end_x = (ra * cos(start + extent)); |
554 | end_y = (rb * sin(start + extent)); |
555 | |
556 | if (extent > M_PI3.14159265358979323846) { |
557 | large = true1; |
558 | } else { |
559 | large = false0; |
560 | } |
561 | |
562 | res = fetch_about_ssenddataf( |
563 | ctx, |
564 | "<path d=\"M %g %g\n" |
565 | "A %g %g 0 %d 1 %g %g\n" |
566 | "L %g %g Z\" fill=\"#%06x\" />\n", |
567 | circle_centre_x + last_x, |
568 | circle_centre_y + last_y, |
569 | ra, rb, large?1:0, |
570 | circle_centre_x + end_x, |
571 | circle_centre_y + end_y, |
572 | circle_centre_x, |
573 | circle_centre_y, |
574 | chart->data.label[curdata].colour); |
575 | if (res != NSERROR_OK) { |
576 | goto aborted; |
577 | } |
578 | last_x = end_x; |
579 | last_y = end_y; |
580 | start +=extent; |
581 | } |
582 | |
583 | res = fetch_about_ssenddataf(ctx, "</svg>\n"); |
584 | if (res != NSERROR_OK) { |
585 | goto aborted; |
586 | } |
587 | |
588 | fetch_about_send_finished(ctx); |
589 | |
590 | return true1; |
591 | |
592 | aborted: |
593 | |
594 | return false0; |
595 | |
596 | } |
597 | |
598 | /** |
599 | * Handler to generate about scheme chart page. |
600 | * |
601 | * generates an svg chart |
602 | * |
603 | * \param ctx The fetcher context. |
604 | * \return true if handled false if aborted. |
605 | */ |
606 | bool_Bool fetch_about_chart_handler(struct fetch_about_context *ctx) |
607 | { |
608 | nserror res; |
609 | struct chart_param chart; |
610 | memset(&chart, 0, sizeof(struct chart_param)); |
611 | |
612 | res = chart_from_query(fetch_about_get_url(ctx), &chart); |
613 | if (res != NSERROR_OK) { |
614 | goto aborted; |
615 | } |
616 | |
617 | switch (chart.type) { |
618 | case CHART_TYPE_PIE: |
619 | return pie_chart(ctx, &chart); |
620 | |
621 | |
622 | default: |
623 | break; |
624 | } |
625 | |
626 | aborted: |
627 | |
628 | return false0; |
629 | |
630 | } |