From a693082cba4ce80628d46ec887b3afe7da04e837 Mon Sep 17 00:00:00 2001 From: goat1000 Date: Fri, 5 Jun 2015 11:38:02 +0100 Subject: [PATCH] New histogram graph type --- SVGGraphHistogram.php | 143 ++++++++++++++++++++++++++++++++++ SVGGraphMultiScatterGraph.php | 22 +++++- SVGGraphPointGraph.php | 5 +- SVGGraphScatterGraph.php | 26 ++++--- svggraph.ini | 9 +++ 5 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 SVGGraphHistogram.php diff --git a/SVGGraphHistogram.php b/SVGGraphHistogram.php new file mode 100644 index 0000000..c21f47b --- /dev/null +++ b/SVGGraphHistogram.php @@ -0,0 +1,143 @@ +. + */ +/** + * For more information, please contact + */ + +require_once 'SVGGraphBarGraph.php'; + +class Histogram extends BarGraph { + + protected $label_centre = FALSE; + protected $force_assoc = TRUE; + protected $minimum_units_y = 1; + + protected $increment = NULL; + protected $percentage = false; + + + /** + * Process the values + */ + public function Values($values) + { + if(!empty($values)) { + parent::Values($values); + $values = array(); + + // find min, max, strip out nulls + $min = $max = NULL; + foreach($this->values[0] as $item) { + if(!is_null($item->value)) { + if(is_null($min) || $item->value < $min) + $min = $item->value; + if(is_null($max) || $item->value > $max) + $max = $item->value; + $values[] = $item->value; + } + } + + // calculate increment? + if($this->increment <= 0) { + $diff = $max - $min; + if($diff <= 0) { + $inc = 1; + } else { + $inc = pow(10, floor(log10($diff))); + $d1 = $diff / $inc; + if(($inc != 1 || !is_integer($diff)) && $d1 < 4) { + if($d1 < 3) + $inc *= 0.2; + else + $inc *= 0.5; + } + } + $this->increment = $inc; + } + + // prefill the map with nulls + $map = array(); + $start = $this->Interval($min); + $end = $this->Interval($max, true) + $this->increment / 2; + for($i = $start; $i < $end; $i += $this->increment) { + $key = Graph::NumString($i); + $map[$key] = null; + } + + foreach($values as $val) { + $k = Graph::NumString($this->Interval($val)); + if(!array_key_exists($k, $map)) + $map[$k] = 1; + else + $map[$k]++; + } + + if($this->percentage) { + $total = count($values); + $pmap = array(); + foreach($map as $k => $v) + $pmap[$k] = 100 * $v / $total; + $values = $pmap; + } else { + $values = $map; + } + + // turn off structured data + $this->structure = NULL; + $this->structured_data = FALSE; + } + parent::Values($values); + } + + /** + * Returns the start (or next) interval for a value + */ + public function Interval($value, $next = false) + { + $n = floor($value / $this->increment); + if($next) + ++$n; + return $n * $this->increment; + } + + /** + * Sets up the colour class with corrected number of colours + */ + protected function ColourSetup($count, $datasets = NULL) + { + // $count is off by 1 because the divisions are numbered + return parent::ColourSetup($count - 1, $datasets); + } + + /** + * Override because of the shifted numbering + */ + protected function GridPosition($key, $ikey) + { + $position = null; + $zero = -0.01; // catch values close to 0 + $axis = $this->x_axes[$this->main_x_axis]; + $offset = $axis->Zero() + ($axis->Unit() * $ikey); + $g_limit = $this->g_width - ($axis->Unit() / 2); + if($offset >= $zero && floor($offset) <= $g_limit) + $position = $this->pad_left + $offset; + + return $position; + } +} + diff --git a/SVGGraphMultiScatterGraph.php b/SVGGraphMultiScatterGraph.php index e3ab553..9b686d2 100644 --- a/SVGGraphMultiScatterGraph.php +++ b/SVGGraphMultiScatterGraph.php @@ -40,6 +40,7 @@ protected function Draw() $chunk_count = count($this->multi_graph); $this->ColourSetup($this->multi_graph->ItemsCount(-1), $chunk_count); + $best_fit_above = $best_fit_below = ''; for($i = 0; $i < $chunk_count; ++$i) { $bnum = 0; $axis = $this->DatasetYAxis($i); @@ -58,18 +59,35 @@ protected function Draw() // draw the best-fit line for this data set if($this->best_fit) { - $best_fit = $this->ArrayOption($this->best_fit, $i); + $bftype = $this->ArrayOption($this->best_fit, $i); $colour = $this->ArrayOption($this->best_fit_colour, $i); $stroke_width = $this->ArrayOption($this->best_fit_width, $i); $dash = $this->ArrayOption($this->best_fit_dash, $i); - $body .= $this->BestFit($best_fit, $i, $colour, $stroke_width, $dash); + $opacity = $this->ArrayOption($this->best_fit_opacity, $i); + $above = $this->ArrayOption($this->best_fit_above, $i); + + $best_fit = $this->BestFit($bftype, $i, $colour, $stroke_width, $dash, + $opacity); + if($above) + $best_fit_above .= $best_fit; + else + $best_fit_below .= $best_fit; } } + if($this->semantic_classes) { + $cls = array('class' => 'bestfit'); + if(!empty($best_fit_below)) + $best_fit_below = $this->Element('g', $cls, NULL, $best_fit_below); + if(!empty($best_fit_above)) + $best_fit_above = $this->Element('g', $cls, NULL, $best_fit_above); + } + $body .= $best_fit_below; $body .= $this->Guidelines(SVGG_GUIDELINE_ABOVE); $body .= $this->Axes(); $body .= $this->CrossHairs(); $body .= $this->DrawMarkers(); + $body .= $best_fit_above; return $body; } diff --git a/SVGGraphPointGraph.php b/SVGGraphPointGraph.php index a8a22db..0b82554 100644 --- a/SVGGraphPointGraph.php +++ b/SVGGraphPointGraph.php @@ -386,7 +386,8 @@ public function MarkerLabel($dataset, $index, &$item, $x, $y) /** * Find the best fit line for the data points */ - protected function BestFit($type, $dataset, $colour, $stroke_width, $dash) + protected function BestFit($type, $dataset, $colour, $stroke_width, $dash, + $opacity) { // only straight lines supported for now if($type != 'straight') @@ -460,6 +461,8 @@ protected function BestFit($type, $dataset, $colour, $stroke_width, $dash) $path['stroke-width'] = $stroke_width; if(!empty($dash)) $path['stroke-dasharray'] = $dash; + if($opacity != 1) + $path['opacity'] = $opacity; return $this->Element('path', $path); } diff --git a/SVGGraphScatterGraph.php b/SVGGraphScatterGraph.php index 245fc08..828c73a 100644 --- a/SVGGraphScatterGraph.php +++ b/SVGGraphScatterGraph.php @@ -52,21 +52,29 @@ protected function Draw() ++$bnum; } + $best_fit_above = $best_fit_below = ''; if($this->best_fit) { - $best_fit = is_array($this->best_fit) ? $this->best_fit[0] : - $this->best_fit; - $colour = is_array($this->best_fit_colour) ? $this->best_fit_colour[0] : - $this->best_fit_colour; - $stroke_width = is_array($this->best_fit_width) ? - $this->best_fit_width[0] : $this->best_fit_width; - $dash = is_array($this->best_fit_dash) ? - $this->best_fit_dash[0] : $this->best_fit_dash; - $body .= $this->BestFit($best_fit, 0, $colour, $stroke_width, $dash); + $bftype = $this->ArrayOption($this->best_fit, 0); + $colour = $this->ArrayOption($this->best_fit_colour, 0); + $stroke_width = $this->ArrayOption($this->best_fit_width, 0); + $dash = $this->ArrayOption($this->best_fit_dash, 0); + $opacity = $this->ArrayOption($this->best_fit_opacity, 0); + $best_fit = $this->BestFit($bftype, 0, $colour, $stroke_width, $dash, + $opacity); + if($this->semantic_classes) + $best_fit = $this->Element('g', array('class' => 'bestfit'), NULL, + $best_fit); + if($this->ArrayOption($this->best_fit_above, 0)) + $best_fit_above = $best_fit; + else + $best_fit_below = $best_fit; } + $body .= $best_fit_below; $body .= $this->Guidelines(SVGG_GUIDELINE_ABOVE); $body .= $this->Axes(); $body .= $this->CrossHairs(); $body .= $this->DrawMarkers(); + $body .= $best_fit_above; return $body; } diff --git a/svggraph.ini b/svggraph.ini index ae9814d..f1e5cc2 100644 --- a/svggraph.ini +++ b/svggraph.ini @@ -331,6 +331,8 @@ best_fit_line = null best_fit_width = 1 best_fit_colour = "rgb(0,0,0)" best_fit_dash = null +best_fit_opacity = 1 +best_fit_above = false [MultiScatterGraph] scatter_2d = false @@ -338,6 +340,8 @@ best_fit_line = null best_fit_width = 1 best_fit_colour = "rgb(0,0,0)" best_fit_dash = null +best_fit_opacity = 1 +best_fit_above = false [StackedBarGraph] bar_space = 10 @@ -484,3 +488,8 @@ stack_group = null [BarAndLineGraph] line_dataset = 1 +[Histogram] +increment = null +percentage = false +bar_space = 0 +