+ /*
+ * Draws a styled dot. Uses world coordinates.
+ * The list of supported shapes can also be found in SetPointShapes().
+ * All shapes are drawn using a 3x3 grid, centered on the data point.
+ * The center is (x_mid, y_mid) and the corners are (x1, y1) and (x2, y2).
+ * $record is the 0-based index that selects the shape and size.
+ */
+ protected function DrawDot($x_world, $y_world, $record, $color)
+ {
+ $index = $record % $this->point_counts;
+ $point_size = $this->point_sizes[$index];
+
+ $half_point = (int)($point_size / 2);
+
+ $x_mid = $this->xtr($x_world);
+ $y_mid = $this->ytr($y_world);
+
+ $x1 = $x_mid - $half_point;
+ $x2 = $x_mid + $half_point;
+ $y1 = $y_mid - $half_point;
+ $y2 = $y_mid + $half_point;
+
+ switch ($this->point_shapes[$index]) {
+ case 'halfline':
+ ImageLine($this->img, $x1, $y_mid, $x_mid, $y_mid, $color);
+ break;
+ case 'line':
+ ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
+ break;
+ case 'plus':
+ ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
+ ImageLine($this->img, $x_mid, $y1, $x_mid, $y2, $color);
+ break;
+ case 'cross':
+ ImageLine($this->img, $x1, $y1, $x2, $y2, $color);
+ ImageLine($this->img, $x1, $y2, $x2, $y1, $color);
+ break;
+ case 'circle':
+ ImageArc($this->img, $x_mid, $y_mid, $point_size, $point_size, 0, 360, $color);
+ break;
+ case 'dot':
+ ImageFilledEllipse($this->img, $x_mid, $y_mid, $point_size, $point_size, $color);
+ break;
+ case 'diamond':
+ $arrpoints = array( $x1, $y_mid, $x_mid, $y1, $x2, $y_mid, $x_mid, $y2);
+ ImageFilledPolygon($this->img, $arrpoints, 4, $color);
+ break;
+ case 'triangle':
+ $arrpoints = array( $x1, $y_mid, $x2, $y_mid, $x_mid, $y2);
+ ImageFilledPolygon($this->img, $arrpoints, 3, $color);
+ break;
+ case 'trianglemid':
+ $arrpoints = array( $x1, $y1, $x2, $y1, $x_mid, $y_mid);
+ ImageFilledPolygon($this->img, $arrpoints, 3, $color);
+ break;
+ case 'yield':
+ $arrpoints = array( $x1, $y1, $x2, $y1, $x_mid, $y2);
+ ImageFilledPolygon($this->img, $arrpoints, 3, $color);
+ break;
+ case 'delta':
+ $arrpoints = array( $x1, $y2, $x2, $y2, $x_mid, $y1);
+ ImageFilledPolygon($this->img, $arrpoints, 3, $color);
+ break;
+ case 'star':
+ ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
+ ImageLine($this->img, $x_mid, $y1, $x_mid, $y2, $color);
+ ImageLine($this->img, $x1, $y1, $x2, $y2, $color);
+ ImageLine($this->img, $x1, $y2, $x2, $y1, $color);
+ break;
+ case 'hourglass':
+ $arrpoints = array( $x1, $y1, $x2, $y1, $x1, $y2, $x2, $y2);
+ ImageFilledPolygon($this->img, $arrpoints, 4, $color);
+ break;
+ case 'bowtie':
+ $arrpoints = array( $x1, $y1, $x1, $y2, $x2, $y1, $x2, $y2);
+ ImageFilledPolygon($this->img, $arrpoints, 4, $color);
+ break;
+ case 'target':
+ ImageFilledRectangle($this->img, $x1, $y1, $x_mid, $y_mid, $color);
+ ImageFilledRectangle($this->img, $x_mid, $y_mid, $x2, $y2, $color);
+ ImageRectangle($this->img, $x1, $y1, $x2, $y2, $color);
+ break;
+ case 'box':
+ ImageRectangle($this->img, $x1, $y1, $x2, $y2, $color);
+ break;
+ case 'home': /* As in: "home plate" (baseball), also looks sort of like a house. */
+ $arrpoints = array( $x1, $y2, $x2, $y2, $x2, $y_mid, $x_mid, $y1, $x1, $y_mid);
+ ImageFilledPolygon($this->img, $arrpoints, 5, $color);
+ break;
+ case 'up':
+ ImagePolygon($this->img, array($x_mid, $y1, $x2, $y2, $x1, $y2), 3, $color);
+ break;
+ case 'down':
+ ImagePolygon($this->img, array($x_mid, $y2, $x1, $y1, $x2, $y1), 3, $color);
+ break;
+ case 'none': /* Special case, no point shape here */
+ break;
+ default: /* Also 'rect' */
+ ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color);
+ break;
+ }
+ return TRUE;
+ }
+
+ /*
+ * Draw a bar (or segment of a bar), with optional shading or border.
+ * This is used by the bar and stackedbar plots, vertical and horizontal.
+ * $x1, $y1 : One corner of the bar.
+ * $x2, $y2 : Other corner of the bar.
+ * $data_color : Color index to use for the bar fill.
+ * $alt_color : Color index to use for the shading (if shading is on), else for the border.
+ * Note the same color is NOT used for shading and border - just the same argument.
+ * See GetBarColors() for where these arguments come from.
+ * $shade_top : Shade the top? (Suppressed for downward stack segments except first.)
+ * $shade_side : Shade the right side? (Suppressed for leftward stack segments except first.)
+ * Only one of $shade_top or $shade_side can be FALSE. Both default to TRUE.
+ */
+ protected function DrawBar($x1, $y1, $x2, $y2, $data_color, $alt_color,
+ $shade_top = TRUE, $shade_side = TRUE)
+ {
+ // Sort the points so x1,y1 is upper left and x2,y2 is lower right. This
+ // is needed in order to get the shading right, and imagerectangle may require it.
+ if ($x1 > $x2) {
+ $t = $x1; $x1 = $x2; $x2 = $t;
+ }
+ if ($y1 > $y2) {
+ $t = $y1; $y1 = $y2; $y2 = $t;
+ }
+
+ // Draw the bar
+ ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $data_color);
+
+ // Draw a shade, or a border.
+ if (($shade = $this->shading) > 0) {
+ if ($shade_top && $shade_side) {
+ $npts = 6;
+ $pts = array($x1, $y1, $x1 + $shade, $y1 - $shade, $x2 + $shade, $y1 - $shade,
+ $x2 + $shade, $y2 - $shade, $x2, $y2, $x2, $y1);
+ } else {
+ $npts = 4;
+ if ($shade_top) { // Suppress side shading
+ $pts = array($x1, $y1, $x1 + $shade, $y1 - $shade, $x2 + $shade, $y1 - $shade, $x2, $y1);
+ } else { // Suppress top shading
+ $pts = array($x2, $y2, $x2, $y1, $x2 + $shade, $y1 - $shade, $x2 + $shade, $y2 - $shade);
+ }
+ }
+ ImageFilledPolygon($this->img, $pts, $npts, $alt_color);
+ } else {
+ ImageRectangle($this->img, $x1, $y1, $x2,$y2, $alt_color);
+ }
+ }
+
+ /*
+ * Draw an Error Bar set. Used by DrawDotsError and DrawLinesError
+ */
+ protected function DrawYErrorBar($x_world, $y_world, $error_height, $error_bar_type, $color)
+ {
+ $x1 = $this->xtr($x_world);
+ $y1 = $this->ytr($y_world);
+ $y2 = $this->ytr($y_world+$error_height) ;
+
+ ImageSetThickness($this->img, $this->error_bar_line_width);
+ ImageLine($this->img, $x1, $y1 , $x1, $y2, $color);
+ if ($error_bar_type == 'tee') {
+ ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color);
+ }
+ ImageSetThickness($this->img, 1);
+ return TRUE;
+ }
+
+/////////////////////////////////////////////
+//////////////////// PLOT DRAWING
+/////////////////////////////////////////////
+
+ /*
+ * Draws a pie chart. Data is 'text-data', 'data-data', or 'text-data-single'.
+ *
+ * For text-data-single, the data array contains records with an ignored label,
+ * and one Y value. Each record defines a sector of the pie, as a portion of
+ * the sum of all Y values.
+ *
+ * For text-data and data-data, the data array contains records with an ignored label,
+ * an ignored X value (for data-data only), and N (N>=1) Y values per record.
+ * The pie chart will be produced with N segments. The relative size of the first
+ * sector of the pie is the sum of the first Y data value in each record, etc.
+ *
+ * Note: With text-data-single, the data labels could be used, but are not currently.
+ *
+ * If there are no valid data points > 0 at all, just draw nothing. It may seem more correct to
+ * raise an error, but all of the other plot types handle it this way implicitly. DrawGraph
+ * checks for an empty data array, but this is different: a non-empty data array with no Y values,
+ * or all Y=0.
+ */
+ protected function DrawPieChart()
+ {
+ if (!$this->CheckDataType('text-data, text-data-single, data-data'))
+ return FALSE;
+
+ // Allocate dark colors only if they will be used for shading.
+ if ($this->shading > 0)
+ $this->NeedDataDarkColors();
+
+ $xpos = $this->plot_area[0] + $this->plot_area_width/2;
+ $ypos = $this->plot_area[1] + $this->plot_area_height/2;
+ $diameter = min($this->plot_area_width, $this->plot_area_height);
+ $radius = $diameter/2;
+
+ $num_slices = $this->data_columns; // See CheckDataArray which calculates this for us.
+ if ($num_slices < 1) return TRUE; // Give up early if there is no data at all.
+ $sumarr = array_fill(0, $num_slices, 0);
+
+ if ($this->datatype_pie_single) {
+ // text-data-single: One data column per row, one pie slice per row.
+ for ($i = 0; $i < $num_slices; $i++) {
+ // $legend[$i] = $this->data[$i][0]; // Note: Labels are not used yet
+ if (is_numeric($this->data[$i][1]))
+ $sumarr[$i] = abs($this->data[$i][1]);
+ }
+ } else {
+ // text-data: Sum each column (skipping label), one pie slice per column.
+ // data-data: Sum each column (skipping X value and label), one pie slice per column.
+ $skip = ($this->datatype_implied) ? 1 : 2; // Leading values to skip in each row.
+ for ($i = 0; $i < $this->num_data_rows; $i++) {
+ for ($j = $skip; $j < $this->num_recs[$i]; $j++) {
+ if (is_numeric($this->data[$i][$j]))
+ $sumarr[$j-$skip] += abs($this->data[$i][$j]);
+ }
+ }
+ }
+
+ $total = array_sum($sumarr);
+
+ if ($total == 0) {
+ // There are either no valid data points, or all are 0.
+ // See top comment about why not to make this an error.
+ return TRUE;
+ }
+
+ if ($this->shading) {
+ $diam2 = $diameter / 2;
+ } else {
+ $diam2 = $diameter;
+ }
+ $max_data_colors = count($this->ndx_data_colors);
+
+ // Use the Y label format precision, with default value:
+ if (isset($this->label_format['y']['precision']))
+ $precision = $this->label_format['y']['precision'];
+ else
+ $precision = 1;
+
+ for ($h = $this->shading; $h >= 0; $h--) {
+ $color_index = 0;
+ $start_angle = 0;
+ $end_angle = 0;
+ for ($j = 0; $j < $num_slices; $j++) {
+ $val = $sumarr[$j];
+
+ // For shaded pies: the last one (at the top of the "stack") has a brighter color:
+ if ($h == 0)
+ $slicecol = $this->ndx_data_colors[$color_index];
+ else
+ $slicecol = $this->ndx_data_dark_colors[$color_index];
+
+ $label_txt = $this->number_format(($val / $total * 100), $precision) . '%';
+ $val = 360 * ($val / $total);
+
+ // NOTE that imagefilledarc measures angles CLOCKWISE (go figure why),
+ // so the pie chart would start clockwise from 3 o'clock, would it not be
+ // for the reversal of start and end angles in imagefilledarc()
+ // Also note ImageFilledArc only takes angles in integer degrees, and if the
+ // the start and end angles match then you get a full circle not a zero-width
+ // pie. This is bad. So skip any zero-size wedge. On the other hand, we cannot
+ // let cumulative error from rounding to integer result in missing wedges. So
+ // keep the running total as a float, and round the angles. It should not
+ // be necessary to check that the last wedge ends at 360 degrees.
+ $start_angle = $end_angle;
+ $end_angle += $val;
+ // This method of conversion to integer - truncate after reversing it - was
+ // chosen to match the implicit method of PHPlot<=5.0.4 to get the same slices.
+ $arc_start_angle = (int)(360 - $start_angle);
+ $arc_end_angle = (int)(360 - $end_angle);
+
+ if ($arc_start_angle > $arc_end_angle) {
+ $mid_angle = deg2rad($end_angle - ($val / 2));
+
+ // Draw the slice
+ ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
+ $arc_end_angle, $arc_start_angle,
+ $slicecol, IMG_ARC_PIE);
+
+ // Draw the labels only once
+ if ($h == 0) {
+ // Draw the outline
+ if (! $this->shading)
+ ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
+ $arc_end_angle, $arc_start_angle, $this->ndx_grid_color,
+ IMG_ARC_PIE | IMG_ARC_EDGED |IMG_ARC_NOFILL);
+
+ // The '* 1.2' trick is to get labels out of the pie chart so there are more
+ // chances they can be seen in small sectors.
+ $label_x = $xpos + ($diameter * 1.2 * cos($mid_angle)) * $this->label_scale_position;
+ $label_y = $ypos+$h - ($diam2 * 1.2 * sin($mid_angle)) * $this->label_scale_position;
+
+ $this->DrawText($this->fonts['generic'], 0, $label_x, $label_y, $this->ndx_grid_color,
+ $label_txt, 'center', 'center');
+ }
+ }
+ if (++$color_index >= $max_data_colors)
+ $color_index = 0;
+ } // end for
+ } // end for
+ return TRUE;
+ }
+
+ /*
+ * Draw the points and errors bars for an error plot of types points and linepoints
+ * Supports only data-data-error format, with each row of the form
+ * array("title", x, y1, error1+, error1-, y2, error2+, error2-, ...)
+ * This is called from DrawDots, with data type already checked.
+ * $paired is true for linepoints error plots, to make sure elements are
+ * only drawn once. If true, data labels are drawn by DrawLinesError, and error
+ * bars are drawn by DrawDotsError. (This choice is for backwards compatibility.)
+ */
+ protected function DrawDotsError($paired = FALSE)
+ {
+ // Adjust the point shapes and point sizes arrays:
+ $this->CheckPointParams();
+
+ $gcvars = array(); // For GetDataErrorColors, which initializes and uses this.
+ // Special flag for data color callback to indicate the 'points' part of 'linepoints':
+ $alt_flag = $paired ? 1 : 0;
+
+ for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {