#include #include #include "chart.h" #include "gdfontl.h" /* Color definitions. These can be changed here. */ #define BACKGROUND 192, 192, 192 #define BORDER 0, 0, 0 #define TEXT 0, 0, 0 #define BAR 255, 64, 64 #define LINE 64, 64, 255 #define CROSS 64, 160, 64 #define TICK 64, 64, 64 /* A module to draw a chart on the specified gd image. The chart will occupy the entire image. */ void chartDraw(gdImagePtr im, chartType t, char **labels, char *units, float *data, int points) { /* Basic colors */ int backgroundColor; int barColor; int lineColor; int crossColor; int borderColor; int textColor; int tickColor; /* Range of data */ float dataMin, dataMax, dataRange; /* Spacing */ int spaceX, spaceY, baseX, baseY; int i; /* Previous pixel, to draw continuous lines */ int lastX, lastY; /* Points for polygons */ gdPoint p[4]; float lastLabel; int first; /* Divisor to use to make the display of units on the Y axis manageable. */ float divisor; /* The size of a Y-axis tick, and an accumulator to progress through ticks. */ float tick, tickAccum; /* First, allocate colors. */ backgroundColor = gdImageColorAllocate(im, BACKGROUND); /* Set this as the transparent color for the GIF. */ gdImageColorTransparent(im, backgroundColor); borderColor = gdImageColorAllocate(im, BORDER); textColor = gdImageColorAllocate(im, TEXT); tickColor = gdImageColorAllocate(im, TICK); if (!points) { /* Nothing to draw! */ return; } if (t & chartBar) { barColor = gdImageColorAllocate(im, BAR); } if (t & chartLine) { lineColor = gdImageColorAllocate(im, LINE); } if (t & chartCross) { crossColor = gdImageColorAllocate(im, CROSS); } /* Second, find the range of the data. */ for (i=0; (i < points); i++) { if ((!i) || (data[i] < dataMin)) { dataMin = data[i]; } if ((!i) || (data[i] > dataMax)) { dataMax = data[i]; } } dataRange = dataMax - dataMin; /* Decide how much space is available. Start with all of it... */ spaceX = gdImageSX(im); /* The left edge is occupied by the Y axis labels and numbers, plus a bit of space for aesthetic purposes... */ baseX = 0; if (units) { baseX += gdFontLarge->h; } baseX += gdFontLarge->h + 2; /* Subtract that space from the space available for the graph... */ spaceX -= baseX; /* Now compute the space available on the Y axis. */ spaceY = gdImageSY(im); spaceY -= (gdFontLarge->h + 2); baseY = 0; /* Make sure we don't divide by zero. */ if (!dataRange) { dataRange = 1.0; } /* Discover how many Y-axis "ticks" are appropriate. There should be at least two and no more than twenty, and the step between ticks should be a power of ten. */ tick = pow(10.0, floor(log10(dataRange / 2.0))); tickAccum = tick * ceil(dataMin / tick); /* Decide the divisor for labels. Powers of 1000 are familiar to the user. */ divisor = 1.0; if (abs(dataMax) > abs(dataMin)) { while (abs(dataMax) / divisor >= 1000.0) { divisor *= 1000.0; } } else { while (abs(dataMin) / divisor >= 1000.0) { divisor *= 1000.0; } } /* Draw the tick lines now, so they will appear "behind" the graph. Also label the ticks as often as is possible without crowding. */ first = 1; while (tickAccum <= dataMax) { int y = spaceY - (spaceY * (tickAccum - dataMin) / dataRange) + baseY; int textY; char s[20]; int w; /* Format the tick label, and see if there is enough space after the previous one to make room for a text label. */ sprintf(s, "%3.2f", tickAccum / divisor); w = gdFontLarge->w * strlen(s); textY = y + w/2; if (first || (textY < lastLabel)) { /* Don't actually draw it unless it fits in the image */ if ((textY - w > baseY) && (textY < (spaceY + baseY))) { gdImageStringUp(im, gdFontLarge, baseX - gdFontLarge->h, textY, s, textColor); } /* The extra two pixels are for aesthetic purposes */ lastLabel = y - w/2 - 2; first = 0; } gdImageLine(im, baseX, y, baseX + spaceX, y, tickColor); tickAccum += tick; } /* Now draw the graph. */ for (i=0; (i < points); i++) { int x, y, w; x = spaceX * (i + 1) / (points + 1) + baseX; y = spaceY - (spaceY * (data[i] - dataMin) / dataRange) + baseY; if (labels) { int label; w = strlen(labels[i]) * gdFontLarge->w; label = x - w / 2; if ((!i) || (label > lastLabel)) { if ((label > 0) && (label + w < baseX + spaceX)) { gdImageString(im, gdFontLarge, label, baseY + spaceY + 2, labels[i], textColor); } lastLabel = x + w / 2 + 2; } } if (t & chartBar) { /* Compute the width, then assemble the points of a polygon. */ w = (spaceX / (points + 1)); /* Reduce the width to two-thirds, making the bars easier to tell apart. */ w = w * 2 / 3; /* Set up the points. */ p[0].x = x - w / 2; p[0].y = y; p[1].x = x + w / 2; p[1].y = y; p[2].x = x + w / 2; p[2].y = baseY + spaceY - 1; p[3].x = x - w / 2; p[3].y = baseY + spaceY - 1; /* Fill the polygon. */ gdImageFilledPolygon(im, p, 4, barColor); } if (t & chartLine) { if (i) { gdImageLine(im, lastX, lastY, x, y, lineColor); } } if (t & chartCross) { gdImageLine(im, x-2, y-2, x+2, y+2, crossColor); gdImageLine(im, x+2, y-2, x-2, y+2, crossColor); } lastX = x; lastY = y; } /* Draw the border lines at left and bottom... */ gdImageLine(im, baseX, baseY, baseX, baseY + spaceY, borderColor); gdImageLine(im, baseX, baseY + spaceY, baseX + spaceX, baseY + spaceY, borderColor); /* Label the Y axis (units) */ if (units) { int h; char label[121]; if (divisor != 1.0) { sprintf(label, "%sx%.0f", units, divisor); } else { sprintf(label, "%s", units); } /* Rotated 90 degrees */ h = gdFontLarge->w * strlen(label); gdImageStringUp(im, gdFontLarge, 0, baseY + (spaceY / 2) + (h / 2), label, textColor); } else { int h; char label[121]; if (divisor != 1.0) { sprintf(label, "x%.0f", divisor); } else { sprintf(label, ""); } /* Rotated 90 degrees */ h = gdFontLarge->w * strlen(label); gdImageStringUp(im, gdFontLarge, 0, baseY + (spaceY / 2) + (h / 2), label, textColor); } }