. */ /** * For more information, please contact */ namespace Goat1000\SVGGraph; /** * Creates and stores gradients */ class GradientList { private $graph; private $gradients = []; private $gradient_map = []; public function __construct(&$graph) { $this->graph =& $graph; } /** * Adds a gradient to the list, returning the element ID for use in url */ public function addGradient($colours, $key = null, $radial = false) { if($key === null || !isset($this->gradients[$key])) { if($radial) { // if this is a radial gradient, it must end with 'r' $last = count($colours) - 1; if(strlen($colours[$last]) == 1) $colours[$last] = 'r'; else $colours[] = 'r'; } // find out if this gradient already stored $hash = md5(serialize($colours)); if(isset($this->gradient_map[$hash])) return $this->gradient_map[$hash]; $id = $this->graph->newID(); if($key === null) $key = $id; $this->gradients[$key] = ['id' => $id, 'colours' => $colours]; $this->gradient_map[$hash] = $id; return $id; } return $this->gradients[$key]['id']; } /** * Creates a gradient element */ private function makeGradient($key) { $stops = ''; $direction = 'v'; $type = 'linearGradient'; $colours = $this->gradients[$key]['colours']; $id = $this->gradients[$key]['id']; if(in_array($colours[count($colours)-1], ['h', 'v', 'r'])) $direction = array_pop($colours); if($direction == 'r') { $type = 'radialGradient'; $gradient = ['id' => $id]; } else { $x2 = $direction == 'v' ? 0 : '100%'; $y2 = $direction == 'h' ? 0 : '100%'; $gradient = ['id' => $id, 'x1' => 0, 'x2' => $x2, 'y1' => 0, 'y2' => $y2]; } $segments = $this->decompose($colours); foreach($segments as $segment) { list($offset, $colour, $opacity) = $segment; $stop = ['offset' => $offset . '%', 'stop-color' => $colour]; if(is_numeric($opacity)) $stop['stop-opacity'] = $opacity; $stops .= $this->graph->element('stop', $stop); } return $this->graph->element($type, $gradient, null, $stops); } /** * Calculates a colour component from a gradient step */ private function calcComponent($c0, $c1, $o0, $o1, $d) { return $c0 + ($c1 - $c0) * ($d - $o0) / ($o1 - $o0); } /** * Returns the colour at a position in the gradient */ public function getColour($key, $position) { if(!isset($this->gradients[$key])) return ''; $colours = $this->gradients[$key]['colours']; if(in_array($colours[count($colours)-1], ['h', 'v', 'r'])) $direction = array_pop($colours); $segments = $this->decompose($colours); // function to produce colour string $make_colour = function($r, $g, $b, $o) { $str = "rgb({$r},{$g},{$b})"; if(!is_numeric($o) || $o >= 1) return $str; if($o <= 0) return 'none'; $str .= ':' . $o; return $str; }; $position = min(100, max(0, $position)); $seg0 = $segments[0]; $seg1 = null; foreach($segments as $seg) { if(abs($seg[0] - $position) < 0.1) { // stop colour at or close to requested position $c = $seg[1]->rgb(); $c_string = $make_colour($c[0], $c[1], $c[2], $seg[2]); $colour = new Colour($this->graph, $c_string); return $colour; } if($seg[0] > $position) { $seg1 = $seg; break; } $seg0 = $seg; } if($seg1 === null) { $seg1 = array_pop($segments); $seg1[0] = 100; } $c0 = $seg0[1]->rgb(); $c1 = $seg1[1]->rgb(); $vr = $this->calcComponent($c0[0], $c1[0], $seg0[0], $seg1[0], $position); $vg = $this->calcComponent($c0[1], $c1[1], $seg0[0], $seg1[0], $position); $vb = $this->calcComponent($c0[2], $c1[2], $seg0[0], $seg1[0], $position); $o0 = $seg0[2] === null ? 1 : $seg0[2]; $o1 = $seg1[2] === null ? 1 : $seg1[2]; $vo = $this->calcComponent($o0, $o1, $seg0[0], $seg1[0], $position); $c_string = $make_colour($vr, $vg, $vb, $vo); $colour = new Colour($this->graph, $c_string); return $colour; } /** * Breaks gradient array down into components */ public function decompose($colours) { $col_mul = 100 / (count($colours) - 1); $offset = 0; $decomposed = []; foreach($colours as $pos => $colour) { $opacity = null; $poffset = $pos * $col_mul; if(strpos($colour, ':') !== false) { // opacity, stop offset or both $parts = explode(':', $colour); if(is_numeric($parts[0]) || count($parts) == 3) { $poffset = array_shift($parts); } // stick the other parts back together and let the Colour class // figure it out $colour = new Colour($this->graph, implode(':', $parts)); $opacity = $colour->opacity(); if($opacity == 1) $opacity = null; } else { $colour = new Colour($this->graph, $colour); } // set the offset to the most meaningful number $offset = min(100, max(0, $offset, $poffset)); $decomposed[] = [$offset, $colour, $opacity]; } return $decomposed; } /** * Defines the list of gradients */ public function makeGradients(&$defs) { foreach($this->gradients as $key => $gradient) $defs->add($this->makeGradient($key)); } }