447 lines
14 KiB
PHP
447 lines
14 KiB
PHP
<?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;
|
|
}
|
|
}
|