1 /*
2 AWFFull - A Webalizer Fork, Full o' features
3
4 graphs.c
5 produces graphs used by AWFFull
6
7 Copyright (C) 1997-2001 Bradford L. Barrett (brad@mrunix.net)
8 Copyright (C) 2004-2008 Stephen McInerney (spm@stedee.id.au)
9 Copyright (C) 2006 by Benoit Rouits (brouits@free.fr)
10
11 This file is part of AWFFull.
12
13 AWFFull is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 AWFFull is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with AWFFull. If not, see <http://www.gnu.org/licenses/>.
25
26 This software uses the gd graphics library, which is copyright by
27 Quest Protein Database Center, Cold Spring Harbor Labs. Please
28 see the documentation supplied with the library for additional
29 information and license terms, or visit www.boutell.com/gd/ for the
30 most recent version of the library and supporting documentation.
31
32 */
33
34 #include <float.h>
35 #include <gd.h>
36
37 #include "awffull.h"
38
39 #ifdef HAVE_GDIMAGESTRINGFT
40 #ifdef HAVE_GDFTUSEFONTCONFIG
41 char *graph_font_default = GRAPH_FONT_DEFAULT;
42 char *graph_font_label = GRAPH_FONT_LABEL;
43 #else
44 char *graph_font_default = GRAPH_FONT_FULLPATH_DEFAULT;
45 char *graph_font_label = GRAPH_FONT_FULLPATH_LABEL;
46 #endif /* HAVE_GDFTUSEFONTCONFIG */
47 #else
48 #include <gdfontt.h>
49 #include <gdfonts.h>
50 #include <gdfontmb.h>
51 #endif /* HAVE_GDIMAGESTRINGFT */
52
53 /* Some systems don't define this */
54 #ifndef PI
55 #define PI 3.14159265358979323846
56 #endif
57
58 #define HITCOLOR hitcolor /* hits (green) */
59 #define FILECOLOR filecolor /* files (blue) */
60 #define SITECOLOR sitecolor /* sites (orange) */
61 #define KBYTECOLOR kbytecolor /* KBytes (red) */
62 #define PAGECOLOR pagecolor /* Files (cyan) */
63 #define VISITCOLOR visitcolor /* Visits (yellow) */
64
65 #define CX 156 /* center x (for pie) */
66 #define CY 150 /* center y (chart) */
67 #define XRAD 240 /* X-axis radius */
68 #define YRAD 200 /* Y-axis radius */
69
70 /* forward reference internal routines */
71
72 static void init_graph(char *, int, int);
73 static struct pie_data *calc_arc(double, double, int, int, int, int);
74
75 /* common public declarations */
76
77 const char *numchar[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
78 "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
79 "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"
80 };
81
82 gdImagePtr im; /* image buffer */
83 FILE *out; /* output file for PNG */
84 char maxvaltxt[32]; /* graph values */
85 double percent; /* percent storage */
86 unsigned long julday; /* julday value */
87
88 struct pie_data {
89 int x;
90 int y; /* line x,y */
91 int mx;
92 int my;
93 }; /* midpoint x,y */
94
95 /* colors */
96 int black, white, grey, dkgrey;
97 int kbytecolor, filecolor, sitecolor, hitcolor, pagecolor, visitcolor;
98
99 /***************************************************************
100 * ashex2int - ascii hexadecimal to integer converter *
101 * does _not_ exactly do the same as strtoul() *
102 * *
103 * Returns a base-10 integer value from a 2 char *
104 * ascii hexadecimal color number *
105 * this integer is usable by th GD functions *
106 * *
107 ***************************************************************/
108 static int
ashex2int(const char * twocharstr)109 ashex2int(const char *twocharstr)
110 {
111 int val;
112
113 switch (twocharstr[1]) {
114 case 'a':;
115 case 'A':
116 val = 10;
117 break;
118 case 'b':;
119 case 'B':
120 val = 11;
121 break;
122 case 'c':;
123 case 'C':
124 val = 12;
125 break;
126 case 'd':;
127 case 'D':
128 val = 13;
129 break;
130 case 'e':;
131 case 'E':
132 val = 14;
133 break;
134 case 'f':;
135 case 'F':
136 val = 15;
137 break;
138 default:
139 val = (int) twocharstr[1] - 48;
140 }
141 switch (twocharstr[0]) {
142 case 'a':;
143 case 'A':
144 val += 160;
145 break;
146 case 'b':;
147 case 'B':
148 val += 176;
149 break;
150 case 'c':;
151 case 'C':
152 val += 192;
153 break;
154 case 'd':;
155 case 'D':
156 val += 208;
157 break;
158 case 'e':;
159 case 'E':
160 val += 224;
161 break;
162 case 'f':;
163 case 'F':
164 val += 240;
165 break;
166 default:
167 val += (int) (twocharstr[0] - 48) * 16;
168 }
169 return val;
170 }
171
172 /*******************************************************************
173 * shortcuts to convert ascii hex color for gdImageColorAllocate() *
174 *******************************************************************/
175 #define getred(s) (ashex2int((s[0] == '#')?s+1:s))
176 /* returns the red base-10 integer value from a html color */
177 #define getgreen(s) (ashex2int((s[0] == '#')?s+3:s+2))
178 /* returns the green base-10 integer value from a html color */
179 #define getblue(s) (ashex2int((s[0] == '#')?s+5:s+4))
180 /* returns the blue base-10 integer value from a html color */
181
182 /****************************************************************
183 * grid_spacing *
184 * Calulate the grid spacing to use for a given range *
185 * *
186 * Heavily based on algorithims found in rrdtool-1.0.40 *
187 * function: horizontal_grid *
188 * *
189 ****************************************************************/
190 double
grid_spacing(unsigned long long range)191 grid_spacing(unsigned long long range)
192 {
193 double range_f; /* covert the range to double */
194 double grid; /* grid value */
195
196 range_f = (double) range;
197 grid = powf((double) 10, floorf(log10f(range_f)));
198 /* Use of DBL_EPSILON to avoid double conversion problems */
199 if (grid <= DBL_EPSILON) { /* range is one -> 0.1 is reasonable scale */
200 grid = 0.1;
201 }
202 if ((range_f / grid) - 5.0 <= DBL_EPSILON) {
203 grid /= 10.0;
204 }
205 if ((range_f / grid) - 10.0 > DBL_EPSILON) {
206 grid *= 10.0;
207 }
208 if ((range_f / grid) - 5.0 <= DBL_EPSILON) {
209 grid /= 5.0;
210 }
211 if ((range_f / grid) - 10.0 > DBL_EPSILON) {
212 grid *= 5.0;
213 }
214 if ((range_f / grid) - 5.0 <= DBL_EPSILON) {
215 grid /= 2.0;
216 }
217 VPRINT(VERBOSE5, _("grid: %f range/grid: %f\n"), grid, range_f / grid);
218 return (grid);
219 }
220
221
222 /****************************************************************
223 * draw_horiz_grid *
224 * Draws a series of horizontal grid lines *
225 * *
226 ****************************************************************/
227 static int
draw_horiz_grid(unsigned long long max,int xleft,int xright,int ybottom,int height,int text_offset,int is_kbytes)228 draw_horiz_grid(unsigned long long max, /* the max value */
229 int xleft, /* px. leftmost point */
230 int xright, /* px. rightmost point */
231 int ybottom, /* px. starting bottom value *
232 * a line will NOT be drawn here */
233 int height, /* px. Height of the "frame" */
234 int text_offset, /* px. Offset for grid numbering
235 Can be -'ve */
236 int is_kbytes /* Boolean. Set to 1 if unit is Kbytes - VOLUMEBASE */
237 )
238 {
239 double grid; /* the grid spacing to use */
240 double pct_offset; /* percentage offset from height for each grid line */
241 double i, j;
242 double max_f = (double) max; /* max cast to double */
243 double height_f = (double) height; /* height cast to double */
244 int ytop = 0; /* y axis for current grid line */
245 char str[10];
246 int text_centring; /* Use to centre the Y Axis text against
247 the grid lines */
248 char prefixes[9 + 1] = " kMGTPEZY"; /* Prefixes for bigger numbers *
249 * see: http://physics.nist.gov/cuu/Units/prefixes.html */
250 int pref_idx = 0;
251 int prev_text_end = ybottom; /* px. end of previous text */
252
253 double grid_cnt = 0.0; /* How many grid lines displayed. Used to avoid double probs */
254 int bounding_rectange[8]; /* GD Bounding Rectangle, use to identify outer limits of drawn text */
255 char *imagestr_rtn = NULL; /* Showing the return value from gdImageStringFT() - assists with debugging font woes */
256 static bool imagestr_errored = false; /* Only display the gdImageStringFT error once. Repeats of the same thing don't help */
257
258 VPRINT(VERBOSE4, "Draw Horiz Grid: Max: %llu\n", max);
259
260 if (is_kbytes == 1) {
261 j = max_f;
262 /* Calculate new max */
263 while ((j - 1000.0 >= (DBL_EPSILON * (pref_idx + 1))) && (pref_idx <= 8)) {
264 j /= 1000.0;
265 pref_idx++;
266 }
267 max = max / ((pow((double) VOLUMEBASE, (double) pref_idx))) * pow(1000.0, (double) pref_idx);
268 max_f = (double) max;
269 VPRINT(VERBOSE5, " Vol Horiz Grid: Max: %llu, Index: %d\n", max, pref_idx);
270 }
271
272 grid = grid_spacing(max);
273 i = grid;
274
275 while (i - max_f <= (DBL_EPSILON * grid_cnt)) {
276 VPRINT(VERBOSE5, " i: %f maxval: %llu\n", i, max);
277 VPRINT(VERBOSE5, "x1: %d x2: %d y: %d y1: %d pte: %d\n", xleft, xright, ybottom, ytop, prev_text_end);
278
279 /* Calculate the prefixes to use */
280 grid_cnt++;
281 j = i;
282 pref_idx = 0;
283 while ((j - 1000.0 >= (DBL_EPSILON * grid_cnt)) && (pref_idx <= 8)) {
284 j /= 1000.0;
285 pref_idx++;
286 }
287
288 if (j - floorf(j) <= (DBL_EPSILON * grid_cnt)) {
289 if (pref_idx != 0) {
290 snprintf(str, 10, "%.0f%c", j, prefixes[pref_idx]);
291 } else {
292 snprintf(str, 10, "%.0f", j);
293 }
294 } else {
295 if (pref_idx != 0) {
296 snprintf(str, 10, "%.1f%c", j, prefixes[pref_idx]);
297 } else {
298 snprintf(str, 10, "%.1f", j);
299 }
300 }
301
302 /* Work out the line height */
303 pct_offset = (i / max_f) * height_f;
304 ytop = ybottom - (int) pct_offset;
305
306 gdImageLine(im, xleft, ytop, xright, ytop, dkgrey);
307 #ifdef HAVE_GDIMAGESTRINGFT
308 /* "print" the string to get an idea of actual size and hence find the centre */
309 imagestr_rtn = gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_default, GRAPH_FONT_SIZE_TINY, 0.0, 0, 0, str);
310 if (imagestr_rtn && imagestr_errored != true) {
311 ERRVPRINT(VERBOSE1, "Error: Invalid gdImageStringFT: %s --> %s Font: %s\n", str, imagestr_rtn, graph_font_default);
312 imagestr_errored = true;
313 }
314 text_centring = (int) (((double) bounding_rectange[4] / 2.0) + 1.0);
315
316 /* Final check to verify that we're not over-writing existing text */
317 if (prev_text_end >= (ytop + text_centring)) {
318 /* Not overwriting, so display! */
319 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_default, GRAPH_FONT_SIZE_TINY, PI / 2.0, xleft - text_offset + GRAPH_FONT_SIZE_TINY, ytop + text_centring,
320 str);
321 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_default, GRAPH_FONT_SIZE_TINY, PI / 2.0, xleft - text_offset + GRAPH_FONT_SIZE_TINY, ytop + text_centring,
322 str);
323 prev_text_end = bounding_rectange[5] - GRAPH_FONT_SIZE_TINY; /* Upper Right (ignore rotate!), and a single character size - adds a nice spaceing gap. */
324 }
325 #else
326 text_centring = (int) ((((double) strlen(str) / 2.0) * 5.0) - 1.0);
327 if (prev_text_end >= (ytop + text_centring)) {
328 /* Final check to verify that we're not over-writing existing text */
329 gdImageStringUp(im, gdFontTiny, xleft - text_offset, ytop + text_centring, (unsigned char *) str, dkgrey);
330 prev_text_end = ytop - text_centring - 7;
331 }
332 #endif
333 i += grid;
334 VPRINT(VERBOSE5, "+ i: %f maxval: %llu max-i: %g eps: %g\n", i, max, max_f - i, DBL_EPSILON);
335 }
336 return (0);
337 }
338
339
340 /****************************************************************
341 * calc_bar_width *
342 * Calculate the individual bar width to use *
343 * *
344 ****************************************************************/
345 static int
calc_bar_width(int drawing_width,int nbr_bars,int * bar_gap,int * bar_sep,int nbr_grouped_bars)346 calc_bar_width(int drawing_width, /* The width of the drawing area */
347 int nbr_bars, /* How many bars will we be drawing */
348 int *bar_gap, /* The gap between bar groupings - white space */
349 int *bar_sep, /* The separation between individual bars in a grouping */
350 /* bar_gap and bar_sep, are ignored as incoming values - reset to the defaults
351 * The new/set values of both will be passed back
352 */
353 int nbr_grouped_bars /* The number of bars in a grouping */
354 )
355 {
356 int bar_width = 1; /* The width to return */
357
358 VPRINT(VERBOSE5, "CalcBarWidth: DrawWidth: %d NbrBars: %d\n", drawing_width, nbr_bars);
359
360 if (nbr_grouped_bars < 0) {
361 ERRVPRINT(VERBOSE3, "Number bars in a group < 0. Bad News! Correcting.\n");
362 nbr_grouped_bars = 0;
363 }
364
365 /* If width gets too small, steadily drop bar_gap and bar_sep - till at most 1px each, and set width to 1px too
366 * May have lotsa data points issues... FIXME! */
367 while (1) {
368 /* bar_width is based on:
369 * a. Size of drawing "window" -> main_width
370 * minus left/right side gaps -> (bar_gap * 2)
371 * b. How many bars to draw - ie divisions -> (idx_end - idx_start + 1 = index_diff + 1)
372 * minus separation/gap between a group and individual bars -> (bar_gap + (bar_sep * 2))
373 * c. Can be left as int - want all bars to be same size!
374 */
375 // bar_width = ((main_width - (bar_gap * 2)) / (index_diff + 1)) - (bar_gap + (bar_sep * 1));
376 bar_width = ((drawing_width - ((*bar_gap) * 2)) / (nbr_bars + 1)) - ((*bar_gap) + ((*bar_sep) * (nbr_grouped_bars - 1)));
377 if ((nbr_grouped_bars == 1) && (bar_width > *bar_gap)) {
378 /* exit when the separation is irrelevant and the barwidth is already ok */
379 break;
380 }
381 if ((bar_width > *bar_sep) && (bar_width > *bar_gap)) {
382 /* exit once the bar width is actually bigger than the separation and the gap */
383 break;
384 }
385
386 /* If we can't get any smaller? Width is the smallest */
387 /* FIXME: width * nbrbars > drawing_width */
388 if ((*bar_gap == 1) && (*bar_sep == 1)) {
389 bar_width = 1;
390 break;
391 }
392
393 /* Decrease the Gap first, then the separation */
394 if (*bar_gap > 1) {
395 (*bar_gap)--;
396 } else {
397 (*bar_sep)--;
398 }
399 }
400
401 VPRINT(VERBOSE5, "CalcBarWidth: Returning BarWidth: %d\n", bar_width);
402 return (bar_width);
403 }
404
405 /****************************************************************
406 * draw_bar *
407 * Draw a single bar *
408 * *
409 ****************************************************************/
410 static void
draw_bar(int xleft,int ybottom,int ytop,int bar_width,double bar_percent,int do_guide,int colour)411 draw_bar(int xleft, /* X Left */
412 int ybottom, /* Y Bottom of section */
413 int ytop, /* Y Top of section */
414 int bar_width, /* Bar Width */
415 double bar_percent, /* The percentage that this bar fills, Y-Axis */
416 int do_guide, /* Display a vertical guide bar; 1 == yes */
417 int colour /* Colour to display this bar in */
418 ) {
419 int ybartop; /* 1st y location for drawing */
420 int xright; /* 2nd x location for drawing */
421 const int c_smallest_width = 2; /* px. Smallest width of a bar to ignore putting a box around, ie 3 or more to box. */
422
423 if (bar_percent > 0.0) {
424 xright = xleft + bar_width - 1;
425 ybartop = ybottom - (bar_percent * (ybottom - ytop - GRAPH_TB_IN_OFFSET));
426 if (ybartop > ybottom) {
427 ybartop = ybottom;
428 }
429 if (do_guide == 1) {
430 /* Put in a vertical bar for readability */
431 gdImageLine(im, xleft, ybottom, xleft, ytop, dkgrey);
432 }
433 gdImageFilledRectangle(im, xleft, ybartop, xright, ybottom, colour);
434 /* Don't do the black outline if we're getting too thin */
435 if ((xright - xleft) > c_smallest_width) {
436 gdImageRectangle(im, xleft, ybartop, xright, ybottom, black);
437 }
438 }
439 }
440
441 /****************************************************************
442 * *
443 * YEAR_GRAPH6x - Year graph with six data sets *
444 * Dependant on history_list being set *
445 * idx_start & idx_end are index's into history_list *
446 * *
447 ****************************************************************/
448 int
year_graph6x(char * fname,char * title,int idx_start,int idx_end)449 year_graph6x(char *fname, /* file name use */
450 char *title, /* title for graph */
451 int idx_start, /* begin start index */
452 int idx_end /* begin end index */
453 )
454 {
455
456 /* local variables */
457 #ifdef HAVE_GDIMAGESTRINGFT
458 int i, x, y;
459 int text_lengthA; /* px. Length of the various text strings to be printed */
460 int text_lengthB;
461 int text_lengthC;
462 int text_lengthD;
463 int edge_text_offsetTOP; /* px. Offset from the TOP edge for side text */
464 int edge_text_offsetBOTTOM; /* px. Offset from the BOTTOM edge for side text */
465 #else
466 int i, j, k, k2;
467 int font_char_width; /* px. Width of an individual char in a given font - magic constant */
468 int font_char_height; /* px. Height of an individual char in a given font - magic constant */
469 #endif
470
471 int xbarleft; /* Leftmost point for a given bar to draw */
472
473 int index_diff; /* Difference between start/end
474 Use to set a maximum bar width */
475
476 int main_width; /* width of the primary section */
477 int main_height; /* Height of the primary section */
478 int main_bottom; /* Baseline of the primary section */
479
480 int section_left; /* Section break is here from left */
481 int section_right; /* End of Section from left, right postion */
482 int section_middle; /* Inner Section break is here from top */
483 int section_bottom; /* Bottom of vertical section line */
484
485 int bar_width; /* Width of the individual bars */
486 double bar_offset; /* basic offset of the individual bars */
487
488 const int c_base_bar_gap = 3; /* Constant. Set the default Bar Gap */
489 const int c_base_bar_sep = 3; /* Constant. Set the default Bar Separation */
490 int bar_gap = c_base_bar_gap; /* px. Space between bar groupings */
491 int bar_sep = c_base_bar_sep; /* px. Space between bars in a group */
492
493 int month_middle_x; /* Mid point offset between veritical bars */
494 int text_centring; /* Use to centre the Y Axis text against the grid lines */
495 int prev_text_end = 0; /* px. end of previous text. Used to print months on X Axis */
496 int cur_text_start; /* px. start of current text. Used to print months on X Axis */
497
498 int edge_text_offset; /* px. Offset from the edge for side text */
499
500 int do_year;
501
502 unsigned long maxval = 1;
503 double fmaxval = 0.0;
504 int bounding_rectange[8]; /* GD Bounding Rectangle, use to identify outer limits of drawn text */
505
506 /* initalize the graph */
507 init_graph(title, g_settings.graphs.index_x, g_settings.graphs.index_y);
508
509 /* Set up base calculations */
510 main_width = ((float) (g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - (GRAPH_INNER_BOX_RIGHT + 1)) * GRAPH_INDEX_SPLIT);
511 main_height = g_settings.graphs.index_y - GRAPH_INNER_BOX_TOP - GRAPH_INNER_BOX_BOTTOM - (GRAPH_TB_IN_OFFSET * 2) - 3;
512 main_bottom = g_settings.graphs.index_y - GRAPH_INNER_BOX_BOTTOM - (GRAPH_TB_IN_OFFSET * 2);
513
514 /* draw inner section lines */
515 section_left = main_width + GRAPH_INNER_BOX_LEFT + 2 + 1;
516 section_right = g_settings.graphs.index_x - GRAPH_INNER_BOX_RIGHT - 2 - 1; /* 2 is width of inner box */
517 section_bottom = g_settings.graphs.index_y - GRAPH_INNER_BOX_BOTTOM - 2 - 1; /* 2 is width of inner box */
518 section_middle = (g_settings.graphs.index_y / 2) + 2; /* 2 is width of inner lines */
519
520 /* Vertical divide line */
521 gdImageLine(im, section_left, GRAPH_INNER_BOX_TOP, section_left, section_bottom, black);
522 gdImageLine(im, section_left - 1, GRAPH_INNER_BOX_TOP, section_left - 1, section_bottom, white);
523
524 /* Middle right line - horizontal */
525 gdImageLine(im, section_left, section_middle, section_right, section_middle, black);
526 gdImageLine(im, section_left, section_middle - 1, section_right, section_middle - 1, white);
527
528 /* Middle left line - horizontal */
529 gdImageLine(im, GRAPH_INNER_BOX_LEFT + 1, section_middle, section_left - 2, section_middle, black);
530 gdImageLine(im, GRAPH_INNER_BOX_LEFT, section_middle - 1, section_left - 2, section_middle - 1, white);
531
532 #ifdef HAVE_GDIMAGESTRINGFT
533 /* Locate TOP and BOTTOM Edge Offsets for Displaying Text */
534 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Pages"));
535 edge_text_offsetTOP =
536 (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_TOP) - (GRAPH_SHADOW_WIDTH + ((GRAPH_INNER_BOX_TOP - (abs(bounding_rectange[7]) + abs(bounding_rectange[1]) + 1)) / 2) +
537 abs(bounding_rectange[1])) + 2;
538 edge_text_offsetBOTTOM =
539 g_settings.graphs.index_y - (GRAPH_SHADOW_WIDTH + ((GRAPH_INNER_BOX_BOTTOM - (abs(bounding_rectange[7]) + abs(bounding_rectange[1] + 1))) / 2) +
540 abs(bounding_rectange[1])) + 1;
541
542 if (g_settings.graphs.legend) { /* print color coded legends? */
543 /* Volume Legend */
544 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Volume"));
545 text_lengthA = bounding_rectange[4];
546 x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthA);
547 y = edge_text_offsetBOTTOM;
548 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Volume"));
549 gdImageStringFT(im, &bounding_rectange[0], KBYTECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Volume"));
550
551 /* Sites/Visits Legend */
552 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Visits"));
553 text_lengthA = abs(bounding_rectange[4]);
554 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Sites"));
555 text_lengthB = abs(bounding_rectange[4]);
556 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, "/");
557 text_lengthD = abs(bounding_rectange[4]);
558
559 y = edge_text_offsetTOP;
560 x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthA + 1 + text_lengthD + text_lengthB);
561 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Visits"));
562 gdImageStringFT(im, &bounding_rectange[0], VISITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Visits"));
563 x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthB + text_lengthD);
564 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, "/");
565 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, "/");
566 x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthB);
567 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Sites"));
568 gdImageStringFT(im, &bounding_rectange[0], SITECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Sites"));
569
570 /* Hits/Files/Pages Legend */
571 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Pages"));
572 text_lengthA = abs(bounding_rectange[4]);
573 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Files"));
574 text_lengthB = abs(bounding_rectange[4]);
575 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Hits"));
576 text_lengthC = abs(bounding_rectange[4]);
577
578 y = edge_text_offsetBOTTOM;
579 x = section_left;
580 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Pages"));
581 gdImageStringFT(im, &bounding_rectange[0], PAGECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Pages"));
582
583 x += text_lengthA + 1;
584 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, "/");
585 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, "/");
586
587 x += text_lengthD;
588 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Files"));
589 gdImageStringFT(im, &bounding_rectange[0], FILECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Files"));
590
591 x += text_lengthB + 1;
592 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, "/");
593 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, "/");
594
595 x += text_lengthD;
596 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Hits"));
597 gdImageStringFT(im, &bounding_rectange[0], HITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Hits"));
598 }
599 #else /* OLD - Non TrueType Text Graphing */
600 /* magic numbers */
601 font_char_width = 6;
602 font_char_height = 10;
603 if (g_settings.graphs.legend) { /* print color coded legends? */
604 k = font_char_width / 2; /* Half Char Sep */
605 k2 = font_char_width * 2; /* Half Space '/' Half Space */
606
607 /* Volume Legend */
608 i = (strlen(_("Volume")) * font_char_width);
609 edge_text_offset = main_bottom + GRAPH_TEXT_Y_OFFSET;
610 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - i, edge_text_offset + 1, (unsigned char *) _("Volume"), dkgrey);
611 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - i, edge_text_offset, (unsigned char *) _("Volume"), KBYTECOLOR);
612
613 /* Sites/Visits Legend */
614 i = (strlen(_("Visits")) * font_char_width);
615 j = (strlen(_("Sites")) * font_char_width);
616 edge_text_offset = GRAPH_INNER_BOX_TOP - font_char_height - GRAPH_TEXT_Y_OFFSET - 1;
617 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - i - j - k2, edge_text_offset + 1, (unsigned char *) _("Visits"), dkgrey);
618 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - i - j - k2, edge_text_offset, (unsigned char *) _("Visits"), VISITCOLOR);
619 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - j - k2 + k, edge_text_offset + 1, (unsigned char *) "/", dkgrey);
620 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - j - k2 + k, edge_text_offset, (unsigned char *) "/", black);
621 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - j, edge_text_offset + 1, (unsigned char *) _("Sites"), dkgrey);
622 gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - j, edge_text_offset, (unsigned char *) _("Sites"), SITECOLOR);
623
624 /* Hits/Files/Pages Legend */
625 i = (strlen(_("Pages")) * font_char_width);
626 j = (strlen(_("Files")) * font_char_width);
627 edge_text_offset = main_bottom + GRAPH_TEXT_Y_OFFSET;
628 gdImageString(im, gdFontSmall, section_left + 1, edge_text_offset + 1, (unsigned char *) _("Pages"), dkgrey);
629 gdImageString(im, gdFontSmall, section_left, edge_text_offset, (unsigned char *) _("Pages"), PAGECOLOR);
630 gdImageString(im, gdFontSmall, section_left + i + k + 1, edge_text_offset + 1, (unsigned char *) "/", dkgrey);
631 gdImageString(im, gdFontSmall, section_left + i + k, edge_text_offset, (unsigned char *) "/", black);
632 gdImageString(im, gdFontSmall, section_left + i + k2 + 1, edge_text_offset + 1, (unsigned char *) _("Files"), dkgrey);
633 gdImageString(im, gdFontSmall, section_left + i + k2, edge_text_offset, (unsigned char *) _("Files"), FILECOLOR);
634 gdImageString(im, gdFontSmall, section_left + i + j + k2 + k + 1, edge_text_offset + 1, (unsigned char *) "/", dkgrey);
635 gdImageString(im, gdFontSmall, section_left + i + j + k2 + k, edge_text_offset, (unsigned char *) "/", black);
636 gdImageString(im, gdFontSmall, section_left + i + j + k2 * 2 + 1, edge_text_offset + 1, (unsigned char *) _("Hits"), dkgrey);
637 gdImageString(im, gdFontSmall, section_left + i + j + k2 * 2, edge_text_offset, (unsigned char *) _("Hits"), HITCOLOR);
638 }
639 #endif
640
641 /****************************************************
642 * Do the main Section *
643 * Hits, Files *
644 ****************************************************/
645 /* get max val */
646 for (i = idx_start; i < idx_end; i++) {
647 if (history_list[i].hit > maxval) {
648 maxval = history_list[i].hit;
649 }
650 if (history_list[i].file > maxval) {
651 maxval = history_list[i].file;
652 }
653 }
654 if (maxval <= 0) {
655 maxval = 1;
656 }
657
658 if (g_settings.graphs.lines) {
659 edge_text_offset = 6 + GRAPH_TEXT_Y_OFFSET + 1;
660 /* tiny font has height of 6 */
661 draw_horiz_grid(maxval, GRAPH_INNER_BOX_LEFT + 1, GRAPH_INNER_BOX_LEFT + main_width + 1, main_bottom, section_bottom - section_middle - GRAPH_TB_IN_OFFSET - 1,
662 edge_text_offset, 0);
663 }
664
665 /* width/offset calculations */
666 index_diff = idx_end - idx_start;
667 if (index_diff < GRAPH_INDEX_MIN_BARS) { /* Set the minimum # of bars */
668 index_diff = GRAPH_INDEX_MIN_BARS;
669 }
670
671 bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 2);
672 bar_offset = (((double) main_width - ((double) bar_gap * 2.0)) / (double) index_diff);
673 /* bar_offset is based on:
674 * a. Size of drawing "window" -> G_YEAR_MAIN_WIDTH
675 * minus left/right side gaps -> (bar_gap * 2)
676 * b. How many bars to draw -> (idx_end - idx_start)
677 * We start from zero, so no need to "+ 1"
678 * c. Must double, else we lose significant fractions and bunch up leftwards - yuk.
679 */
680
681 month_middle_x = (bar_width + bar_sep * 1) / 2 + 1;
682 /* Calc for the mid point on which to then draw the month on the X Axis */
683
684 // fprintf (stderr, "start: %d end: %d diff: %d idx: %d width: %d offset: %f mainwidth: %d\n", idx_start, idx_end, index_diff, idx_end - idx_start, bar_width, bar_offset, main_width);
685 for (i = idx_start; i < idx_end; i++) {
686 /* HITS */
687 percent = ((double) history_list[i].hit / (double) maxval);
688
689 if ((history_list[i].month == 1) && (index_diff > 12)) {
690 do_year = 1;
691 } else {
692 do_year = 0;
693 }
694
695 xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + ((i - idx_start) * bar_offset);
696 draw_bar(xbarleft, main_bottom, section_middle + 1, bar_width, percent, do_year, HITCOLOR);
697
698 /* X Axis */
699 #ifdef HAVE_GDIMAGESTRINGFT
700 gdImageStringFT(NULL, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, 0, 0, (char *) s_month[history_list[i].month - 1]);
701 text_centring = abs(bounding_rectange[4]) / 2;
702 cur_text_start = xbarleft + month_middle_x - text_centring;
703 if (prev_text_end < cur_text_start) {
704 /* Check that we're not over-writing existing text */
705 if ((prev_text_end + 3) < section_left) {
706 /* Make sure we don't overwrite any of the Legend */
707 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, cur_text_start, edge_text_offsetBOTTOM,
708 (char *) s_month[history_list[i].month - 1]);
709 prev_text_end = abs(bounding_rectange[2]); /* Get the rightmost point on the text just printed. Want so as not to overlap text. */
710 }
711 }
712 #else
713 text_centring = (((double) strlen(s_month[history_list[i].month - 1])) / 2.0) * (double) font_char_width;
714 /* Probably overkill - should always be 3 for the strlen */
715 cur_text_start = xbarleft + month_middle_x - text_centring;
716 if (prev_text_end < cur_text_start) {
717 /* Final check to verify that we're not over-writing existing text */
718 gdImageString(im, gdFontSmall, cur_text_start, main_bottom + GRAPH_TEXT_Y_OFFSET, (unsigned char *) s_month[history_list[i].month - 1], black);
719 prev_text_end = xbarleft + month_middle_x + text_centring + (font_char_width / 2);
720 }
721 #endif
722
723 /* FILES */
724 percent = ((double) history_list[i].file / (double) maxval);
725 draw_bar(xbarleft + bar_sep, main_bottom, section_middle + 1, bar_width, percent, 0, FILECOLOR);
726 }
727
728 /****************************************************
729 * Do the Top Left Section *
730 * Pages *
731 ****************************************************/
732 /* get max val */
733 maxval = 0;
734 for (i = idx_start; i < idx_end; i++) {
735 if (history_list[i].page > maxval) {
736 maxval = history_list[i].page;
737 }
738 }
739 if (maxval <= 0) {
740 maxval = 1;
741 }
742 if (g_settings.graphs.lines) {
743 edge_text_offset = 6 + GRAPH_TEXT_Y_OFFSET + 1;
744 draw_horiz_grid(maxval, GRAPH_INNER_BOX_LEFT + 1, GRAPH_INNER_BOX_LEFT + main_width + 1, section_middle, section_middle - GRAPH_INNER_BOX_TOP - GRAPH_TB_IN_OFFSET - 1,
745 edge_text_offset, 0);
746 }
747 bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 1);
748 for (i = idx_start; i < idx_end; i++) {
749 /* PAGES */
750 percent = ((double) history_list[i].page / (double) maxval);
751 if ((history_list[i].month == 1) && (index_diff > 12)) {
752 do_year = 1;
753 } else {
754 do_year = 0;
755 }
756 draw_bar(GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + ((i - idx_start) * bar_offset),
757 section_middle - GRAPH_TB_IN_OFFSET - 1, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, do_year, PAGECOLOR);
758 }
759
760 /****************************************************
761 * Do the Top Right Section *
762 * Visits, Sites *
763 ****************************************************/
764 /* get max val */
765 maxval = 0;
766 for (i = idx_start; i < idx_end; i++) {
767 if (history_list[i].site > maxval) {
768 maxval = history_list[i].site;
769 }
770 if (history_list[i].visit > maxval) {
771 maxval = history_list[i].visit;
772 }
773 }
774 if (maxval <= 0) {
775 maxval = 1;
776 }
777 if (g_settings.graphs.lines) {
778 edge_text_offset = section_left - section_right - GRAPH_TEXT_Y_OFFSET;
779 draw_horiz_grid(maxval, section_left + 1, section_right, section_middle, section_middle - GRAPH_INNER_BOX_TOP - GRAPH_TB_IN_OFFSET - 1, edge_text_offset, 0);
780 }
781 bar_gap = c_base_bar_gap;
782 bar_sep = c_base_bar_sep;
783 bar_width = calc_bar_width(section_right - section_left, index_diff, &bar_gap, &bar_sep, 2);
784 bar_offset = ((double) (section_right - section_left) - ((double) bar_gap * 2.0)) / (double) index_diff;
785 for (i = idx_start; i < idx_end; i++) {
786 /* VISITS */
787 percent = ((double) history_list[i].visit / (double) maxval);
788 if ((history_list[i].month == 1) && (index_diff > 12)) {
789 do_year = 1;
790 } else {
791 do_year = 0;
792 }
793 xbarleft = section_left + 2 + bar_gap + ((i - idx_start) * bar_offset);
794 draw_bar(xbarleft, section_middle - GRAPH_TB_IN_OFFSET - 1, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, do_year, VISITCOLOR);
795
796 /* SITES */
797 percent = ((double) history_list[i].site / (double) maxval);
798 draw_bar(xbarleft + bar_sep, section_middle - GRAPH_TB_IN_OFFSET - 1, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, SITECOLOR);
799 }
800
801 /****************************************************
802 * Do the Bottom Right Section *
803 * Volume *
804 ****************************************************/
805 /* Calculate maxval */
806 fmaxval = 0.0;
807 for (i = idx_start; i < idx_end; i++) {
808 if (history_list[i].xfer > fmaxval) {
809 fmaxval = history_list[i].xfer;
810 }
811 }
812 if (fmaxval <= 0.0) {
813 fmaxval = 1.0;
814 }
815 if (g_settings.graphs.lines) {
816 edge_text_offset = section_left - section_right - GRAPH_TEXT_Y_OFFSET;
817 draw_horiz_grid((unsigned long long) fmaxval * VOLUMEBASE, section_left + 1, section_right, section_bottom, section_bottom - section_middle - GRAPH_TB_IN_OFFSET - 1,
818 edge_text_offset, 1);
819 }
820 bar_width = calc_bar_width(section_right - section_left, index_diff, &bar_gap, &bar_sep, 1);
821 for (i = idx_start; i < idx_end; i++) {
822 /* VOLUME */
823 percent = ((double) history_list[i].xfer / (double) fmaxval);
824 if ((history_list[i].month == 1) && (index_diff > 12)) {
825 do_year = 1;
826 } else {
827 do_year = 0;
828 }
829 draw_bar(section_left + 2 + bar_gap + ((i - idx_start) * bar_offset), main_bottom, section_middle + 1, bar_width, percent, do_year, KBYTECOLOR);
830 }
831
832 /* save png image */
833 if ((out = fopen(fname, "wb")) != NULL) {
834 gdImagePng(im, out);
835 fclose(out);
836 }
837 /* deallocate the Image memory */
838 gdImageDestroy(im);
839
840 return (0);
841 }
842
843
844 /*****************************************************************
845 * *
846 * MONTH_GRAPH6 - Month graph with six data sets *
847 * *
848 *****************************************************************/
849 int
month_graph6(char * fname,char * title,int month,int year,bool is_month,unsigned long data1[31],unsigned long data2[31],unsigned long data3[31],unsigned long long data4[31],unsigned long data5[31],unsigned long data6[31])850 month_graph6(char *fname, /* filename */
851 char *title, /* graph title */
852 int month, /* graph month */
853 int year, /* graph year */
854 bool is_month, /* if true: month, if false: day */
855 unsigned long data1[31], /* data1 (hits) */
856 unsigned long data2[31], /* data2 (files) */
857 unsigned long data3[31], /* data3 (sites) */
858 unsigned long long data4[31], /* data4 (kbytes) */
859 unsigned long data5[31], /* data5 (views) */
860 unsigned long data6[31])
861 { /* data6 (visits) */
862
863 /* local variables */
864 #ifdef HAVE_GDIMAGESTRINGFT
865 int i, x, y;
866 int edge_text_offsetRIGHT;
867 int edge_text_offsetBOTTOM;
868 int bounding_rectange[8]; /* GD Bounding Rectangle, use to identify outer limits of drawn text */
869 int text_lengthA; /* px. Length of the various text strings to be printed */
870 int text_lengthB;
871 int text_lengthC;
872 int text_lengthD;
873 double rotation = 0.0; /* Radians(?) +/- PI/2 */
874 int text_height = 0; /* Adjust offset for Kanji */
875 #else
876 int i, j, k, k2;
877 int font_char_width; /* px. Width of an individual char in a given font - magic constant */
878 int font_char_height; /* px. Height of an individual char in a given font - magic constant */
879 unsigned char text_separator[2] = "/"; /* Just a simple var to hold the char to separate words. Unsigned for GD */
880 #endif
881 const char *x_label; /* The label (a number) to put onto the x axis */
882 int x_colour; /* The colour to use for the above label */
883
884 u_long maxval = 0;
885 double fmaxval = 0.0;
886
887 int graphx, graphy;
888 int max_i; /* Maximum X Axis. To adjust for varying months et al */
889
890 int xbarleft;
891
892 int main_width; /* width of the primary section */
893 int height_upper; /* Height of the hits/pages/files section */
894 int height_middle; /* Height of the visits/sites section */
895 int height_lower; /* Height of the volume section */
896 int bottom_upper;
897 int bottom_middle;
898 int bottom_lower;
899 int day_middle_x; /* px. Mid point offset between veritical bars */
900 int text_centring; /* px. Use to centre the Y Axis text against the grid lines */
901 int cur_text_start; /* px. start of current text. Used to print months on X Axis */
902
903 int section_left; /* Section break is here from left */
904 int section_right; /* End of Section from left, right postion */
905 int section_middle; /* Inner Section break is here from top */
906 int section_bottom; /* Bottom of vertical section line */
907
908 int bar_width; /* Width of the individual bars */
909 double bar_offset; /* basic offset of the individual bars */
910 int bar_gap = 2; /* px. Space between bar groupings */
911 int bar_sep = 2; /* px. Space between bars in a group */
912 int index_diff; /* Difference between start/end
913 Use to set a maximum bar width */
914
915 int edge_text_offset = 0; /* px. Offset from the edge for side text */
916
917 /* calc julian date for month */
918 julday = (jdate(1, month, year) % 7);
919
920 if (is_month) {
921 switch (month) {
922 case 2:
923 max_i = 29;
924 break;
925 case 4:
926 case 6:
927 case 9:
928 case 11:
929 max_i = 30;
930 break;
931 default:
932 max_i = 31;
933 break;
934 }
935 graphx = g_settings.graphs.daily_x;
936 graphy = g_settings.graphs.daily_y;
937 } else {
938 max_i = 24;
939 graphx = g_settings.graphs.hourly_x;
940 graphy = g_settings.graphs.hourly_y;
941 }
942
943 /* initalize the graph */
944 init_graph(title, graphx, graphy);
945
946 /* draw inner section lines */
947 section_left = GRAPH_INNER_BOX_LEFT;
948 section_right = graphx - GRAPH_INNER_BOX_RIGHT - 2 - 1; /* 2 is width of inner box */
949 section_middle = ((graphy - GRAPH_INNER_BOX_TOP - GRAPH_INNER_BOX_BOTTOM) * GRAPH_DAILY_SPLIT_MAIN) + GRAPH_INNER_BOX_TOP;
950 section_bottom = ((graphy - GRAPH_INNER_BOX_TOP - GRAPH_INNER_BOX_BOTTOM) * GRAPH_DAILY_SPLIT_2NDRY) + section_middle;
951 gdImageLine(im, section_left, section_middle, section_right, section_middle, black);
952 gdImageLine(im, section_left - 1, section_middle - 1, section_right, section_middle - 1, white);
953 gdImageLine(im, section_left, section_bottom, section_right, section_bottom, black);
954 gdImageLine(im, section_left, section_bottom - 1, section_right, section_bottom - 1, white);
955
956 /* Set up base calculations */
957 main_width = graphx - (GRAPH_INNER_BOX_LEFT + 1) - (GRAPH_INNER_BOX_RIGHT + 1) - 2;
958 height_upper = section_middle - GRAPH_INNER_BOX_TOP - (GRAPH_TB_IN_OFFSET * 2) - 3;
959 bottom_upper = section_middle - (GRAPH_TB_IN_OFFSET * 2);
960 height_middle = section_bottom - section_middle - (GRAPH_TB_IN_OFFSET * 2) - 3;
961 bottom_middle = section_bottom - (GRAPH_TB_IN_OFFSET * 2);
962 height_lower = graphy - GRAPH_INNER_BOX_BOTTOM - section_bottom - (GRAPH_TB_IN_OFFSET * 2) - 3;
963 bottom_lower = graphy - GRAPH_INNER_BOX_BOTTOM - (GRAPH_TB_IN_OFFSET * 2);
964
965 // gdImageLine (im, GRAPH_INNER_BOX_LEFT + 1, section_middle + 15, GRAPH_INNER_BOX_LEFT + 1 + main_width, section_middle + 15, red);
966
967 #ifdef HAVE_GDIMAGESTRINGFT
968 /* Locate RIGHT and BOTTOM Edge Offsets for Displaying Text */
969 /* Offset at 100,100 to better see =/- offsets. Adjust accordingly in edge_text_offsets.
970 * The x/y position is at the base of the line characters rest on. So 'g' for example, goes *below* the line.
971 * '-2' is a magic offset. Unfortunately. Just looks better with the shadow etc. Could probably account for that in the math but... */
972 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 100, 100, _("Pages"));
973 // for (i=0; i<8; i++) { printf("%d:%d ", i, bounding_rectange[i]); }; printf("\n");
974 edge_text_offsetRIGHT =
975 graphx - (GRAPH_SHADOW_WIDTH + (((GRAPH_INNER_BOX_RIGHT - abs(bounding_rectange[5] - bounding_rectange[1]) + 1) / 2) + abs(100 - bounding_rectange[1]) - 2));
976 edge_text_offsetBOTTOM =
977 graphy - (GRAPH_SHADOW_WIDTH + (((GRAPH_INNER_BOX_BOTTOM - abs(bounding_rectange[7] - bounding_rectange[1]) + 1) / 2) + abs(100 - bounding_rectange[1]) - 2));
978
979 if (g_settings.graphs.legend) { /* print color coded legends? */
980 if (g_settings.graphs.use_kanji == false) {
981 x = edge_text_offsetRIGHT;
982 y = graphy - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + 1);
983 rotation = PI / 2.0;
984 } else {
985 rotation = -PI / 2.0; /* Invert Angle of rotation - text should flow from top down, not bottom up as per English et al */
986 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, 100, 100, _("Pages"));
987 text_height = abs(bounding_rectange[4] - bounding_rectange[0]);
988 edge_text_offsetRIGHT =
989 graphx - (GRAPH_SHADOW_WIDTH + (((GRAPH_INNER_BOX_RIGHT - abs(bounding_rectange[6] - bounding_rectange[0]) + 1) / 2) + abs(100 - bounding_rectange[6]) - 1));
990 x = edge_text_offsetRIGHT;
991 y = graphy - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + 1);
992 }
993
994 if (g_settings.graphs.use_kanji == true) {
995 y = bottom_lower - height_lower + text_height;
996 }
997 /* Volume Legend */
998 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Volume"));
999 gdImageStringFT(im, &bounding_rectange[0], KBYTECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Volume"));
1000
1001 /* Sites/Visits Legend */
1002 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Visits"));
1003 text_lengthA = abs(bounding_rectange[4]);
1004 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, "/");
1005 text_lengthD = abs(bounding_rectange[4]);
1006
1007 x = edge_text_offsetRIGHT;
1008 if (g_settings.graphs.use_kanji == false) {
1009 y = section_bottom - GRAPH_TEXT_Y_OFFSET;
1010 } else {
1011 y = bottom_middle - height_middle + text_height;
1012 }
1013 if (is_month == true) {
1014 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Visits"));
1015 gdImageStringFT(im, &bounding_rectange[0], VISITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Visits"));
1016 if (g_settings.graphs.use_kanji == false) {
1017 y = section_bottom - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1);
1018 } else {
1019 y += text_lengthA + 1;
1020 }
1021 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, "/");
1022 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, "/");
1023 if (g_settings.graphs.use_kanji == false) {
1024 y = section_bottom - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD);
1025 } else {
1026 y += text_height + 1;
1027 }
1028 }
1029 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Sites"));
1030 gdImageStringFT(im, &bounding_rectange[0], SITECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Sites"));
1031
1032 /* Hits/Files/Pages Legend */
1033 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Pages"));
1034 text_lengthA = abs(bounding_rectange[4]);
1035 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Files"));
1036 text_lengthB = abs(bounding_rectange[4]);
1037 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Hits"));
1038 text_lengthC = abs(bounding_rectange[4]);
1039
1040 x = edge_text_offsetRIGHT;
1041 y = section_middle - GRAPH_TEXT_Y_OFFSET;
1042 if (g_settings.graphs.use_kanji == false) {
1043 y = section_middle - GRAPH_TEXT_Y_OFFSET;
1044 } else {
1045 y = bottom_upper - height_upper + text_height;
1046 }
1047 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Pages"));
1048 gdImageStringFT(im, &bounding_rectange[0], PAGECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Pages"));
1049
1050 if (g_settings.graphs.use_kanji == false) {
1051 y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1);
1052 } else {
1053 y += text_lengthA + 1;
1054 }
1055 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, "/");
1056 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, "/");
1057
1058 if (g_settings.graphs.use_kanji == false) {
1059 y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD);
1060 } else {
1061 y += text_height + 1;
1062 }
1063 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Files"));
1064 gdImageStringFT(im, &bounding_rectange[0], FILECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Files"));
1065
1066 if (g_settings.graphs.use_kanji == false) {
1067 y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD + text_lengthB + 1);
1068 } else {
1069 y += text_lengthB + 1;
1070 }
1071 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, "/");
1072 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, "/");
1073
1074 if (g_settings.graphs.use_kanji == false) {
1075 y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD + text_lengthB + 1 + text_lengthD);
1076 } else {
1077 y += text_height + 1;
1078 }
1079 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Hits"));
1080 gdImageStringFT(im, &bounding_rectange[0], HITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Hits"));
1081 }
1082 #else
1083 /* magic numbers */
1084 font_char_width = 6;
1085 font_char_height = 10;
1086 if (g_settings.graphs.legend) { /* print color coded legends? */
1087 /* Volume Legend */
1088 /* i = (strlen (_("Volume")) * font_char_width); */
1089 edge_text_offset = graphx - GRAPH_INNER_BOX_RIGHT; /* + GRAPH_TEXT_X_OFFSET; */
1090 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, graphy - GRAPH_INNER_BOX_BOTTOM - GRAPH_TEXT_Y_OFFSET - 1, (unsigned char *) _("Volume"), dkgrey);
1091 gdImageStringUp(im, gdFontSmall, edge_text_offset, graphy - GRAPH_INNER_BOX_BOTTOM - GRAPH_TEXT_Y_OFFSET, (unsigned char *) _("Volume"), KBYTECOLOR);
1092
1093 /* Sites/Visits Legend */
1094 i = (strlen(_("Sites")) * font_char_width);
1095 k = font_char_width / 2; /* Half Char Sep */
1096 k2 = font_char_width * 2; /* Half Space '/' Half Space */
1097 /* j = (strlen (_("Sites")) * font_char_width); */
1098 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_bottom - GRAPH_TEXT_Y_OFFSET - 1, (unsigned char *) _("Sites"), dkgrey);
1099 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_bottom - GRAPH_TEXT_Y_OFFSET, (unsigned char *) _("Sites"), SITECOLOR);
1100 if (is_month == true) {
1101 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k - 1, (unsigned char *) text_separator, dkgrey);
1102 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k, (unsigned char *) text_separator, black);
1103 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k2 - 1, (unsigned char *) _("Visits"), dkgrey);
1104 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k2, (unsigned char *) _("Visits"), VISITCOLOR);
1105 }
1106
1107 /* Hits/Files/Pages Legend */
1108 i = (strlen(_("Pages")) * font_char_width);
1109 j = (strlen(_("Files")) * font_char_width);
1110 /* k same as above */
1111 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - 1, (unsigned char *) _("Pages"), dkgrey);
1112 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET, (unsigned char *) _("Pages"), PAGECOLOR);
1113 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - k - 1, (unsigned char *) text_separator, dkgrey);
1114 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - k, (unsigned char *) text_separator, black);
1115 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - k2 - 1, (unsigned char *) _("Files"), dkgrey);
1116 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - k2, (unsigned char *) _("Files"), FILECOLOR);
1117 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 - k - 1, (unsigned char *) text_separator, dkgrey);
1118 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 - k - 1, (unsigned char *) text_separator, black);
1119 gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 * 2 - 1, (unsigned char *) _("Hits"), dkgrey);
1120 gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 * 2 - 1, (unsigned char *) _("Hits"), HITCOLOR);
1121 }
1122 #endif
1123
1124
1125 /****************************************************
1126 * Do the Upper Section *
1127 * Hits, Files, Pages *
1128 ****************************************************/
1129 /* get max val */
1130 for (i = 0; i < 31; i++) {
1131 if (data1[i] > maxval) {
1132 maxval = data1[i];
1133 }
1134 if (data2[i] > maxval) {
1135 maxval = data2[i];
1136 }
1137 if (data5[i] > maxval) {
1138 maxval = data5[i];
1139 }
1140 }
1141 if (maxval <= 0) {
1142 maxval = 1;
1143 }
1144
1145 if (g_settings.graphs.lines) {
1146 edge_text_offset = 6 + GRAPH_TEXT_Y_OFFSET + 1;
1147 /* tiny font has height of 6 */
1148 draw_horiz_grid(maxval, section_left + 1, section_right, bottom_upper, height_upper, edge_text_offset, 0);
1149 }
1150 /* width/offset calculations */
1151 index_diff = max_i;
1152 bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 3);
1153 bar_offset = (((double) main_width - ((double) bar_gap * 2.0)) / (double) index_diff);
1154 day_middle_x = (bar_width + bar_sep * 2) / 2 + 1; /* Calc for the mid point on which to then draw the month on the X Axis */
1155
1156 for (i = 0; i < max_i; i++) {
1157 /* HITS */
1158 percent = ((double) data1[i] / (double) maxval);
1159 xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + (i * bar_offset);
1160 draw_bar(xbarleft, bottom_upper, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, HITCOLOR);
1161
1162 /* X Axis */
1163 /* Set X Label values for Daily/Hours. Alter if is_month. */
1164 x_colour = black;
1165 x_label = numchar[i];
1166 if (is_month) {
1167 x_label = numchar[i + 1];
1168 if ((julday % 7 == 6) || (julday % 7 == 0)) {
1169 x_colour = HITCOLOR;
1170 }
1171 }
1172 #ifdef HAVE_GDIMAGESTRINGFT
1173 gdImageStringFT(NULL, &bounding_rectange[0], x_colour, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, 0, 0, (char *) x_label);
1174 text_centring = abs(bounding_rectange[4]) / 2;
1175 cur_text_start = xbarleft + day_middle_x - text_centring;
1176 gdImageStringFT(im, &bounding_rectange[0], x_colour, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, cur_text_start, edge_text_offsetBOTTOM, (char *) x_label);
1177 #else
1178 text_centring = (((double) strlen(x_label)) / 2.0) * (double) font_char_width;
1179 cur_text_start = xbarleft + day_middle_x - text_centring;
1180 gdImageString(im, gdFontSmall, cur_text_start, bottom_lower + GRAPH_TEXT_Y_OFFSET, (unsigned char *) x_label, x_colour);
1181 #endif
1182 julday++;
1183
1184 /* FILES */
1185 percent = ((double) data2[i] / (double) maxval);
1186 draw_bar(xbarleft + bar_sep, bottom_upper, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, FILECOLOR);
1187
1188 /* PAGES */
1189 percent = ((double) data5[i] / (double) maxval);
1190 draw_bar(xbarleft + bar_sep + bar_sep, bottom_upper, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, PAGECOLOR);
1191 }
1192
1193 /****************************************************
1194 * Do the Middle Section *
1195 * Sites, Visits *
1196 ****************************************************/
1197 /* get max val */
1198 maxval = 0;
1199 for (i = 0; i < max_i; i++) {
1200 if (data3[i] > maxval) {
1201 maxval = data3[i];
1202 }
1203 if (data6[i] > maxval) {
1204 maxval = data6[i];
1205 }
1206 }
1207 if (maxval <= 0) {
1208 maxval = 1;
1209 }
1210
1211 if (g_settings.graphs.lines) {
1212 /* re-use edge_text_offset from previous calc */
1213 draw_horiz_grid(maxval, section_left + 1, section_right, bottom_middle, height_middle, edge_text_offset, 0);
1214 }
1215 bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 2);
1216 for (i = 0; i < max_i; i++) {
1217 /* Visits */
1218 percent = ((double) data6[i] / (double) maxval);
1219 xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + (i * bar_offset);
1220 draw_bar(xbarleft, bottom_middle, bottom_middle - height_middle, bar_width, percent, 0, VISITCOLOR);
1221
1222 /* Sites */
1223 percent = ((double) data3[i] / (double) maxval);
1224 draw_bar(xbarleft + bar_sep, bottom_middle, bottom_middle - height_middle, bar_width, percent, 0, SITECOLOR);
1225 }
1226
1227 /****************************************************
1228 * Do the Lower Section *
1229 * Volume Transferred *
1230 ****************************************************/
1231 /* get max val */
1232 fmaxval = 0.0;
1233 for (i = 0; i < max_i; i++) {
1234 if (data4[i] > fmaxval) {
1235 fmaxval = data4[i];
1236 }
1237 }
1238 if (fmaxval <= 0.0) {
1239 fmaxval = 1.0;
1240 }
1241 if (g_settings.graphs.lines) {
1242 /* re-use edge_text_offset from previous calc */
1243 draw_horiz_grid((unsigned long long) fmaxval, section_left + 1, section_right, bottom_lower, height_lower, edge_text_offset, 1);
1244 }
1245 bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 2);
1246 for (i = 0; i < max_i; i++) {
1247 /* VOLUME */
1248 percent = ((double) data4[i] / (double) fmaxval);
1249 xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + (i * bar_offset);
1250 draw_bar(xbarleft, bottom_lower, bottom_lower - height_lower, bar_width, percent, 0, KBYTECOLOR);
1251 }
1252
1253 /* open file for writing */
1254 if ((out = fopen(fname, "wb")) != NULL) {
1255 gdImagePng(im, out);
1256 fclose(out);
1257 }
1258 /* deallocate memory */
1259 gdImageDestroy(im);
1260
1261 return (0);
1262 }
1263
1264
1265 /*****************************************************************
1266 * *
1267 * PIE_CHART - draw a pie chart (10 data items max) *
1268 * *
1269 *****************************************************************/
1270
1271 int
pie_chart(char * fname,char * title,unsigned long long t_val,unsigned long long data1[],char * legend[])1272 pie_chart(char *fname, char *title, unsigned long long t_val, unsigned long long data1[], char *legend[])
1273 {
1274 int i, x, slice_percent, y;
1275 int centre_x; /* Centre for the X location */
1276 int centre_y; /* Centre for the Y location */
1277 int diamtr_x; /* diameter X Axis */
1278 int diamtr_y; /* diameter Y Axis */
1279 double XtoYratio = 5.0 / 6.0; /* Ratio of set X to Y dynamic => 5/6 = 0.833... */
1280 int text_max_x; /* Max right point for text listing */
1281 double s_arc = 0.0;
1282 int r, g, b;
1283 int colour_index;
1284 char buffer[512];
1285
1286 struct pie_data gdata;
1287 int bounding_rectange[8]; /* GD Bounding Rectangle, use to identify outer limits of drawn text */
1288
1289 /* init graph and colors */
1290 init_graph(title, g_settings.graphs.pie_x, g_settings.graphs.pie_y);
1291
1292 /* First deallocate all the hit etc colours, and setup for using pie colours instead */
1293 for (i = 4 + 6 - 1; i > 4 - 1; i--) {
1294 gdImageColorDeallocate(im, i);
1295 }
1296 r = getred(pie_color1);
1297 g = getgreen(pie_color1);
1298 b = getblue(pie_color1);
1299 colour_index = gdImageColorAllocate(im, r, g, b);
1300 r = getred(pie_color2);
1301 g = getgreen(pie_color2);
1302 b = getblue(pie_color2);
1303 colour_index = gdImageColorAllocate(im, r, g, b);
1304 r = getred(pie_color3);
1305 g = getgreen(pie_color3);
1306 b = getblue(pie_color3);
1307 colour_index = gdImageColorAllocate(im, r, g, b);
1308 r = getred(pie_color4);
1309 g = getgreen(pie_color4);
1310 b = getblue(pie_color4);
1311 colour_index = gdImageColorAllocate(im, r, g, b);
1312
1313 /* Default extra colours */
1314 colour_index = gdImageColorAllocate(im, 0, 192, 255); /* cyan */
1315 colour_index = gdImageColorAllocate(im, 255, 255, 0); /* yellow */
1316 colour_index = gdImageColorAllocate(im, 128, 0, 128); /* purple */
1317 colour_index = gdImageColorAllocate(im, 128, 255, 192); /* ltgreen */
1318 colour_index = gdImageColorAllocate(im, 255, 0, 255); /* ltpurple */
1319 colour_index = gdImageColorAllocate(im, 255, 196, 128); /* brown */
1320
1321 /* base caclulations */
1322 centre_x = (int) ((double) g_settings.graphs.pie_x * (1.0 / 3.0) + 1.0);
1323 centre_y = g_settings.graphs.pie_y / 2;
1324 diamtr_x = (centre_x - GRAPH_INNER_BOX_LEFT - GRAPH_PIE_X_INSET) * 2;
1325 diamtr_y = (int) ((double) diamtr_x * XtoYratio + 1.0);
1326 text_max_x = g_settings.graphs.pie_x - GRAPH_INNER_BOX_LEFT - GRAPH_PIE_X_INSET;
1327 VPRINT(VERBOSE5, "PIE: cx: %d cy: %d dx: %d dy: %d x: %d y: %d\n", centre_x, centre_y, diamtr_x, diamtr_y, g_settings.graphs.pie_x, g_settings.graphs.pie_y);
1328
1329 /* do the circle... */
1330 gdImageArc(im, centre_x, centre_y, diamtr_x, diamtr_y, 0, 360, black);
1331 gdImageArc(im, centre_x, centre_y + 10, diamtr_x - 2, diamtr_y - 2, 2, 178, black);
1332 gdImageFillToBorder(im, centre_x, centre_y + (diamtr_y / 2) + 1, black, black);
1333
1334 /* slice the pie */
1335 gdata = *calc_arc(0.0, 0.0, centre_x, centre_y, diamtr_x, diamtr_y);
1336 gdImageLine(im, centre_x, centre_y, gdata.x, gdata.y, black); /* inital line */
1337
1338 /* Do the fills, then come back and do the text. Else, can get text in the pie itself which occludes fills. */
1339 y = centre_y - (diamtr_y / 2);
1340 for (i = 0; i < 10; i++) { /* run through data array */
1341 if ((data1[i] != 0) && (s_arc < 1.0)) { /* make sure valid slice */
1342 slice_percent = (((double) data1[i] / t_val) + 0.005) * 100.0;
1343 if (slice_percent < 1)
1344 break;
1345
1346 if (s_arc + ((double) slice_percent / 100.0) >= 1.0) {
1347 gdata = *calc_arc(s_arc, 1.0, centre_x, centre_y, diamtr_x, diamtr_y);
1348 s_arc = 1.0;
1349 } else {
1350 gdata = *calc_arc(s_arc, s_arc + ((double) slice_percent / 100.0), centre_x, centre_y, diamtr_x, diamtr_y);
1351 s_arc += (double) slice_percent / 100.0;
1352 }
1353
1354 gdImageLine(im, centre_x, centre_y, gdata.x, gdata.y, black);
1355 gdImageFill(im, gdata.mx, gdata.my, i + 4);
1356 }
1357 }
1358
1359 if (s_arc < 1.0) { /* anything left over? */
1360 gdata = *calc_arc(s_arc, 1.0, centre_x, centre_y, diamtr_x, diamtr_y);
1361 gdImageFill(im, gdata.mx, gdata.my, white);
1362 }
1363
1364 /* Repeat drawing loop for text as longer text was stopping the fill from fully ... filling. */
1365 #ifdef HAVE_GDIMAGESTRINGFT
1366 y = centre_y - (diamtr_y / 2) + GRAPH_FONT_SIZE_MEDIUM;
1367 #else
1368 y = centre_y - (diamtr_y / 2);
1369 #endif
1370 s_arc = 0.0;
1371 for (i = 0; i < 10; i++) { /* run through data array */
1372 if ((data1[i] != 0) && (s_arc < 1.0)) { /* make sure valid slice */
1373 slice_percent = (((double) data1[i] / t_val) + 0.005) * 100.0;
1374 if (slice_percent < 1)
1375 break;
1376 if (s_arc + ((double) slice_percent / 100.0) >= 1.0) {
1377 s_arc = 1.0;
1378 } else {
1379 s_arc += (double) slice_percent / 100.0;
1380 }
1381 snprintf(buffer, sizeof(buffer) - 1, "%s (%d%%)", legend[i], slice_percent);
1382
1383 #ifdef HAVE_GDIMAGESTRINGFT
1384 /* "print" the string to get an idea of actual size and hence find the centre */
1385 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 100, 100, buffer);
1386 x = text_max_x - abs(bounding_rectange[4] - bounding_rectange[0]);
1387
1388 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, buffer);
1389 gdImageStringFT(im, &bounding_rectange[0], i + 4, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, buffer);
1390 y += (GRAPH_FONT_SIZE_MEDIUM * 2);
1391 #else
1392 x = text_max_x - (strlen(buffer) * 7);
1393 gdImageString(im, gdFontMediumBold, x + 1, y + 1, (unsigned char *) buffer, black);
1394 gdImageString(im, gdFontMediumBold, x, y, (unsigned char *) buffer, i + 4);
1395 y += 20;
1396 #endif
1397 }
1398 }
1399
1400 if (s_arc < 1.0) { /* anything left over? */
1401 snprintf(buffer, sizeof(buffer) - 1, "%s (%d%%)", _("Other"), 100 - (int) (s_arc * 100));
1402 #ifdef HAVE_GDIMAGESTRINGFT
1403 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, buffer);
1404 x = text_max_x - bounding_rectange[4];
1405 gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, buffer);
1406 gdImageStringFT(im, &bounding_rectange[0], white, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, buffer);
1407 #else
1408 x = text_max_x - (strlen(buffer) * 7);
1409 gdImageString(im, gdFontMediumBold, x + 1, y + 1, (unsigned char *) buffer, black);
1410 gdImageString(im, gdFontMediumBold, x, y, (unsigned char *) buffer, white);
1411 #endif
1412 }
1413
1414 /* save png image */
1415 if ((out = fopen(fname, "wb")) != NULL) {
1416 gdImagePng(im, out);
1417 fclose(out);
1418 }
1419 /* deallocate memory */
1420 gdImageDestroy(im);
1421
1422 return (0);
1423 }
1424
1425 /*****************************************************************
1426 * *
1427 * CALC_ARC - generate x,y coordinates for pie chart *
1428 * *
1429 *****************************************************************/
1430
1431 static struct pie_data *
calc_arc(double min,double max,int cx,int cy,int dx,int dy)1432 calc_arc(double min, double max, int cx, int cy, int dx, int dy)
1433 /* cx, cy - Centre X & Y
1434 * dx, dy - Diameter X & Y */
1435 {
1436 static struct pie_data data;
1437 double d;
1438
1439 /* Calculate max line */
1440 d = max;
1441 data.x = cos(d * (2 * PI)) * ((dx - 2) / 2) + cx;
1442 data.y = sin(d * (2 * PI)) * ((dy - 2) / 2) + cy;
1443 /* Now get mid-point */
1444 d = ((min + max) / 2);
1445 data.mx = cos(d * (2 * PI)) * (dx / 3) + cx;
1446 data.my = sin(d * (2 * PI)) * (dy / 3) + cy;
1447 return &data;
1448 }
1449
1450 /****************************************************************
1451 * *
1452 * INIT_GRAPH - initalize graph and draw borders *
1453 * 1 px black rectange on outer edge *
1454 * G_SHADOW_WIDTH px white/grey shadow effect *
1455 * 1 + 1 px black/white shadow'd line rectange - inner box *
1456 * inset G_INNER_BOX_TOP & G_INNER_BOX_LEFT *
1457 * white line is inset -1 again *
1458 * *
1459 ****************************************************************/
1460
1461 static void
init_graph(char * title,int xsize,int ysize)1462 init_graph(char *title, int xsize, int ysize)
1463 {
1464 int i, r, g, b;
1465 int edge_text_offset; /* px. Offset from the edge for descriptive text */
1466
1467 #ifdef HAVE_GDIMAGESTRINGFT
1468 int bounding_rectange[8]; /* GD Bounding Rectangle, use to identify outer limits of drawn text */
1469 #else
1470 int font_char_height; /* px. Height of an individual char in a given font - magic constant */
1471 #endif
1472
1473 im = gdImageCreate(xsize, ysize);
1474
1475 /* allocate color maps, background color first (grey) */
1476 grey = gdImageColorAllocate(im, 192, 192, 192);
1477 dkgrey = gdImageColorAllocate(im, 128, 128, 128);
1478 black = gdImageColorAllocate(im, 0, 0, 0);
1479 white = gdImageColorAllocate(im, 255, 255, 255);
1480 /* By default, we set up all graphs index'ed off hits, pages etc
1481 * which means for pie charts we need to deallocate these 6 colours */
1482 r = getred(hit_color);
1483 g = getgreen(hit_color);
1484 b = getblue(hit_color);
1485 hitcolor = gdImageColorAllocate(im, r, g, b);
1486 r = getred(site_color);
1487 g = getgreen(site_color);
1488 b = getblue(site_color);
1489 sitecolor = gdImageColorAllocate(im, r, g, b);
1490 r = getred(file_color);
1491 g = getgreen(file_color);
1492 b = getblue(file_color);
1493 filecolor = gdImageColorAllocate(im, r, g, b);
1494 r = getred(kbyte_color);
1495 g = getgreen(kbyte_color);
1496 b = getblue(kbyte_color);
1497 kbytecolor = gdImageColorAllocate(im, r, g, b);
1498 r = getred(page_color);
1499 g = getgreen(page_color);
1500 b = getblue(page_color);
1501 pagecolor = gdImageColorAllocate(im, r, g, b);
1502 r = getred(visit_color);
1503 g = getgreen(visit_color);
1504 b = getblue(visit_color);
1505 visitcolor = gdImageColorAllocate(im, r, g, b);
1506
1507 /* make borders */
1508
1509 for (i = 1; i < (GRAPH_SHADOW_WIDTH + 1); i++) { /* do shadow effect */
1510 gdImageLine(im, i, i, xsize - (i + 1), i, white);
1511 gdImageLine(im, i, i, i, ysize - (i + 1), white);
1512 gdImageLine(im, i + 1, ysize - (i + 1), xsize - (i + 1), ysize - (i + 1), dkgrey);
1513 gdImageLine(im, xsize - (i + 1), i + 1, xsize - (i + 1), ysize - (i + 1), dkgrey);
1514 }
1515
1516 gdImageRectangle(im, GRAPH_INNER_BOX_LEFT, GRAPH_INNER_BOX_TOP, xsize - (GRAPH_INNER_BOX_LEFT + 1), ysize - (GRAPH_INNER_BOX_BOTTOM + 1), black);
1517 gdImageRectangle(im, (GRAPH_INNER_BOX_LEFT - 1), (GRAPH_INNER_BOX_TOP - 1), xsize - (GRAPH_INNER_BOX_LEFT + 2), ysize - (GRAPH_INNER_BOX_BOTTOM + 2), white);
1518 gdImageRectangle(im, 0, 0, xsize - 1, ysize - 1, black);
1519
1520 #ifdef HAVE_GDIMAGESTRINGFT
1521 /* display the graph title */
1522 gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_default, GRAPH_FONT_SIZE, 0.0, 0, 0, title);
1523 edge_text_offset =
1524 (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_TOP) - (GRAPH_SHADOW_WIDTH + ((GRAPH_INNER_BOX_TOP - (abs(bounding_rectange[5]) + abs(bounding_rectange[1]) + 1)) / 2) +
1525 abs(bounding_rectange[1])) + 2;
1526 gdImageStringFT(im, &bounding_rectange[0], filecolor, graph_font_default, GRAPH_FONT_SIZE, 0.0, GRAPH_INNER_BOX_LEFT, edge_text_offset, title);
1527 #else
1528 font_char_height = 10;
1529 edge_text_offset = GRAPH_INNER_BOX_TOP - font_char_height - GRAPH_TEXT_Y_OFFSET - 1;
1530 gdImageString(im, gdFontMediumBold, GRAPH_INNER_BOX_LEFT, edge_text_offset, (unsigned char *) title, filecolor);
1531 #endif
1532
1533 return;
1534 }
1535
1536
1537 /****************************************************************
1538 * initialise_graphs *
1539 * *
1540 * Any graphical Initialisation is done here. Dur. *
1541 * Run before any processing. If we need to fail & exit, better *
1542 * done early. *
1543 * *
1544 * Debugging TTF woes. *
1545 * - use 'fc-list' *
1546 * by default we try and use fontconfig. *
1547 * fc-list will show all avilable 'fonts' on a system *
1548 * Not so much by actual name, more family/alias. *
1549 * see /etc/fonts/... for details. *
1550 * - 'export FC_DEBUG=1' before an awfful run. *
1551 * Use fontconfig internal debugging. If "problems", this *
1552 * will generate a LOT of output. Redirect to file! *
1553 * When working will see the actual file on disk used. *
1554 * eg: *
1555 * file: "/usr/share/fonts/default/Type1/n019003l.pfb"(w) *
1556 * *
1557 * Recommended to have the 'urw-fonts' package installed. *
1558 * *
1559 ****************************************************************/
1560
1561 void
initialise_graphs(void)1562 initialise_graphs(void)
1563 {
1564 int FT_Config_Success;
1565
1566 #ifdef HAVE_GDFTUSEFONTCONFIG
1567 FT_Config_Success = gdFTUseFontConfig(1);
1568 if (!FT_Config_Success) {
1569 /* Bummer. Font config failed to work. Fallback to hardcoded path. */
1570 graph_font_default = GRAPH_FONT_FULLPATH_DEFAULT;
1571 graph_font_label = GRAPH_FONT_FULLPATH_LABEL;
1572 ERRVPRINT(VERBOSE1, "%s: %d\n", _("Error in setting gdFTUseFontConfig"), FT_Config_Success);
1573 ERRVPRINT(VERBOSE2, "Default Font Path: %s\nDefault Font Label Path: %s\n", graph_font_default, graph_font_label);
1574 } else {
1575 VPRINT(VERBOSE2, "%s: %d\n", _("Success in setting gdFTUseFontConfig"), FT_Config_Success);
1576 }
1577 #endif
1578 }
1579