Skip to content

Commit

Permalink
Colour, axis and average updates for SVGGraph 3.7.
Browse files Browse the repository at this point in the history
  • Loading branch information
goat1000 committed Nov 27, 2020
1 parent c7ef20a commit d6e38f0
Show file tree
Hide file tree
Showing 34 changed files with 575 additions and 97 deletions.
116 changes: 116 additions & 0 deletions Average.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php
/**
* Copyright (C) 2020 Graham Breach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* For more information, please contact <[email protected]>
*/

namespace Goat1000\SVGGraph;

/**
* Class for average lines (using guidelines)
*/
class Average {

private $graph;
private $lines = [];

public function __construct(&$graph, &$values, $datasets)
{
foreach($datasets as $d) {
if(!$graph->getOption(['show_average', $d]))
continue;

$avg = $this->calculate($values, $d);
if($avg === null)
continue;

$line = [ $avg ];

$title = $this->getTitle($graph, $avg, $d);
if(strlen($title) > 0)
$line[] = $title;

$cg = new ColourGroup($graph, null, 0, $d, 'average_colour');
$line['colour'] = $cg->stroke();

$tc = $graph->getOption(['average_title_colour', $d]);
if(!empty($tc)) {
$cg = new ColourGroup($graph, null, 0, $d, 'average_title_colour');
$line['text_colour'] = $cg->stroke();
}

$sw = new Number($graph->getOption(['average_stroke_width', $d], 1));
$line['stroke_width'] = $sw;

$opts = ["opacity", "above", "dash", "title_align",
"title_angle", "title_opacity", "title_padding", "title_position",
"font", "font_size", "font_adjust", "font_weight",
"length", "length_units"];
foreach($opts as $opt) {
$g_opt = str_replace('title', 'text', $opt);
$line[$g_opt] = $graph->getOption(['average_' . $opt, $d]);
}

// prevent line from changing graph dimensions
$line['no_min_max'] = true;
$this->lines[] = $line;
}

$this->graph =& $graph;
}

/**
* Adds the average lines to the graph's guidelines
*/
public function getGuidelines()
{
if(empty($this->lines))
return;
$guidelines = Guidelines::normalize($this->graph->getOption('guideline'));
$this->graph->setOption('guideline', array_merge($guidelines, $this->lines));
}

/**
* Calculates the mean average for a dataset
*/
protected function calculate(&$values, $dataset)
{
$sum = 0;
$count = 0;
foreach($values[$dataset] as $p) {
if($p->value === null || !is_numeric($p->value))
continue;
$sum += $p->value;
++$count;
}

return $count ? $sum / $count : null;
}

/**
* Returns the average line title
*/
protected function getTitle(&$graph, $avg, $dataset)
{
$tcb = $graph->getOption(['average_title_callback', $dataset]);
if(is_callable($tcb))
return call_user_func($tcb, $dataset, $avg);

return $graph->getOption(['average_title', $dataset]);
}
}
134 changes: 110 additions & 24 deletions Axis.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Axis {
protected $direction = 1;
protected $label_callback = false;
protected $values = false;
protected $tightness = 1;

public function __construct($length, $max_val, $min_val, $min_unit, $min_space,
$fit, $units_before, $units_after, $decimal_digits, $label_callback, $values)
Expand Down Expand Up @@ -78,50 +79,91 @@ public function getLength()
}

/**
* Returns TRUE if the number $n is 'nice'
* Sets the tightness option
*/
private function nice($n)
public function setTightness($t)
{
if(is_integer($n) && ($n % 100 == 0 || $n % 10 == 0 || $n % 5 == 0))
return true;
$this->tightness = $t;
}

/**
* Returns a score for "niceness"
*/
private function nice($n)
{
if($this->min_unit) {
$d = $n / $this->min_unit;
if($d != floor($d))
return false;
return 0;
}

// convert to string
$nn = new Number($n);
$nn->precision = 4;
$nn->precision = 5;
$s = (string)$nn;

// regex is overkill for this
if(strpos($s, '.') === false)
return true;
$good = ['0.1', '0.2', '0.3', '0.4', '0.5', '1.5', '2.5'];
if(in_array($s, $good, true))
return true;
return false;
$niceness = [
'0.1' => 50,
'0.5' => 40,
'0.2' => 25,
'2.5' => 25,
'1.5' => 20,
'0.3' => 10,
'0.4' => 10,
'1' => 100,
'5' => 95,
'2' => 95,
'3' => 45,
'4' => 40,
'6' => 30,
'8' => 20,
'7' => 10,
'9' => 5,
'25' => 95,
'15' => 40,
'75' => 30,
];

$digits = $s;
if(preg_match('/^([1-9]{1,2})(0*)$/', $s, $parts)) {
// integer with one or two non-zero digit
$digits = $parts[1];
} elseif(preg_match('/^0\.(0+)([1-9]{1,2})$/', $s, $parts)) {
// float with leading zeroes
$digits = $parts[2];
}

return isset($niceness[$digits]) ? $niceness[$digits] : 0;
}

/**
* Determine the axis divisions
*/
private function findDivision($length, $min, &$count, &$neg_count, &$magnitude)
{
if($length / $count >= $min)
if($this->tightness && $length / $count >= $min) {
return;
}

$c = $count - 1;
$inc = 0;
$max_inc = $this->fit ? 0 : floor($count / 5);

// $max_inc is how many extra steps the axis can grow by
if($this->fit)
$max_inc = 0;
else
$max_inc = $count / ($this->tightness ? 5 : 2);

$candidates = [];
while($c > 1) {
$m = ($count + $inc) / $c;
$new_magnitude = $m * $magnitude;
$l = $length / $c;
$nc = $neg_count;

$accept = false;
if($this->nice($m) && $l >= $min) {
$niceness = $this->nice($new_magnitude);
if($niceness > 0 && $l >= $min) {
$accept = true;

// negative values mean an extra check
Expand Down Expand Up @@ -152,13 +194,57 @@ private function findDivision($length, $min, &$count, &$neg_count, &$magnitude)
}

if($accept) {
// this division is acceptable, store it
$candidates[] = [
'cost' => ($inc * 1.5) + $m ,
'magnitude' => $magnitude * $m,
'c' => $c,
'neg_count' => $nc,
];
$pos = ($c - $nc) * $new_magnitude;
$neg = $nc * $new_magnitude;
$pos_niceness = $this->nice($pos);
$neg_niceness = $this->nice($neg);

if($this->tightness || $neg_niceness || $pos_niceness) {
// this division is acceptable, cost and store it
$cost = $m;
if($this->tightness) {
$cost += $inc * 1.5;
} else {
// increasing the length is not as costly
$cost += $inc * 0.5;

// reduce cost for nicer divisions
$cost -= $niceness / 50;

// adjust cost for axis ends
if($nc) {
if($neg_niceness) {
$cost -= $neg_niceness / 100;
if($pos_niceness)
$cost -= $pos_niceness / 100;
} else {
// poor choice
$cost += 3;
}
} elseif($pos_niceness) {
$cost -= $pos_niceness / 100;
}
}

$candidate = [
// usort requires ints to work properly
'cost' => intval(1e5 * $cost),
'magnitude' => $new_magnitude,
'count' => $c,
'neg_count' => $nc,

// these are only used for tuning / debugging
'm' => $m,
'real_cost' => $cost,
'max_pos' => $pos,
'max_neg' => $neg,
'nice_mag' => $niceness,
'nice_pos' => $pos_niceness,
'nice_neg' => $neg_niceness,
];

$candidates[] = $candidate;
}
}

if($inc < $max_inc) {
Expand All @@ -177,7 +263,7 @@ private function findDivision($length, $min, &$count, &$neg_count, &$magnitude)
usort($candidates, function($a, $b) { return $a['cost'] - $b['cost']; });
$winner = $candidates[0];
$magnitude = $winner['magnitude'];
$count = $winner['c'];
$count = $winner['count'];
$neg_count = $winner['neg_count'];
}

Expand Down
3 changes: 1 addition & 2 deletions BarAndLineGraph.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Copyright (C) 2015-2019 Graham Breach
* Copyright (C) 2015-2020 Graham Breach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
Expand Down Expand Up @@ -76,7 +76,6 @@ protected function draw()
$y_bottom = min($y_axis_pos, $this->height - $this->pad_bottom);

$this->barSetup();
$this->colourSetup($this->multi_graph->itemsCount(-1), $chunk_count);
$marker_offset = $this->x_axes[$this->main_x_axis]->unit() / 2;

// draw bars, store line points
Expand Down
7 changes: 4 additions & 3 deletions BarGraphTrait.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Copyright (C) 2019 Graham Breach
* Copyright (C) 2019-2020 Graham Breach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
Expand Down Expand Up @@ -32,13 +32,15 @@ trait BarGraphTrait {
*/
protected function draw()
{
$this->setup();

$body = $this->grid();
$body .= $this->underShapes();
$bars = $this->drawBars();
$bar_group = $this->barGroup();
if(!empty($bar_group))
$bars = $this->element('g', $bar_group, null, $bars);

$body .= $this->underShapes();
$body .= $bars;
$body .= $this->overShapes();
$body .= $this->axes();
Expand All @@ -52,7 +54,6 @@ protected function drawBars()
{
$dataset = $this->getOption(['dataset', 0], 0);
$this->barSetup();
$this->colourSetup($this->values->itemsCount($dataset));

$bars = '';
foreach($this->values[$dataset] as $bnum => $item) {
Expand Down
1 change: 0 additions & 1 deletion BoxAndWhiskerGraph.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ protected function draw()
$median_dash = $this->getOption('median_dash');
$median_colour = $this->getOption('median_colour');
$bnum = 0;
$this->colourSetup($this->values->itemsCount($dataset));
$series = '';
foreach($this->values[$dataset] as $item) {
$bar_pos = $this->gridPosition($item, $bnum);
Expand Down
3 changes: 1 addition & 2 deletions BubbleGraph.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Copyright (C) 2013-2019 Graham Breach
* Copyright (C) 2013-2020 Graham Breach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
Expand Down Expand Up @@ -41,7 +41,6 @@ protected function draw()
{
$body = $this->grid() . $this->underShapes();
$dataset = $this->getOption(['dataset', 0], 0);
$this->colourSetup($this->values->itemsCount($dataset));

$bnum = 0;
$y_axis = $this->y_axes[$this->main_y_axis];
Expand Down
9 changes: 9 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
Version 3.7 (27/11/2020)
-----------
- Added options for displaying mean average lines.
- Added background shadow support.
- Added graph subtitle options.
- Added alternative Y-axis fitting algorithm with axis_tightness_y option.
- Updated axis_label_position options to support aligning text with axis ends.
- Updated colourRangeHex functions to support more colour options.

Version 3.6 (19/06/2020)
-----------
- Added support for multi-level axis division labels.
Expand Down
Loading

0 comments on commit d6e38f0

Please sign in to comment.