mirror of
				https://github.com/Icinga/icingaweb2.git
				synced 2025-10-31 19:34:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			331 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
 | |
| 
 | |
| namespace Icinga\Chart;
 | |
| 
 | |
| use DOMNode;
 | |
| use DOMElement;
 | |
| use DOMDocument;
 | |
| use DOMImplementation;
 | |
| use Icinga\Chart\Render\LayoutBox;
 | |
| use Icinga\Chart\Render\RenderContext;
 | |
| use Icinga\Chart\Primitive\Canvas;
 | |
| 
 | |
| /**
 | |
|  * SVG Renderer component.
 | |
|  *
 | |
|  * Creates the basic DOM tree of the SVG to use
 | |
|  */
 | |
| class SVGRenderer
 | |
| {
 | |
|     const X_ASPECT_RATIO_MIN = 'xMin';
 | |
| 
 | |
|     const X_ASPECT_RATIO_MID = 'xMid';
 | |
| 
 | |
|     const X_ASPECT_RATIO_MAX = 'xMax';
 | |
| 
 | |
|     const Y_ASPECT_RATIO_MIN = 'YMin';
 | |
| 
 | |
|     const Y_ASPECT_RATIO_MID = 'YMid';
 | |
| 
 | |
|     const Y_ASPECT_RATIO_MAX = 'YMax';
 | |
| 
 | |
|     const ASPECT_RATIO_PAD = 'meet';
 | |
| 
 | |
|     const ASPECT_RATIO_CUTOFF = 'slice';
 | |
| 
 | |
|     /**
 | |
|      * The XML-document
 | |
|      *
 | |
|      * @var DOMDocument
 | |
|      */
 | |
|     private $document;
 | |
| 
 | |
|     /**
 | |
|      * The SVG-element
 | |
|      *
 | |
|      * @var DOMNode
 | |
|      */
 | |
|     private $svg;
 | |
| 
 | |
|     /**
 | |
|      * The description of this SVG, useful for screen readers
 | |
|      *
 | |
|      * @var string
 | |
|      */
 | |
|     private $ariaDescription;
 | |
| 
 | |
|     /**
 | |
|      * The title of this SVG, useful for screen readers
 | |
|      *
 | |
|      * @var string
 | |
|      */
 | |
|     private $ariaTitle;
 | |
| 
 | |
|     /**
 | |
|      * The aria role used by this svg element
 | |
|      *
 | |
|      * @var string
 | |
|      */
 | |
|     private $ariaRole = 'img';
 | |
| 
 | |
|     /**
 | |
|      * The root layer for all elements
 | |
|      *
 | |
|      * @var Canvas
 | |
|      */
 | |
|     private $rootCanvas;
 | |
| 
 | |
|     /**
 | |
|      * The width of this renderer
 | |
|      *
 | |
|      * @var int
 | |
|      */
 | |
|     private $width = 100;
 | |
| 
 | |
|     /**
 | |
|      * The height of this renderer
 | |
|      *
 | |
|      * @var int
 | |
|      */
 | |
|     private $height = 100;
 | |
| 
 | |
|     /**
 | |
|      * Whether the aspect ratio is preversed
 | |
|      *
 | |
|      * @var bool
 | |
|      */
 | |
|     private $preserveAspectRatio = false;
 | |
| 
 | |
|     /**
 | |
|      * Horizontal alignment of SVG element
 | |
|      *
 | |
|      * @var string
 | |
|      */
 | |
|     private $xAspectRatio = self::X_ASPECT_RATIO_MID;
 | |
| 
 | |
|     /**
 | |
|      * Vertical alignment of SVG element
 | |
|      *
 | |
|      * @var string
 | |
|      */
 | |
|     private $yAspectRatio = self::Y_ASPECT_RATIO_MID;
 | |
| 
 | |
|     /**
 | |
|      * Define whether aspect differences should be handled using padding (default) or cutoff
 | |
|      *
 | |
|      * @var string
 | |
|      */
 | |
|     private $xFillMode = "meet";
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Create the root document and the SVG root node
 | |
|      */
 | |
|     private function createRootDocument()
 | |
|     {
 | |
|         $implementation = new DOMImplementation();
 | |
|         $docType = $implementation->createDocumentType(
 | |
|             'svg',
 | |
|             '-//W3C//DTD SVG 1.1//EN',
 | |
|             'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
 | |
|         );
 | |
| 
 | |
|         $this->document = $implementation->createDocument(null, null, $docType);
 | |
|         $this->svg = $this->createOuterBox();
 | |
|         $this->document->appendChild($this->svg);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create the outer SVG box  containing the root svg element and namespace and return it
 | |
|      *
 | |
|      * @return DOMElement The SVG root node
 | |
|      */
 | |
|     private function createOuterBox()
 | |
|     {
 | |
|         $ctx = $this->createRenderContext();
 | |
|         $svg = $this->document->createElement('svg');
 | |
|         $svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
 | |
|         $svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
 | |
|         $svg->setAttribute('role', $this->ariaRole);
 | |
|         $svg->setAttribute('width', '100%');
 | |
|         $svg->setAttribute('height', '100%');
 | |
|         $svg->setAttribute(
 | |
|             'viewBox',
 | |
|             sprintf(
 | |
|                 '0 0 %s %s',
 | |
|                 $ctx->getNrOfUnitsX(),
 | |
|                 $ctx->getNrOfUnitsY()
 | |
|             )
 | |
|         );
 | |
|         if ($this->preserveAspectRatio) {
 | |
|             $svg->setAttribute(
 | |
|                 'preserveAspectRatio',
 | |
|                 sprintf(
 | |
|                     '%s%s %s',
 | |
|                     $this->xAspectRatio,
 | |
|                     $this->yAspectRatio,
 | |
|                     $this->xFillMode
 | |
|                 )
 | |
|             );
 | |
|         }
 | |
|         return $svg;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add aria title and description
 | |
|      *
 | |
|      * Adds an aria title and desc element to the given SVG node, which are used to describe this SVG by accessibility
 | |
|      * tools such as screen readers.
 | |
|      *
 | |
|      * @param DOMNode $svg          The SVG DOMNode to which the aria attributes should be attached
 | |
|      * @param         $title        The title text
 | |
|      * @param         $description  The description text
 | |
|      */
 | |
|     private function addAriaDescription(DOMNode $svg, $titleText, $descriptionText)
 | |
|     {
 | |
|         $doc = $svg->ownerDocument;
 | |
| 
 | |
|         $titleId = $descId = '';
 | |
|         if (isset($this->ariaTitle)) {
 | |
|             $titleId = 'aria-title-' . $this->stripNonAlphanumeric($titleText);
 | |
|             $title = $doc->createElement('title');
 | |
|             $title->setAttribute('id', $titleId);
 | |
| 
 | |
|             $title->appendChild($doc->createTextNode($titleText));
 | |
|             $svg->appendChild($title);
 | |
|         }
 | |
| 
 | |
|         if (isset($this->ariaDescription)) {
 | |
|             $descId = 'aria-desc-' . $this->stripNonAlphanumeric($descriptionText);
 | |
|             $desc = $doc->createElement('desc');
 | |
|             $desc->setAttribute('id', $descId);
 | |
| 
 | |
|             $desc->appendChild($doc->createTextNode($descriptionText));
 | |
|             $svg->appendChild($desc);
 | |
|         }
 | |
| 
 | |
|         $svg->setAttribute('aria-labelledby', join(' ', array($titleId, $descId)));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Initialises the XML-document, SVG-element and this figure's root canvas
 | |
|      *
 | |
|      * @param int $width    The width ratio
 | |
|      * @param int $height   The height ratio
 | |
|      */
 | |
|     public function __construct($width, $height)
 | |
|     {
 | |
|         $this->width = $width;
 | |
|         $this->height = $height;
 | |
|         $this->rootCanvas = new Canvas('root', new LayoutBox(0, 0));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Render the SVG-document
 | |
|      *
 | |
|      * @return string The resulting XML structure
 | |
|      */
 | |
|     public function render()
 | |
|     {
 | |
|         $this->createRootDocument();
 | |
|         $ctx = $this->createRenderContext();
 | |
|         $this->addAriaDescription($this->svg, $this->ariaTitle, $this->ariaDescription);
 | |
|         $this->svg->appendChild($this->rootCanvas->toSvg($ctx));
 | |
|         $this->document->formatOutput = true;
 | |
|         return $this->document->saveXML();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a render context that will be used for rendering elements
 | |
|      *
 | |
|      * @return RenderContext The created RenderContext instance
 | |
|      */
 | |
|     public function createRenderContext()
 | |
|     {
 | |
|         return new RenderContext($this->document, $this->width, $this->height);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return the root canvas of this rendered
 | |
|      *
 | |
|      * @return Canvas The canvas that will be the uppermost element in this figure
 | |
|      */
 | |
|     public function getCanvas()
 | |
|     {
 | |
|         return $this->rootCanvas;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Preserve the aspect ratio of the rendered object
 | |
|      *
 | |
|      * Do not deform the content of the SVG when the aspect ratio of the viewBox
 | |
|      * differs from the aspect ratio of the SVG element, but add padding or cutoff
 | |
|      * instead
 | |
|      *
 | |
|      * @param bool $preserve    Whether the aspect ratio should be preserved
 | |
|      */
 | |
|     public function preserveAspectRatio($preserve = true)
 | |
|     {
 | |
|         $this->preserveAspectRatio = $preserve;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Change the horizontal alignment of the SVG element
 | |
|      *
 | |
|      * Change the horizontal alignment of the svg, when preserveAspectRatio is used and
 | |
|      * padding is present. Defaults to
 | |
|      */
 | |
|     public function setXAspectRatioAlignment($alignment)
 | |
|     {
 | |
|         $this->xAspectRatio = $alignment;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Change the vertical alignment of the SVG element
 | |
|      *
 | |
|      * Change the vertical alignment of the svg, when preserveAspectRatio is used and
 | |
|      * padding is present.
 | |
|      */
 | |
|     public function setYAspectRatioAlignment($alignment)
 | |
|     {
 | |
|         $this->yAspectRatio = $alignment;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set the aria description, that is used as a title for this SVG in screen readers
 | |
|      *
 | |
|      * @param $text
 | |
|      */
 | |
|     public function setAriaTitle($text)
 | |
|     {
 | |
|         $this->ariaTitle = $text;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set the aria description, that is used to describe this SVG in screen readers
 | |
|      *
 | |
|      * @param $text
 | |
|      */
 | |
|     public function setAriaDescription($text)
 | |
|     {
 | |
|         $this->ariaDescription = $text;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set the aria role, that is used to describe the purpose of this SVG in screen readers
 | |
|      *
 | |
|      * @param $text
 | |
|      */
 | |
|     public function setAriaRole($text)
 | |
|     {
 | |
|         $this->ariaRole = $text;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     private function stripNonAlphanumeric($str)
 | |
|     {
 | |
|         return preg_replace('/[^A-Za-z]+/', '', $str);
 | |
|     }
 | |
| }
 |