1 #include "common.h"
2 #include "dbsql.h"
3 #include "misc.h"
4 #include "image.h"
5 #include "image_support.h"
6 #include "vnstati.h"
7 
initimagecontent(IMAGECONTENT * ic)8 void initimagecontent(IMAGECONTENT *ic)
9 {
10 	ic->im = NULL;
11 	ic->font = gdFontGetSmall();
12 	ic->lineheight = 12;
13 	ic->large = 0;
14 	ic->showheader = 1;
15 	ic->showedge = 1;
16 	ic->showlegend = 1;
17 	ic->altdate = 0;
18 	ic->headertext[0] = '\0';
19 	ic->databegin[0] = '\0';
20 	ic->dataend[0] = '\0';
21 	ic->interface.name[0] = '\0';
22 	ic->interface.alias[0] = '\0';
23 }
24 
drawimage(IMAGECONTENT * ic)25 void drawimage(IMAGECONTENT *ic)
26 {
27 	switch (cfg.qmode) {
28 		case 1:
29 			drawlist(ic, "day");
30 			break;
31 		case 2:
32 			drawlist(ic, "month");
33 			break;
34 		case 3:
35 			drawlist(ic, "top");
36 			break;
37 		case 4:
38 			drawlist(ic, "year");
39 			break;
40 		case 5:
41 			drawsummary(ic, 0, 0);
42 			break;
43 		case 51:
44 			drawsummary(ic, 1, cfg.hourlyrate); // horizontal
45 			break;
46 		case 52:
47 			drawsummary(ic, 2, cfg.hourlyrate); // vertical
48 			break;
49 		case 7:
50 			drawhourly(ic, cfg.hourlyrate);
51 			break;
52 		case 8:
53 			drawlist(ic, "hour");
54 			break;
55 		case 9:
56 			drawlist(ic, "fiveminute");
57 			break;
58 		case 10:
59 			drawfivegraph(ic, cfg.hourlyrate, cfg.fivegresultcount, cfg.fivegheight);
60 			break;
61 		default:
62 			printf("Error: No such query mode: %d\n", cfg.qmode);
63 			exit(EXIT_FAILURE);
64 	}
65 
66 	/* enable background transparency if needed */
67 	if (cfg.transbg) {
68 		gdImageColorTransparent(ic->im, ic->cbackground);
69 	}
70 }
71 
72 #if HAVE_DECL_GD_NEAREST_NEIGHBOUR
scaleimage(IMAGECONTENT * ic)73 void scaleimage(IMAGECONTENT *ic)
74 {
75 	gdImagePtr im_scaled;
76 	unsigned int width = 0, height = 0;
77 
78 	if (cfg.imagescale == 100 || ic->im == NULL) {
79 		return;
80 	}
81 
82 	width = (unsigned int)((float)gdImageSX(ic->im) * ((float)cfg.imagescale / (float)100));
83 	height = (unsigned int)((float)gdImageSY(ic->im) * ((float)cfg.imagescale / (float)100));
84 
85 	if (width < 100 || height < 100) {
86 		return;
87 	}
88 
89 	if (width > 5000 || height > 5000) {
90 		return;
91 	}
92 
93 	/* keep output sharp when percent is an exact multiplier */
94 	if (cfg.imagescale % 100 == 0) {
95 		gdImageSetInterpolationMethod(ic->im, GD_NEAREST_NEIGHBOUR);
96 	}
97 
98 	im_scaled = gdImageScale(ic->im, width, height);
99 	if (im_scaled == NULL) {
100 		return;
101 	}
102 
103 	gdImageDestroy(ic->im);
104 	ic->im = im_scaled;
105 }
106 #endif
107 
drawhours(IMAGECONTENT * ic,const int xpos,const int ypos,const int rate)108 int drawhours(IMAGECONTENT *ic, const int xpos, const int ypos, const int rate)
109 {
110 	int i, tmax = 0, s = 0, step, prev = 0, diff = 0, chour;
111 	int x = xpos, y = ypos, extrax = 0, extray = 0;
112 	double ratediv;
113 	uint64_t max = 1, scaleunit = 0;
114 	char buffer[32];
115 	struct tm *d;
116 	dbdatalist *datalist = NULL, *datalist_i = NULL;
117 	dbdatalistinfo datainfo;
118 	HOURDATA hourdata[24];
119 	gdFontPtr font;
120 
121 	if (ic->large) {
122 		font = gdFontGetSmall();
123 	} else {
124 		font = gdFontGetTiny();
125 	}
126 
127 	for (i = 0; i < 24; i++) {
128 		hourdata[i].rx = hourdata[i].tx = 0;
129 		hourdata[i].date = 0;
130 	}
131 
132 	if (!db_getdata(&datalist, &datainfo, ic->interface.name, "hour", 24) || datainfo.count == 0) {
133 		gdImageString(ic->im, ic->font, x + (32 * ic->font->w), y + 54, (unsigned char *)"no data available", ic->ctext);
134 		return 0;
135 	}
136 
137 	datalist_i = datalist;
138 
139 	while (datalist_i != NULL) {
140 		d = localtime(&datalist_i->timestamp);
141 		if (hourdata[d->tm_hour].date != 0 || ic->interface.updated - datalist_i->timestamp > 86400) {
142 			datalist_i = datalist_i->next;
143 			continue;
144 		}
145 		hourdata[d->tm_hour].rx = datalist_i->rx;
146 		hourdata[d->tm_hour].tx = datalist_i->tx;
147 		hourdata[d->tm_hour].date = datalist_i->timestamp;
148 		datalist_i = datalist_i->next;
149 	}
150 	dbdatalistfree(&datalist);
151 
152 	ic->current = ic->interface.updated;
153 	chour = localtime(&ic->current)->tm_hour;
154 
155 	if (cfg.rateunit) {
156 		ratediv = 450; /* x * 8 / 3600 */
157 	} else {
158 		ratediv = 3600;
159 	}
160 
161 	/* tmax (time max) = current hour */
162 	/* max = transfer max */
163 
164 	for (i = 0; i < 24; i++) {
165 		/* convert hourly transfer to hourly rate if needed */
166 		if (rate) {
167 			if ((ic->current - hourdata[i].date) > 3600) {
168 				hourdata[i].rx = (uint64_t)((double)hourdata[i].rx / ratediv);
169 				hourdata[i].tx = (uint64_t)((double)hourdata[i].tx / ratediv);
170 			} else {
171 				/* scale ongoing hour properly */
172 				if (chour != i) {
173 					hourdata[i].rx = (uint64_t)((double)hourdata[i].rx / ratediv);
174 					hourdata[i].tx = (uint64_t)((double)hourdata[i].tx / ratediv);
175 				} else {
176 					d = localtime(&ic->current);
177 					diff = d->tm_min * 60;
178 					if (!diff) {
179 						diff = 60;
180 					}
181 					if (cfg.rateunit == 1) {
182 						hourdata[i].rx *= 8;
183 						hourdata[i].tx *= 8;
184 					}
185 					hourdata[i].rx = (uint64_t)((double)hourdata[i].rx / (double)diff);
186 					hourdata[i].tx = (uint64_t)((double)hourdata[i].tx / (double)diff);
187 				}
188 			}
189 		}
190 
191 		if (hourdata[i].date >= hourdata[tmax].date) {
192 			tmax = i;
193 		}
194 		if (hourdata[i].rx >= max) {
195 			max = hourdata[i].rx;
196 		}
197 		if (hourdata[i].tx >= max) {
198 			max = hourdata[i].tx;
199 		}
200 	}
201 
202 	if (ic->large) {
203 		x += 14;
204 		extrax = 145;
205 		extray = 35;
206 	}
207 
208 	/* scale values */
209 	scaleunit = getscale(max, rate);
210 
211 	s = (int)lrint(((double)scaleunit / (double)max) * (124 + extray));
212 	if (s < SCALEMINPIXELS) {
213 		step = 2;
214 	} else {
215 		step = 1;
216 	}
217 
218 	for (i = step; i * s <= (124 + extray + 4); i = i + step) {
219 		gdImageDashedLine(ic->im, x + 36, y + 124 - (i * s), x + 460 + extrax, y + 124 - (i * s), ic->cline);
220 		gdImageDashedLine(ic->im, x + 36, y + 124 - prev - (step * s) / 2, x + 460 + extrax, y + 124 - prev - (step * s) / 2, ic->clinel);
221 		gdImageString(ic->im, font, x + 16 - (ic->large * 3), y + 121 - (i * s) - (ic->large * 3), (unsigned char *)getimagevalue(scaleunit * (unsigned int)i, 3, rate), ic->ctext);
222 		prev = i * s;
223 	}
224 	if ((prev + (step * s) / 2) <= (124 + extray + 4)) {
225 		gdImageDashedLine(ic->im, x + 36, y + 124 - prev - (step * s) / 2, x + 460 + extrax, y + 124 - prev - (step * s) / 2, ic->clinel);
226 	}
227 
228 	/* scale text */
229 	gdImageStringUp(ic->im, font, x - 2 - (ic->large * 14), y + 58 + (rate * 10) - (extray / 2), (unsigned char *)getimagescale(scaleunit * (unsigned int)step, rate), ic->ctext);
230 
231 	/* axis */
232 	gdImageLine(ic->im, x + 36 - 4, y + 124, x + 466 + extrax, y + 124, ic->ctext);
233 	gdImageLine(ic->im, x + 36, y - 10 - extray, x + 36, y + 124 + 4, ic->ctext);
234 
235 	/* arrows */
236 	drawarrowup(ic, x + 36, y - 9 - extray);
237 	drawarrowright(ic, x + 465 + extrax, y + 124);
238 
239 	/* x-axis values and poles */
240 	for (i = 0; i < 24; i++) {
241 		s = tmax - i;
242 		if (s < 0) {
243 			s += 24;
244 		}
245 		snprintf(buffer, 32, "%02d ", s);
246 		if (hourdata[s].date == 0) {
247 			chour = ic->cline;
248 		} else {
249 			chour = ic->ctext;
250 		}
251 		gdImageString(ic->im, font, x + 440 - (i * (17 + ic->large * 6)) + extrax, y + 128, (unsigned char *)buffer, chour);
252 		drawpoles(ic, x + 438 - (i * (17 + ic->large * 6)) + extrax, y - extray, 124 + extray, hourdata[s].rx, hourdata[s].tx, max);
253 		gdImageLine(ic->im, x + 438 - 2 - (i * (17 + ic->large * 6)) + extrax, y + 124, x + 438 + 14 - (i * (17 + ic->large * 6)) + extrax, y + 124, chour);
254 	}
255 
256 	return 1;
257 }
258 
drawhourly(IMAGECONTENT * ic,const int rate)259 void drawhourly(IMAGECONTENT *ic, const int rate)
260 {
261 	int width, height, headermod = 0;
262 
263 	width = 500 + (ic->large * 168);
264 	height = 200 + (ic->large * 48);
265 
266 	if (!ic->showheader) {
267 		headermod = 26;
268 		height -= 22;
269 	}
270 
271 	imageinit(ic, width, height);
272 	layoutinit(ic, " / hourly", width, height);
273 
274 	if (drawhours(ic, 12, 46 - headermod + (ic->large * 40), rate)) {
275 		drawlegend(ic, width / 2 - (ic->large * 10), 183 - headermod + (ic->large * 46), 0);
276 	}
277 }
278 
drawlist(IMAGECONTENT * ic,const char * listname)279 void drawlist(IMAGECONTENT *ic, const char *listname)
280 {
281 	ListType listtype = LT_None;
282 	int textx, texty, offsetx = 0;
283 	int width, height, headermod, i = 1, rowcount = 0;
284 	int estimateavailable = 0, estimatevisible = 0;
285 	int32_t limit;
286 	uint64_t e_rx = 0, e_tx = 0, e_secs = 0;
287 	char buffer[512], datebuff[16], daybuff[16];
288 	char stampformat[64], titlename[16], colname[8];
289 	struct tm *d;
290 	time_t current;
291 	dbdatalist *datalist = NULL, *datalist_i = NULL;
292 	dbdatalistinfo datainfo;
293 
294 	if (strcmp(listname, "day") == 0) {
295 		listtype = LT_Day;
296 		strncpy_nt(colname, listname, 8);
297 		snprintf(titlename, 16, "daily");
298 		strncpy_nt(stampformat, cfg.dformat, 64);
299 		limit = cfg.listdays;
300 	} else if (strcmp(listname, "month") == 0) {
301 		listtype = LT_Month;
302 		strncpy_nt(colname, listname, 8);
303 		snprintf(titlename, 16, "monthly");
304 		strncpy_nt(stampformat, cfg.mformat, 64);
305 		limit = cfg.listmonths;
306 	} else if (strcmp(listname, "year") == 0) {
307 		listtype = LT_Year;
308 		strncpy_nt(colname, listname, 8);
309 		snprintf(titlename, 16, "yearly");
310 		strncpy_nt(stampformat, "%Y", 64);
311 		limit = cfg.listyears;
312 	} else if (strcmp(listname, "top") == 0) {
313 		listtype = LT_Top;
314 		snprintf(colname, 8, "day");
315 		strncpy_nt(stampformat, cfg.tformat, 64);
316 		limit = cfg.listtop;
317 		offsetx = 5 * ic->font->w;
318 	} else if (strcmp(listname, "hour") == 0) {
319 		listtype = LT_Hour;
320 		strncpy_nt(colname, listname, 8);
321 		snprintf(titlename, 16, "hourly");
322 		strncpy_nt(stampformat, "%H:%M", 64);
323 		limit = cfg.listhours;
324 	} else if (strcmp(listname, "fiveminute") == 0) {
325 		listtype = LT_5min;
326 		strncpy_nt(colname, "time", 8);
327 		snprintf(titlename, 16, "5 minute");
328 		strncpy_nt(stampformat, "%H:%M", 64);
329 		limit = cfg.listfivemins;
330 	} else {
331 		return;
332 	}
333 
334 	if (limit < 0) {
335 		limit = 0;
336 	}
337 
338 	daybuff[0] = '\0';
339 
340 	db_getdata_range(&datalist, &datainfo, ic->interface.name, listname, (uint32_t)limit, ic->databegin, ic->dataend);
341 
342 	datalist_i = datalist;
343 
344 	if (strlen(ic->dataend) == 0 && datainfo.count > 0 && listtype != LT_Top) {
345 		getestimates(&e_rx, &e_tx, listtype, ic->interface.updated, &datalist);
346 		if ((cfg.estimatestyle > 0 || cfg.barshowsrate > 0) && e_rx + e_tx > datainfo.max) {
347 			datainfo.max = e_rx + e_tx;
348 		}
349 		estimateavailable = 1;
350 		if (listtype == LT_Day || listtype == LT_Month || listtype == LT_Year) {
351 			estimatevisible = 1;
352 		}
353 	}
354 
355 	if (listtype == LT_Top) {
356 		if (limit > 0 && datainfo.count < (uint32_t)limit) {
357 			limit = (int32_t)datainfo.count;
358 		}
359 		if (limit <= 0 || datainfo.count > 999) {
360 			snprintf(titlename, 16, "top");
361 		} else {
362 			snprintf(titlename, 16, "top %d", limit);
363 		}
364 	}
365 
366 	if (listtype == LT_Hour || listtype == LT_5min) {
367 		while (datalist_i != NULL) {
368 			d = localtime(&datalist_i->timestamp);
369 			strftime(datebuff, 16, cfg.dformat, d);
370 			if (strcmp(daybuff, datebuff) != 0) {
371 				rowcount += 1;
372 				strcpy(daybuff, datebuff);
373 			}
374 			datalist_i = datalist_i->next;
375 		}
376 		datalist_i = datalist;
377 		daybuff[0] = '\0';
378 	}
379 	rowcount += datainfo.count;
380 
381 	width = 83 * ic->font->w + 2 + (ic->large * 2);
382 	height = 62 + 3 * ic->lineheight;
383 
384 	// less space needed when no estimate or sum is shown (Top, 5min and Hour never have estimate)
385 	if ((!estimatevisible && datainfo.count < 2) || (listtype == LT_Top || listtype == LT_Hour || listtype == LT_5min)) {
386 		height = 62 + 2 * ic->lineheight;
387 	}
388 
389 	// exception for 5min and Hour when having sum shown
390 	if ((listtype == LT_5min || listtype == LT_Hour) && datainfo.count > 1 && strlen(ic->dataend) > 0) {
391 		height = 62 + 3 * ic->lineheight;
392 	}
393 
394 	height += (ic->lineheight + cfg.linespaceadjust) * rowcount - cfg.linespaceadjust;
395 
396 	// "no data available"
397 	if (!datainfo.count) {
398 		height = 98 + (ic->large * 12);
399 	}
400 
401 	if (!ic->showheader) {
402 		headermod = 26;
403 		height -= 22;
404 	} else {
405 		headermod = 0;
406 	}
407 
408 	snprintf(buffer, 512, " / %s", titlename);
409 
410 	imageinit(ic, width, height);
411 	layoutinit(ic, buffer, width, height);
412 
413 	if (datainfo.count) {
414 		if (listtype == LT_Top) {
415 			if (cfg.ostyle <= 2) {
416 				drawlegend(ic, 66 * ic->font->w + 2, 40 - headermod, 0);
417 			}
418 			current = time(NULL);
419 			d = localtime(&current);
420 			strftime(daybuff, 16, stampformat, d);
421 		} else { // everything else
422 			if (cfg.ostyle > 2) {
423 				if (estimateavailable && cfg.barshowsrate) {
424 					drawlegend(ic, 72 * ic->font->w, 40 - headermod, 1);
425 				} else {
426 					drawlegend(ic, 72 * ic->font->w, 40 - headermod, 0);
427 				}
428 			} else {
429 				drawlegend(ic, 64 * ic->font->w + 1, 40 - headermod, 0);
430 			}
431 		}
432 	}
433 
434 	textx = 10;
435 	texty = 40 - headermod;
436 
437 	if (listtype == LT_Top) { // top
438 		snprintf(buffer, 512, "   #      day        rx           tx          total");
439 	} else { // everything else
440 		snprintf(buffer, 512, " %8s       rx           tx          total", colname);
441 	}
442 	if (cfg.ostyle > 2) {
443 		strcat(buffer, "       avg. rate");
444 		gdImageString(ic->im, ic->font, textx, texty, (unsigned char *)buffer, ic->ctext);
445 		gdImageLine(ic->im, textx + 2, texty + ic->lineheight + 4, textx + (65 * ic->font->w) + offsetx + 2, texty + ic->lineheight + 4, ic->cline);
446 	} else {
447 		gdImageString(ic->im, ic->font, textx, texty, (unsigned char *)buffer, ic->ctext);
448 		gdImageLine(ic->im, textx + 2, texty + ic->lineheight + 4, textx + (50 * ic->font->w) + offsetx - 4, texty + ic->lineheight + 4, ic->cline);
449 	}
450 
451 	texty += ic->lineheight + 8;
452 
453 	if (datainfo.count) {
454 		gdImageLine(ic->im, textx + (24 * ic->font->w) + offsetx, texty - 6 - ic->lineheight, textx + (24 * ic->font->w) + offsetx, texty + ((ic->lineheight + cfg.linespaceadjust) * rowcount) - cfg.linespaceadjust + 5 - (ic->large * 2), ic->cline);
455 		gdImageLine(ic->im, textx + (37 * ic->font->w) + offsetx, texty - 6 - ic->lineheight, textx + (37 * ic->font->w) + offsetx, texty + ((ic->lineheight + cfg.linespaceadjust) * rowcount) - cfg.linespaceadjust + 5 - (ic->large * 2), ic->cline);
456 		if (cfg.ostyle > 2) {
457 			gdImageLine(ic->im, textx + (50 * ic->font->w) + offsetx, texty - 6 - ic->lineheight, textx + (50 * ic->font->w) + offsetx, texty + ((ic->lineheight + cfg.linespaceadjust) * rowcount) - cfg.linespaceadjust + 5 - (ic->large * 2), ic->cline);
458 		}
459 	} else {
460 		gdImageLine(ic->im, textx + (24 * ic->font->w) + offsetx, texty - 6 - ic->lineheight, textx + (24 * ic->font->w) + offsetx, texty - 4, ic->cline);
461 		gdImageLine(ic->im, textx + (37 * ic->font->w) + offsetx, texty - 6 - ic->lineheight, textx + (37 * ic->font->w) + offsetx, texty - 4, ic->cline);
462 		if (cfg.ostyle > 2) {
463 			gdImageLine(ic->im, textx + (50 * ic->font->w) + offsetx, texty - 6 - ic->lineheight, textx + (50 * ic->font->w) + offsetx, texty - 4, ic->cline);
464 		}
465 	}
466 
467 	while (datalist_i != NULL) {
468 		d = localtime(&datalist_i->timestamp);
469 
470 		if (listtype == LT_5min || listtype == LT_Hour) {
471 			strftime(datebuff, 16, cfg.dformat, d);
472 			if (strcmp(daybuff, datebuff) != 0) {
473 				snprintf(buffer, 32, " %s", datebuff);
474 				gdImageString(ic->im, ic->font, textx, texty, (unsigned char *)buffer, ic->ctext);
475 				texty += ic->lineheight + cfg.linespaceadjust;
476 				strcpy(daybuff, datebuff);
477 			}
478 		}
479 
480 		if (listtype == LT_Top) {
481 			if (strftime(datebuff, 16, stampformat, d) <= 8) {
482 				snprintf(buffer, 32, "  %2d   %*s", i, getpadding(8, datebuff), datebuff);
483 				strcat(buffer, "   ");
484 			} else {
485 				snprintf(buffer, 32, "  %2d  %-*s ", i, getpadding(11, datebuff), datebuff);
486 			}
487 			if (strcmp(datebuff, daybuff) == 0) {
488 				if (cfg.ostyle > 2) {
489 					gdImageFilledRectangle(ic->im, textx + 2, texty + 2 - (ic->large * 1), textx + (65 * ic->font->w) + offsetx + 2, texty + 11 + (ic->large * 3), ic->cbgoffset);
490 				} else {
491 					gdImageFilledRectangle(ic->im, textx + 2, texty + 2 - (ic->large * 1), textx + (50 * ic->font->w) + offsetx - 4, texty + 11 + (ic->large * 3), ic->cbgoffset);
492 				}
493 			}
494 		} else {
495 			if (strftime(datebuff, 16, stampformat, d) <= 8) {
496 				snprintf(buffer, 32, "  %*s", getpadding(8, datebuff), datebuff);
497 				strcat(buffer, "   ");
498 			} else {
499 				snprintf(buffer, 32, " %-*s ", getpadding(11, datebuff), datebuff);
500 			}
501 		}
502 		strncat(buffer, getvalue(datalist_i->rx, 10, RT_Normal), 32);
503 		strcat(buffer, "   ");
504 		strncat(buffer, getvalue(datalist_i->tx, 10, RT_Normal), 32);
505 		strcat(buffer, "   ");
506 		strncat(buffer, getvalue(datalist_i->rx + datalist_i->tx, 10, RT_Normal), 32);
507 		if (cfg.ostyle > 2) {
508 			strcat(buffer, "  ");
509 			if (datalist_i->next == NULL && issametimeslot(listtype, datalist_i->timestamp, ic->interface.updated)) {
510 				e_secs = getperiodseconds(listtype, datalist_i->timestamp, ic->interface.updated, 1);
511 			} else {
512 				e_secs = getperiodseconds(listtype, datalist_i->timestamp, ic->interface.updated, 0);
513 			}
514 			strncat(buffer, gettrafficrate(datalist_i->rx + datalist_i->tx, (time_t)e_secs, 14), 32);
515 		}
516 		gdImageString(ic->im, ic->font, textx, texty, (unsigned char *)buffer, ic->ctext);
517 		if (listtype == LT_Top) {
518 			if (cfg.ostyle > 2) {
519 				drawbar(ic, textx + (71 * ic->font->w) + 2, texty + 4, 9 * ic->font->w - 1, datalist_i->rx, datalist_i->tx, datainfo.max, 0);
520 			} else {
521 				drawbar(ic, textx + (56 * ic->font->w), texty + 4, 23 * ic->font->w + 3, datalist_i->rx, datalist_i->tx, datainfo.max, 0);
522 			}
523 		} else { // everything else
524 			if (cfg.ostyle > 2) {
525 				if (datalist_i->next == NULL && estimateavailable && cfg.barshowsrate) {
526 					drawbar(ic, textx + (67 * ic->font->w) - 2, texty + 4, 13 * ic->font->w + 1, e_rx, e_tx, datainfo.max, 0);
527 				} else {
528 					drawbar(ic, textx + (67 * ic->font->w) - 2, texty + 4, 13 * ic->font->w + 1, datalist_i->rx, datalist_i->tx, datainfo.max, 0);
529 				}
530 			} else {
531 				drawbar(ic, textx + (51 * ic->font->w) - 2, texty + 4, 28 * ic->font->w + 3, datalist_i->rx, datalist_i->tx, datainfo.max, 0);
532 			}
533 		}
534 		texty += ic->lineheight + cfg.linespaceadjust;
535 		if (datalist_i->next == NULL) {
536 			texty -= cfg.linespaceadjust;
537 			break;
538 		}
539 		datalist_i = datalist_i->next;
540 		i++;
541 	}
542 
543 	if (!datainfo.count) {
544 		i = 17 * ic->font->w;
545 		if (cfg.ostyle > 2) {
546 			i += 8 * ic->font->w;
547 		}
548 		gdImageString(ic->im, ic->font, textx + i, texty, (unsigned char *)"no data available", ic->ctext);
549 		texty += ic->lineheight;
550 	}
551 
552 	if (cfg.ostyle > 2) {
553 		gdImageLine(ic->im, textx + 2, texty + 5 - (ic->large * 2), textx + (65 * ic->font->w) + offsetx + 2, texty + 5 - (ic->large * 2), ic->cline);
554 	} else {
555 		gdImageLine(ic->im, textx + 2, texty + 5 - (ic->large * 2), textx + (50 * ic->font->w) + offsetx - 4, texty + 5 - (ic->large * 2), ic->cline);
556 	}
557 
558 	buffer[0] = '\0';
559 
560 	/* estimate visible */
561 	if (estimatevisible) {
562 		snprintf(buffer, 32, " estimated   ");
563 		strncat(buffer, getvalue(e_rx, 10, RT_Estimate), 32);
564 		strcat(buffer, "   ");
565 		strncat(buffer, getvalue(e_tx, 10, RT_Estimate), 32);
566 		strcat(buffer, "   ");
567 		strncat(buffer, getvalue(e_rx + e_tx, 10, RT_Estimate), 32);
568 
569 		if (cfg.estimatestyle) {
570 			if (cfg.ostyle > 2) {
571 				drawbar(ic, textx + (67 * ic->font->w) - 2, texty - ic->lineheight + 4, 13 * ic->font->w + 1, e_rx, e_tx, datainfo.max, 1);
572 				drawbar(ic, textx + (67 * ic->font->w) - 2, texty - ic->lineheight + 4, 13 * ic->font->w + 1, datalist_i->rx, datalist_i->tx, datainfo.max, 0);
573 			} else {
574 				drawbar(ic, textx + (51 * ic->font->w) - 2, texty - ic->lineheight + 4, 28 * ic->font->w + 3, e_rx, e_tx, datainfo.max, 1);
575 				drawbar(ic, textx + (51 * ic->font->w) - 2, texty - ic->lineheight + 4, 28 * ic->font->w + 3, datalist_i->rx, datalist_i->tx, datainfo.max, 0);
576 			}
577 		}
578 
579 	/* sum visible */
580 	} else if (strlen(ic->dataend) > 0 && datainfo.count > 1 && listtype != LT_Top) {
581 		if (datainfo.count < 100) {
582 			snprintf(datebuff, 16, "sum of %" PRIu32 "", datainfo.count);
583 		} else {
584 			snprintf(datebuff, 16, "sum");
585 		}
586 		snprintf(buffer, 32, " %9s   ", datebuff);
587 		strncat(buffer, getvalue(datainfo.sumrx, 10, RT_Normal), 32);
588 		strcat(buffer, "   ");
589 		strncat(buffer, getvalue(datainfo.sumtx, 10, RT_Normal), 32);
590 		strcat(buffer, "   ");
591 		strncat(buffer, getvalue(datainfo.sumrx + datainfo.sumtx, 10, RT_Normal), 32);
592 	}
593 
594 	if (strlen(buffer) > 0) {
595 		texty += 8;
596 		gdImageString(ic->im, ic->font, textx, texty, (unsigned char *)buffer, ic->ctext);
597 
598 		gdImageLine(ic->im, textx + (24 * ic->font->w) + offsetx, texty - 6, textx + (24 * ic->font->w) + offsetx, texty + ic->lineheight - (ic->large * 2), ic->cline);
599 		gdImageLine(ic->im, textx + (37 * ic->font->w) + offsetx, texty - 6, textx + (37 * ic->font->w) + offsetx, texty + ic->lineheight - (ic->large * 2), ic->cline);
600 		if (cfg.ostyle > 2) {
601 			gdImageLine(ic->im, textx + (50 * ic->font->w) + offsetx, texty - 6, textx + (50 * ic->font->w) + offsetx, texty + ic->lineheight - (ic->large * 2), ic->cline);
602 		}
603 	}
604 
605 	dbdatalistfree(&datalist);
606 }
607 
drawsummary(IMAGECONTENT * ic,const int layout,const int rate)608 void drawsummary(IMAGECONTENT *ic, const int layout, const int rate)
609 {
610 	int width, height, headermod;
611 
612 	switch (layout) {
613 		// horizontal
614 		case 1:
615 			width = 163 * ic->font->w + 2 + (ic->large * 2);
616 			height = 56 + 12 * ic->lineheight;
617 			break;
618 		// vertical
619 		case 2:
620 			width = 83 * ic->font->w + 2 + (ic->large * 2);
621 			height = 370 + (ic->large * 90);
622 			break;
623 		// no hours
624 		default:
625 			width = 83 * ic->font->w + 2 + (ic->large * 2);
626 			height = 56 + 12 * ic->lineheight;
627 			break;
628 	}
629 
630 	if (!ic->showheader) {
631 		headermod = 26;
632 		height -= 22;
633 	} else {
634 		headermod = 0;
635 	}
636 
637 	imageinit(ic, width, height);
638 	layoutinit(ic, "", width, height);
639 
640 	if (ic->interface.rxtotal == 0 && ic->interface.txtotal == 0) {
641 		gdImageString(ic->im, ic->font, 33 * ic->font->w, 100, (unsigned char *)"no data available", ic->ctext);
642 		return;
643 	}
644 
645 	drawsummary_alltime(ic, 385 + (ic->large * 125), 57 - headermod + (ic->large * 10));
646 	drawlegend(ic, 410 + (ic->large * 132), 155 - headermod + (ic->large * 40), 0);
647 
648 	drawsummary_digest(ic, 100, 30 - headermod, "day");
649 	drawsummary_digest(ic, 100, 29 + 7 * ic->lineheight - headermod, "month");
650 
651 	switch (layout) {
652 		// horizontal
653 		case 1:
654 			if (cfg.summarygraph == 1) {
655 				drawfiveminutes(ic, 496 + (ic->large * 174), height - 30 - (ic->large * 8), rate, 422 + (ic->large * 154), height - 68 + headermod - (ic->large * 8));
656 			} else {
657 				drawhours(ic, 500 + (ic->large * 160), 46 + (ic->large * 40) - headermod, rate);
658 			}
659 			break;
660 		// vertical
661 		case 2:
662 			if (cfg.summarygraph == 1) {
663 				drawfiveminutes(ic, 8 + (ic->large * 14), height - 31 - (ic->large * 6), rate, 422 + (ic->large * 154), 132 + (ic->large * 35));
664 			} else {
665 				drawhours(ic, 12, 215 + (ic->large * 84) - headermod, rate);
666 			}
667 			break;
668 		default:
669 			break;
670 	}
671 }
672 
drawsummary_alltime(IMAGECONTENT * ic,const int x,const int y)673 void drawsummary_alltime(IMAGECONTENT *ic, const int x, const int y)
674 {
675 	struct tm *d;
676 	char buffer[512], datebuff[16], daytemp[32];
677 	gdFontPtr titlefont;
678 
679 	if (ic->large) {
680 		titlefont = gdFontGetGiant();
681 	} else {
682 		titlefont = gdFontGetLarge();
683 	}
684 
685 	gdImageString(ic->im, titlefont, x + 12 + (ic->large * 10), y, (unsigned char *)"all time", ic->ctext);
686 	snprintf(buffer, 4, "rx ");
687 	strncat(buffer, getvalue(ic->interface.rxtotal, 12, RT_Normal), 32);
688 	gdImageString(ic->im, ic->font, x, y + (2 * ic->lineheight), (unsigned char *)buffer, ic->ctext);
689 	snprintf(buffer, 4, "tx ");
690 	strncat(buffer, getvalue(ic->interface.txtotal, 12, RT_Normal), 32);
691 	gdImageString(ic->im, ic->font, x, y + (3 * ic->lineheight), (unsigned char *)buffer, ic->ctext);
692 	snprintf(buffer, 4, " = ");
693 	strncat(buffer, getvalue(ic->interface.rxtotal + ic->interface.txtotal, 12, RT_Normal), 32);
694 	gdImageString(ic->im, ic->font, x, y + (4 * ic->lineheight) + 2 + (ic->large * 4), (unsigned char *)buffer, ic->ctext);
695 	d = localtime(&ic->interface.created);
696 	strftime(datebuff, 16, cfg.tformat, d);
697 	snprintf(daytemp, 24, "since %s", datebuff);
698 	snprintf(buffer, 32, "%23s", daytemp);
699 	gdImageString(ic->im, ic->font, x - 8 * ic->font->w, y + (5 * ic->lineheight) + 10 + (ic->large * 4), (unsigned char *)buffer, ic->ctext);
700 }
701 
drawsummary_digest(IMAGECONTENT * ic,const int x,const int y,const char * mode)702 void drawsummary_digest(IMAGECONTENT *ic, const int x, const int y, const char *mode)
703 {
704 	int textx, texty, offset = 0;
705 	double rxp, txp, mod;
706 	char buffer[512], datebuff[16], daytemp[32];
707 	time_t yesterday;
708 	struct tm *d = NULL;
709 	dbdatalist *datalist = NULL;
710 	dbdatalist *data_current = NULL, *data_previous = NULL;
711 	dbdatalistinfo datainfo;
712 	gdFontPtr titlefont;
713 
714 	if (ic->large) {
715 		titlefont = gdFontGetGiant();
716 	} else {
717 		titlefont = gdFontGetLarge();
718 	}
719 
720 	yesterday = ic->current - 86400;
721 
722 	switch(mode[0]) {
723 		case 'd':
724 			break;
725 		case 'm':
726 			break;
727 		default:
728 			printf("Error: Unsupported mode %s for summary digest\n", mode);
729 			return;
730 	}
731 
732 	if (!db_getdata(&datalist, &datainfo, ic->interface.name, mode, 2) || datalist == NULL) {
733 		gdImageString(ic->im, ic->font, 25 * ic->font->w, y + 30, (unsigned char *)"no data available", ic->ctext);
734 		return;
735 	} else if (datalist->next == NULL) {
736 		data_current = datalist;
737 	} else {
738 		data_previous = datalist;
739 		data_current = datalist->next;
740 	}
741 
742 	/* latest entry */
743 	if (data_current->rx + data_current->tx == 0) {
744 		rxp = txp = 0;
745 	} else {
746 		rxp = (double)data_current->rx / (double)(data_current->rx + data_current->tx) * 100;
747 		txp = (double)100 - rxp;
748 	}
749 
750 	/* do scaling if needed */
751 	if (data_previous != NULL && (data_current->rx + data_current->tx) < (data_previous->rx + data_previous->tx)) {
752 		mod = (double)(data_current->rx + data_current->tx) / (double)(data_previous->rx + data_previous->tx);
753 		rxp = rxp * mod;
754 		txp = txp * mod;
755 	}
756 
757 	/* move graph to center if there's only one to draw for this line */
758 	if (data_previous == NULL) {
759 		offset = 85 + (ic->large * 25);
760 	}
761 
762 	textx = x + offset;
763 	texty = y;
764 
765 	drawdonut(ic, textx + 50 + (ic->large * 40), texty + 45 + (ic->large * 10), (float)rxp, (float)txp, 49 + (ic->large * 10), 15 + (ic->large * 3));
766 
767 	if (mode[0] == 'd') {
768 		/* get formatted date for today */
769 		d = localtime(&ic->current);
770 		strftime(datebuff, 16, cfg.dformat, d);
771 
772 		/* get formatted date for current day in database */
773 		d = localtime(&data_current->timestamp);
774 		strftime(daytemp, 16, cfg.dformat, d);
775 
776 		/* change daytemp to today if formatted days match */
777 		if (strcmp(datebuff, daytemp) == 0) {
778 			strncpy_nt(daytemp, "today", 32);
779 		}
780 	} else if (mode[0] == 'm') {
781 		d = localtime(&data_current->timestamp);
782 		strftime(daytemp, 16, cfg.mformat, d);
783 	}
784 
785 	snprintf(buffer, 32, "%*s", getpadding(12, daytemp), daytemp);
786 	gdImageString(ic->im, titlefont, textx - 54 + (ic->large * (ic->font->w * 3 - 4)), texty - 1, (unsigned char *)buffer, ic->ctext);
787 
788 	if (cfg.summaryrate) {
789 		d = localtime(&ic->interface.updated);
790 		if (mode[0] == 'd') {
791 			snprintf(buffer, 16, "%15s", gettrafficrate(data_current->rx + data_current->tx, d->tm_sec + (d->tm_min * 60) + (d->tm_hour * 3600), 15));
792 		} else if (mode[0] == 'm') {
793 			snprintf(buffer, 16, "%15s", gettrafficrate(data_current->rx + data_current->tx, mosecs(data_current->timestamp, ic->interface.updated), 15));
794 		}
795 		gdImageString(ic->im, ic->font, textx - 74, texty + 4 * ic->lineheight + 10, (unsigned char *)buffer, ic->ctext);
796 	} else {
797 		texty += 7;
798 	}
799 
800 	snprintf(buffer, 4, "rx ");
801 	strncat(buffer, getvalue(data_current->rx, 12, RT_Normal), 32);
802 	gdImageString(ic->im, ic->font, textx - 74, texty + ic->lineheight + 6, (unsigned char *)buffer, ic->ctext);
803 	snprintf(buffer, 4, "tx ");
804 	strncat(buffer, getvalue(data_current->tx, 12, RT_Normal), 32);
805 	gdImageString(ic->im, ic->font, textx - 74, texty + 2 * ic->lineheight + 6, (unsigned char *)buffer, ic->ctext);
806 	snprintf(buffer, 4, " = ");
807 	strncat(buffer, getvalue(data_current->rx + data_current->tx, 12, RT_Normal), 32);
808 	gdImageString(ic->im, ic->font, textx - 74, texty + 3 * ic->lineheight + 8, (unsigned char *)buffer, ic->ctext);
809 
810 	/* previous entry */
811 	if (data_previous != NULL) {
812 		if (data_previous->rx + data_previous->tx == 0) {
813 			rxp = txp = 0;
814 		} else {
815 			rxp = (double)data_previous->rx / (double)(data_previous->rx + data_previous->tx) * 100;
816 			txp = (double)100 - rxp;
817 		}
818 
819 		/* do scaling if needed */
820 		if ((data_previous->rx + data_previous->tx) < (data_current->rx + data_current->tx)) {
821 			mod = (double)(data_previous->rx + data_previous->tx) / (double)(data_current->rx + data_current->tx);
822 			rxp = rxp * mod;
823 			txp = txp * mod;
824 		}
825 
826 		textx += 180 + (ic->large * 60);
827 
828 		drawdonut(ic, textx + 50 + (ic->large * 40), texty + 45 + (ic->large * 10), (float)rxp, (float)txp, 49 + (ic->large * 10), 15 + (ic->large * 3));
829 
830 		if (mode[0] == 'd') {
831 			/* get formatted date for yesterday */
832 			d = localtime(&yesterday);
833 			strftime(datebuff, 16, cfg.dformat, d);
834 
835 			/* get formatted date for previous day in database */
836 			d = localtime(&data_previous->timestamp);
837 			strftime(daytemp, 16, cfg.dformat, d);
838 
839 			/* change daytemp to yesterday if formatted days match */
840 			if (strcmp(datebuff, daytemp) == 0) {
841 				strncpy_nt(daytemp, "yesterday", 32);
842 			}
843 		} else if (mode[0] == 'm') {
844 			d = localtime(&data_previous->timestamp);
845 			strftime(daytemp, 16, cfg.mformat, d);
846 		}
847 
848 		snprintf(buffer, 32, "%*s", getpadding(12, daytemp), daytemp);
849 		gdImageString(ic->im, titlefont, textx - 54 + (ic->large * (ic->font->w * 3 - 4)), texty - 1, (unsigned char *)buffer, ic->ctext);
850 
851 		if (cfg.summaryrate) {
852 			if (mode[0] == 'd') {
853 				snprintf(buffer, 16, "%15s", gettrafficrate(data_previous->rx + data_previous->tx, 86400, 15));
854 			} else if (mode[0] == 'm') {
855 				snprintf(buffer, 16, "%15s", gettrafficrate(data_previous->rx + data_previous->tx, dmonth(d->tm_mon) * 86400, 15));
856 			}
857 			gdImageString(ic->im, ic->font, textx - 74, texty + 4 * ic->lineheight + 10, (unsigned char *)buffer, ic->ctext);
858 		} else {
859 			texty += 7;
860 		}
861 
862 		snprintf(buffer, 4, "rx ");
863 		strncat(buffer, getvalue(data_previous->rx, 12, RT_Normal), 32);
864 		gdImageString(ic->im, ic->font, textx - 74, texty + ic->lineheight + 6, (unsigned char *)buffer, ic->ctext);
865 		snprintf(buffer, 4, "tx ");
866 		strncat(buffer, getvalue(data_previous->tx, 12, RT_Normal), 32);
867 		gdImageString(ic->im, ic->font, textx - 74, texty + 2 * ic->lineheight + 6, (unsigned char *)buffer, ic->ctext);
868 		snprintf(buffer, 4, " = ");
869 		strncat(buffer, getvalue(data_previous->rx + data_previous->tx, 12, RT_Normal), 32);
870 		gdImageString(ic->im, ic->font, textx - 74, texty + 3 * ic->lineheight + 8, (unsigned char *)buffer, ic->ctext);
871 	}
872 
873 	data_current = NULL;
874 	data_previous = NULL;
875 	dbdatalistfree(&datalist);
876 }
877 
drawfivegraph(IMAGECONTENT * ic,const int rate,const int resultcount,const int height)878 void drawfivegraph(IMAGECONTENT *ic, const int rate, const int resultcount, const int height)
879 {
880 	int imagewidth, imageheight = height, headermod = 0;
881 
882 	imagewidth = resultcount + FIVEMINEXTRASPACE + (ic->large * 14);
883 
884 	if (!ic->showheader) {
885 		headermod = 22;
886 	}
887 
888 	imageinit(ic, imagewidth, imageheight);
889 	layoutinit(ic, " / 5 minute", imagewidth, imageheight);
890 
891 	if (drawfiveminutes(ic, 8 + (ic->large * 14), imageheight - 30 - (ic->large * 8), rate, resultcount, imageheight - 68 + headermod - (ic->large * 8))) {
892 		drawlegend(ic, imagewidth / 2 - (ic->large * 10), imageheight - 17 - (ic->large * 2), 0);
893 	}
894 }
895 
drawfiveminutes(IMAGECONTENT * ic,const int xpos,const int ypos,const int rate,const int resultcount,const int height)896 int drawfiveminutes(IMAGECONTENT *ic, const int xpos, const int ypos, const int rate, const int resultcount, const int height)
897 {
898 	int x = xpos, y = ypos, i = 0, t = 0, rxh = 0, txh = 0, step = 0, s = 0, prev = 0;
899 	uint64_t scaleunit, max;
900 	time_t timestamp;
901 	double ratediv, e;
902 	char buffer[32];
903 	struct tm *d;
904 	dbdatalist *datalist = NULL, *datalist_i = NULL;
905 	dbdatalistinfo datainfo;
906 	gdFontPtr font;
907 
908 	if (ic->large) {
909 		font = gdFontGetSmall();
910 	} else {
911 		font = gdFontGetTiny();
912 	}
913 
914 	if (!db_getdata(&datalist, &datainfo, ic->interface.name, "fiveminute", (uint32_t)resultcount) || datainfo.count == 0) {
915 		x = (resultcount + FIVEMINEXTRASPACE + (ic->large * 14)) / 2 - (8 * ic->font->w + ic->font->w / 2);
916 		gdImageString(ic->im, ic->font, x, y - (height / 2) - ic->font->h, (unsigned char *)"no data available", ic->ctext);
917 		return 0;
918 	}
919 
920 	datalist_i = datalist;
921 
922 	if (cfg.rateunit) {
923 		ratediv = 37.5; /* x * 8 / 300 */
924 	} else {
925 		ratediv = 300;
926 	}
927 
928 	/* axis */
929 	x += 36;
930 	gdImageLine(ic->im, x, y, x + (resultcount + FIVEMINWIDTHFULLPADDING), y, ic->ctext);
931 	gdImageLine(ic->im, x + 4, y + 4, x + 4, y - height, ic->ctext);
932 
933 	/* arrows */
934 	drawarrowup(ic, x + 4, y - 1 - height);
935 	drawarrowright(ic, x + 1 + (resultcount + FIVEMINWIDTHFULLPADDING), y);
936 
937 	max = datainfo.maxrx + datainfo.maxtx;
938 
939 	if (datainfo.maxrx == datainfo.maxtx) {
940 		txh = (int)((height - FIVEMINHEIGHTOFFSET * 2) / 2);
941 		rxh = height - FIVEMINHEIGHTOFFSET * 2 - txh;
942 		max = (uint64_t)((double)datainfo.maxrx / ratediv);
943 		t = rxh;
944 	} else if (datainfo.maxrx > datainfo.maxtx) {
945 		txh = (int)lrint(((double)datainfo.maxtx / (double)max) * (height - FIVEMINHEIGHTOFFSET * 2));
946 		rxh = height - FIVEMINHEIGHTOFFSET * 2 - txh;
947 		max = (uint64_t)((double)datainfo.maxrx / ratediv);
948 		t = rxh;
949 	} else {
950 		rxh = (int)lrint(((double)datainfo.maxrx / (double)max) * (height - FIVEMINHEIGHTOFFSET * 2));
951 		txh = height - FIVEMINHEIGHTOFFSET * 2 - rxh;
952 		max = (uint64_t)((double)datainfo.maxtx / ratediv);
953 		t = txh;
954 	}
955 
956 	/* center line */
957 	x += 5;
958 	y -= txh + FIVEMINHEIGHTOFFSET;
959 	gdImageLine(ic->im, x, y, x + (resultcount + FIVEMINWIDTHPADDING), y, ic->ctext);
960 	gdImageString(ic->im, font, x - 21 - (ic->large * 3), y - 4 - (ic->large * 3), (unsigned char *)"  0", ic->ctext);
961 
962 	/* scale values */
963 	scaleunit = getscale(max, rate);
964 
965 	s = (int)lrint(((double)scaleunit / (double)max) * t);
966 	if (s == 0) {
967 		s = 1; // force to show something when there's not much or any traffic, scale is likely to be wrong in this case
968 	}
969 	while (s * step < SCALEMINPIXELS) {
970 		step++;
971 	}
972 
973 	if (debug) {
974 		printf("maxrx: %" PRIu64 "\n", datainfo.maxrx);
975 		printf("maxtx: %" PRIu64 "\n", datainfo.maxtx);
976 		printf("rxh: %d     txh: %d\n", rxh, txh);
977 		printf("max divided: %" PRIu64 "\n", max);
978 		printf("scaleunit:   %" PRIu64 "\nstep: %d\n", scaleunit, step);
979 		printf("pixels per step: %d\n", s);
980 		printf("mintime: %" PRIu64 "\nmaxtime: %" PRIu64 "\n", (uint64_t)datainfo.mintime, (uint64_t)datainfo.maxtime);
981 		printf("count: %u\n", datainfo.count);
982 	}
983 
984 	/* upper part scale values */
985 	y--; // adjust to start above center line
986 	for (i = step; i * s <= rxh; i = i + step) {
987 		gdImageDashedLine(ic->im, x, y - (i * s), x + (resultcount + FIVEMINWIDTHPADDING), y - (i * s), ic->cline);
988 		gdImageDashedLine(ic->im, x, y - prev - (step * s) / 2, x + (resultcount + FIVEMINWIDTHPADDING), y - prev - (step * s) / 2, ic->clinel);
989 		gdImageString(ic->im, font, x - 21 - (ic->large * 3), y - 3 - (i * s) - (ic->large * 3), (unsigned char *)getimagevalue(scaleunit * (unsigned int)i, 3, rate), ic->ctext);
990 		prev = i * s;
991 	}
992 	if ((prev + (step * s) / 2) <= rxh) {
993 		gdImageDashedLine(ic->im, x, y - prev - (step * s) / 2, x + (resultcount + FIVEMINWIDTHPADDING), y - prev - (step * s) / 2, ic->clinel);
994 	}
995 
996 	y += 2; // adjust to start below center line
997 	prev = 0;
998 
999 	/* lower part scale values */
1000 	for (i = step; i * s <= txh; i = i + step) {
1001 		gdImageDashedLine(ic->im, x, y + (i * s), x + (resultcount + FIVEMINWIDTHPADDING), y + (i * s), ic->cline);
1002 		gdImageDashedLine(ic->im, x, y + prev + (step * s) / 2, x + (resultcount + FIVEMINWIDTHPADDING), y + prev + (step * s) / 2, ic->clinel);
1003 		gdImageString(ic->im, font, x - 21 - (ic->large * 3), y - 3 + (i * s) - (ic->large * 3), (unsigned char *)getimagevalue(scaleunit * (unsigned int)i, 3, rate), ic->ctext);
1004 		prev = i * s;
1005 	}
1006 	if ((prev + (step * s) / 2) <= txh) {
1007 		gdImageDashedLine(ic->im, x, y + prev + (step * s) / 2, x + (resultcount + FIVEMINWIDTHPADDING), y + prev + (step * s) / 2, ic->clinel);
1008 	}
1009 
1010 	y--; // y is now back on center line
1011 
1012 	/* scale text */
1013 	gdImageStringUp(ic->im, font, x - 39 - (ic->large * 14), ypos - height / 2 + (rate * 10), (unsigned char *)getimagescale(scaleunit * (unsigned int)step, rate), ic->ctext);
1014 
1015 	timestamp = datainfo.maxtime - (resultcount * 300);
1016 
1017 	while (datalist_i != NULL && datalist_i->timestamp < timestamp + 300) {
1018 		if (debug) {
1019 			printf("Skip data, %" PRIu64 " < %" PRIu64 "\n", (uint64_t)datalist_i->timestamp, (uint64_t)timestamp + 300);
1020 		}
1021 		datalist_i = datalist_i->next;
1022 	}
1023 
1024 	for (i = 0; i < resultcount; i++) {
1025 
1026 		if (datalist_i == NULL) {
1027 			break;
1028 		}
1029 
1030 		timestamp += 300;
1031 		d = localtime(&timestamp);
1032 
1033 		if (d->tm_min == 0 && i > 2) {
1034 			if (d->tm_hour % 2 == 0) {
1035 				if (d->tm_hour == 0) {
1036 					gdImageLine(ic->im, x + i, y + txh - 1 + FIVEMINHEIGHTOFFSET, x + i, y - rxh - 1, ic->cline);
1037 				} else {
1038 					gdImageLine(ic->im, x + i, y + txh - 1 + FIVEMINHEIGHTOFFSET, x + i, y - rxh - 1, ic->cbgoffset);
1039 				}
1040 
1041 				if (i > font->w) {
1042 					snprintf(buffer, 32, "%02d", d->tm_hour);
1043 					if (datalist_i->timestamp > timestamp) {
1044 						gdImageString(ic->im, font, x + i - font->w + 1, y + txh + font->h - (ic->large * 5), (unsigned char *)buffer, ic->cline);
1045 					} else {
1046 						gdImageString(ic->im, font, x + i - font->w + 1, y + txh + font->h - (ic->large * 5), (unsigned char *)buffer, ic->ctext);
1047 					}
1048 				}
1049 			} else {
1050 				gdImageLine(ic->im, x + i, y + txh - 1 + FIVEMINHEIGHTOFFSET, x + i, y - rxh - 1, ic->cbgoffset);
1051 			}
1052 			gdImageSetPixel(ic->im, x + i, y, ic->ctext);
1053 		}
1054 
1055 		if (datalist_i->timestamp > timestamp) {
1056 			gdImageSetPixel(ic->im, x + i, y, ic->cline);
1057 			gdImageSetPixel(ic->im, x + i, y + txh + FIVEMINHEIGHTOFFSET, ic->cline);
1058 			continue;
1059 		}
1060 
1061 		/* only the last entry can be the currently ongoing period that may need scaling */
1062 		if (datalist_i->next == NULL && issametimeslot(LT_5min, datalist_i->timestamp, ic->interface.updated)) {
1063 			e = (double)(ic->interface.updated - datalist_i->timestamp) / (double)300;
1064 			if (e < 0.01) {
1065 				e = 1;
1066 			}
1067 		} else {
1068 			e = 1;
1069 		}
1070 
1071 		t = (int)lrint(((double)datalist_i->rx / e / (double)datainfo.maxrx) * rxh);
1072 		if (t > rxh) {
1073 			t = rxh;
1074 		}
1075 		drawpole(ic, x + i, y - 1, t, 1, ic->crx);
1076 
1077 		t = (int)lrint(((double)datalist_i->tx / e / (double)datainfo.maxtx) * txh);
1078 		if (t > txh) {
1079 			t = txh;
1080 		}
1081 		drawpole(ic, x + i, y + 1, t, 2, ic->ctx);
1082 
1083 		datalist_i = datalist_i->next;
1084 	}
1085 
1086 	dbdatalistfree(&datalist);
1087 
1088 	return 1;
1089 }
1090