summaryrefslogtreecommitdiff
path: root/content/fetchers/about/chart.c
diff options
context:
space:
mode:
Diffstat (limited to 'content/fetchers/about/chart.c')
-rw-r--r--content/fetchers/about/chart.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/content/fetchers/about/chart.c b/content/fetchers/about/chart.c
new file mode 100644
index 000000000..1565c54d8
--- /dev/null
+++ b/content/fetchers/about/chart.c
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme chart page
+ *
+ * A chart consists of the figure area in which a chart a title and a
+ * key are placed.
+ *
+ *
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "netsurf/inttypes.h"
+#include "utils/utils.h"
+#include "utils/errors.h"
+#include "utils/nsurl.h"
+
+#include "private.h"
+#include "chart.h"
+
+/** minimum figure dimension */
+#define FIGURE_MIN_WIDTH 150
+#define FIGURE_MIN_HEIGHT 100
+
+enum chart_type {
+ CHART_TYPE_UNKNOWN,
+ CHART_TYPE_PIE,
+};
+
+/* type of chart key */
+enum key_type {
+ CHART_KEY_UNSET,
+ CHART_KEY_NONE,
+ CHART_KEY_LEFT,
+ CHART_KEY_RIGHT,
+ CHART_KEY_TOP,
+ CHART_KEY_BOT,
+ CHART_KEY_END
+};
+
+
+struct chart_label {
+ char *title; /* label title */
+ unsigned int colour; /* colour */
+};
+
+struct chart_series {
+ unsigned int len; /* number of values in the series */
+ float *value; /* array of values */
+};
+
+#define MAX_SERIES 4
+
+struct chart_data {
+ unsigned int series_len;
+ struct chart_series series[MAX_SERIES];
+
+ unsigned int label_len; /* number of labels */
+ struct chart_label *label;
+
+};
+
+/**
+ * parameters for a chart figure
+ */
+struct chart_param {
+ enum chart_type type;
+ enum key_type key; /* what type of key to use */
+ unsigned int width; /* width of figure */
+ unsigned int height; /* height of figure */
+ char *title; /* title */
+ struct {
+ unsigned int x;
+ unsigned int y;
+ unsigned int width;
+ unsigned int height;
+ } area; /* chart area within figure */
+ struct chart_data data;
+};
+
+#define DEF_COLOUR_NUM 8
+/** default colour series */
+static unsigned int colour_series[DEF_COLOUR_NUM] =
+ {
+ 0x00ff00, /* green */
+ 0x0000ff, /* blue */
+ 0xff0000, /* red */
+ 0xffff00, /* yellow */
+ 0x00ffff, /* cyan */
+ 0xff00ff, /* pink */
+ 0x777777, /* grey */
+ 0x000000, /* black */
+ };
+
+
+/* ensures there are labels present for every value */
+static nserror ensure_label_count(struct chart_param *chart, unsigned int count)
+{
+ unsigned int lidx;
+ int deltac;
+ struct chart_label *nlabels;
+
+ deltac = count - chart->data.label_len;
+ if (deltac <= 0) {
+ /* there are enough labels */
+ return NSERROR_OK;
+ }
+
+ nlabels = realloc(chart->data.label,
+ count * sizeof(struct chart_label));
+ if (nlabels == NULL) {
+ return NSERROR_NOMEM;
+ }
+ chart->data.label = nlabels;
+
+ for (lidx = chart->data.label_len; lidx < count; lidx++) {
+ chart->data.label[lidx].title = calloc(1, 20);
+ snprintf(chart->data.label[lidx].title, 19, "item %d", lidx + 1);
+ chart->data.label[lidx].colour = colour_series[lidx % DEF_COLOUR_NUM];
+ }
+
+ chart->data.label_len = count;
+
+ return NSERROR_OK;
+}
+
+/**
+ * extract values for a series
+ */
+static nserror
+extract_series_values(struct chart_param *chart,
+ unsigned int series_num,
+ const char *valstr,
+ size_t valstrlen)
+{
+ nserror res;
+ unsigned int valcur;
+ size_t valstart;/* value start in valstr */
+ size_t vallen; /* value end in valstr */
+ struct chart_series *series;
+
+ series = chart->data.series + series_num;
+
+ /* ensure we do not leak any data in this series */
+ if (series->value != NULL) {
+ free(series->value);
+ }
+
+ /* count how many values present */
+ for (series->len = 1, valstart=0; valstart < valstrlen; valstart++) {
+ if (valstr[valstart] == ',') {
+ series->len++;
+ }
+ }
+
+ /* allocate storage for values */
+ series->value = calloc(series->len, sizeof(float));
+ if (series->value == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ /* extract values from query string */
+ for (valcur = 0, vallen = 0, valstart = 0;
+ (valstart < valstrlen) && (valcur < series->len);
+ valstart += vallen, valcur++) {
+ /* get query section length */
+ vallen = 0;
+ while (((valstart + vallen) < valstrlen) &&
+ (valstr[valstart + vallen] != ',')) {
+ vallen++;
+ }
+
+ series->value[valcur] = strtof(valstr + valstart, NULL);
+ vallen++; /* account for , separator */
+ }
+
+ res = ensure_label_count(chart, series->len);
+
+ return res;
+}
+
+
+/**
+ * extract values for next series
+ */
+static nserror
+extract_next_series_values(struct chart_param *chart,
+ const char *valstr,
+ size_t valstrlen)
+{
+ nserror res;
+
+ if (chart->data.series_len >= MAX_SERIES) {
+ return NSERROR_NOSPACE;
+ }
+
+ res = extract_series_values(chart,
+ chart->data.series_len,
+ valstr,
+ valstrlen);
+ if (res == NSERROR_OK) {
+ chart->data.series_len++;
+ }
+
+ return res;
+}
+
+
+/**
+ * extract label title
+ */
+static nserror
+extract_series_labels(struct chart_param *chart,
+ const char *valstr,
+ size_t valstrlen)
+{
+ nserror res;
+ unsigned int valcount; /* count of values in valstr */
+ unsigned int valcur;
+ size_t valstart;/* value start in valstr */
+ size_t vallen; /* value end in valstr */
+
+ for (valcount = 1, valstart=0; valstart < valstrlen; valstart++) {
+ if (valstr[valstart] == ',') {
+ valcount++;
+ }
+ }
+
+ res = ensure_label_count(chart, valcount);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+
+ for (valcur = 0, vallen = 0, valstart = 0;
+ (valstart < valstrlen) && (valcur < chart->data.label_len);
+ valstart += vallen, valcur++) {
+ /* get query section length */
+ vallen = 0;
+ while (((valstart + vallen) < valstrlen) &&
+ (valstr[valstart + vallen] != ',')) {
+ vallen++;
+ }
+
+ chart->data.label[valcur].title = strndup(valstr + valstart, vallen);
+ vallen++; /* account for , separator */
+ }
+ return NSERROR_OK;
+}
+
+
+/**
+ * extract labels colour
+ */
+static nserror
+extract_series_colours(struct chart_param *chart,
+ const char *valstr,
+ size_t valstrlen)
+{
+ return NSERROR_OK;
+}
+
+/**
+ * process a part of a query
+ */
+static nserror
+process_query_section(const char *str, size_t len, struct chart_param *chart)
+{
+ nserror res = NSERROR_OK;
+
+ if ((len > 6) &&
+ (strncmp(str, "width=", 6) == 0)) {
+ /* figure width */
+ chart->width = strtoul(str + 6, NULL, 10);
+ } else if ((len > 7) &&
+ (strncmp(str, "height=", 7) == 0)) {
+ /* figure height */
+ chart->height = strtoul(str + 7, NULL, 10);
+ } else if ((len > 8) &&
+ (strncmp(str, "cawidth=", 8) == 0)) {
+ /* chart area width */
+ chart->area.width = strtoul(str + 8, NULL, 10);
+ } else if ((len > 9) &&
+ (strncmp(str, "caheight=", 9) == 0)) {
+ /* chart area height */
+ chart->area.height = strtoul(str + 9, NULL, 10);
+ } else if ((len > 4) &&
+ (strncmp(str, "key=", 4) == 0)) {
+ /* figure has key */
+ chart->key = strtoul(str + 4, NULL, 10);
+ } else if ((len > 6) &&
+ (strncmp(str, "title=", 6) == 0)) {
+ chart->title = strndup(str + 6, len - 6);
+ } else if ((len > 5) &&
+ (strncmp(str, "type=", 5) == 0)) {
+ if (strncmp(str + 5, "pie", len - 5) == 0) {
+ chart->type = CHART_TYPE_PIE;
+ } else {
+ chart->type = CHART_TYPE_UNKNOWN;
+ }
+ } else if ((len > 7) &&
+ (strncmp(str, "values=", 7) == 0)) {
+ res = extract_next_series_values(chart, str + 7, len - 7);
+ } else if ((len > 7) &&
+ (strncmp(str, "labels=", 7) == 0)) {
+ res = extract_series_labels(chart, str + 7, len - 7);
+ } else if ((len > 8) &&
+ (strncmp(str, "colours=", 8) == 0)) {
+ res = extract_series_colours(chart, str + 8, len - 8);
+ }
+
+ return res;
+}
+
+
+
+static nserror
+chart_from_query(struct nsurl *url, struct chart_param *chart)
+{
+ nserror res;
+ char *querystr;
+ size_t querylen;
+ size_t kvstart;/* key value start */
+ size_t kvlen; /* key value end */
+
+ res = nsurl_get(url, NSURL_QUERY, &querystr, &querylen);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ for (kvlen = 0, kvstart = 0; kvstart < querylen; kvstart += kvlen) {
+ /* get query section length */
+ kvlen = 0;
+ while (((kvstart + kvlen) < querylen) &&
+ (querystr[kvstart + kvlen] != '&')) {
+ kvlen++;
+ }
+
+ res = process_query_section(querystr + kvstart, kvlen, chart);
+ if (res != NSERROR_OK) {
+ break;
+ }
+ kvlen++; /* account for & separator */
+ }
+ free(querystr);
+
+ /* sanity check dimensions */
+ if (chart->width < FIGURE_MIN_WIDTH) {
+ /* bad width - check height */
+ if (chart->height < FIGURE_MIN_HEIGHT) {
+ /* both bad set to defaults */
+ chart->width = FIGURE_MIN_WIDTH;
+ chart->height = FIGURE_MIN_HEIGHT;
+ } else {
+ /* base width on valid height */
+ chart->width = (chart->height * 3) / 2;
+ }
+ } else {
+ /* good width check height */
+ if (chart->height < FIGURE_MIN_HEIGHT) {
+ /* base height on valid width */
+ chart->height = (chart->width * 2) / 3;
+ }
+ }
+
+ /* ensure legend type correct */
+ if ((chart->key == CHART_KEY_UNSET) ||
+ (chart->key >= CHART_KEY_END )) {
+ /* default to putting key on right */
+ chart->key = CHART_KEY_RIGHT;
+ }
+
+ return NSERROR_OK;
+}
+
+
+static nserror
+output_pie_legend(struct fetch_about_context *ctx, struct chart_param *chart)
+{
+ nserror res;
+ unsigned int lblidx;
+ unsigned int legend_width;
+ unsigned int legend_height;
+ unsigned int vertical_spacing;
+
+ switch (chart->key) {
+
+ case CHART_KEY_NONE:
+ break;
+ case CHART_KEY_RIGHT:
+ legend_width = chart->width - chart->area.width - chart->area.x;
+ legend_width -= 10; /* margin */
+ legend_height = chart->height;
+ vertical_spacing = legend_height / (chart->data.label_len + 1);
+
+ for(lblidx = 0; lblidx < chart->data.label_len ; lblidx++) {
+ res = fetch_about_ssenddataf(ctx,
+ "<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"#%06x\" />",
+ chart->width - legend_width,
+ (vertical_spacing * lblidx) + (vertical_spacing/2),
+ vertical_spacing * 2 / 3,
+ vertical_spacing * 2 / 3,
+ chart->data.label[lblidx].colour);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ res = fetch_about_ssenddataf(ctx,
+ "<text x=\"%d\" y=\"%d\" fill=\"#%06x\" >%s</text>",
+ chart->width - legend_width + vertical_spacing,
+ vertical_spacing * (lblidx+1),
+ chart->data.label[lblidx].colour,
+ chart->data.label[lblidx].title);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NSERROR_OK;
+}
+
+static float
+compute_series_total(struct chart_param *chart, unsigned int series)
+{
+ float total;
+ unsigned int curdata;
+
+ for (total = 0, curdata = 0;
+ curdata < chart->data.series[series].len;
+ curdata++) {
+ total += chart->data.series[series].value[curdata];
+ }
+ return total;
+}
+
+/**
+ * render the data as a pie chart svg
+ */
+static bool
+pie_chart(struct fetch_about_context *ctx, struct chart_param *chart)
+{
+ nserror res;
+ float ra; /* pie a radius */
+ float rb; /* pie b radius */
+ float series_total;
+ unsigned int curdata; /* current data point index */
+ float last_x, last_y;
+ float end_x, end_y;
+ float start;
+ float extent;
+ bool large;
+ float circle_centre_x, circle_centre_y;
+
+ /* ensure there is data to render */
+ if ((chart->data.series_len < 1) || (chart->data.series[0].len < 2)) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ /* get the first series total value */
+ series_total = compute_series_total(chart, 0);
+ if (series_total == 0) {
+ /* dividing by zero is embarasing */
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ /*
+ * need to ensure the chart area is setup correctly
+ *
+ * this is left to each chart type as different charts
+ * have differnt requirements
+ */
+ if ((chart->area.width == 0) || (chart->area.height == 0)) {
+ /*
+ * pie chart defaults to square of smaller of figure
+ * width and height
+ */
+ if (chart->width > chart->height) {
+ chart->area.width = chart->area.height = (chart->height - chart->area.x);
+ } else {
+ chart->area.width = chart->area.height = (chart->width - chart->area.y);
+ }
+ }
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx,
+ "Content-Type: image/svg; charset=utf-8")) {
+ goto aborted;
+ }
+
+ /* get the pie charts elipse radii */
+ ra = chart->area.width / 2;
+ rb = chart->area.height / 2;
+
+ /* get the offset to the circle centre */
+ circle_centre_x = chart->area.x + ra;
+ circle_centre_y = chart->area.y + rb;
+
+
+ /* svg header */
+ res = fetch_about_ssenddataf(ctx,
+ "<svg width=\"%u\" height=\"%u\" "
+ "xmlns=\"http://www.w3.org/2000/svg\">\n",
+ chart->width, chart->height);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ /* generate the legend */
+ res = output_pie_legend(ctx, chart);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ /* plot the arcs */
+ start = -M_PI_2;
+ last_x = (ra * cos(start));
+ last_y = (rb * sin(start));
+
+ /* iterate over each data point creating a slice o pie */
+ for (curdata=0; curdata < chart->data.series[0].len; curdata++) {
+ extent = ((chart->data.series[0].value[curdata] / series_total) * 2 * M_PI);
+ end_x = (ra * cos(start + extent));
+ end_y = (rb * sin(start + extent));
+
+ if (extent > M_PI) {
+ large = true;
+ } else {
+ large = false;
+ }
+
+ res = fetch_about_ssenddataf(
+ ctx,
+ "<path d=\"M %g %g\n"
+ "A %g %g 0 %d 1 %g %g\n"
+ "L %g %g Z\" fill=\"#%06x\" />\n",
+ circle_centre_x + last_x,
+ circle_centre_y + last_y,
+ ra, rb, large?1:0,
+ circle_centre_x + end_x,
+ circle_centre_y + end_y,
+ circle_centre_x,
+ circle_centre_y,
+ chart->data.label[curdata].colour);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+ last_x = end_x;
+ last_y = end_y;
+ start +=extent;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</svg>\n");
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+ aborted:
+
+ return false;
+
+}
+
+/**
+ * Handler to generate about scheme chart page.
+ *
+ * generates an svg chart
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_chart_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ struct chart_param chart;
+ memset(&chart, 0, sizeof(struct chart_param));
+
+ res = chart_from_query(fetch_about_get_url(ctx), &chart);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ switch (chart.type) {
+ case CHART_TYPE_PIE:
+ return pie_chart(ctx, &chart);
+
+
+ default:
+ break;
+ }
+
+aborted:
+
+ return false;
+
+}