diff options
Diffstat (limited to 'library/Icinga/Chart/GridChart.php')
-rw-r--r-- | library/Icinga/Chart/GridChart.php | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/library/Icinga/Chart/GridChart.php b/library/Icinga/Chart/GridChart.php new file mode 100644 index 0000000..a8cfca6 --- /dev/null +++ b/library/Icinga/Chart/GridChart.php @@ -0,0 +1,446 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Chart; + +use DOMElement; +use Icinga\Chart\Chart; +use Icinga\Chart\Axis; +use Icinga\Chart\Graph\BarGraph; +use Icinga\Chart\Graph\LineGraph; +use Icinga\Chart\Graph\StackedGraph; +use Icinga\Chart\Graph\Tooltip; +use Icinga\Chart\Primitive\Canvas; +use Icinga\Chart\Primitive\Rect; +use Icinga\Chart\Primitive\Path; +use Icinga\Chart\Render\LayoutBox; +use Icinga\Chart\Render\RenderContext; +use Icinga\Chart\Unit\AxisUnit; + +/** + * Base class for grid based charts. + * + * Allows drawing of Line and Barcharts. See the graphing documentation for further details. + * + * Example: + * <pre> + * <code> + * $this->chart = new GridChart(); + * $this->chart->setAxisLabel("X axis label", "Y axis label"); + * $this->chart->setXAxis(Axis::CalendarUnit()); + * $this->chart->drawLines( + * array( + * 'data' => array( + * array(time()-7200, 10),array(time()-3620, 30), array(time()-1800, 15), array(time(), 92)) + * ) + * ); + * </code> + * </pre> + */ +class GridChart extends Chart +{ + /** + * Internal identifier for Line Chart elements + */ + const TYPE_LINE = "LINE"; + + /** + * Internal identifier fo Bar Chart elements + */ + const TYPE_BAR = "BAR"; + + /** + * Internal array containing all elements to be drawn in the order they are drawn + * + * @var array + */ + private $graphs = array(); + + /** + * An associative array containing all axis of this Chart in the "name" => Axis() form. + * + * Currently only the 'default' axis is really supported + * + * @var array + */ + private $axis = array(); + + /** + * An associative array containing all StackedGraph objects used for cumulative graphs + * + * The array key is the 'stack' value given in the graph definitions + * + * @var array + */ + private $stacks = array(); + + /** + * An associative array containing all Tooltips used to render the titles + * + * Each tooltip represents the summary for all y-values of a certain x-value + * in the grid chart + * + * @var Tooltip + */ + private $tooltips = array(); + + public function __construct() + { + $this->title = t('Grid Chart'); + $this->description = t('Contains data in a bar or line chart.'); + parent::__construct(); + } + + /** + * Check if the current dataset has the proper structure for this chart. + * + * Needs to be overwritten by extending classes. The default implementation returns false. + * + * @return bool True when the dataset is valid, otherwise false + */ + public function isValidDataFormat() + { + foreach ($this->graphs as $values) { + foreach ($values as $value) { + if (!isset($value['data']) || !is_array($value['data'])) { + return false; + } + } + } + return true; + } + + /** + * Calls Axis::addDataset for every graph added to this GridChart + * + * @see Axis::addDataset + */ + private function configureAxisFromDatasets() + { + foreach ($this->graphs as $axis => &$graphs) { + $axisObj = $this->axis[$axis]; + foreach ($graphs as &$graph) { + $axisObj->addDataset($graph); + } + } + } + + /** + * Add an arbitrary number of lines to be drawn + * + * Refer to the graphs.md for a detailed list of allowed attributes + * + * @param array $axis,... The line definitions to draw + * + * @return $this Fluid interface + */ + public function drawLines(array $axis) + { + $this->draw(self::TYPE_LINE, func_get_args()); + return $this; + } + + /** + * Add arbitrary number of bars to be drawn + * + * Refer to the graphs.md for a detailed list of allowed attributes + * + * @param array $axis + * @return $this + */ + public function drawBars(array $axis) + { + $this->draw(self::TYPE_BAR, func_get_args()); + return $this; + } + + /** + * Generic method for adding elements to the drawing stack + * + * @param string $type The type of the element to draw (see TYPE_ constants in this class) + * @param array $data The data given to the draw call + */ + private function draw($type, $data) + { + $axisName = 'default'; + if (is_string($data[0])) { + $axisName = $data[0]; + array_shift($data); + } + foreach ($data as &$graph) { + $graph['graphType'] = $type; + if (isset($graph['stack'])) { + if (!isset($this->stacks[$graph['stack']])) { + $this->stacks[$graph['stack']] = new StackedGraph(); + } + $this->stacks[$graph['stack']]->addGraph($graph); + $graph['stack'] = $this->stacks[$graph['stack']]; + } + + if (!isset($graph['color'])) { + $colorType = isset($graph['palette']) ? $graph['palette'] : Palette::NEUTRAL; + $graph['color'] = $this->palette->getNext($colorType); + } + $this->graphs[$axisName][] = $graph; + if ($this->legend) { + $this->legend->addDataset($graph); + } + } + $this->initTooltips($data); + } + + + private function initTooltips($data) + { + foreach ($data as &$graph) { + foreach ($graph['data'] as $x => $point) { + if (!array_key_exists($x, $this->tooltips)) { + $this->tooltips[$x] = new Tooltip( + array( + 'color' => $graph['color'], + + ) + ); + } + $this->tooltips[$x]->addDataPoint($point); + } + } + } + + /** + * Set the label for the x and y axis + * + * @param string $xAxisLabel The label to use for the x axis + * @param string $yAxisLabel The label to use for the y axis + * @param string $axisName The name of the axis, for now 'default' + * + * @return $this Fluid interface + */ + public function setAxisLabel($xAxisLabel, $yAxisLabel, $axisName = 'default') + { + $this->axis[$axisName]->setXLabel($xAxisLabel)->setYLabel($yAxisLabel); + return $this; + } + + /** + * Set the AxisUnit to use for calculating the values of the x axis + * + * @param AxisUnit $unit The unit for the x axis + * @param string $axisName The name of the axis to set the label for, currently only 'default' + * + * @return $this Fluid interface + */ + public function setXAxis(AxisUnit $unit, $axisName = 'default') + { + $this->axis[$axisName]->setUnitForXAxis($unit); + return $this; + } + + /** + * Set the AxisUnit to use for calculating the values of the y axis + * + * @param AxisUnit $unit The unit for the y axis + * @param string $axisName The name of the axis to set the label for, currently only 'default' + * + * @return $this Fluid interface + */ + public function setYAxis(AxisUnit $unit, $axisName = 'default') + { + $this->axis[$axisName]->setUnitForYAxis($unit); + return $this; + } + + /** + * Pre-render setup of the axis + * + * @see Chart::build + */ + protected function build() + { + $this->configureAxisFromDatasets(); + } + + /** + * Initialize the renderer and overwrite it with an 2:1 ration renderer + */ + protected function init() + { + $this->renderer = new SVGRenderer(100, 100); + $this->setAxis(Axis::createLinearAxis()); + } + + /** + * Overwrite the axis to use + * + * @param Axis $axis The new axis to use + * @param string $name The name of the axis, currently only 'default' + * + * @return $this Fluid interface + */ + public function setAxis(Axis $axis, $name = 'default') + { + $this->axis = array($name => $axis); + return $this; + } + + /** + * Add an axis to this graph (not really supported right now) + * + * @param Axis $axis The axis object to add + * @param string $name The name of the axis + * + * @return $this Fluid interface + */ + public function addAxis(Axis $axis, $name) + { + $this->axis[$name] = $axis; + return $this; + } + + /** + * Set minimum values for the x and y axis. + * + * Setting null to an axis means this will use a value determined by the dataset + * + * @param int $xMin The minimum value for the x axis or null to use a dynamic value + * @param int $yMin The minimum value for the y axis or null to use a dynamic value + * @param string $axisName The name of the axis to set the minimum, currently only 'default' + * + * @return $this Fluid interface + */ + public function setAxisMin($xMin = null, $yMin = null, $axisName = 'default') + { + $this->axis[$axisName]->setXMin($xMin)->setYMin($yMin); + return $this; + } + + /** + * Set maximum values for the x and y axis. + * + * Setting null to an axis means this will use a value determined by the dataset + * + * @param int $xMax The maximum value for the x axis or null to use a dynamic value + * @param int $yMax The maximum value for the y axis or null to use a dynamic value + * @param string $axisName The name of the axis to set the maximum, currently only 'default' + * + * @return $this Fluid interface + */ + public function setAxisMax($xMax = null, $yMax = null, $axisName = 'default') + { + $this->axis[$axisName]->setXMax($xMax)->setYMax($yMax); + return $this; + } + + /** + * Render this GridChart to SVG + * + * @param RenderContext $ctx The context to use for rendering + * + * @return DOMElement + */ + public function toSvg(RenderContext $ctx) + { + $outerBox = new Canvas('outerGraph', new LayoutBox(0, 0, 100, 100)); + $innerBox = new Canvas('graph', new LayoutBox(0, 0, 95, 90)); + + $maxPadding = array(0,0,0,0); + foreach ($this->axis as $axis) { + $padding = $axis->getRequiredPadding(); + for ($i=0; $i < count($padding); $i++) { + $maxPadding[$i] = max($maxPadding[$i], $padding[$i]); + } + $innerBox->addElement($axis); + } + $this->renderGraphContent($innerBox); + + $innerBox->getLayout()->setPadding($maxPadding[0], $maxPadding[1], $maxPadding[2], $maxPadding[3]); + $this->createContentClipBox($innerBox); + + $outerBox->addElement($innerBox); + if ($this->legend) { + $outerBox->addElement($this->legend); + } + return $outerBox->toSvg($ctx); + } + + /** + * Create a clip box that defines which area of the graph is drawable and adds it to the graph. + * + * The clipbox has the id '#clip' and can be used in the clip-mask element + * + * @param Canvas $innerBox The inner canvas of the graph to add the clip box to + */ + private function createContentClipBox(Canvas $innerBox) + { + $clipBox = new Canvas('clip', new LayoutBox(0, 0, 100, 100)); + $clipBox->toClipPath(); + $innerBox->addElement($clipBox); + $rect = new Rect(0.1, 0, 100, 99.9); + $clipBox->addElement($rect); + } + + /** + * Render the content of the graph, i.e. the draw stack + * + * @param Canvas $innerBox The inner canvas of the graph to add the content to + */ + private function renderGraphContent(Canvas $innerBox) + { + foreach ($this->graphs as $axisName => $graphs) { + $axis = $this->axis[$axisName]; + $graphObj = null; + foreach ($graphs as $dataset => $graph) { + // determine the type and create a graph object for it + switch ($graph['graphType']) { + case self::TYPE_BAR: + $graphObj = new BarGraph( + $axis->transform($graph['data']), + $graphs, + $dataset, + $this->tooltips + ); + break; + case self::TYPE_LINE: + $graphObj = new LineGraph( + $axis->transform($graph['data']), + $graphs, + $dataset, + $this->tooltips + ); + break; + default: + continue 2; + } + $el = $this->setupGraph($graphObj, $graph); + if ($el) { + $innerBox->addElement($el); + } + } + } + } + + /** + * Setup the provided Graph type + * + * @param mixed $graphObject The graph class, needs the setStyleFromConfig method + * @param array $graphConfig The configration array of the graph + * + * @return mixed Either the graph to be added or null if the graph is not directly added + * to the document (e.g. stacked graphs are added by + * the StackedGraph Composite object) + */ + private function setupGraph($graphObject, array $graphConfig) + { + $graphObject->setStyleFromConfig($graphConfig); + // When in a stack return the StackedGraph object instead of the graphObject + if (isset($graphConfig['stack'])) { + $graphConfig['stack']->addToStack($graphObject); + if (!$graphConfig['stack']->stackEmpty()) { + return $graphConfig['stack']; + } + // return no object when the graph should not be rendered + return null; + } + return $graphObject; + } +} |